Implement credentialed drill packaging workplan

This commit is contained in:
2026-05-19 01:27:59 +02:00
parent 022cd8d37e
commit 6e0372d21a
23 changed files with 924 additions and 43 deletions

View File

@@ -1,9 +1,16 @@
{
"compatibility": {
"release_note_template": "docs/release-note-template.md"
},
"exports": [
"ADAPTER_PACK_MANIFEST_SCHEMA",
"ActivationPlan",
"CREDENTIALED_ADAPTER_ENV_VARS",
"CREDENTIALED_DRILL_SCHEMA",
"CredentialedDrillConfig",
"Diagnostic",
"EVALUATION_REPORT_SCHEMA",
"EVALUATION_TREND_SCHEMA",
"ExternalAdapterPack",
"FakeExternalEventLog",
"FakeExternalGraphStore",
@@ -49,7 +56,9 @@
"ReviewRecord",
"RuntimeAdapterBundle",
"RuntimeConfig",
"SERVICE_APP_SCHEMA",
"SERVICE_BINDING_SCHEMA",
"ServiceAppConfig",
"ServiceBinding",
"ServiceResponse",
"WordCountTokenEstimator",
@@ -57,9 +66,14 @@
"activation_quality_report",
"adapter_pack_manifest",
"branch_path",
"build_service_binding",
"compact_path",
"create_path",
"create_wsgi_app",
"credentialed_adapter_smoke_report",
"credentialed_drill_config_from_env",
"evaluation_threshold_report",
"evaluation_trend_artifact",
"fake_external_adapter_pack",
"fake_external_runtime_config",
"graph_from_markitect",
@@ -67,6 +81,7 @@
"live_shaped_adapter_pack",
"make_review_record",
"merge_path",
"missing_credentialed_adapter_env",
"package_request_from_selection",
"package_response_envelope",
"path_event",
@@ -85,6 +100,7 @@
"retrieve_graph_neighborhood",
"runtime_from_config",
"select_event_path",
"service_app_metadata",
"service_binding_from_config",
"service_contracts",
"validate_adapter_pack_manifest"

View File

@@ -70,3 +70,58 @@ def test_audit_retention_plan_identifies_eligible_records() -> None:
assert plan["valid"] is True
assert plan["plan"]["eligible_operation_ids"] == ["op:old"]
assert plan["plan"]["eligible_count"] == 1
def test_audit_retention_apply_prunes_eligible_records_and_records_apply() -> None:
runtime = PhaseMemoryRuntime()
runtime.audit_sink.record(
{
"schema_version": "phase_memory.audit.event.v1",
"operation_id": "op:old",
"operation": "manual",
"timestamp": "2026-01-01T00:00:00+00:00",
"subject": {"kind": "audit_events", "id": "old"},
"source": {"ref": "test"},
"dry_run": True,
"allowed": True,
}
)
runtime.audit_sink.record(
{
"schema_version": "phase_memory.audit.event.v1",
"operation_id": "op:new",
"operation": "manual",
"timestamp": "2026-05-18T00:00:00+00:00",
"subject": {"kind": "audit_events", "id": "new"},
"source": {"ref": "test"},
"dry_run": True,
"allowed": True,
}
)
plan = runtime.audit_retention_plan(retention_days=30, now=datetime(2026, 5, 19, tzinfo=timezone.utc))
applied = runtime.apply_audit_retention(plan["plan"])
remaining_ids = [event["operation_id"] for event in runtime.audit_sink.query()]
assert applied["valid"] is True
assert applied["result"]["pruned_operation_ids"] == ["op:old"]
assert "op:old" not in remaining_ids
assert "op:new" in remaining_ids
assert any(event["operation"] == "audit.retention.apply" for event in runtime.audit_sink.query())
def test_audit_retention_apply_noop_and_unsupported_paths() -> None:
runtime = PhaseMemoryRuntime()
noop = runtime.apply_audit_retention(retention_days=30, now=datetime(2026, 5, 19, tzinfo=timezone.utc))
assert noop["valid"] is True
assert noop["result"]["changed"] is False
class UnsupportedAuditSink:
def record(self, event):
return {"recorded": True, "event": event}
unsupported = PhaseMemoryRuntime(audit_sink=UnsupportedAuditSink())
result = unsupported.apply_audit_retention(retention_days=30)
assert result["valid"] is False
assert result["diagnostics"][0]["code"] == "audit_retention_apply_unsupported"

View File

@@ -0,0 +1,32 @@
import os
import pytest
from phase_memory.credentialed_drills import (
CREDENTIALED_ADAPTER_ENV_VARS,
credentialed_adapter_smoke_report,
missing_credentialed_adapter_env,
)
def test_credentialed_adapter_drill_reports_missing_env_without_secrets() -> None:
report = credentialed_adapter_smoke_report({})
assert report["valid"] is False
assert report["skipped"] is True
assert tuple(report["missing_env"]) == CREDENTIALED_ADAPTER_ENV_VARS
assert report["diagnostics"][0]["code"] == "credential_env_missing"
@pytest.mark.skipif(
missing_credentialed_adapter_env(os.environ),
reason="requires env vars: " + ", ".join(CREDENTIALED_ADAPTER_ENV_VARS),
)
def test_credentialed_adapter_drill_reuses_manifest_contract_when_env_is_present() -> None:
report = credentialed_adapter_smoke_report(os.environ)
assert report["valid"] is True
assert report["skipped"] is False
assert report["adapter_pack"]["name"] == "live-shaped"
assert report["config"]["credential_fingerprint"]
assert "PHASE_MEMORY_MARKITECT_TOKEN" not in str(report)

View File

@@ -4,7 +4,7 @@ from pathlib import Path
from phase_memory.adapters import InMemorySemanticIndex
from phase_memory.contracts import graph_from_markitect
from phase_memory.evaluation import EVALUATION_REPORT_SCHEMA, evaluation_threshold_report
from phase_memory.evaluation import EVALUATION_REPORT_SCHEMA, EVALUATION_TREND_SCHEMA, evaluation_threshold_report, evaluation_trend_artifact
from phase_memory.models import ActivationPlan, MemoryPath
from phase_memory.retrieval import activation_quality_report, select_event_path
from phase_memory.runtime import PhaseMemoryRuntime
@@ -102,6 +102,30 @@ def test_evaluation_threshold_report_summarizes_all_scenarios() -> None:
assert report["diagnostics"] == []
def test_evaluation_trend_artifact_tracks_threshold_and_metric_deltas() -> None:
data = json.loads((FIXTURES / "evaluation-scenarios.json").read_text(encoding="utf-8"))
report = evaluation_threshold_report(data)
previous = {
"id": "previous",
"metrics": {
**report["metrics"],
"policy_denial_count": report["metrics"]["policy_denial_count"] + 1,
},
}
trend = evaluation_trend_artifact(
report,
previous_report=previous,
run_metadata={"run_id": "pytest", "created_at": "2026-05-19T00:00:00+00:00"},
)
assert trend["schema_version"] == EVALUATION_TREND_SCHEMA
assert trend["run"]["run_id"] == "pytest"
assert trend["threshold_deltas"]["policy_denial_count"] == 0.0
assert trend["metric_deltas"]["policy_denial_count"] == -1.0
assert trend["diagnostics"][0]["code"] == "evaluation_metric_regressed"
def _activation_plan(response):
data = response["data"]["activation_plan"]
return ActivationPlan(

View File

@@ -13,6 +13,10 @@ def test_public_api_snapshot_is_explicit() -> None:
assert sorted(phase_memory.__all__) == snapshot["exports"]
assert sorted(SERVICE_OPERATIONS) == snapshot["service_operations"]
release_note_template = Path(snapshot["compatibility"]["release_note_template"])
template_text = release_note_template.read_text(encoding="utf-8")
for heading in ("Changed Exports", "Changed Service Operations", "Migration Needs", "Operator Action"):
assert heading in template_text
def test_service_contract_catalog_matches_local_runner_supported_operations() -> None:

41
tests/test_service_app.py Normal file
View File

@@ -0,0 +1,41 @@
import json
from io import BytesIO
from phase_memory.service_app import ServiceAppConfig, create_wsgi_app, main, service_app_metadata
def test_service_app_metadata_exposes_deployable_routes_without_listener(tmp_path) -> None:
config = ServiceAppConfig(host="127.0.0.1", port=8123, local_store_path=str(tmp_path))
metadata = service_app_metadata(config)
assert metadata["schema_version"] == "phase_memory.service.app.v1"
assert metadata["config"]["port"] == 8123
assert metadata["readiness"]["ok"] is True
assert metadata["routes"]["operations"] == "/operations/{operation}"
def test_service_wsgi_app_can_dispatch_without_opening_listener(tmp_path) -> None:
app = create_wsgi_app(ServiceAppConfig(local_store_path=str(tmp_path)))
statuses: list[str] = []
payload = json.dumps({"selection": {"schema_version": "markitect.memory.selection.v1", "id": "svc", "nodes": [], "events": []}}).encode("utf-8")
body = b"".join(
app(
{
"REQUEST_METHOD": "POST",
"PATH_INFO": "/operations/package.compile",
"CONTENT_LENGTH": str(len(payload)),
"wsgi.input": BytesIO(payload),
},
lambda status, _headers: statuses.append(status),
)
)
response = json.loads(body.decode("utf-8"))
assert statuses == ["200 OK"]
assert response["operation"] == "package.compile"
def test_service_main_check_builds_app_without_serving(tmp_path) -> None:
assert main(["--check", "--store", str(tmp_path), "--port", "8124"]) == 0