Implement credentialed live hardening workplan

This commit is contained in:
2026-05-19 03:51:51 +02:00
parent b85f4c02f4
commit 1ccbab5c04
14 changed files with 906 additions and 37 deletions

View File

@@ -13,12 +13,32 @@ from .contracts import graph_from_markitect, profile_from_markitect
from .credentialed_drills import (
CREDENTIALED_ADAPTER_ENV_VARS,
CREDENTIALED_DRILL_SCHEMA,
CREDENTIALED_OPERATOR_REPORT_SCHEMA,
CREDENTIALED_TELEMETRY_DRILL_SCHEMA,
CredentialedDrillConfig,
credentialed_adapter_smoke_report,
credentialed_drill_config_from_env,
credentialed_operator_report,
credentialed_telemetry_retention_drill,
missing_credentialed_adapter_env,
write_credentialed_operator_report,
)
from .deployment import (
MANAGED_DEPLOYMENT_SCHEMA,
MANAGED_DEPLOYMENT_VALIDATION_SCHEMA,
managed_deployment_manifest,
validate_managed_deployment_manifest,
)
from .evaluation import (
EVALUATION_REPORT_SCHEMA,
EVALUATION_TREND_HISTORY_SCHEMA,
EVALUATION_TREND_SCHEMA,
evaluation_threshold_report,
evaluation_trend_artifact,
evaluation_trend_history,
load_evaluation_trend_history,
write_evaluation_trend_history,
)
from .evaluation import EVALUATION_REPORT_SCHEMA, EVALUATION_TREND_SCHEMA, evaluation_threshold_report, evaluation_trend_artifact
from .external_adapters import (
ADAPTER_PACK_MANIFEST_SCHEMA,
ExternalAdapterPack,
@@ -88,16 +108,25 @@ from .service_app import SERVICE_APP_SCHEMA, ServiceAppConfig, build_service_bin
from .service_binding import READINESS_REPORT_SCHEMA, SERVICE_BINDING_SCHEMA, ServiceBinding, ServiceResponse, service_binding_from_config
from .planner import plan_profile_execution
from .runtime import PhaseMemoryRuntime
from .troubleshooting import (
TROUBLESHOOTING_MATRIX_SCHEMA,
TROUBLESHOOTING_REQUIRED_CATEGORIES,
operator_troubleshooting_matrix,
validate_operator_troubleshooting_matrix,
)
__all__ = [
"ActivationPlan",
"ADAPTER_PACK_MANIFEST_SCHEMA",
"CREDENTIALED_ADAPTER_ENV_VARS",
"CREDENTIALED_DRILL_SCHEMA",
"CREDENTIALED_OPERATOR_REPORT_SCHEMA",
"CREDENTIALED_TELEMETRY_DRILL_SCHEMA",
"CredentialedDrillConfig",
"Diagnostic",
"ExternalAdapterPack",
"EVALUATION_REPORT_SCHEMA",
"EVALUATION_TREND_HISTORY_SCHEMA",
"EVALUATION_TREND_SCHEMA",
"FakeExternalEventLog",
"FakeExternalGraphStore",
@@ -137,6 +166,8 @@ __all__ = [
"MemoryOperation",
"MARKITECT_PACKAGE_REQUEST_SCHEMA",
"MARKITECT_PACKAGE_RESPONSE_SCHEMA",
"MANAGED_DEPLOYMENT_SCHEMA",
"MANAGED_DEPLOYMENT_VALIDATION_SCHEMA",
"LocalMarkitectValidator",
"OptionalMarkitectValidator",
"abandon_path",
@@ -145,11 +176,15 @@ __all__ = [
"create_path",
"credentialed_adapter_smoke_report",
"credentialed_drill_config_from_env",
"credentialed_operator_report",
"credentialed_telemetry_retention_drill",
"graph_from_markitect",
"evaluation_threshold_report",
"evaluation_trend_artifact",
"evaluation_trend_history",
"merge_path",
"make_review_record",
"managed_deployment_manifest",
"plan_activation",
"plan_compaction",
"plan_lifecycle_from_profile",
@@ -167,6 +202,7 @@ __all__ = [
"adapter_pack_manifest",
"validate_adapter_pack_manifest",
"path_event",
"operator_troubleshooting_matrix",
"package_request_from_selection",
"package_response_envelope",
"WordCountTokenEstimator",
@@ -183,6 +219,8 @@ __all__ = [
"ServiceBinding",
"ServiceAppConfig",
"ServiceResponse",
"TROUBLESHOOTING_MATRIX_SCHEMA",
"TROUBLESHOOTING_REQUIRED_CATEGORIES",
"build_service_binding",
"create_wsgi_app",
"health_report",
@@ -191,6 +229,11 @@ __all__ = [
"service_binding_from_config",
"service_app_metadata",
"service_contracts",
"load_evaluation_trend_history",
"validate_managed_deployment_manifest",
"validate_operator_troubleshooting_matrix",
"write_credentialed_operator_report",
"write_evaluation_trend_history",
]
__version__ = "0.1.0"

View File

@@ -7,14 +7,20 @@ manifest/conformance path used by live-shaped adapter fixtures.
from __future__ import annotations
import json
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Mapping
from .external_adapters import live_shaped_adapter_pack, validate_adapter_pack_manifest
from .runtime import PhaseMemoryRuntime
from .service import default_conformance_adapters
from .utils import stable_digest
CREDENTIALED_DRILL_SCHEMA = "phase_memory.credentialed_adapter_drill.v1"
CREDENTIALED_OPERATOR_REPORT_SCHEMA = "phase_memory.credentialed_operator_report.v1"
CREDENTIALED_TELEMETRY_DRILL_SCHEMA = "phase_memory.credentialed_telemetry_retention_drill.v1"
CREDENTIALED_ADAPTER_ENV_VARS = (
"PHASE_MEMORY_MARKITECT_URL",
"PHASE_MEMORY_MARKITECT_TOKEN",
@@ -30,8 +36,8 @@ class CredentialedDrillConfig:
def to_dict(self) -> dict[str, str]:
return {
"markitect_url": self.markitect_url,
"kontextual_url": self.kontextual_url,
"markitect_endpoint_fingerprint": stable_digest(self.markitect_url),
"kontextual_endpoint_fingerprint": stable_digest(self.kontextual_url),
"credential_fingerprint": stable_digest([self.markitect_url, self.kontextual_url]),
}
@@ -84,3 +90,110 @@ def credentialed_adapter_smoke_report(environ: Mapping[str, str] | None = None)
"conformance_helpers": sorted(conformance_adapters),
"diagnostics": [diagnostic.to_dict() for diagnostic in diagnostics],
}
def credentialed_operator_report(
environ: Mapping[str, str] | None = None,
*,
run_id: str = "operator-drill",
mode: str = "credentialed",
) -> dict:
smoke = credentialed_adapter_smoke_report(environ)
present = sorted(name for name in CREDENTIALED_ADAPTER_ENV_VARS if (environ or {}).get(name))
return {
"schema_version": CREDENTIALED_OPERATOR_REPORT_SCHEMA,
"id": f"credentialed-operator-report:{stable_digest([run_id, smoke])}",
"run": {"id": run_id, "mode": mode},
"valid": bool(smoke.get("valid")),
"skipped": bool(smoke.get("skipped")),
"redacted_env": {
"present": present,
"missing": list(smoke.get("missing_env", ())),
"secrets_redacted": True,
},
"smoke_report": smoke,
"diagnostics": list(smoke.get("diagnostics", ())),
}
def write_credentialed_operator_report(
path: str | Path,
environ: Mapping[str, str] | None = None,
*,
run_id: str = "operator-drill",
mode: str = "credentialed",
) -> dict:
report = credentialed_operator_report(environ, run_id=run_id, mode=mode)
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
return report
def credentialed_telemetry_retention_drill(
environ: Mapping[str, str] | None = None,
*,
retention_days: int = 30,
operator_approved_fixture: bool = False,
now: datetime | None = None,
) -> dict:
missing = missing_credentialed_adapter_env(environ)
if missing and not operator_approved_fixture:
return {
"schema_version": CREDENTIALED_TELEMETRY_DRILL_SCHEMA,
"valid": False,
"skipped": True,
"missing_env": list(missing),
"diagnostics": [
{
"severity": "warn",
"code": "credential_env_missing",
"message": "Telemetry retention drill skipped because required environment variables are missing.",
"metadata": {"required_env": list(CREDENTIALED_ADAPTER_ENV_VARS), "missing_env": list(missing)},
}
],
}
pack = live_shaped_adapter_pack()
runtime = PhaseMemoryRuntime(audit_sink=pack.adapters["audit_sink"])
runtime.audit_sink.record(_audit_fixture("op:old", "2026-01-01T00:00:00+00:00"))
runtime.audit_sink.record(_audit_fixture("op:new", "2026-05-18T00:00:00+00:00"))
now = now or datetime(2026, 5, 19, tzinfo=timezone.utc)
plan = runtime.audit_retention_plan(retention_days=retention_days, now=now, source_ref="credentialed-telemetry-drill")
applied = runtime.apply_audit_retention(plan["plan"], source_ref="credentialed-telemetry-drill")
export = runtime.export_audit_events(source_ref="credentialed-telemetry-drill")
retained_ids = [
str(event.get("operation_id") or "")
for event in export["batch"]["events"]
if event.get("operation") != "audit.export"
]
return {
"schema_version": CREDENTIALED_TELEMETRY_DRILL_SCHEMA,
"valid": plan["valid"] and applied["valid"],
"skipped": False,
"operator_approved_fixture": operator_approved_fixture,
"redacted_env": {
"present": sorted(name for name in CREDENTIALED_ADAPTER_ENV_VARS if (environ or {}).get(name)),
"missing": list(missing),
"secrets_redacted": True,
},
"plan": plan["plan"],
"apply_result": applied["result"],
"pruned_operation_ids": applied["result"].get("pruned_operation_ids", []),
"retained_operation_ids": retained_ids,
"audit_operations": [event.get("operation") for event in export["batch"]["events"]],
"diagnostics": plan["diagnostics"] + applied["diagnostics"],
}
def _audit_fixture(operation_id: str, timestamp: str) -> dict:
return {
"schema_version": "phase_memory.audit.event.v1",
"operation_id": operation_id,
"operation": "credentialed.telemetry.fixture",
"timestamp": timestamp,
"subject": {"kind": "audit_events", "id": operation_id},
"source": {"ref": "credentialed-telemetry-drill"},
"dry_run": True,
"allowed": True,
}

View File

@@ -0,0 +1,128 @@
"""Managed deployment manifest helpers for the service app."""
from __future__ import annotations
from typing import Any
from .models import Diagnostic
from .service_app import ServiceAppConfig, service_app_metadata
from .utils import stable_digest
MANAGED_DEPLOYMENT_SCHEMA = "phase_memory.managed_deployment.v1"
MANAGED_DEPLOYMENT_VALIDATION_SCHEMA = "phase_memory.managed_deployment.validation.v1"
def managed_deployment_manifest(
config: ServiceAppConfig | None = None,
*,
image: str = "phase-memory:local",
namespace: str = "phase-memory",
replicas: int = 1,
) -> dict[str, Any]:
config = config or ServiceAppConfig(host="0.0.0.0")
command = [
"phase-memory-service",
"--host",
config.host,
"--port",
str(config.port),
"--store",
config.local_store_path,
]
manifest = {
"schema_version": MANAGED_DEPLOYMENT_SCHEMA,
"service": {
"name": "phase-memory-service",
"namespace": namespace,
"image": image,
"replicas": replicas,
"command": command,
"ports": [{"name": "http", "container_port": config.port}],
},
"runtime": service_app_metadata(config),
"storage": {
"volumes": [
{
"name": "phase-memory-local-store",
"mount_path": config.local_store_path,
"purpose": "local graph, event, and audit state",
}
]
},
"probes": {
"liveness": {"path": "/health", "port": config.port},
"readiness": {"path": "/ready", "port": config.port},
},
"rollback": {
"requires_store_snapshot": True,
"checks": ["phase-memory-service --check", "GET /ready", "runtime.repair_diagnostics"],
},
}
manifest["id"] = f"managed-deployment:{stable_digest(manifest)}"
return manifest
def validate_managed_deployment_manifest(manifest: dict[str, Any]) -> dict[str, Any]:
diagnostics: list[Diagnostic] = []
service = manifest.get("service") if isinstance(manifest.get("service"), dict) else {}
command = service.get("command") if isinstance(service.get("command"), list) else []
probes = manifest.get("probes") if isinstance(manifest.get("probes"), dict) else {}
storage = manifest.get("storage") if isinstance(manifest.get("storage"), dict) else {}
volumes = storage.get("volumes") if isinstance(storage.get("volumes"), list) else []
if manifest.get("schema_version") != MANAGED_DEPLOYMENT_SCHEMA:
diagnostics.append(
Diagnostic(
"error",
"managed_deployment_schema_mismatch",
"Managed deployment manifest has an unexpected schema version.",
"schema_version",
{"expected": MANAGED_DEPLOYMENT_SCHEMA},
)
)
if "phase-memory-service" not in command:
diagnostics.append(
Diagnostic(
"error",
"managed_deployment_missing_service_entrypoint",
"Managed deployment command must invoke phase-memory-service.",
"service.command",
)
)
for name, path in (("liveness", "/health"), ("readiness", "/ready")):
probe = probes.get(name) if isinstance(probes.get(name), dict) else {}
if probe.get("path") != path:
diagnostics.append(
Diagnostic(
"error",
"managed_deployment_probe_missing",
"Managed deployment manifest must declare health and readiness probes.",
f"probes.{name}.path",
{"expected": path},
)
)
if not any(volume.get("mount_path") for volume in volumes if isinstance(volume, dict)):
diagnostics.append(
Diagnostic(
"error",
"managed_deployment_store_mount_missing",
"Managed deployment manifest must include a writable local-store mount.",
"storage.volumes",
)
)
if int(service.get("replicas") or 0) < 1:
diagnostics.append(
Diagnostic(
"error",
"managed_deployment_replica_count_invalid",
"Managed deployment manifest must request at least one replica.",
"service.replicas",
)
)
return {
"schema_version": MANAGED_DEPLOYMENT_VALIDATION_SCHEMA,
"valid": not any(diagnostic.severity == "error" for diagnostic in diagnostics),
"manifest_id": str(manifest.get("id") or ""),
"diagnostics": [diagnostic.to_dict() for diagnostic in diagnostics],
}

View File

@@ -2,7 +2,9 @@
from __future__ import annotations
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from .adapters import InMemorySemanticIndex
@@ -14,6 +16,7 @@ from .utils import stable_digest, utc_now_iso
EVALUATION_REPORT_SCHEMA = "phase_memory.evaluation.threshold_report.v1"
EVALUATION_TREND_SCHEMA = "phase_memory.evaluation.trend_artifact.v1"
EVALUATION_TREND_HISTORY_SCHEMA = "phase_memory.evaluation.trend_history.v1"
DEFAULT_THRESHOLDS = {
"policy_denial_count": 1,
@@ -115,6 +118,68 @@ def evaluation_trend_artifact(
"previous_report_id": (previous_report or {}).get("id", ""),
"diagnostics": diagnostics,
}
def evaluation_trend_history(artifacts: list[dict[str, Any]] | tuple[dict[str, Any], ...]) -> dict[str, Any]:
ordered = sorted(
(dict(artifact) for artifact in artifacts),
key=lambda artifact: (
str((artifact.get("run") or {}).get("created_at") or ""),
str((artifact.get("run") or {}).get("run_id") or ""),
str(artifact.get("id") or ""),
),
)
metric_keys = sorted({str(key) for artifact in ordered for key in (artifact.get("metrics") or {})})
diagnostics = [
Diagnostic(
"error",
"evaluation_trend_history_invalid_artifact",
"Trend history can only contain evaluation trend artifacts.",
f"artifacts.{index}.schema_version",
{"artifact_id": artifact.get("id", "")},
).to_dict()
for index, artifact in enumerate(ordered)
if artifact.get("schema_version") != EVALUATION_TREND_SCHEMA
]
return {
"schema_version": EVALUATION_TREND_HISTORY_SCHEMA,
"id": f"evaluation-trend-history:{stable_digest([artifact.get('id', '') for artifact in ordered])}",
"valid": not diagnostics,
"count": len(ordered),
"metric_keys": metric_keys,
"latest_artifact_id": ordered[-1].get("id", "") if ordered else "",
"artifacts": ordered,
"diagnostics": diagnostics,
}
def load_evaluation_trend_history(path: str | Path) -> dict[str, Any]:
path = Path(path)
if not path.exists():
return evaluation_trend_history(())
data = json.loads(path.read_text(encoding="utf-8"))
if data.get("schema_version") == EVALUATION_TREND_HISTORY_SCHEMA:
return data
if data.get("schema_version") == EVALUATION_TREND_SCHEMA:
return evaluation_trend_history((data,))
return evaluation_trend_history((data,))
def write_evaluation_trend_history(path: str | Path, artifact: dict[str, Any]) -> dict[str, Any]:
path = Path(path)
existing = load_evaluation_trend_history(path)
artifacts = list(existing.get("artifacts") or ())
artifact_id = str(artifact.get("id") or "")
if artifact_id and not any(str(item.get("id") or "") == artifact_id for item in artifacts):
artifacts.append(dict(artifact))
elif not artifact_id:
artifacts.append(dict(artifact))
history = evaluation_trend_history(tuple(artifacts))
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(history, indent=2, sort_keys=True) + "\n", encoding="utf-8")
return history
def _policy_scenario(scenario: dict[str, Any]) -> dict[str, Any]:
runtime = PhaseMemoryRuntime()
response = runtime.plan_activation(

View File

@@ -0,0 +1,112 @@
"""Operator troubleshooting matrix for readiness and deployment drills."""
from __future__ import annotations
from typing import Any
from .models import Diagnostic
from .utils import stable_digest
TROUBLESHOOTING_MATRIX_SCHEMA = "phase_memory.operator_troubleshooting.v1"
TROUBLESHOOTING_REQUIRED_CATEGORIES = (
"credentials",
"readiness",
"migrations",
"audit_retention",
"adapter_manifest",
)
def operator_troubleshooting_matrix() -> dict[str, Any]:
rows = [
{
"category": "credentials",
"diagnostic_code": "credential_env_missing",
"signal": "Credentialed drill reports skipped: true.",
"likely_cause": "One or more required operator environment variables are absent.",
"operator_action": "Set the Markitect and Kontextual URL/token variables in the shell that runs the drill; do not persist them to files.",
},
{
"category": "readiness",
"diagnostic_code": "unsupported_operation",
"signal": "Readiness reports unsupported operations or a failing /ready response.",
"likely_cause": "The service binding and local runner operation catalogs are out of sync.",
"operator_action": "Run the public API snapshot and service contract tests, then update operation dispatch or release notes.",
},
{
"category": "migrations",
"diagnostic_code": "store_migration_unsupported",
"signal": "Migration plan/apply returns an error diagnostic.",
"likely_cause": "The configured graph store does not expose migration planning.",
"operator_action": "Switch to a file-backed local store or run repair diagnostics before accepting traffic.",
},
{
"category": "audit_retention",
"diagnostic_code": "audit_retention_apply_unsupported",
"signal": "Retention apply reports unsupported behavior.",
"likely_cause": "The configured audit sink cannot apply retention plans.",
"operator_action": "Use a JSONL or telemetry audit sink with retention support, then rerun the retention drill.",
},
{
"category": "adapter_manifest",
"diagnostic_code": "adapter_pack_manifest_invalid",
"signal": "Adapter pack validation returns an error diagnostic.",
"likely_cause": "A required external adapter capability is missing from the pack.",
"operator_action": "Regenerate the adapter pack manifest and confirm graph, event, policy, package, audit, semantic, and registry adapters are declared.",
},
]
return {
"schema_version": TROUBLESHOOTING_MATRIX_SCHEMA,
"id": f"operator-troubleshooting:{stable_digest(rows)}",
"required_categories": list(TROUBLESHOOTING_REQUIRED_CATEGORIES),
"rows": rows,
}
def validate_operator_troubleshooting_matrix(matrix: dict[str, Any]) -> dict[str, Any]:
diagnostics: list[Diagnostic] = []
rows = matrix.get("rows") if isinstance(matrix.get("rows"), list) else []
categories = {str(row.get("category") or "") for row in rows if isinstance(row, dict)}
missing = sorted(set(TROUBLESHOOTING_REQUIRED_CATEGORIES) - categories)
if matrix.get("schema_version") != TROUBLESHOOTING_MATRIX_SCHEMA:
diagnostics.append(
Diagnostic(
"error",
"troubleshooting_matrix_schema_mismatch",
"Troubleshooting matrix has an unexpected schema version.",
"schema_version",
{"expected": TROUBLESHOOTING_MATRIX_SCHEMA},
)
)
if missing:
diagnostics.append(
Diagnostic(
"error",
"troubleshooting_matrix_missing_category",
"Troubleshooting matrix must cover every required operator category.",
"rows",
{"missing": missing},
)
)
required_fields = ("category", "diagnostic_code", "signal", "likely_cause", "operator_action")
for index, row in enumerate(rows):
if not isinstance(row, dict):
diagnostics.append(
Diagnostic("error", "troubleshooting_matrix_invalid_row", "Troubleshooting row must be an object.", f"rows.{index}")
)
continue
for field in required_fields:
if not row.get(field):
diagnostics.append(
Diagnostic(
"error",
"troubleshooting_matrix_missing_field",
"Troubleshooting row is missing a required field.",
f"rows.{index}.{field}",
)
)
return {
"schema_version": f"{TROUBLESHOOTING_MATRIX_SCHEMA}.validation",
"valid": not any(diagnostic.severity == "error" for diagnostic in diagnostics),
"diagnostics": [diagnostic.to_dict() for diagnostic in diagnostics],
}