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:
2026-05-06 21:24:38 +02:00
parent dee0ce8a12
commit 9705104659
5 changed files with 2191 additions and 48 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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)

View File

@@ -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.