generated from coulomb/repo-seed
Service-API completion: ingestion, retrieval, transformations, workflows, actor/delegation context, bounded agent operations, context packages, and dry-run/review-gate contracts
This commit is contained in:
@@ -35,6 +35,84 @@ Implemented in `KONT-WP-0009-T002`:
|
||||
- `GET /api/v1/audit/events`
|
||||
- `POST /api/v1/policy/evaluate`
|
||||
|
||||
Implemented in `KONT-WP-0009-T003`:
|
||||
|
||||
- `GET /api/v1/ingestion/capabilities`
|
||||
- `POST /api/v1/ingestion/jobs`
|
||||
- `GET /api/v1/ingestion/jobs`
|
||||
- `GET /api/v1/ingestion/jobs/{job_id}`
|
||||
- `POST /api/v1/retrieval/index/refresh`
|
||||
- `POST /api/v1/retrieval/assets`
|
||||
- `POST /api/v1/retrieval/context-entities`
|
||||
- `POST /api/v1/retrieval/relationships`
|
||||
- `POST /api/v1/retrieval/feedback`
|
||||
- `GET /api/v1/retrieval/feedback`
|
||||
- `GET /api/v1/retrieval/quality`
|
||||
- `GET /api/v1/transformations/operations`
|
||||
- `POST /api/v1/transformations/runs`
|
||||
- `GET /api/v1/transformations/runs`
|
||||
- `GET /api/v1/transformations/runs/{run_id}`
|
||||
- `POST /api/v1/transformations/runs/{run_id}/retry`
|
||||
- `POST /api/v1/transformations/runs/{run_id}/cancel`
|
||||
- `POST /api/v1/workflows/templates`
|
||||
- `GET /api/v1/workflows/templates`
|
||||
- `GET /api/v1/workflows/templates/{template_id}`
|
||||
- `POST /api/v1/workflows/runs`
|
||||
- `POST /api/v1/workflows/runs/queue`
|
||||
- `GET /api/v1/workflows/runs`
|
||||
- `GET /api/v1/workflows/runs/{run_id}`
|
||||
- `POST /api/v1/workflows/runs/{run_id}/resume`
|
||||
- `POST /api/v1/workflows/runs/{run_id}/retry`
|
||||
- `POST /api/v1/workflows/runs/{run_id}/cancel`
|
||||
- `GET /api/v1/workflows/runs/{run_id}/reconstruction`
|
||||
- `GET /api/v1/workflows/reviews`
|
||||
- `GET /api/v1/workflows/exceptions`
|
||||
- `POST /api/v1/workflows/runs/{run_id}/reviews/{review_id}/decision`
|
||||
|
||||
Implemented in `KONT-WP-0009-T004`:
|
||||
|
||||
- `GET /api/v1/context`
|
||||
- Actor headers: `X-Actor-Id`, `X-Actor-Type`, `X-Actor-Display-Name`,
|
||||
`X-Actor-External-Ref`, `X-Actor-Groups`.
|
||||
- Delegation headers: `X-Delegated-Actor-Id`, `X-Delegated-Actor-Type`,
|
||||
`X-Delegated-Actor-Display-Name`, `X-Delegated-Actor-External-Ref`,
|
||||
`X-Delegated-Actor-Groups`.
|
||||
- Agent headers: `X-Agent-Id`, `X-Agent-Name`, `X-Agent-Run-Id`,
|
||||
`X-Agent-Tool`.
|
||||
- Scope headers: `X-Request-Scope` and `X-Policy-Scope` as JSON objects.
|
||||
- Redacted HTTP authorization error payloads.
|
||||
|
||||
Implemented in `KONT-WP-0009-T005`:
|
||||
|
||||
- `GET /api/v1/agents/operations`
|
||||
- `GET /api/v1/agents/operations/{operation_id}`
|
||||
- `POST /api/v1/agents/operations/{operation_id}`
|
||||
- Catalog entries declare input/output shape notes, permissions, audit
|
||||
operation, failure modes, and dry-run support.
|
||||
- Execution is limited to documented operation IDs and emits separate
|
||||
`agent.operation.*` audit events before dispatching through existing service
|
||||
contracts.
|
||||
|
||||
Implemented in `KONT-WP-0009-T006`:
|
||||
|
||||
- `GET /api/v1/context-packages/schema`
|
||||
- `POST /api/v1/context-packages`
|
||||
- Context packages are assembled from governed retrieval results.
|
||||
- Payloads carry source refs, snippets, metadata, relationships, policy
|
||||
constraints, opaque external memory refs, and audit/policy references.
|
||||
- The `markitect` format emits a Markitect-compatible envelope while keeping
|
||||
markdown rendering and selector semantics delegated to `markitect-tool`.
|
||||
|
||||
Implemented in `KONT-WP-0009-T007`:
|
||||
|
||||
- Agent operation policies can return `require_review` and receive structured
|
||||
`review_required` envelopes with review obligations.
|
||||
- Agent operation policies can return `dry_run_only` and receive
|
||||
`dry_run_required` envelopes unless the request is already a dry run.
|
||||
- Review and dry-run outcomes are audited with explicit `review_required` and
|
||||
`dry_run` audit outcomes.
|
||||
- Partial-failure job envelopes are covered by contract tests.
|
||||
|
||||
The unversioned health/readiness/version endpoints are operational probes. The
|
||||
versioned `/api/v1/*` endpoints establish the MVP API namespace. Future
|
||||
domain-resource endpoints should live under `/api/v1`.
|
||||
@@ -49,19 +127,14 @@ python3 -m pip install -e '.[service]'
|
||||
|
||||
## Planned Resource Shape
|
||||
|
||||
Planned endpoint groups:
|
||||
Remaining planned endpoint groups:
|
||||
|
||||
- `POST /collections`, `GET /collections`, `GET /collections/{id}`
|
||||
- `POST /artifacts`, `GET /artifacts/{id}`, `GET /artifacts`
|
||||
- `POST /relationships`, `GET /relationships`
|
||||
- `POST /ingest`
|
||||
- `POST /query/artifacts`, `POST /query/relationships`
|
||||
- `POST /runs`, `GET /runs/{id}`, `GET /runs/{id}/manifest`
|
||||
- `POST /context/artifact/{id}`
|
||||
|
||||
For the governed asset registry architecture, these planned groups should be
|
||||
translated to assets, metadata, relationships, ingestion jobs, retrieval,
|
||||
transformations, workflow templates/runs, review queues, and reconstruction
|
||||
translated to assets, metadata, context packages, and bounded agent operation
|
||||
resources.
|
||||
|
||||
## MVP API Versioning Policy
|
||||
@@ -85,10 +158,6 @@ resources.
|
||||
|
||||
## Deferred
|
||||
|
||||
- Ingestion, retrieval, transformation, and workflow endpoints.
|
||||
- Actor context, delegation, and authorization middleware.
|
||||
- Agent-safe operation catalog.
|
||||
- Context package API.
|
||||
- Dry-run and review-gate response envelopes for high-impact operations.
|
||||
No MVP service API task remains deferred in this workplan.
|
||||
- Streaming run execution.
|
||||
- Provider-backed assisted steps.
|
||||
|
||||
@@ -8,8 +8,8 @@ Status: active implementation note for `KONT-WP-0009`.
|
||||
|
||||
This note records the first optional FastAPI service adapter. The service layer
|
||||
is intentionally thin: it exposes operational probes, API version metadata, and
|
||||
the first governed asset-resource surface while leaving domain behavior in the
|
||||
application services.
|
||||
the governed asset-resource, job, retrieval, transformation, and workflow
|
||||
surface while leaving domain behavior in the application services.
|
||||
|
||||
## Implemented Package Shape
|
||||
|
||||
@@ -31,6 +31,7 @@ src/kontextual_engine/
|
||||
- `GET /api/v1/health`
|
||||
- `GET /api/v1/ready`
|
||||
- `GET /api/v1/version`
|
||||
- `GET /api/v1/context`
|
||||
- `POST /api/v1/assets`
|
||||
- `GET /api/v1/assets`
|
||||
- `GET /api/v1/assets/{asset_id}`
|
||||
@@ -41,6 +42,42 @@ src/kontextual_engine/
|
||||
- `GET /api/v1/relationships`
|
||||
- `GET /api/v1/audit/events`
|
||||
- `POST /api/v1/policy/evaluate`
|
||||
- `GET /api/v1/ingestion/capabilities`
|
||||
- `POST /api/v1/ingestion/jobs`
|
||||
- `GET /api/v1/ingestion/jobs`
|
||||
- `GET /api/v1/ingestion/jobs/{job_id}`
|
||||
- `POST /api/v1/retrieval/index/refresh`
|
||||
- `POST /api/v1/retrieval/assets`
|
||||
- `POST /api/v1/retrieval/context-entities`
|
||||
- `POST /api/v1/retrieval/relationships`
|
||||
- `POST /api/v1/retrieval/feedback`
|
||||
- `GET /api/v1/retrieval/feedback`
|
||||
- `GET /api/v1/retrieval/quality`
|
||||
- `GET /api/v1/transformations/operations`
|
||||
- `POST /api/v1/transformations/runs`
|
||||
- `GET /api/v1/transformations/runs`
|
||||
- `GET /api/v1/transformations/runs/{run_id}`
|
||||
- `POST /api/v1/transformations/runs/{run_id}/retry`
|
||||
- `POST /api/v1/transformations/runs/{run_id}/cancel`
|
||||
- `POST /api/v1/workflows/templates`
|
||||
- `GET /api/v1/workflows/templates`
|
||||
- `GET /api/v1/workflows/templates/{template_id}`
|
||||
- `POST /api/v1/workflows/runs`
|
||||
- `POST /api/v1/workflows/runs/queue`
|
||||
- `GET /api/v1/workflows/runs`
|
||||
- `GET /api/v1/workflows/runs/{run_id}`
|
||||
- `POST /api/v1/workflows/runs/{run_id}/resume`
|
||||
- `POST /api/v1/workflows/runs/{run_id}/retry`
|
||||
- `POST /api/v1/workflows/runs/{run_id}/cancel`
|
||||
- `GET /api/v1/workflows/runs/{run_id}/reconstruction`
|
||||
- `GET /api/v1/workflows/reviews`
|
||||
- `GET /api/v1/workflows/exceptions`
|
||||
- `POST /api/v1/workflows/runs/{run_id}/reviews/{review_id}/decision`
|
||||
- `GET /api/v1/agents/operations`
|
||||
- `GET /api/v1/agents/operations/{operation_id}`
|
||||
- `POST /api/v1/agents/operations/{operation_id}`
|
||||
- `GET /api/v1/context-packages/schema`
|
||||
- `POST /api/v1/context-packages`
|
||||
- `GET /openapi.json`
|
||||
|
||||
Unversioned endpoints are operational probes. Versioned endpoints establish
|
||||
@@ -59,18 +96,63 @@ the `/api/v1` namespace for future domain resources.
|
||||
- readiness checks,
|
||||
- version payload,
|
||||
- asset service construction,
|
||||
- basic actor/correlation context construction,
|
||||
- actor/correlation/delegation context construction,
|
||||
- asset, metadata, lifecycle, relationship, audit, and policy operation
|
||||
translation.
|
||||
translation,
|
||||
- ingestion job start/list/get translation,
|
||||
- governed retrieval query translation for assets, context entities, and
|
||||
relationships,
|
||||
- transformation operation/run/retry/cancel translation,
|
||||
- workflow template/run/review/exception/reconstruction translation,
|
||||
- bounded agent operation catalog and dispatch translation,
|
||||
- governed context-package assembly translation.
|
||||
|
||||
Readiness currently checks that the configured asset registry repository can
|
||||
list assets. It does not mutate state.
|
||||
|
||||
Asset, metadata, relationship, lifecycle, audit, and policy operations delegate
|
||||
to `AssetRegistryService`, the configured repository, and the configured
|
||||
`PolicyGateway`. Protected mutations therefore keep the existing policy and
|
||||
audit semantics. The current header-to-actor translation is deliberately simple
|
||||
and will be expanded in `KONT-WP-0009-T004`.
|
||||
Asset, metadata, relationship, lifecycle, audit, policy, ingestion, retrieval,
|
||||
transformation, and workflow operations delegate to existing application
|
||||
services, the configured repository, and the configured `PolicyGateway`.
|
||||
Protected mutations and retrieval operations therefore keep the existing policy
|
||||
and audit semantics.
|
||||
|
||||
`KONT-WP-0009-T004` expanded request context parsing. The FastAPI adapter now
|
||||
accepts explicit actor, delegated actor, group, agent, request-scope, and
|
||||
policy-scope headers, and exposes the resulting operation context at
|
||||
`GET /api/v1/context`. Authorization errors are redacted at the HTTP boundary
|
||||
so denied responses keep action, resource, correlation, and public policy
|
||||
decision fields while omitting protected resource metadata from policy context.
|
||||
|
||||
Job and run responses include correlation IDs, state, output references,
|
||||
failures or diagnostics, and compact retry/cancel hints where the runtime can
|
||||
derive them. Retrieval responses remain permission-filtered and source-grounded
|
||||
through source references, representations, snippets, relationships, and
|
||||
retrieval audit events. Transformation and workflow responses expose lineage,
|
||||
audit events, run reconstruction, review tasks, and exception queues from the
|
||||
underlying core services.
|
||||
|
||||
`KONT-WP-0009-T005` added a bounded agent operation catalog. Agents can list
|
||||
documented operations and execute only those operation IDs. Each catalog entry
|
||||
declares required permissions, input/output shape notes, audit operation,
|
||||
failure modes, and dry-run support. Execution first authorizes and audits
|
||||
`agent.operation.*`, then dispatches through existing runtime operations such
|
||||
as retrieval, metadata enrichment, transformation, workflow invocation, review
|
||||
submission, or result reporting.
|
||||
|
||||
`KONT-WP-0009-T006` added a governed context-package assembly API. Packages are
|
||||
assembled from permission-filtered retrieval results rather than unrestricted
|
||||
repository reads. The payload carries selected assets, snippets, metadata,
|
||||
relationships, representations, source references, policy constraints, opaque
|
||||
external memory refs, and an audit event. The `markitect` format adds a
|
||||
Markitect-compatible payload envelope while keeping markdown rendering,
|
||||
selector semantics, and contract checks delegated to `markitect-tool`.
|
||||
|
||||
`KONT-WP-0009-T007` added explicit review/dry-run response envelopes for agent
|
||||
operation policy decisions. `require_review` decisions now return
|
||||
`review_required` envelopes with review obligations and `review_required` audit
|
||||
outcomes. `dry_run_only` decisions return `dry_run_required` envelopes unless
|
||||
the request is already a dry run. Partial-failure contracts are covered through
|
||||
directory ingestion with mixed supported and unsupported inputs.
|
||||
|
||||
## Dependency Boundary
|
||||
|
||||
@@ -96,6 +178,18 @@ missing-dependency behavior are tested without FastAPI.
|
||||
- runtime relationship create/list operations,
|
||||
- runtime audit query and policy evaluation,
|
||||
- runtime policy denial blocking a protected operation,
|
||||
- runtime actor context with delegated actors and AI-agent identity,
|
||||
- redacted API authorization error payloads,
|
||||
- runtime ingestion jobs with completed and failed job envelopes,
|
||||
- runtime source-grounded retrieval with snippets,
|
||||
- runtime transformation runs with lineage and audit references,
|
||||
- runtime workflow template/run/reconstruction contracts,
|
||||
- runtime bounded agent operation catalog, dry-run behavior, dispatch, and
|
||||
separate agent audit events,
|
||||
- runtime context-package assembly with source-grounded results, opaque memory
|
||||
references, and Markitect-compatible payload shape,
|
||||
- runtime review-required and dry-run-only agent operation envelopes,
|
||||
- runtime partial-failure ingestion job envelopes,
|
||||
- `create_app()` missing-dependency behavior when the optional extra is absent,
|
||||
- health/readiness/version/OpenAPI endpoint contracts when FastAPI and HTTPX
|
||||
are installed,
|
||||
@@ -104,9 +198,4 @@ missing-dependency behavior are tested without FastAPI.
|
||||
|
||||
## Not Yet Implemented
|
||||
|
||||
- Ingestion, retrieval, transformation, workflow, review, and reconstruction
|
||||
endpoints.
|
||||
- Request actor context and delegation middleware.
|
||||
- Bounded agent operation catalog.
|
||||
- Context package API.
|
||||
- Dry-run and review-gate response envelopes.
|
||||
No MVP service API tasks remain open in `KONT-WP-0009`.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,16 @@
|
||||
import pytest
|
||||
|
||||
from kontextual_engine import AuthorizationError, OperationContext, PolicyDecision, ServiceRuntime, create_app
|
||||
from kontextual_engine import (
|
||||
AuthorizationError,
|
||||
OperationContext,
|
||||
PolicyDecision,
|
||||
PolicyEffect,
|
||||
ServiceRuntime,
|
||||
ValidationError,
|
||||
create_app,
|
||||
)
|
||||
from kontextual_engine.adapters.memory import InMemoryAssetRegistryRepository
|
||||
from kontextual_engine.api.app import _authorization_error_payload
|
||||
|
||||
|
||||
def test_service_runtime_health_readiness_and_version_are_importable_without_fastapi() -> None:
|
||||
@@ -115,6 +124,399 @@ def test_service_runtime_policy_denial_blocks_protected_asset_operation() -> Non
|
||||
assert runtime.list_audit_events(target="asset:asset-denied")["items"][0]["outcome"] == "denied"
|
||||
|
||||
|
||||
def test_service_runtime_exposes_ingestion_retrieval_transformation_and_workflow_operations(
|
||||
tmp_path,
|
||||
) -> None:
|
||||
runtime = ServiceRuntime(repository=InMemoryAssetRegistryRepository())
|
||||
context = runtime.operation_context(actor_id="user-api", correlation_id="corr-jobs")
|
||||
source = tmp_path / "kontextual-source.txt"
|
||||
source.write_text(
|
||||
"Kontextual engine captures markdown proxy context, system boundaries, and source-grounded retrieval.",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
ingestion = runtime.start_ingestion_job(
|
||||
{
|
||||
"mode": "file",
|
||||
"path": str(source),
|
||||
"asset_id": "asset-ingested",
|
||||
"classification": {
|
||||
"asset_type": "document",
|
||||
"sensitivity": "internal",
|
||||
"owner": "Platform Knowledge",
|
||||
"topics": ["retrieval", "workflow"],
|
||||
},
|
||||
},
|
||||
context,
|
||||
)
|
||||
retrieval = runtime.query_assets(
|
||||
{
|
||||
"text": "proxy context",
|
||||
"include_snippets": True,
|
||||
"representation_kind": "normalized",
|
||||
"limit": 5,
|
||||
},
|
||||
context,
|
||||
)
|
||||
transformation = runtime.execute_transformation(
|
||||
{
|
||||
"operation_id": "structured_view",
|
||||
"source_asset_ids": ["asset-ingested"],
|
||||
"output_asset_id": "asset-derived",
|
||||
"output_title": "Structured API View",
|
||||
"metadata": {"request": "service-api-test"},
|
||||
},
|
||||
context,
|
||||
)
|
||||
template = runtime.register_workflow_template(
|
||||
{
|
||||
"template_id": "workflow-structured",
|
||||
"name": "Structured Workflow",
|
||||
"inputs": [{"name": "source_asset_ids", "kind": "asset"}],
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "build",
|
||||
"kind": "transformation",
|
||||
"operation_id": "structured_view",
|
||||
"inputs": {"source_asset_ids": "$inputs.source_asset_ids"},
|
||||
"outputs": {
|
||||
"asset_id": "asset-workflow-structured",
|
||||
"title": "Workflow Structured View",
|
||||
"asset_type": "derived_artifact",
|
||||
"media_type": "application/json",
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
context,
|
||||
)
|
||||
workflow = runtime.invoke_workflow_run(
|
||||
{
|
||||
"template_id": template["template_id"],
|
||||
"inputs": {"source_asset_ids": ["asset-ingested"]},
|
||||
},
|
||||
context,
|
||||
)
|
||||
reconstruction = runtime.reconstruct_workflow_run(workflow["run"]["run_id"])
|
||||
|
||||
assert ingestion["status"] == "completed"
|
||||
assert ingestion["correlation_id"] == "corr-jobs"
|
||||
assert ingestion["output_asset_ids"] == ["asset-ingested"]
|
||||
assert ingestion["retry_options"]["retryable"] is False
|
||||
assert ingestion["job"]["partial_results"]["action"] == "created"
|
||||
assert runtime.ingestion_capabilities()["connectors"][0]["supports_directories"] is True
|
||||
|
||||
assert retrieval["success"] is True
|
||||
assert retrieval["correlation_id"] == "corr-jobs"
|
||||
assert retrieval["results"][0]["asset_id"] == "asset-ingested"
|
||||
assert retrieval["results"][0]["source_refs"]
|
||||
assert retrieval["results"][0]["snippets"][0]["source_ref_id"]
|
||||
assert retrieval["metadata"]["policy_enforced"] is True
|
||||
|
||||
assert transformation["success"] is True
|
||||
assert transformation["run"]["status"] == "completed"
|
||||
assert transformation["run"]["output_asset_ids"] == ["asset-derived"]
|
||||
assert transformation["lineage"]["source_asset_ids"] == ["asset-ingested"]
|
||||
assert transformation["audit_event"]["operation"] == "transformation.run.completed"
|
||||
assert transformation["retry_options"]["retryable"] is False
|
||||
assert runtime.get_transformation_run(transformation["run"]["run_id"])["status"] == "completed"
|
||||
assert runtime.list_transformation_operations()["count"] >= 1
|
||||
assert runtime.list_transformation_runs(status="completed")["count"] >= 2
|
||||
|
||||
assert workflow["success"] is True
|
||||
assert workflow["run"]["status"] == "completed"
|
||||
assert workflow["run"]["output_asset_ids"] == ["asset-workflow-structured"]
|
||||
assert workflow["retry_options"]["retryable"] is False
|
||||
assert runtime.get_workflow_run(workflow["run"]["run_id"])["status"] == "completed"
|
||||
assert runtime.list_workflow_runs(status="completed")["count"] == 1
|
||||
assert runtime.list_workflow_review_tasks(workflow_run_id=workflow["run"]["run_id"])["count"] == 0
|
||||
assert runtime.list_workflow_exceptions(workflow_run_id=workflow["run"]["run_id"])["count"] == 0
|
||||
assert reconstruction["run"]["run_id"] == workflow["run"]["run_id"]
|
||||
assert reconstruction["template"]["template_id"] == "workflow-structured"
|
||||
assert reconstruction["transformation_runs"][0]["status"] == "completed"
|
||||
assert reconstruction["derived_lineage"][0]["output_asset_id"] == "asset-workflow-structured"
|
||||
|
||||
|
||||
def test_service_runtime_exposes_failed_ingestion_job_state_and_recovery_envelope(tmp_path) -> None:
|
||||
runtime = ServiceRuntime(repository=InMemoryAssetRegistryRepository())
|
||||
context = runtime.operation_context(actor_id="user-api", correlation_id="corr-failed-job")
|
||||
|
||||
failed = runtime.start_ingestion_job(
|
||||
{"mode": "file", "path": str(tmp_path / "missing.txt")},
|
||||
context,
|
||||
)
|
||||
listed = runtime.list_ingestion_jobs(status="failed")
|
||||
|
||||
assert failed["status"] == "failed"
|
||||
assert failed["correlation_id"] == "corr-failed-job"
|
||||
assert failed["failures"][0]["code"] == "kontextual.not_found"
|
||||
assert failed["retry_options"]["retryable"] is False
|
||||
assert runtime.get_ingestion_job(failed["job_id"])["job_id"] == failed["job_id"]
|
||||
assert listed["count"] == 1
|
||||
assert listed["items"][0]["job_id"] == failed["job_id"]
|
||||
|
||||
|
||||
def test_service_runtime_operation_context_represents_delegation_and_agent_identity() -> None:
|
||||
runtime = ServiceRuntime(repository=InMemoryAssetRegistryRepository())
|
||||
|
||||
context = runtime.operation_context(
|
||||
actor_id="agent-codex",
|
||||
actor_type="ai_agent",
|
||||
display_name="Codex",
|
||||
external_ref="agent://codex",
|
||||
groups=["automation", "engineering"],
|
||||
delegated_actor_id="user-owner",
|
||||
delegated_actor_type="human",
|
||||
delegated_actor_display_name="Owner",
|
||||
delegated_actor_groups=["knowledge-owners"],
|
||||
request_scope={"tenant": "tenant-a", "surface": "service-api"},
|
||||
policy_scope={"allowed_sensitivity": "internal"},
|
||||
agent_id="agent-codex",
|
||||
agent_name="Codex",
|
||||
agent_run_id="run-123",
|
||||
agent_tool="service-api",
|
||||
correlation_id="corr-delegated",
|
||||
)
|
||||
payload = context.to_dict()
|
||||
|
||||
assert payload["actor"]["id"] == "agent-codex"
|
||||
assert payload["actor"]["actor_type"] == "ai_agent"
|
||||
assert payload["actor"]["groups"] == ["automation", "engineering"]
|
||||
assert payload["delegated_actor"]["id"] == "user-owner"
|
||||
assert payload["delegated_actor"]["actor_type"] == "human"
|
||||
assert payload["request_scope"]["tenant"] == "tenant-a"
|
||||
assert payload["policy_scope"]["allowed_sensitivity"] == "internal"
|
||||
assert payload["metadata"]["agent"]["agent_run_id"] == "run-123"
|
||||
assert payload["metadata"]["delegation"]["delegated_actor_id"] == "user-owner"
|
||||
|
||||
|
||||
def test_service_api_authorization_error_payload_redacts_resource_metadata() -> None:
|
||||
runtime = ServiceRuntime(
|
||||
repository=InMemoryAssetRegistryRepository(),
|
||||
policy_gateway=DenyCreatePolicy(),
|
||||
)
|
||||
|
||||
with pytest.raises(AuthorizationError) as exc_info:
|
||||
runtime.create_asset(
|
||||
{
|
||||
"asset_id": "asset-secret",
|
||||
"title": "Protected Title",
|
||||
"classification": {
|
||||
"asset_type": "document",
|
||||
"sensitivity": "restricted",
|
||||
"metadata": {"secret": "do-not-echo"},
|
||||
},
|
||||
},
|
||||
runtime.operation_context(actor_id="user-denied"),
|
||||
)
|
||||
|
||||
payload = _authorization_error_payload(exc_info.value)
|
||||
|
||||
assert payload["details"]["policy_decision"]["effect"] == "deny"
|
||||
assert "resource_metadata" not in payload["details"]["policy_decision"].get("context", {})
|
||||
assert "Protected Title" not in str(payload)
|
||||
assert "do-not-echo" not in str(payload)
|
||||
|
||||
|
||||
def test_service_runtime_exposes_bounded_agent_operation_catalog_and_dispatch() -> None:
|
||||
runtime = ServiceRuntime(repository=InMemoryAssetRegistryRepository())
|
||||
context = runtime.operation_context(
|
||||
actor_id="agent-codex",
|
||||
actor_type="ai_agent",
|
||||
agent_id="agent-codex",
|
||||
agent_run_id="run-agent-1",
|
||||
correlation_id="corr-agent",
|
||||
)
|
||||
runtime.create_asset(
|
||||
{
|
||||
"asset_id": "asset-agent",
|
||||
"title": "Agent Visible Asset",
|
||||
"classification": {"asset_type": "document", "sensitivity": "internal"},
|
||||
"source_refs": [{"source_system": "test", "path": "agent.md"}],
|
||||
},
|
||||
context,
|
||||
)
|
||||
|
||||
catalog = runtime.list_agent_operations()
|
||||
dry_run = runtime.execute_agent_operation(
|
||||
"enrich_metadata",
|
||||
{
|
||||
"dry_run": True,
|
||||
"payload": {"asset_id": "asset-agent", "metadata": {"key": "agent_note", "value": "planned"}},
|
||||
},
|
||||
context,
|
||||
)
|
||||
search = runtime.execute_agent_operation(
|
||||
"search_assets",
|
||||
{"query": {"asset_type": "document", "limit": 5}},
|
||||
context,
|
||||
)
|
||||
bundle = runtime.execute_agent_operation(
|
||||
"retrieve_asset",
|
||||
{"asset_id": "asset-agent"},
|
||||
context,
|
||||
)
|
||||
report = runtime.execute_agent_operation(
|
||||
"report_result",
|
||||
{"summary": "Agent operation completed", "result_ref": {"asset_id": "asset-agent"}},
|
||||
context,
|
||||
)
|
||||
|
||||
assert catalog["count"] >= 10
|
||||
assert runtime.get_agent_operation("search_assets")["audit_operation"] == "agent.operation.search_assets"
|
||||
assert all(item["required_permissions"] for item in catalog["items"])
|
||||
assert all(item["failure_modes"] for item in catalog["items"])
|
||||
assert dry_run["dry_run"] is True
|
||||
assert runtime.list_metadata_records("asset-agent")["count"] == 0
|
||||
assert search["success"] is True
|
||||
assert search["result"]["results"][0]["asset_id"] == "asset-agent"
|
||||
assert search["audit_event"]["operation"] == "agent.operation.search_assets"
|
||||
assert bundle["result"]["asset"]["id"] == "asset-agent"
|
||||
assert bundle["result"]["source_grounded"] is True
|
||||
assert report["result"]["audit_event"]["operation"] == "agent.report.recorded"
|
||||
assert runtime.list_audit_events(target="agent_operation:search_assets")["count"] == 2
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
runtime.execute_agent_operation("shell", {"command": "echo no"}, context)
|
||||
|
||||
|
||||
def test_service_runtime_assembles_source_grounded_context_package_with_opaque_memory_refs(
|
||||
tmp_path,
|
||||
) -> None:
|
||||
runtime = ServiceRuntime(repository=InMemoryAssetRegistryRepository())
|
||||
context = runtime.operation_context(actor_id="agent-codex", actor_type="ai_agent", correlation_id="corr-context")
|
||||
source = tmp_path / "context-source.txt"
|
||||
source.write_text(
|
||||
"Markdown-backed context packages should remain source grounded.",
|
||||
encoding="utf-8",
|
||||
)
|
||||
runtime.start_ingestion_job(
|
||||
{
|
||||
"mode": "file",
|
||||
"path": str(source),
|
||||
"asset_id": "asset-context",
|
||||
"classification": {"asset_type": "markdown", "sensitivity": "internal"},
|
||||
},
|
||||
context,
|
||||
)
|
||||
|
||||
package = runtime.assemble_context_package(
|
||||
{
|
||||
"package_id": "ctxpkg-test",
|
||||
"title": "Context Package",
|
||||
"intent": "Support implementation planning.",
|
||||
"format": "markitect",
|
||||
"query": {
|
||||
"text": "source grounded",
|
||||
"representation_kind": "normalized",
|
||||
"include_snippets": True,
|
||||
},
|
||||
"constraints": {"max_sensitivity": "internal", "no_external_publish": True},
|
||||
"external_memory_refs": [
|
||||
{
|
||||
"ref_id": "phase-memory:episode-1",
|
||||
"system": "phase-memory",
|
||||
"content": "memory graph detail must not be embedded",
|
||||
}
|
||||
],
|
||||
},
|
||||
context,
|
||||
)
|
||||
|
||||
assert runtime.context_package_schema()["formats"] == ["kontextual", "markitect"]
|
||||
assert package["package_id"] == "ctxpkg-test"
|
||||
assert package["source_grounded"] is True
|
||||
assert package["result_count"] == 1
|
||||
assert package["items"][0]["asset_id"] == "asset-context"
|
||||
assert package["items"][0]["source_refs"]
|
||||
assert package["items"][0]["snippets"]
|
||||
assert package["external_memory_refs"] == [
|
||||
{
|
||||
"ref_id": "phase-memory:episode-1",
|
||||
"system": "phase-memory",
|
||||
"kind": "memory_ref",
|
||||
"opaque": True,
|
||||
"metadata": {},
|
||||
}
|
||||
]
|
||||
assert "memory graph detail" not in str(package["external_memory_refs"])
|
||||
assert package["markitect_payload"]["kind"] == "markitect.context_package"
|
||||
assert package["markitect_payload"]["adapter_boundary"] == (
|
||||
"markdown rendering and selector semantics are delegated to markitect-tool"
|
||||
)
|
||||
assert package["audit_event"]["operation"] == "context_package.assemble"
|
||||
assert package["policy_decision"]["effect"] == "allow"
|
||||
|
||||
|
||||
def test_service_runtime_agent_operations_support_review_required_and_dry_run_only_policy() -> None:
|
||||
review_runtime = ServiceRuntime(
|
||||
repository=InMemoryAssetRegistryRepository(),
|
||||
policy_gateway=ReviewRequiredAgentPolicy(),
|
||||
)
|
||||
dry_run_runtime = ServiceRuntime(
|
||||
repository=InMemoryAssetRegistryRepository(),
|
||||
policy_gateway=DryRunOnlyAgentPolicy(),
|
||||
)
|
||||
review_context = review_runtime.operation_context(
|
||||
actor_id="agent-codex",
|
||||
actor_type="ai_agent",
|
||||
correlation_id="corr-review",
|
||||
)
|
||||
dry_run_context = dry_run_runtime.operation_context(
|
||||
actor_id="agent-codex",
|
||||
actor_type="ai_agent",
|
||||
correlation_id="corr-dry-run",
|
||||
)
|
||||
|
||||
review = review_runtime.execute_agent_operation(
|
||||
"enrich_metadata",
|
||||
{"payload": {"asset_id": "asset-review", "metadata": {"key": "risk", "value": "high"}}},
|
||||
review_context,
|
||||
)
|
||||
dry_run_required = dry_run_runtime.execute_agent_operation(
|
||||
"transform_asset",
|
||||
{"payload": {"transformation": {"operation_id": "structured_view"}}},
|
||||
dry_run_context,
|
||||
)
|
||||
dry_run = dry_run_runtime.execute_agent_operation(
|
||||
"transform_asset",
|
||||
{"dry_run": True, "payload": {"transformation": {"operation_id": "structured_view"}}},
|
||||
dry_run_context,
|
||||
)
|
||||
|
||||
assert review["success"] is False
|
||||
assert review["review_required"] is True
|
||||
assert review["audit_event"]["outcome"] == "review_required"
|
||||
assert review["policy_decision"]["effect"] == "require_review"
|
||||
assert dry_run_required["success"] is False
|
||||
assert dry_run_required["dry_run_required"] is True
|
||||
assert dry_run_required["audit_event"]["outcome"] == "dry_run"
|
||||
assert dry_run_required["policy_decision"]["effect"] == "dry_run_only"
|
||||
assert dry_run["success"] is True
|
||||
assert dry_run["dry_run"] is True
|
||||
assert dry_run["audit_event"]["outcome"] == "dry_run"
|
||||
|
||||
|
||||
def test_service_runtime_ingestion_directory_reports_partial_failures(tmp_path) -> None:
|
||||
runtime = ServiceRuntime(repository=InMemoryAssetRegistryRepository())
|
||||
context = runtime.operation_context(actor_id="user-api", correlation_id="corr-partial")
|
||||
(tmp_path / "good.txt").write_text("source grounded text", encoding="utf-8")
|
||||
(tmp_path / "unsupported.bin").write_bytes(b"\x00\x01\x02")
|
||||
|
||||
result = runtime.start_ingestion_job(
|
||||
{"mode": "directory", "path": str(tmp_path), "recursive": False},
|
||||
context,
|
||||
)
|
||||
|
||||
assert result["status"] == "partially_completed"
|
||||
assert result["output_asset_ids"]
|
||||
assert result["failures"][0]["code"] == "kontextual.adapter_unavailable"
|
||||
assert result["job"]["partial_results"]["files_total"] == 2
|
||||
assert result["job"]["partial_results"]["failed"] == 1
|
||||
assert result["job"]["partial_results"]["succeeded"] == 1
|
||||
assert result["retry_options"]["retryable"] is True
|
||||
|
||||
|
||||
def test_create_app_reports_missing_optional_dependency_when_fastapi_is_absent() -> None:
|
||||
try:
|
||||
import fastapi # noqa: F401
|
||||
@@ -160,10 +562,20 @@ def test_service_health_readiness_version_and_openapi_contracts(client) -> None:
|
||||
assert "/api/v1/health" in paths
|
||||
assert "/api/v1/ready" in paths
|
||||
assert "/api/v1/version" in paths
|
||||
assert "/api/v1/context" in paths
|
||||
assert "/api/v1/assets" in paths
|
||||
assert "/api/v1/relationships" in paths
|
||||
assert "/api/v1/audit/events" in paths
|
||||
assert "/api/v1/policy/evaluate" in paths
|
||||
assert "/api/v1/ingestion/jobs" in paths
|
||||
assert "/api/v1/retrieval/assets" in paths
|
||||
assert "/api/v1/transformations/runs" in paths
|
||||
assert "/api/v1/workflows/templates" in paths
|
||||
assert "/api/v1/workflows/runs" in paths
|
||||
assert "/api/v1/workflows/reviews" in paths
|
||||
assert "/api/v1/agents/operations" in paths
|
||||
assert "/api/v1/context-packages" in paths
|
||||
assert "/api/v1/context-packages/schema" in paths
|
||||
|
||||
|
||||
def test_create_app_attaches_runtime_to_application_state(client) -> None:
|
||||
@@ -188,3 +600,46 @@ class DenyCreatePolicy:
|
||||
context={"resource_metadata": resource_metadata or {}},
|
||||
)
|
||||
return PolicyDecision.allow(context.actor.id, action, resource)
|
||||
|
||||
|
||||
class ReviewRequiredAgentPolicy:
|
||||
def authorize(
|
||||
self,
|
||||
context: OperationContext,
|
||||
action: str,
|
||||
resource: str,
|
||||
*,
|
||||
resource_metadata: dict[str, str] | None = None,
|
||||
) -> PolicyDecision:
|
||||
if action == "agent.operation.enrich_metadata":
|
||||
return PolicyDecision(
|
||||
PolicyEffect.REQUIRE_REVIEW,
|
||||
context.actor.id,
|
||||
action,
|
||||
resource,
|
||||
reason="metadata enrichment requires review",
|
||||
obligations={"queue": "knowledge-review"},
|
||||
context={"resource_metadata": resource_metadata or {}},
|
||||
)
|
||||
return PolicyDecision.allow(context.actor.id, action, resource)
|
||||
|
||||
|
||||
class DryRunOnlyAgentPolicy:
|
||||
def authorize(
|
||||
self,
|
||||
context: OperationContext,
|
||||
action: str,
|
||||
resource: str,
|
||||
*,
|
||||
resource_metadata: dict[str, str] | None = None,
|
||||
) -> PolicyDecision:
|
||||
if action == "agent.operation.transform_asset":
|
||||
return PolicyDecision(
|
||||
PolicyEffect.DRY_RUN_ONLY,
|
||||
context.actor.id,
|
||||
action,
|
||||
resource,
|
||||
reason="transformation requires dry-run preview",
|
||||
context={"resource_metadata": resource_metadata or {}},
|
||||
)
|
||||
return PolicyDecision.allow(context.actor.id, action, resource)
|
||||
|
||||
@@ -4,7 +4,7 @@ type: workplan
|
||||
title: "Service API And Agent-Safe Operation"
|
||||
domain: markitect
|
||||
repo: kontextual-engine
|
||||
status: active
|
||||
status: completed
|
||||
owner: codex
|
||||
topic_slug: markitect
|
||||
planning_priority: high
|
||||
@@ -48,14 +48,13 @@ review gates through Markitect APIs.
|
||||
|
||||
## Implementation Status
|
||||
|
||||
The first optional FastAPI service skeleton is implemented for health,
|
||||
readiness, version, OpenAPI contracts, and the initial asset/metadata/
|
||||
relationship/audit/policy resource surface. See
|
||||
The optional FastAPI service skeleton is implemented for health, readiness,
|
||||
version, OpenAPI contracts, asset/metadata/relationship/audit/policy resources,
|
||||
ingestion jobs, governed retrieval, transformations, and workflow resources.
|
||||
See
|
||||
`docs/service-api-implementation.md`.
|
||||
|
||||
Ingestion, retrieval, transformation, workflow, actor context, agent
|
||||
operations, context packages, and dry-run/review-gate response contracts remain
|
||||
open in this workplan.
|
||||
All MVP tasks in this workplan are implemented.
|
||||
|
||||
## S9.1 - Implement versioned FastAPI service skeleton and health contracts
|
||||
|
||||
@@ -116,7 +115,7 @@ Implemented:
|
||||
|
||||
```task
|
||||
id: KONT-WP-0009-T003
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "7271b26d-0dbb-4eca-9140-a7729ad296e4"
|
||||
```
|
||||
@@ -131,11 +130,25 @@ Acceptance:
|
||||
- Retrieval results are permission-aware and source-grounded.
|
||||
- Transformations and workflows expose lineage and audit references.
|
||||
|
||||
Implemented:
|
||||
|
||||
- `ServiceRuntime` exposes ingestion capabilities and ingestion job start/list/get
|
||||
over `AssetIngestionService`.
|
||||
- Governed retrieval endpoints wrap `AssetRetrievalService` for asset, context
|
||||
entity, relationship, feedback, index refresh, and quality metric contracts.
|
||||
- Transformation operations and runs are exposed with retry/cancel hints,
|
||||
lineage, output assets, audit references, and policy decisions.
|
||||
- Workflow templates, queued/invoked runs, run recovery, review decisions,
|
||||
review queues, exception queues, and reconstruction are exposed over
|
||||
`WorkflowService`.
|
||||
- Runtime tests cover completed and failed ingestion jobs, source-grounded
|
||||
retrieval, transformation lineage/audit, and workflow reconstruction.
|
||||
|
||||
## S9.4 - Implement actor context delegation and authorization middleware
|
||||
|
||||
```task
|
||||
id: KONT-WP-0009-T004
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "7becdec7-ddbb-497f-b762-77043e16046e"
|
||||
```
|
||||
@@ -150,11 +163,23 @@ Acceptance:
|
||||
- Authorization failures do not leak protected content in errors or result
|
||||
shapes.
|
||||
|
||||
Implemented:
|
||||
|
||||
- `ServiceRuntime.operation_context()` now supports actor external refs,
|
||||
groups, delegated actors, request scope, policy scope, and AI-agent metadata.
|
||||
- FastAPI request context parsing accepts actor, delegated actor, agent, group,
|
||||
request-scope, and policy-scope headers.
|
||||
- `GET /api/v1/context` exposes the resolved operation context for integration
|
||||
checks and client diagnostics.
|
||||
- FastAPI exception handlers preserve structured errors from dependencies.
|
||||
- HTTP authorization error payloads redact protected resource metadata from
|
||||
policy-decision context.
|
||||
|
||||
## S9.5 - Implement bounded agent operation catalog
|
||||
|
||||
```task
|
||||
id: KONT-WP-0009-T005
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "fc9e1def-229c-4224-8fd3-6fd4f9785c27"
|
||||
```
|
||||
@@ -171,11 +196,24 @@ Acceptance:
|
||||
- Agent operations are auditable separately from human and deterministic
|
||||
automation actions.
|
||||
|
||||
Implemented:
|
||||
|
||||
- Added a bounded agent operation catalog for inspect, retrieve, search,
|
||||
assemble-context preview, metadata enrichment, classification request,
|
||||
transformation, workflow invocation, review submission, and result reporting.
|
||||
- Added `GET /api/v1/agents/operations`, `GET /api/v1/agents/operations/{id}`,
|
||||
and `POST /api/v1/agents/operations/{id}`.
|
||||
- Agent operation execution authorizes `agent.operation.*`, emits separate
|
||||
agent-operation audit events, supports generic dry-run envelopes, and
|
||||
dispatches through existing runtime methods.
|
||||
- Unsupported operation IDs fail with structured validation errors rather than
|
||||
opening arbitrary command or internal-service access.
|
||||
|
||||
## S9.6 - Implement context package API with policy constraints
|
||||
|
||||
```task
|
||||
id: KONT-WP-0009-T006
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "9ff1d345-d0a1-46eb-ae9a-f6beba2fa5e9"
|
||||
```
|
||||
@@ -192,11 +230,24 @@ Acceptance:
|
||||
- Markdown-backed packages can interoperate with Markitect context-package
|
||||
payloads while remaining wrapped in engine permission and audit contracts.
|
||||
|
||||
Implemented:
|
||||
|
||||
- Added `GET /api/v1/context-packages/schema` and
|
||||
`POST /api/v1/context-packages`.
|
||||
- Context packages are assembled from permission-filtered retrieval results and
|
||||
include source refs, snippets, metadata, relationships, representation
|
||||
provenance, policy constraints, audit references, and policy decisions.
|
||||
- External memory refs are represented as opaque pointers and do not embed
|
||||
memory graph content.
|
||||
- The `markitect` format emits a Markitect-compatible envelope while keeping
|
||||
markdown rendering, selector semantics, and validation delegated to
|
||||
`markitect-tool`.
|
||||
|
||||
## S9.7 - Implement dry-run review-gate and contract-test coverage
|
||||
|
||||
```task
|
||||
id: KONT-WP-0009-T007
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "bbbdec75-d3c0-4367-b073-ef9c5dffa2b7"
|
||||
```
|
||||
@@ -211,6 +262,18 @@ Acceptance:
|
||||
responses, and partial failures.
|
||||
- OpenAPI output remains stable for implemented endpoints.
|
||||
|
||||
Implemented:
|
||||
|
||||
- Agent operation policy decisions with `require_review` now return structured
|
||||
`review_required` envelopes and audit `review_required` outcomes.
|
||||
- Agent operation policy decisions with `dry_run_only` now return
|
||||
`dry_run_required` envelopes unless the request is already a dry run.
|
||||
- Generic agent dry-run requests audit with `dry_run` outcomes and avoid domain
|
||||
mutation.
|
||||
- Contract tests cover redacted authorization failures, review-required
|
||||
responses, dry-run-required responses, partial ingestion failures, and OpenAPI
|
||||
stability for implemented endpoint groups.
|
||||
|
||||
## Definition Of Done
|
||||
|
||||
- The service API exposes the MVP operation surface without requiring UI.
|
||||
|
||||
Reference in New Issue
Block a user