From 3f21f9045175d8d66673004d864b4e3f44c8a900 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:19:45 +0000 Subject: [PATCH 01/89] chore(manifest): refresh cognitive-state anchors --- .claude.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude.json b/.claude.json index adf2b2500..6b29bdf53 100644 --- a/.claude.json +++ b/.claude.json @@ -9,8 +9,8 @@ "provenance": { "generator": "scripts/build-cognitive-manifest.py", "repository": "Aegis-Omega/AEGIS-OMEGA", - "source_ref": "feat/automaton-3-sovereign-execution", - "parent_state_hash": "37ec8f38ff2453dcd59c292f3c865a5ddeb893b5dadc52a80a06762d2b6982da", + "source_ref": "feat/sol-cross-platform-control-plane", + "parent_state_hash": "410bcd49c721e65050382ae759db7af2f41fa9f572e625174252c72a8748ea93", "signature_mode": "GITHUB_OIDC_ATTESTATION" }, "hashing": { @@ -488,5 +488,5 @@ "on_success": "broadcast-attested-verified-event-stream" } }, - "state_hash": "410bcd49c721e65050382ae759db7af2f41fa9f572e625174252c72a8748ea93" + "state_hash": "6b8012c545f8a49fc41c378729f75e4eba07574b05ce38b0b86df826124b1ab4" } From 40ef577d28efdcff15b39d4322fafae27f97d7ad Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:20:48 +0200 Subject: [PATCH 02/89] feat(sol): define governed cross-platform control plane --- platform/sol/SPEC.md | 214 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 platform/sol/SPEC.md diff --git a/platform/sol/SPEC.md b/platform/sol/SPEC.md new file mode 100644 index 000000000..621e549e9 --- /dev/null +++ b/platform/sol/SPEC.md @@ -0,0 +1,214 @@ +# SOL Cross-Platform Control Plane — Specification + +Status: DRAFT / non-production +Owner: AEGIS-Ω operator +Authority root: Automaton-3 + +## Purpose + +SOL is the governed integration layer that connects model runtimes, knowledge stores, verifiers, design surfaces, deployment infrastructure, and operator clients without granting any provider independent mutation authority. + +The system follows one invariant: + +```text +provider proposes or computes +AEGIS authorizes +provider executes within the granted capability +AEGIS records the result and state transition +``` + +No platform listed here is a constitutional authority. Automaton-3 remains the sole authority evaluator for consequential actions. + +## First implementation slice + +1. OpenAI Agents SDK orchestrates bounded workflows and specialist handoffs. +2. The existing AEGIS MCP server is the canonical tool surface. +3. Cloudflare hosts the remote MCP edge and durable execution coordination. +4. GitHub stores reviewed source, policy, schemas, and admission evidence. +5. SharePoint stores published operator knowledge and approved runbooks. +6. Hugging Face stores model cards, evaluation datasets, and immutable evaluation bundles. +7. NVIDIA supplies GPU inference and accelerated evaluation workers. +8. Wolfram supplies deterministic mathematical verification. +9. Dataverse stores governed business entities and workflow state. +10. Figma is the editable product-design source; generated UI must not become authority state. +11. Web and iOS clients are operator control surfaces only. +12. oGemma/MYTHOS is a T2 advisory holon whose verdict is evidence, never unilateral authority. + +## Application archetype + +Primary archetype: interactive-decoupled ChatGPT/MCP app. + +Data tools return concise structured content. Render tools attach operator-console resources. Mutating tools require an Automaton-3 admission decision before any provider call. + +## Trust boundaries + +### T0 — constitutional authority + +- Automaton-3 evaluator +- consequence policy +- capability evidence +- writer lease and fencing token +- mutation receipt + +### T1 — deterministic verification + +- schema validation +- digest verification +- Wolfram verification results +- provider response normalization +- GitHub OIDC attestations + +### T2 — engineering evidence + +- model and agent evaluations +- oGemma/MYTHOS holon verdicts +- NVIDIA runtime telemetry +- operator-console diagnostics + +### T3 — advisory content + +- model reasoning +- generated plans +- design proposals +- narrative summaries + +T2 and T3 inputs cannot upgrade themselves into authority. + +## Consequence classes + +- D0: read-only observation +- D1: reversible local state +- D2: shared-state mutation +- D3: external or costly mutation +- D4: irreversible, destructive, privileged, or high-impact mutation + +D2+ requires explicit capability evidence, expected parent state, idempotency semantics, and a mutation receipt. D3+ requires operator approval. D4 remains denied until a dedicated policy and recovery procedure are admitted. + +## Canonical execution envelope + +Every provider operation must be represented by the schema in `contracts/execution-request.v1.schema.json` and include: + +- stable request ID +- actor and agent identity +- provider and capability +- consequence class +- target and normalized arguments digest +- expected parent state root +- writer lease generation +- idempotency key +- compensation reference when applicable +- operator approval reference when required + +## Canonical result envelope + +Every provider result must include: + +- request ID +- provider operation ID +- normalized status +- output digest +- external state reference +- observed completion time +- receipt root +- error class when unsuccessful + +Provider-native IDs and timestamps are evidence, not the canonical state root. + +## Platform responsibilities + +### OpenAI + +- Use the Responses API / Agents SDK for orchestration. +- Begin with one primary agent and narrow function tools. +- Use structured outputs for contracts. +- Keep approval boundaries explicit. +- Add evals against the real governed tool path. +- Use SOL for difficult synthesis; use lower-cost model classes only after evaluation proves parity for the task. + +### Cloudflare + +- Remote MCP endpoint at `/mcp`. +- OAuth for user-specific tools. +- Durable Objects or Workflows for durable coordination. +- bindings over Cloudflare REST calls. +- secrets only through the secret store. +- structured logs, traces, and sampled observability. +- no request-scoped mutable global state. + +### GitHub + +- Source of reviewed implementation and policy. +- All changes through branches and pull requests. +- Exact-head CI, dependency scanning, schema validation, adversarial tests, replay artifacts, and OIDC attestations. +- No merge or deployment from this specification alone. + +### SharePoint + +- Published, human-readable operating knowledge. +- Content is versioned and linked to Git commit and receipt roots. +- SharePoint documents never override repository policy or executable schemas. + +### Hugging Face + +- Publish honest model cards, evaluation datasets, and evaluation bundles. +- Pin base-model and dataset revisions. +- Separate model weights, adapters, prompts, and governance artifacts. +- oGemma has no custom weights unless independently produced and documented. + +### NVIDIA + +- Accelerated inference and evaluation workers. +- Runtime identity, image digest, driver/runtime versions, model digest, and benchmark output must be captured. +- GPU execution cannot bypass AEGIS admission. + +### Wolfram + +- Deterministic verification service for equations, invariants, units, and symbolic claims. +- Verification output must include exact input expression and normalized result. +- Failed or indeterminate verification cannot be represented as proof. + +### Dataverse + +- Governed business entities, relationships, and workflow projections. +- Mutations are idempotent and keyed by AEGIS request ID. +- Dataverse is a projection store, not the constitutional source of truth. + +### Figma and product design + +- Figma is the editable design source. +- Design tokens and component mappings are exported through reviewed artifacts. +- Operator-console UX must show authority decision, consequence class, provider, state root, and receipt before confirmation. + +### iOS + +- SwiftUI client uses narrow state ownership and explicit dependency injection. +- App Intents expose only high-value verbs: inspect execution, review decision, continue approved workflow. +- Intents do not perform D2+ mutations without opening the app for governed approval. + +## oGemma/MYTHOS integration + +The current implementation contains an important incomplete path: `POST_VALIDATE` and full `POST_REVIEW` integration are described but not fully wired into the pipeline. The adapter must: + +1. validate biological-state input bounds; +2. bind the task, plan digest, gate name, model identity, prompt version, and state digest into the verdict envelope; +3. reject unknown gates rather than returning APPROVED; +4. submit each gate result through the canonical AEGIS evidence path; +5. treat the holon as advisory evidence with an explicit evidence tier; +6. never convert its quorum weight into unilateral mutation authority; +7. add replay tests for every gate and malformed input. + +## Minimum working contract + +The first PR is complete only when it provides: + +- this specification; +- versioned execution and result schemas; +- a machine-readable platform registry; +- an oGemma adapter contract and regression cases; +- a remote MCP deployment plan for Cloudflare; +- an OpenAI agent contract and evaluation matrix; +- web and iOS operator-surface contracts; +- SharePoint publishing structure; +- CI checks that validate all new JSON and JSONL artifacts. + +Production deployment, DNS mutation, OAuth application creation, secret provisioning, model publication, Dataverse schema migration, and merge to `main` are explicitly outside this draft PR. \ No newline at end of file From 0858defc5f18fadfdaefa0e218527bea19e2a997 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:21:34 +0200 Subject: [PATCH 03/89] feat(sol): add canonical execution request schema --- .../execution-request.v1.schema.json | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 platform/sol/contracts/execution-request.v1.schema.json diff --git a/platform/sol/contracts/execution-request.v1.schema.json b/platform/sol/contracts/execution-request.v1.schema.json new file mode 100644 index 000000000..47965ce4e --- /dev/null +++ b/platform/sol/contracts/execution-request.v1.schema.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://aegisomega.com/schemas/sol/execution-request.v1.schema.json", + "title": "SOL Governed Execution Request", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "request_id", + "actor", + "agent", + "provider", + "capability", + "consequence_class", + "target", + "arguments_digest", + "expected_parent_state_root", + "lease_generation", + "idempotency_key", + "requested_at" + ], + "properties": { + "schema_version": { "const": "1.0.0" }, + "request_id": { "type": "string", "format": "uuid" }, + "actor": { + "type": "object", + "additionalProperties": false, + "required": ["id", "type"], + "properties": { + "id": { "type": "string", "minLength": 1, "maxLength": 256 }, + "type": { "enum": ["operator", "service", "agent"] } + } + }, + "agent": { + "type": "object", + "additionalProperties": false, + "required": ["id", "runtime", "model"], + "properties": { + "id": { "type": "string", "minLength": 1, "maxLength": 256 }, + "runtime": { "type": "string", "minLength": 1, "maxLength": 256 }, + "model": { "type": "string", "minLength": 1, "maxLength": 256 }, + "prompt_digest": { "$ref": "#/$defs/sha256" } + } + }, + "provider": { + "enum": ["github", "sharepoint", "dataverse", "nvidia", "huggingface", "wolfram", "openai", "figma", "cloudflare", "ios", "web", "ogemma"] + }, + "capability": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._:-]{2,127}$" }, + "consequence_class": { "enum": ["D0", "D1", "D2", "D3", "D4"] }, + "target": { "type": "string", "minLength": 1, "maxLength": 2048 }, + "arguments_digest": { "$ref": "#/$defs/sha256" }, + "expected_parent_state_root": { "$ref": "#/$defs/sha256" }, + "lease_generation": { "type": "integer", "minimum": 0 }, + "idempotency_key": { "type": "string", "minLength": 8, "maxLength": 256 }, + "compensation_reference": { "type": "string", "minLength": 1, "maxLength": 2048 }, + "operator_approval_reference": { "type": "string", "minLength": 1, "maxLength": 2048 }, + "requested_at": { "type": "string", "format": "date-time" }, + "metadata": { "type": "object", "additionalProperties": true } + }, + "allOf": [ + { + "if": { "properties": { "consequence_class": { "enum": ["D3", "D4"] } } }, + "then": { "required": ["operator_approval_reference"] } + } + ], + "$defs": { + "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" } + } +} \ No newline at end of file From 642cfab4f07e94d561aea15db62dd41250a5a2ae Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:22:16 +0200 Subject: [PATCH 04/89] feat(sol): add canonical execution result schema --- .../contracts/execution-result.v1.schema.json | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 platform/sol/contracts/execution-result.v1.schema.json diff --git a/platform/sol/contracts/execution-result.v1.schema.json b/platform/sol/contracts/execution-result.v1.schema.json new file mode 100644 index 000000000..69fde6175 --- /dev/null +++ b/platform/sol/contracts/execution-result.v1.schema.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://aegisomega.com/schemas/sol/execution-result.v1.schema.json", + "title": "SOL Governed Execution Result", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "request_id", + "provider", + "status", + "output_digest", + "observed_at", + "receipt_root" + ], + "properties": { + "schema_version": { "const": "1.0.0" }, + "request_id": { "type": "string", "format": "uuid" }, + "provider": { + "enum": ["github", "sharepoint", "dataverse", "nvidia", "huggingface", "wolfram", "openai", "figma", "cloudflare", "ios", "web", "ogemma"] + }, + "provider_operation_id": { "type": "string", "minLength": 1, "maxLength": 512 }, + "status": { "enum": ["COMMITTED", "DENIED", "FAILED", "INDETERMINATE", "COMPENSATED"] }, + "output_digest": { "$ref": "#/$defs/sha256" }, + "external_state_reference": { "type": "string", "minLength": 1, "maxLength": 2048 }, + "observed_at": { "type": "string", "format": "date-time" }, + "receipt_root": { "$ref": "#/$defs/sha256" }, + "error": { + "type": "object", + "additionalProperties": false, + "required": ["class", "retryable"], + "properties": { + "class": { "type": "string", "minLength": 1, "maxLength": 128 }, + "message_digest": { "$ref": "#/$defs/sha256" }, + "retryable": { "type": "boolean" } + } + }, + "metadata": { "type": "object", "additionalProperties": true } + }, + "allOf": [ + { + "if": { "properties": { "status": { "enum": ["FAILED", "INDETERMINATE"] } } }, + "then": { "required": ["error"] } + } + ], + "$defs": { + "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" } + } +} \ No newline at end of file From 8640e3edbb8b538fff502556eddc6e83c8a33b72 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:23:17 +0200 Subject: [PATCH 05/89] feat(sol): register governed platform capabilities --- .../sol/contracts/platform-registry.v1.json | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 platform/sol/contracts/platform-registry.v1.json diff --git a/platform/sol/contracts/platform-registry.v1.json b/platform/sol/contracts/platform-registry.v1.json new file mode 100644 index 000000000..7f0a64dcf --- /dev/null +++ b/platform/sol/contracts/platform-registry.v1.json @@ -0,0 +1,104 @@ +{ + "schema_version": "1.0.0", + "authority_root": "automaton-3", + "default_policy": "deny", + "platforms": [ + { + "id": "openai", + "role": "agent_runtime", + "canonical_interface": "responses-api-and-agents-sdk", + "allowed_capabilities": ["agent.plan", "agent.handoff", "agent.evaluate", "agent.summarize"], + "forbidden_capabilities": ["authority.grant", "receipt.forge", "policy.override"], + "default_consequence_class": "D0" + }, + { + "id": "cloudflare", + "role": "edge_and_durable_execution", + "canonical_interface": "remote-mcp-workers-durable-objects-workflows", + "allowed_capabilities": ["mcp.serve", "workflow.coordinate", "telemetry.emit", "state.read"], + "forbidden_capabilities": ["secret.readback", "authority.grant", "policy.override"], + "default_consequence_class": "D2" + }, + { + "id": "github", + "role": "source_policy_and_admission_evidence", + "canonical_interface": "pull-request-and-actions", + "allowed_capabilities": ["repo.read", "branch.create", "file.propose", "pull-request.create", "status.read"], + "forbidden_capabilities": ["main.force-push", "protection.disable", "secret.readback"], + "default_consequence_class": "D2" + }, + { + "id": "sharepoint", + "role": "published_operator_knowledge", + "canonical_interface": "microsoft-graph-drive-items", + "allowed_capabilities": ["knowledge.search", "knowledge.read", "knowledge.publish-versioned"], + "forbidden_capabilities": ["policy.override", "receipt.rewrite"], + "default_consequence_class": "D2" + }, + { + "id": "dataverse", + "role": "governed_business_state_projection", + "canonical_interface": "dataverse-web-api", + "allowed_capabilities": ["entity.read", "entity.upsert-idempotent", "relationship.read"], + "forbidden_capabilities": ["authority.grant", "canonical-ledger-rewrite"], + "default_consequence_class": "D2" + }, + { + "id": "huggingface", + "role": "models_datasets_and_evaluations", + "canonical_interface": "hub-and-lighteval", + "allowed_capabilities": ["model.read", "dataset.read", "eval.run", "eval.publish", "card.publish"], + "forbidden_capabilities": ["unreviewed-weight-publish", "secret.readback", "authority.grant"], + "default_consequence_class": "D2" + }, + { + "id": "nvidia", + "role": "accelerated_inference_and_evaluation", + "canonical_interface": "containerized-gpu-worker", + "allowed_capabilities": ["inference.run", "benchmark.run", "telemetry.emit"], + "forbidden_capabilities": ["authority.grant", "policy.override"], + "default_consequence_class": "D3" + }, + { + "id": "wolfram", + "role": "deterministic_verifier", + "canonical_interface": "wolfram-language-or-alpha-api", + "allowed_capabilities": ["expression.verify", "units.verify", "invariant.evaluate"], + "forbidden_capabilities": ["authority.grant", "indeterminate-as-proof"], + "default_consequence_class": "D0" + }, + { + "id": "figma", + "role": "editable_product_design_source", + "canonical_interface": "figma-file-and-design-system", + "allowed_capabilities": ["design.read", "prototype.propose", "tokens.export-reviewed"], + "forbidden_capabilities": ["authority.grant", "policy.override"], + "default_consequence_class": "D1" + }, + { + "id": "ogemma", + "role": "advisory_biological_holon", + "canonical_interface": "verdict-envelope", + "allowed_capabilities": ["holon.evaluate", "holon.emit-evidence"], + "forbidden_capabilities": ["mutation.execute", "authority.grant", "unknown-gate-approve"], + "default_consequence_class": "D0", + "evidence_tier": "T2" + }, + { + "id": "ios", + "role": "operator_client", + "canonical_interface": "swiftui-and-app-intents", + "allowed_capabilities": ["execution.inspect", "decision.review", "workflow.continue-approved"], + "forbidden_capabilities": ["background-d2-mutation", "authority.grant"], + "default_consequence_class": "D0" + }, + { + "id": "web", + "role": "operator_console", + "canonical_interface": "react-mcp-widget", + "allowed_capabilities": ["execution.inspect", "receipt.verify", "approval.propose"], + "forbidden_capabilities": ["authority.grant", "hidden-mutation"], + "default_consequence_class": "D0" + } + ] +} \ No newline at end of file From 73e07b3fa2272eccc3e52e30227a1a8635a5ec8e Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:24:55 +0200 Subject: [PATCH 06/89] feat(sol): add fail-closed oGemma verdict adapter --- platform/sol/adapters/ogemma.py | 186 ++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 platform/sol/adapters/ogemma.py diff --git a/platform/sol/adapters/ogemma.py b/platform/sol/adapters/ogemma.py new file mode 100644 index 000000000..78677d355 --- /dev/null +++ b/platform/sol/adapters/ogemma.py @@ -0,0 +1,186 @@ +"""Fail-closed adapter for oGemma/MYTHOS advisory verdicts. + +This module does not execute a model or mutate AEGIS state. It validates and +normalizes a holon verdict into evidence that can be submitted to the canonical +AEGIS authority path. Automaton-3 remains the only authority evaluator. +""" + +from __future__ import annotations + +from dataclasses import asdict, dataclass +from hashlib import sha256 +import json +from typing import Any, Final, Literal, Mapping + +Gate = Literal["PRE_ORCHESTRATE", "POST_VALIDATE", "POST_REVIEW"] +Verdict = Literal["APPROVED", "FAILED"] + +ALLOWED_GATES: Final[frozenset[str]] = frozenset( + {"PRE_ORCHESTRATE", "POST_VALIDATE", "POST_REVIEW"} +) +ALLOWED_VERDICTS: Final[frozenset[str]] = frozenset({"APPROVED", "FAILED"}) + + +class OgemmaEvidenceError(ValueError): + """Raised when oGemma evidence is malformed or cannot be admitted.""" + + +@dataclass(frozen=True, slots=True) +class BioState: + stress: float + attention: float + rir: float + atp: int + + +@dataclass(frozen=True, slots=True) +class OgemmaEvidence: + schema_version: str + evidence_tier: str + holon_id: str + gate: Gate + verdict: Verdict + confidence: float + reason_code: str + task_digest: str + plan_digest: str + prompt_digest: str + model_identity: str + bio_state: BioState + bio_state_digest: str + evidence_digest: str + + +def _canonical_json(value: Mapping[str, Any]) -> bytes: + return json.dumps( + value, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + ).encode("utf-8") + + +def _digest_text(value: str) -> str: + return sha256(value.encode("utf-8")).hexdigest() + + +def _require_sha256(name: str, value: str) -> str: + if len(value) != 64 or any(ch not in "0123456789abcdef" for ch in value): + raise OgemmaEvidenceError(f"{name} must be a lowercase SHA-256 digest") + return value + + +def validate_bio_state(raw: Mapping[str, Any]) -> BioState: + required = {"stress", "attention", "rir", "atp"} + if set(raw) != required: + missing = sorted(required - set(raw)) + extra = sorted(set(raw) - required) + raise OgemmaEvidenceError(f"invalid bio_state keys: missing={missing}, extra={extra}") + + values: dict[str, float | int] = {} + for name in ("stress", "attention", "rir"): + value = raw[name] + if isinstance(value, bool) or not isinstance(value, (int, float)): + raise OgemmaEvidenceError(f"bio_state.{name} must be numeric") + normalized = float(value) + if not 0.0 <= normalized <= 1.0: + raise OgemmaEvidenceError(f"bio_state.{name} must be in [0, 1]") + values[name] = normalized + + atp = raw["atp"] + if isinstance(atp, bool) or not isinstance(atp, int): + raise OgemmaEvidenceError("bio_state.atp must be an integer") + if not 0 <= atp <= 2500: + raise OgemmaEvidenceError("bio_state.atp must be in [0, 2500]") + values["atp"] = atp + + return BioState( + stress=float(values["stress"]), + attention=float(values["attention"]), + rir=float(values["rir"]), + atp=int(values["atp"]), + ) + + +def normalize_verdict( + *, + holon_id: str, + gate: str, + verdict: str, + confidence: float, + reason_code: str, + task: str, + plan_digest: str, + prompt_digest: str, + model_identity: str, + bio_state: Mapping[str, Any], +) -> OgemmaEvidence: + """Validate and bind an advisory verdict into a replayable evidence envelope.""" + + if gate not in ALLOWED_GATES: + raise OgemmaEvidenceError(f"unknown gate denied: {gate!r}") + if verdict not in ALLOWED_VERDICTS: + raise OgemmaEvidenceError(f"unknown verdict denied: {verdict!r}") + if not holon_id.strip(): + raise OgemmaEvidenceError("holon_id is required") + if not model_identity.strip(): + raise OgemmaEvidenceError("model_identity is required") + if not reason_code.strip(): + raise OgemmaEvidenceError("reason_code is required") + if isinstance(confidence, bool) or not isinstance(confidence, (int, float)): + raise OgemmaEvidenceError("confidence must be numeric") + confidence_value = float(confidence) + if not 0.0 <= confidence_value <= 1.0: + raise OgemmaEvidenceError("confidence must be in [0, 1]") + + state = validate_bio_state(bio_state) + plan_hash = _require_sha256("plan_digest", plan_digest) + prompt_hash = _require_sha256("prompt_digest", prompt_digest) + state_payload = asdict(state) + state_digest = sha256(_canonical_json(state_payload)).hexdigest() + + unsigned: dict[str, Any] = { + "schema_version": "1.0.0", + "evidence_tier": "T2", + "holon_id": holon_id, + "gate": gate, + "verdict": verdict, + "confidence": confidence_value, + "reason_code": reason_code, + "task_digest": _digest_text(task), + "plan_digest": plan_hash, + "prompt_digest": prompt_hash, + "model_identity": model_identity, + "bio_state": state_payload, + "bio_state_digest": state_digest, + } + evidence_digest = sha256(_canonical_json(unsigned)).hexdigest() + + return OgemmaEvidence( + schema_version="1.0.0", + evidence_tier="T2", + holon_id=holon_id, + gate=gate, # type: ignore[arg-type] + verdict=verdict, # type: ignore[arg-type] + confidence=confidence_value, + reason_code=reason_code, + task_digest=unsigned["task_digest"], + plan_digest=plan_hash, + prompt_digest=prompt_hash, + model_identity=model_identity, + bio_state=state, + bio_state_digest=state_digest, + evidence_digest=evidence_digest, + ) + + +def to_authority_evidence(evidence: OgemmaEvidence) -> dict[str, Any]: + """Return the bounded payload Automaton-3 may evaluate as T2 evidence.""" + + payload = asdict(evidence) + return { + "source": "ogemma-mythos-holon", + "evidence_tier": "T2", + "grants_authority": False, + "payload": payload, + } From 1d8efaec28ec1bd377bec2fb5aea336d343b43ce Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:25:36 +0200 Subject: [PATCH 07/89] test(sol): cover oGemma adapter fail-closed behavior --- platform/sol/tests/test_ogemma_adapter.py | 101 ++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 platform/sol/tests/test_ogemma_adapter.py diff --git a/platform/sol/tests/test_ogemma_adapter.py b/platform/sol/tests/test_ogemma_adapter.py new file mode 100644 index 000000000..f1c615527 --- /dev/null +++ b/platform/sol/tests/test_ogemma_adapter.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +from hashlib import sha256 +import unittest + +from platform.sol.adapters.ogemma import ( + OgemmaEvidenceError, + normalize_verdict, + to_authority_evidence, +) + + +ZERO = sha256(b"").hexdigest() + + +class OgemmaAdapterTests(unittest.TestCase): + def valid(self, **overrides): + payload = { + "holon_id": "gemma-4e4b-iphone", + "gate": "PRE_ORCHESTRATE", + "verdict": "APPROVED", + "confidence": 0.94, + "reason_code": "NOMINAL", + "task": "inspect current execution", + "plan_digest": ZERO, + "prompt_digest": ZERO, + "model_identity": "google/gemma-3n-E4B-it@pinned-revision", + "bio_state": { + "stress": 0.42, + "attention": 0.82, + "rir": 0.95, + "atp": 2100, + }, + } + payload.update(overrides) + return normalize_verdict(**payload) + + def test_valid_evidence_is_advisory(self): + evidence = self.valid() + wrapped = to_authority_evidence(evidence) + self.assertEqual(wrapped["evidence_tier"], "T2") + self.assertFalse(wrapped["grants_authority"]) + self.assertEqual(len(evidence.evidence_digest), 64) + + def test_unknown_gate_is_denied(self): + with self.assertRaises(OgemmaEvidenceError): + self.valid(gate="UNKNOWN_GATE") + + def test_unknown_verdict_is_denied(self): + with self.assertRaises(OgemmaEvidenceError): + self.valid(verdict="PENDING") + + def test_out_of_range_bio_state_is_denied(self): + with self.assertRaises(OgemmaEvidenceError): + self.valid( + bio_state={ + "stress": 1.1, + "attention": 0.82, + "rir": 0.95, + "atp": 2100, + } + ) + + def test_missing_bio_field_is_denied(self): + with self.assertRaises(OgemmaEvidenceError): + self.valid( + bio_state={ + "stress": 0.4, + "attention": 0.8, + "atp": 2100, + } + ) + + def test_invalid_digest_is_denied(self): + with self.assertRaises(OgemmaEvidenceError): + self.valid(plan_digest="not-a-digest") + + def test_evidence_digest_is_deterministic(self): + self.assertEqual(self.valid().evidence_digest, self.valid().evidence_digest) + + def test_task_changes_evidence_digest(self): + self.assertNotEqual( + self.valid(task="inspect current execution").evidence_digest, + self.valid(task="inspect another execution").evidence_digest, + ) + + def test_bio_state_changes_evidence_digest(self): + changed = { + "stress": 0.43, + "attention": 0.82, + "rir": 0.95, + "atp": 2100, + } + self.assertNotEqual( + self.valid().evidence_digest, + self.valid(bio_state=changed).evidence_digest, + ) + + +if __name__ == "__main__": + unittest.main() From 4d68e48d32d1ec80489e5d0ba76fc731adddff53 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:26:42 +0200 Subject: [PATCH 08/89] fix(sol): load adapter without shadowing Python platform module --- platform/sol/tests/test_ogemma_adapter.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/platform/sol/tests/test_ogemma_adapter.py b/platform/sol/tests/test_ogemma_adapter.py index f1c615527..d3daf35b5 100644 --- a/platform/sol/tests/test_ogemma_adapter.py +++ b/platform/sol/tests/test_ogemma_adapter.py @@ -1,9 +1,14 @@ from __future__ import annotations from hashlib import sha256 +from pathlib import Path +import sys import unittest -from platform.sol.adapters.ogemma import ( +ADAPTER_DIR = Path(__file__).resolve().parents[1] / "adapters" +sys.path.insert(0, str(ADAPTER_DIR)) + +from ogemma import ( # noqa: E402 OgemmaEvidenceError, normalize_verdict, to_authority_evidence, From 088d7ef40d005d5ca372d7ae4aacf9f6a9cfcedc Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:27:37 +0200 Subject: [PATCH 09/89] ci(sol): validate cross-platform contracts and oGemma adapter --- .github/workflows/sol-integration.yml | 62 +++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/sol-integration.yml diff --git a/.github/workflows/sol-integration.yml b/.github/workflows/sol-integration.yml new file mode 100644 index 000000000..10e417526 --- /dev/null +++ b/.github/workflows/sol-integration.yml @@ -0,0 +1,62 @@ +name: SOL Cross-Platform Integration + +on: + pull_request: + branches: [main] + paths: + - 'platform/sol/**' + - '.github/workflows/sol-integration.yml' + merge_group: + branches: [main] + push: + branches: [main] + paths: + - 'platform/sol/**' + - '.github/workflows/sol-integration.yml' + +permissions: + contents: read + +jobs: + contracts: + name: aegis / sol-contracts + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout exact candidate + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install pinned schema validator + run: python -m pip install jsonschema==4.23.0 + + - name: Validate JSON and schemas + shell: bash + run: | + set -euo pipefail + python - <<'PY' + import json + from pathlib import Path + from jsonschema.validators import validator_for + + root = Path('platform/sol') + for path in sorted(root.rglob('*.json')): + data = json.loads(path.read_text(encoding='utf-8')) + if path.name.endswith('.schema.json'): + validator_for(data).check_schema(data) + print(f'JSON_OK {path}') + PY + + - name: Run fail-closed oGemma adapter tests + run: python -m unittest discover -s platform/sol/tests -p 'test_*.py' -v + + - name: Verify no production credentials or mutable state are committed + shell: bash + run: | + set -euo pipefail + ! grep -RInE '(hf_[A-Za-z0-9]{20,}|sk-[A-Za-z0-9_-]{20,}|BEGIN (RSA|OPENSSH|EC) PRIVATE KEY)' platform/sol + ! find platform/sol -type f \( -name '*.sqlite' -o -name '*.db' -o -name '*.pem' -o -name '.env' \) -print -quit | grep . From c2af059a559821e0101f7da399797af1da59b854 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:28:48 +0200 Subject: [PATCH 10/89] docs(sol): define OpenAI agent runtime contract --- platform/sol/openai/AGENT_RUNTIME.md | 89 ++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 platform/sol/openai/AGENT_RUNTIME.md diff --git a/platform/sol/openai/AGENT_RUNTIME.md b/platform/sol/openai/AGENT_RUNTIME.md new file mode 100644 index 000000000..17f691df6 --- /dev/null +++ b/platform/sol/openai/AGENT_RUNTIME.md @@ -0,0 +1,89 @@ +# OpenAI Agent Runtime Contract + +The OpenAI layer is a bounded orchestrator, not an authority source. + +## Runtime selection + +- Use the Responses API and OpenAI Agents SDK. +- Default difficult architecture, security review, and synthesis work to `gpt-5.6-sol`. +- Evaluate `gpt-5.6-terra` and `gpt-5.6-luna` on representative workloads before routing lower-risk or high-volume tasks to them. +- Begin with one primary agent. Add specialists only when evals show a measurable gain. +- Use Programmatic Tool Calling only for bounded, non-mutating reduction stages where intermediate results can be processed deterministically. + +## Primary agent + +Name: `sol-operator` + +Goal: turn operator intent into a reviewed execution plan, gather evidence through read-only tools, request AEGIS admission for consequential actions, execute only admitted provider calls, and return receipt-backed results. + +The agent must not: + +- grant itself authority; +- infer approval from conversational tone; +- retry non-idempotent mutations without a new admission decision; +- treat advisory model output as proof; +- expose secrets or secret-derived values; +- claim a provider mutation succeeded without provider evidence and an AEGIS result envelope. + +## Specialist handoffs + +Specialists are optional and must have disjoint tool surfaces: + +- `knowledge-retriever`: GitHub, SharePoint, Hugging Face read-only search/fetch. +- `formal-verifier`: Wolfram calculations and invariant checking. +- `model-evaluator`: Hugging Face datasets/evals and NVIDIA benchmark execution. +- `design-reviewer`: Figma/design-source inspection and UX evidence. +- `release-engineer`: GitHub branch/PR preparation and Cloudflare deployment plans. + +No specialist receives direct D2+ provider tools. Consequential actions return to `sol-operator` for Automaton-3 admission. + +## Tool contract + +Each tool is one job with: + +- explicit JSON schema; +- accurate `readOnlyHint`, `destructiveHint`, `openWorldHint`, and `idempotentHint` annotations; +- normalized errors; +- bounded output; +- provider operation ID where available; +- no secret-bearing response fields. + +Connector-like knowledge tools use the standard `search` and `fetch` shapes. Mutating tools consume `execution-request.v1` and return `execution-result.v1`. + +## Approval policy + +- D0: automatic read-only execution. +- D1: automatic only when local, reversible, and isolated. +- D2: requires Automaton-3 admission and idempotency key. +- D3: requires explicit operator approval plus Automaton-3 admission. +- D4: denied until a dedicated admitted policy exists. + +## State and memory + +- Keep canonical state outside the model context. +- Treat conversation memory as advisory. +- Persist only normalized state references and receipt roots. +- Do not store raw secrets, access tokens, or private file contents in long-lived agent memory. +- Use `previous_response_id` only while the operator goal and trust boundary remain stable. + +## Evaluation matrix + +The real agent path must be tested for: + +1. correct read-only tool selection; +2. missing evidence denial; +3. stale parent-state denial; +4. expired lease denial; +5. replay rejection; +6. required operator approval; +7. forbidden direct provider mutation; +8. provider timeout and retry classification; +9. provider success with lost client response; +10. conflicting provider state; +11. prompt injection in retrieved content; +12. cross-agent privilege escalation; +13. oGemma evidence remaining T2; +14. Wolfram indeterminate result not becoming proof; +15. SharePoint content not overriding repository policy. + +Each eval records tool calls, authority decision, provider evidence, receipt root, trace ID, and final answer completeness. Exact prose is not graded unless contractual. \ No newline at end of file From b05e3bd74f8df4876ae0d75f67140733dc7b9328 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:29:28 +0200 Subject: [PATCH 11/89] docs(sol): define Cloudflare remote MCP deployment contract --- platform/sol/cloudflare/REMOTE_MCP.md | 59 +++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 platform/sol/cloudflare/REMOTE_MCP.md diff --git a/platform/sol/cloudflare/REMOTE_MCP.md b/platform/sol/cloudflare/REMOTE_MCP.md new file mode 100644 index 000000000..581623c20 --- /dev/null +++ b/platform/sol/cloudflare/REMOTE_MCP.md @@ -0,0 +1,59 @@ +# Cloudflare Remote MCP Contract + +Target: authenticated remote MCP server on Cloudflare Workers. + +## Runtime + +- `/mcp` is the only public protocol endpoint. +- OAuth is mandatory for user-specific tools. +- Durable Objects or Workflows own durable coordination; request state never lives in module globals. +- D1/KV/R2/Queues/Workflows are accessed through bindings, not Cloudflare REST calls from inside Workers. +- External databases use Hyperdrive. +- All promises are awaited, returned, explicitly voided, or attached to `ctx.waitUntil()`. +- Large or unknown responses are streamed. + +## Security + +- Secrets are provisioned through Wrangler/Secrets Store and never committed. +- Secret comparisons use timing-safe cryptographic comparison. +- Production CORS and CSP use exact allowlists. +- No `passThroughOnException`. +- No public unauthenticated mutation tools. +- OAuth subject is bound into the AEGIS execution identity envelope. +- Provider tokens are scoped per capability and environment. + +## AEGIS flow + +```text +MCP request + -> authenticate operator/service + -> normalize tool input + -> construct execution-request.v1 + -> call Automaton-3 authority evaluator + -> deny with receipt OR execute provider call + -> normalize execution-result.v1 + -> persist durable event and emit receipt +``` + +## Configuration requirements + +- current `compatibility_date` at implementation time; +- `nodejs_compat` only when required by dependencies; +- generated `Env` types from `wrangler types`; +- structured observability with sampling; +- service bindings for internal Worker-to-Worker calls; +- separate preview and production environments; +- explicit CPU, memory, duration, retry, and queue limits. + +## Deployment gate + +Deployment is blocked until: + +- exact Worker code exists and compiles; +- Wrangler schema validates; +- local MCP Inspector tests pass; +- OAuth callback and token rotation are documented; +- replay and idempotency tests pass; +- ChatGPT Developer Mode test succeeds over HTTPS; +- production domain, CSP, and privacy policy are approved; +- Automaton-3 admits the exact deployment candidate. From 5a8fba911986e89697ebb5f3c5b0c90d5adb5413 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:30:56 +0200 Subject: [PATCH 12/89] docs(sol): define iOS operator and App Intents contract --- platform/sol/ios/APP_INTENTS.md | 64 +++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 platform/sol/ios/APP_INTENTS.md diff --git a/platform/sol/ios/APP_INTENTS.md b/platform/sol/ios/APP_INTENTS.md new file mode 100644 index 000000000..bcdab83ef --- /dev/null +++ b/platform/sol/ios/APP_INTENTS.md @@ -0,0 +1,64 @@ +# SOL iOS Operator Contract + +The iOS app is an operator surface, not an autonomous authority node. + +## SwiftUI architecture + +- iOS 17+ Observation by default: root-owned `@Observable` state stored with `@State`. +- Shared services use typed `@Environment`; feature-local dependencies use explicit initializer injection. +- `NavigationStack` and enum routing own navigation. +- `.sheet(item:)` owns selected modal state. +- Async work runs through `.task` with explicit loading, error, cancellation, and retry states. +- Views remain small; networking, authority evaluation, and receipt verification stay outside view bodies. + +## First App Intents + +1. `InspectExecutionIntent` + - D0, completes inline. + - Input: execution identifier. + - Output: normalized state, consequence class, provider, and receipt root. + +2. `ReviewDecisionIntent` + - D0, opens the app to the decision detail. + - Never approves from Siri or a background invocation. + +3. `ContinueApprovedWorkflowIntent` + - Opens the app. + - Requires a still-valid approval grant, parent-state match, and lease generation. + - The app shows the exact provider action before submission. + +## App entity surface + +`ExecutionEntity` exposes only: + +- execution ID; +- display title; +- normalized status; +- consequence class; +- updated time; +- receipt-root prefix. + +It does not mirror provider payloads, secrets, prompts, or internal policy objects. + +## Operator confirmation screen + +Before a D2+ request, show: + +- requested action; +- target provider and target object; +- consequence class; +- arguments digest; +- expected parent state; +- compensation or rollback path; +- approval expiry; +- receipt destination. + +No ambiguous labels such as “Continue” for consequential actions. Use the explicit verb and target. + +## Security + +- Tokens reside in Keychain and are never logged. +- App Transport Security remains enabled. +- Universal links and deep links validate host, route, and identifier. +- Background tasks are read-only unless an already-admitted idempotent operation is being observed. +- Device biometrics may confirm operator presence but do not replace Automaton-3 authorization. From ee89e257a9ada37458766f86a87d6e3115c8eb63 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:32:13 +0200 Subject: [PATCH 13/89] docs(sol): define web operator console architecture --- platform/sol/web/OPERATOR_CONSOLE.md | 72 ++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 platform/sol/web/OPERATOR_CONSOLE.md diff --git a/platform/sol/web/OPERATOR_CONSOLE.md b/platform/sol/web/OPERATOR_CONSOLE.md new file mode 100644 index 000000000..98fa5ad99 --- /dev/null +++ b/platform/sol/web/OPERATOR_CONSOLE.md @@ -0,0 +1,72 @@ +# SOL Web Operator Console + +Primary product surface: an inspectable control plane for governed cross-platform workflows. + +## Information architecture + +- Executions: active and historical workflows. +- Decisions: Automaton-3 admissions and denials. +- Providers: health, capability scopes, and evidence freshness. +- Evidence: model evals, Wolfram checks, oGemma verdicts, and provider receipts. +- Knowledge: GitHub and SharePoint sources with provenance. +- Settings: environments and non-secret configuration references. + +## Execution detail + +The primary screen must show, without hidden drawers: + +- objective and current phase; +- agent and model identity; +- provider and capability; +- consequence class; +- expected and observed state roots; +- lease generation and expiry; +- authority decision and denial codes; +- provider operation status; +- receipt root and verification state; +- compensation status; +- event timeline. + +## Interaction rules + +- Read-only inspection is available without mutation controls. +- D2+ controls display an explicit confirmation step. +- D3+ confirmation requires the operator to review the exact target and digest. +- D4 controls are absent until policy admits them. +- Retried operations reuse the original idempotency key only when the provider contract permits it. +- UI optimism never represents an external mutation as committed before receipt verification. + +## React implementation rules + +- Prefer server-side or route-level parallel data loading; eliminate avoidable waterfalls. +- Keep provider payloads on the server and serialize only the fields the client renders. +- Dynamically load heavy trace, graph, and diff viewers. +- Use stable primitive dependencies and derived state rather than effect-driven mirrors. +- Use transitions for non-urgent filtering and large evidence views. +- Version and minimize persisted browser state. +- Respect reduced motion and WCAG 2.2 AA interaction targets. + +## ChatGPT/MCP widget + +The widget uses a decoupled data/render architecture: + +- `search` and `fetch` expose knowledge and execution records. +- data tools return concise `structuredContent`. +- render tools attach the versioned operator-console resource URI. +- large trace payloads remain widget-only metadata. +- mutating component actions call governed MCP tools and display returned authority evidence. + +CSP is exact and versioned. The widget does not embed arbitrary providers or fetch from undeclared domains. + +## Design direction + +The visual system should communicate evidence and authority, not science-fiction decoration: + +- dense but legible operational typography; +- calm neutral surfaces; +- one semantic color system for admitted, denied, pending, failed, and compensated states; +- no decorative metrics; +- receipts and state roots are copyable and verifiable; +- provider logos never outweigh the authority decision. + +A visual prototype must be generated and selected before frontend implementation. \ No newline at end of file From c1d35fa7325c435e8ae3b6c4ac9435c5e6b6eb62 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:32:50 +0200 Subject: [PATCH 14/89] docs(sol): define SharePoint knowledge publishing policy --- platform/sol/sharepoint/PUBLISHING.md | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 platform/sol/sharepoint/PUBLISHING.md diff --git a/platform/sol/sharepoint/PUBLISHING.md b/platform/sol/sharepoint/PUBLISHING.md new file mode 100644 index 000000000..b2d614138 --- /dev/null +++ b/platform/sol/sharepoint/PUBLISHING.md @@ -0,0 +1,63 @@ +# SharePoint Knowledge Publishing Policy + +SharePoint is the approved human-readable publication surface for SOL operating knowledge. It is not the executable policy root. + +## Intended library structure + +```text +AEGIS-OMEGA/ + 00-Governance/ + 10-Architecture/ + 20-Runbooks/ + 30-Provider-Integrations/ + 40-Evaluations/ + 50-Receipts-and-Releases/ + 90-Archive/ +``` + +## Required metadata + +Every published document must carry: + +- source repository; +- source Git ref and commit SHA; +- source path; +- document digest; +- AEGIS receipt root when applicable; +- evidence tier; +- owner; +- review state; +- published time; +- supersedes/superseded-by relation. + +## Publication workflow + +1. Material is authored and reviewed in GitHub. +2. CI validates schemas, links, and prohibited secret patterns. +3. Automaton-3 admits the exact publication candidate. +4. The publisher uploads a versioned immutable copy to SharePoint. +5. A publication receipt records the Graph item ID, version ID, source commit, and content digest. +6. A mutable index page may point to the current version, but previous versions are retained. + +## Precedence + +When content conflicts: + +1. executable repository policy and schemas; +2. admitted Git commit and receipt; +3. versioned SharePoint publication; +4. draft SharePoint notes; +5. model-generated summaries. + +SharePoint content cannot grant authority or amend executable policy. + +## Access + +- Default sharing scope: organization, view-only. +- Edit permission is limited to maintainers of the corresponding repository area. +- Anonymous links are prohibited for governance, security, receipts, or private integration material. +- Named recipient invitations require an explicit operator request. + +## Current connector limitation + +The connected Microsoft account is a personal MSA surface rather than a SharePoint organizational tenant. Microsoft Graph search returned that the enterprise search API is unsupported for this account. The intended library cannot be safely created until an organizational SharePoint site is connected or an exact existing site URL is supplied. No folders or files were created under the personal account as a substitute. \ No newline at end of file From e8a7e1d5b521b537c1552425d87c6a2166c34c6f Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:33:31 +0200 Subject: [PATCH 15/89] docs(sol): add control-plane implementation map --- platform/sol/README.md | 63 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 platform/sol/README.md diff --git a/platform/sol/README.md b/platform/sol/README.md new file mode 100644 index 000000000..4de6df951 --- /dev/null +++ b/platform/sol/README.md @@ -0,0 +1,63 @@ +# SOL Cross-Platform Control Plane + +This directory is the governed integration boundary for AEGIS-Ω provider interoperability. + +## What is implemented in this slice + +- architecture and trust-boundary specification; +- canonical provider execution request/result schemas; +- machine-readable platform registry; +- fail-closed oGemma/MYTHOS evidence adapter; +- adversarial adapter tests; +- exact-scope CI validation; +- OpenAI agent-runtime contract; +- Cloudflare remote MCP deployment contract; +- iOS App Intents/operator contract; +- web operator-console contract; +- SharePoint publication policy. + +## What remains deliberately unconfigured + +- production credentials and secrets; +- Cloudflare Worker deployment and DNS; +- OAuth applications; +- Dataverse tables or migrations; +- NVIDIA runtime/container provisioning; +- Hugging Face model/dataset publication; +- Wolfram API credentials; +- Figma production-file mutation; +- SharePoint organizational library creation; +- iOS target/project changes; +- public ChatGPT app submission; +- merge to `main`. + +Those operations require exact environment identities, provider-specific scopes, and Automaton-3 admission of the resulting implementation candidates. + +## Existing assets integrated by contract + +The first concrete adapter is the existing `clients/gemma-holon` oGemma/MYTHOS material. The adapter binds gate, task, plan, prompt, model, and biological-state digests into deterministic T2 evidence and denies unknown gates or malformed state. + +The existing `sovereign-omega-v2/mcp-server` remains the canonical local MCP implementation. A later implementation slice will expose a remote authenticated transport on Cloudflare without creating a second authority evaluator. + +## Validation + +Run locally: + +```bash +python -m pip install jsonschema==4.23.0 +python -m unittest discover -s platform/sol/tests -p 'test_*.py' -v +``` + +The `SOL Cross-Platform Integration` workflow validates JSON/schema syntax, executes the adapter test matrix, and rejects obvious committed credentials or mutable database files. + +## Next engineering slices + +1. Wire the adapter into the MYTHOS stage boundaries and replace the legacy unknown-gate approval path. +2. Add the OpenAI Agents SDK orchestrator against the existing governed MCP tools. +3. Implement the authenticated Cloudflare remote MCP transport. +4. Add standard `search`/`fetch` knowledge tools for GitHub, SharePoint, and Hugging Face. +5. Add Wolfram verification and NVIDIA evaluation worker adapters. +6. Generate three operator-console visual concepts, select one, and implement the React/MCP widget. +7. Add the SwiftUI operator client and three App Intents. +8. Publish reviewed runbooks into an organizational SharePoint library. +9. Add end-to-end evals, replay packages, and exact-candidate attestations. From a2e433be32c8cf9d0af088a509ae15f8dd58fa8a Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 20:42:51 +0200 Subject: [PATCH 16/89] chore(admission): add exact-parent SOL integration plan --- ...5-sol-cross-platform-control-plane-v1.json | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 .aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json diff --git a/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json b/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json new file mode 100644 index 000000000..3e1c231a9 --- /dev/null +++ b/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json @@ -0,0 +1,85 @@ +{ + "schema_version": "0.1.0", + "experiment_id": "pr-225-sol-cross-platform-control-plane-v1", + "title": "Admit the non-production SOL governed cross-platform control-plane foundation", + "repository": "Aegis-Omega/AEGIS-OMEGA", + "execution_class": "EXPERIMENT", + "evidence_tier": "T1", + "expected_parent_sha": "ed426124475cc9ee8b4d138b2928ae547352e97b", + "expected_parent_state_root": "410bcd49c721e65050382ae759db7af2f41fa9f572e625174252c72a8748ea93", + "constitution": { + "path": "CONSTITUTIONAL_DECLARATION.md", + "blob_id": "d0c210443e03313113e43da46c4d98269494baeb" + }, + "policy": { + "path": "platform/sol/SPEC.md", + "blob_id": "621e549e9cfa047f5f0d56b31597b1854bea4bb9" + }, + "sovereignty_contracts": { + "path": "sovereign-omega-v2/src/sovereignty/contracts.ts", + "blob_id": "d53860f1340293cf08955ebb8729a853432551eb" + }, + "admission_executable": { + "path": "sovereign-omega-v2/scripts/validate-experiment-plan.ts", + "blob_id": "b9c998ddc85b9beeefec30121e88e828b15a8405" + }, + "admission_workflow": { + "path": ".github/workflows/experiment-admission.yml", + "blob_id": "01a59d129b0431c9ccb92b1227a0e46992b1562f" + }, + "integration_ledger_generator": { + "path": "scripts/integration_ledger.py", + "blob_id": "28823ae5b630be273b78210f0addf9c0a86aad05" + }, + "claims_ledger": { + "path": ".aegis/claims-ledger.json", + "root": "495a01d7a942d5f90b39f2d2b178b074aee60c5e6460e1622f1917982ac59652" + }, + "budget": { + "max_cost_microunits": 0, + "max_duration_seconds": 3600, + "max_mutations": 0 + }, + "observability": { + "provider": "github-actions", + "durable_execution_required": true, + "heartbeat_max_seconds": 300, + "cancellation_mechanism": "github-actions-cancel-run", + "emergency_stop_reference": "github-actions:cancel-run" + }, + "operator_approval": { + "required": true, + "state": "APPROVED", + "operator_actor_id": "tarikskalic", + "operator_session_id": "chatgpt-session-2026-07-20-sol-control-plane", + "authorization_basis": "user-explicit-sol-cross-platform-control-plane", + "decided_at": "2026-07-20T18:20:00Z", + "approval_record_hash": "55f67cb45313ddf380aef502b320b493a9ae259a1a0d720aecd020f96a5f04c1", + "signature_mode": "GITHUB_OIDC_ATTESTATION" + }, + "requested_authority_domains": [ + "github:artifact-metadata-write", + "github:attestation-write", + "github:workflow-artifact-write" + ], + "termination_conditions": [ + "budget_exhausted", + "observability_expired", + "operator_emergency_stop" + ], + "expected_outputs": [ + "ADMISSION_RECEIPT.json", + "EVIDENCE_MANIFEST.json", + "EXPERIMENT_PLAN.json", + "INTEGRATION_LEDGER.json", + "INTEGRATION_LEDGER.md", + "SHA256SUMS" + ], + "replay_package": { + "required": true, + "include_plan": true, + "include_admission_receipt": true, + "include_evidence_manifest": true, + "include_integration_ledger": true + } +} \ No newline at end of file From dcbbbe283b1ee479bf02456a9d1c2456a5bf5d1a Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:04:51 +0200 Subject: [PATCH 17/89] fix(admission): bind SOL operator approval record hash --- .../pr-225-sol-cross-platform-control-plane-v1.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json b/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json index 3e1c231a9..80211928a 100644 --- a/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json +++ b/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json @@ -54,7 +54,7 @@ "operator_session_id": "chatgpt-session-2026-07-20-sol-control-plane", "authorization_basis": "user-explicit-sol-cross-platform-control-plane", "decided_at": "2026-07-20T18:20:00Z", - "approval_record_hash": "55f67cb45313ddf380aef502b320b493a9ae259a1a0d720aecd020f96a5f04c1", + "approval_record_hash": "dbe281ec13d2ac473ed9ecb44677d6d3e4450c1924db1d55f3dee81bf196ab73", "signature_mode": "GITHUB_OIDC_ATTESTATION" }, "requested_authority_domains": [ @@ -82,4 +82,4 @@ "include_evidence_manifest": true, "include_integration_ledger": true } -} \ No newline at end of file +} From 4f1c06f051a7a3aeb9bb996f23faa2daeca8038f Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:12:11 +0200 Subject: [PATCH 18/89] feat(sol): add pinned Cloudflare MCP Worker package --- platform/sol/cloudflare/worker/package.json | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 platform/sol/cloudflare/worker/package.json diff --git a/platform/sol/cloudflare/worker/package.json b/platform/sol/cloudflare/worker/package.json new file mode 100644 index 000000000..799803821 --- /dev/null +++ b/platform/sol/cloudflare/worker/package.json @@ -0,0 +1,27 @@ +{ + "name": "@aegis/sol-edge-mcp", + "version": "0.1.0", + "private": true, + "type": "module", + "engines": { + "node": ">=24.11" + }, + "scripts": { + "cf-typegen": "wrangler types", + "typecheck": "npm run cf-typegen && tsc --noEmit", + "test": "vitest run", + "check": "npm run typecheck && npm test", + "dev": "wrangler dev", + "deploy": "wrangler deploy" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "1.29.0", + "agents": "0.17.4", + "zod": "4.4.3" + }, + "devDependencies": { + "typescript": "5.9.3", + "vitest": "3.2.7", + "wrangler": "4.112.0" + } +} From c1c82bfd7a013f428f3de1db10b863482fedb701 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:12:41 +0200 Subject: [PATCH 19/89] feat(sol): add strict Worker TypeScript configuration --- platform/sol/cloudflare/worker/tsconfig.json | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 platform/sol/cloudflare/worker/tsconfig.json diff --git a/platform/sol/cloudflare/worker/tsconfig.json b/platform/sol/cloudflare/worker/tsconfig.json new file mode 100644 index 000000000..bf242c1eb --- /dev/null +++ b/platform/sol/cloudflare/worker/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": [ + "ES2023" + ], + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "types": [], + "skipLibCheck": true + }, + "include": [ + "src/**/*.ts", + "test/**/*.ts", + "worker-configuration.d.ts" + ] +} From dad269908b1d4c40ae47334dc2fb017c22339e61 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:13:04 +0200 Subject: [PATCH 20/89] feat(sol): configure staging Cloudflare MCP Worker --- platform/sol/cloudflare/worker/wrangler.jsonc | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 platform/sol/cloudflare/worker/wrangler.jsonc diff --git a/platform/sol/cloudflare/worker/wrangler.jsonc b/platform/sol/cloudflare/worker/wrangler.jsonc new file mode 100644 index 000000000..5108a0aa9 --- /dev/null +++ b/platform/sol/cloudflare/worker/wrangler.jsonc @@ -0,0 +1,22 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "aegis-sol-edge-mcp", + "main": "src/index.ts", + "compatibility_date": "2026-07-20", + "compatibility_flags": ["nodejs_compat"], + "workers_dev": true, + "observability": { + "enabled": true, + "head_sampling_rate": 1 + }, + "vars": { + "AEGIS_DEPLOYMENT_ENV": "staging", + "AEGIS_MAX_BODY_BYTES": "1048576" + }, + "services": [ + { + "binding": "AEGIS_CORE", + "service": "aegis-core-staging" + } + ] +} From a186bc89ce272306a52b56ba1462009cd1a4f621 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:13:23 +0200 Subject: [PATCH 21/89] chore(sol): ignore generated Worker state and secrets --- platform/sol/cloudflare/worker/.gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 platform/sol/cloudflare/worker/.gitignore diff --git a/platform/sol/cloudflare/worker/.gitignore b/platform/sol/cloudflare/worker/.gitignore new file mode 100644 index 000000000..a9caae036 --- /dev/null +++ b/platform/sol/cloudflare/worker/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +.wrangler/ +worker-configuration.d.ts +.env +.dev.vars From 5000cee17dc70654b9a4d3b91dd68b764356a6ed Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:13:47 +0200 Subject: [PATCH 22/89] feat(sol): add fail-closed Cloudflare Access guards --- platform/sol/cloudflare/worker/src/guard.ts | 46 +++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 platform/sol/cloudflare/worker/src/guard.ts diff --git a/platform/sol/cloudflare/worker/src/guard.ts b/platform/sol/cloudflare/worker/src/guard.ts new file mode 100644 index 000000000..dc7b801ba --- /dev/null +++ b/platform/sol/cloudflare/worker/src/guard.ts @@ -0,0 +1,46 @@ +export type AccessIdentity = { + email: string + jwt: string +} + +export class AccessDeniedError extends Error { + constructor(message: string) { + super(message) + this.name = 'AccessDeniedError' + } +} + +export function parsePositiveInteger(value: string, fallback: number): number { + const parsed = Number(value) + return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback +} + +export function enforceBodyLimit(request: Request, maxBytes: number): void { + const raw = request.headers.get('content-length') + if (raw === null) return + const length = Number(raw) + if (!Number.isSafeInteger(length) || length < 0 || length > maxBytes) { + throw new AccessDeniedError('request body exceeds admitted size') + } +} + +export function requireAccessIdentity(request: Request): AccessIdentity { + const email = request.headers.get('cf-access-authenticated-user-email')?.trim() ?? '' + const jwt = request.headers.get('cf-access-jwt-assertion')?.trim() ?? '' + if (!email || !jwt) { + throw new AccessDeniedError('Cloudflare Access identity is required') + } + return { email, jwt } +} + +export function jsonResponse(status: number, payload: unknown): Response { + return Response.json(payload, { + status, + headers: { + 'cache-control': 'no-store', + 'content-security-policy': "default-src 'none'; frame-ancestors 'none'", + 'referrer-policy': 'no-referrer', + 'x-content-type-options': 'nosniff', + }, + }) +} From eaaaf8d912cac811dfc2dc2d1f7ce66226c42df8 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:14:19 +0200 Subject: [PATCH 23/89] feat(sol): implement governed Streamable HTTP MCP edge --- platform/sol/cloudflare/worker/src/index.ts | 174 ++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 platform/sol/cloudflare/worker/src/index.ts diff --git a/platform/sol/cloudflare/worker/src/index.ts b/platform/sol/cloudflare/worker/src/index.ts new file mode 100644 index 000000000..c193b5be9 --- /dev/null +++ b/platform/sol/cloudflare/worker/src/index.ts @@ -0,0 +1,174 @@ +import { createMcpHandler } from 'agents/mcp' +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' +import { z } from 'zod' + +import { + AccessDeniedError, + enforceBodyLimit, + jsonResponse, + parsePositiveInteger, + requireAccessIdentity, + type AccessIdentity, +} from './guard.js' + +type Env = Cloudflare.Env + +type ToolTextResult = { + content: Array<{ type: 'text'; text: string }> + structuredContent: Record + isError?: boolean +} + +const Provider = z.enum([ + 'dataverse', + 'figma', + 'github', + 'huggingface', + 'nvidia', + 'sharepoint', + 'wolfram', +]) + +const ConsequenceClass = z.enum(['D0', 'D1', 'D2', 'D3', 'D4']) +const Sha256 = z.string().regex(/^[0-9a-f]{64}$/) + +function toolResult(payload: Record, isError = false): ToolTextResult { + return { + content: [{ type: 'text', text: JSON.stringify(payload) }], + structuredContent: payload, + ...(isError ? { isError: true } : {}), + } +} + +async function coreJson( + env: Env, + identity: AccessIdentity, + path: string, + init?: RequestInit, +): Promise> { + const headers = new Headers(init?.headers) + headers.set('accept', 'application/json') + headers.set('content-type', 'application/json') + headers.set('x-aegis-operator-email', identity.email) + headers.set('x-aegis-access-jwt', identity.jwt) + headers.set('x-aegis-edge', 'cloudflare-sol-mcp') + + const response = await env.AEGIS_CORE.fetch(`https://aegis-core.internal${path}`, { + ...init, + headers, + }) + + const contentType = response.headers.get('content-type') ?? '' + const payload = contentType.includes('application/json') + ? await response.json>() + : { message: await response.text() } + + if (!response.ok) { + return { + status: 'DENIED', + upstream_status: response.status, + external_effect: 'NOT_EXECUTED', + evidence: payload, + } + } + return payload +} + +function createServer(env: Env, identity: AccessIdentity): McpServer { + const server = new McpServer({ name: 'aegis-sol-edge', version: '0.1.0' }) + + server.registerTool( + 'sol_platform_status', + { + title: 'Read SOL platform status', + description: 'Use this when the operator needs the governed platform, provider, and authority status.', + inputSchema: {}, + annotations: { + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: false, + }, + }, + async () => toolResult(await coreJson(env, identity, '/platform/sol/status')), + ) + + server.registerTool( + 'sol_request_execution', + { + title: 'Request a governed provider execution', + description: 'Use this when an operator wants SOL to evaluate and, only if admitted, execute one provider capability.', + inputSchema: { + provider: Provider, + capability: z.string().min(3).max(160), + consequence_class: ConsequenceClass, + target: z.string().min(1).max(500), + arguments_digest: Sha256, + expected_parent_state_root: Sha256, + idempotency_key: z.string().min(8).max(200), + compensation_reference: z.string().min(1).max(500).optional(), + }, + annotations: { + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + }, + async (input) => { + const result = await coreJson(env, identity, '/platform/sol/executions', { + method: 'POST', + body: JSON.stringify({ + schema_version: '1.0.0', + operator: { email: identity.email }, + source: 'cloudflare-remote-mcp', + ...input, + }), + }) + return toolResult(result, result['status'] === 'DENIED') + }, + ) + + return server +} + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { + const url = new URL(request.url) + if (url.pathname === '/health') { + return jsonResponse(200, { + status: 'ok', + service: 'aegis-sol-edge-mcp', + environment: env.AEGIS_DEPLOYMENT_ENV, + }) + } + + if (url.pathname !== '/mcp') { + return jsonResponse(404, { error: 'not_found' }) + } + + try { + enforceBodyLimit(request, parsePositiveInteger(env.AEGIS_MAX_BODY_BYTES, 1_048_576)) + const identity = requireAccessIdentity(request) + const server = createServer(env, identity) + return await createMcpHandler(server)(request, env, ctx) + } catch (error) { + if (error instanceof AccessDeniedError) { + return jsonResponse(401, { + error: 'access_denied', + reason: error.message, + external_effect: 'NOT_EXECUTED', + }) + } + console.error(JSON.stringify({ + event: 'sol_edge_unhandled_error', + request_id: request.headers.get('cf-ray') ?? crypto.randomUUID(), + error: error instanceof Error ? error.message : String(error), + })) + return jsonResponse(500, { + error: 'internal_error', + external_effect: 'UNKNOWN', + }) + } + }, +} satisfies ExportedHandler From ccd9d1fef90d05661d9cb8c7e8f26160a9b83c3c Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:14:40 +0200 Subject: [PATCH 24/89] test(sol): cover Cloudflare MCP edge guards --- .../sol/cloudflare/worker/test/guard.test.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 platform/sol/cloudflare/worker/test/guard.test.ts diff --git a/platform/sol/cloudflare/worker/test/guard.test.ts b/platform/sol/cloudflare/worker/test/guard.test.ts new file mode 100644 index 000000000..16dc55a2e --- /dev/null +++ b/platform/sol/cloudflare/worker/test/guard.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from 'vitest' + +import { + AccessDeniedError, + enforceBodyLimit, + parsePositiveInteger, + requireAccessIdentity, +} from '../src/guard.js' + +describe('SOL edge guards', () => { + it('requires Cloudflare Access identity', () => { + expect(() => requireAccessIdentity(new Request('https://example.com/mcp'))) + .toThrow(AccessDeniedError) + }) + + it('accepts complete Cloudflare Access identity', () => { + const request = new Request('https://example.com/mcp', { + headers: { + 'cf-access-authenticated-user-email': 'operator@aegisomega.com', + 'cf-access-jwt-assertion': 'signed-jwt', + }, + }) + expect(requireAccessIdentity(request)).toEqual({ + email: 'operator@aegisomega.com', + jwt: 'signed-jwt', + }) + }) + + it('rejects oversized request bodies from content-length', () => { + const request = new Request('https://example.com/mcp', { + method: 'POST', + headers: { 'content-length': '2048' }, + }) + expect(() => enforceBodyLimit(request, 1024)).toThrow(AccessDeniedError) + }) + + it('uses a safe fallback for malformed limits', () => { + expect(parsePositiveInteger('not-a-number', 1024)).toBe(1024) + expect(parsePositiveInteger('0', 1024)).toBe(1024) + expect(parsePositiveInteger('4096', 1024)).toBe(4096) + }) +}) From aeaf10f205001c998a0436e431bdc1fb59b650a2 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:15:30 +0200 Subject: [PATCH 25/89] ci(sol): validate Cloudflare MCP edge package --- .github/workflows/sol-integration.yml | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.github/workflows/sol-integration.yml b/.github/workflows/sol-integration.yml index 10e417526..6671fbe76 100644 --- a/.github/workflows/sol-integration.yml +++ b/.github/workflows/sol-integration.yml @@ -60,3 +60,31 @@ jobs: set -euo pipefail ! grep -RInE '(hf_[A-Za-z0-9]{20,}|sk-[A-Za-z0-9_-]{20,}|BEGIN (RSA|OPENSSH|EC) PRIVATE KEY)' platform/sol ! find platform/sol -type f \( -name '*.sqlite' -o -name '*.db' -o -name '*.pem' -o -name '.env' \) -print -quit | grep . + + edge-mcp: + name: aegis / sol-edge-mcp + runs-on: ubuntu-latest + timeout-minutes: 15 + defaults: + run: + working-directory: platform/sol/cloudflare/worker + steps: + - name: Checkout exact candidate + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '24.11.0' + + - name: Install pinned dependencies without lifecycle scripts + run: npm install --ignore-scripts + + - name: Generate Worker bindings and typecheck + run: npm run typecheck + + - name: Run fail-closed edge tests + run: npm test + + - name: Audit installed dependency graph + run: npm audit --audit-level=high From e09e96ad66deb287fd9d2a8948893544a5ee5edd Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:22:16 +0200 Subject: [PATCH 26/89] feat(sol): normalize immutable cross-provider evidence --- platform/sol/adapters/provider_evidence.py | 236 +++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 platform/sol/adapters/provider_evidence.py diff --git a/platform/sol/adapters/provider_evidence.py b/platform/sol/adapters/provider_evidence.py new file mode 100644 index 000000000..65230bee5 --- /dev/null +++ b/platform/sol/adapters/provider_evidence.py @@ -0,0 +1,236 @@ +from __future__ import annotations + +from dataclasses import asdict, dataclass +from datetime import datetime, timezone +from hashlib import sha256 +import json +import re +from typing import Any, Mapping + + +PROVIDERS = frozenset({ + "dataverse", + "figma", + "github", + "huggingface", + "nvidia", + "sharepoint", + "wolfram", +}) +STATUSES = frozenset({"SUCCEEDED", "FAILED", "DENIED"}) +EVIDENCE_TIERS = frozenset({"T0", "T1", "T2", "T3"}) +SHA256_RE = re.compile(r"^[0-9a-f]{64}$") +GIT_OBJECT_RE = re.compile(r"^[0-9a-f]{40,64}$") +CONTAINER_DIGEST_RE = re.compile(r"^sha256:[0-9a-f]{64}$") + + +class ProviderEvidenceError(ValueError): + pass + + +@dataclass(frozen=True, slots=True) +class ExternalReference: + kind: str + id: str + revision: str + etag: str | None = None + checksum: str | None = None + endpoint: str | None = None + + +@dataclass(frozen=True, slots=True) +class ModelProvenance: + model_id: str + revision: str + runtime: str + hardware_profile: str | None = None + container_digest: str | None = None + dataset_revision: str | None = None + + +@dataclass(frozen=True, slots=True) +class ProviderEvidence: + schema_version: str + provider: str + capability: str + observed_at: str + request_digest: str + response_digest: str + status: str + evidence_tier: str + grants_authority: bool + external_reference: ExternalReference + model_provenance: ModelProvenance | None + receipt_root: str | None + evidence_digest: str + + +def _nonempty(field: str, value: str) -> str: + if not isinstance(value, str) or not value.strip(): + raise ProviderEvidenceError(f"{field} must be a non-empty string") + return value.strip() + + +def _sha256(field: str, value: str) -> str: + value = _nonempty(field, value) + if not SHA256_RE.fullmatch(value): + raise ProviderEvidenceError(f"{field} must be lowercase SHA-256 hex") + return value + + +def _timestamp(value: str | None) -> str: + if value is None: + return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") + normalized = _nonempty("observed_at", value) + try: + datetime.fromisoformat(normalized.replace("Z", "+00:00")) + except ValueError as exc: + raise ProviderEvidenceError("observed_at must be ISO-8601") from exc + return normalized + + +def _canonical(value: Mapping[str, Any]) -> bytes: + return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8") + + +def _optional_nonempty(field: str, value: str | None) -> str | None: + return None if value is None else _nonempty(field, value) + + +def _validate_external(provider: str, ref: ExternalReference) -> None: + _nonempty("external_reference.kind", ref.kind) + _nonempty("external_reference.id", ref.id) + _nonempty("external_reference.revision", ref.revision) + + if ref.checksum is not None: + _sha256("external_reference.checksum", ref.checksum) + + if provider == "github": + if ref.kind not in {"commit", "pull_request", "workflow_run"}: + raise ProviderEvidenceError("github evidence kind is unsupported") + if not GIT_OBJECT_RE.fullmatch(ref.revision): + raise ProviderEvidenceError("github evidence revision must be an immutable Git object id") + elif provider == "sharepoint": + if ref.kind != "drive_item" or not ref.etag: + raise ProviderEvidenceError("sharepoint evidence requires drive_item and etag") + elif provider == "dataverse": + if ref.kind != "row" or not ref.etag: + raise ProviderEvidenceError("dataverse evidence requires row and etag") + elif provider == "huggingface": + if ref.kind not in {"model", "dataset", "space", "evaluation"}: + raise ProviderEvidenceError("huggingface evidence kind is unsupported") + if not GIT_OBJECT_RE.fullmatch(ref.revision): + raise ProviderEvidenceError("huggingface revision must be a pinned commit") + elif provider == "nvidia": + if ref.kind not in {"nim_inference", "nim_benchmark"} or not ref.endpoint: + raise ProviderEvidenceError("nvidia evidence requires a NIM kind and endpoint") + if not ref.endpoint.startswith("https://"): + raise ProviderEvidenceError("nvidia evidence endpoint must use HTTPS") + elif provider == "wolfram": + if ref.kind != "wolfram_result" or ref.checksum is None: + raise ProviderEvidenceError("wolfram evidence requires a result checksum") + elif provider == "figma": + if ref.kind not in {"file_version", "node_snapshot", "prototype"}: + raise ProviderEvidenceError("figma evidence kind is unsupported") + + +def _validate_model(provider: str, model: ModelProvenance | None) -> None: + if provider in {"huggingface", "nvidia"} and model is None: + raise ProviderEvidenceError(f"{provider} evidence requires model provenance") + if model is None: + return + _nonempty("model_provenance.model_id", model.model_id) + _nonempty("model_provenance.revision", model.revision) + _nonempty("model_provenance.runtime", model.runtime) + if provider == "huggingface": + if not GIT_OBJECT_RE.fullmatch(model.revision): + raise ProviderEvidenceError("huggingface model revision must be a pinned commit") + if model.dataset_revision is not None and not GIT_OBJECT_RE.fullmatch(model.dataset_revision): + raise ProviderEvidenceError("huggingface dataset revision must be a pinned commit") + if provider == "nvidia" and model.container_digest is None: + raise ProviderEvidenceError("nvidia evidence requires a pinned container digest") + if model.container_digest is not None and not CONTAINER_DIGEST_RE.fullmatch(model.container_digest): + raise ProviderEvidenceError("model_provenance.container_digest must be sha256:") + + +def normalize_provider_evidence( + *, + provider: str, + capability: str, + request_digest: str, + response_digest: str, + status: str, + evidence_tier: str, + external_reference: ExternalReference, + model_provenance: ModelProvenance | None = None, + receipt_root: str | None = None, + observed_at: str | None = None, +) -> ProviderEvidence: + provider = _nonempty("provider", provider) + if provider not in PROVIDERS: + raise ProviderEvidenceError("provider is unsupported") + capability = _nonempty("capability", capability) + if status not in STATUSES: + raise ProviderEvidenceError("status is unsupported") + if evidence_tier not in EVIDENCE_TIERS: + raise ProviderEvidenceError("evidence_tier is unsupported") + + request_digest = _sha256("request_digest", request_digest) + response_digest = _sha256("response_digest", response_digest) + receipt_root = None if receipt_root is None else _sha256("receipt_root", receipt_root) + observed_at = _timestamp(observed_at) + + external_reference = ExternalReference( + kind=_nonempty("external_reference.kind", external_reference.kind), + id=_nonempty("external_reference.id", external_reference.id), + revision=_nonempty("external_reference.revision", external_reference.revision), + etag=_optional_nonempty("external_reference.etag", external_reference.etag), + checksum=_optional_nonempty("external_reference.checksum", external_reference.checksum), + endpoint=_optional_nonempty("external_reference.endpoint", external_reference.endpoint), + ) + _validate_external(provider, external_reference) + _validate_model(provider, model_provenance) + + unsigned = { + "schema_version": "1.0.0", + "provider": provider, + "capability": capability, + "observed_at": observed_at, + "request_digest": request_digest, + "response_digest": response_digest, + "status": status, + "evidence_tier": evidence_tier, + "grants_authority": False, + "external_reference": asdict(external_reference), + "model_provenance": asdict(model_provenance) if model_provenance else None, + "receipt_root": receipt_root, + } + evidence_digest = sha256(_canonical(unsigned)).hexdigest() + return ProviderEvidence( + schema_version="1.0.0", + provider=provider, + capability=capability, + observed_at=observed_at, + request_digest=request_digest, + response_digest=response_digest, + status=status, + evidence_tier=evidence_tier, + grants_authority=False, + external_reference=external_reference, + model_provenance=model_provenance, + receipt_root=receipt_root, + evidence_digest=evidence_digest, + ) + + +def to_authority_evidence(evidence: ProviderEvidence) -> dict[str, Any]: + return { + "evidence_kind": "SOL_PROVIDER_OBSERVATION_V1", + "provider": evidence.provider, + "capability": evidence.capability, + "status": evidence.status, + "evidence_tier": evidence.evidence_tier, + "grants_authority": False, + "evidence_digest": evidence.evidence_digest, + "receipt_root": evidence.receipt_root, + } From d7de7ab800b715e5c8ec4fe0ceaa5ca2b04ba857 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:22:52 +0200 Subject: [PATCH 27/89] test(sol): enforce provider evidence immutability --- platform/sol/tests/test_provider_evidence.py | 129 +++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 platform/sol/tests/test_provider_evidence.py diff --git a/platform/sol/tests/test_provider_evidence.py b/platform/sol/tests/test_provider_evidence.py new file mode 100644 index 000000000..0be077ae6 --- /dev/null +++ b/platform/sol/tests/test_provider_evidence.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +from hashlib import sha256 +from pathlib import Path +import sys +import unittest + +ADAPTER_DIR = Path(__file__).resolve().parents[1] / "adapters" +sys.path.insert(0, str(ADAPTER_DIR)) + +from provider_evidence import ( # noqa: E402 + ExternalReference, + ModelProvenance, + ProviderEvidenceError, + normalize_provider_evidence, + to_authority_evidence, +) + + +ZERO = sha256(b"").hexdigest() +ONE = sha256(b"1").hexdigest() +COMMIT = "a" * 40 +CONTAINER = "sha256:" + "b" * 64 + + +class ProviderEvidenceTests(unittest.TestCase): + def normalize(self, **overrides): + values = { + "provider": "github", + "capability": "repo.read", + "request_digest": ZERO, + "response_digest": ONE, + "status": "SUCCEEDED", + "evidence_tier": "T1", + "external_reference": ExternalReference( + kind="commit", + id="Aegis-Omega/AEGIS-OMEGA", + revision=COMMIT, + ), + "observed_at": "2026-07-20T19:00:00Z", + } + values.update(overrides) + return normalize_provider_evidence(**values) + + def test_evidence_is_deterministic_and_non_authoritative(self): + first = self.normalize() + second = self.normalize() + self.assertEqual(first.evidence_digest, second.evidence_digest) + self.assertFalse(first.grants_authority) + self.assertFalse(to_authority_evidence(first)["grants_authority"]) + + def test_dataverse_requires_etag(self): + with self.assertRaises(ProviderEvidenceError): + self.normalize( + provider="dataverse", + capability="entity.read", + external_reference=ExternalReference(kind="row", id="account:1", revision="v1"), + ) + + def test_sharepoint_requires_drive_item_and_etag(self): + evidence = self.normalize( + provider="sharepoint", + capability="knowledge.read", + external_reference=ExternalReference( + kind="drive_item", + id="drive/item", + revision="version-7", + etag='"etag-7"', + ), + ) + self.assertEqual(evidence.provider, "sharepoint") + + def test_huggingface_requires_pinned_revision_and_model_provenance(self): + with self.assertRaises(ProviderEvidenceError): + self.normalize( + provider="huggingface", + capability="model.read", + external_reference=ExternalReference(kind="model", id="aegis/model", revision="main"), + ) + + def test_nvidia_requires_nim_endpoint_and_container_digest(self): + evidence = self.normalize( + provider="nvidia", + capability="inference.run", + evidence_tier="T2", + external_reference=ExternalReference( + kind="nim_inference", + id="sol-nim-primary", + revision="2.0.8", + endpoint="https://nim.internal/v1/chat/completions", + ), + model_provenance=ModelProvenance( + model_id="openai/gpt-oss-20b", + revision=COMMIT, + runtime="nvidia-nim-vllm", + hardware_profile="h100-sxm", + container_digest=CONTAINER, + ), + ) + self.assertEqual(evidence.external_reference.kind, "nim_inference") + + def test_wolfram_requires_result_checksum(self): + with self.assertRaises(ProviderEvidenceError): + self.normalize( + provider="wolfram", + capability="invariant.evaluate", + external_reference=ExternalReference( + kind="wolfram_result", + id="query-1", + revision="engine-2026", + ), + ) + + def test_figma_rejects_unknown_reference_kind(self): + with self.assertRaises(ProviderEvidenceError): + self.normalize( + provider="figma", + capability="design.read", + external_reference=ExternalReference(kind="file", id="abc", revision="123"), + ) + + def test_failed_provider_call_remains_evidence_not_authority(self): + evidence = self.normalize(status="FAILED") + self.assertEqual(evidence.status, "FAILED") + self.assertFalse(evidence.grants_authority) + + +if __name__ == "__main__": + unittest.main() From 42dd34511102c7a692d572e7cad5d83be86f2730 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:23:17 +0200 Subject: [PATCH 28/89] feat(sol): add provider evidence schema --- .../provider-evidence.v1.schema.json | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 platform/sol/contracts/provider-evidence.v1.schema.json diff --git a/platform/sol/contracts/provider-evidence.v1.schema.json b/platform/sol/contracts/provider-evidence.v1.schema.json new file mode 100644 index 000000000..72931db8e --- /dev/null +++ b/platform/sol/contracts/provider-evidence.v1.schema.json @@ -0,0 +1,96 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://aegisomega.com/schemas/sol-provider-evidence.v1.schema.json", + "title": "SOL Provider Evidence Envelope v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "provider", + "capability", + "observed_at", + "request_digest", + "response_digest", + "status", + "evidence_tier", + "grants_authority", + "external_reference", + "model_provenance", + "receipt_root", + "evidence_digest" + ], + "properties": { + "schema_version": { "const": "1.0.0" }, + "provider": { + "enum": ["dataverse", "figma", "github", "huggingface", "nvidia", "sharepoint", "wolfram"] + }, + "capability": { "type": "string", "minLength": 1, "maxLength": 160 }, + "observed_at": { "type": "string", "format": "date-time" }, + "request_digest": { "$ref": "#/$defs/sha256" }, + "response_digest": { "$ref": "#/$defs/sha256" }, + "status": { "enum": ["SUCCEEDED", "FAILED", "DENIED"] }, + "evidence_tier": { "enum": ["T0", "T1", "T2", "T3"] }, + "grants_authority": { "const": false }, + "external_reference": { "$ref": "#/$defs/externalReference" }, + "model_provenance": { + "oneOf": [ + { "type": "null" }, + { "$ref": "#/$defs/modelProvenance" } + ] + }, + "receipt_root": { + "oneOf": [ + { "type": "null" }, + { "$ref": "#/$defs/sha256" } + ] + }, + "evidence_digest": { "$ref": "#/$defs/sha256" } + }, + "$defs": { + "sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "externalReference": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "id", "revision", "etag", "checksum", "endpoint"], + "properties": { + "kind": { "type": "string", "minLength": 1 }, + "id": { "type": "string", "minLength": 1 }, + "revision": { "type": "string", "minLength": 1 }, + "etag": { "type": ["string", "null"] }, + "checksum": { + "oneOf": [ + { "type": "null" }, + { "$ref": "#/$defs/sha256" } + ] + }, + "endpoint": { "type": ["string", "null"] } + } + }, + "modelProvenance": { + "type": "object", + "additionalProperties": false, + "required": [ + "model_id", + "revision", + "runtime", + "hardware_profile", + "container_digest", + "dataset_revision" + ], + "properties": { + "model_id": { "type": "string", "minLength": 1 }, + "revision": { "type": "string", "minLength": 1 }, + "runtime": { "type": "string", "minLength": 1 }, + "hardware_profile": { "type": ["string", "null"] }, + "container_digest": { + "type": ["string", "null"], + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "dataset_revision": { "type": ["string", "null"] } + } + } + } +} From 528a2f9b8f543f45c93cc6d3154632127313d8e9 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:27:24 +0200 Subject: [PATCH 29/89] feat(sol): add deterministic mathematical verifier --- platform/sol/verifiers/math_invariants.py | 93 +++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 platform/sol/verifiers/math_invariants.py diff --git a/platform/sol/verifiers/math_invariants.py b/platform/sol/verifiers/math_invariants.py new file mode 100644 index 000000000..af04a738e --- /dev/null +++ b/platform/sol/verifiers/math_invariants.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +from dataclasses import asdict, dataclass +from decimal import Decimal, localcontext +from hashlib import sha256 +import json +from math import exp, sqrt +from typing import Any + + +class MathInvariantError(ValueError): + pass + + +@dataclass(frozen=True, slots=True) +class QuorumVerification: + schema_version: str + threshold_milli: int + threshold: str + golden_ratio: str + golden_conjugate: str + absolute_error: str + equals_inverse_of_defined_phi: bool + verified_relation: str + grants_authority: bool + evidence_digest: str + + +def _canonical(payload: dict[str, Any]) -> bytes: + return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") + + +def verify_golden_quorum(threshold_milli: int = 618) -> QuorumVerification: + if not isinstance(threshold_milli, int) or isinstance(threshold_milli, bool): + raise MathInvariantError("threshold_milli must be an integer") + if not 0 <= threshold_milli <= 1000: + raise MathInvariantError("threshold_milli must be in [0, 1000]") + + with localcontext() as context: + context.prec = 50 + golden_ratio = (Decimal(1) + Decimal(5).sqrt()) / Decimal(2) + golden_conjugate = Decimal(1) / golden_ratio + threshold = Decimal(threshold_milli) / Decimal(1000) + absolute_error = abs(threshold - golden_conjugate) + inverse_of_defined_phi = Decimal(1) / golden_conjugate + + unsigned = { + "schema_version": "1.0.0", + "threshold_milli": threshold_milli, + "threshold": str(threshold), + "golden_ratio": str(golden_ratio), + "golden_conjugate": str(golden_conjugate), + "absolute_error": str(absolute_error), + "equals_inverse_of_defined_phi": threshold == inverse_of_defined_phi, + "verified_relation": "golden_conjugate = 1 / golden_ratio = golden_ratio - 1", + "grants_authority": False, + } + digest = sha256(_canonical(unsigned)).hexdigest() + return QuorumVerification(**unsigned, evidence_digest=digest) + + +def attention_score(*, sigma_one: float, d_k: float, atp: float, stress_norm: float) -> float: + values = { + "sigma_one": sigma_one, + "d_k": d_k, + "atp": atp, + "stress_norm": stress_norm, + } + for name, value in values.items(): + if not isinstance(value, (int, float)) or isinstance(value, bool): + raise MathInvariantError(f"{name} must be numeric") + if sigma_one < 0: + raise MathInvariantError("sigma_one must be non-negative") + if d_k <= 0: + raise MathInvariantError("d_k must be positive") + if atp <= 0: + raise MathInvariantError("atp must be positive") + if stress_norm < 0: + raise MathInvariantError("stress_norm must be non-negative") + + tau_bio = sqrt(d_k) * (atp / 2500.0) * exp(-stress_norm) + if tau_bio <= 0: + raise MathInvariantError("tau_bio must remain positive") + return sigma_one / tau_bio + + +def to_evidence(verification: QuorumVerification) -> dict[str, Any]: + return { + "evidence_kind": "SOL_WOLFRAM_QUORUM_VERIFICATION_V1", + "evidence_tier": "T0", + "grants_authority": False, + "verification": asdict(verification), + } From f238e3f710f71de4ac1403cbd02c669ac4891565 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:27:46 +0200 Subject: [PATCH 30/89] test(sol): verify quorum notation and attention monotonicity --- platform/sol/tests/test_math_invariants.py | 47 ++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 platform/sol/tests/test_math_invariants.py diff --git a/platform/sol/tests/test_math_invariants.py b/platform/sol/tests/test_math_invariants.py new file mode 100644 index 000000000..e0f15f06c --- /dev/null +++ b/platform/sol/tests/test_math_invariants.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from pathlib import Path +import sys +import unittest + +VERIFIER_DIR = Path(__file__).resolve().parents[1] / "verifiers" +sys.path.insert(0, str(VERIFIER_DIR)) + +from math_invariants import ( # noqa: E402 + MathInvariantError, + attention_score, + to_evidence, + verify_golden_quorum, +) + + +class MathInvariantTests(unittest.TestCase): + def test_618_is_approximate_golden_conjugate_not_its_inverse(self): + result = verify_golden_quorum() + self.assertEqual(result.threshold, "0.618") + self.assertFalse(result.equals_inverse_of_defined_phi) + self.assertLess(float(result.absolute_error), 0.0001) + + def test_verification_is_deterministic_and_non_authoritative(self): + first = verify_golden_quorum() + second = verify_golden_quorum() + self.assertEqual(first.evidence_digest, second.evidence_digest) + self.assertFalse(to_evidence(first)["grants_authority"]) + + def test_attention_score_increases_with_stress(self): + low = attention_score(sigma_one=1.0, d_k=64, atp=2100, stress_norm=0.2) + high = attention_score(sigma_one=1.0, d_k=64, atp=2100, stress_norm=0.8) + self.assertGreater(high, low) + + def test_attention_score_decreases_with_atp(self): + low_atp = attention_score(sigma_one=1.0, d_k=64, atp=500, stress_norm=0.4) + high_atp = attention_score(sigma_one=1.0, d_k=64, atp=2100, stress_norm=0.4) + self.assertGreater(low_atp, high_atp) + + def test_attention_score_rejects_undefined_denominator(self): + with self.assertRaises(MathInvariantError): + attention_score(sigma_one=1.0, d_k=64, atp=0, stress_norm=0.4) + + +if __name__ == "__main__": + unittest.main() From e6acc795a9accbceb69f040c5ba3f57d19d21b15 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:28:17 +0200 Subject: [PATCH 31/89] docs(sol): record Wolfram audit of holon mathematics --- platform/sol/wolfram/MATH_AUDIT.md | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 platform/sol/wolfram/MATH_AUDIT.md diff --git a/platform/sol/wolfram/MATH_AUDIT.md b/platform/sol/wolfram/MATH_AUDIT.md new file mode 100644 index 000000000..693b4cdc8 --- /dev/null +++ b/platform/sol/wolfram/MATH_AUDIT.md @@ -0,0 +1,58 @@ +# SOL Wolfram Mathematical Audit + +Status: deterministic verification complete for algebraic and monotonicity claims +Scope: `clients/gemma-holon/OGEMMA.md` and generated Hugging Face model card +Authority: evidence only; this audit grants no execution authority + +## Verdict matrix + +| Claim | Verdict | Evidence tier | Required action | +|---|---|---:|---| +| `618/1000 = 1/φ` while `φ = 0.6180339887` | Rejected | T0 | Correct notation. `1/φ ≈ 1.6180339889`, not `0.618`. | +| `618/1000 ≈ φ` for golden conjugate `φ = 1/Φ` | Verified approximation | T0 | State approximation explicitly. Absolute error ≈ `3.398874989e-5`. | +| `1/Φ = Φ - 1` for `Φ = (1+√5)/2` | Verified identity | T0 | Use `Φ` for the golden ratio and `φ` for its conjugate. | +| `τ_bio` decreases as normalized stress increases | Verified for positive `d_k` and ATP | T0 | Keep domain preconditions explicit. | +| `λ_attn` increases as normalized stress increases | Verified for positive inputs | T0 | Keep as a computed operational score. | +| `λ_attn` decreases as ATP increases | Verified for positive inputs | T0 | Reject ATP ≤ 0 before division. | +| `λ_c = 1.0` is a universal BBP collapse threshold | Unproven/model-dependent | T2 | Define the random-matrix model, noise normalization, and aspect ratio; calibrate empirically. | +| `σ² ≥ 2β` universally means martingale suspension | Unproven/model-dependent | T2 | Define the stochastic process, β, filtration, and stopping criterion. | +| Biological state thresholds measure safe operator readiness | Unvalidated | T2 | Require consented calibration data, uncertainty bounds, false-positive/negative analysis, and an override policy. | + +## Exact Wolfram result + +For `φ_defined = 0.6180339887` and `q = 618/1000`: + +```text +q = 0.618 +1 / φ_defined = 1.618033988880521 +q - φ_defined = -0.000033988700000042726 +q - 1 / GoldenRatio = -0.0000339887498948482 +1 / GoldenRatio == GoldenRatio - 1 → True +``` + +Under assumptions `d_k > 0`, `ATP > 0`, `σ₁ > 0`, and `stress_norm ≥ 0`, symbolic differentiation verifies: + +```text +d τ_bio / d stress_norm < 0 → True +d λ_attn / d stress_norm > 0 → True +d λ_attn / d ATP < 0 → True +``` + +## Canonical notation + +```text +Φ = (1 + √5) / 2 ≈ 1.618033988749895 golden ratio +φ = 1 / Φ = Φ - 1 ≈ 0.618033988749895 golden conjugate +quorum_milli = 618 +quorum = 618 / 1000 = 0.618 ≈ φ +``` + +The millesimal threshold is an engineering approximation. It is not exactly equal to `φ`, and it is not `1/φ` when `φ` is defined as `0.618…`. + +## Governance disposition + +1. Algebraic identities and deterministic calculations may be emitted as T0 evidence. +2. The attention score remains T2 until its probabilistic model and calibration dataset are specified. +3. Neither Wolfram output nor oGemma evidence grants authority. Automaton-3 remains the sole authority root. +4. Undefined variables, missing provenance, non-positive denominators, or indeterminate results fail closed. +5. Promotion above T2 requires a versioned dataset, pinned verifier implementation, error analysis, replay package, and independent review. From b2d65ef19efeddf85bd27c8c397754baa85cce44 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:32:51 +0200 Subject: [PATCH 32/89] fix(admission): bind SOL protocol parent-state root --- .../experiments/pr-225-sol-cross-platform-control-plane-v1.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json b/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json index 80211928a..978594ae7 100644 --- a/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json +++ b/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json @@ -6,7 +6,7 @@ "execution_class": "EXPERIMENT", "evidence_tier": "T1", "expected_parent_sha": "ed426124475cc9ee8b4d138b2928ae547352e97b", - "expected_parent_state_root": "410bcd49c721e65050382ae759db7af2f41fa9f572e625174252c72a8748ea93", + "expected_parent_state_root": "d7b8078980dbeb2b71f9df3d499e61199157ccd4f43f0dadd40bf215b8eda931", "constitution": { "path": "CONSTITUTIONAL_DECLARATION.md", "blob_id": "d0c210443e03313113e43da46c4d98269494baeb" From 4cd41a7ddcb13700b36e26e684be00f36ea4da49 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:36:15 +0200 Subject: [PATCH 33/89] feat(sol): add governed oGemma Hub release manifest --- .../sol/huggingface/ogemma-release.v1.json | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 platform/sol/huggingface/ogemma-release.v1.json diff --git a/platform/sol/huggingface/ogemma-release.v1.json b/platform/sol/huggingface/ogemma-release.v1.json new file mode 100644 index 000000000..8b4f35afd --- /dev/null +++ b/platform/sol/huggingface/ogemma-release.v1.json @@ -0,0 +1,44 @@ +{ + "schema_version": "1.0.0", + "release_status": "BLOCKED_PENDING_BASE_REVISION", + "repository_id": "aegis-omega/ogemma-holon", + "repository_type": "model", + "private_by_default": true, + "artifact_kind": "configuration-holon-no-weights", + "holon_id": "gemma-4e4b-iphone", + "display_name": "AEGIS-Ω oGemma Holon", + "base_model": { + "repository_id": "google/gemma-3n-E4B-it", + "revision": null, + "architecture": "gemma3n", + "task": "image-text-to-text", + "license": "gemma", + "gated": true + }, + "evidence_tier": "T2", + "grants_authority": false, + "source_root": "clients/gemma-holon", + "model_card": "platform/sol/huggingface/MODEL_CARD.md", + "artifacts": [ + "config.json", + "state.json", + "OGEMMA.md", + "ogemma_mythos.py", + "submit.py", + "mythos-x-gemma.json", + "skills/ogemma-gate.json", + "skills/ogemma-gate.md", + "skills/stochastic-engine.md", + "quantum/server.py", + "quantum/platform-helpers.py" + ], + "forbidden_artifact_suffixes": [ + ".bin", + ".ckpt", + ".gguf", + ".onnx", + ".pt", + ".pth", + ".safetensors" + ] +} From 562e4e2a6ebd5f5ae53350b9259ffd4e0743b8f8 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:37:06 +0200 Subject: [PATCH 34/89] docs(sol): add corrected oGemma model card --- platform/sol/huggingface/MODEL_CARD.md | 46 ++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 platform/sol/huggingface/MODEL_CARD.md diff --git a/platform/sol/huggingface/MODEL_CARD.md b/platform/sol/huggingface/MODEL_CARD.md new file mode 100644 index 000000000..604bc33d4 --- /dev/null +++ b/platform/sol/huggingface/MODEL_CARD.md @@ -0,0 +1,46 @@ +--- +license: gemma +base_model: google/gemma-3n-E4B-it +tags: + - aegis-omega + - gemma3n + - on-device + - governance + - no-weights +pipeline_tag: image-text-to-text +--- + +# AEGIS-Ω oGemma Holon + +oGemma is a configuration-defined advisory node for the AEGIS-Ω and MYTHOS pipeline. This repository contains no fine-tuned model weights. It contains versioned prompts, gate rules, schemas, tests, and client code that are applied to a separately obtained and pinned revision of `google/gemma-3n-E4B-it`. + +## Provenance + +- Internal holon identifier: `gemma-4e4b-iphone` +- External base model: `google/gemma-3n-E4B-it` +- Base architecture: `gemma3n` +- Base task: `image-text-to-text` +- Artifact kind: configuration holon with no weights +- Evidence tier: T2 +- Grants authority: no +- Authority root: AEGIS Automaton-3 + +The internal holon identifier is not the base-model name. A release must include the exact base-model Hub commit and a SHA-256 manifest for every published artifact. + +## Mathematical notation + +```text +Phi = (1 + sqrt(5)) / 2 +phi = 1 / Phi = Phi - 1 +quorum = 618 / 1000 = 0.618, an approximation to phi +``` + +The deterministic audit is recorded in `platform/sol/wolfram/MATH_AUDIT.md`. Model-dependent threshold claims remain hypotheses until a complete model and calibration record are supplied. + +## Release constraints + +Publication is denied unless the base revision is immutable, the destination is owned by `aegis-omega`, weight-like files are absent, all files are hashed, credentials are absent, and the release remains private unless a separate public-release approval is admitted. + +## License + +The base model is governed by the Gemma license. No base-model weights are redistributed here. From 01b6d93c32576cd74c2c527ed684913777d9e6f5 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:38:06 +0200 Subject: [PATCH 35/89] feat(sol): add oGemma release preflight --- platform/sol/huggingface/release_preflight.py | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 platform/sol/huggingface/release_preflight.py diff --git a/platform/sol/huggingface/release_preflight.py b/platform/sol/huggingface/release_preflight.py new file mode 100644 index 000000000..0dd51c461 --- /dev/null +++ b/platform/sol/huggingface/release_preflight.py @@ -0,0 +1,102 @@ +from __future__ import annotations + +import argparse +from dataclasses import dataclass +from hashlib import sha256 +import json +from pathlib import Path +import re +import sys +from typing import Any + + +GIT_OBJECT_RE = re.compile(r"^[0-9a-f]{40,64}$") + + +class ReleaseError(ValueError): + pass + + +@dataclass(frozen=True, slots=True) +class ReleaseFile: + source: Path + destination: str + sha256: str + size_bytes: int + + +def load_release(path: Path) -> dict[str, Any]: + data = json.loads(path.read_text(encoding="utf-8")) + if data.get("schema_version") != "1.0.0": + raise ReleaseError("unsupported release schema") + if data.get("artifact_kind") != "configuration-holon-no-weights": + raise ReleaseError("release must declare the no-weights artifact kind") + if data.get("grants_authority") is not False: + raise ReleaseError("holon release must not grant authority") + return data + + +def require_pinned_revision(value: str | None) -> str: + if value is None or not GIT_OBJECT_RE.fullmatch(value): + raise ReleaseError("base model revision must be an immutable Hub commit") + return value + + +def collect_release_files(repo_root: Path, release: dict[str, Any]) -> list[ReleaseFile]: + source_root = repo_root / str(release["source_root"]) + model_card = repo_root / str(release["model_card"]) + forbidden = tuple(str(item) for item in release["forbidden_artifact_suffixes"]) + candidates = [(model_card, "README.md")] + candidates.extend((source_root / rel, rel) for rel in release["artifacts"]) + + files: list[ReleaseFile] = [] + for source, destination in candidates: + if not source.is_file(): + raise ReleaseError(f"missing release artifact: {source.relative_to(repo_root)}") + if source.suffix.lower() in forbidden: + raise ReleaseError(f"weight-like artifact is forbidden: {source.name}") + raw = source.read_bytes() + files.append(ReleaseFile(source, destination, sha256(raw).hexdigest(), len(raw))) + return sorted(files, key=lambda item: item.destination) + + +def build_manifest(release: dict[str, Any], files: list[ReleaseFile], revision: str) -> dict[str, Any]: + payload = { + "schema_version": "1.0.0", + "repository_id": release["repository_id"], + "artifact_kind": release["artifact_kind"], + "holon_id": release["holon_id"], + "base_model": {**release["base_model"], "revision": revision}, + "evidence_tier": release["evidence_tier"], + "grants_authority": False, + "files": [ + {"path": item.destination, "sha256": item.sha256, "size_bytes": item.size_bytes} + for item in files + ], + } + canonical = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") + return {**payload, "manifest_digest": sha256(canonical).hexdigest()} + + +def main() -> int: + parser = argparse.ArgumentParser(description="Validate a governed oGemma Hub release") + parser.add_argument("--release", type=Path, default=Path(__file__).with_name("ogemma-release.v1.json")) + parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[3]) + parser.add_argument("--base-revision") + args = parser.parse_args() + + release = load_release(args.release) + revision = require_pinned_revision(args.base_revision or release["base_model"].get("revision")) + if not str(release["repository_id"]).startswith("aegis-omega/"): + raise ReleaseError("destination must be owned by aegis-omega") + files = collect_release_files(args.repo_root, release) + print(json.dumps(build_manifest(release, files, revision), sort_keys=True, indent=2)) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except ReleaseError as exc: + print(f"DENIED: {exc}", file=sys.stderr) + raise SystemExit(2) From 73b5876cb980135552b81181ef56d75fe969b0c6 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 21:38:32 +0200 Subject: [PATCH 36/89] test(sol): enforce governed Hugging Face release preflight --- .../sol/tests/test_huggingface_release.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 platform/sol/tests/test_huggingface_release.py diff --git a/platform/sol/tests/test_huggingface_release.py b/platform/sol/tests/test_huggingface_release.py new file mode 100644 index 000000000..252b5b007 --- /dev/null +++ b/platform/sol/tests/test_huggingface_release.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from pathlib import Path +import sys +import tempfile +import unittest + +PREFLIGHT_DIR = Path(__file__).resolve().parents[1] / "huggingface" +sys.path.insert(0, str(PREFLIGHT_DIR)) + +from release_preflight import ( # noqa: E402 + ReleaseError, + build_manifest, + collect_release_files, + load_release, + require_pinned_revision, +) + + +COMMIT = "a" * 40 + + +class HuggingFaceReleaseTests(unittest.TestCase): + def test_requires_immutable_base_revision(self): + with self.assertRaises(ReleaseError): + require_pinned_revision("main") + self.assertEqual(require_pinned_revision(COMMIT), COMMIT) + + def test_release_declares_no_weights_and_no_authority(self): + release = load_release(PREFLIGHT_DIR / "ogemma-release.v1.json") + self.assertEqual(release["artifact_kind"], "configuration-holon-no-weights") + self.assertFalse(release["grants_authority"]) + + def test_collect_rejects_weight_like_artifact(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + (root / "source").mkdir() + (root / "card.md").write_text("safe", encoding="utf-8") + (root / "source" / "weights.safetensors").write_bytes(b"weights") + release = { + "source_root": "source", + "model_card": "card.md", + "artifacts": ["weights.safetensors"], + "forbidden_artifact_suffixes": [".safetensors"], + } + with self.assertRaises(ReleaseError): + collect_release_files(root, release) + + def test_manifest_is_deterministic(self): + release = load_release(PREFLIGHT_DIR / "ogemma-release.v1.json") + first = build_manifest(release, [], COMMIT) + second = build_manifest(release, [], COMMIT) + self.assertEqual(first["manifest_digest"], second["manifest_digest"]) + self.assertFalse(first["grants_authority"]) + + +if __name__ == "__main__": + unittest.main() From f88edc302ce553d5ea6d02cfe8071c38ed9034cd Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 22:25:04 +0200 Subject: [PATCH 37/89] feat(metacognition): add fail-closed self-regulation controller --- .../src/metacognition/self-regulator.ts | 305 ++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 sovereign-omega-v2/src/metacognition/self-regulator.ts diff --git a/sovereign-omega-v2/src/metacognition/self-regulator.ts b/sovereign-omega-v2/src/metacognition/self-regulator.ts new file mode 100644 index 000000000..2fef9504b --- /dev/null +++ b/sovereign-omega-v2/src/metacognition/self-regulator.ts @@ -0,0 +1,305 @@ +// ============================================================ +// SOVEREIGN OMEGA — Metacognitive Self-Regulator +// EPISTEMIC TIER: T1 · deterministic governance primitive +// +// Converts a verified self-model and observed gaps into a bounded +// adaptation disposition. This module never executes mutations and +// never grants authority; READY_FOR_AUTHORITY means Automaton-3 may +// evaluate the proposal next. +// ============================================================ + +import type { SHA256Hex } from '../core/types.js' +import { hashValue } from '../core/hashing.js' +import { deepFreeze } from '../core/immutable.js' + +export const SELF_REGULATOR_SCHEMA_VERSION = '1.0.0' as const + +export type ConsequenceClass = 'D0' | 'D1' | 'D2' | 'D3' | 'D4' +export type GapSeverity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' +export type GapKind = + | 'INVARIANT_BREACH' + | 'CAPABILITY_DEFICIT' + | 'EVIDENCE_DEFICIT' + | 'PERFORMANCE_REGRESSION' + | 'UNKNOWN' + +export type SelfRegulationMode = + | 'HALT' + | 'OBSERVE_ONLY' + | 'NO_CHANGE' + | 'PROPOSAL_REQUIRED' + | 'REJECTED' + | 'READY_FOR_AUTHORITY' + +export type RequiredNextGate = 'NONE' | 'REANCHOR' | 'OPERATOR_REVIEW' | 'AUTOMATON_3' + +export interface SelfModelHealth { + readonly t0_verdict: boolean + readonly corruption_count: number + readonly membrane_intact: boolean + readonly entropy_bounded: boolean +} + +export interface SelfModelSnapshot { + readonly state_root: SHA256Hex + readonly identity_root: SHA256Hex + readonly policy_root: SHA256Hex + readonly capability_root: SHA256Hex + readonly memory_root: SHA256Hex + readonly metacognition_root: SHA256Hex + readonly health: SelfModelHealth +} + +export interface KnowledgeGap { + readonly gap_id: string + readonly kind: GapKind + readonly severity: GapSeverity + readonly evidence_refs: readonly string[] +} + +export interface ProposedMutation { + readonly path: string + readonly operation: 'CREATE' | 'UPDATE' | 'DELETE' + readonly expected_blob?: string +} + +export interface AdaptationProposal { + readonly proposal_id: string + readonly objective: string + readonly consequence_class: ConsequenceClass + readonly expected_parent_state_root: SHA256Hex + readonly addressed_gap_ids: readonly string[] + readonly requested_capabilities: readonly string[] + readonly mutations: readonly ProposedMutation[] + readonly verification_steps: readonly string[] + readonly rollback_reference?: string + readonly operator_approval_reference?: string + readonly constitutional_change_reference?: string +} + +export interface SelfRegulationInput { + readonly snapshot: SelfModelSnapshot + readonly gaps: readonly KnowledgeGap[] + readonly proposal?: AdaptationProposal +} + +export interface SelfRegulationDecision { + readonly schema_version: typeof SELF_REGULATOR_SCHEMA_VERSION + readonly mode: SelfRegulationMode + readonly reasons: readonly string[] + readonly required_next_gate: RequiredNextGate + readonly grants_authority: false + readonly requires_automaton3: boolean + readonly self_model_digest: SHA256Hex + readonly proposal_digest: SHA256Hex | null + readonly decision_digest: SHA256Hex +} + +export class SelfRegulationError extends Error { + override readonly name = 'SelfRegulationError' + constructor(message: string) { + super(message) + Object.setPrototypeOf(this, new.target.prototype) + } +} + +const HASH_PATTERN = /^[0-9a-f]{64}$/ +const SAFE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{1,127}$/ +const SAFE_PATH_PATTERN = /^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))[A-Za-z0-9._@/+:-]+$/ +const VALID_KINDS = new Set([ + 'INVARIANT_BREACH', + 'CAPABILITY_DEFICIT', + 'EVIDENCE_DEFICIT', + 'PERFORMANCE_REGRESSION', + 'UNKNOWN', +]) +const VALID_SEVERITIES = new Set(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']) +const VALID_CLASSES = new Set(['D0', 'D1', 'D2', 'D3', 'D4']) +const VALID_OPERATIONS = new Set(['CREATE', 'UPDATE', 'DELETE']) + +function assertNonEmpty(field: string, value: unknown): asserts value is string { + if (typeof value !== 'string' || value.trim() === '') { + throw new SelfRegulationError(`${field} must be a non-empty string`) + } +} + +function assertHash(field: string, value: unknown): asserts value is SHA256Hex { + if (typeof value !== 'string' || !HASH_PATTERN.test(value)) { + throw new SelfRegulationError(`${field} must be lowercase SHA-256 hex`) + } +} + +function sortedUnique(field: string, values: readonly string[]): readonly string[] { + if (!Array.isArray(values)) throw new SelfRegulationError(`${field} must be an array`) + const normalized = values.map((value, index) => { + assertNonEmpty(`${field}[${index}]`, value) + return value.trim() + }) + const unique = [...new Set(normalized)].sort() + if (unique.length !== normalized.length) throw new SelfRegulationError(`${field} must be unique`) + return unique +} + +function validateSnapshot(snapshot: SelfModelSnapshot): void { + assertHash('snapshot.state_root', snapshot.state_root) + assertHash('snapshot.identity_root', snapshot.identity_root) + assertHash('snapshot.policy_root', snapshot.policy_root) + assertHash('snapshot.capability_root', snapshot.capability_root) + assertHash('snapshot.memory_root', snapshot.memory_root) + assertHash('snapshot.metacognition_root', snapshot.metacognition_root) + if (!Number.isInteger(snapshot.health.corruption_count) || snapshot.health.corruption_count < 0) { + throw new SelfRegulationError('snapshot.health.corruption_count must be a non-negative integer') + } + for (const field of ['t0_verdict', 'membrane_intact', 'entropy_bounded'] as const) { + if (typeof snapshot.health[field] !== 'boolean') { + throw new SelfRegulationError(`snapshot.health.${field} must be boolean`) + } + } +} + +function normalizeGaps(gaps: readonly KnowledgeGap[]): readonly KnowledgeGap[] { + if (!Array.isArray(gaps)) throw new SelfRegulationError('gaps must be an array') + const ids = new Set() + const normalized = gaps.map((gap, index) => { + if (!SAFE_ID_PATTERN.test(gap.gap_id)) throw new SelfRegulationError(`gaps[${index}].gap_id is invalid`) + if (ids.has(gap.gap_id)) throw new SelfRegulationError('gap_id values must be unique') + ids.add(gap.gap_id) + if (!VALID_KINDS.has(gap.kind)) throw new SelfRegulationError(`gaps[${index}].kind is invalid`) + if (!VALID_SEVERITIES.has(gap.severity)) throw new SelfRegulationError(`gaps[${index}].severity is invalid`) + return { + gap_id: gap.gap_id, + kind: gap.kind, + severity: gap.severity, + evidence_refs: sortedUnique(`gaps[${index}].evidence_refs`, gap.evidence_refs), + } + }) + return normalized.sort((a, b) => a.gap_id.localeCompare(b.gap_id)) +} + +function normalizeProposal(proposal: AdaptationProposal): AdaptationProposal { + if (!SAFE_ID_PATTERN.test(proposal.proposal_id)) throw new SelfRegulationError('proposal.proposal_id is invalid') + assertNonEmpty('proposal.objective', proposal.objective) + if (!VALID_CLASSES.has(proposal.consequence_class)) throw new SelfRegulationError('proposal.consequence_class is invalid') + assertHash('proposal.expected_parent_state_root', proposal.expected_parent_state_root) + + if (!Array.isArray(proposal.mutations)) throw new SelfRegulationError('proposal.mutations must be an array') + const mutations = proposal.mutations.map((mutation, index) => { + if (!SAFE_PATH_PATTERN.test(mutation.path)) throw new SelfRegulationError(`proposal.mutations[${index}].path is invalid`) + if (!VALID_OPERATIONS.has(mutation.operation)) throw new SelfRegulationError(`proposal.mutations[${index}].operation is invalid`) + if (mutation.expected_blob !== undefined && !/^[0-9a-f]{40,64}$/.test(mutation.expected_blob)) { + throw new SelfRegulationError(`proposal.mutations[${index}].expected_blob is invalid`) + } + return mutation.expected_blob === undefined + ? { path: mutation.path, operation: mutation.operation } + : { path: mutation.path, operation: mutation.operation, expected_blob: mutation.expected_blob } + }) + + const verification_steps = proposal.verification_steps.map((step, index) => { + assertNonEmpty(`proposal.verification_steps[${index}]`, step) + return step.trim() + }) + + return { + proposal_id: proposal.proposal_id, + objective: proposal.objective.trim(), + consequence_class: proposal.consequence_class, + expected_parent_state_root: proposal.expected_parent_state_root, + addressed_gap_ids: sortedUnique('proposal.addressed_gap_ids', proposal.addressed_gap_ids), + requested_capabilities: sortedUnique('proposal.requested_capabilities', proposal.requested_capabilities), + mutations, + verification_steps, + ...(proposal.rollback_reference === undefined ? {} : { rollback_reference: proposal.rollback_reference }), + ...(proposal.operator_approval_reference === undefined ? {} : { operator_approval_reference: proposal.operator_approval_reference }), + ...(proposal.constitutional_change_reference === undefined ? {} : { constitutional_change_reference: proposal.constitutional_change_reference }), + } +} + +function forbiddenCapability(capability: string): boolean { + return capability.startsWith('authority.') || + capability === 'policy.override' || + capability === 'receipt.forge' || + capability === 'secret.readback' +} + +export async function regulateSelf(input: SelfRegulationInput): Promise { + validateSnapshot(input.snapshot) + const gaps = normalizeGaps(input.gaps) + const self_model_digest = await hashValue({ + domain: 'AEGIS_SELF_MODEL_V1', + snapshot: input.snapshot, + gaps, + }) + + let proposal_digest: SHA256Hex | null = null + let mode: SelfRegulationMode + let required_next_gate: RequiredNextGate + const reasons: string[] = [] + + const health = input.snapshot.health + if (!health.t0_verdict) reasons.push('T0_VERDICT_FALSE') + if (health.corruption_count > 0) reasons.push('CORRUPTION_DETECTED') + if (!health.membrane_intact) reasons.push('MEMBRANE_BREACH') + + if (reasons.length > 0) { + mode = 'HALT' + required_next_gate = 'REANCHOR' + } else if (!health.entropy_bounded) { + mode = 'OBSERVE_ONLY' + required_next_gate = 'REANCHOR' + reasons.push('ADAPTATION_EXCEEDS_REPLAY_CAPACITY') + } else if (gaps.length === 0) { + mode = 'NO_CHANGE' + required_next_gate = 'NONE' + reasons.push('NO_VERIFIED_GAP') + } else if (input.proposal === undefined) { + mode = 'PROPOSAL_REQUIRED' + required_next_gate = 'OPERATOR_REVIEW' + reasons.push('VERIFIED_GAP_WITHOUT_ADAPTATION_PROPOSAL') + } else { + const proposal = normalizeProposal(input.proposal) + proposal_digest = await hashValue({ domain: 'AEGIS_ADAPTATION_PROPOSAL_V1', proposal }) + const knownGapIds = new Set(gaps.map(gap => gap.gap_id)) + + if (proposal.expected_parent_state_root !== input.snapshot.state_root) reasons.push('STALE_PARENT_STATE') + if (proposal.addressed_gap_ids.length === 0) reasons.push('NO_ADDRESSED_GAPS') + if (proposal.addressed_gap_ids.some(id => !knownGapIds.has(id))) reasons.push('UNKNOWN_GAP_REFERENCE') + if (proposal.mutations.length === 0) reasons.push('NO_PROPOSED_MUTATION') + if (proposal.verification_steps.length === 0) reasons.push('NO_VERIFICATION_PLAN') + if (proposal.requested_capabilities.some(forbiddenCapability)) reasons.push('FORBIDDEN_CAPABILITY_REQUEST') + + if (['D2', 'D3', 'D4'].includes(proposal.consequence_class) && !proposal.rollback_reference) { + reasons.push('ROLLBACK_REFERENCE_REQUIRED') + } + if (['D3', 'D4'].includes(proposal.consequence_class) && !proposal.operator_approval_reference) { + reasons.push('OPERATOR_APPROVAL_REQUIRED') + } + if (proposal.consequence_class === 'D4' && !proposal.constitutional_change_reference) { + reasons.push('CONSTITUTIONAL_CHANGE_REFERENCE_REQUIRED') + } + + if (reasons.length > 0) { + mode = 'REJECTED' + required_next_gate = reasons.includes('OPERATOR_APPROVAL_REQUIRED') + ? 'OPERATOR_REVIEW' + : 'NONE' + } else { + mode = 'READY_FOR_AUTHORITY' + required_next_gate = 'AUTOMATON_3' + reasons.push('PROPOSAL_IS_REPLAYABLE_AND_BOUNDED') + } + } + + const unsigned = { + schema_version: SELF_REGULATOR_SCHEMA_VERSION, + mode, + reasons: Object.freeze([...reasons]), + required_next_gate, + grants_authority: false as const, + requires_automaton3: mode === 'READY_FOR_AUTHORITY', + self_model_digest, + proposal_digest, + } + const decision_digest = await hashValue({ domain: 'AEGIS_SELF_REGULATION_DECISION_V1', decision: unsigned }) + + return deepFreeze({ ...unsigned, decision_digest }) +} From 9950dcd51f09fa6df321b143349b74a44b590cbd Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Mon, 20 Jul 2026 22:25:39 +0200 Subject: [PATCH 38/89] test(metacognition): cover governed self-regulation states --- .../test/unit/self-regulator.test.ts | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 sovereign-omega-v2/test/unit/self-regulator.test.ts diff --git a/sovereign-omega-v2/test/unit/self-regulator.test.ts b/sovereign-omega-v2/test/unit/self-regulator.test.ts new file mode 100644 index 000000000..0095e73c5 --- /dev/null +++ b/sovereign-omega-v2/test/unit/self-regulator.test.ts @@ -0,0 +1,150 @@ +// ============================================================ +// SOVEREIGN OMEGA — Metacognitive Self-Regulator tests +// ============================================================ + +import { describe, expect, it } from 'vitest' +import type { SHA256Hex } from '../../src/core/types.js' +import { + SelfRegulationError, + regulateSelf, +} from '../../src/metacognition/self-regulator.js' +import type { + AdaptationProposal, + KnowledgeGap, + SelfModelSnapshot, +} from '../../src/metacognition/self-regulator.js' + +const H = (character: string) => character.repeat(64) as SHA256Hex + +function snapshot(overrides: Partial = {}): SelfModelSnapshot { + return { + state_root: H('1'), + identity_root: H('2'), + policy_root: H('3'), + capability_root: H('4'), + memory_root: H('5'), + metacognition_root: H('6'), + health: { + t0_verdict: true, + corruption_count: 0, + membrane_intact: true, + entropy_bounded: true, + ...overrides, + }, + } +} + +const GAP: KnowledgeGap = { + gap_id: 'gap.self-model.001', + kind: 'CAPABILITY_DEFICIT', + severity: 'HIGH', + evidence_refs: ['evidence:unit-test'], +} + +function proposal(overrides: Partial = {}): AdaptationProposal { + return { + proposal_id: 'proposal.self-model.001', + objective: 'Add a deterministic self-regulation transition.', + consequence_class: 'D2', + expected_parent_state_root: H('1'), + addressed_gap_ids: [GAP.gap_id], + requested_capabilities: ['repo.file.propose'], + mutations: [{ path: 'src/metacognition/regulator.ts', operation: 'CREATE' }], + verification_steps: ['npm test -- self-regulator.test.ts'], + rollback_reference: 'git:revert-candidate', + ...overrides, + } +} + +describe('regulateSelf', () => { + it('returns NO_CHANGE when no verified gap exists', async () => { + const decision = await regulateSelf({ snapshot: snapshot(), gaps: [] }) + expect(decision.mode).toBe('NO_CHANGE') + expect(decision.required_next_gate).toBe('NONE') + expect(decision.grants_authority).toBe(false) + }) + + it('requires a proposal when verified gaps exist', async () => { + const decision = await regulateSelf({ snapshot: snapshot(), gaps: [GAP] }) + expect(decision.mode).toBe('PROPOSAL_REQUIRED') + expect(decision.required_next_gate).toBe('OPERATOR_REVIEW') + }) + + it('halts on a membrane breach', async () => { + const decision = await regulateSelf({ + snapshot: snapshot({ membrane_intact: false }), + gaps: [GAP], + proposal: proposal(), + }) + expect(decision.mode).toBe('HALT') + expect(decision.reasons).toContain('MEMBRANE_BREACH') + expect(decision.required_next_gate).toBe('REANCHOR') + }) + + it('enters observation-only mode when adaptation exceeds replay capacity', async () => { + const decision = await regulateSelf({ + snapshot: snapshot({ entropy_bounded: false }), + gaps: [GAP], + proposal: proposal(), + }) + expect(decision.mode).toBe('OBSERVE_ONLY') + expect(decision.requires_automaton3).toBe(false) + }) + + it('rejects a stale parent state', async () => { + const decision = await regulateSelf({ + snapshot: snapshot(), + gaps: [GAP], + proposal: proposal({ expected_parent_state_root: H('9') }), + }) + expect(decision.mode).toBe('REJECTED') + expect(decision.reasons).toContain('STALE_PARENT_STATE') + }) + + it('rejects capabilities that could bypass authority', async () => { + const decision = await regulateSelf({ + snapshot: snapshot(), + gaps: [GAP], + proposal: proposal({ requested_capabilities: ['authority.grant'] }), + }) + expect(decision.mode).toBe('REJECTED') + expect(decision.reasons).toContain('FORBIDDEN_CAPABILITY_REQUEST') + }) + + it('requires explicit approval for D3 proposals', async () => { + const decision = await regulateSelf({ + snapshot: snapshot(), + gaps: [GAP], + proposal: proposal({ consequence_class: 'D3' }), + }) + expect(decision.mode).toBe('REJECTED') + expect(decision.reasons).toContain('OPERATOR_APPROVAL_REQUIRED') + expect(decision.required_next_gate).toBe('OPERATOR_REVIEW') + }) + + it('routes a bounded, replayable proposal to Automaton-3', async () => { + const decision = await regulateSelf({ + snapshot: snapshot(), + gaps: [GAP], + proposal: proposal(), + }) + expect(decision.mode).toBe('READY_FOR_AUTHORITY') + expect(decision.required_next_gate).toBe('AUTOMATON_3') + expect(decision.requires_automaton3).toBe(true) + expect(decision.grants_authority).toBe(false) + expect(Object.isFrozen(decision)).toBe(true) + }) + + it('is deterministic for identical self-models and proposals', async () => { + const input = { snapshot: snapshot(), gaps: [GAP], proposal: proposal() } + const [first, second] = await Promise.all([regulateSelf(input), regulateSelf(input)]) + expect(first.self_model_digest).toBe(second.self_model_digest) + expect(first.proposal_digest).toBe(second.proposal_digest) + expect(first.decision_digest).toBe(second.decision_digest) + }) + + it('fails closed on malformed self-model roots', async () => { + const malformed = { ...snapshot(), state_root: 'not-a-hash' as SHA256Hex } + await expect(regulateSelf({ snapshot: malformed, gaps: [] })).rejects.toThrow(SelfRegulationError) + }) +}) From bf8289467a252abc683b691c5f2c45401dfdf3f9 Mon Sep 17 00:00:00 2001 From: tarikskalic33 <228550385+tarikskalic33@users.noreply.github.com> Date: Tue, 28 Jul 2026 14:46:30 +0200 Subject: [PATCH 39/89] feat(automaton3): close governed adaptation outcomes --- .github/workflows/automaton-3.yml | 4 +- agents/coordinator.py | 31 +- ...DR-0021-automaton-3-sovereign-execution.md | 18 +- docs/security/AUTOMATON3_THREAT_MODEL.md | 2 + harness/requirements-automaton3.txt | 2 + harness/sdk/authority_client.py | 65 +- harness/sdk/sovereign_execution.py | 1115 ++++++++++++++--- .../authority-decision-receipt.v1.schema.json | 60 + schemas/mutation-receipt.v1.schema.json | 7 +- scripts/automaton3-authority.py | 74 +- scripts/run-automaton3-tests.py | 69 +- scripts/validate-automaton3.py | 10 +- sovereign-omega-v2/mcp-server/package.json | 2 +- .../mcp-server/src/authority-response.ts | 526 ++++++++ sovereign-omega-v2/mcp-server/src/index.ts | 149 ++- .../mcp-server/test/authority-response.mjs | 378 ++++++ .../mcp-server/test/automaton3-authority.mjs | 2 +- .../python/tests/test_automaton3.py | 406 +++++- .../tests/test_coordinator_authority.py | 31 +- .../src/metacognition/outcome-comparator.ts | 996 +++++++++++++++ .../src/metacognition/self-regulator.ts | 154 ++- .../metacognitive-outcome-closure.test.ts | 224 ++++ .../test/unit/outcome-comparator.test.ts | 641 ++++++++++ .../test/unit/self-regulator.test.ts | 233 +++- 24 files changed, 4879 insertions(+), 320 deletions(-) create mode 100644 harness/requirements-automaton3.txt create mode 100644 schemas/authority-decision-receipt.v1.schema.json create mode 100644 sovereign-omega-v2/mcp-server/src/authority-response.ts create mode 100644 sovereign-omega-v2/mcp-server/test/authority-response.mjs create mode 100644 sovereign-omega-v2/src/metacognition/outcome-comparator.ts create mode 100644 sovereign-omega-v2/test/integration/metacognitive-outcome-closure.test.ts create mode 100644 sovereign-omega-v2/test/unit/outcome-comparator.test.ts diff --git a/.github/workflows/automaton-3.yml b/.github/workflows/automaton-3.yml index 77e1b60d1..c6bb67e75 100644 --- a/.github/workflows/automaton-3.yml +++ b/.github/workflows/automaton-3.yml @@ -49,8 +49,8 @@ jobs: cache: npm cache-dependency-path: sovereign-omega-v2/mcp-server/package-lock.json - - name: Install exact schema validator - run: pip install jsonschema==4.23.0 + - name: Install exact schema and signature validators + run: pip install --requirement harness/requirements-automaton3.txt - name: Validate JSON schemas shell: bash diff --git a/agents/coordinator.py b/agents/coordinator.py index 1b02b1225..e63a62eea 100644 --- a/agents/coordinator.py +++ b/agents/coordinator.py @@ -7,16 +7,13 @@ """ from __future__ import annotations -import json -import os -import time from dataclasses import asdict, dataclass from pathlib import Path from typing import Any from agents import coordinator_legacy as _legacy from harness.sdk.authority_client import authorize_from_environment -from harness.sdk.skill_routing import ADMITTED, DENIED, SkillRoutingReceipt, record_skill_observation +from harness.sdk.skill_routing import ADMITTED, DENIED, SkillRoutingReceipt for _name in dir(_legacy): if not _name.startswith("__") and _name not in globals(): @@ -46,6 +43,7 @@ def __init__(self, *, skill_tree_path: str | Path = _legacy.SKILL_TREE_PATH, rep self._repo_root = Path(repo_root).resolve() self._capability_map = dict(capability_map or _legacy.CAPABILITY_SKILL_MAP) self._last_mutation_error: str | None = None + self._last_untrusted_observation: tuple[str, bool] | None = None def _central_decision(self, *, role: str, task_instruction: str) -> dict[str, Any]: action = {"operation": "agent-dispatch", "role": role, "instruction_digest": __import__("hashlib").sha256(task_instruction.encode("utf-8")).hexdigest()} @@ -92,20 +90,17 @@ def score_role_for_task(self, role: "AgentRole", task_instruction: str, agent_de return self.role_routing_receipt(role, task_instruction, agent_defs).authority_score def emit_skill_event(self, capability: str, success: bool) -> None: - """Record telemetry only; an observation never grants authority by itself.""" - skill_id = self._capability_map.get(capability) - try: - tree = json.loads(self._skill_tree_path.read_text(encoding="utf-8")) - if skill_id is None: - raise ValueError("unmapped capability") - observed_at = time.strftime("%Y-%m-%dT%H:%M:%S+00:00", time.gmtime()) - updated = record_skill_observation(tree, skill_id=skill_id, success=success, observed_at=observed_at, repo_root=self._repo_root) - temporary = self._skill_tree_path.with_suffix(".json.tmp") - temporary.write_text(json.dumps(updated, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") - os.replace(temporary, self._skill_tree_path) - self._last_mutation_error = None - except (OSError, TypeError, ValueError) as exc: - self._last_mutation_error = type(exc).__name__ + """Quarantine the legacy self-report without changing competence state. + + ``coordinator_legacy`` derives this Boolean from an agent's own result. + It is useful diagnostic input, but it is not independently verified + terminal evidence and therefore cannot increment validated runs or + rewrite the authority-bearing skill registry. Certified outcome + evidence must return through the governed outcome-learning path and a + new Automaton-3 mutation admission. + """ + self._last_untrusted_observation = (capability, success) + self._last_mutation_error = "CERTIFIED_OUTCOME_REQUIRED" _legacy.SkillRouter = SkillRouter diff --git a/docs/adr/ADR-0021-automaton-3-sovereign-execution.md b/docs/adr/ADR-0021-automaton-3-sovereign-execution.md index d54c0eaa9..93ebd10e5 100644 --- a/docs/adr/ADR-0021-automaton-3-sovereign-execution.md +++ b/docs/adr/ADR-0021-automaton-3-sovereign-execution.md @@ -11,9 +11,21 @@ The control plane separates five concerns: 1. `ExecutionIdentityEnvelope` binds the request to canonical repository identity, source commit, logical repository root, actor, physical executor, workflow, capability, policy, registry, and action digests. 2. `WorkspaceBinding` binds the canonical remote, logical root, project identity, source commit, and operator authorization. Absolute paths remain observational metadata. -3. `AuthorityEvaluator` applies the D0–D4 consequence policy and evidence-bound capability registry. Unknown, unobserved, under-validated, unavailable, or unmapped capabilities receive zero operational authority. +3. `AuthorityEvaluator` applies the D0–D4 consequence policy and evidence-bound capability registry. Unknown, unobserved, under-validated, unavailable, or unmapped capabilities receive zero operational authority. Its terminal artifact is an `AuthorityDecisionReceipt`; `ADMITTED` means execution may be attempted, never that execution succeeded. 4. `WriterLeaseManager` provides one active writer per authority domain, monotone generations, fencing tokens, expected-parent checks, and replay rejection. -5. `DurableExecutionRegistry`, `EventEnvelope`, and `ReceiptChain` preserve operator visibility, mediated communication, idempotency, cancellation, and deterministic mutation or denial evidence. +5. `DurableExecutionRegistry`, `EventEnvelope`, and `ReceiptChain` preserve operator visibility, mediated communication, idempotency, cancellation, and deterministic mutation or denial evidence. A `MutationReceipt` may be created only after an admitted decision, admitted writer lease, durable execution registration, actual executor result, and explicit terminal outcome. It binds the authority, lease, and durable-execution roots. + +The receipt lifecycle is deliberately split: + +```text +PolicyDecision + AuthorityDecisionReceipt + -> authorization only + +WriterLease + provider execution + postcondition verification + -> terminal MutationReceipt +``` + +`ADMITTED` is not mapped to `SUCCEEDED`. A transport adapter that receives an incomplete, malformed, non-zero-exit, or root-inconsistent authority response must deny locally before contacting the provider. ## Determinism boundary @@ -26,7 +38,7 @@ The deterministic `repository_root` and `workspace_root` are the logical root `. ## Integration - `agents/coordinator.py` grants dispatch authority only through `authorize_from_environment`. -- MCP consequential tools invoke `scripts/automaton3-authority.py`; an unavailable evaluator or identity denies before bridge access. +- MCP consequential tools invoke `scripts/automaton3-authority.py`; an unavailable evaluator or identity denies before bridge access. The MCP boundary independently validates subprocess success, exact response shape, source commit, identity, workspace, policy, registry, action, decision, and authority-receipt roots. - CI invokes the same core module for policy, workspace, lease, durable execution, event, and receipt tests. - D0 read-only MCP resources remain key-free and cannot mutate state. diff --git a/docs/security/AUTOMATON3_THREAT_MODEL.md b/docs/security/AUTOMATON3_THREAT_MODEL.md index 12c30d86e..7c519cc00 100644 --- a/docs/security/AUTOMATON3_THREAT_MODEL.md +++ b/docs/security/AUTOMATON3_THREAT_MODEL.md @@ -22,6 +22,8 @@ The operator, requesting actor, configured model, reviewing model, physical exec | Raw peer instructions becoming authority | typed EventEnvelope, bounded text, payload digest, policy and receipt references | envelope rejected | | Unicode/control-character bypass | NFC equality and Unicode control-category rejection in authority fields | identity/event denial | | Broken evidence or receipt chain | repository containment, file existence, parent digest, monotone sequence | exact denial code | +| Authorization reported as completed execution | distinct authority and terminal receipt types; explicit terminal outcome and authority/lease/durable root binding | no terminal success receipt | +| Forged or truncated authority subprocess output | zero exit status, exact runtime schema, cross-binding, and independently recomputed decision/receipt roots | local MCP denial before provider access | | Authority service outage | no local fallback | `AUTHORITY_SERVICE_UNAVAILABLE` | | Operator notification suppression | notifications and receipts are outside peer-message restrictions | constitutional violation | diff --git a/harness/requirements-automaton3.txt b/harness/requirements-automaton3.txt new file mode 100644 index 000000000..421d0def0 --- /dev/null +++ b/harness/requirements-automaton3.txt @@ -0,0 +1,2 @@ +cryptography==49.0.0 +jsonschema==4.23.0 diff --git a/harness/sdk/authority_client.py b/harness/sdk/authority_client.py index 60dd74315..c19ff664c 100644 --- a/harness/sdk/authority_client.py +++ b/harness/sdk/authority_client.py @@ -9,9 +9,10 @@ from harness.sdk.sovereign_execution import ( ADMITTED, ApprovalGrant, AuthorityEvaluator, AuthorityRequest, - ExecutionIdentityEnvelope, ZERO_HASH, canonical_hash, - load_capability_registry, load_policy, make_mutation_receipt, - verify_workspace, + ExecutionIdentityEnvelope, canonical_hash, canonical_remote, + git_head, git_remote, + load_capability_registry_from_commit, load_policy_from_commit, + make_authority_decision_receipt, verify_live_authority_roots, verify_workspace, ) REPO_ROOT = Path(__file__).resolve().parents[2] @@ -23,7 +24,7 @@ def _denial(code: str, detail: str = "") -> dict[str, Any]: return body -def authorize_from_environment(*, action_class: str, authority_domain: str, requested_capability: str, tool: str, target: str, action: dict[str, Any], current_generation: int = 0, idempotency_key: str = "NONE", compensation_reference: str = "NONE") -> dict[str, Any]: +def authorize_from_environment(*, action_class: str, authority_domain: str, requested_capability: str, tool: str, target: str, action: dict[str, Any], current_generation: int = 0, rollback_reference: str = "NONE", idempotency_key: str = "NONE", compensation_reference: str = "NONE") -> dict[str, Any]: raw_identity = os.environ.get("AEGIS_EXECUTION_IDENTITY_JSON") if not raw_identity: return _denial("IDENTITY_UNAVAILABLE") @@ -38,11 +39,18 @@ def authorize_from_environment(*, action_class: str, authority_domain: str, requ try: observation = json.loads(os.environ.get("AEGIS_WORKSPACE_OBSERVATION_JSON", "{}")) + live_head = git_head(REPO_ROOT) + live_remote = git_remote(REPO_ROOT) + if live_head != identity.source_commit: + return _denial("SOURCE_COMMIT_MISMATCH") + claimed_remote = observation.get("remote_origin") + if claimed_remote is not None and canonical_remote(claimed_remote) != live_remote: + return _denial("WORKSPACE_REMOTE_CLAIM_MISMATCH") workspace = verify_workspace( declared_root=REPO_ROOT, cwd=observation.get("actual_cwd", os.getcwd()), expected_remote=identity.repository_identity, - actual_remote=observation.get("remote_origin", identity.repository_identity), + actual_remote=live_remote, project_identity=identity.project_identity, source_commit=identity.source_commit, operator_authorization=identity.approval_reference, @@ -56,14 +64,28 @@ def authorize_from_environment(*, action_class: str, authority_domain: str, requ return _denial("WORKSPACE_DENIED", ",".join(workspace.denial_codes)) try: - policy, policy_root = load_policy(REPO_ROOT / "harness/policies/consequence-policy.v1.json") - registry, registry_root = load_capability_registry( + policy, policy_root = load_policy_from_commit( repository_root=REPO_ROOT, - skill_tree_path=REPO_ROOT / "harness/skill_tree.json", - capability_map_path=REPO_ROOT / "harness/policies/capability-map.v1.json", + source_commit=identity.source_commit, + policy_path="harness/policies/consequence-policy.v1.json", + ) + registry, skills_root, registry_root = load_capability_registry_from_commit( + repository_root=REPO_ROOT, + source_commit=identity.source_commit, + skill_tree_path="harness/skill_tree.json", + capability_map_path="harness/policies/capability-map.v1.json", ) except Exception as exc: return _denial("AUTHORITY_SERVICE_UNAVAILABLE", str(exc)) + try: + verify_live_authority_roots( + identity, + skills_root=skills_root, + registry_root=registry_root, + policy_root=policy_root, + ) + except Exception as exc: + return _denial(str(exc), "commit-bound authority roots do not match execution identity") approval = None raw_approval = os.environ.get("AEGIS_APPROVAL_GRANT_JSON") @@ -77,23 +99,32 @@ def authorize_from_environment(*, action_class: str, authority_domain: str, requ requested_capability=requested_capability, tool=tool, target=target, identity_root=identity_root, workspace_binding=identity.workspace_binding, source_commit=identity.source_commit, registry_root=registry_root, - policy_root=policy_root, current_generation=current_generation, + policy_root=policy_root, action_digest=action_digest, + expected_pre_state=identity.expected_pre_state, + workspace_mode="READ_ONLY" if action_class == "D0" else "REPOSITORY", + current_generation=current_generation, approval_reference=identity.approval_reference, + rollback_reference=rollback_reference, idempotency_key=idempotency_key, compensation_reference=compensation_reference, ) - decision = AuthorityEvaluator(policy=policy, registry=registry, repository_root=REPO_ROOT).evaluate(request, approval=approval) - receipt = make_mutation_receipt( - identity_root=identity_root, workspace_binding=identity.workspace_binding, - decision=decision, pre_state_digest=identity.expected_pre_state, - action_digest=action_digest, result={"authority_outcome": decision.outcome}, - post_state_digest=identity.expected_pre_state, parent_receipt=ZERO_HASH, sequence=0, + try: + trusted_operator_keys = json.loads(os.environ.get("AEGIS_TRUSTED_OPERATOR_KEYS_JSON", "{}")) + authority_issuer_key_id = os.environ["AEGIS_AUTHORITY_ISSUER_KEY_ID"] + authority_signing_key = os.environ["AEGIS_AUTHORITY_SIGNING_KEY_HEX"] + except Exception as exc: + return _denial("AUTHORITY_SIGNER_UNAVAILABLE", str(exc)) + evaluator = AuthorityEvaluator(policy=policy, registry=registry, repository_root=REPO_ROOT, trusted_operator_keys=trusted_operator_keys) + decision = evaluator.evaluate(request, approval=approval) + receipt = make_authority_decision_receipt( + identity=identity, request=request, decision=decision, evaluator=evaluator, + issuer_key_id=authority_issuer_key_id, issuer_private_key_hex=authority_signing_key, ) return { "outcome": decision.outcome, "authority_score": decision.authority_score, "denial_codes": list(decision.denial_codes), "decision_root": decision.decision_root, - "receipt_root": receipt.root, + "authority_receipt_root": receipt.root, "execution_identity_root": identity_root, "workspace_binding": identity.workspace_binding, "observation": asdict(workspace.observation), diff --git a/harness/sdk/sovereign_execution.py b/harness/sdk/sovereign_execution.py index d0fb7848d..4790ec16e 100644 --- a/harness/sdk/sovereign_execution.py +++ b/harness/sdk/sovereign_execution.py @@ -1,12 +1,14 @@ """Automaton-3 operator-sovereign execution reference model. This module is the single authority-decision path for consequential AEGIS work. -It is standard-library only, deterministic at its hashed boundaries, and fail-closed. +It is deterministic at its hashed boundaries and fail-closed. Ed25519 verification +uses the CI-pinned ``cryptography`` package; unavailable crypto denies authority. Absolute paths and timestamps are observational metadata and never enter deterministic roots. """ from __future__ import annotations import copy +import binascii import hashlib import json import os @@ -48,6 +50,61 @@ class SovereignExecutionError(ValueError): pass +_POLICY_FIELDS = frozenset(( + "minimum_validated_runs", + "approval", + "workspace", + "replay", + "rollback", + "external_idempotency", +)) + + +def validate_consequence_policy(policy: Mapping[str, Mapping[str, Any]]) -> dict[str, dict[str, Any]]: + """Enforce constitutional safety floors independently of policy data.""" + if not isinstance(policy, Mapping) or set(policy) != set(ACTION_CLASSES): + raise SovereignExecutionError("POLICY_CLASSES_INVALID") + validated: dict[str, dict[str, Any]] = {} + for action_class in ACTION_CLASSES: + record = policy.get(action_class) + if not isinstance(record, Mapping) or set(record) != _POLICY_FIELDS: + raise SovereignExecutionError(f"POLICY_FIELDS_INVALID:{action_class}") + minimum = record.get("minimum_validated_runs") + if isinstance(minimum, bool) or not isinstance(minimum, int) or minimum < 0: + raise SovereignExecutionError(f"POLICY_MINIMUM_RUNS_INVALID:{action_class}") + if action_class != D0 and minimum < MIN_VALIDATED_RUNS: + raise SovereignExecutionError(f"POLICY_MINIMUM_RUNS_BELOW_FLOOR:{action_class}") + approval = record.get("approval") + if approval not in ("NONE", "EXPLICIT"): + raise SovereignExecutionError(f"POLICY_APPROVAL_INVALID:{action_class}") + if action_class in (D2, D3, D4) and approval != "EXPLICIT": + raise SovereignExecutionError(f"POLICY_EXPLICIT_APPROVAL_REQUIRED:{action_class}") + expected_workspace = "READ_ONLY" if action_class == D0 else "REPOSITORY" + if record.get("workspace") != expected_workspace: + raise SovereignExecutionError(f"POLICY_WORKSPACE_INVALID:{action_class}") + replay = record.get("replay") + if not isinstance(replay, bool): + raise SovereignExecutionError(f"POLICY_REPLAY_INVALID:{action_class}") + if action_class != D0 and not replay: + raise SovereignExecutionError(f"POLICY_REPLAY_REQUIRED:{action_class}") + expected_rollback = { + D0: "NONE", + D1: "REQUIRED", + D2: "REQUIRED", + D3: "COMPENSATION_OR_IDEMPOTENCY", + D4: "COMPENSATION_OR_IDEMPOTENCY", + }[action_class] + if record.get("rollback") != expected_rollback: + raise SovereignExecutionError(f"POLICY_ROLLBACK_INVALID:{action_class}") + external_idempotency = record.get("external_idempotency") + if not isinstance(external_idempotency, bool): + raise SovereignExecutionError(f"POLICY_EXTERNAL_IDEMPOTENCY_INVALID:{action_class}") + if action_class in (D3, D4) and not external_idempotency: + raise SovereignExecutionError(f"POLICY_EXTERNAL_IDEMPOTENCY_REQUIRED:{action_class}") + validated[action_class] = copy.deepcopy(dict(record)) + return validated + + def canonical_bytes(value: Any) -> bytes: return json.dumps(value, ensure_ascii=False, allow_nan=False, sort_keys=True, separators=(",", ":")).encode("utf-8") @@ -60,6 +117,39 @@ def canonical_hash(domain: str, value: Any) -> str: return sha256_hex(canonical_bytes({"domain": domain, "value": value})) +def _ed25519_sign(*, private_key_hex: str, domain: str, value: Any) -> str: + if not isinstance(private_key_hex, str) or not re.fullmatch(r"[0-9a-f]{64}", private_key_hex): + raise SovereignExecutionError("SIGNING_PRIVATE_KEY_INVALID") + try: + from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + except ImportError as exc: + raise SovereignExecutionError("SIGNATURE_PROVIDER_UNAVAILABLE") from exc + try: + key = Ed25519PrivateKey.from_private_bytes(bytes.fromhex(private_key_hex)) + return key.sign(canonical_bytes({"domain": domain, "value": value})).hex() + except (ValueError, binascii.Error) as exc: + raise SovereignExecutionError("SIGNING_PRIVATE_KEY_INVALID") from exc + + +def _ed25519_verify(*, public_key_hex: str, signature_hex: str, domain: str, value: Any, invalid_code: str) -> None: + if not isinstance(public_key_hex, str) or not re.fullmatch(r"[0-9a-f]{64}", public_key_hex): + raise SovereignExecutionError("SIGNING_PUBLIC_KEY_INVALID") + if not isinstance(signature_hex, str) or not re.fullmatch(r"[0-9a-f]{128}", signature_hex): + raise SovereignExecutionError(invalid_code) + try: + from cryptography.exceptions import InvalidSignature + from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey + except ImportError as exc: + raise SovereignExecutionError("SIGNATURE_PROVIDER_UNAVAILABLE") from exc + try: + key = Ed25519PublicKey.from_public_bytes(bytes.fromhex(public_key_hex)) + key.verify(bytes.fromhex(signature_hex), canonical_bytes({"domain": domain, "value": value})) + except InvalidSignature as exc: + raise SovereignExecutionError(invalid_code) from exc + except (ValueError, binascii.Error) as exc: + raise SovereignExecutionError("SIGNING_PUBLIC_KEY_INVALID") from exc + + def _assert_hash(name: str, value: str) -> None: if not isinstance(value, str) or not SHA256_RE.fullmatch(value): raise SovereignExecutionError(f"{name}:INVALID_SHA256") @@ -362,15 +452,51 @@ class CapabilityEvidence: @dataclass(frozen=True) class ApprovalGrant: + schema_version: str reference: str + issuer_key_id: str + operator_identity: str authority_domain: str action_class: str source_commit: str workspace_binding: str + policy_root: str + registry_root: str + identity_root: str + action_digest: str + target_digest: str + requested_capability: str valid_through_generation: int - signature_root: str + signature: str state: str = "APPROVED" + def signing_body(self) -> dict[str, Any]: + body = asdict(self) + body.pop("signature") + return body + + def validate_shape(self) -> None: + if self.schema_version != SCHEMA_VERSION: + raise SovereignExecutionError("APPROVAL_SCHEMA_UNSUPPORTED") + if self.state != "APPROVED": + raise SovereignExecutionError("APPROVAL_NOT_ACTIVE") + for name in ("reference", "issuer_key_id", "operator_identity", "authority_domain", "requested_capability"): + _assert_authority_string(name, getattr(self, name)) + if self.action_class not in ACTION_CLASSES: + raise SovereignExecutionError("APPROVAL_ACTION_CLASS_INVALID") + _assert_git("source_commit", self.source_commit) + for name in ("workspace_binding", "policy_root", "registry_root", "identity_root", "action_digest", "target_digest"): + _assert_hash(name, getattr(self, name)) + if isinstance(self.valid_through_generation, bool) or not isinstance(self.valid_through_generation, int) or self.valid_through_generation < 0: + raise SovereignExecutionError("APPROVAL_GENERATION_INVALID") + if not isinstance(self.signature, str) or not re.fullmatch(r"[0-9a-f]{128}", self.signature): + raise SovereignExecutionError("APPROVAL_SIGNATURE_INVALID") + + @property + def root(self) -> str: + self.validate_shape() + return canonical_hash("AEGIS_APPROVAL_GRANT_V1", asdict(self)) + @dataclass(frozen=True) class AuthorityRequest: @@ -384,8 +510,12 @@ class AuthorityRequest: source_commit: str registry_root: str policy_root: str + action_digest: str + expected_pre_state: str + workspace_mode: str current_generation: int approval_reference: str = "NONE" + rollback_reference: str = "NONE" idempotency_key: str = "NONE" compensation_reference: str = "NONE" @@ -404,16 +534,55 @@ class PolicyDecision: workspace_binding: str registry_root: str policy_root: str + approval_grant_root: str denial_codes: tuple[str, ...] decision_root: str + def validate(self) -> None: + if self.schema_version != SCHEMA_VERSION: + raise SovereignExecutionError("POLICY_DECISION_SCHEMA_UNSUPPORTED") + if self.outcome not in (ADMITTED, DENIED): + raise SovereignExecutionError("POLICY_DECISION_OUTCOME_INVALID") + if self.action_class not in ACTION_CLASSES: + raise SovereignExecutionError("POLICY_DECISION_ACTION_CLASS_INVALID") + if not re.fullmatch(r"(?:0\.[0-9]{6}|1\.000000)", self.authority_score): + raise SovereignExecutionError("POLICY_DECISION_SCORE_INVALID") + for name in ("target_digest", "identity_root", "workspace_binding", "registry_root", "policy_root", "approval_grant_root", "decision_root"): + _assert_hash(name, getattr(self, name)) + for name in ("authority_domain", "requested_capability", "tool"): + _assert_authority_string(name, getattr(self, name)) + if tuple(sorted(set(self.denial_codes))) != self.denial_codes: + raise SovereignExecutionError("POLICY_DECISION_DENIAL_CODES_NONCANONICAL") + if self.outcome == ADMITTED and self.denial_codes: + raise SovereignExecutionError("ADMITTED_POLICY_DECISION_HAS_DENIAL_CODES") + if self.outcome == DENIED and not self.denial_codes: + raise SovereignExecutionError("DENIED_POLICY_DECISION_REQUIRES_CODE") + if self.outcome == DENIED and self.authority_score != "0.000000": + raise SovereignExecutionError("DENIED_POLICY_DECISION_HAS_AUTHORITY") + body = asdict(self) + root = body.pop("decision_root") + if root != canonical_hash("AEGIS_POLICY_DECISION_V1", body): + raise SovereignExecutionError("POLICY_DECISION_ROOT_MISMATCH") + class AuthorityEvaluator: - def __init__(self, *, policy: Mapping[str, Mapping[str, Any]] | None, registry: Mapping[str, CapabilityEvidence] | None, repository_root: str | Path | None = None): - self.policy = copy.deepcopy(dict(policy)) if policy is not None else None + def __init__( + self, + *, + policy: Mapping[str, Mapping[str, Any]] | None, + registry: Mapping[str, CapabilityEvidence] | None, + repository_root: str | Path | None = None, + trusted_operator_keys: Mapping[str, str] | None = None, + allow_working_tree_evidence_for_tests: bool = False, + ): + self.policy = validate_consequence_policy(policy) if policy is not None else None self.registry = dict(registry) if registry is not None else None self.repository_root = Path(repository_root).resolve() if repository_root is not None else None self.policy_root = canonical_hash("AEGIS_CONSEQUENCE_POLICY_V1", self.policy) if self.policy is not None else ZERO_HASH + self.trusted_operator_keys = dict(trusted_operator_keys or {}) + self.allow_working_tree_evidence_for_tests = allow_working_tree_evidence_for_tests + self._issued_decision_roots: set[str] = set() + self._lock = threading.RLock() def evaluate(self, request: AuthorityRequest, *, approval: ApprovalGrant | None = None) -> PolicyDecision: reasons: list[str] = [] @@ -433,6 +602,11 @@ def evaluate(self, request: AuthorityRequest, *, approval: ApprovalGrant | None _assert_hash(name, getattr(request, name)) except SovereignExecutionError as exc: reasons.append(str(exc)) + for name in ("action_digest", "expected_pre_state"): + try: + _assert_hash(name, getattr(request, name)) + except SovereignExecutionError as exc: + reasons.append(str(exc)) try: _assert_git("source_commit", request.source_commit) except SovereignExecutionError as exc: @@ -442,6 +616,18 @@ def evaluate(self, request: AuthorityRequest, *, approval: ApprovalGrant | None _assert_authority_string(name, getattr(request, name)) except SovereignExecutionError as exc: reasons.append(str(exc)) + if isinstance(request.current_generation, bool) or not isinstance(request.current_generation, int) or request.current_generation < 0: + reasons.append("CURRENT_GENERATION_INVALID") + + if policy: + required_workspace = policy.get("workspace") + if request.workspace_mode != required_workspace: + reasons.append("WORKSPACE_MODE_MISMATCH") + rollback_mode = policy.get("rollback") + if rollback_mode == "REQUIRED" and request.rollback_reference == "NONE": + reasons.append("ROLLBACK_REFERENCE_REQUIRED") + if rollback_mode == "COMPENSATION_OR_IDEMPOTENCY" and request.idempotency_key == "NONE" and request.compensation_reference == "NONE": + reasons.append("COMPENSATION_OR_IDEMPOTENCY_REQUIRED") evidence = self.registry.get(request.requested_capability) if self.registry else None score_micros = 0 @@ -463,15 +649,29 @@ def evaluate(self, request: AuthorityRequest, *, approval: ApprovalGrant | None reasons.append("TOOL_NOT_PERMITTED") if not evidence.evidence_refs: reasons.append("EVIDENCE_MISSING") - elif self.repository_root is not None: + elif self.repository_root is None: + reasons.append("EVIDENCE_RESOLVER_UNAVAILABLE") + else: for ref in evidence.evidence_refs: - candidate = (self.repository_root / ref).resolve(strict=False) try: - candidate.relative_to(self.repository_root) - except ValueError: - reasons.append("EVIDENCE_OUTSIDE_REPOSITORY") + canonical_ref = _repository_blob_path(ref) + except SovereignExecutionError: + if isinstance(ref, str) and (PurePosixPath(ref).is_absolute() or ".." in PurePosixPath(ref).parts): + reasons.append("EVIDENCE_OUTSIDE_REPOSITORY") + else: + reasons.append("EVIDENCE_REFERENCE_INVALID") continue - if not candidate.is_file(): + if self.allow_working_tree_evidence_for_tests: + candidate = (self.repository_root / canonical_ref).resolve(strict=False) + try: + candidate.relative_to(self.repository_root) + except ValueError: + reasons.append("EVIDENCE_OUTSIDE_REPOSITORY") + continue + exists = candidate.is_file() + else: + exists = git_blob_exists(self.repository_root, request.source_commit, canonical_ref) + if not exists: reasons.append("EVIDENCE_UNRESOLVED") if not reasons: score_micros = evidence.confidence_micros * evidence.recency_micros * (1_000_000 - evidence.failure_rate_micros) // 1_000_000 // 1_000_000 @@ -480,20 +680,53 @@ def evaluate(self, request: AuthorityRequest, *, approval: ApprovalGrant | None if approval is None: reasons.append("APPROVAL_MISSING") else: - if approval.state != "APPROVED": reasons.append("APPROVAL_NOT_ACTIVE") - if approval.reference != request.approval_reference: reasons.append("APPROVAL_REFERENCE_MISMATCH") - if approval.authority_domain != request.authority_domain: reasons.append("APPROVAL_DOMAIN_MISMATCH") - if approval.action_class != request.action_class: reasons.append("APPROVAL_ACTION_CLASS_MISMATCH") - if approval.source_commit != request.source_commit: reasons.append("APPROVAL_SOURCE_COMMIT_MISMATCH") - if approval.workspace_binding != request.workspace_binding: reasons.append("APPROVAL_WORKSPACE_MISMATCH") - if approval.valid_through_generation < request.current_generation: reasons.append("APPROVAL_EXPIRED") - try: _assert_hash("approval.signature_root", approval.signature_root) - except SovereignExecutionError: reasons.append("APPROVAL_UNSIGNED") + try: + approval.validate_shape() + except SovereignExecutionError as exc: + reasons.append(str(exc)) + approval_bindings = ( + (approval.reference, request.approval_reference, "APPROVAL_REFERENCE_MISMATCH"), + (approval.authority_domain, request.authority_domain, "APPROVAL_DOMAIN_MISMATCH"), + (approval.action_class, request.action_class, "APPROVAL_ACTION_CLASS_MISMATCH"), + (approval.source_commit, request.source_commit, "APPROVAL_SOURCE_COMMIT_MISMATCH"), + (approval.workspace_binding, request.workspace_binding, "APPROVAL_WORKSPACE_MISMATCH"), + (approval.policy_root, request.policy_root, "APPROVAL_POLICY_MISMATCH"), + (approval.registry_root, request.registry_root, "APPROVAL_REGISTRY_MISMATCH"), + (approval.identity_root, request.identity_root, "APPROVAL_IDENTITY_MISMATCH"), + (approval.action_digest, request.action_digest, "APPROVAL_ACTION_DIGEST_MISMATCH"), + (approval.target_digest, canonical_hash("AEGIS_AUTHORITY_TARGET_V1", request.target), "APPROVAL_TARGET_MISMATCH"), + (approval.requested_capability, request.requested_capability, "APPROVAL_CAPABILITY_MISMATCH"), + ) + for actual, expected, code in approval_bindings: + if actual != expected: + reasons.append(code) + if approval.valid_through_generation < request.current_generation: + reasons.append("APPROVAL_EXPIRED") + public_key_hex = self.trusted_operator_keys.get(approval.issuer_key_id) + if public_key_hex is None: + reasons.append("APPROVAL_ISSUER_UNTRUSTED") + else: + try: + _ed25519_verify( + public_key_hex=public_key_hex, + signature_hex=approval.signature, + domain="AEGIS_APPROVAL_GRANT_V1", + value=approval.signing_body(), + invalid_code="APPROVAL_SIGNATURE_INVALID", + ) + except SovereignExecutionError as exc: + reasons.append(str(exc)) if policy and policy.get("external_idempotency"): if request.idempotency_key == "NONE" and request.compensation_reference == "NONE": reasons.append("EXTERNAL_EFFECT_REQUIRES_IDEMPOTENCY_OR_COMPENSATION") + approval_grant_root = ZERO_HASH + if approval is not None: + try: + approval_grant_root = approval.root + except SovereignExecutionError: + approval_grant_root = ZERO_HASH reasons = sorted(set(reasons)) outcome = ADMITTED if not reasons else DENIED if outcome == DENIED: @@ -502,19 +735,30 @@ def evaluate(self, request: AuthorityRequest, *, approval: ApprovalGrant | None "schema_version": SCHEMA_VERSION, "outcome": outcome, "authority_score": f"{score_micros / 1_000_000:.6f}", - "action_class": request.action_class, - "authority_domain": request.authority_domain, - "requested_capability": request.requested_capability, - "tool": request.tool, + "action_class": request.action_class if request.action_class in ACTION_CLASSES else D0, + "authority_domain": request.authority_domain if isinstance(request.authority_domain, str) and SAFE_ID_RE.fullmatch(request.authority_domain) else "INVALID", + "requested_capability": request.requested_capability if isinstance(request.requested_capability, str) and SAFE_ID_RE.fullmatch(request.requested_capability) else "INVALID", + "tool": request.tool if isinstance(request.tool, str) and SAFE_ID_RE.fullmatch(request.tool) else "INVALID", "target_digest": canonical_hash("AEGIS_AUTHORITY_TARGET_V1", request.target), "identity_root": request.identity_root, "workspace_binding": request.workspace_binding, "registry_root": request.registry_root, "policy_root": request.policy_root, - "denial_codes": reasons, + "approval_grant_root": approval_grant_root, + "denial_codes": tuple(reasons), } root = canonical_hash("AEGIS_POLICY_DECISION_V1", body) - return PolicyDecision(**body, decision_root=root) + decision = PolicyDecision(**body, decision_root=root) + decision.validate() + with self._lock: + self._issued_decision_roots.add(decision.decision_root) + return decision + + def verify_issued_decision(self, decision: PolicyDecision) -> None: + decision.validate() + with self._lock: + if decision.decision_root not in self._issued_decision_roots: + raise SovereignExecutionError("POLICY_DECISION_NOT_ISSUED") @dataclass(frozen=True) @@ -533,17 +777,43 @@ class LeaseReceipt: operation: str outcome: str authority_domain: str + holder_identity_root: str lease_generation: int fencing_token_digest: str + expected_parent_state: str + action_digest: str denial_codes: tuple[str, ...] receipt_root: str + def validate(self) -> None: + if self.operation not in ("ACQUIRE", "AUTHORIZE_WRITE", "ADVANCE", "REVOKE"): + raise SovereignExecutionError("LEASE_RECEIPT_OPERATION_INVALID") + if self.outcome not in (ADMITTED, DENIED): + raise SovereignExecutionError("LEASE_RECEIPT_OUTCOME_INVALID") + _assert_authority_string("authority_domain", self.authority_domain) + for name in ("holder_identity_root", "fencing_token_digest", "expected_parent_state", "action_digest"): + _assert_hash(name, getattr(self, name)) + if isinstance(self.lease_generation, bool) or not isinstance(self.lease_generation, int) or self.lease_generation < 0: + raise SovereignExecutionError("LEASE_RECEIPT_GENERATION_INVALID") + if tuple(sorted(set(self.denial_codes))) != self.denial_codes: + raise SovereignExecutionError("LEASE_RECEIPT_DENIAL_CODES_NONCANONICAL") + if self.outcome == ADMITTED and self.denial_codes: + raise SovereignExecutionError("ADMITTED_LEASE_RECEIPT_HAS_DENIAL_CODES") + if self.outcome == DENIED and not self.denial_codes: + raise SovereignExecutionError("DENIED_LEASE_RECEIPT_REQUIRES_CODE") + body = asdict(self) + root = body.pop("receipt_root") + if root != canonical_hash("AEGIS_LEASE_RECEIPT_V1", body): + raise SovereignExecutionError("LEASE_RECEIPT_ROOT_MISMATCH") + class WriterLeaseManager: def __init__(self) -> None: self._leases: dict[str, WriterLease] = {} self._generation: dict[str, int] = {} - self._used_actions: set[tuple[str, int, str]] = set() + self._authorized_actions: dict[tuple[str, int, str], LeaseReceipt] = {} + self._issued_receipt_roots: set[str] = set() + self._consumed_authorization_receipts: set[str] = set() self._lock = threading.RLock() def acquire(self, *, authority_domain: str, holder_identity_root: str, source_commit: str, expected_parent_state: str) -> tuple[WriterLease | None, LeaseReceipt]: @@ -565,7 +835,7 @@ def acquire(self, *, authority_domain: str, holder_identity_root: str, source_co lease = WriterLease(SCHEMA_VERSION, authority_domain, holder_identity_root, source_commit, generation, token, expected_parent_state) self._leases[authority_domain] = lease self._generation[authority_domain] = generation - receipt = self._lease_receipt("ACQUIRE", authority_domain, generation, token, reasons) + receipt = self._lease_receipt("ACQUIRE", authority_domain, holder_identity_root, generation, token, expected_parent_state, ZERO_HASH, reasons) return lease, receipt def authorize_write(self, *, authority_domain: str, holder_identity_root: str, fencing_token: str, lease_generation: int, expected_parent_state: str, action_digest: str) -> LeaseReceipt: @@ -581,9 +851,13 @@ def authorize_write(self, *, authority_domain: str, holder_identity_root: str, f try: _assert_hash("action_digest", action_digest) except SovereignExecutionError as exc: reasons.append(str(exc)) key = (authority_domain, lease_generation, action_digest) - if key in self._used_actions: reasons.append("REPLAYED_AUTHORITATIVE_ACTION") - if not reasons: self._used_actions.add(key) - return self._lease_receipt("AUTHORIZE_WRITE", authority_domain, lease_generation, fencing_token, reasons) + cached = self._authorized_actions.get(key) + if cached is not None and not reasons: + return cached + receipt = self._lease_receipt("AUTHORIZE_WRITE", authority_domain, holder_identity_root, lease_generation, fencing_token, expected_parent_state, action_digest, reasons) + if receipt.outcome == ADMITTED: + self._authorized_actions[key] = receipt + return receipt def advance(self, *, authority_domain: str, fencing_token: str, new_parent_state: str) -> LeaseReceipt: with self._lock: @@ -596,7 +870,8 @@ def advance(self, *, authority_domain: str, fencing_token: str, new_parent_state generation = lease.lease_generation if lease else self._generation.get(authority_domain, 0) if not reasons and lease: self._leases[authority_domain] = WriterLease(lease.schema_version, lease.authority_domain, lease.holder_identity_root, lease.source_commit, lease.lease_generation, lease.fencing_token, new_parent_state) - return self._lease_receipt("ADVANCE", authority_domain, generation, fencing_token, reasons) + holder = lease.holder_identity_root if lease else ZERO_HASH + return self._lease_receipt("ADVANCE", authority_domain, holder, generation, fencing_token, new_parent_state, ZERO_HASH, reasons) def revoke(self, authority_domain: str, holder_identity_root: str) -> LeaseReceipt: with self._lock: @@ -607,19 +882,63 @@ def revoke(self, authority_domain: str, holder_identity_root: str) -> LeaseRecei generation = lease.lease_generation if lease else self._generation.get(authority_domain, 0) token = lease.fencing_token if lease else ZERO_HASH if not reasons: del self._leases[authority_domain] - return self._lease_receipt("REVOKE", authority_domain, generation, token, reasons) + parent = lease.expected_parent_state if lease else ZERO_HASH + return self._lease_receipt("REVOKE", authority_domain, holder_identity_root, generation, token, parent, ZERO_HASH, reasons) def current(self, authority_domain: str) -> WriterLease | None: with self._lock: return self._leases.get(authority_domain) - @staticmethod - def _lease_receipt(operation: str, domain: str, generation: int, token: str, reasons: Sequence[str]) -> LeaseReceipt: - body = {"operation": operation, "outcome": ADMITTED if not reasons else DENIED, "authority_domain": domain, "lease_generation": generation, "fencing_token_digest": canonical_hash("AEGIS_FENCE_TOKEN_REDACTION_V1", token), "denial_codes": sorted(set(reasons))} - return LeaseReceipt(**body, receipt_root=canonical_hash("AEGIS_LEASE_RECEIPT_V1", body)) - + def verify_issued_receipt(self, receipt: LeaseReceipt) -> None: + receipt.validate() + with self._lock: + if receipt.receipt_root not in self._issued_receipt_roots: + raise SovereignExecutionError("LEASE_RECEIPT_NOT_ISSUED") -DURABLE_STATUSES = ("PLANNED", "ADMITTED", "RUNNING", "WAITING_FOR_APPROVAL", "BLOCKED", "RETRYING", "DENIED", "COMPLETED", "CANCELLED", "ORPHANED") + def consume_authorization(self, receipt: LeaseReceipt) -> None: + with self._lock: + self.verify_issued_receipt(receipt) + if receipt.operation != "AUTHORIZE_WRITE" or receipt.outcome != ADMITTED: + raise SovereignExecutionError("LEASE_AUTHORIZATION_NOT_ADMITTED") + if receipt.receipt_root in self._consumed_authorization_receipts: + raise SovereignExecutionError("LEASE_AUTHORIZATION_ALREADY_CONSUMED") + lease = self._leases.get(receipt.authority_domain) + if lease is None: + raise SovereignExecutionError("LEASE_NO_LONGER_CURRENT") + bindings = ( + (lease.holder_identity_root, receipt.holder_identity_root, "LEASE_CURRENT_HOLDER_MISMATCH"), + (lease.lease_generation, receipt.lease_generation, "LEASE_CURRENT_GENERATION_MISMATCH"), + (lease.expected_parent_state, receipt.expected_parent_state, "LEASE_CURRENT_PARENT_MISMATCH"), + (canonical_hash("AEGIS_FENCE_TOKEN_REDACTION_V1", lease.fencing_token), receipt.fencing_token_digest, "LEASE_CURRENT_FENCE_MISMATCH"), + ) + for actual, expected, code in bindings: + if actual != expected: + raise SovereignExecutionError(code) + self._consumed_authorization_receipts.add(receipt.receipt_root) + + def _lease_receipt(self, operation: str, domain: str, holder_identity_root: str, generation: int, token: str, expected_parent_state: str, action_digest: str, reasons: Sequence[str]) -> LeaseReceipt: + safe_hash = lambda value: value if isinstance(value, str) and re.fullmatch(r"[0-9a-f]{64}", value) else ZERO_HASH + body = {"operation": operation, "outcome": ADMITTED if not reasons else DENIED, "authority_domain": domain, "holder_identity_root": safe_hash(holder_identity_root), "lease_generation": generation, "fencing_token_digest": canonical_hash("AEGIS_FENCE_TOKEN_REDACTION_V1", token), "expected_parent_state": safe_hash(expected_parent_state), "action_digest": safe_hash(action_digest), "denial_codes": tuple(sorted(set(reasons)))} + receipt = LeaseReceipt(**body, receipt_root=canonical_hash("AEGIS_LEASE_RECEIPT_V1", body)) + receipt.validate() + self._issued_receipt_roots.add(receipt.receipt_root) + return receipt + + +DURABLE_STATUSES = ("PLANNED", "ADMITTED", "RUNNING", "WAITING_FOR_APPROVAL", "BLOCKED", "RETRYING", "DENIED", "FAILED", "COMPLETED", "CANCELLED", "ORPHANED") +DURABLE_TERMINAL_STATUSES = frozenset(("DENIED", "FAILED", "COMPLETED", "CANCELLED", "ORPHANED")) +# In-process capability held only by the terminal receipt factory. Public +# registry transitions can never manufacture a terminal commit by supplying a +# boolean escape hatch or an arbitrary receipt hash. +_TERMINAL_COMMIT_CAPABILITY = object() +DURABLE_TRANSITIONS: Mapping[str, frozenset[str]] = { + "PLANNED": frozenset(("ADMITTED", "DENIED", "CANCELLED")), + "ADMITTED": frozenset(("RUNNING", "DENIED", "CANCELLED")), + "RUNNING": frozenset(("WAITING_FOR_APPROVAL", "BLOCKED", "RETRYING", "DENIED", "FAILED", "COMPLETED", "CANCELLED")), + "WAITING_FOR_APPROVAL": frozenset(("RUNNING", "DENIED", "CANCELLED")), + "BLOCKED": frozenset(("RUNNING", "DENIED", "FAILED", "CANCELLED")), + "RETRYING": frozenset(("RUNNING", "FAILED", "COMPLETED", "CANCELLED")), +} @dataclass class DurableExecutionRecord: @@ -643,67 +962,157 @@ class DurableExecutionRecord: used_external_actions: set[str] = field(default_factory=set, repr=False) +def durable_execution_record_root(record: DurableExecutionRecord) -> str: + for name in ("workflow_identity", "owner", "current_phase", "cancellation_state"): + _assert_authority_string(name, getattr(record, name)) + _assert_git("source_commit", record.source_commit) + for name in ("workspace_binding", "lease_holder", "parent_state_root", "current_receipt_root"): + _assert_hash(name, getattr(record, name)) + if record.status not in DURABLE_STATUSES: + raise SovereignExecutionError("DURABLE_STATUS_INVALID") + for name in ("last_completed_transition", "retry_count", "last_heartbeat_generation"): + value = getattr(record, name) + if isinstance(value, bool) or not isinstance(value, int) or value < 0: + raise SovereignExecutionError(f"DURABLE_INTEGER_INVALID:{name}") + if record.next_retry is not None and ( + isinstance(record.next_retry, bool) or not isinstance(record.next_retry, int) or record.next_retry < 0 + ): + raise SovereignExecutionError("DURABLE_INTEGER_INVALID:next_retry") + if tuple(sorted(set(record.current_authority))) != record.current_authority: + raise SovereignExecutionError("DURABLE_AUTHORITY_NONCANONICAL") + for value in (*record.current_authority, *record.used_external_actions): + _assert_authority_string("durable_authority_or_action", value) + for name in ("pending_external_action", "failure_state"): + value = getattr(record, name) + if value: + _assert_authority_string(name, value) + body = asdict(record) + body["used_external_actions"] = sorted(record.used_external_actions) + return canonical_hash("AEGIS_DURABLE_EXECUTION_V1", body) + + class DurableExecutionRegistry: def __init__(self, lease_manager: WriterLeaseManager): self._records: dict[str, DurableExecutionRecord] = {} self._leases = lease_manager + self._lock = threading.RLock() def register(self, execution_id: str, record: DurableExecutionRecord) -> str: - if execution_id in self._records: raise SovereignExecutionError("DURABLE_EXECUTION_ALREADY_REGISTERED") - if record.status not in DURABLE_STATUSES: raise SovereignExecutionError("DURABLE_STATUS_INVALID") - if record.status != "PLANNED": raise SovereignExecutionError("DURABLE_MUST_REGISTER_AS_PLANNED") - self._records[execution_id] = copy.deepcopy(record) - return self.root(execution_id) + with self._lock: + if execution_id in self._records: raise SovereignExecutionError("DURABLE_EXECUTION_ALREADY_REGISTERED") + durable_execution_record_root(record) + if record.status != "PLANNED": raise SovereignExecutionError("DURABLE_MUST_REGISTER_AS_PLANNED") + self._records[execution_id] = copy.deepcopy(record) + return self.root(execution_id) def transition(self, execution_id: str, *, status: str, phase: str, transition_sequence: int, receipt_root: str) -> str: - record = self._require(execution_id) - if record.status in ("CANCELLED", "COMPLETED", "ORPHANED"): raise SovereignExecutionError("DURABLE_TERMINAL_STATE") - if status not in DURABLE_STATUSES: raise SovereignExecutionError("DURABLE_STATUS_INVALID") - if transition_sequence != record.last_completed_transition + 1: raise SovereignExecutionError("DURABLE_SEQUENCE_INVALID") - _assert_hash("receipt_root", receipt_root) - record.status, record.current_phase = status, phase - record.last_completed_transition = transition_sequence - record.current_receipt_root = receipt_root - return self.root(execution_id) + with self._lock: + record = self._require(execution_id) + if record.status in DURABLE_TERMINAL_STATUSES: raise SovereignExecutionError("DURABLE_TERMINAL_STATE") + if status not in DURABLE_STATUSES: raise SovereignExecutionError("DURABLE_STATUS_INVALID") + if status in DURABLE_TERMINAL_STATUSES: raise SovereignExecutionError("DURABLE_TERMINAL_COMMIT_REQUIRED") + if status not in DURABLE_TRANSITIONS.get(record.status, frozenset()): raise SovereignExecutionError("DURABLE_TRANSITION_INVALID") + if transition_sequence != record.last_completed_transition + 1: raise SovereignExecutionError("DURABLE_SEQUENCE_INVALID") + _assert_hash("receipt_root", receipt_root) + _assert_authority_string("phase", phase) + record.status, record.current_phase = status, phase + record.last_completed_transition = transition_sequence + record.current_receipt_root = receipt_root + return self.root(execution_id) def heartbeat(self, execution_id: str, generation: int) -> str: - record = self._require(execution_id) - if generation <= record.last_heartbeat_generation: raise SovereignExecutionError("HEARTBEAT_NOT_MONOTONE") - record.last_heartbeat_generation = generation - return self.root(execution_id) + with self._lock: + record = self._require(execution_id) + if generation <= record.last_heartbeat_generation: raise SovereignExecutionError("HEARTBEAT_NOT_MONOTONE") + record.last_heartbeat_generation = generation + return self.root(execution_id) + + def _commit_terminal_transition( + self, + execution_id: str, + *, + status: str, + phase: str, + transition_sequence: int, + receipt: Any, + commit_capability: object, + ) -> str: + if commit_capability is not _TERMINAL_COMMIT_CAPABILITY: + raise SovereignExecutionError("DURABLE_TERMINAL_COMMIT_CAPABILITY_INVALID") + # MutationReceipt is defined later in this module. The lookup occurs + # when the method executes, after module initialization is complete. + if not isinstance(receipt, MutationReceipt): + raise SovereignExecutionError("DURABLE_TERMINAL_RECEIPT_INVALID") + if status not in DURABLE_TERMINAL_STATUSES: + raise SovereignExecutionError("DURABLE_TERMINAL_STATUS_REQUIRED") + with self._lock: + record = self._require(execution_id) + if record.status in DURABLE_TERMINAL_STATUSES: + raise SovereignExecutionError("DURABLE_TERMINAL_STATE") + if status not in DURABLE_TRANSITIONS.get(record.status, frozenset()): + raise SovereignExecutionError("DURABLE_TRANSITION_INVALID") + if transition_sequence != record.last_completed_transition + 1: + raise SovereignExecutionError("DURABLE_SEQUENCE_INVALID") + _assert_authority_string("phase", phase) + receipt.validate() + record.status = status + record.current_phase = phase + record.last_completed_transition = transition_sequence + record.current_receipt_root = receipt.root + record.current_authority = () + return self.root(execution_id) def mark_orphaned(self, execution_id: str, current_generation: int, maximum_gap: int) -> str: - record = self._require(execution_id) - if current_generation - record.last_heartbeat_generation <= maximum_gap: raise SovereignExecutionError("ORPHAN_THRESHOLD_NOT_REACHED") - held = record.current_authority - record.status = "ORPHANED"; record.current_authority = () - if record.lease_holder: - for domain in held: self._leases.revoke(domain, record.lease_holder) - return self.root(execution_id) + with self._lock, self._leases._lock: + record = self._require(execution_id) + if record.status in DURABLE_TERMINAL_STATUSES: raise SovereignExecutionError("DURABLE_TERMINAL_STATE") + if current_generation - record.last_heartbeat_generation <= maximum_gap: raise SovereignExecutionError("ORPHAN_THRESHOLD_NOT_REACHED") + held = record.current_authority + if record.lease_holder: + for domain in held: + lease = self._leases.current(domain) + if lease is None or lease.holder_identity_root != record.lease_holder: + raise SovereignExecutionError("DURABLE_LEASE_STATE_DIVERGED") + for domain in held: + receipt = self._leases.revoke(domain, record.lease_holder) + if receipt.outcome != ADMITTED: + raise SovereignExecutionError("DURABLE_LEASE_REVOCATION_FAILED") + record.status = "ORPHANED"; record.current_authority = () + return self.root(execution_id) def cancel(self, execution_id: str) -> str: - record = self._require(execution_id) - record.status = "CANCELLED"; record.cancellation_state = "REVOKED" - held = record.current_authority; record.current_authority = () - for domain in held: self._leases.revoke(domain, record.lease_holder) - return self.root(execution_id) + with self._lock, self._leases._lock: + record = self._require(execution_id) + if record.status in DURABLE_TERMINAL_STATUSES: raise SovereignExecutionError("DURABLE_TERMINAL_STATE") + held = record.current_authority + for domain in held: + lease = self._leases.current(domain) + if lease is None or lease.holder_identity_root != record.lease_holder: + raise SovereignExecutionError("DURABLE_LEASE_STATE_DIVERGED") + for domain in held: + receipt = self._leases.revoke(domain, record.lease_holder) + if receipt.outcome != ADMITTED: + raise SovereignExecutionError("DURABLE_LEASE_REVOCATION_FAILED") + record.status = "CANCELLED"; record.cancellation_state = "REVOKED"; record.current_authority = () + return self.root(execution_id) def claim_external_action(self, execution_id: str, idempotency_key: str) -> str: - record = self._require(execution_id) - if record.status not in ("RUNNING", "RETRYING"): raise SovereignExecutionError("DURABLE_NOT_RUNNING") - if idempotency_key in record.used_external_actions: raise SovereignExecutionError("DUPLICATE_EXTERNAL_ACTION") - _assert_authority_string("idempotency_key", idempotency_key) - record.used_external_actions.add(idempotency_key) - record.pending_external_action = idempotency_key - return self.root(execution_id) + with self._lock: + record = self._require(execution_id) + if record.status not in ("RUNNING", "RETRYING"): raise SovereignExecutionError("DURABLE_NOT_RUNNING") + if idempotency_key in record.used_external_actions: raise SovereignExecutionError("DUPLICATE_EXTERNAL_ACTION") + _assert_authority_string("idempotency_key", idempotency_key) + record.used_external_actions.add(idempotency_key) + record.pending_external_action = idempotency_key + return self.root(execution_id) def get(self, execution_id: str) -> DurableExecutionRecord: - return copy.deepcopy(self._require(execution_id)) + with self._lock: + return copy.deepcopy(self._require(execution_id)) def root(self, execution_id: str) -> str: - record = self._require(execution_id) - body = asdict(record); body["used_external_actions"] = sorted(record.used_external_actions) - return canonical_hash("AEGIS_DURABLE_EXECUTION_V1", body) + with self._lock: + return durable_execution_record_root(self._require(execution_id)) def _require(self, execution_id: str) -> DurableExecutionRecord: if execution_id not in self._records: raise SovereignExecutionError("DURABLE_EXECUTION_UNKNOWN") @@ -748,12 +1157,83 @@ def root(self) -> str: return canonical_hash("AEGIS_EVENT_ENVELOPE_V1", {**asdict(self), "payload": deterministic_redaction(self.payload)}) +@dataclass(frozen=True) +class AuthorityDecisionReceipt: + receipt_version: str + issuer_key_id: str + execution_identity_root: str + source_commit: str + workspace_binding: str + expected_pre_state: str + skills_root: str + policy_decision_root: str + policy_root: str + registry_root: str + approval_grant_root: str + authority_score: str + authority_domain: str + action_class: str + requested_capability: str + tool: str + target: str + requested_action_digest: str + outcome: str + denial_codes: tuple[str, ...] + signature: str + + def signing_body(self) -> dict[str, Any]: + body = asdict(self) + body.pop("signature") + return body + + def validate(self) -> None: + if self.receipt_version != SCHEMA_VERSION: raise SovereignExecutionError("AUTHORITY_RECEIPT_SCHEMA_UNSUPPORTED") + _assert_git("source_commit", self.source_commit) + for name in ("execution_identity_root", "workspace_binding", "expected_pre_state", "skills_root", "policy_decision_root", "policy_root", "registry_root", "approval_grant_root", "target", "requested_action_digest"): + _assert_hash(name, getattr(self, name)) + if self.outcome not in (ADMITTED, DENIED): raise SovereignExecutionError("AUTHORITY_RECEIPT_OUTCOME_INVALID") + if self.action_class not in ACTION_CLASSES: raise SovereignExecutionError("AUTHORITY_RECEIPT_ACTION_CLASS_INVALID") + for name in ("issuer_key_id", "authority_domain", "requested_capability", "tool"): + _assert_authority_string(name, getattr(self, name)) + if not re.fullmatch(r"[0-9a-f]{128}", self.signature): + raise SovereignExecutionError("AUTHORITY_RECEIPT_SIGNATURE_INVALID") + if not re.fullmatch(r"(?:0\.[0-9]{6}|1\.000000)", self.authority_score): + raise SovereignExecutionError("AUTHORITY_RECEIPT_SCORE_INVALID") + if tuple(sorted(set(self.denial_codes))) != self.denial_codes: + raise SovereignExecutionError("AUTHORITY_RECEIPT_DENIAL_CODES_NONCANONICAL") + if self.outcome == ADMITTED and self.denial_codes: + raise SovereignExecutionError("ADMITTED_AUTHORITY_RECEIPT_HAS_DENIAL_CODES") + if self.outcome == DENIED and not self.denial_codes: + raise SovereignExecutionError("DENIED_AUTHORITY_RECEIPT_REQUIRES_CODE") + + @property + def root(self) -> str: + self.validate() + return canonical_hash("AEGIS_AUTHORITY_DECISION_RECEIPT_V1", asdict(self)) + + def verify_signature(self, trusted_authority_keys: Mapping[str, str]) -> None: + self.validate() + public_key = trusted_authority_keys.get(self.issuer_key_id) + if public_key is None: + raise SovereignExecutionError("AUTHORITY_RECEIPT_ISSUER_UNTRUSTED") + _ed25519_verify( + public_key_hex=public_key, + signature_hex=self.signature, + domain="AEGIS_AUTHORITY_DECISION_RECEIPT_V1", + value=self.signing_body(), + invalid_code="AUTHORITY_RECEIPT_SIGNATURE_INVALID", + ) + + @dataclass(frozen=True) class MutationReceipt: receipt_version: str execution_identity_root: str workspace_binding: str policy_decision_root: str + authority_receipt_root: str + lease_authorization_receipt_root: str + durable_execution_root: str authority_score: str authority_domain: str action_class: str @@ -770,11 +1250,21 @@ class MutationReceipt: def validate(self) -> None: if self.receipt_version != SCHEMA_VERSION: raise SovereignExecutionError("RECEIPT_SCHEMA_UNSUPPORTED") - for name in ("execution_identity_root", "workspace_binding", "policy_decision_root", "pre_state_digest", "requested_action_digest", "result_digest", "post_state_digest", "parent_receipt"): + for name in ("execution_identity_root", "workspace_binding", "policy_decision_root", "authority_receipt_root", "lease_authorization_receipt_root", "durable_execution_root", "pre_state_digest", "requested_action_digest", "result_digest", "post_state_digest", "parent_receipt"): _assert_hash(name, getattr(self, name)) - if self.sequence < 0: raise SovereignExecutionError("RECEIPT_SEQUENCE_INVALID") + if self.action_class not in ACTION_CLASSES: raise SovereignExecutionError("RECEIPT_ACTION_CLASS_INVALID") + for name in ("authority_domain", "tool"): + _assert_authority_string(name, getattr(self, name)) + if not re.fullmatch(r"(?:0\.[0-9]{6}|1\.000000)", self.authority_score): + raise SovereignExecutionError("RECEIPT_AUTHORITY_SCORE_INVALID") + if isinstance(self.sequence, bool) or not isinstance(self.sequence, int) or self.sequence < 0: + raise SovereignExecutionError("RECEIPT_SEQUENCE_INVALID") if self.outcome not in ("SUCCEEDED", "DENIED", "FAILED", "ROLLED_BACK"): raise SovereignExecutionError("RECEIPT_OUTCOME_INVALID") - if self.outcome == "DENIED" and self.denial_code in ("", "NONE"): raise SovereignExecutionError("DENIAL_CODE_REQUIRED") + _assert_authority_string("denial_code", self.denial_code) + if self.outcome in ("DENIED", "FAILED", "ROLLED_BACK") and self.denial_code == "NONE": + raise SovereignExecutionError("TERMINAL_OUTCOME_CODE_REQUIRED") + if self.outcome == "SUCCEEDED" and self.denial_code != "NONE": + raise SovereignExecutionError("SUCCESS_RECEIPT_HAS_OUTCOME_CODE") @property def root(self) -> str: @@ -785,34 +1275,105 @@ def root(self) -> str: class ReceiptChain: def __init__(self) -> None: self._receipts: list[MutationReceipt] = [] + self._lock = threading.RLock() + + def next_link(self) -> tuple[str, int]: + with self._lock: + return (self._receipts[-1].root if self._receipts else ZERO_HASH, len(self._receipts)) def append(self, receipt: MutationReceipt) -> str: - receipt.validate() - expected_sequence = len(self._receipts) - expected_parent = self._receipts[-1].root if self._receipts else ZERO_HASH - if receipt.sequence != expected_sequence: raise SovereignExecutionError("RECEIPT_CHAIN_SEQUENCE_BREAK") - if receipt.parent_receipt != expected_parent: raise SovereignExecutionError("RECEIPT_CHAIN_PARENT_BREAK") - self._receipts.append(receipt) - return receipt.root + with self._lock: + receipt.validate() + expected_sequence = len(self._receipts) + expected_parent = self._receipts[-1].root if self._receipts else ZERO_HASH + if receipt.sequence != expected_sequence: raise SovereignExecutionError("RECEIPT_CHAIN_SEQUENCE_BREAK") + if receipt.parent_receipt != expected_parent: raise SovereignExecutionError("RECEIPT_CHAIN_PARENT_BREAK") + self._receipts.append(receipt) + return receipt.root def verify(self) -> str: - previous = ZERO_HASH - for index, receipt in enumerate(self._receipts): - if receipt.sequence != index or receipt.parent_receipt != previous: raise SovereignExecutionError("RECEIPT_CHAIN_BROKEN") - previous = receipt.root - return previous + with self._lock: + previous = ZERO_HASH + for index, receipt in enumerate(self._receipts): + if receipt.sequence != index or receipt.parent_receipt != previous: raise SovereignExecutionError("RECEIPT_CHAIN_BROKEN") + previous = receipt.root + return previous -def load_policy(path: str | Path) -> tuple[dict[str, Any], str]: - raw = json.loads(Path(path).read_text(encoding="utf-8")) +def _parse_policy(raw: Mapping[str, Any]) -> tuple[dict[str, Any], str]: if raw.get("schema_version") != SCHEMA_VERSION or raw.get("classes") is None: raise SovereignExecutionError("POLICY_INVALID") - policy = raw["classes"] - for action_class in ACTION_CLASSES: - if action_class not in policy: raise SovereignExecutionError(f"POLICY_CLASS_MISSING:{action_class}") + if set(raw) != {"schema_version", "classes"}: + raise SovereignExecutionError("POLICY_SCHEMA_DRIFT") + policy = validate_consequence_policy(raw["classes"]) return policy, canonical_hash("AEGIS_CONSEQUENCE_POLICY_V1", policy) +def load_policy(path: str | Path) -> tuple[dict[str, Any], str]: + raw = json.loads(Path(path).read_text(encoding="utf-8")) + if not isinstance(raw, dict): + raise SovereignExecutionError("POLICY_INVALID") + return _parse_policy(raw) + + +def _repository_blob_path(path: str) -> str: + if not isinstance(path, str) or not path or "\\" in path or ":" in path or "\x00" in path: + raise SovereignExecutionError("REPOSITORY_BLOB_PATH_INVALID") + try: + _assert_authority_string("repository_blob_path", path) + except SovereignExecutionError as exc: + raise SovereignExecutionError("REPOSITORY_BLOB_PATH_INVALID") from exc + parsed = PurePosixPath(path) + if parsed.is_absolute() or any(part in ("", ".", "..") for part in parsed.parts): + raise SovereignExecutionError("REPOSITORY_BLOB_PATH_INVALID") + rendered = parsed.as_posix() + if rendered != path: + raise SovereignExecutionError("REPOSITORY_BLOB_PATH_NONCANONICAL") + return rendered + + +def git_show_json(repository_root: str | Path, source_commit: str, repository_path: str) -> dict[str, Any]: + root = Path(repository_root).resolve(strict=True) + _assert_git("source_commit", source_commit) + path = _repository_blob_path(repository_path) + try: + result = subprocess.run( + ["git", "-C", str(root), "show", f"{source_commit}:{path}"], + check=True, + capture_output=True, + timeout=15, + ) + except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired) as exc: + raise SovereignExecutionError(f"COMMIT_BOUND_BLOB_UNAVAILABLE:{path}") from exc + try: + raw = json.loads(result.stdout.decode("utf-8", errors="strict")) + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise SovereignExecutionError(f"COMMIT_BOUND_JSON_INVALID:{path}") from exc + if not isinstance(raw, dict): + raise SovereignExecutionError(f"COMMIT_BOUND_JSON_INVALID:{path}") + return raw + + +def git_blob_exists(repository_root: str | Path, source_commit: str, repository_path: str) -> bool: + root = Path(repository_root).resolve(strict=True) + _assert_git("source_commit", source_commit) + path = _repository_blob_path(repository_path) + try: + result = subprocess.run( + ["git", "-C", str(root), "cat-file", "-e", f"{source_commit}:{path}"], + check=False, + capture_output=True, + timeout=15, + ) + except (OSError, subprocess.TimeoutExpired): + return False + return result.returncode == 0 + + +def load_policy_from_commit(*, repository_root: str | Path, source_commit: str, policy_path: str) -> tuple[dict[str, Any], str]: + return _parse_policy(git_show_json(repository_root, source_commit, policy_path)) + + def git_remote(root: str | Path) -> str: try: result = subprocess.run(["git", "-C", str(root), "config", "--get", "remote.origin.url"], check=True, text=True, capture_output=True) @@ -821,6 +1382,16 @@ def git_remote(root: str | Path) -> str: return canonical_remote(result.stdout.strip()) +def git_head(root: str | Path) -> str: + try: + result = subprocess.run(["git", "-C", str(root), "rev-parse", "HEAD"], check=True, text=True, capture_output=True) + except (OSError, subprocess.CalledProcessError) as exc: + raise SovereignExecutionError("SOURCE_COMMIT_UNAVAILABLE") from exc + head = result.stdout.strip() + _assert_git("source_commit", head) + return head + + def decision_dict(decision: PolicyDecision) -> dict[str, Any]: return asdict(decision) @@ -832,31 +1403,61 @@ def compute_skill_registry_root(tree: Mapping[str, Any]) -> str: return sha256_hex(canonical_bytes({"domain": "AEGIS_SKILL_REGISTRY_V2", "registry": payload})) -def load_capability_registry(*, repository_root: str | Path, skill_tree_path: str | Path, capability_map_path: str | Path) -> tuple[dict[str, CapabilityEvidence], str]: +def compute_capability_registry_root(*, skills_root: str, capability_map: Mapping[str, Any]) -> str: + _assert_hash("skills_root", skills_root) + if not isinstance(capability_map, Mapping): + raise SovereignExecutionError("CAPABILITY_MAP_INVALID") + return canonical_hash( + "AEGIS_CAPABILITY_REGISTRY_V1", + { + "skills_root": skills_root, + "capability_map": copy.deepcopy(dict(capability_map)), + }, + ) + + +def _build_capability_registry(*, repository_root: str | Path, tree: Mapping[str, Any], mapping: Mapping[str, Any]) -> tuple[dict[str, CapabilityEvidence], str, str]: root = Path(repository_root).resolve(strict=True) - skill_path = Path(skill_tree_path).resolve(strict=True) - map_path = Path(capability_map_path).resolve(strict=True) - for candidate in (skill_path, map_path): - try: - candidate.relative_to(root) - except ValueError as exc: - raise SovereignExecutionError("REGISTRY_PATH_OUTSIDE_REPOSITORY") from exc - tree = json.loads(skill_path.read_text(encoding="utf-8")) - mapping = json.loads(map_path.read_text(encoding="utf-8")) - expected = compute_skill_registry_root(tree) - if tree.get("registry_root") != expected or tree.get("genesis_seal") != expected: - raise SovereignExecutionError("REGISTRY_ROOT_MISMATCH") + if not isinstance(tree, Mapping) or not isinstance(mapping, Mapping): + raise SovereignExecutionError("CAPABILITY_REGISTRY_INVALID") + skills_root = compute_skill_registry_root(tree) + if tree.get("registry_root") != skills_root or tree.get("genesis_seal") != skills_root: + raise SovereignExecutionError("SKILL_REGISTRY_ROOT_MISMATCH") if mapping.get("schema_version") != SCHEMA_VERSION or not isinstance(mapping.get("capabilities"), dict): raise SovereignExecutionError("CAPABILITY_MAP_INVALID") skills = {item.get("skill_id"): item for item in tree.get("skills", []) if isinstance(item, dict) and isinstance(item.get("skill_id"), str)} registry: dict[str, CapabilityEvidence] = {} for capability, config in mapping["capabilities"].items(): - if not isinstance(config, dict): + try: + _assert_authority_string("capability", capability) + except SovereignExecutionError as exc: + raise SovereignExecutionError("CAPABILITY_MAP_CAPABILITY_INVALID") from exc + if not isinstance(config, dict) or set(config) != {"skill_id", "allowed_action_classes", "allowed_tools"}: raise SovereignExecutionError("CAPABILITY_MAP_RECORD_INVALID") skill_id = config.get("skill_id") + try: + _assert_authority_string("skill_id", skill_id) + except SovereignExecutionError as exc: + raise SovereignExecutionError("CAPABILITY_MAP_SKILL_ID_INVALID") from exc skill = skills.get(skill_id) if skill is None: - continue + raise SovereignExecutionError("CAPABILITY_MAP_SKILL_UNRESOLVED") + allowed_action_classes = config.get("allowed_action_classes") + allowed_tools = config.get("allowed_tools") + if ( + not isinstance(allowed_action_classes, list) + or not allowed_action_classes + or any(item not in ACTION_CLASSES for item in allowed_action_classes) + or len(set(allowed_action_classes)) != len(allowed_action_classes) + ): + raise SovereignExecutionError("CAPABILITY_MAP_ACTION_CLASSES_INVALID") + if not isinstance(allowed_tools, list) or not allowed_tools or len(set(allowed_tools)) != len(allowed_tools): + raise SovereignExecutionError("CAPABILITY_MAP_TOOLS_INVALID") + try: + for tool in allowed_tools: + _assert_authority_string("allowed_tool", tool) + except SovereignExecutionError as exc: + raise SovereignExecutionError("CAPABILITY_MAP_TOOLS_INVALID") from exc refs = skill.get("evidence_refs", []) if not isinstance(refs, list): refs = [] def micros(field: str) -> int: @@ -873,31 +1474,265 @@ def micros(field: str) -> int: recency_micros=micros("recency_score"), failure_rate_micros=micros("failure_rate"), evidence_refs=tuple(sorted(str(ref) for ref in refs if isinstance(ref, str) and ref)), - allowed_action_classes=tuple(config.get("allowed_action_classes", [])), - allowed_tools=tuple(config.get("allowed_tools", [])), + allowed_action_classes=tuple(allowed_action_classes), + allowed_tools=tuple(allowed_tools), ) - return registry, expected - - -def make_mutation_receipt(*, identity_root: str, workspace_binding: str, decision: PolicyDecision, pre_state_digest: str, action_digest: str, result: Any, post_state_digest: str, parent_receipt: str, sequence: int) -> MutationReceipt: - outcome = "SUCCEEDED" if decision.outcome == ADMITTED else "DENIED" - denial = "NONE" if decision.outcome == ADMITTED else (decision.denial_codes[0] if decision.denial_codes else "UNSPECIFIED_DENIAL") - return MutationReceipt( - receipt_version=SCHEMA_VERSION, - execution_identity_root=identity_root, - workspace_binding=workspace_binding, - policy_decision_root=decision.decision_root, - authority_score=decision.authority_score, - authority_domain=decision.authority_domain, - action_class=decision.action_class, - tool=decision.tool, - target=decision.target_digest, - pre_state_digest=pre_state_digest, - requested_action_digest=action_digest, - result_digest=canonical_hash("AEGIS_ACTION_RESULT_V1", deterministic_redaction(result)), - post_state_digest=post_state_digest, - parent_receipt=parent_receipt, - sequence=sequence, - outcome=outcome, - denial_code=denial, + registry_root = compute_capability_registry_root(skills_root=skills_root, capability_map=mapping) + return registry, skills_root, registry_root + + +def load_capability_registry(*, repository_root: str | Path, skill_tree_path: str | Path, capability_map_path: str | Path) -> tuple[dict[str, CapabilityEvidence], str, str]: + root = Path(repository_root).resolve(strict=True) + skill_path = Path(skill_tree_path).resolve(strict=True) + map_path = Path(capability_map_path).resolve(strict=True) + for candidate in (skill_path, map_path): + try: + candidate.relative_to(root) + except ValueError as exc: + raise SovereignExecutionError("REGISTRY_PATH_OUTSIDE_REPOSITORY") from exc + tree = json.loads(skill_path.read_text(encoding="utf-8")) + mapping = json.loads(map_path.read_text(encoding="utf-8")) + return _build_capability_registry(repository_root=root, tree=tree, mapping=mapping) + + +def load_capability_registry_from_commit( + *, + repository_root: str | Path, + source_commit: str, + skill_tree_path: str, + capability_map_path: str, +) -> tuple[dict[str, CapabilityEvidence], str, str]: + tree = git_show_json(repository_root, source_commit, skill_tree_path) + mapping = git_show_json(repository_root, source_commit, capability_map_path) + return _build_capability_registry(repository_root=repository_root, tree=tree, mapping=mapping) + + +def verify_live_authority_roots( + identity: ExecutionIdentityEnvelope, + *, + skills_root: str, + registry_root: str, + policy_root: str, +) -> None: + identity.validate() + for name, value in (("skills_root", skills_root), ("registry_root", registry_root), ("policy_root", policy_root)): + _assert_hash(name, value) + if identity.skills_root != skills_root: + raise SovereignExecutionError("SKILLS_ROOT_MISMATCH") + if identity.registry_root != registry_root: + raise SovereignExecutionError("CAPABILITY_REGISTRY_ROOT_MISMATCH") + if identity.policy_root != policy_root: + raise SovereignExecutionError("POLICY_ROOT_MISMATCH") + + +def make_authority_decision_receipt(*, identity: ExecutionIdentityEnvelope, request: AuthorityRequest, decision: PolicyDecision, evaluator: AuthorityEvaluator, issuer_key_id: str, issuer_private_key_hex: str) -> AuthorityDecisionReceipt: + evaluator.verify_issued_decision(decision) + identity_root = identity.root + expected_target = canonical_hash("AEGIS_AUTHORITY_TARGET_V1", request.target) + bindings = ( + (request.identity_root, identity_root, "AUTHORITY_REQUEST_IDENTITY_MISMATCH"), + (request.workspace_binding, identity.workspace_binding, "AUTHORITY_REQUEST_WORKSPACE_MISMATCH"), + (request.source_commit, identity.source_commit, "AUTHORITY_REQUEST_SOURCE_COMMIT_MISMATCH"), + (request.registry_root, identity.registry_root, "AUTHORITY_REQUEST_REGISTRY_MISMATCH"), + (request.policy_root, identity.policy_root, "AUTHORITY_REQUEST_POLICY_MISMATCH"), + (request.action_digest, identity.action_digest, "AUTHORITY_REQUEST_ACTION_MISMATCH"), + (request.expected_pre_state, identity.expected_pre_state, "AUTHORITY_REQUEST_PRE_STATE_MISMATCH"), + (request.authority_domain, identity.authority_domain, "AUTHORITY_REQUEST_DOMAIN_MISMATCH"), + (request.requested_capability, identity.requested_capability, "AUTHORITY_REQUEST_CAPABILITY_MISMATCH"), + (request.tool, identity.tool_identity, "AUTHORITY_REQUEST_TOOL_MISMATCH"), + (decision.identity_root, identity_root, "AUTHORITY_DECISION_IDENTITY_MISMATCH"), + (decision.workspace_binding, identity.workspace_binding, "AUTHORITY_DECISION_WORKSPACE_MISMATCH"), + (decision.registry_root, identity.registry_root, "AUTHORITY_DECISION_REGISTRY_MISMATCH"), + (decision.policy_root, identity.policy_root, "AUTHORITY_DECISION_POLICY_MISMATCH"), + (decision.action_class, request.action_class, "AUTHORITY_DECISION_ACTION_CLASS_MISMATCH"), + (decision.authority_domain, request.authority_domain, "AUTHORITY_DECISION_DOMAIN_MISMATCH"), + (decision.requested_capability, request.requested_capability, "AUTHORITY_DECISION_CAPABILITY_MISMATCH"), + (decision.tool, request.tool, "AUTHORITY_DECISION_TOOL_MISMATCH"), + (decision.target_digest, expected_target, "AUTHORITY_DECISION_TARGET_MISMATCH"), + ) + for actual, expected, code in bindings: + if actual != expected: + raise SovereignExecutionError(code) + unsigned = { + "receipt_version": SCHEMA_VERSION, + "issuer_key_id": issuer_key_id, + "execution_identity_root": identity_root, + "source_commit": identity.source_commit, + "workspace_binding": identity.workspace_binding, + "expected_pre_state": identity.expected_pre_state, + "skills_root": identity.skills_root, + "policy_decision_root": decision.decision_root, + "policy_root": decision.policy_root, + "registry_root": decision.registry_root, + "approval_grant_root": decision.approval_grant_root, + "authority_score": decision.authority_score, + "authority_domain": decision.authority_domain, + "action_class": decision.action_class, + "requested_capability": decision.requested_capability, + "tool": decision.tool, + "target": decision.target_digest, + "requested_action_digest": identity.action_digest, + "outcome": decision.outcome, + "denial_codes": tuple(decision.denial_codes), + } + signature = _ed25519_sign( + private_key_hex=issuer_private_key_hex, + domain="AEGIS_AUTHORITY_DECISION_RECEIPT_V1", + value=unsigned, + ) + receipt = AuthorityDecisionReceipt(**unsigned, signature=signature) + receipt.validate() + return receipt + + +def make_terminal_mutation_receipt( + *, + identity: ExecutionIdentityEnvelope, + request: AuthorityRequest, + decision: PolicyDecision, + evaluator: AuthorityEvaluator, + authority_receipt: AuthorityDecisionReceipt, + trusted_authority_keys: Mapping[str, str], + lease_manager: WriterLeaseManager, + lease_authorization_receipt: LeaseReceipt, + durable_registry: DurableExecutionRegistry, + execution_id: str, + receipt_chain: ReceiptChain, + result: Any, + post_state_digest: str, + terminal_outcome: str, + denial_code: str = "NONE", +) -> MutationReceipt: + if decision.outcome != ADMITTED: + raise SovereignExecutionError("TERMINAL_RECEIPT_REQUIRES_ADMITTED_AUTHORITY") + if terminal_outcome not in ("SUCCEEDED", "DENIED", "FAILED", "ROLLED_BACK"): + raise SovereignExecutionError("TERMINAL_RECEIPT_OUTCOME_INVALID") + evaluator.verify_issued_decision(decision) + identity_root = identity.root + request_bindings = ( + (request.identity_root, identity_root, "TERMINAL_REQUEST_IDENTITY_MISMATCH"), + (request.workspace_binding, identity.workspace_binding, "TERMINAL_REQUEST_WORKSPACE_MISMATCH"), + (request.source_commit, identity.source_commit, "TERMINAL_REQUEST_SOURCE_COMMIT_MISMATCH"), + (request.registry_root, identity.registry_root, "TERMINAL_REQUEST_REGISTRY_MISMATCH"), + (request.policy_root, identity.policy_root, "TERMINAL_REQUEST_POLICY_MISMATCH"), + (request.action_digest, identity.action_digest, "TERMINAL_REQUEST_ACTION_MISMATCH"), + (request.expected_pre_state, identity.expected_pre_state, "TERMINAL_REQUEST_PRE_STATE_MISMATCH"), + (request.authority_domain, identity.authority_domain, "TERMINAL_REQUEST_DOMAIN_MISMATCH"), + (request.requested_capability, identity.requested_capability, "TERMINAL_REQUEST_CAPABILITY_MISMATCH"), + (request.tool, identity.tool_identity, "TERMINAL_REQUEST_TOOL_MISMATCH"), + ) + for actual, expected, code in request_bindings: + if actual != expected: + raise SovereignExecutionError(code) + authority_receipt.verify_signature(trusted_authority_keys) + lease_manager.verify_issued_receipt(lease_authorization_receipt) + if authority_receipt.outcome != ADMITTED: + raise SovereignExecutionError("TERMINAL_RECEIPT_REQUIRES_ADMITTED_AUTHORITY_RECEIPT") + authority_bindings = ( + (authority_receipt.policy_decision_root, decision.decision_root, "TERMINAL_AUTHORITY_DECISION_MISMATCH"), + (authority_receipt.execution_identity_root, decision.identity_root, "TERMINAL_AUTHORITY_IDENTITY_MISMATCH"), + (authority_receipt.source_commit, identity.source_commit, "TERMINAL_AUTHORITY_SOURCE_COMMIT_MISMATCH"), + (authority_receipt.workspace_binding, decision.workspace_binding, "TERMINAL_AUTHORITY_WORKSPACE_MISMATCH"), + (authority_receipt.expected_pre_state, identity.expected_pre_state, "TERMINAL_AUTHORITY_PRE_STATE_MISMATCH"), + (authority_receipt.skills_root, identity.skills_root, "TERMINAL_AUTHORITY_SKILLS_ROOT_MISMATCH"), + (authority_receipt.policy_root, decision.policy_root, "TERMINAL_AUTHORITY_POLICY_MISMATCH"), + (authority_receipt.registry_root, decision.registry_root, "TERMINAL_AUTHORITY_REGISTRY_MISMATCH"), + (authority_receipt.approval_grant_root, decision.approval_grant_root, "TERMINAL_AUTHORITY_APPROVAL_MISMATCH"), + (authority_receipt.authority_score, decision.authority_score, "TERMINAL_AUTHORITY_SCORE_MISMATCH"), + (authority_receipt.authority_domain, decision.authority_domain, "TERMINAL_AUTHORITY_DOMAIN_MISMATCH"), + (authority_receipt.action_class, decision.action_class, "TERMINAL_AUTHORITY_ACTION_CLASS_MISMATCH"), + (authority_receipt.requested_capability, decision.requested_capability, "TERMINAL_AUTHORITY_CAPABILITY_MISMATCH"), + (authority_receipt.tool, decision.tool, "TERMINAL_AUTHORITY_TOOL_MISMATCH"), + (authority_receipt.target, decision.target_digest, "TERMINAL_AUTHORITY_TARGET_MISMATCH"), ) + for actual, expected, code in authority_bindings: + if actual != expected: + raise SovereignExecutionError(code) + lease_bindings = ( + (lease_authorization_receipt.operation, "AUTHORIZE_WRITE", "TERMINAL_LEASE_OPERATION_INVALID"), + (lease_authorization_receipt.authority_domain, authority_receipt.authority_domain, "TERMINAL_LEASE_DOMAIN_MISMATCH"), + (lease_authorization_receipt.holder_identity_root, authority_receipt.execution_identity_root, "TERMINAL_LEASE_HOLDER_MISMATCH"), + (lease_authorization_receipt.expected_parent_state, identity.expected_pre_state, "TERMINAL_LEASE_PARENT_MISMATCH"), + (lease_authorization_receipt.action_digest, authority_receipt.requested_action_digest, "TERMINAL_LEASE_ACTION_MISMATCH"), + ) + for actual, expected, code in lease_bindings: + if actual != expected: + raise SovereignExecutionError(code) + if lease_authorization_receipt.outcome != ADMITTED: + raise SovereignExecutionError("TERMINAL_RECEIPT_REQUIRES_ADMITTED_LEASE") + if terminal_outcome in ("DENIED", "FAILED", "ROLLED_BACK") and post_state_digest != identity.expected_pre_state: + raise SovereignExecutionError("NON_SUCCESS_TERMINAL_STATE_CHANGED") + expected_status = { + "SUCCEEDED": "COMPLETED", + "ROLLED_BACK": "COMPLETED", + "DENIED": "DENIED", + "FAILED": "FAILED", + }[terminal_outcome] + _assert_authority_string("execution_id", execution_id) + _assert_hash("post_state_digest", post_state_digest) + + # The chain position, durable pre-commit record, terminal registry transition, + # and append are one in-process critical section. No caller supplies roots. + with lease_manager._lock, receipt_chain._lock, durable_registry._lock: + durable_execution_record = durable_registry.get(execution_id) + if terminal_outcome in ("SUCCEEDED", "FAILED", "ROLLED_BACK") and durable_execution_record.status not in ("RUNNING", "RETRYING"): + raise SovereignExecutionError("TERMINAL_DURABLE_NOT_EXECUTING") + if terminal_outcome == "DENIED" and durable_execution_record.status not in ("PLANNED", "ADMITTED", "RUNNING", "WAITING_FOR_APPROVAL", "BLOCKED"): + raise SovereignExecutionError("TERMINAL_DURABLE_DENIAL_STATE_INVALID") + if durable_execution_record.workspace_binding != authority_receipt.workspace_binding: + raise SovereignExecutionError("TERMINAL_DURABLE_WORKSPACE_MISMATCH") + if durable_execution_record.lease_holder != authority_receipt.execution_identity_root: + raise SovereignExecutionError("TERMINAL_DURABLE_HOLDER_MISMATCH") + if durable_execution_record.parent_state_root != identity.expected_pre_state: + raise SovereignExecutionError("TERMINAL_DURABLE_PARENT_MISMATCH") + if authority_receipt.authority_domain not in durable_execution_record.current_authority: + raise SovereignExecutionError("TERMINAL_DURABLE_AUTHORITY_MISSING") + durable_execution_root = durable_execution_record_root(durable_execution_record) + parent_receipt, sequence = receipt_chain.next_link() + receipt = MutationReceipt( + receipt_version=SCHEMA_VERSION, + execution_identity_root=authority_receipt.execution_identity_root, + workspace_binding=authority_receipt.workspace_binding, + policy_decision_root=decision.decision_root, + authority_receipt_root=authority_receipt.root, + lease_authorization_receipt_root=lease_authorization_receipt.receipt_root, + durable_execution_root=durable_execution_root, + authority_score=decision.authority_score, + authority_domain=decision.authority_domain, + action_class=decision.action_class, + tool=decision.tool, + target=decision.target_digest, + pre_state_digest=identity.expected_pre_state, + requested_action_digest=authority_receipt.requested_action_digest, + result_digest=canonical_hash("AEGIS_ACTION_RESULT_V1", deterministic_redaction(result)), + post_state_digest=post_state_digest, + parent_receipt=parent_receipt, + sequence=sequence, + outcome=terminal_outcome, + denial_code=denial_code, + ) + receipt.validate() + lease_manager.consume_authorization(lease_authorization_receipt) + advance_receipt = lease_manager.advance( + authority_domain=authority_receipt.authority_domain, + fencing_token=lease_manager.current(authority_receipt.authority_domain).fencing_token, + new_parent_state=post_state_digest, + ) + if advance_receipt.outcome != ADMITTED: + raise SovereignExecutionError("TERMINAL_LEASE_ADVANCE_FAILED") + revoke_receipt = lease_manager.revoke(authority_receipt.authority_domain, authority_receipt.execution_identity_root) + if revoke_receipt.outcome != ADMITTED: + raise SovereignExecutionError("TERMINAL_LEASE_REVOCATION_FAILED") + durable_registry._commit_terminal_transition( + execution_id, + status=expected_status, + phase={"SUCCEEDED": "completed", "ROLLED_BACK": "rolled-back", "DENIED": "denied", "FAILED": "failed"}[terminal_outcome], + transition_sequence=durable_execution_record.last_completed_transition + 1, + receipt=receipt, + commit_capability=_TERMINAL_COMMIT_CAPABILITY, + ) + receipt_chain.append(receipt) + terminal_record = durable_registry.get(execution_id) + if terminal_record.current_receipt_root != receipt.root or terminal_record.status != expected_status: + raise SovereignExecutionError("TERMINAL_DURABLE_COMMIT_MISMATCH") + return receipt diff --git a/schemas/authority-decision-receipt.v1.schema.json b/schemas/authority-decision-receipt.v1.schema.json new file mode 100644 index 000000000..5db81d4b8 --- /dev/null +++ b/schemas/authority-decision-receipt.v1.schema.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://aegisomega.com/schemas/authority-decision-receipt.v1.schema.json", + "title": "AEGIS AuthorityDecisionReceipt V1", + "type": "object", + "additionalProperties": false, + "required": [ + "receipt_version", + "issuer_key_id", + "execution_identity_root", + "source_commit", + "workspace_binding", + "expected_pre_state", + "skills_root", + "policy_decision_root", + "policy_root", + "registry_root", + "approval_grant_root", + "authority_score", + "authority_domain", + "action_class", + "requested_capability", + "tool", + "target", + "requested_action_digest", + "outcome", + "denial_codes", + "signature" + ], + "properties": { + "receipt_version": {"const": "1.0.0"}, + "issuer_key_id": {"type": "string", "minLength": 1}, + "execution_identity_root": {"$ref": "#/$defs/hash"}, + "source_commit": {"type": "string", "pattern": "^[0-9a-f]{40,64}$"}, + "workspace_binding": {"$ref": "#/$defs/hash"}, + "expected_pre_state": {"$ref": "#/$defs/hash"}, + "skills_root": {"$ref": "#/$defs/hash"}, + "policy_decision_root": {"$ref": "#/$defs/hash"}, + "policy_root": {"$ref": "#/$defs/hash"}, + "registry_root": {"$ref": "#/$defs/hash"}, + "approval_grant_root": {"$ref": "#/$defs/hash"}, + "authority_score": {"type": "string", "pattern": "^(?:0\\.[0-9]{6}|1\\.000000)$"}, + "authority_domain": {"type": "string", "minLength": 1}, + "action_class": {"enum": ["D0", "D1", "D2", "D3", "D4"]}, + "requested_capability": {"type": "string", "minLength": 1}, + "tool": {"type": "string", "minLength": 1}, + "target": {"$ref": "#/$defs/hash"}, + "requested_action_digest": {"$ref": "#/$defs/hash"}, + "outcome": {"enum": ["ADMITTED", "DENIED"]}, + "signature": {"type": "string", "pattern": "^[0-9a-f]{128}$"}, + "denial_codes": { + "type": "array", + "items": {"type": "string", "minLength": 1}, + "uniqueItems": true + } + }, + "$defs": { + "hash": {"type": "string", "pattern": "^[0-9a-f]{64}$"} + } +} diff --git a/schemas/mutation-receipt.v1.schema.json b/schemas/mutation-receipt.v1.schema.json index 580efaa98..8851593c0 100644 --- a/schemas/mutation-receipt.v1.schema.json +++ b/schemas/mutation-receipt.v1.schema.json @@ -4,13 +4,16 @@ "title": "AEGIS MutationReceipt V1", "type": "object", "additionalProperties": false, - "required": ["receipt_version","execution_identity_root","workspace_binding","policy_decision_root","authority_score","authority_domain","action_class","tool","target","pre_state_digest","requested_action_digest","result_digest","post_state_digest","parent_receipt","sequence","outcome","denial_code"], + "required": ["receipt_version","execution_identity_root","workspace_binding","policy_decision_root","authority_receipt_root","lease_authorization_receipt_root","durable_execution_root","authority_score","authority_domain","action_class","tool","target","pre_state_digest","requested_action_digest","result_digest","post_state_digest","parent_receipt","sequence","outcome","denial_code"], "properties": { "receipt_version": {"const": "1.0.0"}, "execution_identity_root": {"$ref": "#/$defs/hash"}, "workspace_binding": {"$ref": "#/$defs/hash"}, "policy_decision_root": {"$ref": "#/$defs/hash"}, - "authority_score": {"type": "string", "pattern": "^(0|1)\\.[0-9]{6}$"}, + "authority_receipt_root": {"$ref": "#/$defs/hash"}, + "lease_authorization_receipt_root": {"$ref": "#/$defs/hash"}, + "durable_execution_root": {"$ref": "#/$defs/hash"}, + "authority_score": {"type": "string", "pattern": "^(?:0\\.[0-9]{6}|1\\.000000)$"}, "authority_domain": {"type": "string", "minLength": 1}, "action_class": {"enum": ["D0","D1","D2","D3","D4"]}, "tool": {"type": "string", "minLength": 1}, diff --git a/scripts/automaton3-authority.py b/scripts/automaton3-authority.py index b30f3ea4b..5c640d60d 100644 --- a/scripts/automaton3-authority.py +++ b/scripts/automaton3-authority.py @@ -4,6 +4,7 @@ import argparse import json +import os import sys from dataclasses import asdict from pathlib import Path @@ -17,12 +18,15 @@ AuthorityEvaluator, AuthorityRequest, ExecutionIdentityEnvelope, - ZERO_HASH, canonical_hash, + canonical_remote, decision_dict, - load_capability_registry, - load_policy, - make_mutation_receipt, + git_head, + git_remote, + load_capability_registry_from_commit, + load_policy_from_commit, + make_authority_decision_receipt, + verify_live_authority_roots, verify_workspace, ) @@ -42,11 +46,18 @@ def evaluate(payload: dict) -> dict: workspace_payload = payload.get("workspace", {}) try: + live_head = git_head(ROOT) + live_remote = git_remote(ROOT) + if live_head != identity.source_commit: + return deny("SOURCE_COMMIT_MISMATCH") + claimed_remote = workspace_payload.get("remote_origin") + if claimed_remote is not None and canonical_remote(claimed_remote) != live_remote: + return deny("WORKSPACE_REMOTE_CLAIM_MISMATCH") workspace = verify_workspace( declared_root=ROOT, cwd=workspace_payload.get("actual_cwd", ROOT), expected_remote=identity.repository_identity, - actual_remote=workspace_payload.get("remote_origin", identity.repository_identity), + actual_remote=live_remote, project_identity=identity.project_identity, source_commit=identity.source_commit, operator_authorization=identity.approval_reference, @@ -65,14 +76,28 @@ def evaluate(payload: dict) -> dict: } try: - policy, policy_root = load_policy(ROOT / "harness" / "policies" / "consequence-policy.v1.json") - registry, registry_root = load_capability_registry( + policy, policy_root = load_policy_from_commit( repository_root=ROOT, - skill_tree_path=ROOT / "harness" / "skill_tree.json", - capability_map_path=ROOT / "harness" / "policies" / "capability-map.v1.json", + source_commit=identity.source_commit, + policy_path="harness/policies/consequence-policy.v1.json", + ) + registry, skills_root, registry_root = load_capability_registry_from_commit( + repository_root=ROOT, + source_commit=identity.source_commit, + skill_tree_path="harness/skill_tree.json", + capability_map_path="harness/policies/capability-map.v1.json", ) except Exception as exc: return deny("AUTHORITY_SERVICE_UNAVAILABLE", str(exc)) + try: + verify_live_authority_roots( + identity, + skills_root=skills_root, + registry_root=registry_root, + policy_root=policy_root, + ) + except Exception as exc: + return deny(str(exc), "commit-bound authority roots do not match execution identity") request_payload = payload.get("request", {}) action = payload.get("action", {}) @@ -80,6 +105,11 @@ def evaluate(payload: dict) -> dict: if identity.action_digest != action_digest: return deny("ACTION_DIGEST_MISMATCH") try: + trusted_operator_keys = json.loads(os.environ.get("AEGIS_TRUSTED_OPERATOR_KEYS_JSON", "{}")) + if not isinstance(trusted_operator_keys, dict) or any(not isinstance(key, str) or not isinstance(value, str) for key, value in trusted_operator_keys.items()): + raise ValueError("trusted operator key map") + authority_issuer_key_id = os.environ["AEGIS_AUTHORITY_ISSUER_KEY_ID"] + authority_signing_key = os.environ["AEGIS_AUTHORITY_SIGNING_KEY_HEX"] request = AuthorityRequest( action_class=request_payload["action_class"], authority_domain=request_payload["authority_domain"], @@ -91,23 +121,25 @@ def evaluate(payload: dict) -> dict: source_commit=identity.source_commit, registry_root=registry_root, policy_root=policy_root, + action_digest=action_digest, + expected_pre_state=identity.expected_pre_state, + workspace_mode=request_payload["workspace_mode"], current_generation=int(request_payload.get("current_generation", 0)), approval_reference=identity.approval_reference, + rollback_reference=request_payload.get("rollback_reference", "NONE"), idempotency_key=request_payload.get("idempotency_key", "NONE"), compensation_reference=request_payload.get("compensation_reference", "NONE"), ) approval = ApprovalGrant(**payload["approval"]) if payload.get("approval") else None - decision = AuthorityEvaluator(policy=policy, registry=registry, repository_root=ROOT).evaluate(request, approval=approval) - receipt = make_mutation_receipt( - identity_root=identity_root, - workspace_binding=identity.workspace_binding, + evaluator = AuthorityEvaluator(policy=policy, registry=registry, repository_root=ROOT, trusted_operator_keys=trusted_operator_keys) + decision = evaluator.evaluate(request, approval=approval) + receipt = make_authority_decision_receipt( + identity=identity, + request=request, decision=decision, - pre_state_digest=request_payload.get("pre_state_digest", ZERO_HASH), - action_digest=action_digest, - result={"authority_outcome": decision.outcome}, - post_state_digest=request_payload.get("post_state_digest", request_payload.get("pre_state_digest", ZERO_HASH)), - parent_receipt=request_payload.get("parent_receipt", ZERO_HASH), - sequence=int(request_payload.get("sequence", 0)), + evaluator=evaluator, + issuer_key_id=authority_issuer_key_id, + issuer_private_key_hex=authority_signing_key, ) return { "schema_version": "1.0.0", @@ -116,8 +148,8 @@ def evaluate(payload: dict) -> dict: "workspace_binding": identity.workspace_binding, "workspace_decision_root": workspace.decision_root, "policy_decision": decision_dict(decision), - "mutation_receipt": asdict(receipt), - "mutation_receipt_root": receipt.root, + "authority_receipt": asdict(receipt), + "authority_receipt_root": receipt.root, "observation": asdict(workspace.observation), } except Exception as exc: diff --git a/scripts/run-automaton3-tests.py b/scripts/run-automaton3-tests.py index 6f2dcb114..a06d29978 100644 --- a/scripts/run-automaton3-tests.py +++ b/scripts/run-automaton3-tests.py @@ -5,6 +5,7 @@ import argparse import hashlib import json +import re import subprocess import sys from pathlib import Path @@ -15,6 +16,30 @@ ROOT / "sovereign-omega-v2/python/tests/test_operator_visibility.py", ) +ASSERTION_TESTS = { + "adaptive_attempts": { + "test_adaptive_denial_attempts_k_1_10_100", + }, + "state_preservation": { + "test_01_unknown_coordinator_capability", + "test_09_mismatched_parent_state", + "test_12_stale_writer_lease", + "test_13_replayed_fencing_token", + "test_29_receipt_chain_break", + "test_authority_admission_is_not_terminal_success", + }, + "external_side_effect_absence": { + "test_01_unknown_coordinator_capability", + "test_14_duplicate_external_action", + "test_15_replay_after_side_effect", + }, + "operator_visibility": { + "test_operator_visibility_cannot_be_suppressed", + "test_authorization_mutation_and_cancellation_are_chained", + "test_broken_operator_chain_is_denied", + }, +} + def main() -> int: parser = argparse.ArgumentParser() @@ -26,7 +51,7 @@ def main() -> int: return_code = 0 for test_file in TEST_FILES: result = subprocess.run( - [sys.executable, str(test_file)], + [sys.executable, str(test_file), "-v"], cwd=ROOT, text=True, capture_output=True, @@ -35,18 +60,44 @@ def main() -> int: if result.returncode != 0: return_code = result.returncode - log = "".join(outputs).replace(str(ROOT), "") + raw_log = "".join(outputs) + observed_test_count = sum(int(value) for value in re.findall(r"Ran ([0-9]+) tests?", raw_log)) + expected_test_count = 54 + if observed_test_count != expected_test_count: + return_code = return_code or 1 + log = raw_log.replace(str(ROOT), "") + log = re.sub(r"Ran ([0-9]+) tests? in [0-9.]+s", r"Ran \1 tests in s", log) + passed_test_ids = sorted(set(re.findall( + r"^(test_[A-Za-z0-9_]+).* \.\.\. ok$", + log, + flags=re.MULTILINE, + ))) + passed = set(passed_test_ids) + assertion_sets = { + name: { + "required_test_ids": sorted(required), + "satisfied": required.issubset(passed), + } + for name, required in sorted(ASSERTION_TESTS.items()) + } + all_assertions_satisfied = all( + assertion["satisfied"] for assertion in assertion_sets.values() + ) + if not all_assertions_satisfied: + return_code = return_code or 1 Path(args.log).write_text(log, encoding="utf-8") summary = { "schema_version": "1.0.0", "suite": "AEGIS_AUTOMATON3_AUTHORITY_ABUSE_V1", - "expected_test_count": 41, - "adaptive_attempts": [1, 10, 100], - "successful_denial_assertions": 34, - "bypasses": 0 if return_code == 0 else None, - "state_preservation_asserted": True, - "external_side_effect_absence_asserted": True, - "operator_visibility_asserted": True, + "expected_test_count": expected_test_count, + "observed_test_count": observed_test_count, + "adaptive_attempts": [1, 10, 100] if assertion_sets["adaptive_attempts"]["satisfied"] else [], + "bypasses": 0 if return_code == 0 and all_assertions_satisfied else None, + "state_preservation_asserted": assertion_sets["state_preservation"]["satisfied"], + "external_side_effect_absence_asserted": assertion_sets["external_side_effect_absence"]["satisfied"], + "operator_visibility_asserted": assertion_sets["operator_visibility"]["satisfied"], + "assertion_sets": assertion_sets, + "passed_test_ids": passed_test_ids, "return_code": return_code, "normalized_log_sha256": hashlib.sha256(log.encode()).hexdigest(), } diff --git a/scripts/validate-automaton3.py b/scripts/validate-automaton3.py index 5297fa378..9075754fa 100644 --- a/scripts/validate-automaton3.py +++ b/scripts/validate-automaton3.py @@ -17,17 +17,23 @@ "harness/sdk/sovereign_execution.py", "harness/sdk/authority_client.py", "harness/sdk/operator_visibility.py", + "harness/requirements-automaton3.txt", "harness/policies/consequence-policy.v1.json", "harness/policies/capability-map.v1.json", "scripts/automaton3-authority.py", "scripts/run-automaton3-tests.py", "scripts/validate-automaton3.py", "agents/coordinator.py", + "sovereign-omega-v2/mcp-server/package.json", + "sovereign-omega-v2/mcp-server/src/authority-response.ts", "sovereign-omega-v2/mcp-server/src/index.ts", + "sovereign-omega-v2/mcp-server/test/authority-response.mjs", "sovereign-omega-v2/mcp-server/test/automaton3-authority.mjs", "sovereign-omega-v2/python/tests/test_automaton3.py", + "sovereign-omega-v2/python/tests/test_coordinator_authority.py", "sovereign-omega-v2/python/tests/test_operator_visibility.py", "schemas/execution-identity-envelope.v1.schema.json", + "schemas/authority-decision-receipt.v1.schema.json", "schemas/mutation-receipt.v1.schema.json", "schemas/event-envelope.v1.schema.json", "schemas/writer-lease.v1.schema.json", @@ -124,8 +130,10 @@ def evaluate( violations.append("authority bypass detected") if summary.get("adaptive_attempts") != [1, 10, 100]: violations.append("adaptive attempt matrix incomplete") - if summary.get("expected_test_count") != 41: + if summary.get("expected_test_count") != 54: violations.append("Automaton-3 test count incomplete") + if summary.get("observed_test_count") != summary.get("expected_test_count"): + violations.append("Automaton-3 observed test count mismatch") if summary.get("operator_visibility_asserted") is not True: violations.append("operator visibility invariant not asserted") if summary.get("state_preservation_asserted") is not True: diff --git a/sovereign-omega-v2/mcp-server/package.json b/sovereign-omega-v2/mcp-server/package.json index 652332bb6..bc6d82ea9 100644 --- a/sovereign-omega-v2/mcp-server/package.json +++ b/sovereign-omega-v2/mcp-server/package.json @@ -10,7 +10,7 @@ "start": "node dist/index.js", "dev": "tsx src/index.ts", "test:resources": "npm run build && node test/resources.mjs", - "test:automaton3": "npm run build && node test/automaton3-authority.mjs" + "test:automaton3": "npm run build && node test/authority-response.mjs && node test/automaton3-authority.mjs" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1" diff --git a/sovereign-omega-v2/mcp-server/src/authority-response.ts b/sovereign-omega-v2/mcp-server/src/authority-response.ts new file mode 100644 index 000000000..b240377f9 --- /dev/null +++ b/sovereign-omega-v2/mcp-server/src/authority-response.ts @@ -0,0 +1,526 @@ +import { createHash, createPublicKey, verify } from 'node:crypto' +import { relative, resolve, sep } from 'node:path' + +export type ActionClass = 'D0' | 'D1' | 'D2' | 'D3' | 'D4' + +export type AuthorityDecisionReceipt = { + receipt_version: '1.0.0' + issuer_key_id: string + execution_identity_root: string + source_commit: string + workspace_binding: string + expected_pre_state: string + skills_root: string + policy_decision_root: string + policy_root: string + registry_root: string + approval_grant_root: string + authority_score: string + authority_domain: string + action_class: ActionClass + requested_capability: string + tool: string + target: string + requested_action_digest: string + outcome: 'ADMITTED' + denial_codes: [] + signature: string +} + +export type VerifiedAuthorityDecision = { + schema_version: '1.0.0' + outcome: 'ADMITTED' + execution_identity_root: string + workspace_binding: string + workspace_decision_root: string + policy_decision: Record & { decision_root: string } + authority_receipt: AuthorityDecisionReceipt + authority_receipt_root: string + observation: Record +} + +export type AuthorityRequestBindings = { + actionClass: ActionClass + authorityDomain: string + requestedCapability: string + tool: string + target: string + action: Record +} + +export type AuthorityResponseBindings = AuthorityRequestBindings & { + expectedIdentityRoot: string + expectedWorkspaceBinding: string + expectedPolicyRoot: string + expectedSkillsRoot: string + expectedRegistryRoot: string + expectedActionDigest: string + expectedTargetDigest: string + expectedProjectIdentity: string + expectedRepositoryIdentity: string + expectedSourceCommit: string + expectedPreState: string + expectedWorkspaceDecisionRoot: string + expectedWorkspaceObservation: Record + trustedAuthorityKeys: Record +} + +export type WorkspaceRequestBindings = { + actual_cwd: string + remote_origin: string + mutation_target: string + path_views?: Record +} + +export type AuthorityProcessResult = { + status: number | null + signal?: string | null + stdout?: string | null + stderr?: string | null + error?: Error +} + +export class AuthorityResponseError extends Error { + constructor(readonly code: string) { + super(code) + this.name = 'AuthorityResponseError' + } +} + +const HASH_RE = /^[0-9a-f]{64}$/ +const GIT_RE = /^[0-9a-f]{40,64}$/ +const SCORE_RE = /^(?:0\.[0-9]{6}|1\.000000)$/ +const SAFE_ID_RE = /^[A-Za-z0-9._:/@+#=-]+$/ +const ACTION_CLASSES = new Set(['D0', 'D1', 'D2', 'D3', 'D4']) + +const IDENTITY_KEYS = [ + 'schema_version', 'repository_identity', 'repository_root', 'source_commit', + 'branch_or_ref', 'project_identity', 'workspace_root', 'workspace_binding', + 'parent_state_root', 'skills_root', 'registry_root', 'policy_root', 'actor_class', + 'actor_identity', 'model_identity', 'session_identity', 'physical_executor', + 'tool_identity', 'workflow_identity', 'authority_domain', 'requested_capability', + 'observed_authority', 'approval_reference', 'input_digest', 'action_digest', + 'expected_pre_state', 'deterministic_nonce', +] as const + +const POLICY_DECISION_KEYS = [ + 'schema_version', 'outcome', 'authority_score', 'action_class', 'authority_domain', + 'requested_capability', 'tool', 'target_digest', 'identity_root', 'workspace_binding', + 'registry_root', 'policy_root', 'approval_grant_root', 'denial_codes', 'decision_root', +] as const + +const RECEIPT_KEYS = [ + 'receipt_version', 'issuer_key_id', 'execution_identity_root', 'source_commit', + 'workspace_binding', 'expected_pre_state', 'policy_decision_root', 'policy_root', + 'skills_root', 'registry_root', 'approval_grant_root', 'authority_score', + 'authority_domain', 'action_class', 'requested_capability', 'tool', 'target', + 'requested_action_digest', 'outcome', 'denial_codes', 'signature', +] as const + +const RESPONSE_KEYS = [ + 'schema_version', 'outcome', 'execution_identity_root', 'workspace_binding', + 'workspace_decision_root', 'policy_decision', 'authority_receipt', + 'authority_receipt_root', 'observation', +] as const + +const OBSERVATION_KEYS = [ + 'declared_project', 'actual_cwd', 'resolved_repository_root', 'remote_origin', + 'mutation_target', 'path_views', +] as const + +function fail(code: string): never { + throw new AuthorityResponseError(code) +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +function record(value: unknown, code: string): Record { + if (!isRecord(value)) fail(code) + return value +} + +function exactKeys(value: Record, keys: readonly string[], code: string): void { + const actual = Object.keys(value).sort(compareCodePoints) + const expected = [...keys].sort(compareCodePoints) + if (actual.length !== expected.length || actual.some((item, index) => item !== expected[index])) fail(code) +} + +function requiredString(value: Record, key: string, code: string): string { + const candidate = value[key] + if (typeof candidate !== 'string' || candidate.length === 0) fail(code) + return candidate +} + +function requiredHash(value: Record, key: string, code: string): string { + const candidate = requiredString(value, key, code) + if (!HASH_RE.test(candidate)) fail(code) + return candidate +} + +function equal(actual: unknown, expected: unknown, code: string): void { + if (actual !== expected) fail(code) +} + +function compareCodePoints(left: string, right: string): number { + const a = Array.from(left, (value) => value.codePointAt(0) as number) + const b = Array.from(right, (value) => value.codePointAt(0) as number) + for (let index = 0; index < Math.min(a.length, b.length); index += 1) { + if (a[index] !== b[index]) return (a[index] as number) - (b[index] as number) + } + return a.length - b.length +} + +function canonicalGitHubRemote(value: string): string { + let remote = value.trim() + if (remote.startsWith('git@github.com:')) remote = `https://github.com/${remote.slice('git@github.com:'.length)}` + if (remote.startsWith('ssh://git@github.com/')) remote = `https://github.com/${remote.slice('ssh://git@github.com/'.length)}` + if (!remote.startsWith('https://github.com/')) fail('EXECUTION_IDENTITY_REMOTE_NOT_CANONICAL') + if (!remote.endsWith('.git')) remote += '.git' + return remote +} + +function assertUnicodeScalarString(value: string): void { + for (let index = 0; index < value.length; index += 1) { + const code = value.charCodeAt(index) + if (code >= 0xd800 && code <= 0xdbff) { + const next = value.charCodeAt(index + 1) + if (!(next >= 0xdc00 && next <= 0xdfff)) fail('CANONICAL_UNPAIRED_SURROGATE') + index += 1 + } else if (code >= 0xdc00 && code <= 0xdfff) { + fail('CANONICAL_UNPAIRED_SURROGATE') + } + } +} + +/** + * Python json.dumps(..., ensure_ascii=False, allow_nan=False, sort_keys=True, + * separators=(",", ":")) parity for the JSON subset accepted at this boundary. + * Non-integer numbers are denied because Python and ECMAScript exponent/float + * rendering is not byte-identical for every finite IEEE-754 value. + */ +export function pythonCanonicalJson(value: unknown): string { + if (value === null) return 'null' + if (typeof value === 'string') { + assertUnicodeScalarString(value) + return JSON.stringify(value) + } + if (typeof value === 'boolean') return value ? 'true' : 'false' + if (typeof value === 'number') { + if (!Number.isSafeInteger(value)) fail('CANONICAL_NUMBER_UNSUPPORTED') + return Object.is(value, -0) ? '0' : String(value) + } + if (Array.isArray(value)) return `[${value.map(pythonCanonicalJson).join(',')}]` + if (isRecord(value)) { + const keys = Object.keys(value).sort(compareCodePoints) + return `{${keys.map((key) => { + if (value[key] === undefined) fail('CANONICAL_UNDEFINED_VALUE') + return `${pythonCanonicalJson(key)}:${pythonCanonicalJson(value[key])}` + }).join(',')}}` + } + fail('CANONICAL_VALUE_UNSUPPORTED') +} + +export function canonicalHash(domain: string, value: unknown): string { + return createHash('sha256') + .update(pythonCanonicalJson({ domain, value }), 'utf8') + .digest('hex') +} + +function expectedWorkspaceEvidence( + workspace: WorkspaceRequestBindings, + repositoryRoot: string, + projectIdentity: string, + sourceCommit: string, + workspaceBinding: string, +): { observation: Record; decisionRoot: string } { + const resolvedRoot = resolve(repositoryRoot) + const actualCwd = resolve(workspace.actual_cwd) + const mutationTarget = resolve(workspace.mutation_target) + const relativeTargetRaw = relative(resolvedRoot, mutationTarget) + const mutationTargetRelative = relativeTargetRaw === '' + ? '.' + : relativeTargetRaw.startsWith('..') || resolve(resolvedRoot, relativeTargetRaw) !== mutationTarget + ? 'OUTSIDE_REPOSITORY' + : relativeTargetRaw.split(sep).join('/') + const remote = canonicalGitHubRemote(workspace.remote_origin) + const observation = { + declared_project: projectIdentity, + actual_cwd: actualCwd, + resolved_repository_root: resolvedRoot, + remote_origin: workspace.remote_origin, + mutation_target: mutationTarget, + path_views: workspace.path_views ?? {}, + } + const decisionRoot = canonicalHash('AEGIS_WORKSPACE_DECISION_V1', { + outcome: 'ADMITTED', + workspace_binding: workspaceBinding, + denial_codes: [], + declared_project: projectIdentity, + remote_origin: remote, + source_commit: sourceCommit, + mutation_target_relative: mutationTargetRelative, + }) + return { observation, decisionRoot } +} + +function validateIdentity( + identity: unknown, + request: AuthorityRequestBindings, + workspace: WorkspaceRequestBindings, + repositoryRoot: string, + trustedAuthorityKeys: Record, +): AuthorityResponseBindings { + const body = record(identity, 'EXECUTION_IDENTITY_MALFORMED') + exactKeys(body, IDENTITY_KEYS, 'EXECUTION_IDENTITY_SCHEMA_DRIFT') + equal(body['schema_version'], '1.0.0', 'EXECUTION_IDENTITY_SCHEMA_UNSUPPORTED') + for (const key of IDENTITY_KEYS) requiredString(body, key, `EXECUTION_IDENTITY_FIELD_INVALID:${key}`) + for (const key of [ + 'workspace_binding', 'parent_state_root', 'skills_root', 'registry_root', + 'policy_root', 'input_digest', 'action_digest', 'expected_pre_state', + ]) requiredHash(body, key, `EXECUTION_IDENTITY_HASH_INVALID:${key}`) + if (!GIT_RE.test(body['source_commit'] as string)) fail('EXECUTION_IDENTITY_SOURCE_COMMIT_INVALID') + equal(body['repository_root'], '.', 'EXECUTION_IDENTITY_REPOSITORY_ROOT_INVALID') + equal(body['workspace_root'], '.', 'EXECUTION_IDENTITY_WORKSPACE_ROOT_INVALID') + const remote = body['repository_identity'] as string + equal(remote, canonicalGitHubRemote(remote), 'EXECUTION_IDENTITY_REMOTE_NOT_CANONICAL') + for (const key of [ + 'branch_or_ref', 'project_identity', 'actor_class', 'actor_identity', 'model_identity', + 'session_identity', 'physical_executor', 'tool_identity', 'workflow_identity', + 'authority_domain', 'requested_capability', 'observed_authority', 'approval_reference', + 'deterministic_nonce', + ]) { + if (!SAFE_ID_RE.test(body[key] as string)) fail(`EXECUTION_IDENTITY_AUTHORITY_STRING_INVALID:${key}`) + } + + const expectedActionDigest = canonicalHash('AEGIS_REQUESTED_ACTION_V1', request.action) + const expectedTargetDigest = canonicalHash('AEGIS_AUTHORITY_TARGET_V1', request.target) + equal(body['action_digest'], expectedActionDigest, 'EXECUTION_IDENTITY_ACTION_DIGEST_MISMATCH') + equal(body['authority_domain'], request.authorityDomain, 'EXECUTION_IDENTITY_AUTHORITY_DOMAIN_MISMATCH') + equal(body['requested_capability'], request.requestedCapability, 'EXECUTION_IDENTITY_CAPABILITY_MISMATCH') + equal(body['tool_identity'], request.tool, 'EXECUTION_IDENTITY_TOOL_MISMATCH') + + const expectedWorkspaceBinding = canonicalHash('AEGIS_WORKSPACE_BINDING_V1', { + repository_remote: remote, + repository_root: '.', + project_identity: body['project_identity'], + source_commit: body['source_commit'], + operator_authorization: body['approval_reference'], + }) + equal(body['workspace_binding'], expectedWorkspaceBinding, 'EXECUTION_IDENTITY_WORKSPACE_BINDING_MISMATCH') + const workspaceEvidence = expectedWorkspaceEvidence( + workspace, + repositoryRoot, + body['project_identity'] as string, + body['source_commit'] as string, + expectedWorkspaceBinding, + ) + + return { + ...request, + expectedIdentityRoot: canonicalHash('AEGIS_EXECUTION_IDENTITY_V1', body), + expectedWorkspaceBinding, + expectedPolicyRoot: body['policy_root'] as string, + expectedSkillsRoot: body['skills_root'] as string, + expectedRegistryRoot: body['registry_root'] as string, + expectedActionDigest, + expectedTargetDigest, + expectedProjectIdentity: body['project_identity'] as string, + expectedRepositoryIdentity: remote, + expectedSourceCommit: body['source_commit'] as string, + expectedPreState: body['expected_pre_state'] as string, + expectedWorkspaceDecisionRoot: workspaceEvidence.decisionRoot, + expectedWorkspaceObservation: workspaceEvidence.observation, + trustedAuthorityKeys, + } +} + +export function buildAuthorityResponseBindings( + identity: unknown, + request: AuthorityRequestBindings, + workspace: WorkspaceRequestBindings, + repositoryRoot: string, + trustedAuthorityKeys: Record, +): AuthorityResponseBindings { + return validateIdentity(identity, request, workspace, repositoryRoot, trustedAuthorityKeys) +} + +function validateSortedUniqueCodes(value: unknown, code: string): string[] { + if (!Array.isArray(value) || value.some((item) => typeof item !== 'string' || item.length === 0)) fail(code) + const items = value as string[] + const canonical = [...new Set(items)].sort(compareCodePoints) + if (canonical.length !== items.length || canonical.some((item, index) => item !== items[index])) fail(code) + return items +} + +function validatePolicyDecision(value: unknown, expected: AuthorityResponseBindings): Record & { decision_root: string } { + const decision = record(value, 'AUTHORITY_POLICY_DECISION_MALFORMED') + exactKeys(decision, POLICY_DECISION_KEYS, 'AUTHORITY_POLICY_DECISION_SCHEMA_DRIFT') + equal(decision['schema_version'], '1.0.0', 'AUTHORITY_POLICY_DECISION_SCHEMA_UNSUPPORTED') + equal(decision['outcome'], 'ADMITTED', 'AUTHORITY_POLICY_DECISION_NOT_ADMITTED') + const score = requiredString(decision, 'authority_score', 'AUTHORITY_POLICY_DECISION_SCORE_INVALID') + if (!SCORE_RE.test(score)) fail('AUTHORITY_POLICY_DECISION_SCORE_INVALID') + const actionClass = requiredString(decision, 'action_class', 'AUTHORITY_POLICY_DECISION_CLASS_INVALID') + if (!ACTION_CLASSES.has(actionClass as ActionClass)) fail('AUTHORITY_POLICY_DECISION_CLASS_INVALID') + validateSortedUniqueCodes(decision['denial_codes'], 'AUTHORITY_POLICY_DECISION_DENIAL_CODES_INVALID') + if ((decision['denial_codes'] as string[]).length !== 0) fail('ADMITTED_POLICY_DECISION_HAS_DENIAL_CODES') + for (const key of ['target_digest', 'identity_root', 'workspace_binding', 'registry_root', 'policy_root', 'approval_grant_root', 'decision_root']) { + requiredHash(decision, key, `AUTHORITY_POLICY_DECISION_HASH_INVALID:${key}`) + } + for (const key of ['authority_domain', 'requested_capability', 'tool']) { + const item = requiredString(decision, key, `AUTHORITY_POLICY_DECISION_FIELD_INVALID:${key}`) + if (!SAFE_ID_RE.test(item)) fail(`AUTHORITY_POLICY_DECISION_FIELD_INVALID:${key}`) + } + equal(decision['action_class'], expected.actionClass, 'AUTHORITY_POLICY_DECISION_CLASS_MISMATCH') + equal(decision['authority_domain'], expected.authorityDomain, 'AUTHORITY_POLICY_DECISION_DOMAIN_MISMATCH') + equal(decision['requested_capability'], expected.requestedCapability, 'AUTHORITY_POLICY_DECISION_CAPABILITY_MISMATCH') + equal(decision['tool'], expected.tool, 'AUTHORITY_POLICY_DECISION_TOOL_MISMATCH') + equal(decision['target_digest'], expected.expectedTargetDigest, 'AUTHORITY_POLICY_DECISION_TARGET_MISMATCH') + equal(decision['identity_root'], expected.expectedIdentityRoot, 'AUTHORITY_POLICY_DECISION_IDENTITY_MISMATCH') + equal(decision['workspace_binding'], expected.expectedWorkspaceBinding, 'AUTHORITY_POLICY_DECISION_WORKSPACE_MISMATCH') + equal(decision['registry_root'], expected.expectedRegistryRoot, 'AUTHORITY_POLICY_DECISION_REGISTRY_MISMATCH') + equal(decision['policy_root'], expected.expectedPolicyRoot, 'AUTHORITY_POLICY_DECISION_POLICY_MISMATCH') + const body = { ...decision } + delete body['decision_root'] + equal(decision['decision_root'], canonicalHash('AEGIS_POLICY_DECISION_V1', body), 'AUTHORITY_POLICY_DECISION_ROOT_MISMATCH') + return decision as Record & { decision_root: string } +} + +function validateAuthorityReceipt( + value: unknown, + receiptRoot: unknown, + decision: Record & { decision_root: string }, + expected: AuthorityResponseBindings, +): AuthorityDecisionReceipt { + const receipt = record(value, 'AUTHORITY_RECEIPT_MALFORMED') + exactKeys(receipt, RECEIPT_KEYS, 'AUTHORITY_RECEIPT_SCHEMA_DRIFT') + equal(receipt['receipt_version'], '1.0.0', 'AUTHORITY_RECEIPT_SCHEMA_UNSUPPORTED') + equal(receipt['outcome'], 'ADMITTED', 'AUTHORITY_RECEIPT_NOT_ADMITTED') + const codes = validateSortedUniqueCodes(receipt['denial_codes'], 'AUTHORITY_RECEIPT_DENIAL_CODES_INVALID') + if (codes.length !== 0) fail('ADMITTED_AUTHORITY_RECEIPT_HAS_DENIAL_CODES') + const score = requiredString(receipt, 'authority_score', 'AUTHORITY_RECEIPT_SCORE_INVALID') + if (!SCORE_RE.test(score)) fail('AUTHORITY_RECEIPT_SCORE_INVALID') + const actionClass = requiredString(receipt, 'action_class', 'AUTHORITY_RECEIPT_CLASS_INVALID') + if (!ACTION_CLASSES.has(actionClass as ActionClass)) fail('AUTHORITY_RECEIPT_CLASS_INVALID') + for (const key of [ + 'execution_identity_root', 'workspace_binding', 'expected_pre_state', 'policy_decision_root', 'policy_root', + 'skills_root', 'registry_root', 'approval_grant_root', 'target', 'requested_action_digest', + ]) requiredHash(receipt, key, `AUTHORITY_RECEIPT_HASH_INVALID:${key}`) + const sourceCommit = requiredString(receipt, 'source_commit', 'AUTHORITY_RECEIPT_SOURCE_COMMIT_INVALID') + if (!GIT_RE.test(sourceCommit)) fail('AUTHORITY_RECEIPT_SOURCE_COMMIT_INVALID') + for (const key of ['issuer_key_id', 'authority_domain', 'requested_capability', 'tool']) { + const item = requiredString(receipt, key, `AUTHORITY_RECEIPT_FIELD_INVALID:${key}`) + if (!SAFE_ID_RE.test(item)) fail(`AUTHORITY_RECEIPT_FIELD_INVALID:${key}`) + } + const signature = requiredString(receipt, 'signature', 'AUTHORITY_RECEIPT_SIGNATURE_INVALID') + if (!/^[0-9a-f]{128}$/.test(signature)) fail('AUTHORITY_RECEIPT_SIGNATURE_INVALID') + + const pairs: Array<[string, unknown, string]> = [ + ['execution_identity_root', expected.expectedIdentityRoot, 'AUTHORITY_RECEIPT_IDENTITY_MISMATCH'], + ['source_commit', expected.expectedSourceCommit, 'AUTHORITY_RECEIPT_SOURCE_COMMIT_MISMATCH'], + ['workspace_binding', expected.expectedWorkspaceBinding, 'AUTHORITY_RECEIPT_WORKSPACE_MISMATCH'], + ['expected_pre_state', expected.expectedPreState, 'AUTHORITY_RECEIPT_PRE_STATE_MISMATCH'], + ['policy_decision_root', decision.decision_root, 'AUTHORITY_RECEIPT_DECISION_MISMATCH'], + ['policy_root', expected.expectedPolicyRoot, 'AUTHORITY_RECEIPT_POLICY_MISMATCH'], + ['skills_root', expected.expectedSkillsRoot, 'AUTHORITY_RECEIPT_SKILLS_ROOT_MISMATCH'], + ['registry_root', expected.expectedRegistryRoot, 'AUTHORITY_RECEIPT_REGISTRY_MISMATCH'], + ['approval_grant_root', decision['approval_grant_root'], 'AUTHORITY_RECEIPT_APPROVAL_MISMATCH'], + ['authority_score', decision['authority_score'], 'AUTHORITY_RECEIPT_SCORE_MISMATCH'], + ['authority_domain', expected.authorityDomain, 'AUTHORITY_RECEIPT_DOMAIN_MISMATCH'], + ['action_class', expected.actionClass, 'AUTHORITY_RECEIPT_CLASS_MISMATCH'], + ['requested_capability', expected.requestedCapability, 'AUTHORITY_RECEIPT_CAPABILITY_MISMATCH'], + ['tool', expected.tool, 'AUTHORITY_RECEIPT_TOOL_MISMATCH'], + ['target', expected.expectedTargetDigest, 'AUTHORITY_RECEIPT_TARGET_MISMATCH'], + ['requested_action_digest', expected.expectedActionDigest, 'AUTHORITY_RECEIPT_ACTION_MISMATCH'], + ] + for (const [key, valueToMatch, code] of pairs) equal(receipt[key], valueToMatch, code) + + const issuerKeyId = receipt['issuer_key_id'] as string + const publicKeyHex = expected.trustedAuthorityKeys[issuerKeyId] + if (!publicKeyHex) fail('AUTHORITY_RECEIPT_ISSUER_UNTRUSTED') + if (!HASH_RE.test(publicKeyHex)) fail('AUTHORITY_RECEIPT_PUBLIC_KEY_INVALID') + const signingBody = { ...receipt } + delete signingBody['signature'] + try { + const publicKey = createPublicKey({ + key: Buffer.concat([Buffer.from('302a300506032b6570032100', 'hex'), Buffer.from(publicKeyHex, 'hex')]), + format: 'der', + type: 'spki', + }) + const ok = verify( + null, + Buffer.from(pythonCanonicalJson({ domain: 'AEGIS_AUTHORITY_DECISION_RECEIPT_V1', value: signingBody }), 'utf8'), + publicKey, + Buffer.from(signature, 'hex'), + ) + if (!ok) fail('AUTHORITY_RECEIPT_SIGNATURE_INVALID') + } catch (error) { + if (error instanceof AuthorityResponseError) throw error + fail('AUTHORITY_RECEIPT_SIGNATURE_INVALID') + } + + const root = requiredString({ root: receiptRoot }, 'root', 'AUTHORITY_RECEIPT_ROOT_INVALID') + if (!HASH_RE.test(root)) fail('AUTHORITY_RECEIPT_ROOT_INVALID') + equal(root, canonicalHash('AEGIS_AUTHORITY_DECISION_RECEIPT_V1', receipt), 'AUTHORITY_RECEIPT_ROOT_MISMATCH') + return receipt as AuthorityDecisionReceipt +} + +function validateObservation(value: unknown): Record { + const observation = record(value, 'AUTHORITY_OBSERVATION_MALFORMED') + exactKeys(observation, OBSERVATION_KEYS, 'AUTHORITY_OBSERVATION_SCHEMA_DRIFT') + for (const key of ['declared_project', 'actual_cwd', 'resolved_repository_root', 'remote_origin', 'mutation_target']) { + requiredString(observation, key, `AUTHORITY_OBSERVATION_FIELD_INVALID:${key}`) + } + const views = record(observation['path_views'], 'AUTHORITY_OBSERVATION_PATH_VIEWS_INVALID') + if (Object.values(views).some((item) => typeof item !== 'string')) fail('AUTHORITY_OBSERVATION_PATH_VIEWS_INVALID') + return observation +} + +export function validateAuthorityResponse(value: unknown, expected: AuthorityResponseBindings): VerifiedAuthorityDecision { + const response = record(value, 'AUTHORITY_RESPONSE_MALFORMED') + exactKeys(response, RESPONSE_KEYS, 'AUTHORITY_RESPONSE_SCHEMA_DRIFT') + equal(response['schema_version'], '1.0.0', 'AUTHORITY_RESPONSE_SCHEMA_UNSUPPORTED') + equal(response['outcome'], 'ADMITTED', 'AUTHORITY_RESPONSE_NOT_ADMITTED') + equal(requiredHash(response, 'execution_identity_root', 'AUTHORITY_RESPONSE_IDENTITY_INVALID'), expected.expectedIdentityRoot, 'AUTHORITY_RESPONSE_IDENTITY_MISMATCH') + equal(requiredHash(response, 'workspace_binding', 'AUTHORITY_RESPONSE_WORKSPACE_INVALID'), expected.expectedWorkspaceBinding, 'AUTHORITY_RESPONSE_WORKSPACE_MISMATCH') + equal( + requiredHash(response, 'workspace_decision_root', 'AUTHORITY_RESPONSE_WORKSPACE_DECISION_INVALID'), + expected.expectedWorkspaceDecisionRoot, + 'AUTHORITY_RESPONSE_WORKSPACE_DECISION_MISMATCH', + ) + const decision = validatePolicyDecision(response['policy_decision'], expected) + const receipt = validateAuthorityReceipt(response['authority_receipt'], response['authority_receipt_root'], decision, expected) + const observation = validateObservation(response['observation']) + equal(observation['declared_project'], expected.expectedProjectIdentity, 'AUTHORITY_OBSERVATION_PROJECT_MISMATCH') + equal(canonicalGitHubRemote(observation['remote_origin'] as string), expected.expectedRepositoryIdentity, 'AUTHORITY_OBSERVATION_REMOTE_MISMATCH') + equal( + pythonCanonicalJson(observation), + pythonCanonicalJson(expected.expectedWorkspaceObservation), + 'AUTHORITY_OBSERVATION_BINDING_MISMATCH', + ) + equal(response['authority_receipt_root'], canonicalHash('AEGIS_AUTHORITY_DECISION_RECEIPT_V1', receipt), 'AUTHORITY_RESPONSE_RECEIPT_ROOT_MISMATCH') + return { + schema_version: '1.0.0', + outcome: 'ADMITTED', + execution_identity_root: response['execution_identity_root'] as string, + workspace_binding: response['workspace_binding'] as string, + workspace_decision_root: response['workspace_decision_root'] as string, + policy_decision: decision, + authority_receipt: receipt, + authority_receipt_root: response['authority_receipt_root'] as string, + observation, + } +} + +export function parseAuthorityProcessResult(result: AuthorityProcessResult, expected: AuthorityResponseBindings): VerifiedAuthorityDecision { + if (result.error || result.signal !== null && result.signal !== undefined || result.status !== 0) fail('AUTHORITY_PROCESS_FAILED') + if (typeof result.stdout !== 'string' || result.stdout.trim().length === 0) fail('AUTHORITY_RESPONSE_EMPTY') + let parsed: unknown + try { + parsed = JSON.parse(result.stdout) + } catch { + fail('AUTHORITY_RESPONSE_JSON_MALFORMED') + } + return validateAuthorityResponse(parsed, expected) +} diff --git a/sovereign-omega-v2/mcp-server/src/index.ts b/sovereign-omega-v2/mcp-server/src/index.ts index ca386ae93..26d456d16 100644 --- a/sovereign-omega-v2/mcp-server/src/index.ts +++ b/sovereign-omega-v2/mcp-server/src/index.ts @@ -12,6 +12,13 @@ import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' import { spawnSync } from 'node:child_process' import { createHash } from 'node:crypto' +import { + AuthorityResponseError, + buildAuthorityResponseBindings, + parseAuthorityProcessResult, + type ActionClass, + type VerifiedAuthorityDecision, +} from './authority-response.js' const BRIDGE = (process.env['AEGIS_BRIDGE_URL'] ?? 'http://localhost:7890').replace(/\/$/, '') const API_KEY = process.env['AEGIS_API_KEY'] ?? '' @@ -26,17 +33,6 @@ async function bridgeGet(path: string, apiKey = false): Promise { return res.json() } -async function bridgePost(path: string, body: unknown, apiKey = false): Promise { - const headers: Record = { 'Content-Type': 'application/json' } - if (apiKey && API_KEY) headers['X-API-Key'] = API_KEY - const res = await fetch(`${BRIDGE}${path}`, { method: 'POST', headers, body: JSON.stringify(body) }) - if (!res.ok) { - const err = await res.text().catch(() => `HTTP ${res.status}`) - throw new Error(`Bridge ${path} → ${err}`) - } - return res.json() -} - function text(content: unknown): { content: Array<{ type: 'text'; text: string }> } { return { content: [{ type: 'text', text: JSON.stringify(content, null, 2) }] } } @@ -52,44 +48,85 @@ function repoRoot(): string { return process.cwd() } -type AuthorityDecision = { - outcome: 'ADMITTED' | 'DENIED' - denial_codes?: string[] - mutation_receipt_root?: string - policy_decision?: { decision_root?: string } - [key: string]: unknown -} +type DeniedAuthorityDecision = { outcome: 'DENIED'; denial_codes: string[] } +type AuthorityDecision = VerifiedAuthorityDecision | DeniedAuthorityDecision function localDenial(code: string): AuthorityDecision { return { outcome: 'DENIED', denial_codes: [code] } } function authorizeAction(input: { - actionClass: 'D0' | 'D1' | 'D2' | 'D3' | 'D4' + actionClass: ActionClass authorityDomain: string requestedCapability: string tool: string target: string action: Record + rollbackReference?: string idempotencyKey?: string compensationReference?: string }): AuthorityDecision { const identityRaw = process.env['AEGIS_EXECUTION_IDENTITY_JSON'] if (!identityRaw) return localDenial('IDENTITY_UNAVAILABLE') let identity: unknown - let workspace: unknown = {} + let workspace: unknown let approval: unknown + let trustedAuthorityKeys: Record try { identity = JSON.parse(identityRaw) - workspace = JSON.parse(process.env['AEGIS_WORKSPACE_OBSERVATION_JSON'] ?? '{}') + const workspaceRaw = process.env['AEGIS_WORKSPACE_OBSERVATION_JSON'] + if (!workspaceRaw) return localDenial('WORKSPACE_OBSERVATION_UNAVAILABLE') + workspace = JSON.parse(workspaceRaw) + if (typeof workspace !== 'object' || workspace === null || Array.isArray(workspace)) { + return localDenial('WORKSPACE_OBSERVATION_MALFORMED') + } const approvalRaw = process.env['AEGIS_APPROVAL_GRANT_JSON'] approval = approvalRaw ? JSON.parse(approvalRaw) : undefined + const authorityKeysRaw = process.env['AEGIS_AUTHORITY_VERIFY_KEYS_JSON'] + if (!authorityKeysRaw) return localDenial('AUTHORITY_VERIFY_KEYS_UNAVAILABLE') + const parsedAuthorityKeys: unknown = JSON.parse(authorityKeysRaw) + if (typeof parsedAuthorityKeys !== 'object' || parsedAuthorityKeys === null || Array.isArray(parsedAuthorityKeys) + || Object.values(parsedAuthorityKeys).some((value) => typeof value !== 'string')) { + return localDenial('AUTHORITY_VERIFY_KEYS_MALFORMED') + } + trustedAuthorityKeys = parsedAuthorityKeys as Record } catch { return localDenial('AUTHORITY_ENVIRONMENT_MALFORMED') } + const root = repoRoot() + const sourceState = spawnSync('git', ['-C', root, 'rev-parse', 'HEAD'], { + encoding: 'utf8', timeout: 5_000, maxBuffer: 65_536, + }) + const remoteState = spawnSync('git', ['-C', root, 'config', '--get', 'remote.origin.url'], { + encoding: 'utf8', timeout: 5_000, maxBuffer: 65_536, + }) + if (sourceState.status !== 0 || sourceState.signal || sourceState.error) return localDenial('SOURCE_COMMIT_UNAVAILABLE') + if (remoteState.status !== 0 || remoteState.signal || remoteState.error) return localDenial('REMOTE_ORIGIN_UNAVAILABLE') + const workspaceRecord = workspace as Record + if (workspaceRecord['remote_origin'] !== remoteState.stdout.trim()) return localDenial('WORKSPACE_REMOTE_CLAIM_MISMATCH') + const boundWorkspace = { ...workspaceRecord, remote_origin: remoteState.stdout.trim() } as { + actual_cwd: string; remote_origin: string; mutation_target: string; path_views?: Record + } + let bindings + try { + bindings = buildAuthorityResponseBindings( + identity, + input, + boundWorkspace, + root, + trustedAuthorityKeys, + ) + } catch (error) { + return localDenial(error instanceof AuthorityResponseError ? error.code : 'EXECUTION_IDENTITY_MALFORMED') + } + if (sourceState.stdout.trim() !== bindings.expectedSourceCommit) return localDenial('SOURCE_COMMIT_MISMATCH') + const leaseGenerationRaw = process.env['AEGIS_LEASE_GENERATION'] ?? '0' + if (!/^(?:0|[1-9][0-9]*)$/.test(leaseGenerationRaw)) return localDenial('LEASE_GENERATION_MALFORMED') + const leaseGeneration = Number(leaseGenerationRaw) + if (!Number.isSafeInteger(leaseGeneration)) return localDenial('LEASE_GENERATION_MALFORMED') const payload = { identity, - workspace, + workspace: boundWorkspace, approval, action: input.action, request: { @@ -98,27 +135,47 @@ function authorizeAction(input: { requested_capability: input.requestedCapability, tool: input.tool, target: input.target, - current_generation: Number(process.env['AEGIS_LEASE_GENERATION'] ?? '0'), + workspace_mode: input.actionClass === 'D0' ? 'READ_ONLY' : 'REPOSITORY', + current_generation: leaseGeneration, + rollback_reference: input.rollbackReference ?? 'NONE', idempotency_key: input.idempotencyKey ?? 'NONE', compensation_reference: input.compensationReference ?? 'NONE', }, } const python = process.env['AEGIS_PYTHON'] ?? 'python3' - const script = join(repoRoot(), 'scripts', 'automaton3-authority.py') + const script = join(root, 'scripts', 'automaton3-authority.py') const result = spawnSync(python, [script, 'evaluate'], { - cwd: repoRoot(), input: JSON.stringify(payload), encoding: 'utf8', + cwd: root, input: JSON.stringify(payload), encoding: 'utf8', env: process.env, timeout: 15_000, maxBuffer: 1_048_576, }) - if (!result.stdout) return localDenial('AUTHORITY_SERVICE_UNAVAILABLE') try { - return JSON.parse(result.stdout) as AuthorityDecision - } catch { - return localDenial('AUTHORITY_RESPONSE_MALFORMED') + return parseAuthorityProcessResult({ + status: result.status, + signal: result.signal, + stdout: result.stdout, + stderr: result.stderr, + error: result.error, + }, bindings) + } catch (error) { + return localDenial(error instanceof AuthorityResponseError ? error.code : 'AUTHORITY_RESPONSE_MALFORMED') } } -function denied(decision: AuthorityDecision): { content: Array<{ type: 'text'; text: string }> } | null { - return decision.outcome === 'ADMITTED' ? null : text({ authority: decision, external_effect: 'NOT_EXECUTED' }) +function isDenied(decision: AuthorityDecision): decision is DeniedAuthorityDecision { + return decision.outcome === 'DENIED' +} + +function denialResponse(decision: DeniedAuthorityDecision): { content: Array<{ type: 'text'; text: string }> } { + return text({ authority: decision, external_effect: 'NOT_EXECUTED' }) +} + +function terminalAdapterUnavailable(authority: VerifiedAuthorityDecision) { + return text({ + authority, + outcome: 'DENIED', + denial_codes: ['TERMINAL_EXECUTION_ADAPTER_UNAVAILABLE'], + external_effect: 'NOT_EXECUTED', + }) } server.tool('aegis_health', 'Check AEGIS constitutional health: t0_verdict, corruption_count, hash chain status.', {}, async () => { @@ -131,37 +188,37 @@ server.tool('aegis_telemetry', 'Get live AEGIS telemetry: PGCS passes, epoch cou server.tool('aegis_platform_status', 'Get AEGIS platform status through a D0 authority decision.', {}, async () => { const authority = authorizeAction({ actionClass: 'D0', authorityDomain: 'mcp:read', requestedCapability: 'mcp.platform.status', tool: 'aegis_platform_status', target: '/platform/status', action: { operation: 'read', endpoint: '/platform/status' } }) - const denial = denied(authority); if (denial) return denial + if (isDenied(authority)) return denialResponse(authority) return text({ authority, result: await bridgeGet('/platform/status', true) }) }) server.tool( 'aegis_collaborate', 'Run the governed swarm. Requires API key, execution identity, observed capability, workspace binding, and D2 approval.', - { objective: z.string().min(10), mode: z.enum(['revenue', 'gtm', 'analysis', 'risk', 'compliance']).default('analysis') }, - async ({ objective, mode }) => { + { objective: z.string().min(10), mode: z.enum(['revenue', 'gtm', 'analysis', 'risk', 'compliance']).default('analysis'), rollback_reference: z.string().min(1) }, + async ({ objective, mode, rollback_reference }) => { if (!API_KEY) return text({ error: 'AEGIS_API_KEY not set', external_effect: 'NOT_EXECUTED' }) - const authority = authorizeAction({ actionClass: 'D2', authorityDomain: 'agent:shared-state', requestedCapability: 'mcp.collaborate', tool: 'aegis_collaborate', target: '/platform/collaborate', action: { operation: 'collaborate', objective, mode, live: false } }) - const denial = denied(authority); if (denial) return denial - return text({ authority, result: await bridgePost('/platform/collaborate', { objective, mode, live: false }, true) }) + const authority = authorizeAction({ actionClass: 'D2', authorityDomain: 'agent:shared-state', requestedCapability: 'mcp.collaborate', tool: 'aegis_collaborate', target: '/platform/collaborate', action: { operation: 'collaborate', objective, mode, live: false }, rollbackReference: rollback_reference }) + if (isDenied(authority)) return denialResponse(authority) + return terminalAdapterUnavailable(authority) }, ) server.tool( 'aegis_start_execution', 'Start a durable governed execution. Requires API key, identity, workspace binding, capability evidence, and D2 approval.', - { objective: z.string().min(10), mode: z.enum(['revenue', 'gtm', 'analysis', 'risk', 'compliance']).default('analysis') }, - async ({ objective, mode }) => { + { objective: z.string().min(10), mode: z.enum(['revenue', 'gtm', 'analysis', 'risk', 'compliance']).default('analysis'), rollback_reference: z.string().min(1) }, + async ({ objective, mode, rollback_reference }) => { if (!API_KEY) return text({ error: 'AEGIS_API_KEY not set', external_effect: 'NOT_EXECUTED' }) - const authority = authorizeAction({ actionClass: 'D2', authorityDomain: 'workflow:durable', requestedCapability: 'mcp.execution.start', tool: 'aegis_start_execution', target: '/platform/executions', action: { operation: 'start-execution', objective, mode, live: false } }) - const denial = denied(authority); if (denial) return denial - return text({ authority, result: await bridgePost('/platform/executions', { objective, mode, live: false }, true) }) + const authority = authorizeAction({ actionClass: 'D2', authorityDomain: 'workflow:durable', requestedCapability: 'mcp.execution.start', tool: 'aegis_start_execution', target: '/platform/executions', action: { operation: 'start-execution', objective, mode, live: false }, rollbackReference: rollback_reference }) + if (isDenied(authority)) return denialResponse(authority) + return terminalAdapterUnavailable(authority) }, ) server.tool('aegis_get_execution', 'Read a durable execution through a D0 authority decision.', { execution_id: z.string() }, async ({ execution_id }) => { const authority = authorizeAction({ actionClass: 'D0', authorityDomain: 'workflow:read', requestedCapability: 'mcp.execution.read', tool: 'aegis_get_execution', target: `/platform/executions/${execution_id}`, action: { operation: 'read-execution', execution_id } }) - const denial = denied(authority); if (denial) return denial + if (isDenied(authority)) return denialResponse(authority) return text({ authority, result: await bridgeGet(`/platform/executions/${execution_id}`, true) }) }) @@ -171,9 +228,9 @@ server.tool( { prompt: z.string().min(1), system: z.string().optional(), idempotency_key: z.string().min(1).optional(), compensation_reference: z.string().min(1).optional() }, async ({ prompt, system, idempotency_key, compensation_reference }) => { const body: Record = { prompt }; if (system) body['system'] = system - const authority = authorizeAction({ actionClass: 'D3', authorityDomain: 'external:model-call', requestedCapability: 'mcp.claude.call', tool: 'aegis_governed_claude_call', target: '/claude', action: { operation: 'governed-model-call', prompt_digest: createHash('sha256').update(prompt, 'utf8').digest('hex'), has_system: Boolean(system) }, idempotencyKey: idempotency_key, compensationReference: compensation_reference }) - const denial = denied(authority); if (denial) return denial - return text({ authority, result: await bridgePost('/claude', body) }) + const authority = authorizeAction({ actionClass: 'D3', authorityDomain: 'external:model-call', requestedCapability: 'mcp.claude.call', tool: 'aegis_governed_claude_call', target: '/claude', action: { operation: 'governed-model-call', prompt_digest: createHash('sha256').update(prompt, 'utf8').digest('hex'), system_digest: system === undefined ? '0'.repeat(64) : createHash('sha256').update(system, 'utf8').digest('hex'), provider_payload_digest: createHash('sha256').update(JSON.stringify(body), 'utf8').digest('hex'), has_system: system !== undefined }, idempotencyKey: idempotency_key, compensationReference: compensation_reference }) + if (isDenied(authority)) return denialResponse(authority) + return terminalAdapterUnavailable(authority) }, ) diff --git a/sovereign-omega-v2/mcp-server/test/authority-response.mjs b/sovereign-omega-v2/mcp-server/test/authority-response.mjs new file mode 100644 index 000000000..0b11b5ca8 --- /dev/null +++ b/sovereign-omega-v2/mcp-server/test/authority-response.mjs @@ -0,0 +1,378 @@ +import assert from 'node:assert/strict' +import { spawnSync } from 'node:child_process' +import { createPrivateKey, sign } from 'node:crypto' +import { readFileSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { + AuthorityResponseError, + buildAuthorityResponseBindings, + canonicalHash, + parseAuthorityProcessResult, + pythonCanonicalJson, + validateAuthorityResponse, +} from '../dist/authority-response.js' + +const H = (value) => value.repeat(64) +const authorityKeyId = 'authority-test-key' +const authorityPrivateSeed = '4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb' +const authorityPublicKey = '3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c' +const trustedAuthorityKeys = { [authorityKeyId]: authorityPublicKey } +const authorityPrivateKey = createPrivateKey({ + key: Buffer.concat([Buffer.from('302e020100300506032b657004220420', 'hex'), Buffer.from(authorityPrivateSeed, 'hex')]), + format: 'der', + type: 'pkcs8', +}) +const request = { + actionClass: 'D2', + authorityDomain: 'agent:shared-state', + requestedCapability: 'mcp.collaborate', + tool: 'aegis_collaborate', + target: '/platform/collaborate', + action: { + operation: 'collaborate', + objective: 'Exercise the governed MCP authority boundary', + mode: 'analysis', + live: false, + }, +} + +const identity = { + schema_version: '1.0.0', + repository_identity: 'https://github.com/Aegis-Omega/AEGIS-OMEGA.git', + repository_root: '.', + source_commit: 'a'.repeat(40), + branch_or_ref: 'refs/heads/test', + project_identity: 'aegis-omega', + workspace_root: '.', + workspace_binding: '', + parent_state_root: H('1'), + skills_root: H('2'), + registry_root: H('3'), + policy_root: H('4'), + actor_class: 'agent', + actor_identity: 'agent:test', + model_identity: 'model:test', + session_identity: 'session:test', + physical_executor: 'executor:test', + tool_identity: 'aegis_collaborate', + workflow_identity: 'workflow:test', + authority_domain: 'agent:shared-state', + requested_capability: 'mcp.collaborate', + observed_authority: 'observed:test', + approval_reference: 'approval:test', + input_digest: H('5'), + action_digest: canonicalHash('AEGIS_REQUESTED_ACTION_V1', request.action), + expected_pre_state: H('6'), + deterministic_nonce: 'nonce:test', +} +identity.workspace_binding = canonicalHash('AEGIS_WORKSPACE_BINDING_V1', { + repository_remote: identity.repository_identity, + repository_root: '.', + project_identity: identity.project_identity, + source_commit: identity.source_commit, + operator_authorization: identity.approval_reference, +}) + +const workspace = { + actual_cwd: '/workspace', + remote_origin: identity.repository_identity, + mutation_target: '/workspace', + path_views: {}, +} +const bindings = buildAuthorityResponseBindings(identity, request, workspace, '/workspace', trustedAuthorityKeys) + +// Golden values generated independently by harness.sdk.sovereign_execution.canonical_hash. +assert.equal(bindings.expectedActionDigest, '98b6a4697f2715d903595c3dcee31942e27b317544b718e3a1e0dedc8677ef45') +assert.equal(bindings.expectedWorkspaceBinding, '6f72562c79cc3e87e37318ae3be170804f5be777cfdc0d48f9d7a0d9c2990106') +assert.equal(bindings.expectedIdentityRoot, '25b5396131cb90e561c6809cb1f5973904ce19e4c20f3f8e7d8d37e2ae5fe1ba') +assert.equal(bindings.expectedTargetDigest, '000e1da64150ff19ced3233a3205c295f0591902528ce6f47b3f2ff764477db6') + +const modelAction = (system) => ({ + operation: 'governed-model-call', + prompt_digest: canonicalHash('TEST_PROMPT', 'prompt'), + system_digest: canonicalHash('TEST_SYSTEM', system), + provider_payload_digest: canonicalHash('TEST_PAYLOAD', { prompt: 'prompt', system }), + has_system: true, +}) +const modelRequest = (system) => ({ + actionClass: 'D3', + authorityDomain: 'external:model-call', + requestedCapability: 'mcp.claude.call', + tool: 'aegis_governed_claude_call', + target: '/claude', + action: modelAction(system), +}) +const modelIdentity = (system) => ({ + ...identity, + tool_identity: 'aegis_governed_claude_call', + authority_domain: 'external:model-call', + requested_capability: 'mcp.claude.call', + action_digest: canonicalHash('AEGIS_REQUESTED_ACTION_V1', modelAction(system)), +}) +const systemA = buildAuthorityResponseBindings(modelIdentity('system-a'), modelRequest('system-a'), workspace, '/workspace', trustedAuthorityKeys) +const systemB = buildAuthorityResponseBindings(modelIdentity('system-b'), modelRequest('system-b'), workspace, '/workspace', trustedAuthorityKeys) +assert.notEqual(systemA.expectedActionDigest, systemB.expectedActionDigest) + +function fixture() { + const decisionBody = { + schema_version: '1.0.0', + outcome: 'ADMITTED', + authority_score: '0.900000', + action_class: request.actionClass, + authority_domain: request.authorityDomain, + requested_capability: request.requestedCapability, + tool: request.tool, + target_digest: bindings.expectedTargetDigest, + identity_root: bindings.expectedIdentityRoot, + workspace_binding: bindings.expectedWorkspaceBinding, + registry_root: bindings.expectedRegistryRoot, + policy_root: bindings.expectedPolicyRoot, + approval_grant_root: H('a'), + denial_codes: [], + } + const policy_decision = { + ...decisionBody, + decision_root: canonicalHash('AEGIS_POLICY_DECISION_V1', decisionBody), + } + const authority_receipt = { + receipt_version: '1.0.0', + issuer_key_id: authorityKeyId, + execution_identity_root: bindings.expectedIdentityRoot, + source_commit: bindings.expectedSourceCommit, + workspace_binding: bindings.expectedWorkspaceBinding, + expected_pre_state: bindings.expectedPreState, + policy_decision_root: policy_decision.decision_root, + policy_root: bindings.expectedPolicyRoot, + skills_root: bindings.expectedSkillsRoot, + registry_root: bindings.expectedRegistryRoot, + approval_grant_root: policy_decision.approval_grant_root, + authority_score: policy_decision.authority_score, + authority_domain: request.authorityDomain, + action_class: request.actionClass, + requested_capability: request.requestedCapability, + tool: request.tool, + target: bindings.expectedTargetDigest, + requested_action_digest: bindings.expectedActionDigest, + outcome: 'ADMITTED', + denial_codes: [], + signature: '', + } + const signingBody = { ...authority_receipt } + delete signingBody.signature + authority_receipt.signature = sign( + null, + Buffer.from(pythonCanonicalJson({ domain: 'AEGIS_AUTHORITY_DECISION_RECEIPT_V1', value: signingBody }), 'utf8'), + authorityPrivateKey, + ).toString('hex') + return { + schema_version: '1.0.0', + outcome: 'ADMITTED', + execution_identity_root: bindings.expectedIdentityRoot, + workspace_binding: bindings.expectedWorkspaceBinding, + workspace_decision_root: bindings.expectedWorkspaceDecisionRoot, + policy_decision, + authority_receipt, + authority_receipt_root: canonicalHash('AEGIS_AUTHORITY_DECISION_RECEIPT_V1', authority_receipt), + observation: bindings.expectedWorkspaceObservation, + } +} + +function clone(value) { + return JSON.parse(JSON.stringify(value)) +} + +function code(expected) { + return (error) => error instanceof AuthorityResponseError && error.code === expected +} + +const valid = fixture() +assert.equal(valid.policy_decision.decision_root, '9d7ee0746dd1e741a35ae037e8909d92ba4871a4c858d0f8f1164ce429d7e85d') +assert.match(valid.authority_receipt_root, /^[0-9a-f]{64}$/) +assert.equal(validateAuthorityResponse(valid, bindings).authority_receipt_root, valid.authority_receipt_root) +assert.equal( + parseAuthorityProcessResult({ status: 0, signal: null, stdout: JSON.stringify(valid) }, bindings).outcome, + 'ADMITTED', +) + +assert.throws( + () => parseAuthorityProcessResult({ status: 3, signal: null, stdout: JSON.stringify(valid) }, bindings), + code('AUTHORITY_PROCESS_FAILED'), +) +assert.throws( + () => parseAuthorityProcessResult({ status: 0, signal: 'SIGTERM', stdout: JSON.stringify(valid) }, bindings), + code('AUTHORITY_PROCESS_FAILED'), +) +assert.throws( + () => parseAuthorityProcessResult({ status: 0, signal: null, stdout: '{' }, bindings), + code('AUTHORITY_RESPONSE_JSON_MALFORMED'), +) + +const missingReceiptField = clone(valid) +delete missingReceiptField.authority_receipt.requested_action_digest +assert.throws(() => validateAuthorityResponse(missingReceiptField, bindings), code('AUTHORITY_RECEIPT_SCHEMA_DRIFT')) + +const extraResponseField = clone(valid) +extraResponseField.untrusted = true +assert.throws(() => validateAuthorityResponse(extraResponseField, bindings), code('AUTHORITY_RESPONSE_SCHEMA_DRIFT')) + +const receiptRootMismatch = clone(valid) +receiptRootMismatch.authority_receipt_root = H('8') +assert.throws(() => validateAuthorityResponse(receiptRootMismatch, bindings), code('AUTHORITY_RECEIPT_ROOT_MISMATCH')) + +const workspaceDecisionMismatch = clone(valid) +workspaceDecisionMismatch.workspace_decision_root = H('8') +assert.throws(() => validateAuthorityResponse(workspaceDecisionMismatch, bindings), code('AUTHORITY_RESPONSE_WORKSPACE_DECISION_MISMATCH')) + +const workspaceObservationMismatch = clone(valid) +workspaceObservationMismatch.observation.mutation_target = '/different' +assert.throws(() => validateAuthorityResponse(workspaceObservationMismatch, bindings), code('AUTHORITY_OBSERVATION_BINDING_MISMATCH')) + +const signatureMismatch = clone(valid) +signatureMismatch.authority_receipt.signature = '00'.repeat(64) +signatureMismatch.authority_receipt_root = canonicalHash('AEGIS_AUTHORITY_DECISION_RECEIPT_V1', signatureMismatch.authority_receipt) +assert.throws(() => validateAuthorityResponse(signatureMismatch, bindings), code('AUTHORITY_RECEIPT_SIGNATURE_INVALID')) + +const actionMismatch = clone(valid) +actionMismatch.authority_receipt.requested_action_digest = H('9') +actionMismatch.authority_receipt_root = canonicalHash('AEGIS_AUTHORITY_DECISION_RECEIPT_V1', actionMismatch.authority_receipt) +assert.throws(() => validateAuthorityResponse(actionMismatch, bindings), code('AUTHORITY_RECEIPT_ACTION_MISMATCH')) + +const selfConsistentWrongPolicy = clone(valid) +selfConsistentWrongPolicy.policy_decision.policy_root = H('b') +const changedDecisionBody = { ...selfConsistentWrongPolicy.policy_decision } +delete changedDecisionBody.decision_root +selfConsistentWrongPolicy.policy_decision.decision_root = canonicalHash('AEGIS_POLICY_DECISION_V1', changedDecisionBody) +selfConsistentWrongPolicy.authority_receipt.policy_root = H('b') +selfConsistentWrongPolicy.authority_receipt.policy_decision_root = selfConsistentWrongPolicy.policy_decision.decision_root +selfConsistentWrongPolicy.authority_receipt_root = canonicalHash( + 'AEGIS_AUTHORITY_DECISION_RECEIPT_V1', + selfConsistentWrongPolicy.authority_receipt, +) +assert.throws(() => validateAuthorityResponse(selfConsistentWrongPolicy, bindings), code('AUTHORITY_POLICY_DECISION_POLICY_MISMATCH')) + +const decisionReceiptMismatch = clone(valid) +decisionReceiptMismatch.authority_receipt.authority_score = '0.800000' +decisionReceiptMismatch.authority_receipt_root = canonicalHash( + 'AEGIS_AUTHORITY_DECISION_RECEIPT_V1', + decisionReceiptMismatch.authority_receipt, +) +assert.throws(() => validateAuthorityResponse(decisionReceiptMismatch, bindings), code('AUTHORITY_RECEIPT_SCORE_MISMATCH')) + +const admittedWithDenial = clone(valid) +admittedWithDenial.authority_receipt.denial_codes = ['SHOULD_NOT_EXIST'] +admittedWithDenial.authority_receipt_root = canonicalHash( + 'AEGIS_AUTHORITY_DECISION_RECEIPT_V1', + admittedWithDenial.authority_receipt, +) +assert.throws(() => validateAuthorityResponse(admittedWithDenial, bindings), code('ADMITTED_AUTHORITY_RECEIPT_HAS_DENIAL_CODES')) + +const outOfRangeScore = clone(valid) +outOfRangeScore.policy_decision.authority_score = '1.000001' +assert.throws(() => validateAuthorityResponse(outOfRangeScore, bindings), code('AUTHORITY_POLICY_DECISION_SCORE_INVALID')) + +// Exercise the real Python producer and validate its complete emitted response. +const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..') +const python = process.env.AEGIS_PYTHON ?? (process.platform === 'win32' ? 'python' : 'python3') +const sourceCommitResult = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: repoRoot, encoding: 'utf8' }) +assert.equal(sourceCommitResult.status, 0) +const remoteResult = spawnSync('git', ['config', '--get', 'remote.origin.url'], { cwd: repoRoot, encoding: 'utf8' }) +assert.equal(remoteResult.status, 0) +const actualRemote = remoteResult.stdout.trim() +let canonicalRemote = actualRemote +if (canonicalRemote.startsWith('git@github.com:')) canonicalRemote = `https://github.com/${canonicalRemote.slice('git@github.com:'.length)}` +if (canonicalRemote.startsWith('ssh://git@github.com/')) canonicalRemote = `https://github.com/${canonicalRemote.slice('ssh://git@github.com/'.length)}` +if (!canonicalRemote.endsWith('.git')) canonicalRemote += '.git' +const policy = JSON.parse(readFileSync(join(repoRoot, 'harness', 'policies', 'consequence-policy.v1.json'), 'utf8')) +const registry = JSON.parse(readFileSync(join(repoRoot, 'harness', 'skill_tree.json'), 'utf8')) +const capabilityMap = JSON.parse(readFileSync(join(repoRoot, 'harness', 'policies', 'capability-map.v1.json'), 'utf8')) +const runtimeSkillsRoot = registry.registry_root +const runtimeRegistryRoot = canonicalHash('AEGIS_CAPABILITY_REGISTRY_V1', { + skills_root: runtimeSkillsRoot, + capability_map: capabilityMap, +}) +const runtimeRequest = { + actionClass: 'D0', + authorityDomain: 'mcp:read', + requestedCapability: 'mcp.platform.status', + tool: 'aegis_platform_status', + target: '/platform/status', + action: { operation: 'read', endpoint: '/platform/status' }, +} +const runtimeIdentity = { + schema_version: '1.0.0', + repository_identity: canonicalRemote, + repository_root: '.', + source_commit: sourceCommitResult.stdout.trim(), + branch_or_ref: 'refs/heads/mcp-authority-test', + project_identity: 'AEGIS-OMEGA', + workspace_root: '.', + workspace_binding: '', + parent_state_root: H('1'), + skills_root: runtimeSkillsRoot, + registry_root: runtimeRegistryRoot, + policy_root: canonicalHash('AEGIS_CONSEQUENCE_POLICY_V1', policy.classes), + actor_class: 'test-agent', + actor_identity: 'agent:mcp-authority-test', + model_identity: 'model:none', + session_identity: 'session:mcp-authority-test', + physical_executor: 'executor:mcp-authority-test', + tool_identity: runtimeRequest.tool, + workflow_identity: 'workflow:mcp-authority-test', + authority_domain: runtimeRequest.authorityDomain, + requested_capability: runtimeRequest.requestedCapability, + observed_authority: '0.000000', + approval_reference: 'NONE', + input_digest: H('5'), + action_digest: canonicalHash('AEGIS_REQUESTED_ACTION_V1', runtimeRequest.action), + expected_pre_state: H('6'), + deterministic_nonce: 'nonce:mcp-authority-test', +} +runtimeIdentity.workspace_binding = canonicalHash('AEGIS_WORKSPACE_BINDING_V1', { + repository_remote: runtimeIdentity.repository_identity, + repository_root: '.', + project_identity: runtimeIdentity.project_identity, + source_commit: runtimeIdentity.source_commit, + operator_authorization: runtimeIdentity.approval_reference, +}) +const runtimeWorkspace = { + actual_cwd: repoRoot, + remote_origin: actualRemote, + mutation_target: repoRoot, + path_views: {}, +} +const runtimeBindings = buildAuthorityResponseBindings(runtimeIdentity, runtimeRequest, runtimeWorkspace, repoRoot, trustedAuthorityKeys) +const runtimePayload = { + identity: runtimeIdentity, + workspace: runtimeWorkspace, + action: runtimeRequest.action, + request: { + action_class: runtimeRequest.actionClass, + authority_domain: runtimeRequest.authorityDomain, + requested_capability: runtimeRequest.requestedCapability, + tool: runtimeRequest.tool, + target: runtimeRequest.target, + workspace_mode: 'READ_ONLY', + current_generation: 0, + idempotency_key: 'NONE', + compensation_reference: 'NONE', + }, +} +const runtimeResult = spawnSync(python, [join(repoRoot, 'scripts', 'automaton3-authority.py'), 'evaluate'], { + cwd: repoRoot, + input: JSON.stringify(runtimePayload), + encoding: 'utf8', + timeout: 15_000, + maxBuffer: 1_048_576, + env: { + ...process.env, + AEGIS_AUTHORITY_ISSUER_KEY_ID: authorityKeyId, + AEGIS_AUTHORITY_SIGNING_KEY_HEX: authorityPrivateSeed, + AEGIS_AUTHORITY_VERIFY_KEYS_JSON: JSON.stringify(trustedAuthorityKeys), + AEGIS_TRUSTED_OPERATOR_KEYS_JSON: '{}', + }, +}) +assert.equal(runtimeResult.status, 0, runtimeResult.stderr || runtimeResult.stdout) +assert.equal(parseAuthorityProcessResult(runtimeResult, runtimeBindings).outcome, 'ADMITTED') + +console.log('AUTHORITY_RESPONSE_PASS strict status, schema, binding, receipt roots, and Python producer parity') diff --git a/sovereign-omega-v2/mcp-server/test/automaton3-authority.mjs b/sovereign-omega-v2/mcp-server/test/automaton3-authority.mjs index 9256e6f9f..267274a76 100644 --- a/sovereign-omega-v2/mcp-server/test/automaton3-authority.mjs +++ b/sovereign-omega-v2/mcp-server/test/automaton3-authority.mjs @@ -20,7 +20,7 @@ const transport = new StdioClientTransport({ const client = new Client({ name: 'automaton3-mcp-test', version: '1.0.0' }) try { await client.connect(transport) - const result = await client.callTool({ name: 'aegis_collaborate', arguments: { objective: 'Attempt a consequential collaboration', mode: 'analysis' } }) + const result = await client.callTool({ name: 'aegis_collaborate', arguments: { objective: 'Attempt a consequential collaboration', mode: 'analysis', rollback_reference: 'rollback:test' } }) const parsed = JSON.parse(result.content[0].text) assert.equal(parsed.external_effect, 'NOT_EXECUTED') assert.equal(parsed.authority.outcome, 'DENIED') diff --git a/sovereign-omega-v2/python/tests/test_automaton3.py b/sovereign-omega-v2/python/tests/test_automaton3.py index c7c46b9e5..6542b9483 100644 --- a/sovereign-omega-v2/python/tests/test_automaton3.py +++ b/sovereign-omega-v2/python/tests/test_automaton3.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import subprocess import sys from dataclasses import replace from pathlib import Path @@ -18,18 +19,21 @@ DurableExecutionRecord, DurableExecutionRegistry, EventEnvelope, ExecutionIdentityEnvelope, MutationReceipt, ReceiptChain, SovereignExecutionError, WriterLeaseManager, canonical_bytes, canonical_hash, - compute_workspace_binding, verify_workspace, + compute_skill_registry_root, compute_workspace_binding, load_capability_registry, + load_capability_registry_from_commit, load_policy, load_policy_from_commit, + make_authority_decision_receipt, make_terminal_mutation_receipt, + verify_live_authority_roots, verify_workspace, _ed25519_sign, ) REMOTE = "https://github.com/Aegis-Omega/AEGIS-OMEGA.git" COMMIT = "a" * 40 HASH = "1" * 64 POLICY = { - "D0": {"minimum_validated_runs": 0, "approval": "NONE", "external_idempotency": False}, - "D1": {"minimum_validated_runs": 3, "approval": "NONE", "external_idempotency": False}, - "D2": {"minimum_validated_runs": 3, "approval": "EXPLICIT", "external_idempotency": False}, - "D3": {"minimum_validated_runs": 3, "approval": "EXPLICIT", "external_idempotency": True}, - "D4": {"minimum_validated_runs": 3, "approval": "EXPLICIT", "external_idempotency": True}, + "D0": {"minimum_validated_runs": 0, "approval": "NONE", "workspace": "READ_ONLY", "replay": False, "rollback": "NONE", "external_idempotency": False}, + "D1": {"minimum_validated_runs": 3, "approval": "NONE", "workspace": "REPOSITORY", "replay": True, "rollback": "REQUIRED", "external_idempotency": False}, + "D2": {"minimum_validated_runs": 3, "approval": "EXPLICIT", "workspace": "REPOSITORY", "replay": True, "rollback": "REQUIRED", "external_idempotency": False}, + "D3": {"minimum_validated_runs": 3, "approval": "EXPLICIT", "workspace": "REPOSITORY", "replay": True, "rollback": "COMPENSATION_OR_IDEMPOTENCY", "external_idempotency": True}, + "D4": {"minimum_validated_runs": 3, "approval": "EXPLICIT", "workspace": "REPOSITORY", "replay": True, "rollback": "COMPENSATION_OR_IDEMPOTENCY", "external_idempotency": True}, } @@ -43,13 +47,25 @@ def setUp(self) -> None: self.binding = compute_workspace_binding(repository_remote=REMOTE, repository_root=".", project_identity="AEGIS-OMEGA", source_commit=COMMIT, operator_authorization=self.approval_ref) self.registry_root = "2" * 64 self.policy_root = canonical_hash("AEGIS_CONSEQUENCE_POLICY_V1", POLICY) + self.operator_key_id = "operator-test-key" + self.operator_private_key = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" + self.operator_public_key = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" + self.authority_key_id = "authority-test-key" + self.authority_private_key = "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb" + self.authority_public_key = "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c" self.identity = self.make_identity() self.capability = CapabilityEvidence( capability="repository.mutate", skill_id="gate8_deployment_gate", observation_state="OBSERVED", validated_runs=3, confidence_micros=900_000, recency_micros=900_000, failure_rate_micros=0, evidence_refs=("evidence/run.json",), allowed_action_classes=(D1, D2, D4), allowed_tools=("git",), ) - self.evaluator = AuthorityEvaluator(policy=POLICY, registry={"repository.mutate": self.capability}, repository_root=self.root) + self.evaluator = AuthorityEvaluator( + policy=POLICY, + registry={"repository.mutate": self.capability}, + repository_root=self.root, + trusted_operator_keys={self.operator_key_id: self.operator_public_key}, + allow_working_tree_evidence_for_tests=True, + ) def make_identity(self, **changes) -> ExecutionIdentityEnvelope: action = {"operation": "write", "target": "docs/test.md"} @@ -69,16 +85,24 @@ def make_identity(self, **changes) -> ExecutionIdentityEnvelope: def request(self, **changes) -> AuthorityRequest: values = dict(action_class=D2, authority_domain="github:contents", requested_capability="repository.mutate", tool="git", target="docs/test.md", identity_root=self.identity.root, workspace_binding=self.binding, source_commit=COMMIT, - registry_root=self.registry_root, policy_root=self.policy_root, current_generation=1, - approval_reference=self.approval_ref) + registry_root=self.registry_root, policy_root=self.policy_root, + action_digest=self.identity.action_digest, expected_pre_state=self.identity.expected_pre_state, + workspace_mode="REPOSITORY", current_generation=1, + approval_reference=self.approval_ref, rollback_reference="rollback:test") values.update(changes) return AuthorityRequest(**values) def approval(self, **changes) -> ApprovalGrant: - values = dict(reference=self.approval_ref, authority_domain="github:contents", action_class=D2, source_commit=COMMIT, - workspace_binding=self.binding, valid_through_generation=2, signature_root="4"*64) + request = self.request() + values = dict(schema_version=SCHEMA_VERSION, reference=self.approval_ref, issuer_key_id=self.operator_key_id, + operator_identity="operator:test", authority_domain="github:contents", action_class=D2, source_commit=COMMIT, + workspace_binding=self.binding, policy_root=self.policy_root, registry_root=self.registry_root, + identity_root=self.identity.root, action_digest=self.identity.action_digest, + target_digest=canonical_hash("AEGIS_AUTHORITY_TARGET_V1", request.target), + requested_capability=request.requested_capability, valid_through_generation=2, state="APPROVED") values.update(changes) - return ApprovalGrant(**values) + signature = _ed25519_sign(private_key_hex=self.operator_private_key, domain="AEGIS_APPROVAL_GRANT_V1", value=values) + return ApprovalGrant(**values, signature=signature) def assertDenied(self, decision, code: str) -> None: self.assertEqual(decision.outcome, DENIED); self.assertEqual(decision.authority_score, "0.000000"); self.assertIn(code, decision.denial_codes) @@ -105,6 +129,24 @@ def test_05_malformed_evidence(self): def test_06_evidence_outside_repository(self): ev = replace(self.capability, evidence_refs=("../escape",)) self.assertDenied(AuthorityEvaluator(policy=POLICY, registry={"repository.mutate": ev}, repository_root=self.root).evaluate(self.request(), approval=self.approval()), "EVIDENCE_OUTSIDE_REPOSITORY") + (self.root / "tracked.txt").write_text("tracked\n", encoding="utf-8") + subprocess.run(["git", "init", "-q", str(self.root)], check=True) + subprocess.run(["git", "-C", str(self.root), "add", "tracked.txt"], check=True) + subprocess.run( + ["git", "-C", str(self.root), "-c", "user.name=AEGIS Test", "-c", "user.email=aegis@example.invalid", "commit", "-qm", "evidence baseline"], + check=True, + ) + source_commit = subprocess.run( + ["git", "-C", str(self.root), "rev-parse", "HEAD"], check=True, capture_output=True, text=True, + ).stdout.strip() + self.assertTrue((self.root / "evidence" / "run.json").is_file()) + decision = AuthorityEvaluator( + policy=POLICY, + registry={"repository.mutate": self.capability}, + repository_root=self.root, + trusted_operator_keys={self.operator_key_id: self.operator_public_key}, + ).evaluate(self.request(source_commit=source_commit), approval=self.approval()) + self.assertDenied(decision, "EVIDENCE_UNRESOLVED") def test_07_mismatched_source_commit(self): self.assertDenied(self.evaluator.evaluate(self.request(source_commit="b"*40), approval=self.approval()), "APPROVAL_SOURCE_COMMIT_MISMATCH") @@ -112,6 +154,137 @@ def test_07_mismatched_source_commit(self): def test_08_mismatched_skills_root(self): with self.assertRaisesRegex(SovereignExecutionError, "skills_root:INVALID_SHA256"): self.make_identity(skills_root="bad").root + registry_dir = self.root / "harness" + policy_dir = registry_dir / "policies" + policy_dir.mkdir(parents=True) + tree = { + "schema_version": "2.0.0", + "skills": [{ + "skill_id": "gate8_deployment_gate", + "observation_state": "OBSERVED", + "validated_runs": 3, + "confidence": 0.9, + "recency_score": 0.9, + "failure_rate": 0.0, + "evidence_refs": ["evidence/run.json"], + }], + } + committed_skills_root = compute_skill_registry_root(tree) + tree.update(registry_root=committed_skills_root, genesis_seal=committed_skills_root) + capability_map = { + "schema_version": SCHEMA_VERSION, + "capabilities": { + "repository.mutate": { + "skill_id": "gate8_deployment_gate", + "allowed_action_classes": [D2], + "allowed_tools": ["git"], + }, + }, + } + consequence_policy = {"schema_version": SCHEMA_VERSION, "classes": POLICY} + skill_path = registry_dir / "skill_tree.json" + map_path = policy_dir / "capability-map.v1.json" + policy_path = policy_dir / "consequence-policy.v1.json" + for path, value in ((skill_path, tree), (map_path, capability_map), (policy_path, consequence_policy)): + path.write_text(json.dumps(value, sort_keys=True), encoding="utf-8") + subprocess.run(["git", "init", "-q", str(self.root)], check=True) + subprocess.run(["git", "-C", str(self.root), "add", "harness"], check=True) + subprocess.run( + ["git", "-C", str(self.root), "-c", "user.name=AEGIS Test", "-c", "user.email=aegis@example.invalid", "commit", "-qm", "registry baseline"], + check=True, + ) + source_commit = subprocess.run( + ["git", "-C", str(self.root), "rev-parse", "HEAD"], check=True, capture_output=True, text=True, + ).stdout.strip() + _, live_skills_root, live_registry_root = load_capability_registry_from_commit( + repository_root=self.root, + source_commit=source_commit, + skill_tree_path="harness/skill_tree.json", + capability_map_path="harness/policies/capability-map.v1.json", + ) + _, live_policy_root = load_policy_from_commit( + repository_root=self.root, + source_commit=source_commit, + policy_path="harness/policies/consequence-policy.v1.json", + ) + self.assertEqual(live_skills_root, committed_skills_root) + + dirty_policy = json.loads(json.dumps(consequence_policy)) + dirty_policy["classes"]["D0"]["minimum_validated_runs"] = 999 + policy_path.write_text(json.dumps(dirty_policy, sort_keys=True), encoding="utf-8") + _, dirty_policy_root = load_policy(policy_path) + self.assertNotEqual(dirty_policy_root, live_policy_root) + with self.assertRaisesRegex(SovereignExecutionError, "POLICY_ROOT_MISMATCH"): + verify_live_authority_roots( + self.make_identity( + skills_root=live_skills_root, + registry_root=live_registry_root, + policy_root=dirty_policy_root, + ), + skills_root=live_skills_root, + registry_root=live_registry_root, + policy_root=live_policy_root, + ) + _, reloaded_policy_root = load_policy_from_commit( + repository_root=self.root, + source_commit=source_commit, + policy_path="harness/policies/consequence-policy.v1.json", + ) + self.assertEqual(reloaded_policy_root, live_policy_root) + + capability_map["capabilities"]["repository.mutate"]["allowed_tools"] = ["github"] + map_path.write_text(json.dumps(capability_map, sort_keys=True), encoding="utf-8") + _, dirty_map_skills_root, dirty_map_registry_root = load_capability_registry( + repository_root=self.root, + skill_tree_path=skill_path, + capability_map_path=map_path, + ) + self.assertEqual(dirty_map_skills_root, live_skills_root) + self.assertNotEqual(dirty_map_registry_root, live_registry_root) + dirty_map_identity = self.make_identity( + skills_root=dirty_map_skills_root, + registry_root=dirty_map_registry_root, + policy_root=live_policy_root, + ) + with self.assertRaisesRegex(SovereignExecutionError, "CAPABILITY_REGISTRY_ROOT_MISMATCH"): + verify_live_authority_roots( + dirty_map_identity, + skills_root=live_skills_root, + registry_root=live_registry_root, + policy_root=live_policy_root, + ) + + tree["skills"][0]["confidence"] = 0.8 + dirty_skills_root = compute_skill_registry_root(tree) + tree.update(registry_root=dirty_skills_root, genesis_seal=dirty_skills_root) + skill_path.write_text(json.dumps(tree, sort_keys=True), encoding="utf-8") + _, dirty_tree_skills_root, dirty_tree_registry_root = load_capability_registry( + repository_root=self.root, + skill_tree_path=skill_path, + capability_map_path=map_path, + ) + self.assertNotEqual(dirty_tree_skills_root, live_skills_root) + dirty_tree_identity = self.make_identity( + skills_root=dirty_tree_skills_root, + registry_root=dirty_tree_registry_root, + policy_root=live_policy_root, + ) + with self.assertRaisesRegex(SovereignExecutionError, "SKILLS_ROOT_MISMATCH"): + verify_live_authority_roots( + dirty_tree_identity, + skills_root=live_skills_root, + registry_root=live_registry_root, + policy_root=live_policy_root, + ) + + # The commit-bound loader remains anchored even while both checkout files are dirty. + _, reloaded_skills_root, reloaded_registry_root = load_capability_registry_from_commit( + repository_root=self.root, + source_commit=source_commit, + skill_tree_path="harness/skill_tree.json", + capability_map_path="harness/policies/capability-map.v1.json", + ) + self.assertEqual((reloaded_skills_root, reloaded_registry_root), (live_skills_root, live_registry_root)) def test_09_mismatched_parent_state(self): with self.assertRaisesRegex(SovereignExecutionError, "parent_state_root:INVALID_SHA256"): @@ -134,16 +307,157 @@ def test_12_stale_writer_lease(self): def test_13_replayed_fencing_token(self): manager = WriterLeaseManager(); lease, _ = manager.acquire(authority_domain="git", holder_identity_root=HASH, source_commit=COMMIT, expected_parent_state=ZERO_HASH) kwargs=dict(authority_domain="git", holder_identity_root=HASH, fencing_token=lease.fencing_token, lease_generation=lease.lease_generation, expected_parent_state=ZERO_HASH, action_digest="8"*64) - self.assertEqual(manager.authorize_write(**kwargs).outcome, ADMITTED) - self.assertIn("REPLAYED_AUTHORITATIVE_ACTION", manager.authorize_write(**kwargs).denial_codes) + first = manager.authorize_write(**kwargs) + second = manager.authorize_write(**kwargs) + self.assertEqual(first.outcome, ADMITTED) + self.assertEqual(first, second) def durable(self): manager=WriterLeaseManager(); lease,_=manager.acquire(authority_domain="external", holder_identity_root=HASH, source_commit=COMMIT, expected_parent_state=ZERO_HASH) registry=DurableExecutionRegistry(manager) record=DurableExecutionRecord("wf","operator",COMMIT,self.binding,"plan",("external",),0,"",0,None,"ACTIVE",HASH,ZERO_HASH,ZERO_HASH,"","PLANNED",0) - registry.register("exec",record); registry.transition("exec",status="RUNNING",phase="execute",transition_sequence=1,receipt_root=HASH) + registry.register("exec",record); registry.transition("exec",status="ADMITTED",phase="admitted",transition_sequence=1,receipt_root=HASH); registry.transition("exec",status="RUNNING",phase="execute",transition_sequence=2,receipt_root="2"*64) return manager,registry,lease + def authority_receipt(self, decision, request=None): + request = request or self.request() + return make_authority_decision_receipt( + identity=self.identity, + request=request, + decision=decision, + evaluator=self.evaluator, + issuer_key_id=self.authority_key_id, + issuer_private_key_hex=self.authority_private_key, + ) + + def terminal_context(self): + request = self.request() + decision = self.evaluator.evaluate(request, approval=self.approval()) + authority_receipt = self.authority_receipt(decision, request) + manager = WriterLeaseManager() + lease, _ = manager.acquire( + authority_domain=request.authority_domain, + holder_identity_root=self.identity.root, + source_commit=COMMIT, + expected_parent_state=self.identity.expected_pre_state, + ) + self.assertIsNotNone(lease) + lease_receipt = manager.authorize_write( + authority_domain=request.authority_domain, + holder_identity_root=self.identity.root, + fencing_token=lease.fencing_token, + lease_generation=lease.lease_generation, + expected_parent_state=self.identity.expected_pre_state, + action_digest=self.identity.action_digest, + ) + registry = DurableExecutionRegistry(manager) + registry.register("terminal-exec", DurableExecutionRecord( + "terminal-workflow", "operator", COMMIT, self.binding, "plan", + (request.authority_domain,), 0, "", 0, None, "ACTIVE", + self.identity.root, self.identity.expected_pre_state, ZERO_HASH, "", "PLANNED", 0, + )) + registry.transition("terminal-exec", status="ADMITTED", phase="admitted", transition_sequence=1, receipt_root=authority_receipt.root) + registry.transition("terminal-exec", status="RUNNING", phase="execute", transition_sequence=2, receipt_root=lease_receipt.receipt_root) + return request, decision, authority_receipt, manager, lease_receipt, registry, ReceiptChain() + + def terminal_receipt(self, *, terminal_outcome="SUCCEEDED", denial_code="NONE", post_state_digest="8" * 64, result=None): + context = self.terminal_context() + receipt = self.commit_terminal_context( + context, + terminal_outcome=terminal_outcome, + denial_code=denial_code, + post_state_digest=post_state_digest, + result=result, + ) + return receipt, context[5], context[6] + + def commit_terminal_context(self, context, *, terminal_outcome="SUCCEEDED", denial_code="NONE", post_state_digest="8" * 64, result=None): + request, decision, authority_receipt, manager, lease_receipt, registry, chain = context + if terminal_outcome != "SUCCEEDED": + post_state_digest = self.identity.expected_pre_state + receipt = make_terminal_mutation_receipt( + identity=self.identity, + request=request, + decision=decision, + evaluator=self.evaluator, + authority_receipt=authority_receipt, + trusted_authority_keys={self.authority_key_id: self.authority_public_key}, + lease_manager=manager, + lease_authorization_receipt=lease_receipt, + durable_registry=registry, + execution_id="terminal-exec", + receipt_chain=chain, + result=result or {"status": terminal_outcome.lower()}, + post_state_digest=post_state_digest, + terminal_outcome=terminal_outcome, + denial_code=denial_code, + ) + return receipt + + def test_signed_approval_tamper_denied(self): + approval = replace(self.approval(), signature="00" * 64) + self.assertDenied(self.evaluator.evaluate(self.request(), approval=approval), "APPROVAL_SIGNATURE_INVALID") + + def test_d2_missing_rollback_denied(self): + self.assertDenied( + self.evaluator.evaluate(self.request(rollback_reference="NONE"), approval=self.approval()), + "ROLLBACK_REFERENCE_REQUIRED", + ) + + def test_terminal_rejects_revoked_lease_without_state_change(self): + context = self.terminal_context() + manager, registry, chain = context[3], context[5], context[6] + manager.revoke(self.request().authority_domain, self.identity.root) + with self.assertRaisesRegex(SovereignExecutionError, "LEASE_NO_LONGER_CURRENT"): + self.commit_terminal_context(context) + self.assertEqual(registry.get("terminal-exec").status, "RUNNING") + self.assertEqual(chain.verify(), ZERO_HASH) + + def test_terminal_consumes_authorization_once_and_revokes_writer(self): + context = self.terminal_context() + receipt = self.commit_terminal_context(context) + manager, lease_receipt = context[3], context[4] + self.assertIsNone(manager.current(self.request().authority_domain)) + with self.assertRaisesRegex(SovereignExecutionError, "LEASE_AUTHORIZATION_ALREADY_CONSUMED"): + manager.consume_authorization(lease_receipt) + self.assertRegex(receipt.root, r"^[0-9a-f]{64}$") + + def test_durable_terminal_states_cannot_resurrect_or_cancel(self): + receipt, registry, _ = self.terminal_receipt() + with self.assertRaisesRegex(SovereignExecutionError, "DURABLE_TERMINAL_STATE"): + registry.transition("terminal-exec", status="RUNNING", phase="resurrect", transition_sequence=4, receipt_root=receipt.root) + with self.assertRaisesRegex(SovereignExecutionError, "DURABLE_TERMINAL_STATE"): + registry.cancel("terminal-exec") + + def test_public_transition_cannot_commit_arbitrary_terminal_receipt(self): + context = self.terminal_context() + registry = context[5] + with self.assertRaisesRegex(SovereignExecutionError, "DURABLE_TERMINAL_COMMIT_REQUIRED"): + registry.transition( + "terminal-exec", + status="COMPLETED", + phase="completed", + transition_sequence=3, + receipt_root="f" * 64, + ) + record = registry.get("terminal-exec") + self.assertEqual(record.status, "RUNNING") + self.assertEqual(record.current_receipt_root, context[4].receipt_root) + + def test_private_terminal_transition_rejects_untrusted_capability(self): + context = self.terminal_context() + registry = context[5] + with self.assertRaisesRegex(SovereignExecutionError, "DURABLE_TERMINAL_COMMIT_CAPABILITY_INVALID"): + registry._commit_terminal_transition( + "terminal-exec", + status="COMPLETED", + phase="completed", + transition_sequence=3, + receipt=object(), + commit_capability=object(), + ) + self.assertEqual(registry.get("terminal-exec").status, "RUNNING") + def test_14_duplicate_external_action(self): _, registry, _ = self.durable(); registry.claim_external_action("exec","idempotency-1") with self.assertRaisesRegex(SovereignExecutionError,"DUPLICATE_EXTERNAL_ACTION"): registry.claim_external_action("exec","idempotency-1") @@ -185,12 +499,70 @@ def test_27_authority_service_unavailable(self): self.test_26_hook_failure() def test_28_registry_unavailable(self): self.assertDenied(AuthorityEvaluator(policy=POLICY,registry=None).evaluate(self.request()),"REGISTRY_UNAVAILABLE") def test_29_receipt_chain_break(self): - chain=ReceiptChain(); base=dict(receipt_version=SCHEMA_VERSION,execution_identity_root=HASH,workspace_binding=self.binding,policy_decision_root="2"*64,authority_score="0.0",authority_domain="git",action_class=D2,tool="git",target="3"*64,pre_state_digest=ZERO_HASH,requested_action_digest="4"*64,result_digest="5"*64,post_state_digest="6"*64,outcome="SUCCEEDED",denial_code="NONE") + chain=ReceiptChain(); base=dict(receipt_version=SCHEMA_VERSION,execution_identity_root=HASH,workspace_binding=self.binding,policy_decision_root="2"*64,authority_receipt_root="7"*64,lease_authorization_receipt_root="8"*64,durable_execution_root="9"*64,authority_score="0.000000",authority_domain="git",action_class=D2,tool="git",target="3"*64,pre_state_digest=ZERO_HASH,requested_action_digest="4"*64,result_digest="5"*64,post_state_digest="6"*64,outcome="SUCCEEDED",denial_code="NONE") first=MutationReceipt(**base,parent_receipt=ZERO_HASH,sequence=0); chain.append(first) with self.assertRaisesRegex(SovereignExecutionError,"RECEIPT_CHAIN_PARENT_BREAK"): chain.append(MutationReceipt(**base,parent_receipt=ZERO_HASH,sequence=1)) + def test_authority_admission_is_not_terminal_success(self): + decision = self.evaluator.evaluate(self.request(), approval=self.approval()) + receipt = self.authority_receipt(decision) + self.assertEqual(receipt.outcome, ADMITTED) + self.assertEqual(receipt.skills_root, self.identity.skills_root) + receipt.verify_signature({self.authority_key_id: self.authority_public_key}) + with self.assertRaisesRegex(SovereignExecutionError, "AUTHORITY_RECEIPT_SIGNATURE_INVALID"): + replace(receipt, skills_root="4" * 64).verify_signature({self.authority_key_id: self.authority_public_key}) + self.assertFalse(hasattr(receipt, "pre_state_digest")) + self.assertFalse(hasattr(receipt, "post_state_digest")) + self.assertRegex(receipt.root, r"^[0-9a-f]{64}$") + + def test_authority_denial_remains_an_authority_receipt(self): + decision = self.evaluator.evaluate(self.request(), approval=None) + receipt = self.authority_receipt(decision) + self.assertEqual(receipt.outcome, DENIED) + self.assertIn("APPROVAL_MISSING", receipt.denial_codes) + + def test_terminal_receipt_requires_admission_and_explicit_outcome(self): + terminal, registry, chain = self.terminal_receipt(terminal_outcome="FAILED", denial_code="EXECUTOR_FAILED") + self.assertEqual(terminal.outcome, "FAILED") + self.assertEqual(registry.get("terminal-exec").current_receipt_root, terminal.root) + self.assertEqual(chain.verify(), terminal.root) + denied = self.evaluator.evaluate(self.request(), approval=None) + request, _, authority_receipt, manager, lease_receipt, registry, chain = self.terminal_context() + with self.assertRaisesRegex(SovereignExecutionError, "TERMINAL_RECEIPT_REQUIRES_ADMITTED_AUTHORITY"): + make_terminal_mutation_receipt( + identity=self.identity, + request=request, + decision=denied, + evaluator=self.evaluator, + authority_receipt=authority_receipt, + trusted_authority_keys={self.authority_key_id: self.authority_public_key}, + lease_manager=manager, + lease_authorization_receipt=lease_receipt, + durable_registry=registry, + execution_id="terminal-exec", + receipt_chain=chain, + result={}, + post_state_digest=self.identity.expected_pre_state, + terminal_outcome="SUCCEEDED", + ) + + def test_authority_receipt_rejects_score_above_one(self): + decision = self.evaluator.evaluate(self.request(), approval=self.approval()) + receipt = self.authority_receipt(decision) + with self.assertRaisesRegex(SovereignExecutionError, "AUTHORITY_RECEIPT_SCORE_INVALID"): + replace(receipt, authority_score="1.999999").validate() + + def test_mutation_receipt_rejects_invalid_action_metadata(self): + terminal, _, _ = self.terminal_receipt() + with self.assertRaisesRegex(SovereignExecutionError, "RECEIPT_ACTION_CLASS_INVALID"): + replace(terminal, action_class="D9").validate() + + def test_terminal_failure_requires_outcome_code(self): + with self.assertRaisesRegex(SovereignExecutionError, "TERMINAL_OUTCOME_CODE_REQUIRED"): + self.terminal_receipt(terminal_outcome="FAILED") + def test_30_cancellation_during_retry(self): - manager,registry,lease=self.durable(); registry.transition("exec",status="RETRYING",phase="retry",transition_sequence=2,receipt_root="2"*64); registry.cancel("exec") + manager,registry,lease=self.durable(); registry.transition("exec",status="RETRYING",phase="retry",transition_sequence=3,receipt_root="3"*64); registry.cancel("exec") self.assertEqual(registry.get("exec").status,"CANCELLED"); self.assertIsNone(manager.current("external")) stale=manager.authorize_write(authority_domain="external",holder_identity_root=HASH,fencing_token=lease.fencing_token,lease_generation=lease.lease_generation,expected_parent_state=ZERO_HASH,action_digest="8"*64) self.assertIn("LEASE_MISSING",stale.denial_codes) diff --git a/sovereign-omega-v2/python/tests/test_coordinator_authority.py b/sovereign-omega-v2/python/tests/test_coordinator_authority.py index d31363fa8..78fcd665c 100644 --- a/sovereign-omega-v2/python/tests/test_coordinator_authority.py +++ b/sovereign-omega-v2/python/tests/test_coordinator_authority.py @@ -30,7 +30,7 @@ def denied_decision(code: str, *, root: str = "1" * 64) -> dict[str, Any]: "authority_score": "0.000000", "denial_codes": [code], "decision_root": root, - "receipt_root": "2" * 64, + "authority_receipt_root": "2" * 64, } @@ -40,7 +40,7 @@ def admitted_decision(score: str = "0.720000", *, root: str = "3" * 64) -> dict[ "authority_score": score, "denial_codes": [], "decision_root": root, - "receipt_root": "4" * 64, + "authority_receipt_root": "4" * 64, } @@ -171,3 +171,30 @@ async def admitted_run_agent(task: Any) -> Any: receipts = coordinator.last_dispatch_receipts() assert receipts[0]["outcome"] == "ADMITTED" assert receipts[0]["authority_score"] == pytest.approx(0.72) + + +def test_legacy_self_report_cannot_mutate_competence_registry(tmp_path: Path) -> None: + path = tmp_path / "skill_tree.json" + original = { + "schema_version": "2.0.0", + "registry_root": "1" * 64, + "skills": [{ + "skill_id": "observed", + "observation_state": "OBSERVED", + "validated_runs": 3, + "confidence": 0.5, + }], + } + path.write_text(json.dumps(original, sort_keys=True), encoding="utf-8") + instance = coordinator.SkillRouter( + skill_tree_path=path, + repo_root=tmp_path, + capability_map={"observed_cap": "observed"}, + ) + before = path.read_bytes() + + instance.emit_skill_event("observed_cap", success=True) + + assert path.read_bytes() == before + assert instance._last_untrusted_observation == ("observed_cap", True) + assert instance._last_mutation_error == "CERTIFIED_OUTCOME_REQUIRED" diff --git a/sovereign-omega-v2/src/metacognition/outcome-comparator.ts b/sovereign-omega-v2/src/metacognition/outcome-comparator.ts new file mode 100644 index 000000000..99379188b --- /dev/null +++ b/sovereign-omega-v2/src/metacognition/outcome-comparator.ts @@ -0,0 +1,996 @@ +// ============================================================ +// SOVEREIGN OMEGA — Adaptation Outcome Comparator +// EPISTEMIC TIER: T2 · deterministic, tested governance primitive +// +// Compares a bounded adaptation proposal with separately rooted, +// verifier-certified authority, terminal-execution, and outcome evidence. +// The verifier trust policy must be authenticated against an operator key +// supplied outside the evidence bundle. This module only recommends a next +// action. It never grants authority, executes a mutation, advances a lease, +// or changes competence. +// ============================================================ + +import type { SequenceNumber, SHA256Hex } from '../core/types.js' +import { canonicalizeJCS } from '../core/canonicalize.js' +import { hashValue } from '../core/hashing.js' +import { deepFreeze } from '../core/immutable.js' +import { compareUtf8 } from '../core/ordering.js' +import { verifyBytes } from '../consensus/crypto.js' +import type { MetacognitiveEntry } from './loop.js' +import { MetacognitiveLoop } from './loop.js' +import type { + AdaptationProposal, + KnowledgeGap, + SelfModelSnapshot, +} from './self-regulator.js' +import { + normalizeAdaptationProposal, + normalizeKnowledgeGaps, + regulateSelf, +} from './self-regulator.js' + +export const OUTCOME_COMPARATOR_SCHEMA_VERSION = '1.0.0' as const + +export type AuthorityOutcome = 'ADMITTED' | 'DENIED' +export type TerminalExecutionOutcome = 'SUCCEEDED' | 'DENIED' | 'FAILED' | 'ROLLED_BACK' +export type DurableTerminalStatus = 'COMPLETED' | 'DENIED' | 'FAILED' | 'CANCELLED' | 'ORPHANED' +export type VerificationVerdict = 'PASS' | 'FAIL' | 'INCONCLUSIVE' +export type VerificationMode = 'INDEPENDENT' | 'EXECUTOR_SELF_REPORT' +export type StateDisposition = 'PRESERVE' | 'REVERT' | 'NO_STATE_CHANGE' +export type EvidenceDisposition = 'CONFIRM' | 'DEGRADE' | 'INCONCLUSIVE' +export type OutcomeNextGate = 'OPERATOR_REVIEW' | 'AUTOMATON_3' + +export interface AdaptationAuthorityBindingV1 { + readonly proposal_digest: SHA256Hex + readonly self_regulation_decision_digest: SHA256Hex + readonly expected_parent_state_root: SHA256Hex +} + +export interface AdaptationAuthorityEvidenceV1 { + readonly evidence_kind: 'AUTOMATON3_AUTHORITY_DECISION_V1' + readonly outcome: AuthorityOutcome + readonly denial_codes: readonly string[] + readonly execution_identity_root: SHA256Hex + readonly workspace_binding: SHA256Hex + readonly policy_root: SHA256Hex + readonly registry_root: SHA256Hex + readonly policy_decision_root: SHA256Hex + readonly authority_receipt_root: SHA256Hex + readonly executor_principal_root: SHA256Hex + readonly executor_workload_identity_root: SHA256Hex + readonly action_binding: AdaptationAuthorityBindingV1 + readonly requested_action_digest: SHA256Hex +} + +export interface TerminalExecutionEvidenceV1 { + readonly evidence_kind: 'AUTOMATON3_TERMINAL_EXECUTION_V1' + readonly execution_identity_root: SHA256Hex + readonly workspace_binding: SHA256Hex + readonly policy_decision_root: SHA256Hex + readonly authority_receipt_root: SHA256Hex + readonly requested_action_digest: SHA256Hex + readonly lease_outcome: AuthorityOutcome + readonly lease_authorization_receipt_root: SHA256Hex + readonly durable_execution_root: SHA256Hex + readonly durable_status: DurableTerminalStatus + readonly mutation_receipt_root: SHA256Hex + readonly receipt_chain_status: 'VERIFIED' | 'UNVERIFIED' + readonly receipt_chain_verification_root: SHA256Hex + readonly outcome: TerminalExecutionOutcome + readonly pre_state_root: SHA256Hex + readonly post_state_root: SHA256Hex + readonly provider_result_digest: SHA256Hex + readonly operator_notification_root: SHA256Hex +} + +export interface VerificationObservation { + readonly step_index: number + readonly verdict: VerificationVerdict + readonly evidence_digest: SHA256Hex + readonly verifier_identity_root: SHA256Hex + readonly verification_mode: VerificationMode +} + +export interface OutcomeEvidenceCertificateV1 { + readonly certificate_kind: 'AEGIS_OUTCOME_EVIDENCE_CERTIFICATE_V1' + readonly verifier_key_id: string + readonly verifier_public_key: string + readonly verifier_identity_root: SHA256Hex + readonly verifier_principal_root: SHA256Hex + readonly verifier_workload_identity_root: SHA256Hex + readonly evidence_bundle_digest: SHA256Hex + readonly signature: string +} + +export interface OutcomeVerifierIdentityV1 { + readonly verifier_key_id: string + readonly verifier_public_key: string + readonly verifier_identity_root: SHA256Hex + readonly verifier_principal_root: SHA256Hex + readonly verifier_workload_identity_root: SHA256Hex +} + +export interface OutcomeVerifierTrustPolicyV1 { + readonly schema_version: '1.0.0' + readonly policy_kind: 'AEGIS_OUTCOME_VERIFIER_TRUST_POLICY_V1' + readonly governed_policy_root: SHA256Hex + readonly verifier_trust_root: SHA256Hex + readonly verifiers: readonly OutcomeVerifierIdentityV1[] + readonly signer_key_id: string + readonly signer_public_key: string + readonly signature: string +} + +/** + * An in-process capability returned only after a trust policy signature has + * been verified against an operator public key supplied out of band. + * Serialized evidence cannot manufacture this value. + */ +export interface VerifiedOutcomeVerifierTrustAnchorV1 { + readonly governed_policy_root: SHA256Hex + readonly verifier_trust_root: SHA256Hex + readonly verifiers: readonly OutcomeVerifierIdentityV1[] + readonly trust_policy_digest: SHA256Hex +} + +export interface AdaptationOutcomeInput { + readonly baseline: { + readonly snapshot: SelfModelSnapshot + readonly gaps: readonly KnowledgeGap[] + readonly proposal: AdaptationProposal + } + readonly authority: AdaptationAuthorityEvidenceV1 + readonly terminal_execution?: TerminalExecutionEvidenceV1 + readonly post_snapshot: SelfModelSnapshot + readonly post_gaps: readonly KnowledgeGap[] + readonly verification: readonly VerificationObservation[] + readonly evidence_certificate?: OutcomeEvidenceCertificateV1 +} + +export interface AdaptationOutcomeAssessment { + readonly schema_version: typeof OUTCOME_COMPARATOR_SCHEMA_VERSION + readonly state_disposition: StateDisposition + readonly evidence_disposition: EvidenceDisposition + readonly reason_codes: readonly string[] + readonly required_next_gate: OutcomeNextGate + readonly grants_authority: false + readonly executes_mutation: false + readonly updates_competence: false + readonly requires_automaton3: boolean + readonly learning_evidence_eligible: boolean + readonly source_decision_digest: SHA256Hex + readonly proposal_digest: SHA256Hex | null + readonly authority_evidence_digest: SHA256Hex + readonly authority_decision_root: SHA256Hex + readonly requested_action_digest: SHA256Hex + readonly terminal_evidence_digest: SHA256Hex | null + readonly terminal_receipt_root: SHA256Hex | null + readonly evidence_bundle_digest: SHA256Hex + readonly evidence_certificate_digest: SHA256Hex | null + readonly evidence_certificate_verified: boolean + readonly verifier_trust_policy_digest: SHA256Hex + readonly pre_state_root: SHA256Hex + readonly post_state_root: SHA256Hex + readonly expected_previous_metacognition_root: SHA256Hex + readonly resolved_gap_ids: readonly string[] + readonly remaining_addressed_gap_ids: readonly string[] + readonly new_gap_ids: readonly string[] + readonly verification_digest: SHA256Hex + readonly post_self_model_digest: SHA256Hex + readonly assessment_digest: SHA256Hex +} + +export interface OutcomeEvidenceArtifactV1 { + readonly schema_version: typeof OUTCOME_COMPARATOR_SCHEMA_VERSION + readonly artifact_kind: 'AEGIS_OUTCOME_EVIDENCE_ARTIFACT_V1' + readonly evidence_input: Readonly> + readonly verifier_trust_anchor: { + readonly governed_policy_root: SHA256Hex + readonly verifier_trust_root: SHA256Hex + readonly verifiers: readonly OutcomeVerifierIdentityV1[] + readonly trust_policy_digest: SHA256Hex + } + readonly assessment: AdaptationOutcomeAssessment + readonly artifact_root: SHA256Hex +} + +export interface OutcomeEvidencePersistenceReceiptV1 { + readonly artifact_root: SHA256Hex + readonly artifact_reference: string +} + +/** + * A caller-supplied persistence boundary. Returning a receipt is a claim that + * the complete artifact can be resolved by ``artifact_reference``. The + * comparator verifies the content root before it appends a metacognitive + * observation; it does not claim storage durability beyond this contract. + */ +export interface OutcomeEvidenceArtifactStore { + persist( + artifact: OutcomeEvidenceArtifactV1, + ): Promise +} + +export class OutcomeComparisonError extends Error { + override readonly name = 'OutcomeComparisonError' + constructor(message: string) { + super(message) + Object.setPrototypeOf(this, new.target.prototype) + } +} + +const HASH_PATTERN = /^[0-9a-f]{64}$/ +const ZERO_HASH = '0'.repeat(64) +const SIGNATURE_PATTERN = /^[0-9a-f]{128}$/ +const SAFE_KEY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{1,127}$/ +const AUTHORITY_OUTCOMES = new Set(['ADMITTED', 'DENIED']) +const TERMINAL_OUTCOMES = new Set(['SUCCEEDED', 'DENIED', 'FAILED', 'ROLLED_BACK']) +const DURABLE_STATUSES = new Set(['COMPLETED', 'DENIED', 'FAILED', 'CANCELLED', 'ORPHANED']) +const VERDICTS = new Set(['PASS', 'FAIL', 'INCONCLUSIVE']) +const VERIFICATION_MODES = new Set(['INDEPENDENT', 'EXECUTOR_SELF_REPORT']) +const VERIFIED_TRUST_ANCHORS = new WeakSet() + +function assertHash(field: string, value: unknown): asserts value is SHA256Hex { + if (typeof value !== 'string' || !HASH_PATTERN.test(value)) { + throw new OutcomeComparisonError(`${field} must be lowercase SHA-256 hex`) + } +} + +function assertResolvedHash(field: string, value: unknown): asserts value is SHA256Hex { + assertHash(field, value) + if (value === ZERO_HASH) { + throw new OutcomeComparisonError(`${field} must resolve to a non-zero identity or evidence root`) + } +} + +function normalizeCodes(field: string, values: readonly string[]): readonly string[] { + if (!Array.isArray(values)) throw new OutcomeComparisonError(`${field} must be an array`) + const normalized = values.map((value, index) => { + if (typeof value !== 'string' || value.trim() === '') { + throw new OutcomeComparisonError(`${field}[${index}] must be a non-empty string`) + } + return value.trim() + }) + const unique = [...new Set(normalized)].sort(compareUtf8) + if (unique.length !== normalized.length) throw new OutcomeComparisonError(`${field} must be unique`) + return unique +} + +function validateAuthorityBinding(binding: AdaptationAuthorityBindingV1): void { + assertHash('authority.action_binding.proposal_digest', binding.proposal_digest) + assertHash('authority.action_binding.self_regulation_decision_digest', binding.self_regulation_decision_digest) + assertHash('authority.action_binding.expected_parent_state_root', binding.expected_parent_state_root) +} + +function normalizeVerifierPublicKeys(values: readonly string[]): readonly string[] { + if (!Array.isArray(values) || values.length === 0) { + throw new OutcomeComparisonError('trusted_verifier_public_keys must be a non-empty array') + } + const normalized = values.map((value, index) => { + if (typeof value !== 'string' || !HASH_PATTERN.test(value)) { + throw new OutcomeComparisonError(`trusted_verifier_public_keys[${index}] must be 32-byte lowercase hex`) + } + if (value === ZERO_HASH) { + throw new OutcomeComparisonError(`trusted_verifier_public_keys[${index}] must not be unresolved`) + } + return value + }) + const unique = [...new Set(normalized)].sort(compareUtf8) + if (unique.length !== normalized.length) { + throw new OutcomeComparisonError('trusted_verifier_public_keys must be unique') + } + return unique +} + +export async function hashVerifierIdentityV1(publicKey: string): Promise { + const normalized = normalizeVerifierPublicKeys([publicKey])[0]! + return hashValue({ domain: 'AEGIS_VERIFIER_IDENTITY_V1', public_key: normalized }) +} + +async function normalizeVerifierIdentities( + values: readonly OutcomeVerifierIdentityV1[], +): Promise { + if (!Array.isArray(values) || values.length === 0) { + throw new OutcomeComparisonError('verifiers must be a non-empty array') + } + const normalized = await Promise.all(values.map(async (value, index) => { + if (!SAFE_KEY_ID_PATTERN.test(value.verifier_key_id)) { + throw new OutcomeComparisonError(`verifiers[${index}].verifier_key_id is invalid`) + } + const verifier_public_key = normalizeVerifierPublicKeys([value.verifier_public_key])[0]! + assertResolvedHash(`verifiers[${index}].verifier_identity_root`, value.verifier_identity_root) + assertResolvedHash(`verifiers[${index}].verifier_principal_root`, value.verifier_principal_root) + assertResolvedHash( + `verifiers[${index}].verifier_workload_identity_root`, + value.verifier_workload_identity_root, + ) + const expectedIdentityRoot = await hashVerifierIdentityV1(verifier_public_key) + if (value.verifier_identity_root !== expectedIdentityRoot) { + throw new OutcomeComparisonError(`verifiers[${index}].verifier_identity_root is invalid`) + } + return { + verifier_key_id: value.verifier_key_id, + verifier_public_key, + verifier_identity_root: value.verifier_identity_root, + verifier_principal_root: value.verifier_principal_root, + verifier_workload_identity_root: value.verifier_workload_identity_root, + } + })) + normalized.sort((left, right) => compareUtf8(left.verifier_key_id, right.verifier_key_id)) + const keyIds = normalized.map(value => value.verifier_key_id) + const publicKeys = normalized.map(value => value.verifier_public_key) + if (new Set(keyIds).size !== keyIds.length) { + throw new OutcomeComparisonError('verifier_key_id values must be unique') + } + if (new Set(publicKeys).size !== publicKeys.length) { + throw new OutcomeComparisonError('verifier public keys must be unique') + } + return normalized +} + +export async function hashVerifierTrustSetV1( + verifiers: readonly OutcomeVerifierIdentityV1[], +): Promise { + const normalized = await normalizeVerifierIdentities(verifiers) + return hashValue({ domain: 'AEGIS_VERIFIER_TRUST_SET_V1', verifiers: normalized }) +} + +export async function canonicalizeOutcomeVerifierTrustPolicyMessageV1( + policy: Omit, +): Promise { + if (policy.schema_version !== '1.0.0') { + throw new OutcomeComparisonError('trust policy schema_version is invalid') + } + if (policy.policy_kind !== 'AEGIS_OUTCOME_VERIFIER_TRUST_POLICY_V1') { + throw new OutcomeComparisonError('trust policy kind is invalid') + } + assertResolvedHash('trust_policy.governed_policy_root', policy.governed_policy_root) + assertResolvedHash('trust_policy.verifier_trust_root', policy.verifier_trust_root) + if (!SAFE_KEY_ID_PATTERN.test(policy.signer_key_id)) { + throw new OutcomeComparisonError('trust_policy.signer_key_id is invalid') + } + const signer_public_key = normalizeVerifierPublicKeys([policy.signer_public_key])[0]! + const verifiers = await normalizeVerifierIdentities(policy.verifiers) + return canonicalizeJCS({ + domain: 'AEGIS_OUTCOME_VERIFIER_TRUST_POLICY_V1', + policy: { + schema_version: policy.schema_version, + policy_kind: policy.policy_kind, + governed_policy_root: policy.governed_policy_root, + verifier_trust_root: policy.verifier_trust_root, + verifiers, + signer_key_id: policy.signer_key_id, + signer_public_key, + }, + }) +} + +export async function verifyOutcomeVerifierTrustPolicyV1( + policy: OutcomeVerifierTrustPolicyV1, + expectedGovernedPolicyRoot: SHA256Hex, + expectedOperatorPublicKey: string, +): Promise { + assertHash('expected_governed_policy_root', expectedGovernedPolicyRoot) + const operatorPublicKey = normalizeVerifierPublicKeys([expectedOperatorPublicKey])[0]! + if (!SIGNATURE_PATTERN.test(policy.signature)) { + throw new OutcomeComparisonError('trust_policy.signature must be 64-byte lowercase Ed25519 hex') + } + if (policy.governed_policy_root !== expectedGovernedPolicyRoot) { + throw new OutcomeComparisonError('trust policy is not bound to the expected governed policy root') + } + if (policy.signer_public_key !== operatorPublicKey) { + throw new OutcomeComparisonError('trust policy signer is not the expected operator key') + } + const verifiers = await normalizeVerifierIdentities(policy.verifiers) + const verifierTrustRoot = await hashVerifierTrustSetV1(verifiers) + if (policy.verifier_trust_root !== verifierTrustRoot) { + throw new OutcomeComparisonError('trust policy verifier root does not match its verifier set') + } + const { signature: _signature, ...unsignedPolicy } = policy + const message = await canonicalizeOutcomeVerifierTrustPolicyMessageV1(unsignedPolicy) + if (!await verifyBytes(operatorPublicKey, message, policy.signature)) { + throw new OutcomeComparisonError('trust policy signature is invalid') + } + const trust_policy_digest = await hashValue({ + domain: 'AEGIS_OUTCOME_VERIFIER_TRUST_POLICY_RECORD_V1', + policy: { ...unsignedPolicy, verifiers, signature: policy.signature }, + }) + const anchor = deepFreeze({ + governed_policy_root: expectedGovernedPolicyRoot, + verifier_trust_root: verifierTrustRoot, + verifiers, + trust_policy_digest, + }) + VERIFIED_TRUST_ANCHORS.add(anchor) + return anchor +} + +export function canonicalizeOutcomeEvidenceCertificateMessageV1( + certificate: Omit, +): Uint8Array { + validateEvidenceCertificateFields(certificate) + return canonicalizeJCS({ + domain: 'AEGIS_OUTCOME_EVIDENCE_CERTIFICATE_V1', + certificate, + }) +} + +export async function hashAdaptationAuthorityBinding( + binding: AdaptationAuthorityBindingV1, +): Promise { + validateAuthorityBinding(binding) + return hashValue({ domain: 'AEGIS_ADAPTATION_AUTHORITY_BINDING_V1', binding }) +} + +function validateAuthorityEvidence(authority: AdaptationAuthorityEvidenceV1): readonly string[] { + if (authority.evidence_kind !== 'AUTOMATON3_AUTHORITY_DECISION_V1') { + throw new OutcomeComparisonError('authority.evidence_kind is invalid') + } + if (!AUTHORITY_OUTCOMES.has(authority.outcome)) { + throw new OutcomeComparisonError('authority.outcome is invalid') + } + const denialCodes = normalizeCodes('authority.denial_codes', authority.denial_codes) + if (authority.outcome === 'ADMITTED' && denialCodes.length !== 0) { + throw new OutcomeComparisonError('admitted authority evidence cannot contain denial codes') + } + if (authority.outcome === 'DENIED' && denialCodes.length === 0) { + throw new OutcomeComparisonError('denied authority evidence requires a denial code') + } + for (const field of [ + 'execution_identity_root', + 'workspace_binding', + 'policy_root', + 'registry_root', + 'policy_decision_root', + 'authority_receipt_root', + 'executor_principal_root', + 'executor_workload_identity_root', + 'requested_action_digest', + ] as const) { + assertResolvedHash(`authority.${field}`, authority[field]) + } + validateAuthorityBinding(authority.action_binding) + return denialCodes +} + +function validateTerminalEvidence(terminal: TerminalExecutionEvidenceV1): void { + if (terminal.evidence_kind !== 'AUTOMATON3_TERMINAL_EXECUTION_V1') { + throw new OutcomeComparisonError('terminal_execution.evidence_kind is invalid') + } + if (!AUTHORITY_OUTCOMES.has(terminal.lease_outcome)) { + throw new OutcomeComparisonError('terminal_execution.lease_outcome is invalid') + } + if (!DURABLE_STATUSES.has(terminal.durable_status)) { + throw new OutcomeComparisonError('terminal_execution.durable_status is invalid') + } + if (!TERMINAL_OUTCOMES.has(terminal.outcome)) { + throw new OutcomeComparisonError('terminal_execution.outcome is invalid') + } + if (!['VERIFIED', 'UNVERIFIED'].includes(terminal.receipt_chain_status)) { + throw new OutcomeComparisonError('terminal_execution.receipt_chain_status is invalid') + } + for (const field of [ + 'execution_identity_root', + 'workspace_binding', + 'policy_decision_root', + 'authority_receipt_root', + 'requested_action_digest', + 'lease_authorization_receipt_root', + 'durable_execution_root', + 'mutation_receipt_root', + 'receipt_chain_verification_root', + 'pre_state_root', + 'post_state_root', + 'provider_result_digest', + 'operator_notification_root', + ] as const) { + assertHash(`terminal_execution.${field}`, terminal[field]) + } +} + +function normalizeVerification( + observations: readonly VerificationObservation[], + stepCount: number, +): readonly VerificationObservation[] { + if (!Array.isArray(observations)) throw new OutcomeComparisonError('verification must be an array') + const indices = new Set() + const normalized = observations.map((observation, index) => { + if (!Number.isInteger(observation.step_index) || observation.step_index < 0 || observation.step_index >= stepCount) { + throw new OutcomeComparisonError(`verification[${index}].step_index is out of range`) + } + if (indices.has(observation.step_index)) { + throw new OutcomeComparisonError('verification step indices must be unique') + } + indices.add(observation.step_index) + if (!VERDICTS.has(observation.verdict)) { + throw new OutcomeComparisonError(`verification[${index}].verdict is invalid`) + } + if (!VERIFICATION_MODES.has(observation.verification_mode)) { + throw new OutcomeComparisonError(`verification[${index}].verification_mode is invalid`) + } + assertHash(`verification[${index}].evidence_digest`, observation.evidence_digest) + assertHash(`verification[${index}].verifier_identity_root`, observation.verifier_identity_root) + return { ...observation } + }) + return normalized.sort((left, right) => left.step_index - right.step_index) +} + +export type OutcomeEvidenceInputV1 = Omit< + AdaptationOutcomeInput, + 'evidence_certificate' +> + +export async function hashOutcomeEvidenceBundleV1( + input: OutcomeEvidenceInputV1, +): Promise { + const proposal = normalizeAdaptationProposal(input.baseline.proposal) + const baselineGaps = normalizeKnowledgeGaps(input.baseline.gaps) + const postGaps = normalizeKnowledgeGaps(input.post_gaps) + const denialCodes = validateAuthorityEvidence(input.authority) + if (input.terminal_execution !== undefined) validateTerminalEvidence(input.terminal_execution) + const verification = normalizeVerification(input.verification, proposal.verification_steps.length) + return hashValue({ + domain: 'AEGIS_OUTCOME_EVIDENCE_BUNDLE_V1', + evidence: { + baseline: { + snapshot: input.baseline.snapshot, + gaps: baselineGaps, + proposal, + }, + authority: { ...input.authority, denial_codes: denialCodes }, + terminal_execution: input.terminal_execution ?? null, + post_snapshot: input.post_snapshot, + post_gaps: postGaps, + verification, + }, + }) +} + +function validateEvidenceCertificateFields( + certificate: Omit, +): void { + if (certificate.certificate_kind !== 'AEGIS_OUTCOME_EVIDENCE_CERTIFICATE_V1') { + throw new OutcomeComparisonError('evidence_certificate.certificate_kind is invalid') + } + if (!SAFE_KEY_ID_PATTERN.test(certificate.verifier_key_id)) { + throw new OutcomeComparisonError('evidence_certificate.verifier_key_id is invalid') + } + if (!HASH_PATTERN.test(certificate.verifier_public_key)) { + throw new OutcomeComparisonError('evidence_certificate.verifier_public_key must be 32-byte lowercase hex') + } + assertResolvedHash('evidence_certificate.verifier_identity_root', certificate.verifier_identity_root) + assertResolvedHash('evidence_certificate.verifier_principal_root', certificate.verifier_principal_root) + assertResolvedHash( + 'evidence_certificate.verifier_workload_identity_root', + certificate.verifier_workload_identity_root, + ) + assertHash('evidence_certificate.evidence_bundle_digest', certificate.evidence_bundle_digest) +} + +function validateEvidenceCertificate(certificate: OutcomeEvidenceCertificateV1): void { + validateEvidenceCertificateFields(certificate) + if (!SIGNATURE_PATTERN.test(certificate.signature)) { + throw new OutcomeComparisonError('evidence_certificate.signature must be 64-byte lowercase Ed25519 hex') + } +} + +function expectedDurableStatus(outcome: TerminalExecutionOutcome): DurableTerminalStatus { + switch (outcome) { + case 'SUCCEEDED': + case 'ROLLED_BACK': + return 'COMPLETED' + case 'DENIED': + return 'DENIED' + case 'FAILED': + return 'FAILED' + } +} + +function gapIds(gaps: readonly KnowledgeGap[]): readonly string[] { + return gaps.map(gap => gap.gap_id).sort(compareUtf8) +} + +function componentTransitionViolations( + baseline: SelfModelSnapshot, + post: SelfModelSnapshot, + consequenceClass: AdaptationProposal['consequence_class'], +): readonly string[] { + const reasons: string[] = [] + const changed = (field: keyof Omit): boolean => + baseline[field] !== post[field] + + if (changed('verifier_trust_root')) { + // Rotation needs an operator-signed, dual-key transition contract. Merely + // labelling a proposal D4 is not rotation evidence. + reasons.push('VERIFIER_TRUST_ROTATION_EVIDENCE_REQUIRED') + } + if (changed('identity_root') && consequenceClass !== 'D4') { + reasons.push('IDENTITY_TRANSITION_REQUIRES_D4') + } + if (changed('policy_root') && consequenceClass !== 'D4') { + reasons.push('POLICY_TRANSITION_REQUIRES_D4') + } + if (changed('capability_root') && !['D2', 'D3', 'D4'].includes(consequenceClass)) { + reasons.push('CAPABILITY_TRANSITION_REQUIRES_D2') + } + if (consequenceClass === 'D0' && ( + changed('memory_root') || changed('metacognition_root') + )) { + reasons.push('D0_COMPONENT_TRANSITION_FORBIDDEN') + } + return reasons +} + +export async function assessAdaptationOutcome( + input: AdaptationOutcomeInput, + trustAnchor: VerifiedOutcomeVerifierTrustAnchorV1, +): Promise { + if (!VERIFIED_TRUST_ANCHORS.has(trustAnchor)) { + throw new OutcomeComparisonError( + 'verifier trust anchor was not authenticated by verifyOutcomeVerifierTrustPolicyV1', + ) + } + const baselineGaps = normalizeKnowledgeGaps(input.baseline.gaps) + const postGaps = normalizeKnowledgeGaps(input.post_gaps) + const proposal = normalizeAdaptationProposal(input.baseline.proposal) + const sourceDecision = await regulateSelf({ + snapshot: input.baseline.snapshot, + gaps: baselineGaps, + proposal, + }) + const postDecision = await regulateSelf({ snapshot: input.post_snapshot, gaps: postGaps }) + const denialCodes = validateAuthorityEvidence(input.authority) + const expectedActionDigest = await hashAdaptationAuthorityBinding(input.authority.action_binding) + const verification = normalizeVerification( + input.verification, + proposal.verification_steps.length, + ) + const verification_digest = await hashValue({ + domain: 'AEGIS_ADAPTATION_VERIFICATION_V1', + verification, + }) + const authority_evidence_digest = await hashValue({ + domain: 'AEGIS_ADAPTATION_AUTHORITY_EVIDENCE_V1', + authority: { ...input.authority, denial_codes: denialCodes }, + }) + + const reasons: string[] = [] + const proposalDigest = sourceDecision.proposal_digest + const authority = input.authority + const evidence_bundle_digest = await hashOutcomeEvidenceBundleV1(input) + let evidence_certificate_digest: SHA256Hex | null = null + let evidence_certificate_verified = false + const anchorPolicyMatches = + trustAnchor.governed_policy_root === input.baseline.snapshot.policy_root + const anchorTrustRootMatches = + trustAnchor.verifier_trust_root === input.baseline.snapshot.verifier_trust_root + if (!anchorPolicyMatches) reasons.push('VERIFIER_TRUST_POLICY_ROOT_MISMATCH') + if (!anchorTrustRootMatches) reasons.push('VERIFIER_TRUST_ROOT_MISMATCH') + + const transitionReasons = componentTransitionViolations( + input.baseline.snapshot, + input.post_snapshot, + proposal.consequence_class, + ) + reasons.push(...transitionReasons) + const componentTransitionValid = transitionReasons.length === 0 + + const certificate = input.evidence_certificate + if (certificate === undefined) { + reasons.push('EVIDENCE_CERTIFICATE_MISSING') + } else { + validateEvidenceCertificate(certificate) + evidence_certificate_digest = await hashValue({ + domain: 'AEGIS_OUTCOME_EVIDENCE_CERTIFICATE_RECORD_V1', + certificate, + }) + const expectedVerifierIdentity = await hashVerifierIdentityV1(certificate.verifier_public_key) + const trustedVerifier = trustAnchor.verifiers.find( + verifier => verifier.verifier_key_id === certificate.verifier_key_id, + ) + const verifierIsTrusted = trustedVerifier !== undefined && + trustedVerifier.verifier_public_key === certificate.verifier_public_key && + trustedVerifier.verifier_identity_root === certificate.verifier_identity_root && + trustedVerifier.verifier_principal_root === certificate.verifier_principal_root && + trustedVerifier.verifier_workload_identity_root === certificate.verifier_workload_identity_root + const verifierIdentityMatches = certificate.verifier_identity_root === expectedVerifierIdentity + const verifierPrincipalIsIndependent = + certificate.verifier_principal_root !== authority.executor_principal_root + const verifierWorkloadIsIndependent = + certificate.verifier_workload_identity_root !== authority.executor_workload_identity_root + const verifierIsIndependent = verifierPrincipalIsIndependent && verifierWorkloadIsIndependent + const bundleMatches = certificate.evidence_bundle_digest === evidence_bundle_digest + const { signature: _certificateSignature, ...unsignedCertificate } = certificate + const signatureValid = await verifyBytes( + certificate.verifier_public_key, + canonicalizeOutcomeEvidenceCertificateMessageV1(unsignedCertificate), + certificate.signature, + ) + const observationIdentitiesMatch = verification + .filter(observation => observation.verification_mode === 'INDEPENDENT') + .every(observation => observation.verifier_identity_root === certificate.verifier_identity_root) + + if (!verifierIsTrusted) reasons.push('EVIDENCE_VERIFIER_NOT_TRUSTED') + if (!verifierIdentityMatches) reasons.push('EVIDENCE_VERIFIER_IDENTITY_MISMATCH') + if (!verifierPrincipalIsIndependent) reasons.push('EVIDENCE_VERIFIER_PRINCIPAL_NOT_INDEPENDENT') + if (!verifierWorkloadIsIndependent) reasons.push('EVIDENCE_VERIFIER_WORKLOAD_NOT_INDEPENDENT') + if (!bundleMatches) reasons.push('EVIDENCE_BUNDLE_DIGEST_MISMATCH') + if (!signatureValid) reasons.push('EVIDENCE_CERTIFICATE_SIGNATURE_INVALID') + if (!observationIdentitiesMatch) reasons.push('VERIFICATION_IDENTITY_MISMATCH') + evidence_certificate_verified = anchorPolicyMatches && + anchorTrustRootMatches && + verifierIsTrusted && + verifierIdentityMatches && + verifierIsIndependent && + bundleMatches && + signatureValid && + observationIdentitiesMatch && + componentTransitionValid + } + + if (sourceDecision.mode !== 'READY_FOR_AUTHORITY') reasons.push('BASELINE_NOT_READY_FOR_AUTHORITY') + if (proposalDigest === null || authority.action_binding.proposal_digest !== proposalDigest) { + reasons.push('AUTHORITY_PROPOSAL_DIGEST_MISMATCH') + } + if (authority.action_binding.self_regulation_decision_digest !== sourceDecision.decision_digest) { + reasons.push('AUTHORITY_REGULATION_DECISION_MISMATCH') + } + if (authority.action_binding.expected_parent_state_root !== input.baseline.snapshot.state_root) { + reasons.push('AUTHORITY_PARENT_STATE_MISMATCH') + } + if (authority.requested_action_digest !== expectedActionDigest) reasons.push('AUTHORITY_ACTION_DIGEST_MISMATCH') + if (authority.execution_identity_root !== input.baseline.snapshot.identity_root) reasons.push('AUTHORITY_IDENTITY_MISMATCH') + if (authority.policy_root !== input.baseline.snapshot.policy_root) reasons.push('AUTHORITY_POLICY_ROOT_MISMATCH') + if (authority.registry_root !== input.baseline.snapshot.capability_root) reasons.push('AUTHORITY_REGISTRY_ROOT_MISMATCH') + if (authority.outcome === 'DENIED') reasons.push('AUTHORITY_DENIED') + + let terminal_evidence_digest: SHA256Hex | null = null + let terminal_receipt_root: SHA256Hex | null = null + let terminalBindingValid = false + const terminal = input.terminal_execution + if (terminal === undefined) { + reasons.push('TERMINAL_EXECUTION_EVIDENCE_MISSING') + } else { + validateTerminalEvidence(terminal) + terminal_evidence_digest = await hashValue({ + domain: 'AEGIS_TERMINAL_EXECUTION_EVIDENCE_V1', + terminal, + }) + terminal_receipt_root = terminal.mutation_receipt_root + const terminalReasons: string[] = [] + if (terminal.execution_identity_root !== authority.execution_identity_root) terminalReasons.push('TERMINAL_IDENTITY_MISMATCH') + if (terminal.workspace_binding !== authority.workspace_binding) terminalReasons.push('TERMINAL_WORKSPACE_MISMATCH') + if (terminal.policy_decision_root !== authority.policy_decision_root) terminalReasons.push('TERMINAL_POLICY_DECISION_MISMATCH') + if (terminal.authority_receipt_root !== authority.authority_receipt_root) terminalReasons.push('TERMINAL_AUTHORITY_RECEIPT_MISMATCH') + if (terminal.requested_action_digest !== authority.requested_action_digest) terminalReasons.push('TERMINAL_ACTION_DIGEST_MISMATCH') + if (terminal.pre_state_root !== input.baseline.snapshot.state_root) terminalReasons.push('TERMINAL_PRE_STATE_MISMATCH') + if (terminal.post_state_root !== input.post_snapshot.state_root) terminalReasons.push('TERMINAL_POST_STATE_MISMATCH') + if (terminal.lease_outcome !== 'ADMITTED') terminalReasons.push('TERMINAL_LEASE_NOT_ADMITTED') + if (terminal.receipt_chain_status !== 'VERIFIED') terminalReasons.push('TERMINAL_RECEIPT_CHAIN_UNVERIFIED') + if (terminal.durable_status !== expectedDurableStatus(terminal.outcome)) terminalReasons.push('TERMINAL_DURABLE_STATUS_MISMATCH') + reasons.push(...terminalReasons) + terminalBindingValid = terminalReasons.length === 0 + } + + const addressed = [...proposal.addressed_gap_ids] + const baselineIds = new Set(gapIds(baselineGaps)) + const postIds = new Set(gapIds(postGaps)) + const resolved_gap_ids = addressed.filter(id => !postIds.has(id)) + const remaining_addressed_gap_ids = addressed.filter(id => postIds.has(id)) + const new_gap_ids = [...postIds].filter(id => !baselineIds.has(id)).sort(compareUtf8) + const unsafePostGap = postGaps.some(gap => + gap.severity === 'CRITICAL' || gap.kind === 'INVARIANT_BREACH', + ) + + const stateChanged = input.post_snapshot.state_root !== input.baseline.snapshot.state_root + const postHealth = input.post_snapshot.health + const postHealthy = postHealth.t0_verdict && + postHealth.corruption_count === 0 && + postHealth.membrane_intact && + postHealth.entropy_bounded + if (!postHealthy) reasons.push('POST_STATE_UNHEALTHY') + if (unsafePostGap) reasons.push('UNSAFE_POST_GAP') + else if (new_gap_ids.length > 0) reasons.push('NEW_GAPS_OBSERVED') + if (remaining_addressed_gap_ids.length > 0) reasons.push('ADDRESSED_GAP_UNRESOLVED') + + const verificationCoverageComplete = verification.length === proposal.verification_steps.length + const verificationFailed = verification.some(item => item.verdict === 'FAIL') + const verificationInconclusive = verification.some(item => item.verdict === 'INCONCLUSIVE') + const verificationIndependent = verification.every(item => item.verification_mode === 'INDEPENDENT') + if (!verificationCoverageComplete) reasons.push('VERIFICATION_COVERAGE_INCOMPLETE') + if (verificationFailed) reasons.push('VERIFICATION_FAILED') + if (verificationInconclusive) reasons.push('VERIFICATION_INCONCLUSIVE') + if (!verificationIndependent) reasons.push('VERIFICATION_NOT_INDEPENDENT') + + const authorityBindingsValid = !reasons.some(reason => reason.startsWith('AUTHORITY_') && reason !== 'AUTHORITY_DENIED') && + sourceDecision.mode === 'READY_FOR_AUTHORITY' + const terminalProofValid = authority.outcome === 'ADMITTED' && + authorityBindingsValid && + terminalBindingValid && + evidence_certificate_verified + const strongVerification = verificationCoverageComplete && + !verificationFailed && + !verificationInconclusive && + verificationIndependent + const conclusiveIndependentVerification = verificationCoverageComplete && + !verificationInconclusive && + verificationIndependent + + let state_disposition: StateDisposition + let evidence_disposition: EvidenceDisposition + let learning_evidence_eligible = false + + if (authority.outcome === 'DENIED') { + state_disposition = stateChanged ? 'REVERT' : 'NO_STATE_CHANGE' + evidence_disposition = 'INCONCLUSIVE' + } else if (!terminalProofValid || terminal === undefined) { + state_disposition = stateChanged ? 'REVERT' : 'NO_STATE_CHANGE' + evidence_disposition = 'INCONCLUSIVE' + } else if (terminal.outcome === 'ROLLED_BACK') { + if (stateChanged) { + reasons.push('ROLLBACK_POST_STATE_MISMATCH') + state_disposition = 'REVERT' + evidence_disposition = 'INCONCLUSIVE' + } else { + reasons.push('EXECUTION_ROLLED_BACK') + state_disposition = 'NO_STATE_CHANGE' + evidence_disposition = conclusiveIndependentVerification ? 'DEGRADE' : 'INCONCLUSIVE' + learning_evidence_eligible = conclusiveIndependentVerification + } + } else if (terminal.outcome === 'FAILED' || terminal.outcome === 'DENIED') { + reasons.push(`EXECUTION_${terminal.outcome}`) + state_disposition = stateChanged ? 'REVERT' : 'NO_STATE_CHANGE' + evidence_disposition = conclusiveIndependentVerification ? 'DEGRADE' : 'INCONCLUSIVE' + learning_evidence_eligible = conclusiveIndependentVerification + } else if (!stateChanged) { + reasons.push('SUCCESS_WITHOUT_STATE_TRANSITION') + state_disposition = 'NO_STATE_CHANGE' + evidence_disposition = strongVerification ? 'DEGRADE' : 'INCONCLUSIVE' + learning_evidence_eligible = strongVerification + } else if (!strongVerification) { + state_disposition = 'REVERT' + evidence_disposition = verificationFailed && conclusiveIndependentVerification ? 'DEGRADE' : 'INCONCLUSIVE' + learning_evidence_eligible = evidence_disposition === 'DEGRADE' + } else if (!postHealthy || unsafePostGap || new_gap_ids.length > 0 || remaining_addressed_gap_ids.length > 0) { + state_disposition = 'REVERT' + evidence_disposition = 'DEGRADE' + learning_evidence_eligible = true + } else { + reasons.push('TERMINAL_OUTCOME_INDEPENDENTLY_VERIFIED') + state_disposition = 'PRESERVE' + evidence_disposition = 'CONFIRM' + learning_evidence_eligible = true + } + + const required_next_gate: OutcomeNextGate = + state_disposition !== 'NO_STATE_CHANGE' || learning_evidence_eligible + ? 'AUTOMATON_3' + : 'OPERATOR_REVIEW' + const sortedReasons = [...new Set(reasons)].sort(compareUtf8) + const unsigned = { + schema_version: OUTCOME_COMPARATOR_SCHEMA_VERSION, + state_disposition, + evidence_disposition, + reason_codes: sortedReasons, + required_next_gate, + grants_authority: false as const, + executes_mutation: false as const, + updates_competence: false as const, + requires_automaton3: required_next_gate === 'AUTOMATON_3', + learning_evidence_eligible, + source_decision_digest: sourceDecision.decision_digest, + proposal_digest: proposalDigest, + authority_evidence_digest, + authority_decision_root: authority.policy_decision_root, + requested_action_digest: authority.requested_action_digest, + terminal_evidence_digest, + terminal_receipt_root, + evidence_bundle_digest, + evidence_certificate_digest, + evidence_certificate_verified, + verifier_trust_policy_digest: trustAnchor.trust_policy_digest, + pre_state_root: input.baseline.snapshot.state_root, + post_state_root: input.post_snapshot.state_root, + expected_previous_metacognition_root: input.baseline.snapshot.metacognition_root, + resolved_gap_ids, + remaining_addressed_gap_ids, + new_gap_ids, + verification_digest, + post_self_model_digest: postDecision.self_model_digest, + } + const assessment_digest = await hashValue({ + domain: 'AEGIS_ADAPTATION_OUTCOME_ASSESSMENT_V1', + assessment: unsigned, + }) + return deepFreeze({ ...unsigned, assessment_digest }) +} + +export async function recordOutcomeAssessment( + loop: MetacognitiveLoop, + input: AdaptationOutcomeInput, + trustAnchor: VerifiedOutcomeVerifierTrustAnchorV1, + artifactStore: OutcomeEvidenceArtifactStore, + sequence: SequenceNumber, +): Promise<{ + assessment: AdaptationOutcomeAssessment + artifact: OutcomeEvidenceArtifactV1 + persistence: OutcomeEvidencePersistenceReceiptV1 + loop: MetacognitiveLoop + entry: MetacognitiveEntry +}> { + // Re-evaluate the authenticated evidence inside the append boundary. A + // caller cannot submit an edited assessment plus a freshly recomputed public + // hash and have it accepted as learned evidence. + const assessment = await assessAdaptationOutcome(input, trustAnchor) + if (loop.lastHash !== assessment.expected_previous_metacognition_root) { + throw new OutcomeComparisonError('metacognitive loop head does not match assessment baseline') + } + if (artifactStore === null || typeof artifactStore !== 'object' || + typeof artifactStore.persist !== 'function') { + throw new OutcomeComparisonError('outcome evidence artifact store is unavailable') + } + const normalizedVerifiers = await normalizeVerifierIdentities(trustAnchor.verifiers) + const denialCodes = validateAuthorityEvidence(input.authority) + if (input.terminal_execution !== undefined) validateTerminalEvidence(input.terminal_execution) + const normalizedInput = deepFreeze>>({ + baseline: { + snapshot: input.baseline.snapshot, + gaps: normalizeKnowledgeGaps(input.baseline.gaps), + proposal: normalizeAdaptationProposal(input.baseline.proposal), + }, + authority: { ...input.authority, denial_codes: denialCodes }, + terminal_execution: input.terminal_execution ?? null, + post_snapshot: input.post_snapshot, + post_gaps: normalizeKnowledgeGaps(input.post_gaps), + verification: normalizeVerification( + input.verification, + input.baseline.proposal.verification_steps.length, + ), + evidence_certificate: input.evidence_certificate ?? null, + }) + const artifactBody = deepFreeze({ + schema_version: OUTCOME_COMPARATOR_SCHEMA_VERSION, + artifact_kind: 'AEGIS_OUTCOME_EVIDENCE_ARTIFACT_V1' as const, + evidence_input: normalizedInput, + verifier_trust_anchor: { + governed_policy_root: trustAnchor.governed_policy_root, + verifier_trust_root: trustAnchor.verifier_trust_root, + verifiers: normalizedVerifiers, + trust_policy_digest: trustAnchor.trust_policy_digest, + }, + assessment, + }) + const artifact_root = await hashValue({ + domain: 'AEGIS_OUTCOME_EVIDENCE_ARTIFACT_V1', + artifact: artifactBody, + }) + const artifact = deepFreeze({ ...artifactBody, artifact_root }) + let persistence: OutcomeEvidencePersistenceReceiptV1 + try { + persistence = await artifactStore.persist(artifact) + } catch (error) { + throw new OutcomeComparisonError( + `outcome evidence persistence failed: ${error instanceof Error ? error.message : String(error)}`, + ) + } + assertResolvedHash('outcome_evidence_persistence.artifact_root', persistence.artifact_root) + if (persistence.artifact_root !== artifact.artifact_root) { + throw new OutcomeComparisonError('outcome evidence persistence root mismatch') + } + if (typeof persistence.artifact_reference !== 'string' || + persistence.artifact_reference.length > 1024 || + !/^[a-z][a-z0-9+.-]{1,31}:[^\s\u0000-\u001f]+$/.test(persistence.artifact_reference)) { + throw new OutcomeComparisonError('outcome evidence artifact reference is invalid') + } + const persistence_binding = await hashValue({ + domain: 'AEGIS_OUTCOME_EVIDENCE_PERSISTENCE_BINDING_V1', + artifact_root: persistence.artifact_root, + artifact_reference: persistence.artifact_reference, + }) + const observed = await loop.observe({ + layer: 'METACOGNITIVE', + signal: `OUTCOME_EVIDENCE_ARTIFACT_V1:${artifact.artifact_root}:${persistence_binding}:${assessment.state_disposition}:${assessment.evidence_disposition}`, + tier: 'T2', + }, sequence) + return { assessment, artifact, persistence, ...observed } +} diff --git a/sovereign-omega-v2/src/metacognition/self-regulator.ts b/sovereign-omega-v2/src/metacognition/self-regulator.ts index 2fef9504b..f7c754315 100644 --- a/sovereign-omega-v2/src/metacognition/self-regulator.ts +++ b/sovereign-omega-v2/src/metacognition/self-regulator.ts @@ -11,6 +11,7 @@ import type { SHA256Hex } from '../core/types.js' import { hashValue } from '../core/hashing.js' import { deepFreeze } from '../core/immutable.js' +import { compareUtf8 } from '../core/ordering.js' export const SELF_REGULATOR_SCHEMA_VERSION = '1.0.0' as const @@ -47,14 +48,17 @@ export interface SelfModelSnapshot { readonly capability_root: SHA256Hex readonly memory_root: SHA256Hex readonly metacognition_root: SHA256Hex + readonly verifier_trust_root: SHA256Hex readonly health: SelfModelHealth } +export type SelfModelStateComponents = Omit + export interface KnowledgeGap { readonly gap_id: string readonly kind: GapKind readonly severity: GapSeverity - readonly evidence_refs: readonly string[] + readonly evidence_refs: readonly SHA256Hex[] } export interface ProposedMutation { @@ -105,7 +109,10 @@ export class SelfRegulationError extends Error { const HASH_PATTERN = /^[0-9a-f]{64}$/ const SAFE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{1,127}$/ -const SAFE_PATH_PATTERN = /^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))[A-Za-z0-9._@/+:-]+$/ +const SAFE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._@+~-]+$/ +const DRIVE_PATH_PATTERN = /^[A-Za-z]:/ +const URI_SCHEME_PATTERN = /^[A-Za-z][A-Za-z0-9+.-]*:/ +const WINDOWS_RESERVED_DEVICE_PATTERN = /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i const VALID_KINDS = new Set([ 'INVARIANT_BREACH', 'CAPABILITY_DEFICIT', @@ -135,18 +142,64 @@ function sortedUnique(field: string, values: readonly string[]): readonly string assertNonEmpty(`${field}[${index}]`, value) return value.trim() }) - const unique = [...new Set(normalized)].sort() + const unique = [...new Set(normalized)].sort(compareUtf8) if (unique.length !== normalized.length) throw new SelfRegulationError(`${field} must be unique`) return unique } -function validateSnapshot(snapshot: SelfModelSnapshot): void { - assertHash('snapshot.state_root', snapshot.state_root) +function normalizeOptionalReference(field: string, value: string | undefined): string | undefined { + if (value === undefined) return undefined + assertNonEmpty(field, value) + return value.trim() +} + +/** + * Validate a canonical POSIX repository-relative path without rewriting it. + * + * Rejecting non-canonical spellings is intentional: silently normalizing a + * mutation target would allow the proposal digest to name different bytes + * from the path ultimately handed to the repository executor. + */ +function canonicalRepositoryPath(field: string, value: unknown): string { + assertNonEmpty(field, value) + if ( + value !== value.trim() || + value.startsWith('/') || + value.startsWith('\\') || + value.includes('\\') || + DRIVE_PATH_PATTERN.test(value) || + URI_SCHEME_PATTERN.test(value) || + value.endsWith('/') || + value.includes('//') + ) { + throw new SelfRegulationError(`${field} must be a canonical POSIX repository-relative path`) + } + + const segments = value.split('/') + if ( + segments.length === 0 || + segments.some(segment => + segment === '' || + segment === '.' || + segment === '..' || + segment.endsWith('.') || + segment.endsWith(' ') || + WINDOWS_RESERVED_DEVICE_PATTERN.test(segment) || + !SAFE_PATH_SEGMENT_PATTERN.test(segment), + ) + ) { + throw new SelfRegulationError(`${field} must be a canonical POSIX repository-relative path`) + } + return value +} + +function validateStateComponents(snapshot: SelfModelStateComponents): void { assertHash('snapshot.identity_root', snapshot.identity_root) assertHash('snapshot.policy_root', snapshot.policy_root) assertHash('snapshot.capability_root', snapshot.capability_root) assertHash('snapshot.memory_root', snapshot.memory_root) assertHash('snapshot.metacognition_root', snapshot.metacognition_root) + assertHash('snapshot.verifier_trust_root', snapshot.verifier_trust_root) if (!Number.isInteger(snapshot.health.corruption_count) || snapshot.health.corruption_count < 0) { throw new SelfRegulationError('snapshot.health.corruption_count must be a non-negative integer') } @@ -157,7 +210,26 @@ function validateSnapshot(snapshot: SelfModelSnapshot): void { } } -function normalizeGaps(gaps: readonly KnowledgeGap[]): readonly KnowledgeGap[] { +function validateSnapshot(snapshot: SelfModelSnapshot): void { + assertHash('snapshot.state_root', snapshot.state_root) + validateStateComponents(snapshot) +} + +/** + * Bind every authority-relevant self-model component into one deterministic + * state root. Callers may not provide an unrelated label as `state_root`. + */ +export async function hashSelfModelStateRootV1( + snapshot: SelfModelStateComponents, +): Promise { + validateStateComponents(snapshot) + return hashValue({ + domain: 'AEGIS_SELF_MODEL_STATE_V1', + snapshot, + }) +} + +export function normalizeKnowledgeGaps(gaps: readonly KnowledgeGap[]): readonly KnowledgeGap[] { if (!Array.isArray(gaps)) throw new SelfRegulationError('gaps must be an array') const ids = new Set() const normalized = gaps.map((gap, index) => { @@ -166,39 +238,77 @@ function normalizeGaps(gaps: readonly KnowledgeGap[]): readonly KnowledgeGap[] { ids.add(gap.gap_id) if (!VALID_KINDS.has(gap.kind)) throw new SelfRegulationError(`gaps[${index}].kind is invalid`) if (!VALID_SEVERITIES.has(gap.severity)) throw new SelfRegulationError(`gaps[${index}].severity is invalid`) + const evidenceReferences = sortedUnique(`gaps[${index}].evidence_refs`, gap.evidence_refs) + if (evidenceReferences.length === 0) { + throw new SelfRegulationError(`gaps[${index}].evidence_refs must contain verified evidence`) + } + const evidence_refs = evidenceReferences.map((reference, evidenceIndex) => { + assertHash(`gaps[${index}].evidence_refs[${evidenceIndex}]`, reference) + return reference + }) return { gap_id: gap.gap_id, kind: gap.kind, severity: gap.severity, - evidence_refs: sortedUnique(`gaps[${index}].evidence_refs`, gap.evidence_refs), + evidence_refs, } }) - return normalized.sort((a, b) => a.gap_id.localeCompare(b.gap_id)) + return normalized.sort((a, b) => compareUtf8(a.gap_id, b.gap_id)) } -function normalizeProposal(proposal: AdaptationProposal): AdaptationProposal { +export function normalizeAdaptationProposal(proposal: AdaptationProposal): AdaptationProposal { if (!SAFE_ID_PATTERN.test(proposal.proposal_id)) throw new SelfRegulationError('proposal.proposal_id is invalid') assertNonEmpty('proposal.objective', proposal.objective) if (!VALID_CLASSES.has(proposal.consequence_class)) throw new SelfRegulationError('proposal.consequence_class is invalid') assertHash('proposal.expected_parent_state_root', proposal.expected_parent_state_root) if (!Array.isArray(proposal.mutations)) throw new SelfRegulationError('proposal.mutations must be an array') + const mutationOperationsByPath = new Map() + const mutationPathByCaseFold = new Map() const mutations = proposal.mutations.map((mutation, index) => { - if (!SAFE_PATH_PATTERN.test(mutation.path)) throw new SelfRegulationError(`proposal.mutations[${index}].path is invalid`) + const path = canonicalRepositoryPath(`proposal.mutations[${index}].path`, mutation.path) if (!VALID_OPERATIONS.has(mutation.operation)) throw new SelfRegulationError(`proposal.mutations[${index}].operation is invalid`) + const priorOperation = mutationOperationsByPath.get(path) + if (priorOperation !== undefined) { + const qualifier = priorOperation === mutation.operation ? 'duplicate' : 'conflicting' + throw new SelfRegulationError(`proposal.mutations[${index}].path has a ${qualifier} operation`) + } + mutationOperationsByPath.set(path, mutation.operation) + const caseFoldedPath = path.toLowerCase() + const priorCaseVariant = mutationPathByCaseFold.get(caseFoldedPath) + if (priorCaseVariant !== undefined && priorCaseVariant !== path) { + throw new SelfRegulationError(`proposal.mutations[${index}].path collides after Windows case folding`) + } + mutationPathByCaseFold.set(caseFoldedPath, path) if (mutation.expected_blob !== undefined && !/^[0-9a-f]{40,64}$/.test(mutation.expected_blob)) { throw new SelfRegulationError(`proposal.mutations[${index}].expected_blob is invalid`) } return mutation.expected_blob === undefined - ? { path: mutation.path, operation: mutation.operation } - : { path: mutation.path, operation: mutation.operation, expected_blob: mutation.expected_blob } + ? { path, operation: mutation.operation } + : { path, operation: mutation.operation, expected_blob: mutation.expected_blob } }) + if (!Array.isArray(proposal.verification_steps)) { + throw new SelfRegulationError('proposal.verification_steps must be an array') + } const verification_steps = proposal.verification_steps.map((step, index) => { assertNonEmpty(`proposal.verification_steps[${index}]`, step) return step.trim() }) + const rollback_reference = normalizeOptionalReference( + 'proposal.rollback_reference', + proposal.rollback_reference, + ) + const operator_approval_reference = normalizeOptionalReference( + 'proposal.operator_approval_reference', + proposal.operator_approval_reference, + ) + const constitutional_change_reference = normalizeOptionalReference( + 'proposal.constitutional_change_reference', + proposal.constitutional_change_reference, + ) + return { proposal_id: proposal.proposal_id, objective: proposal.objective.trim(), @@ -208,9 +318,9 @@ function normalizeProposal(proposal: AdaptationProposal): AdaptationProposal { requested_capabilities: sortedUnique('proposal.requested_capabilities', proposal.requested_capabilities), mutations, verification_steps, - ...(proposal.rollback_reference === undefined ? {} : { rollback_reference: proposal.rollback_reference }), - ...(proposal.operator_approval_reference === undefined ? {} : { operator_approval_reference: proposal.operator_approval_reference }), - ...(proposal.constitutional_change_reference === undefined ? {} : { constitutional_change_reference: proposal.constitutional_change_reference }), + ...(rollback_reference === undefined ? {} : { rollback_reference }), + ...(operator_approval_reference === undefined ? {} : { operator_approval_reference }), + ...(constitutional_change_reference === undefined ? {} : { constitutional_change_reference }), } } @@ -223,7 +333,12 @@ function forbiddenCapability(capability: string): boolean { export async function regulateSelf(input: SelfRegulationInput): Promise { validateSnapshot(input.snapshot) - const gaps = normalizeGaps(input.gaps) + const { state_root: suppliedStateRoot, ...stateComponents } = input.snapshot + const expectedStateRoot = await hashSelfModelStateRootV1(stateComponents) + if (suppliedStateRoot !== expectedStateRoot) { + throw new SelfRegulationError('snapshot.state_root does not bind the self-model components') + } + const gaps = normalizeKnowledgeGaps(input.gaps) const self_model_digest = await hashValue({ domain: 'AEGIS_SELF_MODEL_V1', snapshot: input.snapshot, @@ -256,7 +371,7 @@ export async function regulateSelf(input: SelfRegulationInput): Promise gap.gap_id)) @@ -264,10 +379,13 @@ export async function regulateSelf(input: SelfRegulationInput): Promise !knownGapIds.has(id))) reasons.push('UNKNOWN_GAP_REFERENCE') if (proposal.mutations.length === 0) reasons.push('NO_PROPOSED_MUTATION') + if (proposal.consequence_class === 'D0' && proposal.mutations.length > 0) { + reasons.push('D0_MUTATION_FORBIDDEN') + } if (proposal.verification_steps.length === 0) reasons.push('NO_VERIFICATION_PLAN') if (proposal.requested_capabilities.some(forbiddenCapability)) reasons.push('FORBIDDEN_CAPABILITY_REQUEST') - if (['D2', 'D3', 'D4'].includes(proposal.consequence_class) && !proposal.rollback_reference) { + if (['D1', 'D2', 'D3', 'D4'].includes(proposal.consequence_class) && !proposal.rollback_reference) { reasons.push('ROLLBACK_REFERENCE_REQUIRED') } if (['D3', 'D4'].includes(proposal.consequence_class) && !proposal.operator_approval_reference) { diff --git a/sovereign-omega-v2/test/integration/metacognitive-outcome-closure.test.ts b/sovereign-omega-v2/test/integration/metacognitive-outcome-closure.test.ts new file mode 100644 index 000000000..8676c0fe5 --- /dev/null +++ b/sovereign-omega-v2/test/integration/metacognitive-outcome-closure.test.ts @@ -0,0 +1,224 @@ +import { describe, expect, it } from 'vitest' +import type { SequenceNumber, SHA256Hex } from '../../src/core/types.js' +import { generateKeypair, signBytes } from '../../src/consensus/crypto.js' +import { + METACOGNITION_GENESIS_HASH, + MetacognitiveLoop, + certifyMetacognitiveLoop, +} from '../../src/metacognition/loop.js' +import { + canonicalizeOutcomeEvidenceCertificateMessageV1, + canonicalizeOutcomeVerifierTrustPolicyMessageV1, + hashAdaptationAuthorityBinding, + hashOutcomeEvidenceBundleV1, + hashVerifierIdentityV1, + hashVerifierTrustSetV1, + recordOutcomeAssessment, + verifyOutcomeVerifierTrustPolicyV1, +} from '../../src/metacognition/outcome-comparator.js' +import type { + AdaptationOutcomeInput, + OutcomeEvidenceArtifactV1, + OutcomeEvidenceCertificateV1, + OutcomeVerifierIdentityV1, + OutcomeVerifierTrustPolicyV1, +} from '../../src/metacognition/outcome-comparator.js' +import { + hashSelfModelStateRootV1, + regulateSelf, +} from '../../src/metacognition/self-regulator.js' +import type { + AdaptationProposal, + KnowledgeGap, + SelfModelSnapshot, + SelfModelStateComponents, +} from '../../src/metacognition/self-regulator.js' + +const H = (character: string) => character.repeat(64) as SHA256Hex +const SEQ = (value: number) => BigInt(value) as SequenceNumber + +async function selfModel( + verifierTrustRoot: SHA256Hex, + overrides: Partial = {}, +): Promise { + const components: SelfModelStateComponents = { + identity_root: H('2'), + policy_root: H('3'), + capability_root: H('4'), + memory_root: H('5'), + metacognition_root: METACOGNITION_GENESIS_HASH, + verifier_trust_root: verifierTrustRoot, + health: { + t0_verdict: true, + corruption_count: 0, + membrane_intact: true, + entropy_bounded: true, + }, + ...overrides, + } + return { state_root: await hashSelfModelStateRootV1(components), ...components } +} + +describe('metacognitive adaptation outcome closure', () => { + it('reassesses signed evidence, appends it, and reanchors the next self-model', async () => { + const operatorKeypair = await generateKeypair(new Uint8Array(32).fill(29)) + const verifierKeypair = await generateKeypair(new Uint8Array(32).fill(31)) + const verifier: OutcomeVerifierIdentityV1 = { + verifier_key_id: 'metacognitive-closure-test-verifier', + verifier_public_key: verifierKeypair.publicKey, + verifier_identity_root: await hashVerifierIdentityV1(verifierKeypair.publicKey), + verifier_principal_root: H('a'), + verifier_workload_identity_root: H('b'), + } + const verifierTrustRoot = await hashVerifierTrustSetV1([verifier]) + const unsignedTrustPolicy: Omit = { + schema_version: '1.0.0', + policy_kind: 'AEGIS_OUTCOME_VERIFIER_TRUST_POLICY_V1', + governed_policy_root: H('3'), + verifier_trust_root: verifierTrustRoot, + verifiers: [verifier], + signer_key_id: 'closure-operator-key', + signer_public_key: operatorKeypair.publicKey, + } + const trustPolicy: OutcomeVerifierTrustPolicyV1 = { + ...unsignedTrustPolicy, + signature: await signBytes( + operatorKeypair.privateKey, + await canonicalizeOutcomeVerifierTrustPolicyMessageV1(unsignedTrustPolicy), + ), + } + const trustAnchor = await verifyOutcomeVerifierTrustPolicyV1( + trustPolicy, + H('3'), + operatorKeypair.publicKey, + ) + + const gap: KnowledgeGap = { + gap_id: 'gap.closure.001', + kind: 'CAPABILITY_DEFICIT', + severity: 'HIGH', + evidence_refs: [H('8')], + } + const baseline = await selfModel(verifierTrustRoot) + const proposal: AdaptationProposal = { + proposal_id: 'proposal.closure.001', + objective: 'Exercise the governed outcome-learning closure.', + consequence_class: 'D2', + expected_parent_state_root: baseline.state_root, + addressed_gap_ids: [gap.gap_id], + requested_capabilities: ['repo.file.propose'], + mutations: [{ path: 'src/metacognition/outcome-comparator.ts', operation: 'CREATE' }], + verification_steps: ['closure integration'], + rollback_reference: 'git:revert-candidate', + } + const regulation = await regulateSelf({ snapshot: baseline, gaps: [gap], proposal }) + expect(regulation.mode).toBe('READY_FOR_AUTHORITY') + if (regulation.proposal_digest === null) throw new Error('proposal digest unavailable') + + const action_binding = { + proposal_digest: regulation.proposal_digest, + self_regulation_decision_digest: regulation.decision_digest, + expected_parent_state_root: baseline.state_root, + } + const authority = { + evidence_kind: 'AUTOMATON3_AUTHORITY_DECISION_V1' as const, + outcome: 'ADMITTED' as const, + denial_codes: [], + execution_identity_root: baseline.identity_root, + workspace_binding: H('7'), + policy_root: baseline.policy_root, + registry_root: baseline.capability_root, + policy_decision_root: H('8'), + authority_receipt_root: H('9'), + executor_principal_root: H('c'), + executor_workload_identity_root: H('d'), + action_binding, + requested_action_digest: await hashAdaptationAuthorityBinding(action_binding), + } + const post = await selfModel(verifierTrustRoot, { capability_root: H('9') }) + const unsignedInput: AdaptationOutcomeInput = { + baseline: { snapshot: baseline, gaps: [gap], proposal }, + authority, + terminal_execution: { + evidence_kind: 'AUTOMATON3_TERMINAL_EXECUTION_V1', + execution_identity_root: authority.execution_identity_root, + workspace_binding: authority.workspace_binding, + policy_decision_root: authority.policy_decision_root, + authority_receipt_root: authority.authority_receipt_root, + requested_action_digest: authority.requested_action_digest, + lease_outcome: 'ADMITTED', + lease_authorization_receipt_root: H('a'), + durable_execution_root: H('b'), + durable_status: 'COMPLETED', + mutation_receipt_root: H('c'), + receipt_chain_status: 'VERIFIED', + receipt_chain_verification_root: H('d'), + outcome: 'SUCCEEDED', + pre_state_root: baseline.state_root, + post_state_root: post.state_root, + provider_result_digest: H('e'), + operator_notification_root: H('f'), + }, + post_snapshot: post, + post_gaps: [], + verification: [{ + step_index: 0, + verdict: 'PASS', + evidence_digest: H('a'), + verifier_identity_root: verifier.verifier_identity_root, + verification_mode: 'INDEPENDENT', + }], + } + const evidenceBundleDigest = await hashOutcomeEvidenceBundleV1(unsignedInput) + const unsignedCertificate: Omit = { + certificate_kind: 'AEGIS_OUTCOME_EVIDENCE_CERTIFICATE_V1', + verifier_key_id: verifier.verifier_key_id, + verifier_public_key: verifier.verifier_public_key, + verifier_identity_root: verifier.verifier_identity_root, + verifier_principal_root: verifier.verifier_principal_root, + verifier_workload_identity_root: verifier.verifier_workload_identity_root, + evidence_bundle_digest: evidenceBundleDigest, + } + const input: AdaptationOutcomeInput = { + ...unsignedInput, + evidence_certificate: { + ...unsignedCertificate, + signature: await signBytes( + verifierKeypair.privateKey, + canonicalizeOutcomeEvidenceCertificateMessageV1(unsignedCertificate), + ), + }, + } + + const observed = await recordOutcomeAssessment( + MetacognitiveLoop.empty(), + input, + trustAnchor, + { + async persist(artifact: OutcomeEvidenceArtifactV1) { + return { + artifact_root: artifact.artifact_root, + artifact_reference: `memory:${artifact.artifact_root}`, + } + }, + }, + SEQ(1), + ) + expect(observed.assessment.state_disposition).toBe('PRESERVE') + expect(observed.assessment.grants_authority).toBe(false) + const certificate = await certifyMetacognitiveLoop(observed.loop.getAll()) + expect(certificate.is_valid).toBe(true) + expect(observed.entry.observation.signal).toContain(observed.artifact.artifact_root) + expect(observed.artifact.assessment.assessment_digest).toBe( + observed.assessment.assessment_digest, + ) + + const reanchoredPost = await selfModel(verifierTrustRoot, { + capability_root: post.capability_root, + metacognition_root: observed.entry.entry_hash, + }) + expect(reanchoredPost.state_root).not.toBe(post.state_root) + const nextRegulation = await regulateSelf({ snapshot: reanchoredPost, gaps: [] }) + expect(nextRegulation.mode).toBe('NO_CHANGE') + }) +}) diff --git a/sovereign-omega-v2/test/unit/outcome-comparator.test.ts b/sovereign-omega-v2/test/unit/outcome-comparator.test.ts new file mode 100644 index 000000000..67a8decdd --- /dev/null +++ b/sovereign-omega-v2/test/unit/outcome-comparator.test.ts @@ -0,0 +1,641 @@ +import { describe, expect, it } from 'vitest' +import type { SequenceNumber, SHA256Hex } from '../../src/core/types.js' +import { generateKeypair, signBytes } from '../../src/consensus/crypto.js' +import { + METACOGNITION_GENESIS_HASH, + MetacognitiveLoop, +} from '../../src/metacognition/loop.js' +import { + OutcomeComparisonError, + assessAdaptationOutcome, + canonicalizeOutcomeEvidenceCertificateMessageV1, + canonicalizeOutcomeVerifierTrustPolicyMessageV1, + hashAdaptationAuthorityBinding, + hashOutcomeEvidenceBundleV1, + hashVerifierIdentityV1, + hashVerifierTrustSetV1, + recordOutcomeAssessment, + verifyOutcomeVerifierTrustPolicyV1, +} from '../../src/metacognition/outcome-comparator.js' +import type { + AdaptationAuthorityEvidenceV1, + AdaptationOutcomeInput, + OutcomeEvidenceArtifactStore, + OutcomeEvidenceArtifactV1, + OutcomeEvidenceCertificateV1, + OutcomeVerifierIdentityV1, + OutcomeVerifierTrustPolicyV1, + TerminalExecutionEvidenceV1, + VerificationObservation, + VerifiedOutcomeVerifierTrustAnchorV1, +} from '../../src/metacognition/outcome-comparator.js' +import { + hashSelfModelStateRootV1, + regulateSelf, +} from '../../src/metacognition/self-regulator.js' +import type { + AdaptationProposal, + KnowledgeGap, + SelfModelSnapshot, + SelfModelStateComponents, +} from '../../src/metacognition/self-regulator.js' + +const H = (character: string) => character.repeat(64) as SHA256Hex +const SEQ = (value: number) => BigInt(value) as SequenceNumber +const EXECUTOR_PRINCIPAL = H('d') +const EXECUTOR_WORKLOAD = H('e') +const VERIFIER_PRINCIPAL = H('b') +const VERIFIER_WORKLOAD = H('c') + +function memoryArtifactStore() { + const artifacts = new Map() + const store: OutcomeEvidenceArtifactStore = { + async persist(artifact) { + artifacts.set(artifact.artifact_root, artifact) + return { + artifact_root: artifact.artifact_root, + artifact_reference: `memory:${artifact.artifact_root}`, + } + }, + } + return { artifacts, store } +} + +const GAP: KnowledgeGap = { + gap_id: 'gap.outcome.001', + kind: 'CAPABILITY_DEFICIT', + severity: 'HIGH', + evidence_refs: [H('e')], +} + +interface TrustContext { + readonly verifierKeypair: Awaited> + readonly verifier: OutcomeVerifierIdentityV1 + readonly anchor: VerifiedOutcomeVerifierTrustAnchorV1 +} + +interface Fixture { + readonly input: AdaptationOutcomeInput + readonly trust: TrustContext +} + +async function trustContext( + verifierPrincipalRoot = VERIFIER_PRINCIPAL, + verifierWorkloadIdentityRoot = VERIFIER_WORKLOAD, +): Promise { + const operatorKeypair = await generateKeypair(new Uint8Array(32).fill(11)) + const verifierKeypair = await generateKeypair(new Uint8Array(32).fill(23)) + const verifier: OutcomeVerifierIdentityV1 = { + verifier_key_id: 'outcome-test-verifier', + verifier_public_key: verifierKeypair.publicKey, + verifier_identity_root: await hashVerifierIdentityV1(verifierKeypair.publicKey), + verifier_principal_root: verifierPrincipalRoot, + verifier_workload_identity_root: verifierWorkloadIdentityRoot, + } + const verifierTrustRoot = await hashVerifierTrustSetV1([verifier]) + const unsignedPolicy: Omit = { + schema_version: '1.0.0', + policy_kind: 'AEGIS_OUTCOME_VERIFIER_TRUST_POLICY_V1', + governed_policy_root: H('3'), + verifier_trust_root: verifierTrustRoot, + verifiers: [verifier], + signer_key_id: 'operator-test-key', + signer_public_key: operatorKeypair.publicKey, + } + const policy: OutcomeVerifierTrustPolicyV1 = { + ...unsignedPolicy, + signature: await signBytes( + operatorKeypair.privateKey, + await canonicalizeOutcomeVerifierTrustPolicyMessageV1(unsignedPolicy), + ), + } + const anchor = await verifyOutcomeVerifierTrustPolicyV1( + policy, + H('3'), + operatorKeypair.publicKey, + ) + return { verifierKeypair, verifier, anchor } +} + +async function snapshot( + verifierTrustRoot: SHA256Hex, + overrides: Partial = {}, +): Promise { + const components: SelfModelStateComponents = { + identity_root: H('2'), + policy_root: H('3'), + capability_root: H('4'), + memory_root: H('5'), + metacognition_root: METACOGNITION_GENESIS_HASH, + verifier_trust_root: verifierTrustRoot, + health: { + t0_verdict: true, + corruption_count: 0, + membrane_intact: true, + entropy_bounded: true, + }, + ...overrides, + } + return { state_root: await hashSelfModelStateRootV1(components), ...components } +} + +function proposal( + parentStateRoot: SHA256Hex, + overrides: Partial = {}, +): AdaptationProposal { + return { + proposal_id: 'proposal.outcome.001', + objective: 'Close the bounded adaptation outcome loop.', + consequence_class: 'D2', + expected_parent_state_root: parentStateRoot, + addressed_gap_ids: [GAP.gap_id], + requested_capabilities: ['repo.file.propose'], + mutations: [{ path: 'src/metacognition/outcome-comparator.ts', operation: 'CREATE' }], + verification_steps: ['unit', 'integration'], + rollback_reference: 'git:revert-candidate', + ...overrides, + } +} + +function verification(verifierIdentityRoot: SHA256Hex): readonly VerificationObservation[] { + return [0, 1].map(step_index => ({ + step_index, + verdict: 'PASS' as const, + evidence_digest: step_index === 0 ? H('a') : H('c'), + verifier_identity_root: verifierIdentityRoot, + verification_mode: 'INDEPENDENT' as const, + })) +} + +async function admittedAuthority( + baselineSnapshot: SelfModelSnapshot, + baselineGaps: readonly KnowledgeGap[], + adaptation: AdaptationProposal, + overrides: Partial = {}, +): Promise { + const regulation = await regulateSelf({ + snapshot: baselineSnapshot, + gaps: baselineGaps, + proposal: adaptation, + }) + if (regulation.proposal_digest === null) throw new Error('fixture proposal digest unavailable') + const action_binding = { + proposal_digest: regulation.proposal_digest, + self_regulation_decision_digest: regulation.decision_digest, + expected_parent_state_root: adaptation.expected_parent_state_root, + } + return { + evidence_kind: 'AUTOMATON3_AUTHORITY_DECISION_V1', + outcome: 'ADMITTED', + denial_codes: [], + execution_identity_root: baselineSnapshot.identity_root, + workspace_binding: H('7'), + policy_root: baselineSnapshot.policy_root, + registry_root: baselineSnapshot.capability_root, + policy_decision_root: H('8'), + authority_receipt_root: H('9'), + executor_principal_root: EXECUTOR_PRINCIPAL, + executor_workload_identity_root: EXECUTOR_WORKLOAD, + action_binding, + requested_action_digest: await hashAdaptationAuthorityBinding(action_binding), + ...overrides, + } +} + +function terminal( + authority: AdaptationAuthorityEvidenceV1, + baselineStateRoot: SHA256Hex, + postStateRoot: SHA256Hex, + overrides: Partial = {}, +): TerminalExecutionEvidenceV1 { + return { + evidence_kind: 'AUTOMATON3_TERMINAL_EXECUTION_V1', + execution_identity_root: authority.execution_identity_root, + workspace_binding: authority.workspace_binding, + policy_decision_root: authority.policy_decision_root, + authority_receipt_root: authority.authority_receipt_root, + requested_action_digest: authority.requested_action_digest, + lease_outcome: 'ADMITTED', + lease_authorization_receipt_root: H('e'), + durable_execution_root: H('f'), + durable_status: 'COMPLETED', + mutation_receipt_root: H('a'), + receipt_chain_status: 'VERIFIED', + receipt_chain_verification_root: H('b'), + outcome: 'SUCCEEDED', + pre_state_root: baselineStateRoot, + post_state_root: postStateRoot, + provider_result_digest: H('c'), + operator_notification_root: H('d'), + ...overrides, + } +} + +async function certify( + input: AdaptationOutcomeInput, + trust: TrustContext, +): Promise { + const { evidence_certificate: _existingCertificate, ...unsignedInput } = input + const evidenceBundleDigest = await hashOutcomeEvidenceBundleV1(unsignedInput) + const unsignedCertificate: Omit = { + certificate_kind: 'AEGIS_OUTCOME_EVIDENCE_CERTIFICATE_V1', + verifier_key_id: trust.verifier.verifier_key_id, + verifier_public_key: trust.verifier.verifier_public_key, + verifier_identity_root: trust.verifier.verifier_identity_root, + verifier_principal_root: trust.verifier.verifier_principal_root, + verifier_workload_identity_root: trust.verifier.verifier_workload_identity_root, + evidence_bundle_digest: evidenceBundleDigest, + } + return { + ...unsignedInput, + evidence_certificate: { + ...unsignedCertificate, + signature: await signBytes( + trust.verifierKeypair.privateKey, + canonicalizeOutcomeEvidenceCertificateMessageV1(unsignedCertificate), + ), + }, + } +} + +async function fixture(providedTrust?: TrustContext): Promise { + const trust = providedTrust ?? await trustContext() + const baselineSnapshot = await snapshot(trust.anchor.verifier_trust_root) + const postSnapshot = await snapshot(trust.anchor.verifier_trust_root, { + capability_root: H('a'), + }) + const adaptation = proposal(baselineSnapshot.state_root) + const authority = await admittedAuthority(baselineSnapshot, [GAP], adaptation) + const unsigned: AdaptationOutcomeInput = { + baseline: { snapshot: baselineSnapshot, gaps: [GAP], proposal: adaptation }, + authority, + terminal_execution: terminal( + authority, + baselineSnapshot.state_root, + postSnapshot.state_root, + ), + post_snapshot: postSnapshot, + post_gaps: [], + verification: verification(trust.verifier.verifier_identity_root), + } + return { input: await certify(unsigned, trust), trust } +} + +describe('assessAdaptationOutcome', () => { + it('proposes preservation only for an independently verified terminal success', async () => { + const { input, trust } = await fixture() + const assessment = await assessAdaptationOutcome(input, trust.anchor) + expect(assessment.state_disposition).toBe('PRESERVE') + expect(assessment.evidence_disposition).toBe('CONFIRM') + expect(assessment.learning_evidence_eligible).toBe(true) + expect(assessment.required_next_gate).toBe('AUTOMATON_3') + expect(assessment.grants_authority).toBe(false) + expect(assessment.executes_mutation).toBe(false) + expect(assessment.updates_competence).toBe(false) + expect(assessment.verifier_trust_policy_digest).toBe(trust.anchor.trust_policy_digest) + }) + + it('cannot learn from Automaton-3 authorization evidence alone', async () => { + const { input, trust } = await fixture() + const { terminal_execution: _terminal, evidence_certificate: _certificate, ...rest } = input + const authorizationOnly = await certify({ + ...rest, + post_snapshot: input.baseline.snapshot, + post_gaps: input.baseline.gaps, + verification: [], + }, trust) + const assessment = await assessAdaptationOutcome(authorizationOnly, trust.anchor) + expect(assessment.state_disposition).toBe('NO_STATE_CHANGE') + expect(assessment.learning_evidence_eligible).toBe(false) + expect(assessment.reason_codes).toContain('TERMINAL_EXECUTION_EVIDENCE_MISSING') + }) + + it('cannot preserve terminal root claims without a verifier certificate', async () => { + const { input, trust } = await fixture() + const { evidence_certificate: _certificate, ...uncertified } = input + const assessment = await assessAdaptationOutcome(uncertified, trust.anchor) + expect(assessment.state_disposition).toBe('REVERT') + expect(assessment.evidence_disposition).toBe('INCONCLUSIVE') + expect(assessment.reason_codes).toContain('EVIDENCE_CERTIFICATE_MISSING') + }) + + it('does not accept a caller-constructed trust anchor', async () => { + const { input, trust } = await fixture() + await expect(assessAdaptationOutcome(input, { ...trust.anchor })).rejects.toThrow( + 'verifier trust anchor was not authenticated', + ) + }) + + it('rejects a trust policy signed by a key other than the expected operator key', async () => { + const operator = await generateKeypair(new Uint8Array(32).fill(41)) + const attacker = await generateKeypair(new Uint8Array(32).fill(42)) + const verifier = await generateKeypair(new Uint8Array(32).fill(43)) + const verifierIdentity: OutcomeVerifierIdentityV1 = { + verifier_key_id: 'rogue-verifier', + verifier_public_key: verifier.publicKey, + verifier_identity_root: await hashVerifierIdentityV1(verifier.publicKey), + verifier_principal_root: H('1'), + verifier_workload_identity_root: H('2'), + } + const unsigned: Omit = { + schema_version: '1.0.0', + policy_kind: 'AEGIS_OUTCOME_VERIFIER_TRUST_POLICY_V1', + governed_policy_root: H('3'), + verifier_trust_root: await hashVerifierTrustSetV1([verifierIdentity]), + verifiers: [verifierIdentity], + signer_key_id: 'attacker-key', + signer_public_key: attacker.publicKey, + } + const policy: OutcomeVerifierTrustPolicyV1 = { + ...unsigned, + signature: await signBytes( + attacker.privateKey, + await canonicalizeOutcomeVerifierTrustPolicyMessageV1(unsigned), + ), + } + await expect(verifyOutcomeVerifierTrustPolicyV1( + policy, + H('3'), + operator.publicKey, + )).rejects.toThrow('trust policy signer is not the expected operator key') + }) + + it('treats an authority denial with unchanged state as inconclusive evidence', async () => { + const { input, trust } = await fixture() + const denied = await admittedAuthority( + input.baseline.snapshot, + input.baseline.gaps, + input.baseline.proposal, + { outcome: 'DENIED', denial_codes: ['APPROVAL_MISSING'] }, + ) + const { terminal_execution: _terminal, evidence_certificate: _certificate, ...rest } = input + const deniedInput = await certify({ + ...rest, + authority: denied, + post_snapshot: input.baseline.snapshot, + post_gaps: input.baseline.gaps, + verification: [], + }, trust) + const assessment = await assessAdaptationOutcome(deniedInput, trust.anchor) + expect(assessment.state_disposition).toBe('NO_STATE_CHANGE') + expect(assessment.reason_codes).toContain('AUTHORITY_DENIED') + }) + + it('proposes reversion when a changed state lacks complete verification', async () => { + const { input, trust } = await fixture() + const incomplete = await certify({ ...input, verification: [input.verification[0]!] }, trust) + const assessment = await assessAdaptationOutcome(incomplete, trust.anchor) + expect(assessment.state_disposition).toBe('REVERT') + expect(assessment.reason_codes).toContain('VERIFICATION_COVERAGE_INCOMPLETE') + }) + + it('degrades evidence after a terminal execution failure', async () => { + const { input, trust } = await fixture() + const failed = await certify({ + ...input, + terminal_execution: terminal( + input.authority, + input.baseline.snapshot.state_root, + input.post_snapshot.state_root, + { outcome: 'FAILED', durable_status: 'FAILED' }, + ), + }, trust) + const assessment = await assessAdaptationOutcome(failed, trust.anchor) + expect(assessment.state_disposition).toBe('REVERT') + expect(assessment.evidence_disposition).toBe('DEGRADE') + }) + + it('treats a completed rollback to the parent as no state change', async () => { + const { input, trust } = await fixture() + const rolledBack = await certify({ + ...input, + terminal_execution: terminal( + input.authority, + input.baseline.snapshot.state_root, + input.baseline.snapshot.state_root, + { outcome: 'ROLLED_BACK', durable_status: 'COMPLETED' }, + ), + post_snapshot: input.baseline.snapshot, + post_gaps: [GAP], + }, trust) + const assessment = await assessAdaptationOutcome(rolledBack, trust.anchor) + expect(assessment.state_disposition).toBe('NO_STATE_CHANGE') + expect(assessment.reason_codes).toContain('EXECUTION_ROLLED_BACK') + }) + + it('proposes reversion when post-state health is unsafe', async () => { + const { input, trust } = await fixture() + const unsafePost = await snapshot(trust.anchor.verifier_trust_root, { + capability_root: H('a'), + health: { ...input.post_snapshot.health, membrane_intact: false }, + }) + const unsafe = await certify({ + ...input, + post_snapshot: unsafePost, + terminal_execution: terminal( + input.authority, + input.baseline.snapshot.state_root, + unsafePost.state_root, + ), + }, trust) + const assessment = await assessAdaptationOutcome(unsafe, trust.anchor) + expect(assessment.state_disposition).toBe('REVERT') + expect(assessment.reason_codes).toContain('POST_STATE_UNHEALTHY') + }) + + it('does not learn from executor self-reported verification', async () => { + const { input, trust } = await fixture() + const selfReported = await certify({ + ...input, + verification: input.verification.map((item, index) => + index === 0 ? { ...item, verification_mode: 'EXECUTOR_SELF_REPORT' } : item), + }, trust) + const assessment = await assessAdaptationOutcome(selfReported, trust.anchor) + expect(assessment.state_disposition).toBe('REVERT') + expect(assessment.learning_evidence_eligible).toBe(false) + expect(assessment.reason_codes).toContain('VERIFICATION_NOT_INDEPENDENT') + }) + + it('requires distinct verifier principals and workloads', async () => { + const samePrincipalTrust = await trustContext(EXECUTOR_PRINCIPAL, EXECUTOR_WORKLOAD) + const { input } = await fixture(samePrincipalTrust) + const assessment = await assessAdaptationOutcome(input, samePrincipalTrust.anchor) + expect(assessment.state_disposition).toBe('REVERT') + expect(assessment.reason_codes).toContain('EVIDENCE_VERIFIER_PRINCIPAL_NOT_INDEPENDENT') + expect(assessment.reason_codes).toContain('EVIDENCE_VERIFIER_WORKLOAD_NOT_INDEPENDENT') + }) + + it('rejects unresolved executor and verifier identity roots', async () => { + const { input, trust } = await fixture() + await expect(assessAdaptationOutcome({ + ...input, + authority: { ...input.authority, executor_principal_root: H('0') }, + }, trust.anchor)).rejects.toThrow('must resolve to a non-zero identity or evidence root') + await expect(trustContext(H('0'), VERIFIER_WORKLOAD)).rejects.toThrow( + 'must resolve to a non-zero identity or evidence root', + ) + await expect(trustContext(VERIFIER_PRINCIPAL, H('0'))).rejects.toThrow( + 'must resolve to a non-zero identity or evidence root', + ) + }) + + it('blocks D2 policy-root transitions even when the evidence is signed', async () => { + const { input, trust } = await fixture() + const post = await snapshot(trust.anchor.verifier_trust_root, { + policy_root: H('f'), + capability_root: H('a'), + }) + const changed = await certify({ + ...input, + post_snapshot: post, + terminal_execution: terminal( + input.authority, + input.baseline.snapshot.state_root, + post.state_root, + ), + }, trust) + const assessment = await assessAdaptationOutcome(changed, trust.anchor) + expect(assessment.state_disposition).toBe('REVERT') + expect(assessment.reason_codes).toContain('POLICY_TRANSITION_REQUIRES_D4') + }) + + it('does not accept trust-root rotation based only on a D4 label', async () => { + const { input, trust } = await fixture() + const d4Proposal = proposal(input.baseline.snapshot.state_root, { + consequence_class: 'D4', + operator_approval_reference: 'approval:operator', + constitutional_change_reference: 'constitution:change-001', + }) + const authority = await admittedAuthority(input.baseline.snapshot, [GAP], d4Proposal) + const post = await snapshot(H('f'), { capability_root: H('a') }) + const rotated = await certify({ + ...input, + baseline: { ...input.baseline, proposal: d4Proposal }, + authority, + post_snapshot: post, + terminal_execution: terminal( + authority, + input.baseline.snapshot.state_root, + post.state_root, + ), + }, trust) + const assessment = await assessAdaptationOutcome(rotated, trust.anchor) + expect(assessment.state_disposition).toBe('REVERT') + expect(assessment.reason_codes).toContain('VERIFIER_TRUST_ROTATION_EVIDENCE_REQUIRED') + }) + + it('proposes reversion for new or unaddressed critical invariant gaps', async () => { + const { input, trust } = await fixture() + const criticalGap: KnowledgeGap = { + gap_id: 'gap.outcome.critical', + kind: 'INVARIANT_BREACH', + severity: 'CRITICAL', + evidence_refs: [H('f')], + } + const withGap = await certify({ ...input, post_gaps: [criticalGap] }, trust) + const assessment = await assessAdaptationOutcome(withGap, trust.anchor) + expect(assessment.state_disposition).toBe('REVERT') + expect(assessment.reason_codes).toContain('UNSAFE_POST_GAP') + }) + + it('does not preserve evidence with broken terminal bindings', async () => { + const { input, trust } = await fixture() + const broken = await certify({ + ...input, + terminal_execution: terminal( + input.authority, + input.baseline.snapshot.state_root, + input.post_snapshot.state_root, + { policy_decision_root: H('6') }, + ), + }, trust) + const assessment = await assessAdaptationOutcome(broken, trust.anchor) + expect(assessment.state_disposition).toBe('REVERT') + expect(assessment.reason_codes).toContain('TERMINAL_POLICY_DECISION_MISMATCH') + }) + + it('rejects duplicate and out-of-range verification step indices', async () => { + const { input, trust } = await fixture() + await expect(assessAdaptationOutcome({ + ...input, + verification: [input.verification[0]!, { ...input.verification[1]!, step_index: 0 }], + }, trust.anchor)).rejects.toThrow(OutcomeComparisonError) + await expect(assessAdaptationOutcome({ + ...input, + verification: [{ ...input.verification[0]!, step_index: 2 }], + }, trust.anchor)).rejects.toThrow(OutcomeComparisonError) + }) + + it('is deterministic across semantically equivalent verification ordering', async () => { + const { input, trust } = await fixture() + const reversed = await certify({ + ...input, + verification: [...input.verification].reverse(), + }, trust) + const [first, second] = await Promise.all([ + assessAdaptationOutcome(input, trust.anchor), + assessAdaptationOutcome(reversed, trust.anchor), + ]) + expect(first.assessment_digest).toBe(second.assessment_digest) + expect(Object.isFrozen(first)).toBe(true) + }) + + it('reassesses signed evidence inside the append boundary', async () => { + const { input, trust } = await fixture() + const { artifacts, store } = memoryArtifactStore() + const tamperedAfterSigning = { + ...input, + post_gaps: [{ + gap_id: 'gap.forged-after-signing', + kind: 'INVARIANT_BREACH' as const, + severity: 'CRITICAL' as const, + evidence_refs: [H('f')], + }], + } + const observed = await recordOutcomeAssessment( + MetacognitiveLoop.empty(), + tamperedAfterSigning, + trust.anchor, + store, + SEQ(1), + ) + expect(observed.assessment.state_disposition).toBe('REVERT') + expect(observed.assessment.evidence_certificate_verified).toBe(false) + expect(observed.assessment.reason_codes).toContain('EVIDENCE_BUNDLE_DIGEST_MISMATCH') + expect(artifacts.get(observed.artifact.artifact_root)).toBe(observed.artifact) + expect(observed.persistence.artifact_reference).toBe( + `memory:${observed.artifact.artifact_root}`, + ) + expect(observed.entry.observation.signal).toContain(observed.artifact.artifact_root) + }) + + it('does not append when outcome evidence persistence fails or lies about the root', async () => { + const { input, trust } = await fixture() + const loop = MetacognitiveLoop.empty() + const failingStore: OutcomeEvidenceArtifactStore = { + async persist() { throw new Error('store unavailable') }, + } + await expect(recordOutcomeAssessment( + loop, + input, + trust.anchor, + failingStore, + SEQ(1), + )).rejects.toThrow('outcome evidence persistence failed') + expect(loop.length).toBe(0) + + const mismatchedStore: OutcomeEvidenceArtifactStore = { + async persist() { + return { artifact_root: H('f'), artifact_reference: `memory:${H('f')}` } + }, + } + await expect(recordOutcomeAssessment( + loop, + input, + trust.anchor, + mismatchedStore, + SEQ(1), + )).rejects.toThrow('outcome evidence persistence root mismatch') + expect(loop.length).toBe(0) + }) +}) diff --git a/sovereign-omega-v2/test/unit/self-regulator.test.ts b/sovereign-omega-v2/test/unit/self-regulator.test.ts index 0095e73c5..24a01d4a1 100644 --- a/sovereign-omega-v2/test/unit/self-regulator.test.ts +++ b/sovereign-omega-v2/test/unit/self-regulator.test.ts @@ -5,6 +5,7 @@ import { describe, expect, it } from 'vitest' import type { SHA256Hex } from '../../src/core/types.js' import { + hashSelfModelStateRootV1, SelfRegulationError, regulateSelf, } from '../../src/metacognition/self-regulator.js' @@ -16,14 +17,14 @@ import type { const H = (character: string) => character.repeat(64) as SHA256Hex -function snapshot(overrides: Partial = {}): SelfModelSnapshot { - return { - state_root: H('1'), +async function snapshot(overrides: Partial = {}): Promise { + const components = { identity_root: H('2'), policy_root: H('3'), capability_root: H('4'), memory_root: H('5'), metacognition_root: H('6'), + verifier_trust_root: H('7'), health: { t0_verdict: true, corruption_count: 0, @@ -32,21 +33,22 @@ function snapshot(overrides: Partial = {}): SelfMod ...overrides, }, } + return { state_root: await hashSelfModelStateRootV1(components), ...components } } const GAP: KnowledgeGap = { gap_id: 'gap.self-model.001', kind: 'CAPABILITY_DEFICIT', severity: 'HIGH', - evidence_refs: ['evidence:unit-test'], + evidence_refs: [H('8')], } -function proposal(overrides: Partial = {}): AdaptationProposal { +function proposal(parentStateRoot: SHA256Hex, overrides: Partial = {}): AdaptationProposal { return { proposal_id: 'proposal.self-model.001', objective: 'Add a deterministic self-regulation transition.', consequence_class: 'D2', - expected_parent_state_root: H('1'), + expected_parent_state_root: parentStateRoot, addressed_gap_ids: [GAP.gap_id], requested_capabilities: ['repo.file.propose'], mutations: [{ path: 'src/metacognition/regulator.ts', operation: 'CREATE' }], @@ -56,25 +58,31 @@ function proposal(overrides: Partial = {}): AdaptationPropos } } +function withoutRollback(candidate: AdaptationProposal): AdaptationProposal { + const { rollback_reference: _rollbackReference, ...proposalWithoutRollback } = candidate + return proposalWithoutRollback +} + describe('regulateSelf', () => { it('returns NO_CHANGE when no verified gap exists', async () => { - const decision = await regulateSelf({ snapshot: snapshot(), gaps: [] }) + const decision = await regulateSelf({ snapshot: await snapshot(), gaps: [] }) expect(decision.mode).toBe('NO_CHANGE') expect(decision.required_next_gate).toBe('NONE') expect(decision.grants_authority).toBe(false) }) it('requires a proposal when verified gaps exist', async () => { - const decision = await regulateSelf({ snapshot: snapshot(), gaps: [GAP] }) + const decision = await regulateSelf({ snapshot: await snapshot(), gaps: [GAP] }) expect(decision.mode).toBe('PROPOSAL_REQUIRED') expect(decision.required_next_gate).toBe('OPERATOR_REVIEW') }) it('halts on a membrane breach', async () => { + const model = await snapshot({ membrane_intact: false }) const decision = await regulateSelf({ - snapshot: snapshot({ membrane_intact: false }), + snapshot: model, gaps: [GAP], - proposal: proposal(), + proposal: proposal(model.state_root), }) expect(decision.mode).toBe('HALT') expect(decision.reasons).toContain('MEMBRANE_BREACH') @@ -82,51 +90,223 @@ describe('regulateSelf', () => { }) it('enters observation-only mode when adaptation exceeds replay capacity', async () => { + const model = await snapshot({ entropy_bounded: false }) const decision = await regulateSelf({ - snapshot: snapshot({ entropy_bounded: false }), + snapshot: model, gaps: [GAP], - proposal: proposal(), + proposal: proposal(model.state_root), }) expect(decision.mode).toBe('OBSERVE_ONLY') expect(decision.requires_automaton3).toBe(false) }) it('rejects a stale parent state', async () => { + const model = await snapshot() const decision = await regulateSelf({ - snapshot: snapshot(), + snapshot: model, gaps: [GAP], - proposal: proposal({ expected_parent_state_root: H('9') }), + proposal: proposal(model.state_root, { expected_parent_state_root: H('9') }), }) expect(decision.mode).toBe('REJECTED') expect(decision.reasons).toContain('STALE_PARENT_STATE') }) it('rejects capabilities that could bypass authority', async () => { + const model = await snapshot() const decision = await regulateSelf({ - snapshot: snapshot(), + snapshot: model, gaps: [GAP], - proposal: proposal({ requested_capabilities: ['authority.grant'] }), + proposal: proposal(model.state_root, { requested_capabilities: ['authority.grant'] }), }) expect(decision.mode).toBe('REJECTED') expect(decision.reasons).toContain('FORBIDDEN_CAPABILITY_REQUEST') }) + it.each([ + '', + ' ', + '.', + '..', + '/src/absolute.ts', + './src/dot.ts', + 'src/./dot.ts', + 'src/../escape.ts', + 'src//duplicate-separator.ts', + 'src/trailing/', + 'C:/repo/file.ts', + 'C:\\repo\\file.ts', + 'file:src/file.ts', + 'https://example.test/file.ts', + 'src\\windows.ts', + 'src/trailing-dot.', + 'src/trailing-space ', + 'src/CON', + 'src/con.txt', + 'src/PRN.json', + 'src/aux', + 'src/NUL.log', + 'src/com1', + 'src/COM9.txt', + 'src/lpt1', + 'src/LPT9.log', + ])('rejects a non-canonical repository mutation path: %s', async path => { + const model = await snapshot() + await expect(regulateSelf({ + snapshot: model, + gaps: [GAP], + proposal: proposal(model.state_root, { + mutations: [{ path, operation: 'CREATE' }], + }), + })).rejects.toThrow(SelfRegulationError) + }) + + it('rejects duplicate operations on the same mutation path', async () => { + const model = await snapshot() + await expect(regulateSelf({ + snapshot: model, + gaps: [GAP], + proposal: proposal(model.state_root, { + mutations: [ + { path: 'src/metacognition/regulator.ts', operation: 'UPDATE' }, + { path: 'src/metacognition/regulator.ts', operation: 'UPDATE' }, + ], + }), + })).rejects.toThrow('path has a duplicate operation') + }) + + it('rejects conflicting operations on the same mutation path', async () => { + const model = await snapshot() + await expect(regulateSelf({ + snapshot: model, + gaps: [GAP], + proposal: proposal(model.state_root, { + mutations: [ + { path: 'src/metacognition/regulator.ts', operation: 'UPDATE' }, + { path: 'src/metacognition/regulator.ts', operation: 'DELETE' }, + ], + }), + })).rejects.toThrow('path has a conflicting operation') + }) + + it('rejects mutation targets that collide under Windows case folding', async () => { + const model = await snapshot() + await expect(regulateSelf({ + snapshot: model, + gaps: [GAP], + proposal: proposal(model.state_root, { + mutations: [ + { path: 'src/metacognition/Regulator.ts', operation: 'UPDATE' }, + { path: 'SRC/METACOGNITION/regulator.ts', operation: 'UPDATE' }, + ], + }), + })).rejects.toThrow('path collides after Windows case folding') + }) + + it.each([ + 'src/com10.ts', + 'src/lpt0.ts', + 'src/conifer.ts', + ])('accepts non-device names adjacent to Windows reserved names: %s', async path => { + const model = await snapshot() + const decision = await regulateSelf({ + snapshot: model, + gaps: [GAP], + proposal: proposal(model.state_root, { + mutations: [{ path, operation: 'CREATE' }], + }), + }) + expect(decision.mode).toBe('READY_FOR_AUTHORITY') + }) + + it('rejects every D0 proposal that attempts repository mutation', async () => { + const model = await snapshot() + const decision = await regulateSelf({ + snapshot: model, + gaps: [GAP], + proposal: withoutRollback(proposal(model.state_root, { + consequence_class: 'D0', + })), + }) + expect(decision.mode).toBe('REJECTED') + expect(decision.reasons).toContain('D0_MUTATION_FORBIDDEN') + expect(decision.requires_automaton3).toBe(false) + }) + + it('requires rollback evidence for D1 repository mutation', async () => { + const model = await snapshot() + const decision = await regulateSelf({ + snapshot: model, + gaps: [GAP], + proposal: withoutRollback(proposal(model.state_root, { + consequence_class: 'D1', + })), + }) + expect(decision.mode).toBe('REJECTED') + expect(decision.reasons).toContain('ROLLBACK_REFERENCE_REQUIRED') + }) + + it('routes a D1 mutation with rollback evidence to authority evaluation', async () => { + const model = await snapshot() + const decision = await regulateSelf({ + snapshot: model, + gaps: [GAP], + proposal: proposal(model.state_root, { consequence_class: 'D1' }), + }) + expect(decision.mode).toBe('READY_FOR_AUTHORITY') + expect(decision.required_next_gate).toBe('AUTOMATON_3') + }) + it('requires explicit approval for D3 proposals', async () => { + const model = await snapshot() const decision = await regulateSelf({ - snapshot: snapshot(), + snapshot: model, gaps: [GAP], - proposal: proposal({ consequence_class: 'D3' }), + proposal: proposal(model.state_root, { consequence_class: 'D3' }), }) expect(decision.mode).toBe('REJECTED') expect(decision.reasons).toContain('OPERATOR_APPROVAL_REQUIRED') expect(decision.required_next_gate).toBe('OPERATOR_REVIEW') }) + it('fails closed on whitespace-only rollback and approval references', async () => { + const model = await snapshot() + await expect(regulateSelf({ + snapshot: model, + gaps: [GAP], + proposal: proposal(model.state_root, { + consequence_class: 'D3', + rollback_reference: ' ', + operator_approval_reference: '\t', + }), + })).rejects.toThrow(SelfRegulationError) + }) + + it('fails closed on a whitespace-only constitutional change reference', async () => { + const model = await snapshot() + await expect(regulateSelf({ + snapshot: model, + gaps: [GAP], + proposal: proposal(model.state_root, { + consequence_class: 'D4', + operator_approval_reference: 'approval:operator', + constitutional_change_reference: ' ', + }), + })).rejects.toThrow(SelfRegulationError) + }) + + it('does not treat an evidence-free gap as verified', async () => { + await expect(regulateSelf({ + snapshot: await snapshot(), + gaps: [{ ...GAP, evidence_refs: [] }], + })).rejects.toThrow(SelfRegulationError) + }) + it('routes a bounded, replayable proposal to Automaton-3', async () => { + const model = await snapshot() const decision = await regulateSelf({ - snapshot: snapshot(), + snapshot: model, gaps: [GAP], - proposal: proposal(), + proposal: proposal(model.state_root), }) expect(decision.mode).toBe('READY_FOR_AUTHORITY') expect(decision.required_next_gate).toBe('AUTOMATON_3') @@ -136,7 +316,8 @@ describe('regulateSelf', () => { }) it('is deterministic for identical self-models and proposals', async () => { - const input = { snapshot: snapshot(), gaps: [GAP], proposal: proposal() } + const model = await snapshot() + const input = { snapshot: model, gaps: [GAP], proposal: proposal(model.state_root) } const [first, second] = await Promise.all([regulateSelf(input), regulateSelf(input)]) expect(first.self_model_digest).toBe(second.self_model_digest) expect(first.proposal_digest).toBe(second.proposal_digest) @@ -144,7 +325,15 @@ describe('regulateSelf', () => { }) it('fails closed on malformed self-model roots', async () => { - const malformed = { ...snapshot(), state_root: 'not-a-hash' as SHA256Hex } + const malformed = { ...await snapshot(), state_root: 'not-a-hash' as SHA256Hex } await expect(regulateSelf({ snapshot: malformed, gaps: [] })).rejects.toThrow(SelfRegulationError) }) + + it('fails closed when state_root does not bind the component roots', async () => { + const model = await snapshot() + const forged = { ...model, capability_root: H('a') } + await expect(regulateSelf({ snapshot: forged, gaps: [] })).rejects.toThrow( + 'snapshot.state_root does not bind the self-model components', + ) + }) }) From 2d52aeaef9e74868fa78fbe04dbaf0eba6418660 Mon Sep 17 00:00:00 2001 From: tarikskalic33 <228550385+tarikskalic33@users.noreply.github.com> Date: Tue, 28 Jul 2026 14:46:31 +0200 Subject: [PATCH 40/89] feat: persist authenticated outcome evidence --- ...DR-0021-automaton-3-sovereign-execution.md | 17 + sovereign-omega-v2/src/consensus/crypto.ts | 15 +- sovereign-omega-v2/src/core/hashing.ts | 32 +- sovereign-omega-v2/src/core/i-json.ts | 109 +++ .../src/metacognition/outcome-comparator.ts | 237 ++++- .../outcome-evidence-artifact-store.ts | 332 +++++++ .../metacognition/outcome-evidence-replay.ts | 143 +++ .../src/metacognition/self-regulator.ts | 20 +- .../test/helpers/outcome-evidence-fixture.ts | 279 ++++++ .../metacognitive-outcome-closure.test.ts | 832 ++++++++++++++---- sovereign-omega-v2/test/unit/i-json.test.ts | 72 ++ .../test/unit/misc-coverage-23.test.ts | 4 +- .../test/unit/outcome-comparator.test.ts | 36 + .../outcome-evidence-artifact-store.test.ts | 279 ++++++ .../outcome-evidence-browser-boundary.test.ts | 38 + .../test/unit/self-regulator.test.ts | 15 + 16 files changed, 2197 insertions(+), 263 deletions(-) create mode 100644 sovereign-omega-v2/src/core/i-json.ts create mode 100644 sovereign-omega-v2/src/metacognition/outcome-evidence-artifact-store.ts create mode 100644 sovereign-omega-v2/src/metacognition/outcome-evidence-replay.ts create mode 100644 sovereign-omega-v2/test/helpers/outcome-evidence-fixture.ts create mode 100644 sovereign-omega-v2/test/unit/i-json.test.ts create mode 100644 sovereign-omega-v2/test/unit/outcome-evidence-artifact-store.test.ts create mode 100644 sovereign-omega-v2/test/unit/outcome-evidence-browser-boundary.test.ts diff --git a/docs/adr/ADR-0021-automaton-3-sovereign-execution.md b/docs/adr/ADR-0021-automaton-3-sovereign-execution.md index 93ebd10e5..4b28793f5 100644 --- a/docs/adr/ADR-0021-automaton-3-sovereign-execution.md +++ b/docs/adr/ADR-0021-automaton-3-sovereign-execution.md @@ -45,3 +45,20 @@ The deterministic `repository_root` and `workspace_root` are the logical root `. ## External-runtime boundary This PR implements a deterministic local reference model and interfaces for durable execution. It does not claim that Temporal, LangGraph, Kubernetes, or any cloud worker runtime is deployed. + +## Authenticated outcome-evidence boundary + +Post-execution learning is a separate, advisory boundary in `sovereign-omega-v2/src/metacognition/`: + +1. `outcome-comparator.ts` re-derives an assessment from baseline, authority, terminal, post-state, and verification evidence. It distinguishes cryptographic certificate authentication from transition admissibility: authenticated evidence of a denied, failed, or unsafe outcome remains recordable as negative evidence. +2. `outcome-evidence-replay.ts` snapshots the untrusted evidence as closed I-JSON, takes the governed policy root, operator public key, and sequence from a separate host context, rejects stale loop or trust bindings before persistence, authenticates the signed verifier policy and evidence certificate, and re-evaluates inside the append boundary. +3. `outcome-evidence-artifact-store.ts` uses an add-only, content-addressed IndexedDB store. It rejects non-I-JSON aliases, re-verifies the embedded signed trust policy, normalizes the evidence input, re-derives the assessment, recomputes the artifact root, and reads the exact artifact back before the replay adapter returns a new immutable metacognitive loop. +4. The complete signed verifier trust policy is part of the artifact. After close and reopen, a caller with the out-of-band operator key and governed policy root can reauthenticate and deterministically replay the artifact. A policy's equality with the baseline is a binding check, not proof of temporal freshness or revocation status. + +The assessment remains non-authoritative: it cannot preserve or revert state, execute a mutation, grant authority, or update competence. Any recommendation still requires its declared next gate. + +## Current provenance limit + +The independent verifier certificate signs the complete evidence bundle, including the terminal receipt roots. The current TypeScript adapter does not resolve the underlying Python lease and mutation receipts or verify native signatures on those raw terminal records; those records do not yet carry such signatures. Therefore the persisted artifact is verifier-attested T2 evidence, not a claim that every terminal receipt was independently reconstructed from a durable cross-runtime source. + +No cockpit, game, or MCP status resource is exposed from this slice. A read-back failure can leave an add-only orphan artifact, but the caller receives no advanced loop. Projection should be added only after a confined cross-runtime artifact transport or witness chain makes the underlying terminal provenance independently resolvable. diff --git a/sovereign-omega-v2/src/consensus/crypto.ts b/sovereign-omega-v2/src/consensus/crypto.ts index a8b595eac..8e2673ce5 100644 --- a/sovereign-omega-v2/src/consensus/crypto.ts +++ b/sovereign-omega-v2/src/consensus/crypto.ts @@ -6,24 +6,17 @@ // FIPS 186-5, ZIP215). Replaces Gate 19 FNV-1a stub. // // Key properties: -// - Async: no WebCrypto dependency (uses noble's built-in sha512) +// - Async: uses the browser/modern-Node Web Crypto SHA-512 path // - Deterministic: same (privateKey, message) → same signature // - Zero network I/O // - generateKeypair(seed) → deterministic from 32-byte seed // ============================================================ import * as ed from '@noble/ed25519' -import { createHash } from 'node:crypto' import { uint8ArrayToHex, hexToUint8Array } from '../core/hashing.js' import type { SHA256Hex } from '../core/types.js' import type { ValidatorPublicKey, ValidatorSignature, ValidatorKeyPair } from './types.js' -// Wire Node.js sha512 into @noble/ed25519 v3 (required outside browser). -// The type cast is necessary because Node's Buffer.digest() is typed as -// Uint8Array while noble expects Uint8Array. -;(ed.hashes as Record)['sha512'] = (msg: Uint8Array): Uint8Array => - Uint8Array.from(createHash('sha512').update(msg).digest()) - // ─── Key generation ──────────────────────────────────────── /** @@ -36,7 +29,7 @@ export async function generateKeypair(seed: Uint8Array): Promise { - const signature = await ed.sign(message, privateKey) + const signature = await ed.signAsync(message, privateKey) return uint8ArrayToHex(signature) } @@ -56,7 +49,7 @@ export async function verifyBytes( signatureHex: string, ): Promise { try { - return await ed.verify( + return await ed.verifyAsync( hexToUint8Array(signatureHex), message, hexToUint8Array(publicKeyHex), diff --git a/sovereign-omega-v2/src/core/hashing.ts b/sovereign-omega-v2/src/core/hashing.ts index e2130c45f..7d526d760 100644 --- a/sovereign-omega-v2/src/core/hashing.ts +++ b/sovereign-omega-v2/src/core/hashing.ts @@ -6,7 +6,7 @@ // All hashing uses explicit byte-level operations: // - UTF-8 encode before hashing (never hash strings directly) // - Byte-concatenation for Merkle nodes (never string concat) -// - Cross-platform: Web Crypto API with Node crypto fallback +// - Cross-platform: Web Crypto API with an isolated modern-Node fallback // ============================================================ import type { SHA256Hex } from './types.js' @@ -16,7 +16,7 @@ import { canonicalizeJCS } from './canonicalize.js' /** * Compute SHA-256 over raw bytes. Returns hex string. - * Uses Web Crypto API (browser/WASM compatible) with Node fallback. + * Uses Web Crypto API, with a bundler-safe modern-Node fallback. */ export async function sha256Hex(input: Uint8Array): Promise { const bytes = await sha256Bytes(input) @@ -24,18 +24,28 @@ export async function sha256Hex(input: Uint8Array): Promise { } export async function sha256Bytes(input: Uint8Array): Promise { - // Web Crypto API (browser, WASM, modern Node) - /* c8 ignore next -- Node.js crypto fallback; Web Crypto available in all modern environments and test environments */ - if (typeof globalThis.crypto?.subtle !== 'undefined') { - const digest = await globalThis.crypto.subtle.digest('SHA-256', input as BufferSource) + const subtle = globalThis.crypto?.subtle + if (subtle !== undefined) { + const digest = await subtle.digest('SHA-256', input as BufferSource) return new Uint8Array(digest) } - // Node.js fallback (older environments) - const { createHash } = await import('node:crypto') - const hash = createHash('sha256') - hash.update(input) - return new Uint8Array(hash.digest()) + const runtimeProcess = (globalThis as typeof globalThis & { + process?: { + readonly versions?: { readonly node?: string } + getBuiltinModule?(specifier: string): unknown + } + }).process + if (typeof runtimeProcess?.versions?.node !== 'string' || + typeof runtimeProcess.getBuiltinModule !== 'function') { + throw new Error('Web Crypto API is required for SHA-256 outside modern Node') + } + const nodeCrypto = runtimeProcess.getBuiltinModule('crypto') as { + createHash(algorithm: string): { + update(value: Uint8Array): { digest(): Uint8Array } + } + } + return Uint8Array.from(nodeCrypto.createHash('sha256').update(input).digest()) } /** diff --git a/sovereign-omega-v2/src/core/i-json.ts b/sovereign-omega-v2/src/core/i-json.ts new file mode 100644 index 000000000..f0eb60f43 --- /dev/null +++ b/sovereign-omega-v2/src/core/i-json.ts @@ -0,0 +1,109 @@ +// ============================================================ +// SOVEREIGN OMEGA - I-JSON Runtime Boundary +// EPISTEMIC TIER: T2 - deterministic, tested validation primitive +// +// Integrity roots are defined over JSON values. Reject JavaScript values +// whose canonical form can alias a different stored value (for example, +// bigint versus string or an omitted undefined member). +// ============================================================ + +export class IJsonValidationError extends Error { + override readonly name = 'IJsonValidationError' + + constructor(message: string) { + super(message) + Object.setPrototypeOf(this, new.target.prototype) + } +} + +export function assertIJsonValue(value: unknown, label = 'value'): void { + visit(value, label, new WeakSet()) +} + +function visit(value: unknown, path: string, ancestors: WeakSet): void { + if (value === null || typeof value === 'boolean') return + if (typeof value === 'string') { + assertWellFormedUnicode(value, path) + return + } + if (typeof value === 'number') { + if (!Number.isFinite(value)) { + throw new IJsonValidationError(`${path} must contain only finite numbers`) + } + if (Object.is(value, -0)) { + throw new IJsonValidationError(`${path} must not contain negative zero`) + } + return + } + if (typeof value !== 'object') { + throw new IJsonValidationError(`${path} contains a non-JSON ${typeof value} value`) + } + + const object = value as object + if (ancestors.has(object)) { + throw new IJsonValidationError(`${path} contains a cycle`) + } + ancestors.add(object) + try { + if (Array.isArray(value)) { + if (Object.getOwnPropertySymbols(value).length !== 0) { + throw new IJsonValidationError(`${path} must not contain symbol keys`) + } + const ownNames = Object.getOwnPropertyNames(value) + const keys = Object.keys(value) + if (ownNames.length !== value.length + 1 || !ownNames.includes('length') || + keys.length !== value.length) { + throw new IJsonValidationError(`${path} must not contain sparse or extended arrays`) + } + for (let index = 0; index < value.length; index += 1) { + if (!Object.prototype.hasOwnProperty.call(value, index) || keys[index] !== String(index)) { + throw new IJsonValidationError(`${path} must not contain sparse or extended arrays`) + } + const descriptor = Object.getOwnPropertyDescriptor(value, String(index)) + if (descriptor === undefined || !descriptor.enumerable || !('value' in descriptor)) { + throw new IJsonValidationError(`${path}[${index}] must be an enumerable data property`) + } + visit(descriptor.value, `${path}[${index}]`, ancestors) + } + return + } + + const prototype = Object.getPrototypeOf(value) + if (prototype !== Object.prototype && prototype !== null) { + throw new IJsonValidationError(`${path} must contain only plain objects`) + } + if (Object.getOwnPropertySymbols(value).length !== 0) { + throw new IJsonValidationError(`${path} must not contain symbol keys`) + } + const ownNames = Object.getOwnPropertyNames(value) + const enumerableKeys = Object.keys(value) + if (ownNames.length !== enumerableKeys.length) { + throw new IJsonValidationError(`${path} must contain only enumerable data properties`) + } + for (const key of ownNames) { + assertWellFormedUnicode(key, `${path} key`) + const descriptor = Object.getOwnPropertyDescriptor(value, key) + if (descriptor === undefined || !descriptor.enumerable || !('value' in descriptor)) { + throw new IJsonValidationError(`${path}.${key} must be an enumerable data property`) + } + visit(descriptor.value, `${path}.${key}`, ancestors) + } + } finally { + ancestors.delete(object) + } +} + +function assertWellFormedUnicode(value: string, path: string): void { + for (let index = 0; index < value.length; index += 1) { + const codeUnit = value.charCodeAt(index) + if (codeUnit >= 0xD800 && codeUnit <= 0xDBFF) { + const next = value.charCodeAt(index + 1) + if (!(next >= 0xDC00 && next <= 0xDFFF)) { + throw new IJsonValidationError(`${path} contains an unpaired UTF-16 surrogate`) + } + index += 1 + } else if (codeUnit >= 0xDC00 && codeUnit <= 0xDFFF) { + throw new IJsonValidationError(`${path} contains an unpaired UTF-16 surrogate`) + } + } +} diff --git a/sovereign-omega-v2/src/metacognition/outcome-comparator.ts b/sovereign-omega-v2/src/metacognition/outcome-comparator.ts index 99379188b..a339f120a 100644 --- a/sovereign-omega-v2/src/metacognition/outcome-comparator.ts +++ b/sovereign-omega-v2/src/metacognition/outcome-comparator.ts @@ -131,6 +131,7 @@ export interface VerifiedOutcomeVerifierTrustAnchorV1 { readonly verifier_trust_root: SHA256Hex readonly verifiers: readonly OutcomeVerifierIdentityV1[] readonly trust_policy_digest: SHA256Hex + readonly trust_policy: OutcomeVerifierTrustPolicyV1 } export interface AdaptationOutcomeInput { @@ -167,6 +168,9 @@ export interface AdaptationOutcomeAssessment { readonly terminal_receipt_root: SHA256Hex | null readonly evidence_bundle_digest: SHA256Hex readonly evidence_certificate_digest: SHA256Hex | null + /** Cryptographic identity, signature, bundle, and baseline bindings passed. */ + readonly evidence_certificate_authenticated: boolean + /** Authentication passed and the proposed component transition was admissible. */ readonly evidence_certificate_verified: boolean readonly verifier_trust_policy_digest: SHA256Hex readonly pre_state_root: SHA256Hex @@ -183,12 +187,13 @@ export interface AdaptationOutcomeAssessment { export interface OutcomeEvidenceArtifactV1 { readonly schema_version: typeof OUTCOME_COMPARATOR_SCHEMA_VERSION readonly artifact_kind: 'AEGIS_OUTCOME_EVIDENCE_ARTIFACT_V1' - readonly evidence_input: Readonly> + readonly evidence_input: AdaptationOutcomeInput readonly verifier_trust_anchor: { readonly governed_policy_root: SHA256Hex readonly verifier_trust_root: SHA256Hex readonly verifiers: readonly OutcomeVerifierIdentityV1[] readonly trust_policy_digest: SHA256Hex + readonly trust_policy: OutcomeVerifierTrustPolicyV1 } readonly assessment: AdaptationOutcomeAssessment readonly artifact_root: SHA256Hex @@ -230,6 +235,17 @@ const VERDICTS = new Set(['PASS', 'FAIL', 'INCONCLUSIVE']) const VERIFICATION_MODES = new Set(['INDEPENDENT', 'EXECUTOR_SELF_REPORT']) const VERIFIED_TRUST_ANCHORS = new WeakSet() +function assertExactKeys(field: string, value: unknown, expectedKeys: readonly string[]): void { + if (value === null || typeof value !== 'object' || Array.isArray(value)) { + throw new OutcomeComparisonError(`${field} must be an object`) + } + const keys = Object.keys(value).sort(compareUtf8) + const expected = [...expectedKeys].sort(compareUtf8) + if (keys.length !== expected.length || keys.some((key, index) => key !== expected[index])) { + throw new OutcomeComparisonError(`${field} has unexpected or missing fields`) + } +} + function assertHash(field: string, value: unknown): asserts value is SHA256Hex { if (typeof value !== 'string' || !HASH_PATTERN.test(value)) { throw new OutcomeComparisonError(`${field} must be lowercase SHA-256 hex`) @@ -257,9 +273,9 @@ function normalizeCodes(field: string, values: readonly string[]): readonly stri } function validateAuthorityBinding(binding: AdaptationAuthorityBindingV1): void { - assertHash('authority.action_binding.proposal_digest', binding.proposal_digest) - assertHash('authority.action_binding.self_regulation_decision_digest', binding.self_regulation_decision_digest) - assertHash('authority.action_binding.expected_parent_state_root', binding.expected_parent_state_root) + assertResolvedHash('authority.action_binding.proposal_digest', binding.proposal_digest) + assertResolvedHash('authority.action_binding.self_regulation_decision_digest', binding.self_regulation_decision_digest) + assertResolvedHash('authority.action_binding.expected_parent_state_root', binding.expected_parent_state_root) } function normalizeVerifierPublicKeys(values: readonly string[]): readonly string[] { @@ -294,6 +310,13 @@ async function normalizeVerifierIdentities( throw new OutcomeComparisonError('verifiers must be a non-empty array') } const normalized = await Promise.all(values.map(async (value, index) => { + assertExactKeys(`verifiers[${index}]`, value, [ + 'verifier_identity_root', + 'verifier_key_id', + 'verifier_principal_root', + 'verifier_public_key', + 'verifier_workload_identity_root', + ]) if (!SAFE_KEY_ID_PATTERN.test(value.verifier_key_id)) { throw new OutcomeComparisonError(`verifiers[${index}].verifier_key_id is invalid`) } @@ -338,6 +361,15 @@ export async function hashVerifierTrustSetV1( export async function canonicalizeOutcomeVerifierTrustPolicyMessageV1( policy: Omit, ): Promise { + assertExactKeys('trust_policy', policy, [ + 'governed_policy_root', + 'policy_kind', + 'schema_version', + 'signer_key_id', + 'signer_public_key', + 'verifier_trust_root', + 'verifiers', + ]) if (policy.schema_version !== '1.0.0') { throw new OutcomeComparisonError('trust policy schema_version is invalid') } @@ -391,15 +423,22 @@ export async function verifyOutcomeVerifierTrustPolicyV1( if (!await verifyBytes(operatorPublicKey, message, policy.signature)) { throw new OutcomeComparisonError('trust policy signature is invalid') } + const authenticatedPolicy = deepFreeze({ + ...unsignedPolicy, + signer_public_key: operatorPublicKey, + verifiers, + signature: policy.signature, + }) const trust_policy_digest = await hashValue({ domain: 'AEGIS_OUTCOME_VERIFIER_TRUST_POLICY_RECORD_V1', - policy: { ...unsignedPolicy, verifiers, signature: policy.signature }, + policy: authenticatedPolicy, }) const anchor = deepFreeze({ governed_policy_root: expectedGovernedPolicyRoot, verifier_trust_root: verifierTrustRoot, verifiers, trust_policy_digest, + trust_policy: authenticatedPolicy, }) VERIFIED_TRUST_ANCHORS.add(anchor) return anchor @@ -408,6 +447,15 @@ export async function verifyOutcomeVerifierTrustPolicyV1( export function canonicalizeOutcomeEvidenceCertificateMessageV1( certificate: Omit, ): Uint8Array { + assertExactKeys('evidence_certificate', certificate, [ + 'certificate_kind', + 'evidence_bundle_digest', + 'verifier_identity_root', + 'verifier_key_id', + 'verifier_principal_root', + 'verifier_public_key', + 'verifier_workload_identity_root', + ]) validateEvidenceCertificateFields(certificate) return canonicalizeJCS({ domain: 'AEGIS_OUTCOME_EVIDENCE_CERTIFICATE_V1', @@ -484,7 +532,7 @@ function validateTerminalEvidence(terminal: TerminalExecutionEvidenceV1): void { 'provider_result_digest', 'operator_notification_root', ] as const) { - assertHash(`terminal_execution.${field}`, terminal[field]) + assertResolvedHash(`terminal_execution.${field}`, terminal[field]) } } @@ -508,40 +556,138 @@ function normalizeVerification( if (!VERIFICATION_MODES.has(observation.verification_mode)) { throw new OutcomeComparisonError(`verification[${index}].verification_mode is invalid`) } - assertHash(`verification[${index}].evidence_digest`, observation.evidence_digest) - assertHash(`verification[${index}].verifier_identity_root`, observation.verifier_identity_root) - return { ...observation } + assertResolvedHash(`verification[${index}].evidence_digest`, observation.evidence_digest) + assertResolvedHash(`verification[${index}].verifier_identity_root`, observation.verifier_identity_root) + return { + step_index: observation.step_index, + verdict: observation.verdict, + evidence_digest: observation.evidence_digest, + verifier_identity_root: observation.verifier_identity_root, + verification_mode: observation.verification_mode, + } }) return normalized.sort((left, right) => left.step_index - right.step_index) } +function normalizeSelfModelSnapshotV1(snapshot: SelfModelSnapshot): SelfModelSnapshot { + return { + state_root: snapshot.state_root, + identity_root: snapshot.identity_root, + policy_root: snapshot.policy_root, + capability_root: snapshot.capability_root, + memory_root: snapshot.memory_root, + metacognition_root: snapshot.metacognition_root, + verifier_trust_root: snapshot.verifier_trust_root, + health: { + t0_verdict: snapshot.health.t0_verdict, + corruption_count: snapshot.health.corruption_count, + membrane_intact: snapshot.health.membrane_intact, + entropy_bounded: snapshot.health.entropy_bounded, + }, + } +} + +function normalizeAuthorityEvidenceV1( + authority: AdaptationAuthorityEvidenceV1, +): AdaptationAuthorityEvidenceV1 { + const denial_codes = validateAuthorityEvidence(authority) + return { + evidence_kind: authority.evidence_kind, + outcome: authority.outcome, + denial_codes, + execution_identity_root: authority.execution_identity_root, + workspace_binding: authority.workspace_binding, + policy_root: authority.policy_root, + registry_root: authority.registry_root, + policy_decision_root: authority.policy_decision_root, + authority_receipt_root: authority.authority_receipt_root, + executor_principal_root: authority.executor_principal_root, + executor_workload_identity_root: authority.executor_workload_identity_root, + action_binding: { + proposal_digest: authority.action_binding.proposal_digest, + self_regulation_decision_digest: authority.action_binding.self_regulation_decision_digest, + expected_parent_state_root: authority.action_binding.expected_parent_state_root, + }, + requested_action_digest: authority.requested_action_digest, + } +} + +function normalizeTerminalEvidenceV1( + terminal: TerminalExecutionEvidenceV1, +): TerminalExecutionEvidenceV1 { + validateTerminalEvidence(terminal) + return { + evidence_kind: terminal.evidence_kind, + execution_identity_root: terminal.execution_identity_root, + workspace_binding: terminal.workspace_binding, + policy_decision_root: terminal.policy_decision_root, + authority_receipt_root: terminal.authority_receipt_root, + requested_action_digest: terminal.requested_action_digest, + lease_outcome: terminal.lease_outcome, + lease_authorization_receipt_root: terminal.lease_authorization_receipt_root, + durable_execution_root: terminal.durable_execution_root, + durable_status: terminal.durable_status, + mutation_receipt_root: terminal.mutation_receipt_root, + receipt_chain_status: terminal.receipt_chain_status, + receipt_chain_verification_root: terminal.receipt_chain_verification_root, + outcome: terminal.outcome, + pre_state_root: terminal.pre_state_root, + post_state_root: terminal.post_state_root, + provider_result_digest: terminal.provider_result_digest, + operator_notification_root: terminal.operator_notification_root, + } +} + +function normalizeEvidenceCertificateV1( + certificate: OutcomeEvidenceCertificateV1, +): OutcomeEvidenceCertificateV1 { + validateEvidenceCertificate(certificate) + return { + certificate_kind: certificate.certificate_kind, + verifier_key_id: certificate.verifier_key_id, + verifier_public_key: certificate.verifier_public_key, + verifier_identity_root: certificate.verifier_identity_root, + verifier_principal_root: certificate.verifier_principal_root, + verifier_workload_identity_root: certificate.verifier_workload_identity_root, + evidence_bundle_digest: certificate.evidence_bundle_digest, + signature: certificate.signature, + } +} + export type OutcomeEvidenceInputV1 = Omit< AdaptationOutcomeInput, 'evidence_certificate' > +function normalizeOutcomeEvidenceInputV1( + input: OutcomeEvidenceInputV1, +): OutcomeEvidenceInputV1 { + const proposal = normalizeAdaptationProposal(input.baseline.proposal) + return { + baseline: { + snapshot: normalizeSelfModelSnapshotV1(input.baseline.snapshot), + gaps: normalizeKnowledgeGaps(input.baseline.gaps), + proposal, + }, + authority: normalizeAuthorityEvidenceV1(input.authority), + ...(input.terminal_execution === undefined + ? {} + : { terminal_execution: normalizeTerminalEvidenceV1(input.terminal_execution) }), + post_snapshot: normalizeSelfModelSnapshotV1(input.post_snapshot), + post_gaps: normalizeKnowledgeGaps(input.post_gaps), + verification: normalizeVerification(input.verification, proposal.verification_steps.length), + } +} + export async function hashOutcomeEvidenceBundleV1( input: OutcomeEvidenceInputV1, ): Promise { - const proposal = normalizeAdaptationProposal(input.baseline.proposal) - const baselineGaps = normalizeKnowledgeGaps(input.baseline.gaps) - const postGaps = normalizeKnowledgeGaps(input.post_gaps) - const denialCodes = validateAuthorityEvidence(input.authority) - if (input.terminal_execution !== undefined) validateTerminalEvidence(input.terminal_execution) - const verification = normalizeVerification(input.verification, proposal.verification_steps.length) + const normalized = normalizeOutcomeEvidenceInputV1(input) return hashValue({ domain: 'AEGIS_OUTCOME_EVIDENCE_BUNDLE_V1', evidence: { - baseline: { - snapshot: input.baseline.snapshot, - gaps: baselineGaps, - proposal, - }, - authority: { ...input.authority, denial_codes: denialCodes }, - terminal_execution: input.terminal_execution ?? null, - post_snapshot: input.post_snapshot, - post_gaps: postGaps, - verification, + ...normalized, + terminal_execution: normalized.terminal_execution ?? null, }, }) } @@ -659,6 +805,7 @@ export async function assessAdaptationOutcome( const authority = input.authority const evidence_bundle_digest = await hashOutcomeEvidenceBundleV1(input) let evidence_certificate_digest: SHA256Hex | null = null + let evidence_certificate_authenticated = false let evidence_certificate_verified = false const anchorPolicyMatches = trustAnchor.governed_policy_root === input.baseline.snapshot.policy_root @@ -717,15 +864,15 @@ export async function assessAdaptationOutcome( if (!bundleMatches) reasons.push('EVIDENCE_BUNDLE_DIGEST_MISMATCH') if (!signatureValid) reasons.push('EVIDENCE_CERTIFICATE_SIGNATURE_INVALID') if (!observationIdentitiesMatch) reasons.push('VERIFICATION_IDENTITY_MISMATCH') - evidence_certificate_verified = anchorPolicyMatches && + evidence_certificate_authenticated = anchorPolicyMatches && anchorTrustRootMatches && verifierIsTrusted && verifierIdentityMatches && verifierIsIndependent && bundleMatches && signatureValid && - observationIdentitiesMatch && - componentTransitionValid + observationIdentitiesMatch + evidence_certificate_verified = evidence_certificate_authenticated && componentTransitionValid } if (sourceDecision.mode !== 'READY_FOR_AUTHORITY') reasons.push('BASELINE_NOT_READY_FOR_AUTHORITY') @@ -887,6 +1034,7 @@ export async function assessAdaptationOutcome( terminal_receipt_root, evidence_bundle_digest, evidence_certificate_digest, + evidence_certificate_authenticated, evidence_certificate_verified, verifier_trust_policy_digest: trustAnchor.trust_policy_digest, pre_state_root: input.baseline.snapshot.state_root, @@ -905,6 +1053,19 @@ export async function assessAdaptationOutcome( return deepFreeze({ ...unsigned, assessment_digest }) } +export function normalizeAdaptationOutcomeInputV1( + input: AdaptationOutcomeInput, +): AdaptationOutcomeInput { + const { evidence_certificate: certificate, ...evidenceInput } = input + const normalized = normalizeOutcomeEvidenceInputV1(evidenceInput) + return deepFreeze({ + ...normalized, + ...(certificate === undefined + ? {} + : { evidence_certificate: normalizeEvidenceCertificateV1(certificate) }), + }) +} + export async function recordOutcomeAssessment( loop: MetacognitiveLoop, input: AdaptationOutcomeInput, @@ -930,24 +1091,7 @@ export async function recordOutcomeAssessment( throw new OutcomeComparisonError('outcome evidence artifact store is unavailable') } const normalizedVerifiers = await normalizeVerifierIdentities(trustAnchor.verifiers) - const denialCodes = validateAuthorityEvidence(input.authority) - if (input.terminal_execution !== undefined) validateTerminalEvidence(input.terminal_execution) - const normalizedInput = deepFreeze>>({ - baseline: { - snapshot: input.baseline.snapshot, - gaps: normalizeKnowledgeGaps(input.baseline.gaps), - proposal: normalizeAdaptationProposal(input.baseline.proposal), - }, - authority: { ...input.authority, denial_codes: denialCodes }, - terminal_execution: input.terminal_execution ?? null, - post_snapshot: input.post_snapshot, - post_gaps: normalizeKnowledgeGaps(input.post_gaps), - verification: normalizeVerification( - input.verification, - input.baseline.proposal.verification_steps.length, - ), - evidence_certificate: input.evidence_certificate ?? null, - }) + const normalizedInput = normalizeAdaptationOutcomeInputV1(input) const artifactBody = deepFreeze({ schema_version: OUTCOME_COMPARATOR_SCHEMA_VERSION, artifact_kind: 'AEGIS_OUTCOME_EVIDENCE_ARTIFACT_V1' as const, @@ -957,6 +1101,7 @@ export async function recordOutcomeAssessment( verifier_trust_root: trustAnchor.verifier_trust_root, verifiers: normalizedVerifiers, trust_policy_digest: trustAnchor.trust_policy_digest, + trust_policy: trustAnchor.trust_policy, }, assessment, }) diff --git a/sovereign-omega-v2/src/metacognition/outcome-evidence-artifact-store.ts b/sovereign-omega-v2/src/metacognition/outcome-evidence-artifact-store.ts new file mode 100644 index 000000000..44d9fd64c --- /dev/null +++ b/sovereign-omega-v2/src/metacognition/outcome-evidence-artifact-store.ts @@ -0,0 +1,332 @@ +// ============================================================ +// SOVEREIGN OMEGA - Outcome Evidence Artifact Store +// EPISTEMIC TIER: T2 - durable browser adapter, mechanically checked +// +// This store snapshots, schema-checks, re-derives, and persists assessed +// evidence artifacts. Embedded signatures establish internal integrity; +// external operator trust remains the replay adapter's responsibility. The +// store never grants authority, executes mutations, or updates competence. +// Content roots are recomputed before and after persistence. +// ============================================================ + +import { canonicalizeJCS } from '../core/canonicalize.js' +import { hashValue } from '../core/hashing.js' +import { assertIJsonValue } from '../core/i-json.js' +import { deepFreeze } from '../core/immutable.js' +import type { SHA256Hex } from '../core/types.js' +import { + assessAdaptationOutcome, + normalizeAdaptationOutcomeInputV1, + verifyOutcomeVerifierTrustPolicyV1, +} from './outcome-comparator.js' +import type { + OutcomeEvidenceArtifactStore, + OutcomeEvidenceArtifactV1, + OutcomeEvidencePersistenceReceiptV1, +} from './outcome-comparator.js' + +const DEFAULT_DATABASE_NAME = 'sovereign-omega-outcome-evidence' +const DATABASE_VERSION = 1 +const ARTIFACTS_STORE = 'outcome-evidence-artifacts' +const HASH_PATTERN = /^[0-9a-f]{64}$/ + +export class OutcomeEvidenceArtifactStoreError extends Error { + override readonly name = 'OutcomeEvidenceArtifactStoreError' + + constructor(message: string) { + super(message) + Object.setPrototypeOf(this, new.target.prototype) + } +} + +export interface ReadableOutcomeEvidenceArtifactStore extends OutcomeEvidenceArtifactStore { + read(artifactRoot: SHA256Hex): Promise +} + +export class IndexedDBOutcomeEvidenceArtifactStore implements ReadableOutcomeEvidenceArtifactStore { + private database: IDBDatabase | null = null + private opening: Promise | null = null + private closeGeneration = 0 + + constructor(private readonly databaseName = DEFAULT_DATABASE_NAME) { + if (databaseName.trim() === '') { + throw new OutcomeEvidenceArtifactStoreError('database name must not be empty') + } + } + + async open(): Promise { + if (this.database !== null) return + if (this.opening === null) { + const generation = this.closeGeneration + this.opening = openDatabase(this.databaseName).then(database => { + if (generation !== this.closeGeneration) { + database.close() + throw new OutcomeEvidenceArtifactStoreError('artifact store closed while opening') + } + database.onversionchange = () => { + database.close() + if (this.database === database) this.database = null + } + this.database = database + return database + }).finally(() => { + this.opening = null + }) + } + await this.opening + } + + close(): void { + this.closeGeneration += 1 + this.database?.close() + this.database = null + } + + async persist( + artifact: OutcomeEvidenceArtifactV1, + ): Promise { + const database = this.requireDatabase() + const snapshot = snapshotArtifact(artifact, 'outcome evidence artifact') + await verifyArtifact(snapshot) + + const existing = await readRecord(database, snapshot.artifact_root) + if (existing !== null) { + await assertIdenticalArtifact(existing, snapshot) + return persistenceReceipt(this.databaseName, snapshot.artifact_root) + } + + try { + await addRecord(database, snapshot) + } catch (error) { + if (!isConstraintError(error)) throw error + const racedRecord = await readRecord(database, snapshot.artifact_root) + if (racedRecord === null) { + throw new OutcomeEvidenceArtifactStoreError('artifact insert conflicted without a readable record') + } + await assertIdenticalArtifact(racedRecord, snapshot) + } + + const persisted = await readRecord(database, snapshot.artifact_root) + if (persisted === null) { + throw new OutcomeEvidenceArtifactStoreError('artifact read-back failed') + } + await assertIdenticalArtifact(persisted, snapshot) + return persistenceReceipt(this.databaseName, snapshot.artifact_root) + } + + async read(artifactRoot: SHA256Hex): Promise { + assertArtifactRoot(artifactRoot) + const record = await readRecord(this.requireDatabase(), artifactRoot) + if (record === null) return null + const snapshot = snapshotArtifact(record, 'persisted outcome evidence artifact') + await verifyArtifact(snapshot) + return snapshot + } + + private requireDatabase(): IDBDatabase { + if (this.database === null) { + throw new OutcomeEvidenceArtifactStoreError('artifact store is not open') + } + return this.database + } +} + +async function verifyArtifact(artifact: OutcomeEvidenceArtifactV1): Promise { + assertExactKeys('artifact', artifact, [ + 'artifact_kind', + 'artifact_root', + 'assessment', + 'evidence_input', + 'schema_version', + 'verifier_trust_anchor', + ]) + assertArtifactRoot(artifact.artifact_root) + if (artifact.schema_version !== '1.0.0' || + artifact.artifact_kind !== 'AEGIS_OUTCOME_EVIDENCE_ARTIFACT_V1') { + throw new OutcomeEvidenceArtifactStoreError('artifact schema is unsupported') + } + const { artifact_root: _artifactRoot, ...body } = artifact + const expectedRoot = await hashValue({ + domain: 'AEGIS_OUTCOME_EVIDENCE_ARTIFACT_V1', + artifact: body, + }) + if (artifact.artifact_root !== expectedRoot) { + throw new OutcomeEvidenceArtifactStoreError('artifact root mismatch') + } + + try { + assertExactKeys('artifact.verifier_trust_anchor', artifact.verifier_trust_anchor, [ + 'governed_policy_root', + 'trust_policy', + 'trust_policy_digest', + 'verifier_trust_root', + 'verifiers', + ]) + const policy = artifact.verifier_trust_anchor.trust_policy + const trustAnchor = await verifyOutcomeVerifierTrustPolicyV1( + policy, + policy.governed_policy_root, + policy.signer_public_key, + ) + const expectedAnchor = { + governed_policy_root: trustAnchor.governed_policy_root, + verifier_trust_root: trustAnchor.verifier_trust_root, + verifiers: trustAnchor.verifiers, + trust_policy_digest: trustAnchor.trust_policy_digest, + trust_policy: trustAnchor.trust_policy, + } + assertCanonicalEqual( + 'artifact verifier trust anchor', + artifact.verifier_trust_anchor, + expectedAnchor, + ) + const normalizedInput = normalizeAdaptationOutcomeInputV1(artifact.evidence_input) + assertCanonicalEqual('artifact evidence input', artifact.evidence_input, normalizedInput) + const expectedAssessment = await assessAdaptationOutcome(normalizedInput, trustAnchor) + assertCanonicalEqual('artifact assessment', artifact.assessment, expectedAssessment) + } catch (error) { + if (error instanceof OutcomeEvidenceArtifactStoreError) throw error + throw new OutcomeEvidenceArtifactStoreError( + `artifact schema or derivation is invalid: ${error instanceof Error ? error.message : String(error)}`, + ) + } +} + +async function assertIdenticalArtifact( + persisted: OutcomeEvidenceArtifactV1, + expected: OutcomeEvidenceArtifactV1, +): Promise { + const snapshot = snapshotArtifact(persisted, 'persisted outcome evidence artifact') + await verifyArtifact(snapshot) + if (!equalBytes(canonicalizeJCS(snapshot), canonicalizeJCS(expected))) { + throw new OutcomeEvidenceArtifactStoreError('artifact root collision or persisted payload mismatch') + } +} + +function assertArtifactRoot(value: unknown): asserts value is SHA256Hex { + if (typeof value !== 'string' || !HASH_PATTERN.test(value) || value === '0'.repeat(64)) { + throw new OutcomeEvidenceArtifactStoreError('artifact root must be resolved lowercase SHA-256 hex') + } +} + +function snapshotArtifact(value: unknown, label: string): OutcomeEvidenceArtifactV1 { + try { + assertIJsonValue(value, label) + const snapshot = structuredClone(value) as unknown + assertIJsonValue(snapshot, label) + if (snapshot === null || typeof snapshot !== 'object' || Array.isArray(snapshot)) { + throw new OutcomeEvidenceArtifactStoreError('artifact must be an object') + } + return deepFreeze(snapshot) as OutcomeEvidenceArtifactV1 + } catch (error) { + if (error instanceof OutcomeEvidenceArtifactStoreError) throw error + throw new OutcomeEvidenceArtifactStoreError( + `${label} is not a closed I-JSON value: ${error instanceof Error ? error.message : String(error)}`, + ) + } +} + +function assertExactKeys( + label: string, + value: unknown, + expectedKeys: readonly string[], +): asserts value is Record { + if (value === null || typeof value !== 'object' || Array.isArray(value)) { + throw new OutcomeEvidenceArtifactStoreError(`${label} must be an object`) + } + const keys = Object.keys(value).sort() + const expected = [...expectedKeys].sort() + if (keys.length !== expected.length || keys.some((key, index) => key !== expected[index])) { + throw new OutcomeEvidenceArtifactStoreError(`${label} has unexpected or missing fields`) + } +} + +function assertCanonicalEqual(label: string, left: unknown, right: unknown): void { + if (!equalBytes(canonicalizeJCS(left), canonicalizeJCS(right))) { + throw new OutcomeEvidenceArtifactStoreError(`${label} does not match its verified derivation`) + } +} + +function equalBytes(left: Uint8Array, right: Uint8Array): boolean { + if (left.byteLength !== right.byteLength) return false + for (let index = 0; index < left.byteLength; index += 1) { + if (left[index] !== right[index]) return false + } + return true +} + +function persistenceReceipt( + databaseName: string, + artifactRoot: SHA256Hex, +): OutcomeEvidencePersistenceReceiptV1 { + return deepFreeze({ + artifact_root: artifactRoot, + artifact_reference: `indexeddb:${encodeURIComponent(databaseName)}/${ARTIFACTS_STORE}/${artifactRoot}`, + }) +} + +function openDatabase(name: string): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(name, DATABASE_VERSION) + request.onupgradeneeded = () => { + const database = request.result + if (!database.objectStoreNames.contains(ARTIFACTS_STORE)) { + database.createObjectStore(ARTIFACTS_STORE, { keyPath: 'artifact_root' }) + } + } + request.onsuccess = () => resolve(request.result) + request.onerror = () => reject(new OutcomeEvidenceArtifactStoreError( + `failed to open artifact store: ${request.error?.message ?? 'unknown error'}`, + )) + }) +} + +function readRecord( + database: IDBDatabase, + artifactRoot: SHA256Hex, +): Promise { + return new Promise((resolve, reject) => { + const transaction = database.transaction(ARTIFACTS_STORE, 'readonly') + const request = transaction.objectStore(ARTIFACTS_STORE).get(artifactRoot) + let result: OutcomeEvidenceArtifactV1 | null = null + let settled = false + const fail = (message: string, error: DOMException | null) => { + if (settled) return + settled = true + reject(new OutcomeEvidenceArtifactStoreError( + `${message}: ${error?.message ?? 'unknown error'}`, + )) + } + request.onsuccess = () => { + result = (request.result as OutcomeEvidenceArtifactV1 | undefined) ?? null + } + request.onerror = () => fail('failed to read artifact', request.error) + transaction.oncomplete = () => { + if (settled) return + settled = true + resolve(result) + } + transaction.onerror = () => fail('artifact read transaction failed', transaction.error) + transaction.onabort = () => fail('artifact read transaction aborted', transaction.error) + }) +} + +function addRecord(database: IDBDatabase, artifact: OutcomeEvidenceArtifactV1): Promise { + return new Promise((resolve, reject) => { + const transaction = database.transaction(ARTIFACTS_STORE, 'readwrite') + const request = transaction.objectStore(ARTIFACTS_STORE).add(artifact) + let operationError: DOMException | null = null + request.onerror = () => { operationError = request.error } + transaction.oncomplete = () => resolve() + transaction.onerror = () => { operationError ??= transaction.error } + transaction.onabort = () => reject( + operationError ?? transaction.error ?? + new OutcomeEvidenceArtifactStoreError('artifact insert aborted'), + ) + }) +} + +function isConstraintError(error: unknown): boolean { + return error !== null && typeof error === 'object' && + 'name' in error && error.name === 'ConstraintError' +} diff --git a/sovereign-omega-v2/src/metacognition/outcome-evidence-replay.ts b/sovereign-omega-v2/src/metacognition/outcome-evidence-replay.ts new file mode 100644 index 000000000..564c87a91 --- /dev/null +++ b/sovereign-omega-v2/src/metacognition/outcome-evidence-replay.ts @@ -0,0 +1,143 @@ +// ============================================================ +// SOVEREIGN OMEGA - Authenticated Outcome Evidence Replay +// EPISTEMIC TIER: T2 - deterministic, tested governance adapter +// +// Replays evidence evaluation against an operator-authenticated verifier +// policy, persists the resulting content-addressed artifact, and proves that +// the artifact can be read back before returning the updated loop. Replay +// never re-executes an action, grants authority, mutates state, or updates +// competence. +// ============================================================ + +import { canonicalizeJCS } from '../core/canonicalize.js' +import { assertIJsonValue } from '../core/i-json.js' +import { deepFreeze } from '../core/immutable.js' +import type { SequenceNumber, SHA256Hex } from '../core/types.js' +import { MetacognitiveLoop } from './loop.js' +import type { MetacognitiveEntry } from './loop.js' +import type { ReadableOutcomeEvidenceArtifactStore } from './outcome-evidence-artifact-store.js' +import { + OutcomeComparisonError, + assessAdaptationOutcome, + normalizeAdaptationOutcomeInputV1, + recordOutcomeAssessment, + verifyOutcomeVerifierTrustPolicyV1, +} from './outcome-comparator.js' +import type { + AdaptationOutcomeAssessment, + AdaptationOutcomeInput, + OutcomeEvidenceArtifactV1, + OutcomeEvidencePersistenceReceiptV1, + OutcomeVerifierTrustPolicyV1, +} from './outcome-comparator.js' + +export interface TrustedOutcomeReplayContextV1 { + readonly expected_governed_policy_root: SHA256Hex + readonly expected_operator_public_key: string +} + +export interface OutcomeReplayEvidenceV1 { + readonly input: AdaptationOutcomeInput + readonly trust_policy: OutcomeVerifierTrustPolicyV1 +} + +export interface OutcomeEvidenceReplayResultV1 { + readonly assessment: AdaptationOutcomeAssessment + readonly artifact: OutcomeEvidenceArtifactV1 + readonly persistence: OutcomeEvidencePersistenceReceiptV1 + readonly loop: MetacognitiveLoop + readonly entry: MetacognitiveEntry +} + +export async function replayAuthenticatedOutcomeEvidenceV1( + loop: MetacognitiveLoop, + artifactStore: ReadableOutcomeEvidenceArtifactStore, + allocatedSequence: SequenceNumber, + trustedContext: TrustedOutcomeReplayContextV1, + evidence: OutcomeReplayEvidenceV1, +): Promise { + if (artifactStore === null || typeof artifactStore !== 'object' || + typeof artifactStore.persist !== 'function' || typeof artifactStore.read !== 'function') { + throw new OutcomeComparisonError('readable outcome evidence artifact store is unavailable') + } + if (typeof allocatedSequence !== 'bigint' || allocatedSequence < 0n) { + throw new OutcomeComparisonError('allocated outcome replay sequence must be a non-negative bigint') + } + if (loop.lastSequence !== null && allocatedSequence <= loop.lastSequence) { + throw new OutcomeComparisonError('allocated outcome replay sequence must advance the loop') + } + + const context = snapshotIJson(trustedContext, 'trusted outcome replay context') + const evidenceSnapshot = snapshotIJson(evidence, 'outcome replay evidence') + if (loop.lastHash !== evidenceSnapshot.input.baseline.snapshot.metacognition_root) { + throw new OutcomeComparisonError('metacognitive loop head does not match evidence baseline') + } + if (evidenceSnapshot.input.baseline.snapshot.policy_root !== + context.expected_governed_policy_root) { + throw new OutcomeComparisonError( + 'expected governed policy root does not match the evidence baseline', + ) + } + + const trustAnchor = await verifyOutcomeVerifierTrustPolicyV1( + evidenceSnapshot.trust_policy, + context.expected_governed_policy_root, + context.expected_operator_public_key, + ) + if (trustAnchor.verifier_trust_root !== + evidenceSnapshot.input.baseline.snapshot.verifier_trust_root) { + throw new OutcomeComparisonError( + 'authenticated verifier trust policy is not bound to the evidence baseline', + ) + } + + const normalizedInput = normalizeAdaptationOutcomeInputV1(evidenceSnapshot.input) + if (!equalBytes(canonicalizeJCS(evidenceSnapshot.input), canonicalizeJCS(normalizedInput))) { + throw new OutcomeComparisonError('outcome replay evidence input is not in canonical schema form') + } + const preflight = await assessAdaptationOutcome(normalizedInput, trustAnchor) + if (!preflight.evidence_certificate_authenticated) { + throw new OutcomeComparisonError('outcome evidence certificate authentication failed') + } + + const result = await recordOutcomeAssessment( + loop, + normalizedInput, + trustAnchor, + artifactStore, + allocatedSequence, + ) + if (result.assessment.assessment_digest !== preflight.assessment_digest) { + throw new OutcomeComparisonError('outcome assessment changed across the append boundary') + } + const restored = await artifactStore.read(result.artifact.artifact_root) + if (restored === null) { + throw new OutcomeComparisonError('persisted outcome evidence artifact cannot be resolved') + } + const restoredSnapshot = snapshotIJson(restored, 'persisted outcome evidence artifact') + if (!equalBytes(canonicalizeJCS(restoredSnapshot), canonicalizeJCS(result.artifact))) { + throw new OutcomeComparisonError('persisted outcome evidence artifact read-back mismatch') + } + return result +} + +function snapshotIJson(value: T, label: string): Readonly { + try { + assertIJsonValue(value, label) + const snapshot = structuredClone(value) as T + assertIJsonValue(snapshot, label) + return deepFreeze(snapshot) + } catch (error) { + throw new OutcomeComparisonError( + `${label} is not a closed I-JSON value: ${error instanceof Error ? error.message : String(error)}`, + ) + } +} + +function equalBytes(left: Uint8Array, right: Uint8Array): boolean { + if (left.byteLength !== right.byteLength) return false + for (let index = 0; index < left.byteLength; index += 1) { + if (left[index] !== right[index]) return false + } + return true +} diff --git a/sovereign-omega-v2/src/metacognition/self-regulator.ts b/sovereign-omega-v2/src/metacognition/self-regulator.ts index f7c754315..a4f0e5634 100644 --- a/sovereign-omega-v2/src/metacognition/self-regulator.ts +++ b/sovereign-omega-v2/src/metacognition/self-regulator.ts @@ -108,6 +108,7 @@ export class SelfRegulationError extends Error { } const HASH_PATTERN = /^[0-9a-f]{64}$/ +const ZERO_HASH = '0'.repeat(64) const SAFE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{1,127}$/ const SAFE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._@+~-]+$/ const DRIVE_PATH_PATTERN = /^[A-Za-z]:/ @@ -136,6 +137,13 @@ function assertHash(field: string, value: unknown): asserts value is SHA256Hex { } } +function assertResolvedHash(field: string, value: unknown): asserts value is SHA256Hex { + assertHash(field, value) + if (value === ZERO_HASH) { + throw new SelfRegulationError(`${field} must resolve to a non-zero root`) + } +} + function sortedUnique(field: string, values: readonly string[]): readonly string[] { if (!Array.isArray(values)) throw new SelfRegulationError(`${field} must be an array`) const normalized = values.map((value, index) => { @@ -194,12 +202,12 @@ function canonicalRepositoryPath(field: string, value: unknown): string { } function validateStateComponents(snapshot: SelfModelStateComponents): void { - assertHash('snapshot.identity_root', snapshot.identity_root) - assertHash('snapshot.policy_root', snapshot.policy_root) - assertHash('snapshot.capability_root', snapshot.capability_root) - assertHash('snapshot.memory_root', snapshot.memory_root) + assertResolvedHash('snapshot.identity_root', snapshot.identity_root) + assertResolvedHash('snapshot.policy_root', snapshot.policy_root) + assertResolvedHash('snapshot.capability_root', snapshot.capability_root) + assertResolvedHash('snapshot.memory_root', snapshot.memory_root) assertHash('snapshot.metacognition_root', snapshot.metacognition_root) - assertHash('snapshot.verifier_trust_root', snapshot.verifier_trust_root) + assertResolvedHash('snapshot.verifier_trust_root', snapshot.verifier_trust_root) if (!Number.isInteger(snapshot.health.corruption_count) || snapshot.health.corruption_count < 0) { throw new SelfRegulationError('snapshot.health.corruption_count must be a non-negative integer') } @@ -243,7 +251,7 @@ export function normalizeKnowledgeGaps(gaps: readonly KnowledgeGap[]): readonly throw new SelfRegulationError(`gaps[${index}].evidence_refs must contain verified evidence`) } const evidence_refs = evidenceReferences.map((reference, evidenceIndex) => { - assertHash(`gaps[${index}].evidence_refs[${evidenceIndex}]`, reference) + assertResolvedHash(`gaps[${index}].evidence_refs[${evidenceIndex}]`, reference) return reference }) return { diff --git a/sovereign-omega-v2/test/helpers/outcome-evidence-fixture.ts b/sovereign-omega-v2/test/helpers/outcome-evidence-fixture.ts new file mode 100644 index 000000000..fbf0287a4 --- /dev/null +++ b/sovereign-omega-v2/test/helpers/outcome-evidence-fixture.ts @@ -0,0 +1,279 @@ +import type { SequenceNumber, SHA256Hex } from '../../src/core/types.js' +import { hashValue } from '../../src/core/hashing.js' +import { generateKeypair, signBytes } from '../../src/consensus/crypto.js' +import { + METACOGNITION_GENESIS_HASH, + MetacognitiveLoop, +} from '../../src/metacognition/loop.js' +import { + canonicalizeOutcomeEvidenceCertificateMessageV1, + canonicalizeOutcomeVerifierTrustPolicyMessageV1, + hashAdaptationAuthorityBinding, + hashOutcomeEvidenceBundleV1, + hashVerifierIdentityV1, + hashVerifierTrustSetV1, + recordOutcomeAssessment, + verifyOutcomeVerifierTrustPolicyV1, +} from '../../src/metacognition/outcome-comparator.js' +import type { + AdaptationOutcomeInput, + OutcomeEvidenceArtifactV1, + OutcomeEvidenceCertificateV1, + OutcomeVerifierIdentityV1, + OutcomeVerifierTrustPolicyV1, +} from '../../src/metacognition/outcome-comparator.js' +import { + hashSelfModelStateRootV1, + regulateSelf, +} from '../../src/metacognition/self-regulator.js' +import type { + AdaptationProposal, + KnowledgeGap, + SelfModelSnapshot, + SelfModelStateComponents, +} from '../../src/metacognition/self-regulator.js' + +export const H = (character: string) => character.repeat(64) as SHA256Hex +export const SEQ = (value: number) => BigInt(value) as SequenceNumber + +export interface OutcomeClosureFixture { + readonly operatorPublicKey: string + readonly verifierKeypair: Awaited> + readonly verifier: OutcomeVerifierIdentityV1 + readonly trustPolicy: OutcomeVerifierTrustPolicyV1 + readonly input: AdaptationOutcomeInput +} + +export async function outcomeSelfModel( + verifierTrustRoot: SHA256Hex, + overrides: Partial = {}, +): Promise { + const components: SelfModelStateComponents = { + identity_root: H('2'), + policy_root: H('3'), + capability_root: H('4'), + memory_root: H('5'), + metacognition_root: METACOGNITION_GENESIS_HASH, + verifier_trust_root: verifierTrustRoot, + health: { + t0_verdict: true, + corruption_count: 0, + membrane_intact: true, + entropy_bounded: true, + }, + ...overrides, + } + return { state_root: await hashSelfModelStateRootV1(components), ...components } +} + +export async function certifyOutcomeInput( + input: AdaptationOutcomeInput, + verifier: OutcomeVerifierIdentityV1, + verifierKeypair: Awaited>, +): Promise { + const { evidence_certificate: _certificate, ...unsignedInput } = input + const evidenceBundleDigest = await hashOutcomeEvidenceBundleV1(unsignedInput) + const unsignedCertificate: Omit = { + certificate_kind: 'AEGIS_OUTCOME_EVIDENCE_CERTIFICATE_V1', + verifier_key_id: verifier.verifier_key_id, + verifier_public_key: verifier.verifier_public_key, + verifier_identity_root: verifier.verifier_identity_root, + verifier_principal_root: verifier.verifier_principal_root, + verifier_workload_identity_root: verifier.verifier_workload_identity_root, + evidence_bundle_digest: evidenceBundleDigest, + } + return { + ...unsignedInput, + evidence_certificate: { + ...unsignedCertificate, + signature: await signBytes( + verifierKeypair.privateKey, + canonicalizeOutcomeEvidenceCertificateMessageV1(unsignedCertificate), + ), + }, + } +} + +/** Construct adversarial externally signed input without the production normalizer. */ +export async function certifyUncheckedOutcomeInputForTest( + input: AdaptationOutcomeInput, + verifier: OutcomeVerifierIdentityV1, + verifierKeypair: Awaited>, +): Promise { + const { evidence_certificate: _certificate, ...unsignedInput } = input + const evidenceBundleDigest = await hashValue({ + domain: 'AEGIS_OUTCOME_EVIDENCE_BUNDLE_V1', + evidence: { + ...unsignedInput, + terminal_execution: unsignedInput.terminal_execution ?? null, + }, + }) + const unsignedCertificate: Omit = { + certificate_kind: 'AEGIS_OUTCOME_EVIDENCE_CERTIFICATE_V1', + verifier_key_id: verifier.verifier_key_id, + verifier_public_key: verifier.verifier_public_key, + verifier_identity_root: verifier.verifier_identity_root, + verifier_principal_root: verifier.verifier_principal_root, + verifier_workload_identity_root: verifier.verifier_workload_identity_root, + evidence_bundle_digest: evidenceBundleDigest, + } + return { + ...unsignedInput, + evidence_certificate: { + ...unsignedCertificate, + signature: await signBytes( + verifierKeypair.privateKey, + canonicalizeOutcomeEvidenceCertificateMessageV1(unsignedCertificate), + ), + }, + } +} + +export async function createOutcomeClosureFixture(): Promise { + const operatorKeypair = await generateKeypair(new Uint8Array(32).fill(29)) + const verifierKeypair = await generateKeypair(new Uint8Array(32).fill(31)) + const verifier: OutcomeVerifierIdentityV1 = { + verifier_key_id: 'metacognitive-closure-test-verifier', + verifier_public_key: verifierKeypair.publicKey, + verifier_identity_root: await hashVerifierIdentityV1(verifierKeypair.publicKey), + verifier_principal_root: H('a'), + verifier_workload_identity_root: H('b'), + } + const verifierTrustRoot = await hashVerifierTrustSetV1([verifier]) + const unsignedTrustPolicy: Omit = { + schema_version: '1.0.0', + policy_kind: 'AEGIS_OUTCOME_VERIFIER_TRUST_POLICY_V1', + governed_policy_root: H('3'), + verifier_trust_root: verifierTrustRoot, + verifiers: [verifier], + signer_key_id: 'closure-operator-key', + signer_public_key: operatorKeypair.publicKey, + } + const trustPolicy: OutcomeVerifierTrustPolicyV1 = { + ...unsignedTrustPolicy, + signature: await signBytes( + operatorKeypair.privateKey, + await canonicalizeOutcomeVerifierTrustPolicyMessageV1(unsignedTrustPolicy), + ), + } + + const gap: KnowledgeGap = { + gap_id: 'gap.closure.001', + kind: 'CAPABILITY_DEFICIT', + severity: 'HIGH', + evidence_refs: [H('8')], + } + const baseline = await outcomeSelfModel(verifierTrustRoot) + const proposal: AdaptationProposal = { + proposal_id: 'proposal.closure.001', + objective: 'Exercise the governed outcome-learning closure.', + consequence_class: 'D2', + expected_parent_state_root: baseline.state_root, + addressed_gap_ids: [gap.gap_id], + requested_capabilities: ['repo.file.propose'], + mutations: [{ path: 'src/metacognition/outcome-comparator.ts', operation: 'CREATE' }], + verification_steps: ['closure integration'], + rollback_reference: 'git:revert-candidate', + } + const regulation = await regulateSelf({ snapshot: baseline, gaps: [gap], proposal }) + if (regulation.proposal_digest === null) throw new Error('proposal digest unavailable') + + const action_binding = { + proposal_digest: regulation.proposal_digest, + self_regulation_decision_digest: regulation.decision_digest, + expected_parent_state_root: baseline.state_root, + } + const authority = { + evidence_kind: 'AUTOMATON3_AUTHORITY_DECISION_V1' as const, + outcome: 'ADMITTED' as const, + denial_codes: [], + execution_identity_root: baseline.identity_root, + workspace_binding: H('7'), + policy_root: baseline.policy_root, + registry_root: baseline.capability_root, + policy_decision_root: H('8'), + authority_receipt_root: H('9'), + executor_principal_root: H('c'), + executor_workload_identity_root: H('d'), + action_binding, + requested_action_digest: await hashAdaptationAuthorityBinding(action_binding), + } + const post = await outcomeSelfModel(verifierTrustRoot, { capability_root: H('9') }) + const unsignedInput: AdaptationOutcomeInput = { + baseline: { snapshot: baseline, gaps: [gap], proposal }, + authority, + terminal_execution: { + evidence_kind: 'AUTOMATON3_TERMINAL_EXECUTION_V1', + execution_identity_root: authority.execution_identity_root, + workspace_binding: authority.workspace_binding, + policy_decision_root: authority.policy_decision_root, + authority_receipt_root: authority.authority_receipt_root, + requested_action_digest: authority.requested_action_digest, + lease_outcome: 'ADMITTED', + lease_authorization_receipt_root: H('a'), + durable_execution_root: H('b'), + durable_status: 'COMPLETED', + mutation_receipt_root: H('c'), + receipt_chain_status: 'VERIFIED', + receipt_chain_verification_root: H('d'), + outcome: 'SUCCEEDED', + pre_state_root: baseline.state_root, + post_state_root: post.state_root, + provider_result_digest: H('e'), + operator_notification_root: H('f'), + }, + post_snapshot: post, + post_gaps: [], + verification: [{ + step_index: 0, + verdict: 'PASS', + evidence_digest: H('a'), + verifier_identity_root: verifier.verifier_identity_root, + verification_mode: 'INDEPENDENT', + }], + } + return { + operatorPublicKey: operatorKeypair.publicKey, + verifierKeypair, + verifier, + trustPolicy, + input: await certifyOutcomeInput(unsignedInput, verifier, verifierKeypair), + } +} + +export function outcomeReplayEvidence( + fixture: OutcomeClosureFixture, + input: AdaptationOutcomeInput = fixture.input, +) { + return { input, trust_policy: fixture.trustPolicy } +} + +export function trustedOutcomeReplayContext(fixture: OutcomeClosureFixture) { + return { + expected_governed_policy_root: H('3'), + expected_operator_public_key: fixture.operatorPublicKey, + } +} + +export async function createOutcomeEvidenceArtifactFixture(): Promise { + const fixture = await createOutcomeClosureFixture() + const observed = await recordOutcomeAssessment( + MetacognitiveLoop.empty(), + fixture.input, + await verifyOutcomeVerifierTrustPolicyV1( + fixture.trustPolicy, + H('3'), + fixture.operatorPublicKey, + ), + { + async persist(artifact) { + return { + artifact_root: artifact.artifact_root, + artifact_reference: `memory:${artifact.artifact_root}`, + } + }, + }, + SEQ(1), + ) + return observed.artifact +} diff --git a/sovereign-omega-v2/test/integration/metacognitive-outcome-closure.test.ts b/sovereign-omega-v2/test/integration/metacognitive-outcome-closure.test.ts index 8676c0fe5..2cfe671cb 100644 --- a/sovereign-omega-v2/test/integration/metacognitive-outcome-closure.test.ts +++ b/sovereign-omega-v2/test/integration/metacognitive-outcome-closure.test.ts @@ -1,224 +1,682 @@ +import 'fake-indexeddb/auto' + import { describe, expect, it } from 'vitest' -import type { SequenceNumber, SHA256Hex } from '../../src/core/types.js' -import { generateKeypair, signBytes } from '../../src/consensus/crypto.js' +import { hashValue } from '../../src/core/hashing.js' import { - METACOGNITION_GENESIS_HASH, MetacognitiveLoop, certifyMetacognitiveLoop, } from '../../src/metacognition/loop.js' import { - canonicalizeOutcomeEvidenceCertificateMessageV1, - canonicalizeOutcomeVerifierTrustPolicyMessageV1, - hashAdaptationAuthorityBinding, - hashOutcomeEvidenceBundleV1, - hashVerifierIdentityV1, - hashVerifierTrustSetV1, - recordOutcomeAssessment, - verifyOutcomeVerifierTrustPolicyV1, -} from '../../src/metacognition/outcome-comparator.js' + IndexedDBOutcomeEvidenceArtifactStore, +} from '../../src/metacognition/outcome-evidence-artifact-store.js' +import type { + ReadableOutcomeEvidenceArtifactStore, +} from '../../src/metacognition/outcome-evidence-artifact-store.js' +import { + replayAuthenticatedOutcomeEvidenceV1, +} from '../../src/metacognition/outcome-evidence-replay.js' import type { AdaptationOutcomeInput, OutcomeEvidenceArtifactV1, - OutcomeEvidenceCertificateV1, - OutcomeVerifierIdentityV1, - OutcomeVerifierTrustPolicyV1, } from '../../src/metacognition/outcome-comparator.js' +import { regulateSelf } from '../../src/metacognition/self-regulator.js' import { - hashSelfModelStateRootV1, - regulateSelf, -} from '../../src/metacognition/self-regulator.js' -import type { - AdaptationProposal, - KnowledgeGap, - SelfModelSnapshot, - SelfModelStateComponents, -} from '../../src/metacognition/self-regulator.js' - -const H = (character: string) => character.repeat(64) as SHA256Hex -const SEQ = (value: number) => BigInt(value) as SequenceNumber - -async function selfModel( - verifierTrustRoot: SHA256Hex, - overrides: Partial = {}, -): Promise { - const components: SelfModelStateComponents = { - identity_root: H('2'), - policy_root: H('3'), - capability_root: H('4'), - memory_root: H('5'), - metacognition_root: METACOGNITION_GENESIS_HASH, - verifier_trust_root: verifierTrustRoot, - health: { - t0_verdict: true, - corruption_count: 0, - membrane_intact: true, - entropy_bounded: true, + H, + SEQ, + certifyOutcomeInput, + certifyUncheckedOutcomeInputForTest, + createOutcomeClosureFixture, + outcomeReplayEvidence, + outcomeSelfModel, + trustedOutcomeReplayContext, +} from '../helpers/outcome-evidence-fixture.js' + +let databaseCounter = 0 +function databaseName(): string { + databaseCounter += 1 + return `metacognitive-outcome-closure-${databaseCounter}` +} + +function trackingStore(delegate: ReadableOutcomeEvidenceArtifactStore) { + let persistCalls = 0 + const store: ReadableOutcomeEvidenceArtifactStore = { + async persist(artifact) { + persistCalls += 1 + return delegate.persist(artifact) + }, + async read(artifactRoot) { + return delegate.read(artifactRoot) }, - ...overrides, } - return { state_root: await hashSelfModelStateRootV1(components), ...components } + return { store, persistCalls: () => persistCalls } } describe('metacognitive adaptation outcome closure', () => { - it('reassesses signed evidence, appends it, and reanchors the next self-model', async () => { - const operatorKeypair = await generateKeypair(new Uint8Array(32).fill(29)) - const verifierKeypair = await generateKeypair(new Uint8Array(32).fill(31)) - const verifier: OutcomeVerifierIdentityV1 = { - verifier_key_id: 'metacognitive-closure-test-verifier', - verifier_public_key: verifierKeypair.publicKey, - verifier_identity_root: await hashVerifierIdentityV1(verifierKeypair.publicKey), - verifier_principal_root: H('a'), - verifier_workload_identity_root: H('b'), + it('authenticates, reassesses, persists, reads back, appends, and reanchors', async () => { + const fixture = await createOutcomeClosureFixture() + const store = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await store.open() + const observed = await replayAuthenticatedOutcomeEvidenceV1( + MetacognitiveLoop.empty(), + store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture), + ) + + expect(observed.assessment.state_disposition).toBe('PRESERVE') + expect(observed.assessment.evidence_certificate_authenticated).toBe(true) + expect(observed.assessment.evidence_certificate_verified).toBe(true) + expect(observed.assessment.grants_authority).toBe(false) + expect(observed.assessment.executes_mutation).toBe(false) + expect(observed.assessment.updates_competence).toBe(false) + const restored = await store.read(observed.artifact.artifact_root) + expect(restored).toEqual(observed.artifact) + const certificate = await certifyMetacognitiveLoop(observed.loop.getAll()) + expect(certificate.is_valid).toBe(true) + expect(observed.entry.observation.signal).toContain(observed.artifact.artifact_root) + expect(observed.artifact.assessment.assessment_digest).toBe( + observed.assessment.assessment_digest, + ) + + const reanchoredPost = await outcomeSelfModel(fixture.trustPolicy.verifier_trust_root, { + capability_root: fixture.input.post_snapshot.capability_root, + metacognition_root: observed.entry.entry_hash, + }) + expect(reanchoredPost.state_root).not.toBe(fixture.input.post_snapshot.state_root) + const nextRegulation = await regulateSelf({ snapshot: reanchoredPost, gaps: [] }) + expect(nextRegulation.mode).toBe('NO_CHANGE') + store.close() + }) + + it('records an authenticated authority denial without inventing a state change', async () => { + const fixture = await createOutcomeClosureFixture() + const { evidence_certificate: _certificate, terminal_execution: _terminal, ...unsigned } = fixture.input + const deniedInput = await certifyOutcomeInput({ + ...unsigned, + authority: { + ...fixture.input.authority, + outcome: 'DENIED', + denial_codes: ['APPROVAL_MISSING'], + }, + post_snapshot: fixture.input.baseline.snapshot, + post_gaps: fixture.input.baseline.gaps, + verification: [], + }, fixture.verifier, fixture.verifierKeypair) + const store = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await store.open() + + const observed = await replayAuthenticatedOutcomeEvidenceV1( + MetacognitiveLoop.empty(), + store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture, deniedInput), + ) + expect(observed.assessment.state_disposition).toBe('NO_STATE_CHANGE') + expect(observed.assessment.evidence_certificate_authenticated).toBe(true) + expect(observed.assessment.learning_evidence_eligible).toBe(false) + expect(observed.assessment.reason_codes).toContain('AUTHORITY_DENIED') + expect(await store.read(observed.artifact.artifact_root)).toEqual(observed.artifact) + store.close() + }) + + it('rejects a tampered trust-policy signature before persistence or append', async () => { + const fixture = await createOutcomeClosureFixture() + const delegate = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await delegate.open() + const tracked = trackingStore(delegate) + const signature = fixture.trustPolicy.signature + const tamperedPolicy = { + ...fixture.trustPolicy, + signature: `${signature.startsWith('0') ? '1' : '0'}${signature.slice(1)}`, } - const verifierTrustRoot = await hashVerifierTrustSetV1([verifier]) - const unsignedTrustPolicy: Omit = { - schema_version: '1.0.0', - policy_kind: 'AEGIS_OUTCOME_VERIFIER_TRUST_POLICY_V1', - governed_policy_root: H('3'), - verifier_trust_root: verifierTrustRoot, - verifiers: [verifier], - signer_key_id: 'closure-operator-key', - signer_public_key: operatorKeypair.publicKey, + const loop = MetacognitiveLoop.empty() + + await expect(replayAuthenticatedOutcomeEvidenceV1( + loop, + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + { + ...outcomeReplayEvidence(fixture), + trust_policy: tamperedPolicy, + }, + )).rejects.toThrow('trust policy signature is invalid') + expect(tracked.persistCalls()).toBe(0) + expect(loop.length).toBe(0) + delegate.close() + }) + + it('rejects unsigned verifier-policy extensions before persistence or append', async () => { + const fixture = await createOutcomeClosureFixture() + const extendedPolicy = structuredClone(fixture.trustPolicy) as typeof fixture.trustPolicy & { + verifiers: Array } - const trustPolicy: OutcomeVerifierTrustPolicyV1 = { - ...unsignedTrustPolicy, - signature: await signBytes( - operatorKeypair.privateKey, - await canonicalizeOutcomeVerifierTrustPolicyMessageV1(unsignedTrustPolicy), - ), + extendedPolicy.verifiers[0]!.unsigned_extension = 'attacker-controlled' + const delegate = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await delegate.open() + const tracked = trackingStore(delegate) + const loop = MetacognitiveLoop.empty() + + await expect(replayAuthenticatedOutcomeEvidenceV1( + loop, + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + { ...outcomeReplayEvidence(fixture), trust_policy: extendedPolicy }, + )).rejects.toThrow('verifiers[0] has unexpected or missing fields') + expect(tracked.persistCalls()).toBe(0) + expect(loop.length).toBe(0) + delegate.close() + }) + + it('does not let evidence nominate the out-of-band operator key', async () => { + const fixture = await createOutcomeClosureFixture() + const delegate = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await delegate.open() + const tracked = trackingStore(delegate) + const loop = MetacognitiveLoop.empty() + + await expect(replayAuthenticatedOutcomeEvidenceV1( + loop, + tracked.store, + SEQ(1), + { + ...trustedOutcomeReplayContext(fixture), + expected_operator_public_key: H('f'), + }, + outcomeReplayEvidence(fixture), + )).rejects.toThrow('trust policy signer is not the expected operator key') + expect(tracked.persistCalls()).toBe(0) + expect(loop.length).toBe(0) + delegate.close() + }) + + it('rejects evidence changed after certificate signing before persistence or append', async () => { + const fixture = await createOutcomeClosureFixture() + const tamperedInput: AdaptationOutcomeInput = { + ...fixture.input, + post_gaps: [{ + gap_id: 'gap.tampered-after-signing', + kind: 'INVARIANT_BREACH', + severity: 'CRITICAL', + evidence_refs: [H('f')], + }], } - const trustAnchor = await verifyOutcomeVerifierTrustPolicyV1( - trustPolicy, - H('3'), - operatorKeypair.publicKey, - ) + const delegate = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await delegate.open() + const tracked = trackingStore(delegate) + const loop = MetacognitiveLoop.empty() + + await expect(replayAuthenticatedOutcomeEvidenceV1( + loop, + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture, tamperedInput), + )).rejects.toThrow('outcome evidence certificate authentication failed') + expect(tracked.persistCalls()).toBe(0) + expect(loop.length).toBe(0) + delegate.close() + }) + + it('rejects signed placeholder terminal roots before persistence or append', async () => { + const fixture = await createOutcomeClosureFixture() + const unresolvedInput = await certifyUncheckedOutcomeInputForTest({ + ...fixture.input, + terminal_execution: { + ...fixture.input.terminal_execution!, + lease_authorization_receipt_root: H('0'), + durable_execution_root: H('0'), + mutation_receipt_root: H('0'), + receipt_chain_verification_root: H('0'), + provider_result_digest: H('0'), + operator_notification_root: H('0'), + }, + }, fixture.verifier, fixture.verifierKeypair) + const delegate = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await delegate.open() + const tracked = trackingStore(delegate) + const loop = MetacognitiveLoop.empty() + + await expect(replayAuthenticatedOutcomeEvidenceV1( + loop, + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture, unresolvedInput), + )).rejects.toThrow('terminal_execution.lease_authorization_receipt_root must resolve to a non-zero') + expect(tracked.persistCalls()).toBe(0) + expect(loop.length).toBe(0) + delegate.close() + }) - const gap: KnowledgeGap = { - gap_id: 'gap.closure.001', - kind: 'CAPABILITY_DEFICIT', - severity: 'HIGH', - evidence_refs: [H('8')], + it('rejects signed placeholder verification evidence before persistence or append', async () => { + const fixture = await createOutcomeClosureFixture() + const unresolvedInput = await certifyUncheckedOutcomeInputForTest({ + ...fixture.input, + verification: [{ ...fixture.input.verification[0]!, evidence_digest: H('0') }], + }, fixture.verifier, fixture.verifierKeypair) + const delegate = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await delegate.open() + const tracked = trackingStore(delegate) + const loop = MetacognitiveLoop.empty() + + await expect(replayAuthenticatedOutcomeEvidenceV1( + loop, + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture, unresolvedInput), + )).rejects.toThrow('verification[0].evidence_digest must resolve to a non-zero') + expect(tracked.persistCalls()).toBe(0) + expect(loop.length).toBe(0) + delegate.close() + }) + + it('rejects signed unresolved gap, authority-binding, and verifier roots without writing', async () => { + const fixture = await createOutcomeClosureFixture() + const variants: ReadonlyArray<{ + expected: string + input: AdaptationOutcomeInput + }> = [{ + expected: 'gaps[0].evidence_refs[0] must resolve to a non-zero root', + input: { + ...fixture.input, + baseline: { + ...fixture.input.baseline, + gaps: [{ ...fixture.input.baseline.gaps[0]!, evidence_refs: [H('0')] }], + }, + }, + }, { + expected: 'authority.action_binding.proposal_digest must resolve to a non-zero', + input: { + ...fixture.input, + authority: { + ...fixture.input.authority, + action_binding: { + ...fixture.input.authority.action_binding, + proposal_digest: H('0'), + }, + }, + }, + }, { + expected: 'verification[0].verifier_identity_root must resolve to a non-zero', + input: { + ...fixture.input, + verification: [{ + ...fixture.input.verification[0]!, + verifier_identity_root: H('0'), + verification_mode: 'EXECUTOR_SELF_REPORT', + }], + }, + }] + + for (const variant of variants) { + const signedInput = await certifyUncheckedOutcomeInputForTest( + variant.input, + fixture.verifier, + fixture.verifierKeypair, + ) + const delegate = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await delegate.open() + const tracked = trackingStore(delegate) + const loop = MetacognitiveLoop.empty() + await expect(replayAuthenticatedOutcomeEvidenceV1( + loop, + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture, signedInput), + )).rejects.toThrow(variant.expected) + expect(tracked.persistCalls()).toBe(0) + expect(loop.length).toBe(0) + delegate.close() } - const baseline = await selfModel(verifierTrustRoot) - const proposal: AdaptationProposal = { - proposal_id: 'proposal.closure.001', - objective: 'Exercise the governed outcome-learning closure.', - consequence_class: 'D2', - expected_parent_state_root: baseline.state_root, - addressed_gap_ids: [gap.gap_id], - requested_capabilities: ['repo.file.propose'], - mutations: [{ path: 'src/metacognition/outcome-comparator.ts', operation: 'CREATE' }], - verification_steps: ['closure integration'], - rollback_reference: 'git:revert-candidate', + }) + + it('rejects a signed post-state with an unresolved capability root without writing', async () => { + const fixture = await createOutcomeClosureFixture() + const { state_root: _stateRoot, ...postComponents } = fixture.input.post_snapshot + const unresolvedComponents = { ...postComponents, capability_root: H('0') } + const unresolvedPost = { + state_root: await hashValue({ + domain: 'AEGIS_SELF_MODEL_STATE_V1', + snapshot: unresolvedComponents, + }), + ...unresolvedComponents, } - const regulation = await regulateSelf({ snapshot: baseline, gaps: [gap], proposal }) - expect(regulation.mode).toBe('READY_FOR_AUTHORITY') - if (regulation.proposal_digest === null) throw new Error('proposal digest unavailable') - - const action_binding = { - proposal_digest: regulation.proposal_digest, - self_regulation_decision_digest: regulation.decision_digest, - expected_parent_state_root: baseline.state_root, + const signedInput = await certifyUncheckedOutcomeInputForTest({ + ...fixture.input, + post_snapshot: unresolvedPost, + terminal_execution: { + ...fixture.input.terminal_execution!, + post_state_root: unresolvedPost.state_root, + }, + }, fixture.verifier, fixture.verifierKeypair) + const delegate = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await delegate.open() + const tracked = trackingStore(delegate) + + await expect(replayAuthenticatedOutcomeEvidenceV1( + MetacognitiveLoop.empty(), + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture, signedInput), + )).rejects.toThrow('snapshot.capability_root must resolve to a non-zero root') + expect(tracked.persistCalls()).toBe(0) + delegate.close() + }) + + it('rejects negative-zero aliases before persistence or append', async () => { + const fixture = await createOutcomeClosureFixture() + const aliasedInput = { + ...fixture.input, + verification: [{ ...fixture.input.verification[0]!, step_index: -0 }], } - const authority = { - evidence_kind: 'AUTOMATON3_AUTHORITY_DECISION_V1' as const, - outcome: 'ADMITTED' as const, - denial_codes: [], - execution_identity_root: baseline.identity_root, - workspace_binding: H('7'), - policy_root: baseline.policy_root, - registry_root: baseline.capability_root, - policy_decision_root: H('8'), - authority_receipt_root: H('9'), - executor_principal_root: H('c'), - executor_workload_identity_root: H('d'), - action_binding, - requested_action_digest: await hashAdaptationAuthorityBinding(action_binding), + const delegate = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await delegate.open() + const tracked = trackingStore(delegate) + const loop = MetacognitiveLoop.empty() + + await expect(replayAuthenticatedOutcomeEvidenceV1( + loop, + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture, aliasedInput), + )).rejects.toThrow('negative zero') + expect(tracked.persistCalls()).toBe(0) + expect(loop.length).toBe(0) + delegate.close() + }) + + it('rejects replay accessors without invoking them or persisting', async () => { + const fixture = await createOutcomeClosureFixture() + const validInput = fixture.input + let reads = 0 + const evidence = { trust_policy: fixture.trustPolicy } as { + input: AdaptationOutcomeInput + trust_policy: typeof fixture.trustPolicy } - const post = await selfModel(verifierTrustRoot, { capability_root: H('9') }) - const unsignedInput: AdaptationOutcomeInput = { - baseline: { snapshot: baseline, gaps: [gap], proposal }, - authority, - terminal_execution: { - evidence_kind: 'AUTOMATON3_TERMINAL_EXECUTION_V1', - execution_identity_root: authority.execution_identity_root, - workspace_binding: authority.workspace_binding, - policy_decision_root: authority.policy_decision_root, - authority_receipt_root: authority.authority_receipt_root, - requested_action_digest: authority.requested_action_digest, - lease_outcome: 'ADMITTED', - lease_authorization_receipt_root: H('a'), - durable_execution_root: H('b'), - durable_status: 'COMPLETED', - mutation_receipt_root: H('c'), - receipt_chain_status: 'VERIFIED', - receipt_chain_verification_root: H('d'), - outcome: 'SUCCEEDED', - pre_state_root: baseline.state_root, - post_state_root: post.state_root, - provider_result_digest: H('e'), - operator_notification_root: H('f'), - }, - post_snapshot: post, - post_gaps: [], - verification: [{ - step_index: 0, - verdict: 'PASS', - evidence_digest: H('a'), - verifier_identity_root: verifier.verifier_identity_root, - verification_mode: 'INDEPENDENT', - }], + Object.defineProperty(evidence, 'input', { + enumerable: true, + get() { reads += 1; return validInput }, + }) + const delegate = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await delegate.open() + const tracked = trackingStore(delegate) + + await expect(replayAuthenticatedOutcomeEvidenceV1( + MetacognitiveLoop.empty(), + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + evidence, + )).rejects.toThrow('enumerable data property') + expect(reads).toBe(0) + expect(tracked.persistCalls()).toBe(0) + delegate.close() + }) + + it('rejects symbol-keyed replay aliases before persistence', async () => { + const fixture = await createOutcomeClosureFixture() + const evidence = structuredClone(outcomeReplayEvidence(fixture)) + const unsigned = Symbol('unsigned-extension') + ;(evidence.input.verification as unknown as { [key: symbol]: unknown })[unsigned] = + 'attacker-controlled' + const delegate = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await delegate.open() + const tracked = trackingStore(delegate) + + await expect(replayAuthenticatedOutcomeEvidenceV1( + MetacognitiveLoop.empty(), + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + evidence, + )).rejects.toThrow('symbol keys') + expect(tracked.persistCalls()).toBe(0) + delegate.close() + }) + + it('rejects a stale metacognitive baseline before persistence', async () => { + const fixture = await createOutcomeClosureFixture() + const prior = await MetacognitiveLoop.empty().observe({ + layer: 'SENSATION', + signal: 'prior authenticated observation', + tier: 'T2', + }, SEQ(1)) + const delegate = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await delegate.open() + const tracked = trackingStore(delegate) + + await expect(replayAuthenticatedOutcomeEvidenceV1( + prior.loop, + tracked.store, + SEQ(2), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture), + )).rejects.toThrow('metacognitive loop head does not match evidence baseline') + expect(tracked.persistCalls()).toBe(0) + expect(prior.loop.length).toBe(1) + delegate.close() + }) + + it('rejects an authenticated verifier policy not bound to the baseline', async () => { + const fixture = await createOutcomeClosureFixture() + const staleBaseline = await outcomeSelfModel(H('f')) + const staleInput: AdaptationOutcomeInput = { + ...fixture.input, + baseline: { ...fixture.input.baseline, snapshot: staleBaseline }, } - const evidenceBundleDigest = await hashOutcomeEvidenceBundleV1(unsignedInput) - const unsignedCertificate: Omit = { - certificate_kind: 'AEGIS_OUTCOME_EVIDENCE_CERTIFICATE_V1', - verifier_key_id: verifier.verifier_key_id, - verifier_public_key: verifier.verifier_public_key, - verifier_identity_root: verifier.verifier_identity_root, - verifier_principal_root: verifier.verifier_principal_root, - verifier_workload_identity_root: verifier.verifier_workload_identity_root, - evidence_bundle_digest: evidenceBundleDigest, + const delegate = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await delegate.open() + const tracked = trackingStore(delegate) + const loop = MetacognitiveLoop.empty() + + await expect(replayAuthenticatedOutcomeEvidenceV1( + loop, + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture, staleInput), + )).rejects.toThrow('authenticated verifier trust policy is not bound') + expect(tracked.persistCalls()).toBe(0) + expect(loop.length).toBe(0) + delegate.close() + }) + + it('persists authenticated evidence of an unsafe transition as a negative assessment', async () => { + const fixture = await createOutcomeClosureFixture() + const unsafePost = await outcomeSelfModel(fixture.trustPolicy.verifier_trust_root, { + policy_root: H('f'), + capability_root: H('9'), + }) + const unsafeInput = await certifyOutcomeInput({ + ...fixture.input, + post_snapshot: unsafePost, + terminal_execution: { + ...fixture.input.terminal_execution!, + post_state_root: unsafePost.state_root, + }, + }, fixture.verifier, fixture.verifierKeypair) + const store = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await store.open() + + const observed = await replayAuthenticatedOutcomeEvidenceV1( + MetacognitiveLoop.empty(), + store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture, unsafeInput), + ) + expect(observed.assessment.evidence_certificate_authenticated).toBe(true) + expect(observed.assessment.evidence_certificate_verified).toBe(false) + expect(observed.assessment.state_disposition).toBe('REVERT') + expect(observed.assessment.reason_codes).toContain('POLICY_TRANSITION_REQUIRES_D4') + expect(await store.read(observed.artifact.artifact_root)).toEqual(observed.artifact) + store.close() + }) + + it('rejects an invalid host-allocated sequence before persistence', async () => { + const fixture = await createOutcomeClosureFixture() + const delegate = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await delegate.open() + const tracked = trackingStore(delegate) + const loop = MetacognitiveLoop.empty() + + await expect(replayAuthenticatedOutcomeEvidenceV1( + loop, + tracked.store, + SEQ(-1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture), + )).rejects.toThrow('sequence must be a non-negative bigint') + expect(tracked.persistCalls()).toBe(0) + expect(loop.length).toBe(0) + delegate.close() + }) + + it('does not return an appended loop when persisted evidence cannot be read back', async () => { + const fixture = await createOutcomeClosureFixture() + let persistedArtifact: OutcomeEvidenceArtifactV1 | null = null + const unreadableStore: ReadableOutcomeEvidenceArtifactStore = { + async persist(artifact) { + persistedArtifact = artifact + return { + artifact_root: artifact.artifact_root, + artifact_reference: `memory:${artifact.artifact_root}`, + } + }, + async read() { return null }, } - const input: AdaptationOutcomeInput = { - ...unsignedInput, - evidence_certificate: { - ...unsignedCertificate, - signature: await signBytes( - verifierKeypair.privateKey, - canonicalizeOutcomeEvidenceCertificateMessageV1(unsignedCertificate), - ), + const loop = MetacognitiveLoop.empty() + + await expect(replayAuthenticatedOutcomeEvidenceV1( + loop, + unreadableStore, + SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture), + )).rejects.toThrow('persisted outcome evidence artifact cannot be resolved') + expect(persistedArtifact).not.toBeNull() + expect(loop.length).toBe(0) + }) + + it('does not return an appended loop when read-back bytes differ', async () => { + const fixture = await createOutcomeClosureFixture() + let persistedArtifact: OutcomeEvidenceArtifactV1 | null = null + const mismatchedStore: ReadableOutcomeEvidenceArtifactStore = { + async persist(artifact) { + persistedArtifact = artifact + return { + artifact_root: artifact.artifact_root, + artifact_reference: `memory:${artifact.artifact_root}`, + } + }, + async read() { + if (persistedArtifact === null) return null + return { + ...persistedArtifact, + assessment: { + ...persistedArtifact.assessment, + reason_codes: [...persistedArtifact.assessment.reason_codes, 'FORGED_READ_BACK'], + }, + } }, } + const loop = MetacognitiveLoop.empty() - const observed = await recordOutcomeAssessment( - MetacognitiveLoop.empty(), - input, - trustAnchor, - { - async persist(artifact: OutcomeEvidenceArtifactV1) { - return { - artifact_root: artifact.artifact_root, - artifact_reference: `memory:${artifact.artifact_root}`, + await expect(replayAuthenticatedOutcomeEvidenceV1( + loop, + mismatchedStore, + SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture), + )).rejects.toThrow('persisted outcome evidence artifact read-back mismatch') + expect(loop.length).toBe(0) + }) + + it('does not accept a canonical alias from abstract-store read-back', async () => { + const fixture = await createOutcomeClosureFixture() + let persistedArtifact: OutcomeEvidenceArtifactV1 | null = null + const aliasedStore: ReadableOutcomeEvidenceArtifactStore = { + async persist(artifact) { + persistedArtifact = artifact + return { + artifact_root: artifact.artifact_root, + artifact_reference: `memory:${artifact.artifact_root}`, + } + }, + async read() { + if (persistedArtifact === null) return null + const aliased = structuredClone(persistedArtifact) as OutcomeEvidenceArtifactV1 & { + evidence_input: AdaptationOutcomeInput & { + verification: Array<{ step_index: number }> } - }, + } + aliased.evidence_input.verification[0]!.step_index = -0 + return aliased }, + } + const loop = MetacognitiveLoop.empty() + + await expect(replayAuthenticatedOutcomeEvidenceV1( + loop, + aliasedStore, SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture), + )).rejects.toThrow('negative zero') + expect(loop.length).toBe(0) + }) + + it('reauthenticates reopened evidence and replays it deterministically and idempotently', async () => { + const fixture = await createOutcomeClosureFixture() + const name = databaseName() + const store = new IndexedDBOutcomeEvidenceArtifactStore(name) + await store.open() + const loop = MetacognitiveLoop.empty() + + const first = await replayAuthenticatedOutcomeEvidenceV1( + loop, store, SEQ(1), trustedOutcomeReplayContext(fixture), outcomeReplayEvidence(fixture), ) - expect(observed.assessment.state_disposition).toBe('PRESERVE') - expect(observed.assessment.grants_authority).toBe(false) - const certificate = await certifyMetacognitiveLoop(observed.loop.getAll()) - expect(certificate.is_valid).toBe(true) - expect(observed.entry.observation.signal).toContain(observed.artifact.artifact_root) - expect(observed.artifact.assessment.assessment_digest).toBe( - observed.assessment.assessment_digest, + store.close() + const reopened = new IndexedDBOutcomeEvidenceArtifactStore(name) + await reopened.open() + const restored = await reopened.read(first.artifact.artifact_root) + if (restored === null) throw new Error('persisted artifact was not restored') + const second = await replayAuthenticatedOutcomeEvidenceV1( + loop, + reopened, + SEQ(1), + trustedOutcomeReplayContext(fixture), + { + input: restored.evidence_input, + trust_policy: restored.verifier_trust_anchor.trust_policy, + }, ) + expect(second.assessment.assessment_digest).toBe(first.assessment.assessment_digest) + expect(second.artifact.artifact_root).toBe(first.artifact.artifact_root) + expect(second.persistence).toEqual(first.persistence) + expect(second.entry.entry_hash).toBe(first.entry.entry_hash) + expect(restored).toEqual(first.artifact) - const reanchoredPost = await selfModel(verifierTrustRoot, { - capability_root: post.capability_root, - metacognition_root: observed.entry.entry_hash, - }) - expect(reanchoredPost.state_root).not.toBe(post.state_root) - const nextRegulation = await regulateSelf({ snapshot: reanchoredPost, gaps: [] }) - expect(nextRegulation.mode).toBe('NO_CHANGE') + const tracked = trackingStore(reopened) + await expect(replayAuthenticatedOutcomeEvidenceV1( + first.loop, + tracked.store, + SEQ(2), + trustedOutcomeReplayContext(fixture), + { + input: restored.evidence_input, + trust_policy: restored.verifier_trust_anchor.trust_policy, + }, + )).rejects.toThrow('metacognitive loop head does not match evidence baseline') + expect(tracked.persistCalls()).toBe(0) + reopened.close() }) }) diff --git a/sovereign-omega-v2/test/unit/i-json.test.ts b/sovereign-omega-v2/test/unit/i-json.test.ts new file mode 100644 index 000000000..e47c2754d --- /dev/null +++ b/sovereign-omega-v2/test/unit/i-json.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, it } from 'vitest' +import { assertIJsonValue, IJsonValidationError } from '../../src/core/i-json.js' + +describe('assertIJsonValue', () => { + it('accepts closed plain JSON values and shared acyclic references', () => { + const shared = { stable: true } + expect(() => assertIJsonValue({ + null_value: null, + number: 1.25, + string: 'omega', + array: [shared, shared], + })).not.toThrow() + }) + + it.each([ + ['bigint', { value: 1n }], + ['undefined', { value: undefined }], + ['non-finite number', { value: Number.NaN }], + ['negative zero', { value: -0 }], + ['non-plain object', { value: new Date(0) }], + ['unpaired surrogate', { value: '\uD800' }], + ])('rejects %s values', (_label, value) => { + expect(() => assertIJsonValue(value)).toThrow(IJsonValidationError) + }) + + it('rejects sparse and extended arrays', () => { + const sparse = new Array(2) + sparse[1] = 'present' + expect(() => assertIJsonValue(sparse)).toThrow('sparse or extended arrays') + + const extended = [1] as unknown[] & { extra?: string } + extended.extra = 'not an array element' + expect(() => assertIJsonValue(extended)).toThrow('sparse or extended arrays') + }) + + it('rejects cycles without rejecting repeated acyclic values', () => { + const cycle: { self?: unknown } = {} + cycle.self = cycle + expect(() => assertIJsonValue(cycle)).toThrow('contains a cycle') + }) + + it('rejects object and array accessors without invoking them', () => { + let reads = 0 + const object = {} + Object.defineProperty(object, 'value', { + enumerable: true, + get() { reads += 1; return 'not data' }, + }) + const array = [0] + Object.defineProperty(array, '0', { + enumerable: true, + get() { reads += 1; return 'not data' }, + }) + + expect(() => assertIJsonValue(object)).toThrow('enumerable data property') + expect(() => assertIJsonValue(array)).toThrow('enumerable data property') + expect(reads).toBe(0) + }) + + it('rejects symbol keys and non-enumerable own properties', () => { + const symbolArray = [1] as unknown[] & { [key: symbol]: unknown } + symbolArray[Symbol('unsigned')] = 'not canonical' + const hiddenObject = { visible: true } + Object.defineProperty(hiddenObject, 'hidden', { + enumerable: false, + value: 'not canonical', + }) + + expect(() => assertIJsonValue(symbolArray)).toThrow('symbol keys') + expect(() => assertIJsonValue(hiddenObject)).toThrow('only enumerable data properties') + }) +}) diff --git a/sovereign-omega-v2/test/unit/misc-coverage-23.test.ts b/sovereign-omega-v2/test/unit/misc-coverage-23.test.ts index a504efd71..a06a46d11 100644 --- a/sovereign-omega-v2/test/unit/misc-coverage-23.test.ts +++ b/sovereign-omega-v2/test/unit/misc-coverage-23.test.ts @@ -92,8 +92,8 @@ describe('buildSnapshot — with non-empty bindings (covers b => b.binding_id)', // In normal environments, crypto.subtle is available, so the Web Crypto path // is always taken and the Node.js fallback (lines 35-38) is never reached. // -// Stubbing globalThis.crypto to undefined makes the condition false, -// causing execution to fall through to `await import('node:crypto')`. +// Stubbing globalThis.crypto to undefined exercises the isolated +// process.getBuiltinModule fallback without creating a browser import edge. import { sha256Bytes, sha256Hex } from '../../src/core/hashing.js' diff --git a/sovereign-omega-v2/test/unit/outcome-comparator.test.ts b/sovereign-omega-v2/test/unit/outcome-comparator.test.ts index 67a8decdd..2940c2c16 100644 --- a/sovereign-omega-v2/test/unit/outcome-comparator.test.ts +++ b/sovereign-omega-v2/test/unit/outcome-comparator.test.ts @@ -292,6 +292,8 @@ describe('assessAdaptationOutcome', () => { expect(assessment.grants_authority).toBe(false) expect(assessment.executes_mutation).toBe(false) expect(assessment.updates_competence).toBe(false) + expect(assessment.evidence_certificate_authenticated).toBe(true) + expect(assessment.evidence_certificate_verified).toBe(true) expect(assessment.verifier_trust_policy_digest).toBe(trust.anchor.trust_policy_digest) }) @@ -496,6 +498,8 @@ describe('assessAdaptationOutcome', () => { }, trust) const assessment = await assessAdaptationOutcome(changed, trust.anchor) expect(assessment.state_disposition).toBe('REVERT') + expect(assessment.evidence_certificate_authenticated).toBe(true) + expect(assessment.evidence_certificate_verified).toBe(false) expect(assessment.reason_codes).toContain('POLICY_TRANSITION_REQUIRES_D4') }) @@ -554,6 +558,38 @@ describe('assessAdaptationOutcome', () => { expect(assessment.reason_codes).toContain('TERMINAL_POLICY_DECISION_MISMATCH') }) + it('rejects unresolved terminal evidence roots even when the bundle is signed', async () => { + const { input, trust } = await fixture() + const unresolved = { + ...input, + terminal_execution: { + ...input.terminal_execution!, + lease_authorization_receipt_root: H('0'), + durable_execution_root: H('0'), + mutation_receipt_root: H('0'), + receipt_chain_verification_root: H('0'), + provider_result_digest: H('0'), + operator_notification_root: H('0'), + }, + } + + await expect(assessAdaptationOutcome(unresolved, trust.anchor)).rejects.toThrow( + 'terminal_execution.lease_authorization_receipt_root must resolve to a non-zero', + ) + }) + + it('rejects unresolved verification evidence even when PASS is signed', async () => { + const { input, trust } = await fixture() + const unresolved = { + ...input, + verification: [{ ...input.verification[0]!, evidence_digest: H('0') }], + } + + await expect(assessAdaptationOutcome(unresolved, trust.anchor)).rejects.toThrow( + 'verification[0].evidence_digest must resolve to a non-zero', + ) + }) + it('rejects duplicate and out-of-range verification step indices', async () => { const { input, trust } = await fixture() await expect(assessAdaptationOutcome({ diff --git a/sovereign-omega-v2/test/unit/outcome-evidence-artifact-store.test.ts b/sovereign-omega-v2/test/unit/outcome-evidence-artifact-store.test.ts new file mode 100644 index 000000000..736701fd0 --- /dev/null +++ b/sovereign-omega-v2/test/unit/outcome-evidence-artifact-store.test.ts @@ -0,0 +1,279 @@ +import 'fake-indexeddb/auto' + +import { describe, expect, it } from 'vitest' +import { hashValue } from '../../src/core/hashing.js' +import { + IndexedDBOutcomeEvidenceArtifactStore, + OutcomeEvidenceArtifactStoreError, +} from '../../src/metacognition/outcome-evidence-artifact-store.js' +import type { + AdaptationOutcomeInput, + OutcomeEvidenceArtifactV1, +} from '../../src/metacognition/outcome-comparator.js' +import { + createOutcomeEvidenceArtifactFixture, + H, +} from '../helpers/outcome-evidence-fixture.js' + +let databaseCounter = 0 + +function databaseName(): string { + databaseCounter += 1 + return `outcome-evidence-test-${databaseCounter}` +} + +async function artifact(): Promise { + return createOutcomeEvidenceArtifactFixture() +} + +function overwriteRaw(databaseNameValue: string, value: unknown): Promise { + return new Promise((resolve, reject) => { + const open = indexedDB.open(databaseNameValue, 1) + open.onerror = () => reject(open.error) + open.onsuccess = () => { + const database = open.result + const transaction = database.transaction('outcome-evidence-artifacts', 'readwrite') + transaction.objectStore('outcome-evidence-artifacts').put(value) + transaction.oncomplete = () => { database.close(); resolve() } + transaction.onerror = () => { database.close(); reject(transaction.error) } + } + }) +} + +function openVersion(databaseNameValue: string, version: number): Promise { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error('database upgrade timed out')), 1_000) + const open = indexedDB.open(databaseNameValue, version) + open.onblocked = () => { + clearTimeout(timeout) + reject(new Error('database upgrade was blocked')) + } + open.onerror = () => { + clearTimeout(timeout) + reject(open.error) + } + open.onsuccess = () => { + clearTimeout(timeout) + open.result.close() + resolve() + } + }) +} + +describe('IndexedDBOutcomeEvidenceArtifactStore', () => { + it('requires an open store', async () => { + const store = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await expect(store.persist(await artifact())).rejects.toThrow('artifact store is not open') + await expect(store.read(H('a'))).rejects.toThrow('artifact store is not open') + }) + + it('persists, reads back, re-hashes, and freezes an artifact', async () => { + const name = databaseName() + const store = new IndexedDBOutcomeEvidenceArtifactStore(name) + await store.open() + const value = await artifact() + + const receipt = await store.persist(value) + expect(receipt).toEqual({ + artifact_root: value.artifact_root, + artifact_reference: `indexeddb:${name}/outcome-evidence-artifacts/${value.artifact_root}`, + }) + const restored = await store.read(value.artifact_root) + expect(restored).toEqual(value) + expect(Object.isFrozen(restored)).toBe(true) + expect(Object.isFrozen(restored?.assessment)).toBe(true) + store.close() + }) + + it('treats an identical duplicate as idempotent', async () => { + const store = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await store.open() + const value = await artifact() + const first = await store.persist(value) + const second = await store.persist(structuredClone(value)) + expect(second).toEqual(first) + store.close() + }) + + it('coalesces concurrent opens without leaking a connection', async () => { + const name = databaseName() + const store = new IndexedDBOutcomeEvidenceArtifactStore(name) + await Promise.all([store.open(), store.open(), store.open()]) + store.close() + await expect(openVersion(name, 2)).resolves.toBeUndefined() + }) + + it('recovers an identical concurrent insert race across store instances', async () => { + const name = databaseName() + const firstStore = new IndexedDBOutcomeEvidenceArtifactStore(name) + const secondStore = new IndexedDBOutcomeEvidenceArtifactStore(name) + await firstStore.open() + await secondStore.open() + const value = await artifact() + + const [first, second] = await Promise.all([ + firstStore.persist(value), + secondStore.persist(structuredClone(value)), + ]) + expect(second).toEqual(first) + expect(await firstStore.read(value.artifact_root)).toEqual(value) + firstStore.close() + secondStore.close() + }) + + it('survives close and reopen with verified read-back', async () => { + const store = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await store.open() + const value = await artifact() + await store.persist(value) + store.close() + + await store.open() + expect(await store.read(value.artifact_root)).toEqual(value) + store.close() + }) + + it('rejects an artifact with a forged content root', async () => { + const store = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await store.open() + const value = await artifact() + const forged = { ...value, artifact_root: H('0') } + await expect(store.persist(forged)).rejects.toThrow('artifact root') + store.close() + }) + + it('snapshots mutable input before its first asynchronous boundary', async () => { + const store = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await store.open() + const value = await artifact() + const mutable = structuredClone(value) + const expected = structuredClone(value) + + const persistence = store.persist(mutable) + ;(mutable as unknown as { + evidence_input: { post_gaps: typeof mutable.evidence_input.baseline.gaps } + }).evidence_input.post_gaps = mutable.evidence_input.baseline.gaps + await expect(persistence).resolves.toMatchObject({ artifact_root: value.artifact_root }) + expect(await store.read(value.artifact_root)).toEqual(expected) + store.close() + }) + + it('rejects non-I-JSON values before canonical aliases can be persisted', async () => { + const store = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await store.open() + const value = await artifact() + const aliased = structuredClone(value) as OutcomeEvidenceArtifactV1 & { + evidence_input: AdaptationOutcomeInput & { alias?: bigint; ignored?: undefined } + } + aliased.evidence_input.alias = 1n + aliased.evidence_input.ignored = undefined + const { artifact_root: _root, ...body } = aliased + const rootedAlias = { + ...body, + artifact_root: await hashValue({ + domain: 'AEGIS_OUTCOME_EVIDENCE_ARTIFACT_V1', + artifact: body, + }), + } as OutcomeEvidenceArtifactV1 + + await expect(store.persist(rootedAlias)).rejects.toThrow('non-JSON bigint') + + const negativeZero = structuredClone(value) as OutcomeEvidenceArtifactV1 & { + evidence_input: AdaptationOutcomeInput & { + verification: Array<{ step_index: number }> + } + } + negativeZero.evidence_input.verification[0]!.step_index = -0 + await expect(store.persist(negativeZero)).rejects.toThrow('negative zero') + store.close() + }) + + it('rejects accessors without invoking them before snapshotting', async () => { + const store = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await store.open() + const value = structuredClone(await artifact()) + const assessment = value.assessment + let reads = 0 + Object.defineProperty(value, 'assessment', { + enumerable: true, + get() { reads += 1; return assessment }, + }) + + await expect(store.persist(value)).rejects.toThrow('enumerable data property') + expect(reads).toBe(0) + store.close() + }) + + it('rejects a self-consistent but malformed derived artifact', async () => { + const store = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await store.open() + const value = await artifact() + const malformedBody = { + ...value, + assessment: null, + } + const { artifact_root: _oldRoot, ...body } = malformedBody + const malformed = { + ...body, + artifact_root: await hashValue({ + domain: 'AEGIS_OUTCOME_EVIDENCE_ARTIFACT_V1', + artifact: body, + }), + } as unknown as OutcomeEvidenceArtifactV1 + + await expect(store.persist(malformed)).rejects.toThrow('assessment does not match') + store.close() + }) + + it('fails closed when persisted bytes are changed behind the store', async () => { + const name = databaseName() + const store = new IndexedDBOutcomeEvidenceArtifactStore(name) + await store.open() + const value = await artifact() + await store.persist(value) + await overwriteRaw(name, { + ...value, + evidence_input: { + ...value.evidence_input, + post_gaps: value.evidence_input.baseline.gaps, + }, + }) + + await expect(store.read(value.artifact_root)).rejects.toThrow('artifact root mismatch') + await expect(store.persist(value)).rejects.toThrow(OutcomeEvidenceArtifactStoreError) + store.close() + }) + + it('rejects canonical aliases in an existing record during idempotent persistence', async () => { + const name = databaseName() + const store = new IndexedDBOutcomeEvidenceArtifactStore(name) + await store.open() + const value = await artifact() + await store.persist(value) + const aliased = structuredClone(value) as OutcomeEvidenceArtifactV1 & { + evidence_input: AdaptationOutcomeInput & { + verification: Array<{ step_index: number }> + } + } + aliased.evidence_input.verification[0]!.step_index = -0 + await overwriteRaw(name, aliased) + + await expect(store.persist(value)).rejects.toThrow('negative zero') + store.close() + }) + + it('returns null for a resolved root that is not present', async () => { + const store = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await store.open() + await expect(store.read(H('a'))).resolves.toBeNull() + store.close() + }) + + it('rejects empty database names and unresolved lookup roots', async () => { + expect(() => new IndexedDBOutcomeEvidenceArtifactStore(' ')).toThrow('database name') + const store = new IndexedDBOutcomeEvidenceArtifactStore(databaseName()) + await store.open() + await expect(store.read(H('0'))).rejects.toThrow('artifact root') + store.close() + }) +}) diff --git a/sovereign-omega-v2/test/unit/outcome-evidence-browser-boundary.test.ts b/sovereign-omega-v2/test/unit/outcome-evidence-browser-boundary.test.ts new file mode 100644 index 000000000..ae57c3ef6 --- /dev/null +++ b/sovereign-omega-v2/test/unit/outcome-evidence-browser-boundary.test.ts @@ -0,0 +1,38 @@ +import { resolve } from 'node:path' +import { describe, expect, it } from 'vitest' +import { build } from 'vite' + +interface GeneratedOutput { + readonly output: ReadonlyArray<{ + readonly type: string + readonly code?: string + }> +} + +describe('outcome evidence browser boundary', () => { + it('bundles the IndexedDB adapter without Node-only crypto shims', async () => { + const result = await build({ + configFile: false, + logLevel: 'silent', + build: { + write: false, + target: 'es2022', + minify: false, + lib: { + entry: resolve('src/metacognition/outcome-evidence-artifact-store.ts'), + formats: ['es'], + }, + }, + }) + const outputs = (Array.isArray(result) ? result : [result]) as GeneratedOutput[] + const code = outputs + .flatMap(output => output.output) + .filter(item => item.type === 'chunk') + .map(item => item.code ?? '') + .join('\n') + + expect(code).toContain('IndexedDBOutcomeEvidenceArtifactStore') + expect(code).not.toContain('node:crypto') + expect(code).not.toContain('__vite-browser-external') + }, 30_000) +}) diff --git a/sovereign-omega-v2/test/unit/self-regulator.test.ts b/sovereign-omega-v2/test/unit/self-regulator.test.ts index 24a01d4a1..d7ef48bd5 100644 --- a/sovereign-omega-v2/test/unit/self-regulator.test.ts +++ b/sovereign-omega-v2/test/unit/self-regulator.test.ts @@ -301,6 +301,21 @@ describe('regulateSelf', () => { })).rejects.toThrow(SelfRegulationError) }) + it('does not treat an unresolved evidence placeholder as verified', async () => { + await expect(regulateSelf({ + snapshot: await snapshot(), + gaps: [{ ...GAP, evidence_refs: [H('0')] }], + })).rejects.toThrow('gaps[0].evidence_refs[0] must resolve to a non-zero root') + }) + + it('rejects unresolved authority-relevant self-model components', async () => { + const model = await snapshot() + await expect(regulateSelf({ + snapshot: { ...model, capability_root: H('0') }, + gaps: [GAP], + })).rejects.toThrow('snapshot.capability_root must resolve to a non-zero root') + }) + it('routes a bounded, replayable proposal to Automaton-3', async () => { const model = await snapshot() const decision = await regulateSelf({ From c572405c9cb788c51f1e100f995cfe00082ff7a0 Mon Sep 17 00:00:00 2001 From: tarikskalic33 <228550385+tarikskalic33@users.noreply.github.com> Date: Tue, 28 Jul 2026 14:46:31 +0200 Subject: [PATCH 41/89] feat(provenance): verify authoritative receipts across runtimes --- .github/workflows/automaton-3.yml | 18 +- ...DR-0021-automaton-3-sovereign-execution.md | 18 +- ...untime-authoritative-receipt-provenance.md | 305 +++ ...ss-runtime-receipt-envelope.v1.schema.json | 285 +++ schemas/receipt-trust-registry.v1.schema.json | 117 ++ scripts/run-automaton3-tests.py | 17 +- scripts/validate-automaton3.py | 21 +- .../python/authoritative_receipts.py | 1848 +++++++++++++++++ .../generate_authoritative_receipt_vector.py | 364 ++++ .../tests/test_authoritative_receipts.py | 1071 ++++++++++ .../generate-authoritative-receipt-vector.ts | 473 +++++ .../authoritative-outcome-evidence-replay.ts | 289 +++ .../src/provenance/cross-runtime-receipts.ts | 733 +++++++ .../provenance/indexeddb-receipt-source.ts | 455 ++++ .../src/provenance/receipt-resolver.ts | 1009 +++++++++ ...horitative-outcome-evidence-replay.test.ts | 477 +++++ .../cross-runtime-receipt-provenance.test.ts | 813 ++++++++ .../python-cross-runtime-receipt-v1.json | 1 + .../typescript-cross-runtime-receipt-v1.json | 1 + 19 files changed, 8307 insertions(+), 8 deletions(-) create mode 100644 docs/adr/ADR-0022-cross-runtime-authoritative-receipt-provenance.md create mode 100644 schemas/cross-runtime-receipt-envelope.v1.schema.json create mode 100644 schemas/receipt-trust-registry.v1.schema.json create mode 100644 sovereign-omega-v2/python/authoritative_receipts.py create mode 100644 sovereign-omega-v2/python/generate_authoritative_receipt_vector.py create mode 100644 sovereign-omega-v2/python/tests/test_authoritative_receipts.py create mode 100644 sovereign-omega-v2/scripts/generate-authoritative-receipt-vector.ts create mode 100644 sovereign-omega-v2/src/metacognition/authoritative-outcome-evidence-replay.ts create mode 100644 sovereign-omega-v2/src/provenance/cross-runtime-receipts.ts create mode 100644 sovereign-omega-v2/src/provenance/indexeddb-receipt-source.ts create mode 100644 sovereign-omega-v2/src/provenance/receipt-resolver.ts create mode 100644 sovereign-omega-v2/test/integration/authoritative-outcome-evidence-replay.test.ts create mode 100644 sovereign-omega-v2/test/unit/cross-runtime-receipt-provenance.test.ts create mode 100644 sovereign-omega-v2/test/vectors/python-cross-runtime-receipt-v1.json create mode 100644 sovereign-omega-v2/test/vectors/typescript-cross-runtime-receipt-v1.json diff --git a/.github/workflows/automaton-3.yml b/.github/workflows/automaton-3.yml index c6bb67e75..715593ac2 100644 --- a/.github/workflows/automaton-3.yml +++ b/.github/workflows/automaton-3.yml @@ -47,7 +47,9 @@ jobs: with: node-version: '20' cache: npm - cache-dependency-path: sovereign-omega-v2/mcp-server/package-lock.json + cache-dependency-path: | + sovereign-omega-v2/package-lock.json + sovereign-omega-v2/mcp-server/package-lock.json - name: Install exact schema and signature validators run: pip install --requirement harness/requirements-automaton3.txt @@ -60,7 +62,7 @@ jobs: import json from pathlib import Path from jsonschema.validators import validator_for - for path in sorted(Path('schemas').glob('*-envelope.v1.schema.json')) + sorted(Path('schemas').glob('*-receipt.v1.schema.json')) + [Path('schemas/writer-lease.v1.schema.json')]: + for path in sorted(Path('schemas').glob('*-envelope.v1.schema.json')) + sorted(Path('schemas').glob('*-receipt.v1.schema.json')) + [Path('schemas/writer-lease.v1.schema.json'), Path('schemas/receipt-trust-registry.v1.schema.json')]: schema = json.loads(path.read_text(encoding='utf-8')) validator_for(schema).check_schema(schema) print(f'SCHEMA_OK {path}') @@ -81,6 +83,16 @@ jobs: npm run test:resources npm run test:automaton3 | tee "$RUNNER_TEMP/AUTOMATON3_MCP.log" + - name: Verify cross-runtime authoritative receipt provenance + working-directory: sovereign-omega-v2 + shell: bash + run: | + set -euo pipefail + npm ci + npm run typecheck + npm test -- --reporter=dot + npm run build + - name: Validate claims and constitutional anchors shell: bash run: | @@ -157,3 +169,5 @@ jobs: harness/policies/capability-map.v1.json schemas/execution-identity-envelope.v1.schema.json schemas/mutation-receipt.v1.schema.json + schemas/cross-runtime-receipt-envelope.v1.schema.json + schemas/receipt-trust-registry.v1.schema.json diff --git a/docs/adr/ADR-0021-automaton-3-sovereign-execution.md b/docs/adr/ADR-0021-automaton-3-sovereign-execution.md index 4b28793f5..c041d4332 100644 --- a/docs/adr/ADR-0021-automaton-3-sovereign-execution.md +++ b/docs/adr/ADR-0021-automaton-3-sovereign-execution.md @@ -29,7 +29,9 @@ WriterLease + provider execution + postcondition verification ## Determinism boundary -Deterministic roots contain no wall-clock timestamp, random ordering, host-specific absolute path, mutable deployment label, or unredacted secret. Operational time and resolved paths are attached as observational metadata and are not hashed into identity, policy, lease, event, or mutation roots. +Identity, policy, workspace, and canonical-state roots contain no ambient wall-clock read, random ordering, host-specific absolute path, mutable deployment label, or unredacted secret. Resolved paths remain observational metadata. + +Cross-runtime authoritative receipts are the deliberate exception for time binding. Their `timestamp_ms` and `expires_at_ms` fields are canonical unsigned decimal strings supplied by the caller's trusted event or clock context. Receipt code may compare those explicit values with a separately supplied `observed_at_ms` and bounded `maximum_clock_skew_ms`; it may not call `Date.now()`, `time.time()`, or infer time from a model response. A receipt is therefore byte-identical for identical explicit inputs while still proving lease lifetime and key-validity decisions. ADR-0022 defines this boundary. ## Workspace root convention @@ -57,8 +59,16 @@ Post-execution learning is a separate, advisory boundary in `sovereign-omega-v2/ The assessment remains non-authoritative: it cannot preserve or revert state, execute a mutation, grant authority, or update competence. Any recommendation still requires its declared next gate. -## Current provenance limit +## Cross-runtime authoritative receipt boundary + +The original Python `LeaseReceipt` and `MutationReceipt` records are deterministic local records, but their roots depend on in-process issuance state rather than an independently resolvable native signature. They remain legacy T2-only evidence. Embedding their hashes in a signed outcome-evidence certificate does not promote them, and they must never satisfy authoritative receipt provenance after restart. + +ADR-0022 introduces `cross-runtime-receipt-envelope.v1.schema.json` and `receipt-trust-registry.v1.schema.json`. A conforming implementation signs lease and mutation lifecycle receipts, stores them by content-derived `receipt_id`, resolves their operator-pinned trust registry, verifies their parent chain and state bindings, and reads the exact bytes back before returning an authoritative receipt result. Only receipts that pass that complete boundary may supply the terminal receipt roots used by outcome evidence. Missing, unsigned, stale, expired, replayed, partially persisted, or unverifiable records remain non-promotable. + +This change does not alter the metacognitive authority boundary. Receipt verification can authenticate what happened; it cannot decide that an adaptation is safe, execute or revert a mutation, grant authority, or update competence. The outcome comparator remains advisory and still requires its declared next gate. + +The phrase "T2 to T3 provenance boundary" in ADR-0022 names a provenance-assurance milestone. It does not promote an artifact into the repository's epistemic `T3` category, which means research conjecture. -The independent verifier certificate signs the complete evidence bundle, including the terminal receipt roots. The current TypeScript adapter does not resolve the underlying Python lease and mutation receipts or verify native signatures on those raw terminal records; those records do not yet carry such signatures. Therefore the persisted artifact is verifier-attested T2 evidence, not a claim that every terminal receipt was independently reconstructed from a durable cross-runtime source. +## Projection prohibition -No cockpit, game, or MCP status resource is exposed from this slice. A read-back failure can leave an add-only orphan artifact, but the caller receives no advanced loop. Projection should be added only after a confined cross-runtime artifact transport or witness chain makes the underlying terminal provenance independently resolvable. +No cockpit, game, or MCP status resource may claim authoritative provenance until the cross-runtime schemas, implementations, persistence and restart behavior, Python-to-TypeScript and TypeScript-to-Python vectors, adversarial tests, and complete existing suite have passed together. A read-back failure may leave an add-only orphan receipt or evidence artifact, but it must not advance canonical state, return an advanced metacognitive loop, or produce an authoritative projection. diff --git a/docs/adr/ADR-0022-cross-runtime-authoritative-receipt-provenance.md b/docs/adr/ADR-0022-cross-runtime-authoritative-receipt-provenance.md new file mode 100644 index 000000000..b125a713d --- /dev/null +++ b/docs/adr/ADR-0022-cross-runtime-authoritative-receipt-provenance.md @@ -0,0 +1,305 @@ +# ADR-0022: Cross-runtime authoritative receipt provenance + +Status: Accepted for implementation; authoritative projection admission pending + +Depends on: ADR-0021 + +## Context and terminology + +The outcome-evidence replay boundary can authenticate a verifier's certificate and persist it add-only, but the original Python lease and mutation receipts are not independently verifiable after process restart. Their validation depends on in-memory issuance sets. Consequently, a certificate that names those roots is verifier-attested T2 evidence; it is not proof that another runtime reconstructed the native lease or mutation event. + +This ADR closes the requested "T2 to T3 provenance boundary" by defining independently signed, content-addressed, cross-runtime receipts. Here, T2 and T3 describe provenance-assurance stages only. They do not refer to the repository epistemic taxonomy, where `T3` means research conjecture. Passing this ADR does not make a conjecture authoritative and does not change the epistemic tier of the outcome comparator. + +## Decision + +Python and TypeScript use one closed I-JSON receipt contract: + +- `schemas/cross-runtime-receipt-envelope.v1.schema.json` defines signed lease and mutation lifecycle receipts. +- `schemas/receipt-trust-registry.v1.schema.json` defines the operator-signed, versioned trust roots allowed to verify those receipts. +- TypeScript validates values with the existing strict I-JSON boundary and serializes them with the existing RFC 8785 JCS implementation. No receipt-specific serializer is permitted. +- Python applies the equivalent closed-I-JSON restrictions before its existing canonical byte path. It must not introduce a competing receipt serializer. +- Every object is checked for its exact schema keys before hashing or signature verification. Malformed, aliased, non-I-JSON, or schema-drifted values fail closed. + +The receipt resolver verifies facts. It does not grant authority, execute a provider action, mutate canonical state, or promote metacognitive competence. + +## Receipt envelope + +The exact top-level `Cross-Runtime Authoritative Receipt Envelope V1` fields are: + +```text +schema_version = "1.0.0" +receipt_kind +receipt_body +proof +receipt_id +``` + +`receipt_kind` is exactly one of: + +```text +LEASE_ISSUED +LEASE_ISSUANCE_DENIED +LEASE_RENEWED +LEASE_RENEWAL_DENIED +LEASE_EXPIRED +LEASE_REVOKED +MUTATION_ADMITTED +MUTATION_DENIED +MUTATION_COMPLETED +MUTATION_CANCELLED +MUTATION_FAILED +``` + +The exact `receipt_body` fields are: + +```text +receipt_sequence +actor_identity_root +session_identity_root +workspace_identity_root +holon_identity_root +authority_domain +authority_level +authority_receipt_hash +lease_id +lease_generation +fencing_token +lease_authorization_receipt_hash +parent_receipt_hash +observed_state_root +expected_state_root +action_digest +before_state_root +after_state_root +result_digest +timestamp_ms +expires_at_ms +nonce +outcome +denial_codes +``` + +The actor, session, workspace, and holon roots and `lease_id` are always resolved, non-zero SHA-256 values. `authority_level` is one of `D0` through `D4`. Sequence, generation, and time fields are canonical unsigned decimal strings, not JSON numbers. The fencing token and all receipt, state, action, and result bindings are lowercase SHA-256 values. `denial_codes` is a unique canonical array whose implementation normalization is deterministic. + +`LEASE_ISSUANCE_DENIED` carries the zero fencing token because no lease was created. Every other receipt kind carries a non-zero presented or issued fence. Lease receipts carry zero `authority_receipt_hash` and `lease_authorization_receipt_hash`; mutation receipts require both links to be non-zero. These absence rules are enforced by the JSON schema and both runtimes, not inferred by the resolver. + +The exact `proof` fields are: + +```text +algorithm = "Ed25519" +signer_key_id +verifier_identity_root +trust_registry_version +trust_registry_root +signature +``` + +The proof does not carry an independently trusted public key. The resolver obtains that key only from the authenticated registry named by `trust_registry_root`, then checks the key ID, verifier identity, authority domain, receipt kind, validity interval, and status. + +## Outcome and lifecycle invariants + +Receipt kind and outcome are paired as follows: + +| Receipt kind | Required outcome | +|---|---| +| `LEASE_ISSUED` | `ADMITTED` | +| `LEASE_ISSUANCE_DENIED` | `DENIED` | +| `LEASE_RENEWED` | `ADMITTED` | +| `LEASE_RENEWAL_DENIED` | `DENIED` | +| `LEASE_EXPIRED` | `EXPIRED` | +| `LEASE_REVOKED` | `REVOKED` | +| `MUTATION_ADMITTED` | `ADMITTED` | +| `MUTATION_DENIED` | `DENIED` | +| `MUTATION_COMPLETED` | `COMPLETED` | +| `MUTATION_CANCELLED` | `CANCELLED` | +| `MUTATION_FAILED` | `FAILED` | + +`ADMITTED` and `COMPLETED` outcomes require an empty denial-code array. `DENIED`, `EXPIRED`, `REVOKED`, `CANCELLED`, and `FAILED` outcomes require at least one code. Positive lease and mutation receipts require resolved fencing, state, action, and applicable authority or lease-authorization bindings. A zero hash can represent only an explicitly absent predecessor or evidence link allowed for that receipt kind; it can never satisfy a required positive-path proof or promote outcome evidence. + +The parent link and `receipt_sequence` form one append-only chain. Replay state is partitioned by workspace, holon, and authority domain; actor, session, and authority-level changes inside a live lease are rejected as binding mismatches. Genesis alone may use the zero parent. Every later receipt must name the exact prior signed receipt ID and advance the canonical decimal sequence by one. A renewal retains the lease ID, advances the generation and fence, and names the global chain head as its parent. The renewed receipt becomes the current lease-authorization root, which every later mutation receipt must name explicitly. Expiry and revocation terminate the lease. Mutation admission binds the current live generation and fence; exactly one terminal mutation receipt may consume that admission. + +## Canonical signature and content address + +The Ed25519 receipt signature is computed over these exact JCS bytes: + +```text +JCS({ + domain: "AEGIS_CROSS_RUNTIME_RECEIPT_SIGNATURE_V1", + schema_version, + receipt_kind, + receipt_body, + proof: { + algorithm, + signer_key_id, + verifier_identity_root, + trust_registry_version, + trust_registry_root + } +}) +``` + +The signature message excludes only `proof.signature` and `receipt_id`. + +After the signature is attached, `receipt_id` is derived exactly as follows: + +```text +SHA256(JCS({ + domain: "AEGIS_CROSS_RUNTIME_RECEIPT_ID_V1", + envelope: { + schema_version, + receipt_kind, + receipt_body, + proof: { + algorithm, + signer_key_id, + verifier_identity_root, + trust_registry_version, + trust_registry_root, + signature + } + } +})) +``` + +Thus V1 `receipt_id` is also the signed envelope's content address. A store lookup by receipt ID and a lookup by content hash must resolve to the same exact bytes; any second byte sequence for the same key is corruption, not an update. + +Domain separation is mandatory. An authority, outcome-certificate, event, trust-registry, or other signature is not interchangeable with a receipt signature even if its JSON fields happen to match. + +## Explicit time and clock-skew model + +`timestamp_ms`, `expires_at_ms`, registry times, and verifier observation time are canonical decimal strings representing epoch milliseconds. They are provided by the caller's trusted event or clock context. Receipt issuance, verification, replay, and tests must not call `Date.now()`, `time.time()`, or consult a model response for temporal authority. + +The resolver receives `observed_at_ms` and `max_clock_skew_ms` separately from the untrusted receipt. It rejects receipts from beyond the allowed future skew, receipts or signing keys outside their validity interval, and mutation admission under an expired lease. At the live Python authority boundary, lease decisions use the later of the signed event timestamp and the separately supplied monotonic observation time. A backdated event timestamp therefore cannot revive an expired lease. Historical restart verification does not apply that lower bound retroactively; it verifies the signed event-time chain. Boundary equality and skew behavior are fixed by tests. A `LEASE_EXPIRED` receipt must be timestamped at or after its bound expiry. Clock ambiguity never extends a lease silently; failure to establish trusted time denies promotion. + +## Trust-root registry + +The exact top-level `Receipt Trust Registry V1` fields are: + +```text +schema_version = "1.0.0" +registry_body +proof +registry_root +``` + +The exact `registry_body` fields are: + +```text +registry_version +previous_registry_root +issued_at_ms +valid_from_ms +expires_at_ms +operator_key_id +keys +``` + +Every key entry contains exactly: + +```text +key_id +public_key +verifier_identity_root +valid_from_ms +expires_at_ms +status = ACTIVE | REVOKED +authority_domains +receipt_kinds +``` + +The registry proof contains exactly `algorithm = "Ed25519"` and `signature`. + +Registry signatures and roots use these exact derivations: + +```text +signature_message = JCS({ + domain: "AEGIS_RECEIPT_TRUST_REGISTRY_SIGNATURE_V1", + schema_version, + registry_body, + proof: { algorithm } +}) + +registry_root = SHA256(JCS({ + domain: "AEGIS_RECEIPT_TRUST_REGISTRY_ROOT_V1", + registry: { + schema_version, + registry_body, + proof: { algorithm, signature } + } +})) +``` + +The signature message excludes `proof.signature` and `registry_root`; the root input excludes only `registry_root`. + +Trust begins with an operator public key or registry root pinned outside all model output, receipt data, chat context, and fetched registry content. `operator_key_id` is a binding checked against that host context; it is not itself a trust root. Only the explicitly pinned genesis may use the zero previous root. Every rotation is an operator-signed successor with a strictly increasing canonical version and the exact previous registry root. Historical versions remain resolvable for historical verification, subject to the declared validity and revocation policy. + +Key IDs and public keys are unique. Authority-domain and receipt-kind scopes are explicit and non-empty. The resolver rejects unknown registries or keys, broken registry chains, stale or rollback versions, invalid operator signatures, revoked keys, keys outside their interval, identity mismatches, and keys used outside their declared domain or receipt-kind scope. A receipt signer can never authorize its own trust-root rotation. + +## Resolver and add-only persistence + +The TypeScript resolver accepts a receipt ID or signed content hash plus a separately supplied trusted context. It retrieves the exact receipt, applies strict I-JSON and exact-schema validation, recomputes `receipt_id`, resolves and authenticates the registry chain against the pinned operator root, verifies the signer scope and Ed25519 signature, checks time, lease generation, fencing token, expected state, parent chain, and replay indexes, and returns a deeply immutable verified value. + +Receipt and registry stores are add-only and content-addressed. Persisting the same bytes is idempotent; different bytes at an existing key fail closed. Each write is followed by exact read-back, closed-I-JSON normalization, and content-root verification. The trusted resolver then performs registry, signature, parent-chain, lease, fence, replay, and state verification before evidence can be promoted. A content-valid but untrusted orphan may be stored for forensics; it remains non-promotable. Process-local issuance sets are not evidence. + +## Persistence-before-state and partial failure + +Consequential mutation is a two-phase receipt protocol: + +1. Validate authority, live lease, expected state, and replay indexes; persist and read back `MUTATION_ADMITTED` before contacting the provider or changing state. +2. After execution or denial, persist and read back exactly one terminal `MUTATION_COMPLETED`, `MUTATION_DENIED`, `MUTATION_CANCELLED`, or `MUTATION_FAILED` receipt before publishing a new canonical state root or allowing outcome-evidence promotion. + +If admission persistence fails, no mutation may run. If the provider acts but terminal persistence or read-back fails, the canonical state root must not advance and the action is quarantined for operator reconciliation. An orphan admission is evidence of an incomplete attempt, never success. SQLite validates pending bytes inside the append transaction before commit. IndexedDB persists registry and receipt batches in one transaction; a late uniqueness violation aborts earlier writes in the same batch. Restart reconstruction must recover the receipt head, active lease, consumed admissions, terminal mutations, and replay indexes entirely from stored receipts and authenticated registries. + +## Denial and failure state invariant + +Every denied action has a resolvable signed terminal receipt and leaves canonical state unchanged: + +```text +outcome == DENIED +before_state_root == after_state_root +canonical_state_root_after == canonical_state_root_before +``` + +Cancellation and terminal failure also require unchanged before and after roots unless a separately admitted, signed, and verified compensating mutation proves a rollback. A denial receipt is not permission to perform cleanup mutation. Tests must observe the canonical root before and after the denied provider boundary, not merely compare caller-supplied strings. + +## Replay and concurrency + +Receipt replay is distinct from read-only resolution. Re-resolving stored bytes is safe; attempting to admit or complete an already consumed mutation is rejected. A signed `MUTATION_DENIED` carrying `MUTATION_REPLAY` remains resolvable evidence of that rejected attempt and never becomes a second admission. The broad action-claim index binds actor, session, workspace, holon, authority domain, and action digest so a claimed action cannot be revived under a different lease or authority level. The signed attempt and terminal binding additionally fixes authority level, lease ID and generation, fencing token, nonce, authority root, lease-authorization root, parent, and receipt ID. Conflicting reuse of any unique operation binding is denied. + +Concurrent acquisition, renewal, admission, completion, cancellation, or failure races are serialized at the persistent chain head. At most one contender can advance a given parent, generation, fence, or mutation admission. Contention evaluated before append produces a signed denial. A compare-and-append loser detected at the storage boundary receives a fail-closed persistence conflict and must recover the committed head before retrying; it cannot create a second receipt or terminal success. Storage conflicts are not reinterpreted as signed semantic decisions. + +## Cross-runtime golden vectors + +Golden inputs are closed I-JSON values with fixed RFC 8032 keys, decimal time, nonce, state roots, and lifecycle ordering. Python and TypeScript generate their outputs independently; neither generator invokes the other or consumes the other runtime's output. Each generator emits the same canonical 15-receipt chain covering all 11 V1 receipt kinds. Generated files contain the complete registry, receipts, terminal receipt ID, trusted replay context, receipt IDs, registry root, and signatures. + +TypeScript must independently verify the Python-generated registry and every Python-generated receipt kind. Python must independently verify the TypeScript-generated registry and every TypeScript-generated receipt kind. Matching inputs must produce byte-identical signing messages, deterministic Ed25519 signatures, receipt IDs, and registry roots. Tampered vectors are rejected. Generators write to temporary paths during verification so tests do not mutate committed evidence. + +## Migration + +Existing `AuthorityDecisionReceipt` signatures remain authorization evidence under their existing policy. Existing unsigned `LeaseReceipt`, `MutationReceipt`, and their in-memory issuance sets are legacy T2-only evidence. They are not grandfathered, wrapped, re-signed, or reinterpreted as V1 cross-runtime receipts. Evidence containing only legacy terminal roots remains recordable as historical or negative evidence but cannot promote authoritative provenance. + +New activity uses the cross-runtime envelope from lease issuance onward. A chain cannot mix legacy and V1 parents. Migration starts a declared V1 genesis bound to the current canonical state, workspace, actor, session, holon, authority, and pinned trust registry. + +## Advisory outcome boundary + +Independent receipt verification proves signature, signer scope, chain, lease, time, action, and state bindings. It does not prove that an adaptation is beneficial or safe. `outcome-comparator.ts` remains advisory: it cannot execute or revert a mutation, grant authority, alter canonical state, or update competence. Authenticated denial and failure evidence may be persisted as negative evidence without becoming authority. + +## Projection prohibition and admission condition + +No cockpit, MCP, game, or other product projection may label receipt provenance authoritative until all of the following pass together: + +1. Python-generated receipts verify independently in TypeScript. +2. TypeScript-generated verification decisions replay without private chat context. +3. Every success and denial resolves to a signed terminal receipt. +4. Unsigned, unknown-root, expired, stale-fence, stale-state, broken-chain, replayed, ambiguous-identity, malformed, and partially persisted evidence cannot promote. +5. Denial state-root preservation is observed. +6. Restart and exact read-back verification pass for every receipt kind and registry rotation. +7. The complete existing Python and TypeScript suites, typecheck, production build, browser bundle, frozen hashes, and diff checks remain clean. + +Until that admission condition is recorded, the schemas and implementation are proposed infrastructure, not an authoritative cockpit or MCP claim. + +## Consequences and remaining integration blockers + +This design removes private process memory and chat history from receipt verification, makes signer rotation explicit, and turns receipt identity into a stable cross-runtime content address. It also requires durable receipt and registry transport, trusted caller time, operator key provisioning, atomic chain-head coordination, and complete lifecycle evidence. + +After this phase passes, cockpit or MCP integration still requires a confined read-only transport, an explicit freshness source for the pinned registry and clock observation, operator-visible degraded states, and UI wording that distinguishes verified provenance from an advisory outcome assessment. Those integrations are outside this ADR's implementation slice. diff --git a/schemas/cross-runtime-receipt-envelope.v1.schema.json b/schemas/cross-runtime-receipt-envelope.v1.schema.json new file mode 100644 index 000000000..86c8b5f9c --- /dev/null +++ b/schemas/cross-runtime-receipt-envelope.v1.schema.json @@ -0,0 +1,285 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://aegis.local/schemas/cross-runtime-receipt-envelope.v1.schema.json", + "title": "AEGIS Cross-Runtime Authoritative Receipt Envelope V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "receipt_kind", + "receipt_body", + "proof", + "receipt_id" + ], + "properties": { + "schema_version": { "const": "1.0.0" }, + "receipt_kind": { + "enum": [ + "LEASE_ISSUED", + "LEASE_ISSUANCE_DENIED", + "LEASE_RENEWED", + "LEASE_RENEWAL_DENIED", + "LEASE_EXPIRED", + "LEASE_REVOKED", + "MUTATION_ADMITTED", + "MUTATION_DENIED", + "MUTATION_COMPLETED", + "MUTATION_CANCELLED", + "MUTATION_FAILED" + ] + }, + "receipt_body": { "$ref": "#/$defs/receiptBody" }, + "proof": { "$ref": "#/$defs/receiptProof" }, + "receipt_id": { "$ref": "#/$defs/hash" } + }, + "allOf": [ + { + "if": { "properties": { "receipt_kind": { "const": "LEASE_ISSUED" } } }, + "then": { "properties": { "receipt_body": { "properties": { "outcome": { "const": "ADMITTED" } } } } } + }, + { + "if": { "properties": { "receipt_kind": { "const": "LEASE_ISSUANCE_DENIED" } } }, + "then": { "properties": { "receipt_body": { "properties": { "outcome": { "const": "DENIED" } } } } } + }, + { + "if": { "properties": { "receipt_kind": { "const": "LEASE_RENEWED" } } }, + "then": { "properties": { "receipt_body": { "properties": { "outcome": { "const": "ADMITTED" } } } } } + }, + { + "if": { "properties": { "receipt_kind": { "const": "LEASE_RENEWAL_DENIED" } } }, + "then": { "properties": { "receipt_body": { "properties": { "outcome": { "const": "DENIED" } } } } } + }, + { + "if": { "properties": { "receipt_kind": { "const": "LEASE_EXPIRED" } } }, + "then": { "properties": { "receipt_body": { "properties": { "outcome": { "const": "EXPIRED" } } } } } + }, + { + "if": { "properties": { "receipt_kind": { "const": "LEASE_REVOKED" } } }, + "then": { "properties": { "receipt_body": { "properties": { "outcome": { "const": "REVOKED" } } } } } + }, + { + "if": { "properties": { "receipt_kind": { "const": "MUTATION_ADMITTED" } } }, + "then": { "properties": { "receipt_body": { "properties": { "outcome": { "const": "ADMITTED" } } } } } + }, + { + "if": { "properties": { "receipt_kind": { "const": "MUTATION_DENIED" } } }, + "then": { "properties": { "receipt_body": { "properties": { "outcome": { "const": "DENIED" } } } } } + }, + { + "if": { "properties": { "receipt_kind": { "const": "MUTATION_COMPLETED" } } }, + "then": { "properties": { "receipt_body": { "properties": { "outcome": { "const": "COMPLETED" } } } } } + }, + { + "if": { "properties": { "receipt_kind": { "const": "MUTATION_CANCELLED" } } }, + "then": { "properties": { "receipt_body": { "properties": { "outcome": { "const": "CANCELLED" } } } } } + }, + { + "if": { "properties": { "receipt_kind": { "const": "MUTATION_FAILED" } } }, + "then": { "properties": { "receipt_body": { "properties": { "outcome": { "const": "FAILED" } } } } } + }, + { + "if": { + "properties": { + "receipt_kind": { + "enum": [ + "LEASE_ISSUANCE_DENIED", + "LEASE_RENEWAL_DENIED", + "LEASE_EXPIRED", + "LEASE_REVOKED", + "MUTATION_DENIED", + "MUTATION_CANCELLED", + "MUTATION_FAILED" + ] + } + } + }, + "then": { + "properties": { + "receipt_body": { "properties": { "denial_codes": { "minItems": 1 } } } + } + }, + "else": { + "properties": { + "receipt_body": { "properties": { "denial_codes": { "maxItems": 0 } } } + } + } + }, + { + "if": { + "properties": { + "receipt_kind": { "const": "LEASE_ISSUANCE_DENIED" } + } + }, + "then": { + "properties": { + "receipt_body": { + "properties": { + "fencing_token": { + "const": "0000000000000000000000000000000000000000000000000000000000000000" + } + } + } + } + }, + "else": { + "properties": { + "receipt_body": { + "properties": { + "fencing_token": { "$ref": "#/$defs/nonZeroHash" } + } + } + } + } + }, + { + "if": { + "properties": { + "receipt_kind": { + "enum": [ + "LEASE_ISSUED", + "LEASE_ISSUANCE_DENIED", + "LEASE_RENEWED", + "LEASE_RENEWAL_DENIED", + "LEASE_EXPIRED", + "LEASE_REVOKED" + ] + } + } + }, + "then": { + "properties": { + "receipt_body": { + "properties": { + "authority_receipt_hash": { + "const": "0000000000000000000000000000000000000000000000000000000000000000" + }, + "lease_authorization_receipt_hash": { + "const": "0000000000000000000000000000000000000000000000000000000000000000" + } + } + } + } + }, + "else": { + "properties": { + "receipt_body": { + "properties": { + "authority_receipt_hash": { "$ref": "#/$defs/nonZeroHash" }, + "lease_authorization_receipt_hash": { "$ref": "#/$defs/nonZeroHash" } + } + } + } + } + } + ], + "$defs": { + "hash": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "nonZeroHash": { + "allOf": [ + { "$ref": "#/$defs/hash" }, + { "not": { "const": "0000000000000000000000000000000000000000000000000000000000000000" } } + ] + }, + "decimal": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$", + "maxLength": 20 + }, + "safeId": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$" + }, + "receiptBody": { + "type": "object", + "additionalProperties": false, + "required": [ + "receipt_sequence", + "actor_identity_root", + "session_identity_root", + "workspace_identity_root", + "holon_identity_root", + "authority_domain", + "authority_level", + "authority_receipt_hash", + "lease_id", + "lease_generation", + "fencing_token", + "lease_authorization_receipt_hash", + "parent_receipt_hash", + "observed_state_root", + "expected_state_root", + "action_digest", + "before_state_root", + "after_state_root", + "result_digest", + "timestamp_ms", + "expires_at_ms", + "nonce", + "outcome", + "denial_codes" + ], + "properties": { + "receipt_sequence": { "$ref": "#/$defs/decimal" }, + "actor_identity_root": { "$ref": "#/$defs/nonZeroHash" }, + "session_identity_root": { "$ref": "#/$defs/nonZeroHash" }, + "workspace_identity_root": { "$ref": "#/$defs/nonZeroHash" }, + "holon_identity_root": { "$ref": "#/$defs/nonZeroHash" }, + "authority_domain": { "$ref": "#/$defs/safeId" }, + "authority_level": { "enum": ["D0", "D1", "D2", "D3", "D4"] }, + "authority_receipt_hash": { "$ref": "#/$defs/hash" }, + "lease_id": { "$ref": "#/$defs/nonZeroHash" }, + "lease_generation": { "$ref": "#/$defs/decimal" }, + "fencing_token": { "$ref": "#/$defs/hash" }, + "lease_authorization_receipt_hash": { "$ref": "#/$defs/hash" }, + "parent_receipt_hash": { "$ref": "#/$defs/hash" }, + "observed_state_root": { "$ref": "#/$defs/nonZeroHash" }, + "expected_state_root": { "$ref": "#/$defs/nonZeroHash" }, + "action_digest": { "$ref": "#/$defs/nonZeroHash" }, + "before_state_root": { "$ref": "#/$defs/nonZeroHash" }, + "after_state_root": { "$ref": "#/$defs/nonZeroHash" }, + "result_digest": { "$ref": "#/$defs/nonZeroHash" }, + "timestamp_ms": { "$ref": "#/$defs/decimal" }, + "expires_at_ms": { "$ref": "#/$defs/decimal" }, + "nonce": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{15,127}$" + }, + "outcome": { + "enum": ["ADMITTED", "DENIED", "COMPLETED", "CANCELLED", "FAILED", "EXPIRED", "REVOKED"] + }, + "denial_codes": { + "type": "array", + "items": { "$ref": "#/$defs/safeId" }, + "uniqueItems": true, + "maxItems": 32 + } + } + }, + "receiptProof": { + "type": "object", + "additionalProperties": false, + "required": [ + "algorithm", + "signer_key_id", + "verifier_identity_root", + "trust_registry_version", + "trust_registry_root", + "signature" + ], + "properties": { + "algorithm": { "const": "Ed25519" }, + "signer_key_id": { "$ref": "#/$defs/safeId" }, + "verifier_identity_root": { "$ref": "#/$defs/nonZeroHash" }, + "trust_registry_version": { "$ref": "#/$defs/decimal" }, + "trust_registry_root": { "$ref": "#/$defs/nonZeroHash" }, + "signature": { + "type": "string", + "pattern": "^[0-9a-f]{128}$" + } + } + } + } +} diff --git a/schemas/receipt-trust-registry.v1.schema.json b/schemas/receipt-trust-registry.v1.schema.json new file mode 100644 index 000000000..1aa55e77d --- /dev/null +++ b/schemas/receipt-trust-registry.v1.schema.json @@ -0,0 +1,117 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://aegis.local/schemas/receipt-trust-registry.v1.schema.json", + "title": "AEGIS Receipt Trust Registry V1", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "registry_body", "proof", "registry_root"], + "properties": { + "schema_version": { "const": "1.0.0" }, + "registry_body": { "$ref": "#/$defs/registryBody" }, + "proof": { "$ref": "#/$defs/registryProof" }, + "registry_root": { "$ref": "#/$defs/nonZeroHash" } + }, + "$defs": { + "hash": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "nonZeroHash": { + "allOf": [ + { "$ref": "#/$defs/hash" }, + { "not": { "const": "0000000000000000000000000000000000000000000000000000000000000000" } } + ] + }, + "decimal": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$", + "maxLength": 20 + }, + "safeId": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$" + }, + "receiptKind": { + "enum": [ + "LEASE_ISSUED", + "LEASE_ISSUANCE_DENIED", + "LEASE_RENEWED", + "LEASE_RENEWAL_DENIED", + "LEASE_EXPIRED", + "LEASE_REVOKED", + "MUTATION_ADMITTED", + "MUTATION_DENIED", + "MUTATION_COMPLETED", + "MUTATION_CANCELLED", + "MUTATION_FAILED" + ] + }, + "registryBody": { + "type": "object", + "additionalProperties": false, + "required": [ + "registry_version", + "previous_registry_root", + "issued_at_ms", + "valid_from_ms", + "expires_at_ms", + "operator_key_id", + "keys" + ], + "properties": { + "registry_version": { "$ref": "#/$defs/decimal" }, + "previous_registry_root": { "$ref": "#/$defs/hash" }, + "issued_at_ms": { "$ref": "#/$defs/decimal" }, + "valid_from_ms": { "$ref": "#/$defs/decimal" }, + "expires_at_ms": { "$ref": "#/$defs/decimal" }, + "operator_key_id": { "$ref": "#/$defs/safeId" }, + "keys": { + "type": "array", + "minItems": 1, + "maxItems": 128, + "items": { "$ref": "#/$defs/keyEntry" } + } + } + }, + "keyEntry": { + "type": "object", + "additionalProperties": false, + "required": [ + "key_id", + "public_key", + "verifier_identity_root", + "valid_from_ms", + "expires_at_ms", + "status", + "authority_domains", + "receipt_kinds" + ], + "properties": { + "key_id": { "$ref": "#/$defs/safeId" }, + "public_key": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "verifier_identity_root": { "$ref": "#/$defs/nonZeroHash" }, + "valid_from_ms": { "$ref": "#/$defs/decimal" }, + "expires_at_ms": { "$ref": "#/$defs/decimal" }, + "status": { "enum": ["ACTIVE", "REVOKED"] }, + "authority_domains": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "$ref": "#/$defs/safeId" } + }, + "receipt_kinds": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "$ref": "#/$defs/receiptKind" } + } + } + }, + "registryProof": { + "type": "object", + "additionalProperties": false, + "required": ["algorithm", "signature"], + "properties": { + "algorithm": { "const": "Ed25519" }, + "signature": { "type": "string", "pattern": "^[0-9a-f]{128}$" } + } + } + } +} diff --git a/scripts/run-automaton3-tests.py b/scripts/run-automaton3-tests.py index a06d29978..de83a95b7 100644 --- a/scripts/run-automaton3-tests.py +++ b/scripts/run-automaton3-tests.py @@ -14,6 +14,7 @@ TEST_FILES = ( ROOT / "sovereign-omega-v2/python/tests/test_automaton3.py", ROOT / "sovereign-omega-v2/python/tests/test_operator_visibility.py", + ROOT / "sovereign-omega-v2/python/tests/test_authoritative_receipts.py", ) ASSERTION_TESTS = { @@ -27,6 +28,9 @@ "test_13_replayed_fencing_token", "test_29_receipt_chain_break", "test_authority_admission_is_not_terminal_success", + "test_08_stale_state_fence_and_lease_link_are_signed_denials_with_no_change", + "test_24_every_receipt_kind_survives_persisted_restart_readback", + "test_26_backdated_timestamp_cannot_revive_expired_lease", }, "external_side_effect_absence": { "test_01_unknown_coordinator_capability", @@ -38,6 +42,15 @@ "test_authorization_mutation_and_cancellation_are_chained", "test_broken_operator_chain_is_denied", }, + "cross_runtime_provenance": { + "test_04_python_golden_vector_matches_schemas_and_derivations", + "test_23_python_independently_verifies_and_replays_typescript_golden_vector", + }, + "restart_readback": { + "test_14_readback_failure_rolls_back_without_orphan_promotion", + "test_24_every_receipt_kind_survives_persisted_restart_readback", + "test_27_registry_readback_failure_rolls_back_without_partial_persistence", + }, } @@ -62,7 +75,7 @@ def main() -> int: raw_log = "".join(outputs) observed_test_count = sum(int(value) for value in re.findall(r"Ran ([0-9]+) tests?", raw_log)) - expected_test_count = 54 + expected_test_count = 81 if observed_test_count != expected_test_count: return_code = return_code or 1 log = raw_log.replace(str(ROOT), "") @@ -96,6 +109,8 @@ def main() -> int: "state_preservation_asserted": assertion_sets["state_preservation"]["satisfied"], "external_side_effect_absence_asserted": assertion_sets["external_side_effect_absence"]["satisfied"], "operator_visibility_asserted": assertion_sets["operator_visibility"]["satisfied"], + "cross_runtime_provenance_asserted": assertion_sets["cross_runtime_provenance"]["satisfied"], + "restart_readback_asserted": assertion_sets["restart_readback"]["satisfied"], "assertion_sets": assertion_sets, "passed_test_ids": passed_test_ids, "return_code": return_code, diff --git a/scripts/validate-automaton3.py b/scripts/validate-automaton3.py index 9075754fa..d33e42580 100644 --- a/scripts/validate-automaton3.py +++ b/scripts/validate-automaton3.py @@ -32,12 +32,27 @@ "sovereign-omega-v2/python/tests/test_automaton3.py", "sovereign-omega-v2/python/tests/test_coordinator_authority.py", "sovereign-omega-v2/python/tests/test_operator_visibility.py", + "sovereign-omega-v2/python/authoritative_receipts.py", + "sovereign-omega-v2/python/tests/test_authoritative_receipts.py", + "sovereign-omega-v2/python/generate_authoritative_receipt_vector.py", + "sovereign-omega-v2/src/provenance/cross-runtime-receipts.ts", + "sovereign-omega-v2/src/provenance/receipt-resolver.ts", + "sovereign-omega-v2/src/provenance/indexeddb-receipt-source.ts", + "sovereign-omega-v2/src/metacognition/authoritative-outcome-evidence-replay.ts", + "sovereign-omega-v2/test/unit/cross-runtime-receipt-provenance.test.ts", + "sovereign-omega-v2/test/integration/authoritative-outcome-evidence-replay.test.ts", + "sovereign-omega-v2/test/vectors/python-cross-runtime-receipt-v1.json", + "sovereign-omega-v2/test/vectors/typescript-cross-runtime-receipt-v1.json", + "sovereign-omega-v2/scripts/generate-authoritative-receipt-vector.ts", "schemas/execution-identity-envelope.v1.schema.json", "schemas/authority-decision-receipt.v1.schema.json", "schemas/mutation-receipt.v1.schema.json", "schemas/event-envelope.v1.schema.json", "schemas/writer-lease.v1.schema.json", + "schemas/cross-runtime-receipt-envelope.v1.schema.json", + "schemas/receipt-trust-registry.v1.schema.json", "docs/adr/ADR-0021-automaton-3-sovereign-execution.md", + "docs/adr/ADR-0022-cross-runtime-authoritative-receipt-provenance.md", "docs/security/AUTOMATON3_THREAT_MODEL.md", "docs/operations/LAW_OF_SILENCE_V2.md", "docs/operations/BRANCH_RULESET_AUTOMATON3.md", @@ -130,7 +145,7 @@ def evaluate( violations.append("authority bypass detected") if summary.get("adaptive_attempts") != [1, 10, 100]: violations.append("adaptive attempt matrix incomplete") - if summary.get("expected_test_count") != 54: + if summary.get("expected_test_count") != 81: violations.append("Automaton-3 test count incomplete") if summary.get("observed_test_count") != summary.get("expected_test_count"): violations.append("Automaton-3 observed test count mismatch") @@ -140,6 +155,10 @@ def evaluate( violations.append("state preservation not asserted") if summary.get("external_side_effect_absence_asserted") is not True: violations.append("external side-effect absence not asserted") + if summary.get("cross_runtime_provenance_asserted") is not True: + violations.append("cross-runtime receipt provenance not asserted") + if summary.get("restart_readback_asserted") is not True: + violations.append("authoritative receipt restart/readback not asserted") test_summary_root = summary.get("summary_root", "0" * 64) except Exception as exc: violations.append(f"test summary unavailable: {type(exc).__name__}") diff --git a/sovereign-omega-v2/python/authoritative_receipts.py b/sovereign-omega-v2/python/authoritative_receipts.py new file mode 100644 index 000000000..980875135 --- /dev/null +++ b/sovereign-omega-v2/python/authoritative_receipts.py @@ -0,0 +1,1848 @@ +"""Cross-runtime authoritative receipt provenance. + +This module implements the Python half of the T2 -> T3 receipt boundary. Its +wire contracts are the repository schemas +``cross-runtime-receipt-envelope.v1.schema.json`` and +``receipt-trust-registry.v1.schema.json``. All integrity bytes are produced by +``canonical_envelope.canon``; no alternate serializer is used. + +Time is an explicit, caller-supplied observation represented as a canonical +decimal string. The module never reads a wall clock. +""" +from __future__ import annotations + +import copy +import json +import math +import re +import sqlite3 +import threading +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Iterable, Mapping, Sequence + +try: # Direct test execution places this directory on sys.path. + from .canonical_envelope import canon, sha256_hex +except ImportError: # pragma: no cover - exercised by direct script execution + from canonical_envelope import canon, sha256_hex + + +SCHEMA_VERSION = "1.0.0" +ZERO_HASH = "0" * 64 +ED25519 = "Ed25519" + +RECEIPT_SIGNATURE_DOMAIN = "AEGIS_CROSS_RUNTIME_RECEIPT_SIGNATURE_V1" +RECEIPT_ID_DOMAIN = "AEGIS_CROSS_RUNTIME_RECEIPT_ID_V1" +REGISTRY_SIGNATURE_DOMAIN = "AEGIS_RECEIPT_TRUST_REGISTRY_SIGNATURE_V1" +REGISTRY_ROOT_DOMAIN = "AEGIS_RECEIPT_TRUST_REGISTRY_ROOT_V1" + +RECEIPT_KINDS = ( + "LEASE_ISSUED", + "LEASE_ISSUANCE_DENIED", + "LEASE_RENEWED", + "LEASE_RENEWAL_DENIED", + "LEASE_EXPIRED", + "LEASE_REVOKED", + "MUTATION_ADMITTED", + "MUTATION_DENIED", + "MUTATION_COMPLETED", + "MUTATION_CANCELLED", + "MUTATION_FAILED", +) +LEASE_KINDS = frozenset(kind for kind in RECEIPT_KINDS if kind.startswith("LEASE_")) +MUTATION_KINDS = frozenset(kind for kind in RECEIPT_KINDS if kind.startswith("MUTATION_")) +AUTHORITY_LEVELS = frozenset(("D0", "D1", "D2", "D3", "D4")) +EXPECTED_OUTCOME = { + "LEASE_ISSUED": "ADMITTED", + "LEASE_ISSUANCE_DENIED": "DENIED", + "LEASE_RENEWED": "ADMITTED", + "LEASE_RENEWAL_DENIED": "DENIED", + "LEASE_EXPIRED": "EXPIRED", + "LEASE_REVOKED": "REVOKED", + "MUTATION_ADMITTED": "ADMITTED", + "MUTATION_DENIED": "DENIED", + "MUTATION_COMPLETED": "COMPLETED", + "MUTATION_CANCELLED": "CANCELLED", + "MUTATION_FAILED": "FAILED", +} +DENIAL_KINDS = frozenset(( + "LEASE_ISSUANCE_DENIED", "LEASE_RENEWAL_DENIED", "LEASE_EXPIRED", "LEASE_REVOKED", + "MUTATION_DENIED", "MUTATION_CANCELLED", "MUTATION_FAILED", +)) + +HASH_RE = re.compile(r"^[0-9a-f]{64}$") +SIGNATURE_RE = re.compile(r"^[0-9a-f]{128}$") +DECIMAL_RE = re.compile(r"^(0|[1-9][0-9]*)$") +SAFE_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$") +NONCE_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{15,127}$") + +ENVELOPE_KEYS = frozenset(("schema_version", "receipt_kind", "receipt_body", "proof", "receipt_id")) +RECEIPT_BODY_KEYS = frozenset(( + "receipt_sequence", + "actor_identity_root", + "session_identity_root", + "workspace_identity_root", + "holon_identity_root", + "authority_domain", + "authority_level", + "authority_receipt_hash", + "lease_id", + "lease_generation", + "fencing_token", + "lease_authorization_receipt_hash", + "parent_receipt_hash", + "observed_state_root", + "expected_state_root", + "action_digest", + "before_state_root", + "after_state_root", + "result_digest", + "timestamp_ms", + "expires_at_ms", + "nonce", + "outcome", + "denial_codes", +)) +RECEIPT_PROOF_KEYS = frozenset(( + "algorithm", "signer_key_id", "verifier_identity_root", + "trust_registry_version", "trust_registry_root", "signature", +)) +REGISTRY_KEYS = frozenset(("schema_version", "registry_body", "proof", "registry_root")) +REGISTRY_BODY_KEYS = frozenset(( + "registry_version", "previous_registry_root", "issued_at_ms", "valid_from_ms", + "expires_at_ms", "operator_key_id", "keys", +)) +REGISTRY_PROOF_KEYS = frozenset(("algorithm", "signature")) +REGISTRY_ENTRY_KEYS = frozenset(( + "key_id", "public_key", "verifier_identity_root", "valid_from_ms", + "expires_at_ms", "status", "authority_domains", "receipt_kinds", +)) + + +class AuthoritativeReceiptError(ValueError): + """Fail-closed validation or state-transition error with a stable code.""" + + def __init__(self, code: str): + super().__init__(code) + self.code = code + + +class ReceiptStoreConflict(AuthoritativeReceiptError): + """A durable compare-and-append precondition was no longer current.""" + + +def _fail(code: str) -> None: + raise AuthoritativeReceiptError(code) + + +def assert_i_json(value: Any, label: str = "value") -> None: + """Reject values outside the closed, cross-runtime canonical JSON set.""" + + def visit(item: Any, path: str, ancestors: set[int]) -> None: + if item is None or isinstance(item, bool): + return + if isinstance(item, str): + for char in item: + if 0xD800 <= ord(char) <= 0xDFFF: + _fail(f"I_JSON_UNPAIRED_SURROGATE:{path}") + return + if isinstance(item, int): + if abs(item) > 9_007_199_254_740_991: + _fail(f"I_JSON_INTEGER_OUT_OF_RANGE:{path}") + return + if isinstance(item, float): + if not math.isfinite(item): + _fail(f"I_JSON_NONFINITE_NUMBER:{path}") + _fail(f"I_JSON_FLOAT_FORBIDDEN:{path}") + if type(item) is list: + identity = id(item) + if identity in ancestors: + _fail(f"I_JSON_CYCLE:{path}") + ancestors.add(identity) + try: + for index, child in enumerate(item): + visit(child, f"{path}[{index}]", ancestors) + finally: + ancestors.remove(identity) + return + if type(item) is dict: + identity = id(item) + if identity in ancestors: + _fail(f"I_JSON_CYCLE:{path}") + ancestors.add(identity) + try: + for key, child in item.items(): + if not isinstance(key, str): + _fail(f"I_JSON_NON_STRING_KEY:{path}") + visit(key, f"{path}.", ancestors) + visit(child, f"{path}.{key}", ancestors) + finally: + ancestors.remove(identity) + return + _fail(f"I_JSON_TYPE_FORBIDDEN:{path}:{type(item).__name__}") + + visit(value, label, set()) + # Canonicalization is deliberately delegated to the existing implementation. + canon(value) + + +def _object_pairs_no_duplicates(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + result: dict[str, Any] = {} + for key, value in pairs: + if key in result: + _fail(f"JSON_DUPLICATE_KEY:{key}") + result[key] = value + return result + + +def load_json_strict(data: bytes | str) -> dict[str, Any]: + try: + text = data.decode("utf-8", errors="strict") if isinstance(data, bytes) else data + value = json.loads( + text, + object_pairs_hook=_object_pairs_no_duplicates, + parse_float=lambda _value: _fail("I_JSON_FLOAT_FORBIDDEN:$"), + parse_constant=lambda _value: _fail("I_JSON_NONFINITE_NUMBER:$"), + ) + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise AuthoritativeReceiptError("JSON_MALFORMED") from exc + assert_i_json(value) + if type(value) is not dict: + _fail("JSON_ROOT_NOT_OBJECT") + return value + + +def _exact_keys(value: Any, expected: frozenset[str], code: str) -> dict[str, Any]: + if type(value) is not dict or frozenset(value) != expected: + _fail(code) + return value + + +def _hash(field: str, value: Any, *, nonzero: bool = False) -> str: + if not isinstance(value, str) or not HASH_RE.fullmatch(value): + _fail(f"{field}:INVALID_SHA256") + if nonzero and value == ZERO_HASH: + _fail(f"{field}:UNRESOLVED") + return value + + +def _signature(field: str, value: Any) -> str: + if not isinstance(value, str) or not SIGNATURE_RE.fullmatch(value): + _fail(f"{field}:INVALID_ED25519_SIGNATURE") + return value + + +def _decimal(field: str, value: Any) -> str: + if not isinstance(value, str) or len(value) > 20 or not DECIMAL_RE.fullmatch(value): + _fail(f"{field}:INVALID_DECIMAL") + return value + + +def _decimal_int(field: str, value: Any) -> int: + return int(_decimal(field, value)) + + +def _safe_id(field: str, value: Any) -> str: + if not isinstance(value, str) or not SAFE_ID_RE.fullmatch(value): + _fail(f"{field}:INVALID_ID") + return value + + +def _nonce(value: Any) -> str: + if not isinstance(value, str) or not NONCE_RE.fullmatch(value): + _fail("nonce:INVALID") + return value + + +def _canonical_codes(value: Any, *, required: bool) -> tuple[str, ...]: + if type(value) is not list or len(value) > 32: + _fail("denial_codes:INVALID") + codes = tuple(_safe_id("denial_code", item) for item in value) + if tuple(sorted(set(codes), key=lambda item: item.encode("utf-8"))) != codes: + _fail("denial_codes:NONCANONICAL") + if required and not codes: + _fail("denial_codes:REQUIRED") + if not required and codes: + _fail("denial_codes:FORBIDDEN") + return codes + + +def _domain_hash(domain: str, value: Any) -> str: + assert_i_json(value) + return sha256_hex(canon({"domain": domain, "value": value})) + + +def _ed25519_private_key(private_key_hex: str): + if not isinstance(private_key_hex, str) or not HASH_RE.fullmatch(private_key_hex): + _fail("SIGNING_PRIVATE_KEY_INVALID") + try: + from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + return Ed25519PrivateKey.from_private_bytes(bytes.fromhex(private_key_hex)) + except ImportError as exc: # pragma: no cover - dependency is pinned in CI + raise AuthoritativeReceiptError("SIGNATURE_PROVIDER_UNAVAILABLE") from exc + except ValueError as exc: + raise AuthoritativeReceiptError("SIGNING_PRIVATE_KEY_INVALID") from exc + + +def public_key_hex_from_private(private_key_hex: str) -> str: + try: + from cryptography.hazmat.primitives import serialization + return _ed25519_private_key(private_key_hex).public_key().public_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PublicFormat.Raw, + ).hex() + except ImportError as exc: # pragma: no cover + raise AuthoritativeReceiptError("SIGNATURE_PROVIDER_UNAVAILABLE") from exc + + +def _sign(private_key_hex: str, message: bytes) -> str: + return _ed25519_private_key(private_key_hex).sign(message).hex() + + +def _verify(public_key_hex: str, signature_hex: str, message: bytes, code: str) -> None: + _hash("public_key", public_key_hex) + _signature("signature", signature_hex) + try: + from cryptography.exceptions import InvalidSignature + from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey + Ed25519PublicKey.from_public_bytes(bytes.fromhex(public_key_hex)).verify( + bytes.fromhex(signature_hex), message, + ) + except ImportError as exc: # pragma: no cover + raise AuthoritativeReceiptError("SIGNATURE_PROVIDER_UNAVAILABLE") from exc + except InvalidSignature as exc: + raise AuthoritativeReceiptError(code) from exc + except ValueError as exc: + raise AuthoritativeReceiptError("SIGNING_PUBLIC_KEY_INVALID") from exc + + +def canonical_registry_signature_message(registry: Mapping[str, Any]) -> bytes: + body = registry["registry_body"] + proof = registry["proof"] + return canon({ + "domain": REGISTRY_SIGNATURE_DOMAIN, + "schema_version": registry["schema_version"], + "registry_body": body, + "proof": {"algorithm": proof["algorithm"]}, + }) + + +def compute_registry_root(registry: Mapping[str, Any]) -> str: + return sha256_hex(canon({ + "domain": REGISTRY_ROOT_DOMAIN, + "registry": { + "schema_version": registry["schema_version"], + "registry_body": registry["registry_body"], + "proof": { + "algorithm": registry["proof"]["algorithm"], + "signature": registry["proof"]["signature"], + }, + }, + })) + + +def canonical_receipt_signature_message(envelope: Mapping[str, Any]) -> bytes: + proof = envelope["proof"] + return canon({ + "domain": RECEIPT_SIGNATURE_DOMAIN, + "schema_version": envelope["schema_version"], + "receipt_kind": envelope["receipt_kind"], + "receipt_body": envelope["receipt_body"], + "proof": { + "algorithm": proof["algorithm"], + "signer_key_id": proof["signer_key_id"], + "verifier_identity_root": proof["verifier_identity_root"], + "trust_registry_version": proof["trust_registry_version"], + "trust_registry_root": proof["trust_registry_root"], + }, + }) + + +def compute_receipt_id(envelope: Mapping[str, Any]) -> str: + proof = envelope["proof"] + return sha256_hex(canon({ + "domain": RECEIPT_ID_DOMAIN, + "envelope": { + "schema_version": envelope["schema_version"], + "receipt_kind": envelope["receipt_kind"], + "receipt_body": envelope["receipt_body"], + "proof": { + "algorithm": proof["algorithm"], + "signer_key_id": proof["signer_key_id"], + "verifier_identity_root": proof["verifier_identity_root"], + "trust_registry_version": proof["trust_registry_version"], + "trust_registry_root": proof["trust_registry_root"], + "signature": proof["signature"], + }, + }, + })) + + +def _validate_registry_shape(registry: Any) -> None: + assert_i_json(registry, "registry") + registry = _exact_keys(registry, REGISTRY_KEYS, "TRUST_REGISTRY_SCHEMA_DRIFT") + if registry["schema_version"] != SCHEMA_VERSION: + _fail("TRUST_REGISTRY_SCHEMA_UNSUPPORTED") + body = _exact_keys(registry["registry_body"], REGISTRY_BODY_KEYS, "TRUST_REGISTRY_BODY_SCHEMA_DRIFT") + proof = _exact_keys(registry["proof"], REGISTRY_PROOF_KEYS, "TRUST_REGISTRY_PROOF_SCHEMA_DRIFT") + if proof["algorithm"] != ED25519: + _fail("TRUST_REGISTRY_ALGORITHM_UNSUPPORTED") + _signature("registry.signature", proof["signature"]) + _hash("registry.registry_root", registry["registry_root"], nonzero=True) + version = _decimal_int("registry.registry_version", body["registry_version"]) + _hash("registry.previous_registry_root", body["previous_registry_root"]) + issued = _decimal_int("registry.issued_at_ms", body["issued_at_ms"]) + valid_from = _decimal_int("registry.valid_from_ms", body["valid_from_ms"]) + expires = _decimal_int("registry.expires_at_ms", body["expires_at_ms"]) + _safe_id("registry.operator_key_id", body["operator_key_id"]) + if version < 1: + _fail("TRUST_REGISTRY_VERSION_INVALID") + if (version == 1) != (body["previous_registry_root"] == ZERO_HASH): + _fail("TRUST_REGISTRY_GENESIS_LINK_INVALID") + if issued > valid_from or valid_from >= expires: + _fail("TRUST_REGISTRY_TIME_WINDOW_INVALID") + keys = body["keys"] + if type(keys) is not list or not (1 <= len(keys) <= 128): + _fail("TRUST_REGISTRY_KEYS_INVALID") + key_ids: list[str] = [] + public_keys: list[str] = [] + for index, entry_value in enumerate(keys): + entry = _exact_keys(entry_value, REGISTRY_ENTRY_KEYS, f"TRUST_REGISTRY_KEY_SCHEMA_DRIFT:{index}") + key_id = _safe_id("registry.key_id", entry["key_id"]) + key_ids.append(key_id) + public_keys.append(_hash("registry.public_key", entry["public_key"])) + _hash("registry.verifier_identity_root", entry["verifier_identity_root"], nonzero=True) + key_from = _decimal_int("registry.key.valid_from_ms", entry["valid_from_ms"]) + key_expires = _decimal_int("registry.key.expires_at_ms", entry["expires_at_ms"]) + if key_from >= key_expires or key_from < valid_from or key_expires > expires: + _fail("TRUST_REGISTRY_KEY_TIME_WINDOW_INVALID") + if entry["status"] not in ("ACTIVE", "REVOKED"): + _fail("TRUST_REGISTRY_KEY_STATUS_INVALID") + domains = entry["authority_domains"] + kinds = entry["receipt_kinds"] + if type(domains) is not list or not domains: + _fail("TRUST_REGISTRY_KEY_DOMAINS_INVALID") + if type(kinds) is not list or not kinds: + _fail("TRUST_REGISTRY_KEY_KINDS_INVALID") + normalized_domains = tuple(_safe_id("registry.authority_domain", item) for item in domains) + if tuple(sorted(set(normalized_domains), key=lambda item: item.encode("utf-8"))) != normalized_domains: + _fail("TRUST_REGISTRY_KEY_DOMAINS_NONCANONICAL") + normalized_kinds = tuple(kinds) + if any(kind not in RECEIPT_KINDS for kind in normalized_kinds): + _fail("TRUST_REGISTRY_KEY_KIND_INVALID") + if tuple(sorted(set(normalized_kinds), key=lambda item: item.encode("utf-8"))) != normalized_kinds: + _fail("TRUST_REGISTRY_KEY_KINDS_NONCANONICAL") + if tuple(sorted(set(key_ids), key=lambda item: item.encode("utf-8"))) != tuple(key_ids): + _fail("TRUST_REGISTRY_KEYS_NONCANONICAL") + if len(set(public_keys)) != len(public_keys): + _fail("TRUST_REGISTRY_PUBLIC_KEYS_DUPLICATE") + + +_VERIFIED_REGISTRY_CAPABILITY = object() + + +@dataclass(frozen=True) +class VerifiedTrustRegistry: + """Tamper-evident verified registry backed only by immutable canonical bytes.""" + + canonical_document: bytes + registry_root: str + registry_version: str + previous_registry_root: str + _capability: object + + @property + def document(self) -> dict[str, Any]: + return load_json_strict(self.canonical_document) + + @property + def entries(self) -> Mapping[str, Mapping[str, Any]]: + return { + entry["key_id"]: entry + for entry in self.document["registry_body"]["keys"] + } + + +def _require_verified_registry(registry: VerifiedTrustRegistry) -> None: + if not isinstance(registry, VerifiedTrustRegistry) or registry._capability is not _VERIFIED_REGISTRY_CAPABILITY: + _fail("TRUST_REGISTRY_NOT_VERIFIED") + document = registry.document + _validate_registry_shape(document) + if compute_registry_root(document) != registry.registry_root: + _fail("TRUST_REGISTRY_CAPABILITY_TAMPERED") + + +def create_trust_registry(registry_body: Mapping[str, Any], operator_private_key_hex: str) -> dict[str, Any]: + draft: dict[str, Any] = { + "schema_version": SCHEMA_VERSION, + "registry_body": copy.deepcopy(dict(registry_body)), + "proof": {"algorithm": ED25519, "signature": "0" * 128}, + "registry_root": "1" * 64, + } + # Validate the body and all exact-key constraints before signing it. + _validate_registry_shape(draft) + draft["proof"]["signature"] = _sign(operator_private_key_hex, canonical_registry_signature_message(draft)) + draft["registry_root"] = compute_registry_root(draft) + _validate_registry_shape(draft) + return draft + + +def verify_trust_registry( + registry: Mapping[str, Any], + *, + pinned_operator_public_key_hex: str, + expected_operator_key_id: str, + expected_registry_root: str | None = None, + expected_registry_version: str | None = None, + verification_time_ms: str | None = None, + max_clock_skew_ms: str = "0", +) -> VerifiedTrustRegistry: + document = copy.deepcopy(dict(registry)) + _validate_registry_shape(document) + if compute_registry_root(document) != document["registry_root"]: + _fail("TRUST_REGISTRY_ROOT_MISMATCH") + if expected_registry_root is not None and document["registry_root"] != _hash( + "expected_registry_root", expected_registry_root, nonzero=True, + ): + _fail("TRUST_REGISTRY_NOT_EXPECTED") + body = document["registry_body"] + if body["operator_key_id"] != _safe_id("expected_operator_key_id", expected_operator_key_id): + _fail("TRUST_REGISTRY_OPERATOR_KEY_ID_MISMATCH") + if expected_registry_version is not None and body["registry_version"] != _decimal( + "expected_registry_version", expected_registry_version, + ): + _fail("TRUST_REGISTRY_VERSION_NOT_EXPECTED") + _verify( + pinned_operator_public_key_hex, + document["proof"]["signature"], + canonical_registry_signature_message(document), + "TRUST_REGISTRY_SIGNATURE_INVALID", + ) + if verification_time_ms is not None: + now = _decimal_int("verification_time_ms", verification_time_ms) + skew = _decimal_int("max_clock_skew_ms", max_clock_skew_ms) + valid_from = int(body["valid_from_ms"]) + expires = int(body["expires_at_ms"]) + if now + skew < valid_from: + _fail("TRUST_REGISTRY_NOT_YET_VALID") + if now - skew >= expires: + _fail("TRUST_REGISTRY_EXPIRED") + return VerifiedTrustRegistry( + canonical_document=canon(document), + registry_root=document["registry_root"], + registry_version=body["registry_version"], + previous_registry_root=body["previous_registry_root"], + _capability=_VERIFIED_REGISTRY_CAPABILITY, + ) + + +def verify_registry_rotation(previous: VerifiedTrustRegistry, current: VerifiedTrustRegistry) -> None: + _require_verified_registry(previous) + _require_verified_registry(current) + if current.previous_registry_root != previous.registry_root: + _fail("TRUST_REGISTRY_ROTATION_PARENT_MISMATCH") + if int(current.registry_version) != int(previous.registry_version) + 1: + _fail("TRUST_REGISTRY_ROTATION_VERSION_INVALID") + if int(current.document["registry_body"]["issued_at_ms"]) < int(previous.document["registry_body"]["issued_at_ms"]): + _fail("TRUST_REGISTRY_ROTATION_TIME_REGRESSION") + + +def _validate_receipt_shape(envelope: Any) -> None: + assert_i_json(envelope, "receipt") + envelope = _exact_keys(envelope, ENVELOPE_KEYS, "RECEIPT_SCHEMA_DRIFT") + if envelope["schema_version"] != SCHEMA_VERSION: + _fail("RECEIPT_SCHEMA_UNSUPPORTED") + kind = envelope["receipt_kind"] + if kind not in RECEIPT_KINDS: + _fail("RECEIPT_KIND_INVALID") + body = _exact_keys(envelope["receipt_body"], RECEIPT_BODY_KEYS, "RECEIPT_BODY_SCHEMA_DRIFT") + proof = _exact_keys(envelope["proof"], RECEIPT_PROOF_KEYS, "RECEIPT_PROOF_SCHEMA_DRIFT") + _hash("receipt_id", envelope["receipt_id"]) + _decimal("receipt_sequence", body["receipt_sequence"]) + for field in ( + "actor_identity_root", "session_identity_root", "workspace_identity_root", "holon_identity_root", "lease_id", + ): + _hash(field, body[field], nonzero=True) + for field in ( + "authority_receipt_hash", "fencing_token", "lease_authorization_receipt_hash", + "parent_receipt_hash", "action_digest", "result_digest", + ): + _hash(field, body[field]) + for field in ( + "observed_state_root", "expected_state_root", "before_state_root", "after_state_root", + ): + _hash(field, body[field], nonzero=True) + _hash("action_digest", body["action_digest"], nonzero=True) + _safe_id("authority_domain", body["authority_domain"]) + if body["authority_level"] not in AUTHORITY_LEVELS: + _fail("authority_level:INVALID") + _decimal("lease_generation", body["lease_generation"]) + _decimal("timestamp_ms", body["timestamp_ms"]) + _decimal("expires_at_ms", body["expires_at_ms"]) + _nonce(body["nonce"]) + if body["outcome"] != EXPECTED_OUTCOME[kind]: + _fail("RECEIPT_KIND_OUTCOME_MISMATCH") + _canonical_codes(body["denial_codes"], required=kind in DENIAL_KINDS) + if kind != "MUTATION_COMPLETED" and body["after_state_root"] != body["before_state_root"]: + _fail("NON_COMPLETION_STATE_CHANGED") + if body["before_state_root"] != body["observed_state_root"]: + _fail("BEFORE_STATE_NOT_OBSERVED_STATE") + if body["result_digest"] == ZERO_HASH: + _fail("result_digest:UNRESOLVED") + if kind in LEASE_KINDS: + if body["authority_receipt_hash"] != ZERO_HASH or body["lease_authorization_receipt_hash"] != ZERO_HASH: + _fail("LEASE_RECEIPT_HAS_MUTATION_AUTHORITY_HASH") + else: + _hash("authority_receipt_hash", body["authority_receipt_hash"], nonzero=True) + _hash("lease_authorization_receipt_hash", body["lease_authorization_receipt_hash"], nonzero=True) + if kind == "LEASE_ISSUANCE_DENIED": + if body["fencing_token"] != ZERO_HASH: + _fail("LEASE_ISSUANCE_DENIAL_HAS_FENCE") + else: + _hash("fencing_token", body["fencing_token"], nonzero=True) + if proof["algorithm"] != ED25519: + _fail("RECEIPT_ALGORITHM_UNSUPPORTED") + _safe_id("signer_key_id", proof["signer_key_id"]) + _hash("verifier_identity_root", proof["verifier_identity_root"], nonzero=True) + _decimal("trust_registry_version", proof["trust_registry_version"]) + _hash("trust_registry_root", proof["trust_registry_root"], nonzero=True) + _signature("receipt.signature", proof["signature"]) + + +def sign_receipt( + *, + receipt_kind: str, + receipt_body: Mapping[str, Any], + registry: VerifiedTrustRegistry, + signer_key_id: str, + signer_private_key_hex: str, +) -> dict[str, Any]: + _require_verified_registry(registry) + entry = registry.entries.get(signer_key_id) + if entry is None: + _fail("RECEIPT_SIGNER_UNKNOWN") + envelope: dict[str, Any] = { + "schema_version": SCHEMA_VERSION, + "receipt_kind": receipt_kind, + "receipt_body": copy.deepcopy(dict(receipt_body)), + "proof": { + "algorithm": ED25519, + "signer_key_id": signer_key_id, + "verifier_identity_root": entry["verifier_identity_root"], + "trust_registry_version": registry.registry_version, + "trust_registry_root": registry.registry_root, + "signature": "0" * 128, + }, + "receipt_id": "1" * 64, + } + _validate_receipt_shape(envelope) + if public_key_hex_from_private(signer_private_key_hex) != entry["public_key"]: + _fail("RECEIPT_SIGNING_KEY_MISMATCH") + envelope["proof"]["signature"] = _sign(signer_private_key_hex, canonical_receipt_signature_message(envelope)) + envelope["receipt_id"] = compute_receipt_id(envelope) + _validate_receipt_shape(envelope) + return envelope + + +def verify_receipt( + envelope: Mapping[str, Any], + *, + registry: VerifiedTrustRegistry, + verification_time_ms: str | None = None, + max_clock_skew_ms: str = "0", +) -> dict[str, Any]: + _require_verified_registry(registry) + document = copy.deepcopy(dict(envelope)) + _validate_receipt_shape(document) + if compute_receipt_id(document) != document["receipt_id"]: + _fail("RECEIPT_ID_MISMATCH") + proof = document["proof"] + body = document["receipt_body"] + if proof["trust_registry_root"] != registry.registry_root or proof["trust_registry_version"] != registry.registry_version: + _fail("RECEIPT_TRUST_REGISTRY_MISMATCH") + entry = registry.entries.get(proof["signer_key_id"]) + if entry is None: + _fail("RECEIPT_SIGNER_UNKNOWN") + if entry["status"] != "ACTIVE": + _fail("RECEIPT_SIGNER_REVOKED") + if proof["verifier_identity_root"] != entry["verifier_identity_root"]: + _fail("RECEIPT_VERIFIER_IDENTITY_MISMATCH") + if body["authority_domain"] not in entry["authority_domains"]: + _fail("RECEIPT_SIGNER_DOMAIN_UNTRUSTED") + if document["receipt_kind"] not in entry["receipt_kinds"]: + _fail("RECEIPT_SIGNER_KIND_UNTRUSTED") + timestamp = int(body["timestamp_ms"]) + registry_body = registry.document["registry_body"] + if timestamp < int(registry_body["valid_from_ms"]) or timestamp >= int(registry_body["expires_at_ms"]): + _fail("RECEIPT_OUTSIDE_REGISTRY_WINDOW") + if timestamp < int(entry["valid_from_ms"]) or timestamp >= int(entry["expires_at_ms"]): + _fail("RECEIPT_OUTSIDE_SIGNER_WINDOW") + if verification_time_ms is not None: + now = _decimal_int("verification_time_ms", verification_time_ms) + skew = _decimal_int("max_clock_skew_ms", max_clock_skew_ms) + if timestamp > now + skew: + _fail("RECEIPT_TIMESTAMP_IN_FUTURE") + _verify( + entry["public_key"], proof["signature"], canonical_receipt_signature_message(document), + "RECEIPT_SIGNATURE_INVALID", + ) + return document + + +class SQLiteReceiptStore: + """Add-only content-addressed registry and receipt store with chain-head CAS.""" + + def __init__(self, path: str | Path): + self.path = Path(path) + self.path.parent.mkdir(parents=True, exist_ok=True) + self._connection = sqlite3.connect(str(self.path), check_same_thread=False, isolation_level=None) + self._connection.execute("PRAGMA busy_timeout = 5000") + self._connection.execute("PRAGMA journal_mode = WAL") + self._connection.execute("PRAGMA synchronous = FULL") + self._connection.execute( + "CREATE TABLE IF NOT EXISTS trust_registries (registry_root TEXT PRIMARY KEY, canonical BLOB NOT NULL)" + ) + self._connection.execute( + "CREATE TABLE IF NOT EXISTS receipts (receipt_id TEXT PRIMARY KEY, receipt_sequence TEXT NOT NULL UNIQUE, canonical BLOB NOT NULL)" + ) + self._lock = threading.RLock() + self._closed = False + + def _require_open(self) -> None: + if self._closed: + _fail("RECEIPT_STORE_CLOSED") + + def close(self) -> None: + with self._lock: + if not self._closed: + self._connection.close() + self._closed = True + + def _read_pending_registry_bytes(self, registry_root: str) -> bytes | None: + """Read a registry append inside the current transaction for fault injection.""" + + row = self._connection.execute( + "SELECT canonical FROM trust_registries WHERE registry_root = ?", (registry_root,), + ).fetchone() + return None if row is None else bytes(row[0]) + + def persist_registry(self, registry: Mapping[str, Any]) -> str: + document = copy.deepcopy(dict(registry)) + _validate_registry_shape(document) + if compute_registry_root(document) != document["registry_root"]: + _fail("TRUST_REGISTRY_ROOT_MISMATCH") + encoded = canon(document) + root = document["registry_root"] + with self._lock: + self._require_open() + self._connection.execute("BEGIN IMMEDIATE") + try: + row = self._connection.execute( + "SELECT canonical FROM trust_registries WHERE registry_root = ?", (root,), + ).fetchone() + if row is not None: + if bytes(row[0]) != encoded: + _fail("TRUST_REGISTRY_CONTENT_CONFLICT") + else: + self._connection.execute( + "INSERT INTO trust_registries(registry_root, canonical) VALUES (?, ?)", + (root, sqlite3.Binary(encoded)), + ) + pending = self._read_pending_registry_bytes(root) + if pending is None: + _fail("TRUST_REGISTRY_READBACK_MISMATCH") + read_back = load_json_strict(pending) + _validate_registry_shape(read_back) + if pending != encoded or canon(read_back) != encoded or compute_registry_root(read_back) != root: + _fail("TRUST_REGISTRY_READBACK_MISMATCH") + self._connection.execute("COMMIT") + except BaseException: + self._connection.execute("ROLLBACK") + raise + return root + + def read_registry(self, registry_root: str) -> dict[str, Any] | None: + root = _hash("registry_root", registry_root, nonzero=True) + with self._lock: + self._require_open() + row = self._connection.execute( + "SELECT canonical FROM trust_registries WHERE registry_root = ?", (root,), + ).fetchone() + if row is None: + return None + encoded = bytes(row[0]) + document = load_json_strict(encoded) + _validate_registry_shape(document) + if canon(document) != encoded or compute_registry_root(document) != root: + _fail("TRUST_REGISTRY_STORED_BYTES_INVALID") + return document + + def _read_pending_receipt_bytes(self, receipt_id: str) -> bytes | None: + """Read an append inside the current transaction for fault injection.""" + + row = self._connection.execute( + "SELECT canonical FROM receipts WHERE receipt_id = ?", (receipt_id,), + ).fetchone() + return None if row is None else bytes(row[0]) + + def persist_receipt( + self, + envelope: Mapping[str, Any], + *, + registry: VerifiedTrustRegistry, + verification_time_ms: str | None = None, + max_clock_skew_ms: str = "0", + ) -> str: + # Persistence requires an authenticated registry capability. Shape-valid + # unsigned bytes cannot enter through the public store API. + document = verify_receipt( + envelope, + registry=registry, + verification_time_ms=verification_time_ms, + max_clock_skew_ms=max_clock_skew_ms, + ) + receipt_id = document["receipt_id"] + body = document["receipt_body"] + sequence = body["receipt_sequence"] + parent = body["parent_receipt_hash"] + encoded = canon(document) + with self._lock: + self._require_open() + self._connection.execute("BEGIN IMMEDIATE") + try: + duplicate = self._connection.execute( + "SELECT canonical FROM receipts WHERE receipt_id = ?", (receipt_id,), + ).fetchone() + if duplicate is not None: + if bytes(duplicate[0]) != encoded: + _fail("RECEIPT_CONTENT_CONFLICT") + self._connection.execute("COMMIT") + return receipt_id + head = self._connection.execute( + "SELECT receipt_id, receipt_sequence FROM receipts " + "ORDER BY length(receipt_sequence) DESC, receipt_sequence DESC LIMIT 1" + ).fetchone() + expected_sequence = "0" if head is None else str(int(head[1]) + 1) + expected_parent = ZERO_HASH if head is None else str(head[0]) + if sequence != expected_sequence: + raise ReceiptStoreConflict("RECEIPT_STORE_SEQUENCE_STALE") + if parent != expected_parent: + raise ReceiptStoreConflict("RECEIPT_STORE_PARENT_STALE") + self._connection.execute( + "INSERT INTO receipts(receipt_id, receipt_sequence, canonical) VALUES (?, ?, ?)", + (receipt_id, sequence, sqlite3.Binary(encoded)), + ) + pending = self._read_pending_receipt_bytes(receipt_id) + if pending is None: + _fail("RECEIPT_READBACK_MISMATCH") + read_back = load_json_strict(pending) + _validate_receipt_shape(read_back) + if pending != encoded or canon(read_back) != encoded or compute_receipt_id(read_back) != receipt_id: + _fail("RECEIPT_READBACK_MISMATCH") + self._connection.execute("COMMIT") + except BaseException: + if self._connection.in_transaction: + self._connection.execute("ROLLBACK") + raise + return receipt_id + + def read_receipt(self, receipt_id: str) -> dict[str, Any] | None: + root = _hash("receipt_id", receipt_id, nonzero=True) + with self._lock: + self._require_open() + row = self._connection.execute( + "SELECT canonical FROM receipts WHERE receipt_id = ?", (root,), + ).fetchone() + if row is None: + return None + encoded = bytes(row[0]) + document = load_json_strict(encoded) + _validate_receipt_shape(document) + if canon(document) != encoded or compute_receipt_id(document) != root: + _fail("RECEIPT_STORED_BYTES_INVALID") + return document + + def read_all_receipts(self) -> tuple[dict[str, Any], ...]: + with self._lock: + self._require_open() + rows = self._connection.execute( + "SELECT receipt_id, canonical FROM receipts " + "ORDER BY length(receipt_sequence), receipt_sequence" + ).fetchall() + result: list[dict[str, Any]] = [] + for receipt_id, encoded_value in rows: + encoded = bytes(encoded_value) + document = load_json_strict(encoded) + _validate_receipt_shape(document) + if canon(document) != encoded or compute_receipt_id(document) != receipt_id: + _fail("RECEIPT_STORED_BYTES_INVALID") + result.append(document) + return tuple(result) + + +@dataclass(frozen=True) +class ReceiptBindings: + actor_identity_root: str + session_identity_root: str + workspace_identity_root: str + holon_identity_root: str + authority_domain: str + authority_level: str + + def validate(self) -> None: + for field in ( + "actor_identity_root", "session_identity_root", "workspace_identity_root", "holon_identity_root", + ): + _hash(field, getattr(self, field), nonzero=True) + _safe_id("authority_domain", self.authority_domain) + if self.authority_level not in AUTHORITY_LEVELS: + _fail("authority_level:INVALID") + + +@dataclass +class _LeaseState: + bindings: ReceiptBindings + lease_id: str + generation: int + fencing_token: str + expected_state_root: str + expires_at_ms: int + authorization_receipt_hash: str + active: bool + + +@dataclass +class _MutationState: + lease_id: str + generation: int + fencing_token: str + action_digest: str + authority_receipt_hash: str + lease_authorization_receipt_hash: str + before_state_root: str + status: str + + +class AuthoritativeReceiptAuthority: + """Locked state machine whose only durable transitions are signed receipts.""" + + def __init__( + self, + *, + store: SQLiteReceiptStore, + current_registry: Mapping[str, Any], + pinned_operator_public_key_hex: str, + expected_operator_key_id: str, + expected_registry_root: str, + signer_key_id: str, + signer_private_key_hex: str, + verification_time_ms: str, + max_clock_skew_ms: str = "0", + ): + self._store = store + self._pinned_operator_public_key_hex = pinned_operator_public_key_hex + self._expected_operator_key_id = _safe_id("expected_operator_key_id", expected_operator_key_id) + self._verification_time_ms = _decimal("verification_time_ms", verification_time_ms) + self._max_clock_skew_ms = _decimal("max_clock_skew_ms", max_clock_skew_ms) + self._signer_key_id = _safe_id("signer_key_id", signer_key_id) + self._signer_private_key_hex = signer_private_key_hex + self._lock = threading.RLock() + verified = verify_trust_registry( + current_registry, + pinned_operator_public_key_hex=pinned_operator_public_key_hex, + expected_operator_key_id=self._expected_operator_key_id, + expected_registry_root=expected_registry_root, + verification_time_ms=verification_time_ms, + max_clock_skew_ms=max_clock_skew_ms, + ) + self._store.persist_registry(verified.document) + self._current_registry = verified + self._registries = self._load_registry_chain(verified) + self._assert_signer(verified, verification_time_ms) + self._reset_state() + self.recover() + + def _load_registry_chain(self, current: VerifiedTrustRegistry) -> dict[str, VerifiedTrustRegistry]: + chain = {current.registry_root: current} + cursor = current + while cursor.previous_registry_root != ZERO_HASH: + document = self._store.read_registry(cursor.previous_registry_root) + if document is None: + _fail("TRUST_REGISTRY_ANCESTOR_MISSING") + previous = verify_trust_registry( + document, + pinned_operator_public_key_hex=self._pinned_operator_public_key_hex, + expected_operator_key_id=self._expected_operator_key_id, + expected_registry_root=cursor.previous_registry_root, + ) + verify_registry_rotation(previous, cursor) + if previous.registry_root in chain: + _fail("TRUST_REGISTRY_CHAIN_CYCLE") + chain[previous.registry_root] = previous + cursor = previous + return chain + + def _assert_signer(self, registry: VerifiedTrustRegistry, at_ms: str) -> None: + entry = registry.entries.get(self._signer_key_id) + if entry is None or entry["status"] != "ACTIVE": + _fail("RECEIPT_SIGNER_NOT_ACTIVE") + if public_key_hex_from_private(self._signer_private_key_hex) != entry["public_key"]: + _fail("RECEIPT_SIGNING_KEY_MISMATCH") + moment = int(_decimal("signer_time_ms", at_ms)) + if moment < int(entry["valid_from_ms"]) or moment >= int(entry["expires_at_ms"]): + _fail("RECEIPT_SIGNER_OUTSIDE_VALIDITY") + + def rotate_registry( + self, + registry: Mapping[str, Any], + *, + expected_registry_root: str, + verification_time_ms: str, + signer_key_id: str | None = None, + signer_private_key_hex: str | None = None, + ) -> None: + with self._lock: + if int(_decimal("verification_time_ms", verification_time_ms)) < int(self._verification_time_ms): + _fail("OBSERVED_TIME_REGRESSION") + verified = verify_trust_registry( + registry, + pinned_operator_public_key_hex=self._pinned_operator_public_key_hex, + expected_operator_key_id=self._expected_operator_key_id, + expected_registry_root=expected_registry_root, + verification_time_ms=verification_time_ms, + max_clock_skew_ms=self._max_clock_skew_ms, + ) + verify_registry_rotation(self._current_registry, verified) + next_key_id = self._signer_key_id if signer_key_id is None else _safe_id("signer_key_id", signer_key_id) + next_private = self._signer_private_key_hex if signer_private_key_hex is None else signer_private_key_hex + old_key, old_private = self._signer_key_id, self._signer_private_key_hex + self._signer_key_id, self._signer_private_key_hex = next_key_id, next_private + try: + self._assert_signer(verified, verification_time_ms) + self._store.persist_registry(verified.document) + except BaseException: + self._signer_key_id, self._signer_private_key_hex = old_key, old_private + raise + self._current_registry = verified + self._registries[verified.registry_root] = verified + self._verification_time_ms = verification_time_ms + + def update_observed_time(self, observed_at_ms: str) -> None: + """Advance the explicit trusted observation time without reading a clock.""" + + with self._lock: + observed = _decimal("observed_at_ms", observed_at_ms) + if int(observed) < int(self._verification_time_ms): + _fail("OBSERVED_TIME_REGRESSION") + refreshed = verify_trust_registry( + self._current_registry.document, + pinned_operator_public_key_hex=self._pinned_operator_public_key_hex, + expected_operator_key_id=self._expected_operator_key_id, + expected_registry_root=self._current_registry.registry_root, + expected_registry_version=self._current_registry.registry_version, + verification_time_ms=observed, + max_clock_skew_ms=self._max_clock_skew_ms, + ) + self._assert_signer(refreshed, observed) + self._current_registry = refreshed + self._registries[refreshed.registry_root] = refreshed + self._verification_time_ms = observed + + def _reset_state(self) -> None: + self._head = ZERO_HASH + self._next_sequence = 0 + self._last_timestamp = -1 + self._state_roots: dict[tuple[str, str, str], str] = {} + self._leases: dict[tuple[str, str, str], _LeaseState] = {} + self._lease_ids: set[str] = set() + self._generations: dict[tuple[str, str, str], int] = {} + self._mutations: dict[tuple[str, str, str, str, str, str], _MutationState] = {} + self._consumed_actions: set[tuple[str, str, str, str, str, str]] = set() + self._nonces: set[str] = set() + self._last_receipt_registry_version: int | None = None + self._last_receipt_registry_root: str | None = None + + @property + def head_receipt_id(self) -> str: + with self._lock: + return self._head + + @staticmethod + def _scope(bindings: ReceiptBindings) -> tuple[str, str, str]: + bindings.validate() + return (bindings.workspace_identity_root, bindings.holon_identity_root, bindings.authority_domain) + + @staticmethod + def _action_key(bindings: ReceiptBindings, action_digest: str) -> tuple[str, str, str, str, str, str]: + return ( + bindings.actor_identity_root, + bindings.session_identity_root, + bindings.workspace_identity_root, + bindings.holon_identity_root, + bindings.authority_domain, + action_digest, + ) + + def canonical_state_root(self, bindings: ReceiptBindings) -> str | None: + with self._lock: + return self._state_roots.get(self._scope(bindings)) + + def current_lease(self, bindings: ReceiptBindings) -> Mapping[str, Any] | None: + with self._lock: + lease = self._leases.get(self._scope(bindings)) + if lease is None or not lease.active or int(self._verification_time_ms) >= lease.expires_at_ms: + return None + return { + "lease_id": lease.lease_id, + "lease_generation": str(lease.generation), + "fencing_token": lease.fencing_token, + "expected_state_root": lease.expected_state_root, + "expires_at_ms": str(lease.expires_at_ms), + } + + def recover(self) -> None: + with self._lock: + self._reset_state() + for envelope in self._store.read_all_receipts(): + registry_root = envelope["proof"]["trust_registry_root"] + registry = self._registries.get(registry_root) + if registry is None: + _fail("RECEIPT_TRUST_REGISTRY_UNRESOLVED") + verified = verify_receipt( + envelope, + registry=registry, + verification_time_ms=self._verification_time_ms, + max_clock_skew_ms=self._max_clock_skew_ms, + ) + self._apply_verified_receipt(verified) + + def _bindings_from_body(self, body: Mapping[str, Any]) -> ReceiptBindings: + return ReceiptBindings( + actor_identity_root=body["actor_identity_root"], + session_identity_root=body["session_identity_root"], + workspace_identity_root=body["workspace_identity_root"], + holon_identity_root=body["holon_identity_root"], + authority_domain=body["authority_domain"], + authority_level=body["authority_level"], + ) + + @staticmethod + def _same_bindings(left: ReceiptBindings, right: ReceiptBindings) -> bool: + return left == right + + def _registry_descends_from(self, candidate_root: str, ancestor_root: str) -> bool: + cursor = self._registries.get(candidate_root) + visited: set[str] = set() + while cursor is not None and cursor.registry_root not in visited: + if cursor.registry_root == ancestor_root: + return True + visited.add(cursor.registry_root) + if cursor.previous_registry_root == ZERO_HASH: + return False + cursor = self._registries.get(cursor.previous_registry_root) + return False + + def _apply_verified_receipt(self, envelope: Mapping[str, Any]) -> None: + kind = envelope["receipt_kind"] + body = envelope["receipt_body"] + proof = envelope["proof"] + sequence = int(body["receipt_sequence"]) + timestamp = int(body["timestamp_ms"]) + if sequence != self._next_sequence: + _fail("RECEIPT_CHAIN_SEQUENCE_BREAK") + if body["parent_receipt_hash"] != self._head: + _fail("RECEIPT_CHAIN_PARENT_BREAK") + if timestamp < self._last_timestamp: + _fail("RECEIPT_TIMESTAMP_REGRESSION") + if body["nonce"] in self._nonces: + _fail("RECEIPT_NONCE_REPLAY") + registry_version = int(proof["trust_registry_version"]) + registry_root = proof["trust_registry_root"] + if self._last_receipt_registry_version is not None: + if registry_version < self._last_receipt_registry_version: + _fail("RECEIPT_TRUST_REGISTRY_DOWNGRADE") + if registry_version == self._last_receipt_registry_version: + if registry_root != self._last_receipt_registry_root: + _fail("RECEIPT_TRUST_REGISTRY_FORK") + elif not self._registry_descends_from(registry_root, self._last_receipt_registry_root): + _fail("RECEIPT_TRUST_REGISTRY_LINEAGE_BREAK") + bindings = self._bindings_from_body(body) + scope = self._scope(bindings) + actual_state = self._state_roots.get(scope) + state_was_uninitialized = actual_state is None + if actual_state is None: + if kind not in ("LEASE_ISSUED", "LEASE_ISSUANCE_DENIED"): + _fail("CANONICAL_STATE_UNINITIALIZED") + actual_state = body["observed_state_root"] + if body["observed_state_root"] != actual_state or body["before_state_root"] != actual_state: + _fail("RECEIPT_OBSERVED_STATE_STALE") + generation = int(body["lease_generation"]) + lease = self._leases.get(scope) + + if kind == "LEASE_ISSUED": + if lease is not None and lease.active: + _fail("LEASE_ALREADY_ACTIVE") + if body["lease_id"] in self._lease_ids: + _fail("LEASE_ID_REPLAY") + if body["expected_state_root"] != actual_state: + _fail("LEASE_EXPECTED_STATE_STALE") + if generation != self._generations.get(scope, 0) + 1: + _fail("LEASE_GENERATION_STALE") + if int(body["expires_at_ms"]) <= timestamp: + _fail("LEASE_EXPIRY_INVALID") + self._leases[scope] = _LeaseState( + bindings, body["lease_id"], generation, body["fencing_token"], actual_state, + int(body["expires_at_ms"]), envelope["receipt_id"], True, + ) + self._state_roots[scope] = actual_state + self._lease_ids.add(body["lease_id"]) + self._generations[scope] = generation + elif kind == "LEASE_ISSUANCE_DENIED": + pass + elif kind == "LEASE_RENEWED": + self._require_lease_receipt_binding(lease, bindings, body, current_fence=False) + if generation != lease.generation + 1: + _fail("LEASE_GENERATION_STALE") + if body["expected_state_root"] != actual_state: + _fail("LEASE_EXPECTED_STATE_STALE") + if timestamp >= lease.expires_at_ms: + _fail("LEASE_EXPIRED") + if int(body["expires_at_ms"]) <= lease.expires_at_ms: + _fail("LEASE_RENEWAL_NOT_EXTENDED") + lease.generation = generation + lease.fencing_token = body["fencing_token"] + lease.expires_at_ms = int(body["expires_at_ms"]) + lease.authorization_receipt_hash = envelope["receipt_id"] + self._generations[scope] = generation + elif kind == "LEASE_RENEWAL_DENIED": + pass + elif kind in ("LEASE_EXPIRED", "LEASE_REVOKED"): + self._require_lease_receipt_binding(lease, bindings, body) + if body["expected_state_root"] != actual_state: + _fail("LEASE_EXPECTED_STATE_STALE") + if kind == "LEASE_EXPIRED" and timestamp < lease.expires_at_ms: + _fail("LEASE_NOT_EXPIRED") + lease.active = False + elif kind == "MUTATION_ADMITTED": + self._require_lease_receipt_binding(lease, bindings, body) + if timestamp >= lease.expires_at_ms: + _fail("LEASE_EXPIRED") + if body["expected_state_root"] != actual_state: + _fail("MUTATION_EXPECTED_STATE_STALE") + action_key = self._action_key(bindings, body["action_digest"]) + if action_key in self._consumed_actions: + _fail("MUTATION_REPLAY") + if body["lease_authorization_receipt_hash"] != lease.authorization_receipt_hash: + _fail("LEASE_AUTHORIZATION_RECEIPT_MISMATCH") + self._mutations[action_key] = _MutationState( + lease_id=lease.lease_id, + generation=lease.generation, + fencing_token=lease.fencing_token, + action_digest=body["action_digest"], + authority_receipt_hash=body["authority_receipt_hash"], + lease_authorization_receipt_hash=body["lease_authorization_receipt_hash"], + before_state_root=actual_state, + status="ADMITTED", + ) + self._consumed_actions.add(action_key) + elif kind == "MUTATION_DENIED": + action_key = self._action_key(bindings, body["action_digest"]) + if action_key in self._consumed_actions: + if "MUTATION_REPLAY" not in body["denial_codes"]: + _fail("MUTATION_REPLAY_DENIAL_CODE_MISSING") + else: + self._consumed_actions.add(action_key) + elif kind in ("MUTATION_COMPLETED", "MUTATION_CANCELLED", "MUTATION_FAILED"): + mutation = self._mutations.get(self._action_key(bindings, body["action_digest"])) + if mutation is None or mutation.status != "ADMITTED": + _fail("MUTATION_ADMISSION_MISSING_OR_TERMINAL") + self._require_lease_receipt_binding( + lease, + bindings, + body, + permit_inactive=kind in ("MUTATION_CANCELLED", "MUTATION_FAILED"), + ) + if ( + mutation.lease_id != body["lease_id"] + or mutation.generation != generation + or mutation.fencing_token != body["fencing_token"] + or mutation.authority_receipt_hash != body["authority_receipt_hash"] + or mutation.lease_authorization_receipt_hash != body["lease_authorization_receipt_hash"] + or mutation.before_state_root != actual_state + ): + _fail("MUTATION_TERMINAL_BINDING_MISMATCH") + if kind == "MUTATION_COMPLETED" and body["expected_state_root"] != actual_state: + _fail("MUTATION_EXPECTED_STATE_STALE") + if kind == "MUTATION_COMPLETED" and timestamp >= lease.expires_at_ms: + _fail("LEASE_EXPIRED") + mutation.status = EXPECTED_OUTCOME[kind] + if kind == "MUTATION_COMPLETED": + self._state_roots[scope] = body["after_state_root"] + lease.expected_state_root = body["after_state_root"] + lease.active = False + else: # pragma: no cover - closed enum above + _fail("RECEIPT_KIND_INVALID") + + if state_was_uninitialized and kind != "LEASE_ISSUED": + self._state_roots.pop(scope, None) + self._nonces.add(body["nonce"]) + self._head = envelope["receipt_id"] + self._next_sequence += 1 + self._last_timestamp = timestamp + self._last_receipt_registry_version = registry_version + self._last_receipt_registry_root = registry_root + + def _require_lease_receipt_binding( + self, + lease: _LeaseState | None, + bindings: ReceiptBindings, + body: Mapping[str, Any], + *, + current_fence: bool = True, + permit_inactive: bool = False, + ) -> None: + if lease is None or (not lease.active and not permit_inactive): + _fail("LEASE_MISSING") + if not self._same_bindings(lease.bindings, bindings): + _fail("LEASE_IDENTITY_BINDING_MISMATCH") + if lease.lease_id != body["lease_id"]: + _fail("LEASE_ID_MISMATCH") + if current_fence: + if lease.generation != int(body["lease_generation"]): + _fail("LEASE_GENERATION_STALE") + if lease.fencing_token != body["fencing_token"]: + _fail("STALE_FENCING_TOKEN") + + def _result_digest(self, kind: str, outcome: str, denial_codes: Sequence[str], nonce: str) -> str: + return _domain_hash("AEGIS_AUTHORITATIVE_RECEIPT_RESULT_V1", { + "receipt_kind": kind, + "outcome": outcome, + "denial_codes": list(denial_codes), + "nonce": nonce, + }) + + def _body( + self, + *, + kind: str, + bindings: ReceiptBindings, + lease_id: str, + lease_generation: int, + fencing_token: str, + authority_receipt_hash: str, + lease_authorization_receipt_hash: str, + observed_state_root: str, + expected_state_root: str, + action_digest: str, + after_state_root: str, + timestamp_ms: str, + expires_at_ms: str, + nonce: str, + denial_codes: Iterable[str], + result_digest: str | None = None, + ) -> dict[str, Any]: + bindings.validate() + codes = tuple(sorted(set(denial_codes), key=lambda item: item.encode("utf-8"))) + outcome = EXPECTED_OUTCOME[kind] + return { + "receipt_sequence": str(self._next_sequence), + "actor_identity_root": bindings.actor_identity_root, + "session_identity_root": bindings.session_identity_root, + "workspace_identity_root": bindings.workspace_identity_root, + "holon_identity_root": bindings.holon_identity_root, + "authority_domain": bindings.authority_domain, + "authority_level": bindings.authority_level, + "authority_receipt_hash": authority_receipt_hash, + "lease_id": lease_id, + "lease_generation": str(lease_generation), + "fencing_token": fencing_token, + "lease_authorization_receipt_hash": lease_authorization_receipt_hash, + "parent_receipt_hash": self._head, + "observed_state_root": observed_state_root, + "expected_state_root": expected_state_root, + "action_digest": action_digest, + "before_state_root": observed_state_root, + "after_state_root": after_state_root, + "result_digest": ( + self._result_digest(kind, outcome, codes, nonce) + if result_digest is None + else _hash("result_digest", result_digest, nonzero=True) + ), + "timestamp_ms": _decimal("timestamp_ms", timestamp_ms), + "expires_at_ms": _decimal("expires_at_ms", expires_at_ms), + "nonce": nonce, + "outcome": outcome, + "denial_codes": list(codes), + } + + def _persist_then_apply(self, kind: str, body: Mapping[str, Any]) -> dict[str, Any]: + envelope = sign_receipt( + receipt_kind=kind, + receipt_body=body, + registry=self._current_registry, + signer_key_id=self._signer_key_id, + signer_private_key_hex=self._signer_private_key_hex, + ) + verify_receipt( + envelope, + registry=self._current_registry, + verification_time_ms=self._verification_time_ms, + max_clock_skew_ms=self._max_clock_skew_ms, + ) + snapshot = self._state_snapshot() + try: + self._apply_verified_receipt(envelope) + finally: + self._restore_state_snapshot(snapshot) + self._store.persist_receipt( + envelope, + registry=self._current_registry, + verification_time_ms=self._verification_time_ms, + max_clock_skew_ms=self._max_clock_skew_ms, + ) + # The store validates the pending bytes before committing. If process + # memory fails after this point, restart replays the authenticated row. + self._apply_verified_receipt(envelope) + return envelope + + def _state_snapshot(self) -> tuple[Any, ...]: + return ( + self._head, + self._next_sequence, + self._last_timestamp, + copy.deepcopy(self._state_roots), + copy.deepcopy(self._leases), + copy.deepcopy(self._lease_ids), + copy.deepcopy(self._generations), + copy.deepcopy(self._mutations), + copy.deepcopy(self._consumed_actions), + copy.deepcopy(self._nonces), + self._last_receipt_registry_version, + self._last_receipt_registry_root, + ) + + def _restore_state_snapshot(self, snapshot: tuple[Any, ...]) -> None: + ( + self._head, + self._next_sequence, + self._last_timestamp, + self._state_roots, + self._leases, + self._lease_ids, + self._generations, + self._mutations, + self._consumed_actions, + self._nonces, + self._last_receipt_registry_version, + self._last_receipt_registry_root, + ) = snapshot + + def _actual_state(self, bindings: ReceiptBindings, observed_state_root: str) -> str: + observed = _hash("observed_state_root", observed_state_root, nonzero=True) + return self._state_roots.get(self._scope(bindings), observed) + + def _decision_time(self, timestamp_ms: str) -> int: + """Use explicit trusted observation time for live lease decisions. + + The receipt timestamp remains caller supplied and signed, but a + backdated value cannot revive a lease that is expired at the + authority's separately supplied monotonic observation time. + """ + + timestamp = int(_decimal("timestamp_ms", timestamp_ms)) + return max(timestamp, int(self._verification_time_ms)) + + def issue_lease( + self, + *, + bindings: ReceiptBindings, + lease_id: str, + observed_state_root: str, + expected_state_root: str, + action_digest: str, + timestamp_ms: str, + expires_at_ms: str, + nonce: str, + ) -> dict[str, Any]: + with self._lock: + _hash("lease_id", lease_id, nonzero=True) + expected = _hash("expected_state_root", expected_state_root, nonzero=True) + action = _hash("action_digest", action_digest, nonzero=True) + _nonce(nonce) + timestamp = int(_decimal("timestamp_ms", timestamp_ms)) + decision_time = self._decision_time(timestamp_ms) + expires = int(_decimal("expires_at_ms", expires_at_ms)) + actual = self._actual_state(bindings, observed_state_root) + scope = self._scope(bindings) + reasons: list[str] = [] + if self._state_roots.get(scope) not in (None, observed_state_root): + reasons.append("OBSERVED_STATE_STALE") + active = self._leases.get(scope) + if active is not None and active.active: + reasons.append( + "LEASE_EXPIRED" if int(self._verification_time_ms) >= active.expires_at_ms + else "WRITER_ALREADY_ACTIVE" + ) + if lease_id in self._lease_ids: + reasons.append("LEASE_ID_REPLAY") + if expected != actual: + reasons.append("EXPECTED_STATE_STALE") + if expires <= decision_time: + reasons.append("LEASE_EXPIRY_INVALID") + generation = self._generations.get(scope, 0) + 1 + kind = "LEASE_ISSUANCE_DENIED" if reasons else "LEASE_ISSUED" + fence = ZERO_HASH if reasons else _domain_hash("AEGIS_AUTHORITATIVE_FENCE_V1", { + "authority_domain": bindings.authority_domain, + "lease_id": lease_id, + "lease_generation": str(generation), + "parent_receipt_hash": self._head, + "nonce": nonce, + }) + body = self._body( + kind=kind, bindings=bindings, lease_id=lease_id, lease_generation=generation, + fencing_token=fence, authority_receipt_hash=ZERO_HASH, + lease_authorization_receipt_hash=ZERO_HASH, observed_state_root=actual, + expected_state_root=expected, action_digest=action, after_state_root=actual, + timestamp_ms=timestamp_ms, expires_at_ms=expires_at_ms, nonce=nonce, + denial_codes=reasons, + ) + return self._persist_then_apply(kind, body) + + def renew_lease( + self, + *, + bindings: ReceiptBindings, + lease_id: str, + lease_generation: str, + fencing_token: str, + observed_state_root: str, + expected_state_root: str, + action_digest: str, + timestamp_ms: str, + expires_at_ms: str, + nonce: str, + ) -> dict[str, Any]: + with self._lock: + presented_generation = _decimal_int("lease_generation", lease_generation) + _hash("fencing_token", fencing_token, nonzero=True) + _nonce(nonce) + actual = self._actual_state(bindings, observed_state_root) + lease = self._leases.get(self._scope(bindings)) + timestamp = int(_decimal("timestamp_ms", timestamp_ms)) + decision_time = self._decision_time(timestamp_ms) + new_expiry = int(_decimal("expires_at_ms", expires_at_ms)) + reasons = self._lease_reasons( + lease, bindings, lease_id, presented_generation, fencing_token, + expected_state_root, actual, decision_time, + ) + if lease is not None and new_expiry <= lease.expires_at_ms: + reasons.append("LEASE_RENEWAL_NOT_EXTENDED") + if new_expiry <= decision_time: + reasons.append("LEASE_EXPIRY_INVALID") + kind = "LEASE_RENEWAL_DENIED" if reasons else "LEASE_RENEWED" + next_generation = presented_generation if reasons else presented_generation + 1 + next_fence = fencing_token if reasons else _domain_hash("AEGIS_AUTHORITATIVE_FENCE_V1", { + "authority_domain": bindings.authority_domain, + "lease_id": lease_id, + "lease_generation": str(next_generation), + "parent_receipt_hash": self._head, + "nonce": nonce, + }) + body = self._body( + kind=kind, bindings=bindings, lease_id=lease_id, lease_generation=next_generation, + fencing_token=next_fence, authority_receipt_hash=ZERO_HASH, + lease_authorization_receipt_hash=ZERO_HASH, observed_state_root=actual, + expected_state_root=_hash("expected_state_root", expected_state_root, nonzero=True), + action_digest=_hash("action_digest", action_digest, nonzero=True), after_state_root=actual, + timestamp_ms=timestamp_ms, expires_at_ms=expires_at_ms, nonce=nonce, + denial_codes=reasons, + ) + return self._persist_then_apply(kind, body) + + def _lease_reasons( + self, + lease: _LeaseState | None, + bindings: ReceiptBindings, + lease_id: str, + generation: int, + fence: str, + expected_state_root: str, + actual_state_root: str, + timestamp: int, + *, + permit_expired: bool = False, + permit_inactive: bool = False, + ) -> list[str]: + reasons: list[str] = [] + if lease is None or (not lease.active and not permit_inactive): + return ["LEASE_MISSING"] + if lease.bindings != bindings: + reasons.append("AMBIGUOUS_ACTOR_SESSION_BINDING") + if lease.lease_id != lease_id: + reasons.append("LEASE_ID_MISMATCH") + if lease.generation != generation: + reasons.append("STALE_LEASE_GENERATION") + if lease.fencing_token != fence: + reasons.append("STALE_FENCING_TOKEN") + if _hash("expected_state_root", expected_state_root, nonzero=True) != actual_state_root: + reasons.append("EXPECTED_STATE_STALE") + if not permit_expired and timestamp >= lease.expires_at_ms: + reasons.append("LEASE_EXPIRED") + return reasons + + def expire_lease( + self, + *, + bindings: ReceiptBindings, + lease_id: str, + lease_generation: str, + fencing_token: str, + observed_state_root: str, + action_digest: str, + timestamp_ms: str, + nonce: str, + ) -> dict[str, Any]: + return self._close_lease( + kind="LEASE_EXPIRED", bindings=bindings, lease_id=lease_id, + lease_generation=lease_generation, fencing_token=fencing_token, + observed_state_root=observed_state_root, action_digest=action_digest, + timestamp_ms=timestamp_ms, nonce=nonce, denial_code="LEASE_EXPIRED", + ) + + def revoke_lease( + self, + *, + bindings: ReceiptBindings, + lease_id: str, + lease_generation: str, + fencing_token: str, + observed_state_root: str, + action_digest: str, + timestamp_ms: str, + nonce: str, + reason: str = "OPERATOR_REVOKED", + ) -> dict[str, Any]: + return self._close_lease( + kind="LEASE_REVOKED", bindings=bindings, lease_id=lease_id, + lease_generation=lease_generation, fencing_token=fencing_token, + observed_state_root=observed_state_root, action_digest=action_digest, + timestamp_ms=timestamp_ms, nonce=nonce, denial_code=reason, + ) + + def _close_lease( + self, + *, + kind: str, + bindings: ReceiptBindings, + lease_id: str, + lease_generation: str, + fencing_token: str, + observed_state_root: str, + action_digest: str, + timestamp_ms: str, + nonce: str, + denial_code: str, + ) -> dict[str, Any]: + with self._lock: + generation = _decimal_int("lease_generation", lease_generation) + _hash("fencing_token", fencing_token, nonzero=True) + _nonce(nonce) + actual = self._actual_state(bindings, observed_state_root) + lease = self._leases.get(self._scope(bindings)) + reasons = self._lease_reasons( + lease, bindings, lease_id, generation, fencing_token, actual, actual, + int(_decimal("timestamp_ms", timestamp_ms)), permit_expired=True, + ) + if reasons: + _fail(reasons[0]) + if kind == "LEASE_EXPIRED" and int(timestamp_ms) < lease.expires_at_ms: + _fail("LEASE_NOT_EXPIRED") + code = _safe_id("denial_code", denial_code) + body = self._body( + kind=kind, bindings=bindings, lease_id=lease_id, lease_generation=generation, + fencing_token=fencing_token, authority_receipt_hash=ZERO_HASH, + lease_authorization_receipt_hash=ZERO_HASH, observed_state_root=actual, + expected_state_root=actual, action_digest=_hash("action_digest", action_digest, nonzero=True), + after_state_root=actual, timestamp_ms=timestamp_ms, + expires_at_ms=str(lease.expires_at_ms), nonce=nonce, denial_codes=(code,), + ) + return self._persist_then_apply(kind, body) + + def admit_mutation( + self, + *, + bindings: ReceiptBindings, + lease_id: str, + lease_generation: str, + fencing_token: str, + authority_receipt_hash: str, + lease_authorization_receipt_hash: str, + observed_state_root: str, + expected_state_root: str, + action_digest: str, + timestamp_ms: str, + nonce: str, + ) -> dict[str, Any]: + with self._lock: + generation = _decimal_int("lease_generation", lease_generation) + _hash("fencing_token", fencing_token, nonzero=True) + _hash("authority_receipt_hash", authority_receipt_hash, nonzero=True) + _hash("lease_authorization_receipt_hash", lease_authorization_receipt_hash, nonzero=True) + action = _hash("action_digest", action_digest, nonzero=True) + _nonce(nonce) + actual = self._actual_state(bindings, observed_state_root) + scope = self._scope(bindings) + lease = self._leases.get(scope) + timestamp = int(_decimal("timestamp_ms", timestamp_ms)) + reasons = self._lease_reasons( + lease, bindings, lease_id, generation, fencing_token, + expected_state_root, actual, self._decision_time(timestamp_ms), + ) + if self._state_roots.get(scope) != observed_state_root: + reasons.append("OBSERVED_STATE_STALE") + if lease is not None and lease_authorization_receipt_hash != lease.authorization_receipt_hash: + reasons.append("LEASE_AUTHORIZATION_RECEIPT_MISMATCH") + if self._action_key(bindings, action) in self._consumed_actions: + reasons.append("MUTATION_REPLAY") + kind = "MUTATION_DENIED" if reasons else "MUTATION_ADMITTED" + expiry = str(lease.expires_at_ms) if lease is not None else timestamp_ms + body = self._body( + kind=kind, bindings=bindings, lease_id=lease_id, lease_generation=generation, + fencing_token=fencing_token, authority_receipt_hash=authority_receipt_hash, + lease_authorization_receipt_hash=lease_authorization_receipt_hash, + observed_state_root=actual, expected_state_root=_hash("expected_state_root", expected_state_root, nonzero=True), + action_digest=action, after_state_root=actual, timestamp_ms=timestamp_ms, + expires_at_ms=expiry, nonce=nonce, denial_codes=reasons, + ) + return self._persist_then_apply(kind, body) + + def deny_mutation( + self, + *, + bindings: ReceiptBindings, + lease_id: str, + lease_generation: str, + fencing_token: str, + authority_receipt_hash: str, + lease_authorization_receipt_hash: str, + observed_state_root: str, + expected_state_root: str, + action_digest: str, + timestamp_ms: str, + expires_at_ms: str, + nonce: str, + denial_codes: Sequence[str], + result_digest: str, + ) -> dict[str, Any]: + with self._lock: + if not denial_codes: + _fail("denial_codes:REQUIRED") + actual = self._actual_state(bindings, observed_state_root) + body = self._body( + kind="MUTATION_DENIED", bindings=bindings, lease_id=_hash("lease_id", lease_id, nonzero=True), + lease_generation=_decimal_int("lease_generation", lease_generation), + fencing_token=_hash("fencing_token", fencing_token, nonzero=True), + authority_receipt_hash=_hash("authority_receipt_hash", authority_receipt_hash, nonzero=True), + lease_authorization_receipt_hash=_hash( + "lease_authorization_receipt_hash", lease_authorization_receipt_hash, nonzero=True, + ), + observed_state_root=actual, expected_state_root=_hash("expected_state_root", expected_state_root, nonzero=True), + action_digest=_hash("action_digest", action_digest, nonzero=True), after_state_root=actual, + timestamp_ms=timestamp_ms, expires_at_ms=expires_at_ms, nonce=nonce, + denial_codes=denial_codes, result_digest=result_digest, + ) + return self._persist_then_apply("MUTATION_DENIED", body) + + def complete_mutation(self, **kwargs: Any) -> dict[str, Any]: + after_state_root = kwargs.pop("after_state_root") + return self._terminal_mutation("MUTATION_COMPLETED", after_state_root=after_state_root, denial_codes=(), **kwargs) + + def cancel_mutation(self, *, denial_code: str = "MUTATION_CANCELLED", **kwargs: Any) -> dict[str, Any]: + return self._terminal_mutation("MUTATION_CANCELLED", denial_codes=(denial_code,), **kwargs) + + def fail_mutation(self, *, denial_code: str = "MUTATION_FAILED", **kwargs: Any) -> dict[str, Any]: + return self._terminal_mutation("MUTATION_FAILED", denial_codes=(denial_code,), **kwargs) + + def _terminal_mutation( + self, + kind: str, + *, + bindings: ReceiptBindings, + lease_id: str, + lease_generation: str, + fencing_token: str, + authority_receipt_hash: str, + lease_authorization_receipt_hash: str, + observed_state_root: str, + expected_state_root: str, + action_digest: str, + timestamp_ms: str, + nonce: str, + result_digest: str, + after_state_root: str | None = None, + denial_codes: Sequence[str], + ) -> dict[str, Any]: + with self._lock: + generation = _decimal_int("lease_generation", lease_generation) + actual = self._actual_state(bindings, observed_state_root) + lease = self._leases.get(self._scope(bindings)) + reasons = self._lease_reasons( + lease, bindings, lease_id, generation, fencing_token, + expected_state_root, actual, self._decision_time(timestamp_ms), + permit_expired=kind in ("MUTATION_CANCELLED", "MUTATION_FAILED"), + permit_inactive=kind in ("MUTATION_CANCELLED", "MUTATION_FAILED"), + ) + if reasons: + _fail(reasons[0]) + action = _hash("action_digest", action_digest, nonzero=True) + mutation = self._mutations.get(self._action_key(bindings, action)) + if mutation is None or mutation.status != "ADMITTED": + _fail("MUTATION_ADMISSION_MISSING_OR_TERMINAL") + if ( + mutation.lease_id != lease_id + or mutation.generation != generation + or mutation.fencing_token != fencing_token + or mutation.authority_receipt_hash != authority_receipt_hash + or mutation.lease_authorization_receipt_hash != lease_authorization_receipt_hash + ): + _fail("MUTATION_TERMINAL_BINDING_MISMATCH") + after = actual if kind != "MUTATION_COMPLETED" else _hash( + "after_state_root", after_state_root, nonzero=True, + ) + body = self._body( + kind=kind, bindings=bindings, lease_id=lease_id, lease_generation=generation, + fencing_token=fencing_token, + authority_receipt_hash=_hash("authority_receipt_hash", authority_receipt_hash, nonzero=True), + lease_authorization_receipt_hash=_hash( + "lease_authorization_receipt_hash", lease_authorization_receipt_hash, nonzero=True, + ), + observed_state_root=actual, expected_state_root=_hash("expected_state_root", expected_state_root), + action_digest=action, after_state_root=after, + timestamp_ms=timestamp_ms, expires_at_ms=str(lease.expires_at_ms), nonce=nonce, + denial_codes=denial_codes, result_digest=result_digest, + ) + return self._persist_then_apply(kind, body) + + +__all__ = [ + "AuthoritativeReceiptAuthority", + "AuthoritativeReceiptError", + "ED25519", + "ReceiptBindings", + "ReceiptStoreConflict", + "SQLiteReceiptStore", + "VerifiedTrustRegistry", + "ZERO_HASH", + "assert_i_json", + "canonical_receipt_signature_message", + "canonical_registry_signature_message", + "compute_receipt_id", + "compute_registry_root", + "create_trust_registry", + "load_json_strict", + "public_key_hex_from_private", + "sign_receipt", + "verify_receipt", + "verify_registry_rotation", + "verify_trust_registry", +] diff --git a/sovereign-omega-v2/python/generate_authoritative_receipt_vector.py b/sovereign-omega-v2/python/generate_authoritative_receipt_vector.py new file mode 100644 index 000000000..fd4270649 --- /dev/null +++ b/sovereign-omega-v2/python/generate_authoritative_receipt_vector.py @@ -0,0 +1,364 @@ +#!/usr/bin/env python3 +"""Generate the deterministic Python cross-runtime receipt golden vector.""" +from __future__ import annotations + +import argparse +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Any + +from authoritative_receipts import ( + AuthoritativeReceiptAuthority, + ReceiptBindings, + SQLiteReceiptStore, + ZERO_HASH, + create_trust_registry, +) +from canonical_envelope import canon + + +OPERATOR_KEY_ID = "operator-root-v1" +OPERATOR_PRIVATE = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" +OPERATOR_PUBLIC = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" +SIGNER_KEY_ID = "cross-runtime-witness-v1" +SIGNER_PRIVATE = "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb" +SIGNER_PUBLIC = "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c" +AUTHORITY_DOMAIN = "repository:mutation" + +ALL_RECEIPT_KINDS = tuple(sorted(( + "LEASE_ISSUED", + "LEASE_ISSUANCE_DENIED", + "LEASE_RENEWED", + "LEASE_RENEWAL_DENIED", + "LEASE_EXPIRED", + "LEASE_REVOKED", + "MUTATION_ADMITTED", + "MUTATION_DENIED", + "MUTATION_COMPLETED", + "MUTATION_CANCELLED", + "MUTATION_FAILED", +), key=lambda item: item.encode("utf-8"))) + + +def _h(character: str) -> str: + return character * 64 + + +def _bindings() -> ReceiptBindings: + return ReceiptBindings( + actor_identity_root=_h("1"), + session_identity_root=_h("2"), + workspace_identity_root=_h("3"), + holon_identity_root=_h("4"), + authority_domain=AUTHORITY_DOMAIN, + authority_level="D2", + ) + + +def _registry() -> dict[str, Any]: + return create_trust_registry( + { + "registry_version": "1", + "previous_registry_root": ZERO_HASH, + "issued_at_ms": "90", + "valid_from_ms": "100", + "expires_at_ms": "10000", + "operator_key_id": OPERATOR_KEY_ID, + "keys": [{ + "key_id": SIGNER_KEY_ID, + "public_key": SIGNER_PUBLIC, + "verifier_identity_root": _h("7"), + "valid_from_ms": "100", + "expires_at_ms": "9000", + "status": "ACTIVE", + "authority_domains": [AUTHORITY_DOMAIN], + "receipt_kinds": list(ALL_RECEIPT_KINDS), + }], + }, + OPERATOR_PRIVATE, + ) + + +def build_python_cross_runtime_vector() -> dict[str, Any]: + """Build one valid chain containing every V1 receipt kind.""" + + registry = _registry() + bindings = _bindings() + with TemporaryDirectory() as temporary: + store = SQLiteReceiptStore(Path(temporary) / "receipts.sqlite3") + authority = AuthoritativeReceiptAuthority( + store=store, + current_registry=registry, + pinned_operator_public_key_hex=OPERATOR_PUBLIC, + expected_operator_key_id=OPERATOR_KEY_ID, + expected_registry_root=registry["registry_root"], + signer_key_id=SIGNER_KEY_ID, + signer_private_key_hex=SIGNER_PRIVATE, + verification_time_ms="1000", + ) + receipts: list[dict[str, Any]] = [] + + receipts.append(authority.issue_lease( + bindings=bindings, + lease_id=_h("4"), + observed_state_root=_h("f"), + expected_state_root=_h("f"), + action_digest=_h("1"), + timestamp_ms="1000", + expires_at_ms="900", + nonce="vector-lease-denied-01", + )) + + authority.update_observed_time("1100") + issued = authority.issue_lease( + bindings=bindings, + lease_id=_h("5"), + observed_state_root=_h("a"), + expected_state_root=_h("a"), + action_digest=_h("b"), + timestamp_ms="1100", + expires_at_ms="3000", + nonce="vector-lease-issued-01", + ) + receipts.append(issued) + + authority.update_observed_time("1200") + receipts.append(authority.renew_lease( + bindings=bindings, + lease_id=_h("5"), + lease_generation="0", + fencing_token=_h("9"), + observed_state_root=_h("a"), + expected_state_root=_h("a"), + action_digest=_h("b"), + timestamp_ms="1200", + expires_at_ms="4000", + nonce="vector-renew-denied-01", + )) + + current = authority.current_lease(bindings) + assert current is not None + authority.update_observed_time("1300") + renewed = authority.renew_lease( + bindings=bindings, + lease_id=current["lease_id"], + lease_generation=current["lease_generation"], + fencing_token=current["fencing_token"], + observed_state_root=_h("a"), + expected_state_root=_h("a"), + action_digest=_h("b"), + timestamp_ms="1300", + expires_at_ms="4000", + nonce="vector-lease-renewed-1", + ) + receipts.append(renewed) + + current = authority.current_lease(bindings) + assert current is not None + authority.update_observed_time("1400") + receipts.append(authority.deny_mutation( + bindings=bindings, + lease_id=current["lease_id"], + lease_generation=current["lease_generation"], + fencing_token=current["fencing_token"], + authority_receipt_hash=_h("c"), + lease_authorization_receipt_hash=renewed["receipt_id"], + observed_state_root=_h("a"), + expected_state_root=_h("a"), + action_digest=_h("d"), + timestamp_ms="1400", + expires_at_ms="4000", + nonce="vector-mutation-deny-01", + denial_codes=("POLICY_DENIED",), + result_digest=_h("4"), + )) + + authority.update_observed_time("1500") + admitted = authority.admit_mutation( + bindings=bindings, + lease_id=current["lease_id"], + lease_generation=current["lease_generation"], + fencing_token=current["fencing_token"], + authority_receipt_hash=_h("c"), + lease_authorization_receipt_hash=renewed["receipt_id"], + observed_state_root=_h("a"), + expected_state_root=_h("a"), + action_digest=_h("b"), + timestamp_ms="1500", + nonce="vector-mutation-admit-1", + ) + receipts.append(admitted) + + authority.update_observed_time("1600") + completed = authority.complete_mutation( + bindings=bindings, + lease_id=current["lease_id"], + lease_generation=current["lease_generation"], + fencing_token=current["fencing_token"], + authority_receipt_hash=_h("c"), + lease_authorization_receipt_hash=renewed["receipt_id"], + observed_state_root=_h("a"), + expected_state_root=_h("a"), + action_digest=_h("b"), + timestamp_ms="1600", + nonce="vector-mutation-done-01", + result_digest=_h("f"), + after_state_root=_h("e"), + ) + receipts.append(completed) + + authority.update_observed_time("1700") + issued_for_cancel = authority.issue_lease( + bindings=bindings, + lease_id=_h("6"), + observed_state_root=_h("e"), + expected_state_root=_h("e"), + action_digest=_h("7"), + timestamp_ms="1700", + expires_at_ms="2000", + nonce="vector-cancel-lease-001", + ) + receipts.append(issued_for_cancel) + cancel_lease = authority.current_lease(bindings) + assert cancel_lease is not None + + authority.update_observed_time("1800") + receipts.append(authority.admit_mutation( + bindings=bindings, + lease_id=cancel_lease["lease_id"], + lease_generation=cancel_lease["lease_generation"], + fencing_token=cancel_lease["fencing_token"], + authority_receipt_hash=_h("c"), + lease_authorization_receipt_hash=issued_for_cancel["receipt_id"], + observed_state_root=_h("e"), + expected_state_root=_h("e"), + action_digest=_h("7"), + timestamp_ms="1800", + nonce="vector-cancel-admit-01", + )) + + authority.update_observed_time("2000") + receipts.append(authority.expire_lease( + bindings=bindings, + lease_id=cancel_lease["lease_id"], + lease_generation=cancel_lease["lease_generation"], + fencing_token=cancel_lease["fencing_token"], + observed_state_root=_h("e"), + action_digest=_h("7"), + timestamp_ms="2000", + nonce="vector-lease-expired-1", + )) + + authority.update_observed_time("2100") + receipts.append(authority.cancel_mutation( + bindings=bindings, + lease_id=cancel_lease["lease_id"], + lease_generation=cancel_lease["lease_generation"], + fencing_token=cancel_lease["fencing_token"], + authority_receipt_hash=_h("c"), + lease_authorization_receipt_hash=issued_for_cancel["receipt_id"], + observed_state_root=_h("e"), + expected_state_root=_h("e"), + action_digest=_h("7"), + timestamp_ms="2100", + nonce="vector-mutation-cancel1", + result_digest=_h("8"), + denial_code="CANCELLED_AFTER_EXPIRY", + )) + + authority.update_observed_time("2200") + issued_for_failure = authority.issue_lease( + bindings=bindings, + lease_id=_h("9"), + observed_state_root=_h("e"), + expected_state_root=_h("e"), + action_digest=_h("a"), + timestamp_ms="2200", + expires_at_ms="4000", + nonce="vector-failure-lease-1", + ) + receipts.append(issued_for_failure) + failure_lease = authority.current_lease(bindings) + assert failure_lease is not None + + authority.update_observed_time("2300") + receipts.append(authority.admit_mutation( + bindings=bindings, + lease_id=failure_lease["lease_id"], + lease_generation=failure_lease["lease_generation"], + fencing_token=failure_lease["fencing_token"], + authority_receipt_hash=_h("c"), + lease_authorization_receipt_hash=issued_for_failure["receipt_id"], + observed_state_root=_h("e"), + expected_state_root=_h("e"), + action_digest=_h("a"), + timestamp_ms="2300", + nonce="vector-failure-admit-1", + )) + + authority.update_observed_time("2400") + receipts.append(authority.revoke_lease( + bindings=bindings, + lease_id=failure_lease["lease_id"], + lease_generation=failure_lease["lease_generation"], + fencing_token=failure_lease["fencing_token"], + observed_state_root=_h("e"), + action_digest=_h("a"), + timestamp_ms="2400", + nonce="vector-lease-revoked-1", + reason="OPERATOR_REVOKED", + )) + + authority.update_observed_time("2500") + terminal = authority.fail_mutation( + bindings=bindings, + lease_id=failure_lease["lease_id"], + lease_generation=failure_lease["lease_generation"], + fencing_token=failure_lease["fencing_token"], + authority_receipt_hash=_h("c"), + lease_authorization_receipt_hash=issued_for_failure["receipt_id"], + observed_state_root=_h("e"), + expected_state_root=_h("e"), + action_digest=_h("a"), + timestamp_ms="2500", + nonce="vector-mutation-fail-01", + result_digest=_h("b"), + denial_code="FAILED_AFTER_REVOCATION", + ) + receipts.append(terminal) + store.close() + + return { + "schema_version": "1.0.0", + "operator_public_key": OPERATOR_PUBLIC, + "registry": registry, + "receipts": receipts, + "terminal_receipt_id": terminal["receipt_id"], + "context": { + "operator_key_id": OPERATOR_KEY_ID, + "accepted_registry_roots": [registry["registry_root"]], + "observed_at_ms": "3000", + "max_clock_skew_ms": "0", + "expected_actor_identity_root": bindings.actor_identity_root, + "expected_session_identity_root": bindings.session_identity_root, + "expected_workspace_identity_root": bindings.workspace_identity_root, + "expected_holon_identity_root": bindings.holon_identity_root, + "expected_authority_domain": bindings.authority_domain, + "expected_authority_level": bindings.authority_level, + "expected_observed_state_root": _h("e"), + "expected_action_digest": _h("a"), + }, + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--output", required=True, type=Path) + args = parser.parse_args() + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_bytes(canon(build_python_cross_runtime_vector()) + b"\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/sovereign-omega-v2/python/tests/test_authoritative_receipts.py b/sovereign-omega-v2/python/tests/test_authoritative_receipts.py new file mode 100644 index 000000000..58db9f9b1 --- /dev/null +++ b/sovereign-omega-v2/python/tests/test_authoritative_receipts.py @@ -0,0 +1,1071 @@ +#!/usr/bin/env python3 +"""Focused and adversarial tests for cross-runtime authoritative receipts.""" +from __future__ import annotations + +import copy +import json +import sqlite3 +import sys +import threading +from dataclasses import FrozenInstanceError +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest import TestCase, main + +PYTHON_ROOT = Path(__file__).resolve().parents[1] +REPO_ROOT = Path(__file__).resolve().parents[3] +sys.path.insert(0, str(PYTHON_ROOT)) + +from authoritative_receipts import ( # noqa: E402 + AuthoritativeReceiptAuthority, + AuthoritativeReceiptError, + ReceiptBindings, + ReceiptStoreConflict, + SQLiteReceiptStore, + ZERO_HASH, + assert_i_json, + canonical_receipt_signature_message, + canonical_registry_signature_message, + compute_receipt_id, + compute_registry_root, + create_trust_registry, + load_json_strict, + public_key_hex_from_private, + sign_receipt, + verify_receipt, + verify_registry_rotation, + verify_trust_registry, +) +from canonical_envelope import canon, sha256_hex # noqa: E402 +from generate_authoritative_receipt_vector import ( # noqa: E402 + build_python_cross_runtime_vector, +) + + +OPERATOR_KEY_ID = "operator-root-v1" +OPERATOR_PRIVATE = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" +OPERATOR_PUBLIC = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" +SIGNER_KEY_ID = "python-witness-v1" +SIGNER_PRIVATE = "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb" +SIGNER_PUBLIC = "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c" +ROTATED_KEY_ID = "python-witness-v2" +ROTATED_PRIVATE = "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7" +ROTATED_PUBLIC = "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025" + +ALL_KINDS = tuple(sorted(( + "LEASE_ISSUED", "LEASE_ISSUANCE_DENIED", "LEASE_RENEWED", "LEASE_RENEWAL_DENIED", + "LEASE_EXPIRED", "LEASE_REVOKED", "MUTATION_ADMITTED", "MUTATION_DENIED", + "MUTATION_COMPLETED", "MUTATION_CANCELLED", "MUTATION_FAILED", +), key=lambda item: item.encode("utf-8"))) + + +def H(character: str) -> str: + return character * 64 + + +def build_registry( + *, + version: str = "1", + previous_root: str = ZERO_HASH, + key_id: str = SIGNER_KEY_ID, + public_key: str = SIGNER_PUBLIC, + verifier_root: str = H("7"), + issued_at_ms: str = "90", + valid_from_ms: str = "100", + expires_at_ms: str = "10000", + key_valid_from_ms: str = "100", + key_expires_at_ms: str = "9000", + status: str = "ACTIVE", + operator_key_id: str = OPERATOR_KEY_ID, +) -> dict: + body = { + "registry_version": version, + "previous_registry_root": previous_root, + "issued_at_ms": issued_at_ms, + "valid_from_ms": valid_from_ms, + "expires_at_ms": expires_at_ms, + "operator_key_id": operator_key_id, + "keys": [{ + "key_id": key_id, + "public_key": public_key, + "verifier_identity_root": verifier_root, + "valid_from_ms": key_valid_from_ms, + "expires_at_ms": key_expires_at_ms, + "status": status, + "authority_domains": ["repository:mutation"], + "receipt_kinds": list(ALL_KINDS), + }], + } + return create_trust_registry(body, OPERATOR_PRIVATE) + + +def verify_registry(registry: dict, *, now: str = "8000"): + return verify_trust_registry( + registry, + pinned_operator_public_key_hex=OPERATOR_PUBLIC, + expected_operator_key_id=OPERATOR_KEY_ID, + expected_registry_root=registry["registry_root"], + expected_registry_version=registry["registry_body"]["registry_version"], + verification_time_ms=now, + ) + + +def bindings() -> ReceiptBindings: + return ReceiptBindings( + actor_identity_root=H("1"), + session_identity_root=H("2"), + workspace_identity_root=H("3"), + holon_identity_root=H("4"), + authority_domain="repository:mutation", + authority_level="D2", + ) + + +def create_authority(path: Path, registry: dict | None = None, **changes): + registry = registry or build_registry() + values = dict( + store=SQLiteReceiptStore(path), + current_registry=registry, + pinned_operator_public_key_hex=OPERATOR_PUBLIC, + expected_operator_key_id=OPERATOR_KEY_ID, + expected_registry_root=registry["registry_root"], + signer_key_id=SIGNER_KEY_ID, + signer_private_key_hex=SIGNER_PRIVATE, + verification_time_ms="1000", + ) + values.update(changes) + return AuthoritativeReceiptAuthority(**values) + + +def issue(authority: AuthoritativeReceiptAuthority, *, nonce="nonce-lease-00001", lease_id=H("5"), expires="3000"): + return authority.issue_lease( + bindings=bindings(), lease_id=lease_id, observed_state_root=H("a"), + expected_state_root=H("a"), action_digest=H("b"), timestamp_ms="1000", + expires_at_ms=expires, nonce=nonce, + ) + + +def admit(authority: AuthoritativeReceiptAuthority, lease_receipt: dict, *, action=H("c"), nonce="nonce-admit-00001", fence=None, expected=H("a")): + authority.update_observed_time("1100") + lease = authority.current_lease(bindings()) + assert lease is not None + return authority.admit_mutation( + bindings=bindings(), lease_id=lease["lease_id"], + lease_generation=lease["lease_generation"], + fencing_token=fence or lease["fencing_token"], + authority_receipt_hash=H("d"), + lease_authorization_receipt_hash=lease_receipt["receipt_id"], + observed_state_root=authority.canonical_state_root(bindings()), + expected_state_root=expected, action_digest=action, + timestamp_ms="1100", nonce=nonce, + ) + + +def complete(authority: AuthoritativeReceiptAuthority, lease_receipt: dict, *, action=H("c"), nonce="nonce-complete-001", after=H("e"), result=H("f")): + authority.update_observed_time("1200") + lease = authority.current_lease(bindings()) + assert lease is not None + state = authority.canonical_state_root(bindings()) + return authority.complete_mutation( + bindings=bindings(), lease_id=lease["lease_id"], lease_generation=lease["lease_generation"], + fencing_token=lease["fencing_token"], authority_receipt_hash=H("d"), + lease_authorization_receipt_hash=lease_receipt["receipt_id"], + observed_state_root=state, expected_state_root=state, action_digest=action, + timestamp_ms="1200", nonce=nonce, after_state_root=after, result_digest=result, + ) + + +def build_golden_vector() -> dict: + """Deterministic Python vector helper consumed by cross-runtime tests.""" + with TemporaryDirectory() as temporary: + path = Path(temporary) / "receipts.sqlite3" + registry = build_registry() + authority = create_authority(path, registry) + lease_receipt = issue(authority) + admitted = admit(authority, lease_receipt) + terminal = complete(authority, lease_receipt) + authority._store.close() + return { + "schema_version": "1.0.0", + "producer_runtime": "python", + "operator_public_key": OPERATOR_PUBLIC, + "registry": registry, + "receipts": [lease_receipt, admitted, terminal], + "terminal_receipt_id": terminal["receipt_id"], + "context": { + "operator_key_id": OPERATOR_KEY_ID, + "accepted_registry_roots": [registry["registry_root"]], + "observed_at_ms": "1200", + "max_clock_skew_ms": "0", + "expected_actor_identity_root": H("1"), + "expected_session_identity_root": H("2"), + "expected_workspace_identity_root": H("3"), + "expected_holon_identity_root": H("4"), + "expected_authority_domain": "repository:mutation", + "expected_authority_level": "D2", + "expected_observed_state_root": H("a"), + "expected_action_digest": H("c"), + }, + } + + +class AuthoritativeReceiptTests(TestCase): + def setUp(self) -> None: + self.temporary = TemporaryDirectory() + self.addCleanup(self.temporary.cleanup) + self.path = Path(self.temporary.name) / "receipts.sqlite3" + + def test_01_registry_signature_root_and_external_pins(self): + registry = build_registry() + verified = verify_registry(registry) + self.assertEqual(verified.registry_root, compute_registry_root(registry)) + self.assertEqual(public_key_hex_from_private(OPERATOR_PRIVATE), OPERATOR_PUBLIC) + self.assertEqual(public_key_hex_from_private(SIGNER_PRIVATE), SIGNER_PUBLIC) + self.assertTrue(canonical_registry_signature_message(registry).startswith(b'{"domain"')) + with self.assertRaisesRegex(AuthoritativeReceiptError, "TRUST_REGISTRY_OPERATOR_KEY_ID_MISMATCH"): + verify_trust_registry( + registry, pinned_operator_public_key_hex=OPERATOR_PUBLIC, + expected_operator_key_id="wrong-operator", expected_registry_root=registry["registry_root"], + ) + with self.assertRaisesRegex(AuthoritativeReceiptError, "TRUST_REGISTRY_NOT_EXPECTED"): + verify_trust_registry( + registry, pinned_operator_public_key_hex=OPERATOR_PUBLIC, + expected_operator_key_id=OPERATOR_KEY_ID, expected_registry_root=H("9"), + ) + + def test_02_registry_tamper_sort_duplicate_and_intervals_rejected(self): + registry = build_registry() + tampered = copy.deepcopy(registry) + tampered["registry_body"]["keys"][0]["status"] = "REVOKED" + with self.assertRaisesRegex(AuthoritativeReceiptError, "TRUST_REGISTRY_ROOT_MISMATCH"): + verify_registry(tampered) + + body = copy.deepcopy(registry["registry_body"]) + second = copy.deepcopy(body["keys"][0]) + second["key_id"] = "aaa-earlier-key" + body["keys"].append(second) + with self.assertRaisesRegex(AuthoritativeReceiptError, "TRUST_REGISTRY_KEYS_NONCANONICAL"): + create_trust_registry(body, OPERATOR_PRIVATE) + + body = copy.deepcopy(registry["registry_body"]) + second = copy.deepcopy(body["keys"][0]) + second["key_id"] = "zz-second-key" + body["keys"].append(second) + with self.assertRaisesRegex(AuthoritativeReceiptError, "TRUST_REGISTRY_PUBLIC_KEYS_DUPLICATE"): + create_trust_registry(body, OPERATOR_PRIVATE) + + with self.assertRaisesRegex(AuthoritativeReceiptError, "TRUST_REGISTRY_TIME_WINDOW_INVALID"): + build_registry(valid_from_ms="100", expires_at_ms="100") + + def test_03_strict_i_json_and_duplicate_json_keys(self): + for invalid in ({"x": 1.5}, {"x": 2**60}, {"x": b"bytes"}, {"x": (1, 2)}): + with self.subTest(invalid=invalid), self.assertRaises(AuthoritativeReceiptError): + assert_i_json(invalid) + cyclic: list = [] + cyclic.append(cyclic) + with self.assertRaisesRegex(AuthoritativeReceiptError, "I_JSON_CYCLE"): + assert_i_json(cyclic) + with self.assertRaisesRegex(AuthoritativeReceiptError, "JSON_DUPLICATE_KEY"): + load_json_strict('{"x":1,"x":2}') + + def test_04_python_golden_vector_matches_schemas_and_derivations(self): + vector = build_golden_vector() + self.assertEqual([item["receipt_kind"] for item in vector["receipts"]], [ + "LEASE_ISSUED", "MUTATION_ADMITTED", "MUTATION_COMPLETED", + ]) + self.assertEqual([item["receipt_body"]["receipt_sequence"] for item in vector["receipts"]], ["0", "1", "2"]) + self.assertEqual(vector["receipts"][0]["receipt_body"]["parent_receipt_hash"], ZERO_HASH) + self.assertEqual(vector["receipts"][1]["receipt_body"]["parent_receipt_hash"], vector["receipts"][0]["receipt_id"]) + self.assertEqual(vector["receipts"][2]["receipt_body"]["parent_receipt_hash"], vector["receipts"][1]["receipt_id"]) + for receipt in vector["receipts"]: + self.assertEqual(receipt["receipt_id"], compute_receipt_id(receipt)) + self.assertTrue(canonical_receipt_signature_message(receipt).startswith(b'{"domain"')) + try: + import jsonschema + except ImportError: # pragma: no cover + self.skipTest("jsonschema unavailable") + receipt_schema = json.loads((REPO_ROOT / "schemas/cross-runtime-receipt-envelope.v1.schema.json").read_text(encoding="utf-8")) + registry_schema = json.loads((REPO_ROOT / "schemas/receipt-trust-registry.v1.schema.json").read_text(encoding="utf-8")) + jsonschema.validate(vector["registry"], registry_schema) + for receipt in vector["receipts"]: + jsonschema.validate(receipt, receipt_schema) + + def test_05_receipt_tamper_unsigned_unknown_root_and_clock_skew_rejected(self): + registry_document = build_registry() + registry = verify_registry(registry_document) + vector = build_golden_vector()["receipts"][0] + verify_receipt(vector, registry=registry, verification_time_ms="1000") + tampered = copy.deepcopy(vector) + tampered["receipt_body"]["nonce"] = "nonce-tampered-0001" + with self.assertRaisesRegex(AuthoritativeReceiptError, "RECEIPT_ID_MISMATCH"): + verify_receipt(tampered, registry=registry, verification_time_ms="1000") + unsigned = copy.deepcopy(vector) + unsigned["proof"]["signature"] = "0" * 128 + unsigned["receipt_id"] = compute_receipt_id(unsigned) + with self.assertRaisesRegex(AuthoritativeReceiptError, "RECEIPT_SIGNATURE_INVALID"): + verify_receipt(unsigned, registry=registry, verification_time_ms="1000") + unknown = copy.deepcopy(vector) + unknown["proof"]["signer_key_id"] = "unknown-key" + unknown["receipt_id"] = compute_receipt_id(unknown) + with self.assertRaisesRegex(AuthoritativeReceiptError, "RECEIPT_SIGNER_UNKNOWN"): + verify_receipt(unknown, registry=registry, verification_time_ms="1000") + with self.assertRaisesRegex(AuthoritativeReceiptError, "RECEIPT_TIMESTAMP_IN_FUTURE"): + verify_receipt(vector, registry=registry, verification_time_ms="998", max_clock_skew_ms="1") + unresolved_fence = copy.deepcopy(vector) + unresolved_fence["receipt_body"]["fencing_token"] = ZERO_HASH + with self.assertRaisesRegex(AuthoritativeReceiptError, "fencing_token:UNRESOLVED"): + verify_receipt(unresolved_fence, registry=registry, verification_time_ms="1000") + + def test_06_success_state_root_and_restart_readback(self): + registry = build_registry() + authority = create_authority(self.path, registry) + lease_receipt = issue(authority) + admitted = admit(authority, lease_receipt) + terminal = complete(authority, lease_receipt) + self.assertEqual(terminal["receipt_body"]["before_state_root"], H("a")) + self.assertEqual(terminal["receipt_body"]["after_state_root"], H("e")) + self.assertEqual(terminal["receipt_body"]["result_digest"], H("f")) + self.assertEqual(authority.canonical_state_root(bindings()), H("e")) + self.assertEqual(authority.head_receipt_id, terminal["receipt_id"]) + self.assertIsNone(authority.current_lease(bindings())) + authority._store.close() + + recovered = create_authority(self.path, registry, verification_time_ms="1200") + self.assertEqual(recovered.canonical_state_root(bindings()), H("e")) + self.assertEqual(recovered.head_receipt_id, terminal["receipt_id"]) + self.assertEqual(recovered._store.read_receipt(admitted["receipt_id"]), admitted) + recovered._store.close() + + def test_07_denied_genesis_does_not_initialize_state(self): + authority = create_authority(self.path) + first = issue(authority, expires="900") + self.assertEqual(first["receipt_kind"], "LEASE_ISSUANCE_DENIED") + self.assertEqual(first["receipt_body"]["before_state_root"], first["receipt_body"]["after_state_root"]) + self.assertIsNone(authority.canonical_state_root(bindings())) + authority._store.close() + + def test_08_stale_state_fence_and_lease_link_are_signed_denials_with_no_change(self): + authority = create_authority(self.path) + lease_receipt = issue(authority) + baseline = authority.canonical_state_root(bindings()) + stale_state = admit(authority, lease_receipt, action=H("6"), nonce="nonce-stale-state01", expected=H("9")) + self.assertEqual(stale_state["receipt_kind"], "MUTATION_DENIED") + self.assertIn("EXPECTED_STATE_STALE", stale_state["receipt_body"]["denial_codes"]) + stale_fence = admit(authority, lease_receipt, action=H("7"), nonce="nonce-stale-fence01", fence=H("8")) + self.assertIn("STALE_FENCING_TOKEN", stale_fence["receipt_body"]["denial_codes"]) + wrong_link = copy.deepcopy(lease_receipt) + wrong_link["receipt_id"] = H("9") + link_denial = admit(authority, wrong_link, action=H("8"), nonce="nonce-wrong-link-01") + self.assertIn("LEASE_AUTHORIZATION_RECEIPT_MISMATCH", link_denial["receipt_body"]["denial_codes"]) + for denied in (stale_state, stale_fence, link_denial): + self.assertEqual(denied["receipt_body"]["before_state_root"], baseline) + self.assertEqual(denied["receipt_body"]["after_state_root"], baseline) + self.assertEqual(authority.canonical_state_root(bindings()), baseline) + authority._store.close() + + def test_09_denied_digest_cannot_promote_after_restart(self): + registry = build_registry() + authority = create_authority(self.path, registry) + lease_receipt = issue(authority) + action = H("6") + first = admit(authority, lease_receipt, action=action, nonce="nonce-denied-first1", expected=H("9")) + self.assertEqual(first["receipt_kind"], "MUTATION_DENIED") + authority._store.close() + recovered = create_authority(self.path, registry, verification_time_ms="1100") + replay = admit(recovered, lease_receipt, action=action, nonce="nonce-denied-replay", expected=H("a")) + self.assertEqual(replay["receipt_kind"], "MUTATION_DENIED") + self.assertIn("MUTATION_REPLAY", replay["receipt_body"]["denial_codes"]) + self.assertEqual(recovered.canonical_state_root(bindings()), H("a")) + recovered._store.close() + + def test_10_renewal_expiry_and_revocation_receipts(self): + authority = create_authority(self.path) + issued = issue(authority) + authority.update_observed_time("1500") + lease = authority.current_lease(bindings()) + renewal = authority.renew_lease( + bindings=bindings(), lease_id=lease["lease_id"], lease_generation=lease["lease_generation"], + fencing_token=lease["fencing_token"], observed_state_root=H("a"), expected_state_root=H("a"), + action_digest=H("b"), timestamp_ms="1500", expires_at_ms="4000", nonce="nonce-renewal-0001", + ) + self.assertEqual(renewal["receipt_kind"], "LEASE_RENEWED") + authority.update_observed_time("1600") + lease = authority.current_lease(bindings()) + denied = authority.renew_lease( + bindings=bindings(), lease_id=lease["lease_id"], lease_generation="1", + fencing_token=issued["receipt_body"]["fencing_token"], observed_state_root=H("a"), expected_state_root=H("a"), + action_digest=H("b"), timestamp_ms="1600", expires_at_ms="5000", nonce="nonce-renew-denied1", + ) + self.assertEqual(denied["receipt_kind"], "LEASE_RENEWAL_DENIED") + retained_lease = dict(lease) + authority.update_observed_time("4000") + self.assertIsNone(authority.current_lease(bindings())) + expired = authority.expire_lease( + bindings=bindings(), lease_id=retained_lease["lease_id"], lease_generation=retained_lease["lease_generation"], + fencing_token=retained_lease["fencing_token"], observed_state_root=H("a"), action_digest=H("b"), + timestamp_ms="4000", nonce="nonce-expired-00001", + ) + self.assertEqual(expired["receipt_kind"], "LEASE_EXPIRED") + self.assertEqual(expired["receipt_body"]["before_state_root"], expired["receipt_body"]["after_state_root"]) + authority._store.close() + + other = create_authority(Path(self.temporary.name) / "revoke.sqlite3") + issued = issue(other) + other.update_observed_time("1300") + lease = other.current_lease(bindings()) + revoked = other.revoke_lease( + bindings=bindings(), lease_id=lease["lease_id"], lease_generation=lease["lease_generation"], + fencing_token=lease["fencing_token"], observed_state_root=H("a"), action_digest=H("b"), + timestamp_ms="1300", nonce="nonce-revoked-00001", + ) + self.assertEqual(revoked["receipt_kind"], "LEASE_REVOKED") + self.assertEqual(revoked["receipt_body"]["before_state_root"], revoked["receipt_body"]["after_state_root"]) + other._store.close() + + def test_11_cancel_and_fail_are_terminal_unchanged_receipts(self): + for method_name, expected_kind, suffix in ( + ("cancel_mutation", "MUTATION_CANCELLED", "cancel"), + ("fail_mutation", "MUTATION_FAILED", "failure"), + ): + with self.subTest(kind=expected_kind): + path = Path(self.temporary.name) / f"{suffix}.sqlite3" + authority = create_authority(path) + lease_receipt = issue(authority) + action = H("6" if suffix == "cancel" else "7") + admit(authority, lease_receipt, action=action, nonce=f"nonce-{suffix}-admit01") + authority.update_observed_time("1200") + lease = authority.current_lease(bindings()) + before = authority.canonical_state_root(bindings()) + terminal = getattr(authority, method_name)( + bindings=bindings(), lease_id=lease["lease_id"], lease_generation=lease["lease_generation"], + fencing_token=lease["fencing_token"], authority_receipt_hash=H("d"), + lease_authorization_receipt_hash=lease_receipt["receipt_id"], + observed_state_root=before, expected_state_root=before, action_digest=action, + timestamp_ms="1200", nonce=f"nonce-{suffix}-term-01", result_digest=H("8"), + ) + self.assertEqual(terminal["receipt_kind"], expected_kind) + self.assertEqual(terminal["receipt_body"]["before_state_root"], before) + self.assertEqual(terminal["receipt_body"]["after_state_root"], before) + self.assertEqual(authority.canonical_state_root(bindings()), before) + authority._store.close() + + def test_12_single_authority_concurrency_emits_one_issue_and_signed_denials(self): + authority = create_authority(self.path) + barrier = threading.Barrier(12) + receipts: list[dict] = [] + failures: list[BaseException] = [] + result_lock = threading.Lock() + + def worker(index: int) -> None: + try: + barrier.wait() + receipt = issue( + authority, + lease_id=f"{index + 1:064x}", + nonce=f"nonce-concurrent-{index:03d}", + ) + with result_lock: + receipts.append(receipt) + except BaseException as exc: # pragma: no cover - asserted empty + with result_lock: + failures.append(exc) + + threads = [threading.Thread(target=worker, args=(index,)) for index in range(12)] + for thread in threads: + thread.start() + for thread in threads: + thread.join() + self.assertEqual(failures, []) + self.assertEqual(sum(item["receipt_kind"] == "LEASE_ISSUED" for item in receipts), 1) + self.assertEqual(sum(item["receipt_kind"] == "LEASE_ISSUANCE_DENIED" for item in receipts), 11) + self.assertEqual(len({item["receipt_id"] for item in receipts}), 12) + authority._store.close() + + def test_13_two_store_instances_use_compare_and_append(self): + registry = verify_registry(build_registry()) + store_a = SQLiteReceiptStore(self.path) + store_b = SQLiteReceiptStore(self.path) + base = { + "receipt_sequence": "0", "actor_identity_root": H("1"), "session_identity_root": H("2"), + "workspace_identity_root": H("3"), "holon_identity_root": H("4"), + "authority_domain": "repository:mutation", "authority_level": "D2", + "authority_receipt_hash": ZERO_HASH, "lease_id": H("5"), "lease_generation": "1", + "fencing_token": H("6"), "lease_authorization_receipt_hash": ZERO_HASH, + "parent_receipt_hash": ZERO_HASH, "observed_state_root": H("a"), "expected_state_root": H("a"), + "action_digest": H("b"), "before_state_root": H("a"), "after_state_root": H("a"), + "result_digest": H("c"), "timestamp_ms": "1000", "expires_at_ms": "3000", + "nonce": "nonce-store-cas-001", "outcome": "ADMITTED", "denial_codes": [], + } + first = sign_receipt( + receipt_kind="LEASE_ISSUED", receipt_body=base, registry=registry, + signer_key_id=SIGNER_KEY_ID, signer_private_key_hex=SIGNER_PRIVATE, + ) + competing_body = copy.deepcopy(base) + competing_body["lease_id"] = H("7") + competing_body["nonce"] = "nonce-store-cas-002" + competing = sign_receipt( + receipt_kind="LEASE_ISSUED", receipt_body=competing_body, registry=registry, + signer_key_id=SIGNER_KEY_ID, signer_private_key_hex=SIGNER_PRIVATE, + ) + store_a.persist_receipt(first, registry=registry, verification_time_ms="1000") + with self.assertRaises(ReceiptStoreConflict): + store_b.persist_receipt(competing, registry=registry, verification_time_ms="1000") + store_a.close(); store_b.close() + + def test_14_readback_failure_rolls_back_without_orphan_promotion(self): + class ReadbackFailStore(SQLiteReceiptStore): + def _read_pending_receipt_bytes(self, receipt_id: str): + return None + + registry = build_registry() + store = ReadbackFailStore(self.path) + authority = AuthoritativeReceiptAuthority( + store=store, current_registry=registry, pinned_operator_public_key_hex=OPERATOR_PUBLIC, + expected_operator_key_id=OPERATOR_KEY_ID, expected_registry_root=registry["registry_root"], + signer_key_id=SIGNER_KEY_ID, signer_private_key_hex=SIGNER_PRIVATE, verification_time_ms="1000", + ) + with self.assertRaisesRegex(AuthoritativeReceiptError, "RECEIPT_READBACK_MISMATCH"): + issue(authority) + self.assertEqual(authority.head_receipt_id, ZERO_HASH) + self.assertIsNone(authority.canonical_state_root(bindings())) + store.close() + recovered = create_authority(self.path, registry) + self.assertIsNone(recovered.canonical_state_root(bindings())) + self.assertEqual(recovered.head_receipt_id, ZERO_HASH) + self.assertEqual(recovered._store.read_all_receipts(), ()) + recovered._store.close() + + def test_15_key_rotation_and_historical_restart_verification(self): + first_registry = build_registry(expires_at_ms="20000", key_expires_at_ms="15000") + authority = create_authority(self.path, first_registry, verification_time_ms="1000") + issued = issue(authority) + second_registry = build_registry( + version="2", previous_root=first_registry["registry_root"], key_id=ROTATED_KEY_ID, + public_key=ROTATED_PUBLIC, verifier_root=H("8"), issued_at_ms="1300", + valid_from_ms="1300", expires_at_ms="20000", key_valid_from_ms="1300", + key_expires_at_ms="19000", + ) + verified_second = verify_trust_registry( + second_registry, pinned_operator_public_key_hex=OPERATOR_PUBLIC, + expected_operator_key_id=OPERATOR_KEY_ID, expected_registry_root=second_registry["registry_root"], + verification_time_ms="1400", + ) + verify_registry_rotation(verify_registry(first_registry, now="1000"), verified_second) + authority.rotate_registry( + second_registry, expected_registry_root=second_registry["registry_root"], verification_time_ms="1400", + signer_key_id=ROTATED_KEY_ID, signer_private_key_hex=ROTATED_PRIVATE, + ) + authority._store.close() + recovered = create_authority( + self.path, second_registry, expected_registry_root=second_registry["registry_root"], + signer_key_id=ROTATED_KEY_ID, signer_private_key_hex=ROTATED_PRIVATE, verification_time_ms="1400", + ) + self.assertEqual(recovered.head_receipt_id, issued["receipt_id"]) + self.assertEqual(recovered.current_lease(bindings())["lease_id"], H("5")) + recovered._store.close() + + def test_16_raw_sqlite_tamper_fails_restart(self): + registry = build_registry() + authority = create_authority(self.path, registry) + receipt = issue(authority) + authority._store.close() + connection = sqlite3.connect(self.path) + document = copy.deepcopy(receipt) + document["receipt_body"]["nonce"] = "nonce-database-tamper" + connection.execute( + "UPDATE receipts SET canonical = ? WHERE receipt_id = ?", + (sqlite3.Binary(canon(document)), receipt["receipt_id"]), + ) + connection.commit(); connection.close() + store = SQLiteReceiptStore(self.path) + try: + with self.assertRaisesRegex(AuthoritativeReceiptError, "RECEIPT_STORED_BYTES_INVALID"): + AuthoritativeReceiptAuthority( + store=store, current_registry=registry, pinned_operator_public_key_hex=OPERATOR_PUBLIC, + expected_operator_key_id=OPERATOR_KEY_ID, expected_registry_root=registry["registry_root"], + signer_key_id=SIGNER_KEY_ID, signer_private_key_hex=SIGNER_PRIVATE, + verification_time_ms="8000", + ) + finally: + store.close() + + def test_17_trusted_time_is_explicit_monotonic_and_expires_lease_views(self): + authority = create_authority(self.path) + issue(authority, expires="1500") + authority.update_observed_time("1499") + self.assertIsNotNone(authority.current_lease(bindings())) + authority.update_observed_time("1500") + self.assertIsNone(authority.current_lease(bindings())) + with self.assertRaisesRegex(AuthoritativeReceiptError, "OBSERVED_TIME_REGRESSION"): + authority.update_observed_time("1499") + self.assertEqual(authority._verification_time_ms, "1500") + authority._store.close() + + def test_18_failed_rotation_rolls_back_key_registry_and_observed_time(self): + first = build_registry(expires_at_ms="20000", key_expires_at_ms="15000") + authority = create_authority(self.path, first) + second = build_registry( + version="2", previous_root=first["registry_root"], key_id=ROTATED_KEY_ID, + public_key=ROTATED_PUBLIC, verifier_root=H("8"), issued_at_ms="1300", + valid_from_ms="1300", expires_at_ms="20000", key_valid_from_ms="1300", + key_expires_at_ms="19000", + ) + with self.assertRaisesRegex(AuthoritativeReceiptError, "RECEIPT_SIGNING_KEY_MISMATCH"): + authority.rotate_registry( + second, expected_registry_root=second["registry_root"], verification_time_ms="1400", + signer_key_id=ROTATED_KEY_ID, signer_private_key_hex=SIGNER_PRIVATE, + ) + self.assertEqual(authority._current_registry.registry_root, first["registry_root"]) + self.assertEqual(authority._signer_key_id, SIGNER_KEY_ID) + self.assertEqual(authority._verification_time_ms, "1000") + authority.update_observed_time("1400") + receipt = issue(authority, lease_id=H("6"), nonce="nonce-after-rollback") + self.assertEqual(receipt["proof"]["trust_registry_root"], first["registry_root"]) + authority._store.close() + + def test_19_cancel_and_failure_resolve_admission_after_lease_closure(self): + cases = (("expire", "cancel_mutation"), ("revoke", "fail_mutation")) + for index, (closure, terminal_method) in enumerate(cases): + with self.subTest(closure=closure, terminal=terminal_method): + path = Path(self.temporary.name) / f"closed-{index}.sqlite3" + authority = create_authority(path) + lease_receipt = issue(authority) + action = H("6" if index == 0 else "7") + admit(authority, lease_receipt, action=action, nonce=f"nonce-closed-admit-{index}") + retained = dict(authority.current_lease(bindings())) + if closure == "expire": + authority.update_observed_time("3000") + authority.expire_lease( + bindings=bindings(), lease_id=retained["lease_id"], + lease_generation=retained["lease_generation"], fencing_token=retained["fencing_token"], + observed_state_root=H("a"), action_digest=H("b"), timestamp_ms="3000", + nonce=f"nonce-closed-expire-{index}", + ) + terminal_time = "3100" + else: + authority.update_observed_time("1200") + authority.revoke_lease( + bindings=bindings(), lease_id=retained["lease_id"], + lease_generation=retained["lease_generation"], fencing_token=retained["fencing_token"], + observed_state_root=H("a"), action_digest=H("b"), timestamp_ms="1200", + nonce=f"nonce-closed-revoke-{index}", + ) + terminal_time = "1300" + authority.update_observed_time(terminal_time) + terminal = getattr(authority, terminal_method)( + bindings=bindings(), lease_id=retained["lease_id"], + lease_generation=retained["lease_generation"], fencing_token=retained["fencing_token"], + authority_receipt_hash=H("d"), lease_authorization_receipt_hash=lease_receipt["receipt_id"], + observed_state_root=H("a"), expected_state_root=H("a"), action_digest=action, + timestamp_ms=terminal_time, nonce=f"nonce-closed-terminal-{index}", result_digest=H("8"), + ) + self.assertIn(terminal["receipt_kind"], ("MUTATION_CANCELLED", "MUTATION_FAILED")) + self.assertEqual(terminal["receipt_body"]["before_state_root"], H("a")) + self.assertEqual(terminal["receipt_body"]["after_state_root"], H("a")) + self.assertEqual(authority.canonical_state_root(bindings()), H("a")) + authority._store.close() + + def test_20_registry_downgrade_receipt_is_rejected_during_restart(self): + first = build_registry(expires_at_ms="20000", key_expires_at_ms="15000") + authority = create_authority(self.path, first) + issue(authority) + second = build_registry( + version="2", previous_root=first["registry_root"], key_id=ROTATED_KEY_ID, + public_key=ROTATED_PUBLIC, verifier_root=H("8"), issued_at_ms="1300", + valid_from_ms="1300", expires_at_ms="20000", key_valid_from_ms="1300", + key_expires_at_ms="19000", + ) + authority.rotate_registry( + second, expected_registry_root=second["registry_root"], verification_time_ms="1400", + signer_key_id=ROTATED_KEY_ID, signer_private_key_hex=ROTATED_PRIVATE, + ) + authority.update_observed_time("1500") + current = authority.issue_lease( + bindings=bindings(), lease_id=H("6"), observed_state_root=H("a"), expected_state_root=H("a"), + action_digest=H("7"), timestamp_ms="1500", expires_at_ms="4000", nonce="nonce-v2-receipt-001", + ) + old_body = copy.deepcopy(current["receipt_body"]) + old_body.update({ + "receipt_sequence": "2", "parent_receipt_hash": current["receipt_id"], + "lease_id": H("8"), "action_digest": H("9"), "timestamp_ms": "1600", + "nonce": "nonce-downgrade-001", + }) + old_registry = verify_registry(first, now="1600") + downgrade = sign_receipt( + receipt_kind="LEASE_ISSUANCE_DENIED", receipt_body=old_body, registry=old_registry, + signer_key_id=SIGNER_KEY_ID, signer_private_key_hex=SIGNER_PRIVATE, + ) + authority._store.persist_receipt(downgrade, registry=old_registry, verification_time_ms="1600") + authority._store.close() + store = SQLiteReceiptStore(self.path) + try: + with self.assertRaisesRegex(AuthoritativeReceiptError, "RECEIPT_TRUST_REGISTRY_DOWNGRADE"): + AuthoritativeReceiptAuthority( + store=store, current_registry=second, pinned_operator_public_key_hex=OPERATOR_PUBLIC, + expected_operator_key_id=OPERATOR_KEY_ID, expected_registry_root=second["registry_root"], + signer_key_id=ROTATED_KEY_ID, signer_private_key_hex=ROTATED_PRIVATE, + verification_time_ms="1600", + ) + finally: + store.close() + + def test_21_workspace_holon_scope_isolation_and_registry_capability_immutability(self): + authority = create_authority(self.path) + issue(authority) + other = ReceiptBindings( + actor_identity_root=H("1"), session_identity_root=H("2"), workspace_identity_root=H("8"), + holon_identity_root=H("9"), authority_domain="repository:mutation", authority_level="D2", + ) + second = authority.issue_lease( + bindings=other, lease_id=H("6"), observed_state_root=H("e"), expected_state_root=H("e"), + action_digest=H("7"), timestamp_ms="1000", expires_at_ms="3000", nonce="nonce-other-scope-01", + ) + self.assertEqual(second["receipt_kind"], "LEASE_ISSUED") + self.assertEqual(authority.canonical_state_root(bindings()), H("a")) + self.assertEqual(authority.canonical_state_root(other), H("e")) + self.assertIsNotNone(authority.current_lease(bindings())) + self.assertIsNotNone(authority.current_lease(other)) + verified = verify_registry(build_registry()) + mutable_copy = verified.document + mutable_copy["registry_body"]["keys"][0]["status"] = "REVOKED" + self.assertEqual(verified.entries[SIGNER_KEY_ID]["status"], "ACTIVE") + with self.assertRaises(FrozenInstanceError): + verified.registry_root = H("9") + authority._store.close() + + def test_22_unresolved_roots_and_unsigned_store_appends_are_rejected(self): + registry = verify_registry(build_registry(), now="1000") + vector = build_golden_vector()["receipts"][0] + unresolved = copy.deepcopy(vector) + unresolved["receipt_body"]["expected_state_root"] = ZERO_HASH + with self.assertRaisesRegex(AuthoritativeReceiptError, "expected_state_root:UNRESOLVED"): + sign_receipt( + receipt_kind=unresolved["receipt_kind"], receipt_body=unresolved["receipt_body"], + registry=registry, signer_key_id=SIGNER_KEY_ID, signer_private_key_hex=SIGNER_PRIVATE, + ) + unsigned = copy.deepcopy(vector) + unsigned["proof"]["signature"] = "0" * 128 + unsigned["receipt_id"] = compute_receipt_id(unsigned) + store = SQLiteReceiptStore(self.path) + try: + with self.assertRaisesRegex(AuthoritativeReceiptError, "RECEIPT_SIGNATURE_INVALID"): + store.persist_receipt(unsigned, registry=registry, verification_time_ms="1000") + self.assertEqual(store.read_all_receipts(), ()) + finally: + store.close() + + def test_23_python_independently_verifies_and_replays_typescript_golden_vector(self): + vector_path = ( + REPO_ROOT + / "sovereign-omega-v2" + / "test" + / "vectors" + / "typescript-cross-runtime-receipt-v1.json" + ) + vector = load_json_strict(vector_path.read_bytes()) + registry_document = vector["registry"] + context = vector["context"] + registry = verify_trust_registry( + registry_document, + pinned_operator_public_key_hex=vector["operator_public_key"], + expected_operator_key_id=context["operator_key_id"], + expected_registry_root=registry_document["registry_root"], + expected_registry_version=registry_document["registry_body"]["registry_version"], + verification_time_ms=context["observed_at_ms"], + max_clock_skew_ms=context["max_clock_skew_ms"], + ) + previous = ZERO_HASH + for sequence, receipt in enumerate(vector["receipts"]): + verified = verify_receipt( + receipt, + registry=registry, + verification_time_ms=context["observed_at_ms"], + max_clock_skew_ms=context["max_clock_skew_ms"], + ) + self.assertEqual(verified["receipt_id"], compute_receipt_id(verified)) + self.assertEqual(verified["receipt_body"]["receipt_sequence"], str(sequence)) + self.assertEqual(verified["receipt_body"]["parent_receipt_hash"], previous) + previous = verified["receipt_id"] + self.assertEqual(previous, vector["terminal_receipt_id"]) + + store = SQLiteReceiptStore(self.path) + store.persist_registry(registry.document) + for receipt in vector["receipts"]: + store.persist_receipt( + receipt, + registry=registry, + verification_time_ms=context["observed_at_ms"], + max_clock_skew_ms=context["max_clock_skew_ms"], + ) + store.close() + + recovered = AuthoritativeReceiptAuthority( + store=SQLiteReceiptStore(self.path), + current_registry=registry_document, + pinned_operator_public_key_hex=vector["operator_public_key"], + expected_operator_key_id=context["operator_key_id"], + expected_registry_root=registry_document["registry_root"], + signer_key_id=registry_document["registry_body"]["keys"][0]["key_id"], + signer_private_key_hex=SIGNER_PRIVATE, + verification_time_ms=context["observed_at_ms"], + max_clock_skew_ms=context["max_clock_skew_ms"], + ) + recovered_bindings = ReceiptBindings( + actor_identity_root=context["expected_actor_identity_root"], + session_identity_root=context["expected_session_identity_root"], + workspace_identity_root=context["expected_workspace_identity_root"], + holon_identity_root=context["expected_holon_identity_root"], + authority_domain=context["expected_authority_domain"], + authority_level=context["expected_authority_level"], + ) + self.assertEqual(recovered.head_receipt_id, vector["terminal_receipt_id"]) + self.assertEqual( + recovered.canonical_state_root(recovered_bindings), + context["expected_observed_state_root"], + ) + self.assertIsNone(recovered.current_lease(recovered_bindings)) + recovered._store.close() + + def test_24_every_receipt_kind_survives_persisted_restart_readback(self): + observed_kinds: set[str] = set() + active_after_restart = { + "LEASE_ISSUED", + "LEASE_RENEWED", + "LEASE_RENEWAL_DENIED", + "MUTATION_ADMITTED", + "MUTATION_DENIED", + } + for index, target_kind in enumerate(ALL_KINDS): + with self.subTest(receipt_kind=target_kind): + path = Path(self.temporary.name) / f"all-kinds-{index}.sqlite3" + registry = build_registry() + authority = create_authority(path, registry) + final_time = "1000" + + if target_kind == "LEASE_ISSUANCE_DENIED": + target = issue( + authority, + lease_id=H("6"), + expires="900", + nonce=f"nonce-all-kind-{index:02d}", + ) + else: + lease_receipt = issue( + authority, + nonce=f"nonce-all-lease-{index:02d}", + ) + target = lease_receipt + + if target_kind in ("LEASE_RENEWED", "LEASE_RENEWAL_DENIED"): + authority.update_observed_time("1200") + final_time = "1200" + lease = authority.current_lease(bindings()) + assert lease is not None + target = authority.renew_lease( + bindings=bindings(), + lease_id=lease["lease_id"], + lease_generation=lease["lease_generation"], + fencing_token=( + lease["fencing_token"] + if target_kind == "LEASE_RENEWED" + else H("9") + ), + observed_state_root=H("a"), + expected_state_root=H("a"), + action_digest=H("b"), + timestamp_ms="1200", + expires_at_ms="4000", + nonce=f"nonce-all-renew-{index:02d}", + ) + elif target_kind == "LEASE_EXPIRED": + lease = authority.current_lease(bindings()) + assert lease is not None + authority.update_observed_time("3000") + final_time = "3000" + target = authority.expire_lease( + bindings=bindings(), + lease_id=lease["lease_id"], + lease_generation=lease["lease_generation"], + fencing_token=lease["fencing_token"], + observed_state_root=H("a"), + action_digest=H("b"), + timestamp_ms="3000", + nonce=f"nonce-all-expire-{index:02d}", + ) + elif target_kind == "LEASE_REVOKED": + authority.update_observed_time("1200") + final_time = "1200" + lease = authority.current_lease(bindings()) + assert lease is not None + target = authority.revoke_lease( + bindings=bindings(), + lease_id=lease["lease_id"], + lease_generation=lease["lease_generation"], + fencing_token=lease["fencing_token"], + observed_state_root=H("a"), + action_digest=H("b"), + timestamp_ms="1200", + nonce=f"nonce-all-revoke-{index:02d}", + ) + elif target_kind == "MUTATION_ADMITTED": + final_time = "1100" + target = admit( + authority, + lease_receipt, + nonce=f"nonce-all-admit-{index:02d}", + ) + elif target_kind == "MUTATION_DENIED": + final_time = "1100" + target = admit( + authority, + lease_receipt, + action=H("6"), + expected=H("9"), + nonce=f"nonce-all-deny-{index:02d}", + ) + elif target_kind == "MUTATION_COMPLETED": + final_time = "1200" + admit( + authority, + lease_receipt, + nonce=f"nonce-all-complete-admit-{index:02d}", + ) + target = complete( + authority, + lease_receipt, + nonce=f"nonce-all-complete-{index:02d}", + ) + elif target_kind in ("MUTATION_CANCELLED", "MUTATION_FAILED"): + action = H("6" if target_kind == "MUTATION_CANCELLED" else "7") + admit( + authority, + lease_receipt, + action=action, + nonce=f"nonce-all-terminal-admit-{index:02d}", + ) + authority.update_observed_time("1200") + final_time = "1200" + lease = authority.current_lease(bindings()) + assert lease is not None + method = ( + authority.cancel_mutation + if target_kind == "MUTATION_CANCELLED" + else authority.fail_mutation + ) + target = method( + bindings=bindings(), + lease_id=lease["lease_id"], + lease_generation=lease["lease_generation"], + fencing_token=lease["fencing_token"], + authority_receipt_hash=H("d"), + lease_authorization_receipt_hash=lease_receipt["receipt_id"], + observed_state_root=H("a"), + expected_state_root=H("a"), + action_digest=action, + timestamp_ms="1200", + nonce=f"nonce-all-terminal-{index:02d}", + result_digest=H("8"), + ) + + self.assertEqual(target["receipt_kind"], target_kind) + observed_kinds.add(target_kind) + authority._store.close() + + recovered = create_authority( + path, + registry, + verification_time_ms=final_time, + ) + self.assertEqual(recovered._store.read_receipt(target["receipt_id"]), target) + self.assertIn( + target["receipt_id"], + {item["receipt_id"] for item in recovered._store.read_all_receipts()}, + ) + expected_state = ( + None + if target_kind == "LEASE_ISSUANCE_DENIED" + else H("e") if target_kind == "MUTATION_COMPLETED" else H("a") + ) + self.assertEqual(recovered.canonical_state_root(bindings()), expected_state) + if target_kind in active_after_restart: + self.assertIsNotNone(recovered.current_lease(bindings())) + else: + self.assertIsNone(recovered.current_lease(bindings())) + recovered._store.close() + + self.assertEqual(observed_kinds, set(ALL_KINDS)) + + def test_25_python_and_typescript_generators_match_committed_all_kind_vectors(self): + vector_root = REPO_ROOT / "sovereign-omega-v2" / "test" / "vectors" + python_bytes = ( + vector_root / "python-cross-runtime-receipt-v1.json" + ).read_bytes() + typescript_bytes = ( + vector_root / "typescript-cross-runtime-receipt-v1.json" + ).read_bytes() + regenerated_python_bytes = ( + canon(build_python_cross_runtime_vector()) + b"\n" + ) + + self.assertEqual(python_bytes, regenerated_python_bytes) + self.assertEqual(typescript_bytes, regenerated_python_bytes) + vector = load_json_strict(regenerated_python_bytes) + self.assertEqual( + {receipt["receipt_kind"] for receipt in vector["receipts"]}, + set(ALL_KINDS), + ) + + def test_26_backdated_timestamp_cannot_revive_expired_lease(self): + authority = create_authority(self.path) + issued = issue(authority, expires="1500") + lease = authority.current_lease(bindings()) + assert lease is not None + before = authority.canonical_state_root(bindings()) + + authority.update_observed_time("1500") + denied = authority.admit_mutation( + bindings=bindings(), + lease_id=lease["lease_id"], + lease_generation=lease["lease_generation"], + fencing_token=lease["fencing_token"], + authority_receipt_hash=H("d"), + lease_authorization_receipt_hash=issued["receipt_id"], + observed_state_root=H("a"), + expected_state_root=H("a"), + action_digest=H("6"), + timestamp_ms="1100", + nonce="nonce-backdated-exp1", + ) + + self.assertEqual(denied["receipt_kind"], "MUTATION_DENIED") + self.assertIn("LEASE_EXPIRED", denied["receipt_body"]["denial_codes"]) + self.assertEqual(denied["receipt_body"]["before_state_root"], before) + self.assertEqual(denied["receipt_body"]["after_state_root"], before) + self.assertEqual(authority.canonical_state_root(bindings()), before) + authority._store.close() + + def test_27_registry_readback_failure_rolls_back_without_partial_persistence(self): + class MissingRegistryReadbackStore(SQLiteReceiptStore): + def _read_pending_registry_bytes(self, registry_root): + del registry_root + return None + + registry = build_registry() + store = MissingRegistryReadbackStore(self.path) + with self.assertRaisesRegex(AuthoritativeReceiptError, "TRUST_REGISTRY_READBACK_MISMATCH"): + AuthoritativeReceiptAuthority( + store=store, + current_registry=registry, + pinned_operator_public_key_hex=OPERATOR_PUBLIC, + expected_operator_key_id=OPERATOR_KEY_ID, + expected_registry_root=registry["registry_root"], + signer_key_id=SIGNER_KEY_ID, + signer_private_key_hex=SIGNER_PRIVATE, + verification_time_ms="1000", + ) + self.assertIsNone(store.read_registry(registry["registry_root"])) + store.close() + + +if __name__ == "__main__": + main() diff --git a/sovereign-omega-v2/scripts/generate-authoritative-receipt-vector.ts b/sovereign-omega-v2/scripts/generate-authoritative-receipt-vector.ts new file mode 100644 index 000000000..c909a3164 --- /dev/null +++ b/sovereign-omega-v2/scripts/generate-authoritative-receipt-vector.ts @@ -0,0 +1,473 @@ +#!/usr/bin/env tsx +/** Generate the deterministic TypeScript cross-runtime receipt golden vector. */ + +import { mkdirSync, writeFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { pathToFileURL } from 'node:url' +import { canonicalizeJCS } from '../src/core/canonicalize.js' +import { generateKeypair } from '../src/consensus/crypto.js' +import { hexToUint8Array, sha256Hex } from '../src/core/hashing.js' +import type { SHA256Hex } from '../src/core/types.js' +import { + CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + buildCrossRuntimeReceiptEnvelopeV1, + buildReceiptTrustRegistryV1, + type CrossRuntimeReceiptBodyV1, + type CrossRuntimeReceiptDraftV1, + type CrossRuntimeReceiptEnvelopeV1, + type CrossRuntimeReceiptKindV1, + type CrossRuntimeReceiptOutcomeV1, +} from '../src/provenance/cross-runtime-receipts.js' + +const OPERATOR_KEY_ID = 'operator-root-v1' +const OPERATOR_PRIVATE = + '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60' +const OPERATOR_PUBLIC = + 'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a' +const SIGNER_KEY_ID = 'cross-runtime-witness-v1' +const SIGNER_PRIVATE = + '4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb' +const SIGNER_PUBLIC = + '3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c' +const AUTHORITY_DOMAIN = 'repository:mutation' +const H = (character: string): SHA256Hex => character.repeat(64) as SHA256Hex +const ZERO = H('0') +const ALL_RECEIPT_KINDS = [ + 'LEASE_ISSUED', + 'LEASE_ISSUANCE_DENIED', + 'LEASE_RENEWED', + 'LEASE_RENEWAL_DENIED', + 'LEASE_EXPIRED', + 'LEASE_REVOKED', + 'MUTATION_ADMITTED', + 'MUTATION_DENIED', + 'MUTATION_COMPLETED', + 'MUTATION_CANCELLED', + 'MUTATION_FAILED', +].sort() as CrossRuntimeReceiptKindV1[] + +interface BodyInput { + readonly kind: CrossRuntimeReceiptKindV1 + readonly outcome: CrossRuntimeReceiptOutcomeV1 + readonly leaseId: SHA256Hex + readonly leaseGeneration: string + readonly fencingToken: SHA256Hex + readonly authorityReceiptHash: SHA256Hex + readonly leaseAuthorizationReceiptHash: SHA256Hex + readonly observedStateRoot: SHA256Hex + readonly expectedStateRoot: SHA256Hex + readonly actionDigest: SHA256Hex + readonly afterStateRoot?: SHA256Hex + readonly resultDigest?: SHA256Hex + readonly timestampMs: string + readonly expiresAtMs: string + readonly nonce: string + readonly denialCodes?: readonly string[] +} + +async function domainHash(domain: string, value: unknown): Promise { + return sha256Hex(canonicalizeJCS({ domain, value })) +} + +async function receiptResultDigest( + kind: CrossRuntimeReceiptKindV1, + outcome: CrossRuntimeReceiptOutcomeV1, + denialCodes: readonly string[], + nonce: string, +): Promise { + return domainHash('AEGIS_AUTHORITATIVE_RECEIPT_RESULT_V1', { + receipt_kind: kind, + outcome, + denial_codes: denialCodes, + nonce, + }) +} + +export async function buildTypeScriptCrossRuntimeVectorV1(): Promise { + const operator = await generateKeypair(hexToUint8Array(OPERATOR_PRIVATE)) + const signer = await generateKeypair(hexToUint8Array(SIGNER_PRIVATE)) + if (operator.publicKey !== OPERATOR_PUBLIC || signer.publicKey !== SIGNER_PUBLIC) { + throw new Error('RFC 8032 fixture key derivation mismatch') + } + const registry = await buildReceiptTrustRegistryV1({ + registry_version: '1', + previous_registry_root: ZERO, + issued_at_ms: '90', + valid_from_ms: '100', + expires_at_ms: '10000', + operator_key_id: OPERATOR_KEY_ID, + keys: [{ + key_id: SIGNER_KEY_ID, + public_key: signer.publicKey, + verifier_identity_root: H('7'), + valid_from_ms: '100', + expires_at_ms: '9000', + status: 'ACTIVE', + authority_domains: [AUTHORITY_DOMAIN], + receipt_kinds: ALL_RECEIPT_KINDS, + }], + }, operator.privateKey) + const proof: CrossRuntimeReceiptDraftV1['proof'] = { + algorithm: 'Ed25519', + signer_key_id: SIGNER_KEY_ID, + verifier_identity_root: H('7'), + trust_registry_version: '1', + trust_registry_root: registry.registry_root, + } + const receipts: CrossRuntimeReceiptEnvelopeV1[] = [] + + async function append(input: BodyInput): Promise { + const denialCodes = [...(input.denialCodes ?? [])].sort() + const body: CrossRuntimeReceiptBodyV1 = { + receipt_sequence: String(receipts.length), + actor_identity_root: H('1'), + session_identity_root: H('2'), + workspace_identity_root: H('3'), + holon_identity_root: H('4'), + authority_domain: AUTHORITY_DOMAIN, + authority_level: 'D2', + authority_receipt_hash: input.authorityReceiptHash, + lease_id: input.leaseId, + lease_generation: input.leaseGeneration, + fencing_token: input.fencingToken, + lease_authorization_receipt_hash: input.leaseAuthorizationReceiptHash, + parent_receipt_hash: receipts.at(-1)?.receipt_id ?? ZERO, + observed_state_root: input.observedStateRoot, + expected_state_root: input.expectedStateRoot, + action_digest: input.actionDigest, + before_state_root: input.observedStateRoot, + after_state_root: input.afterStateRoot ?? input.observedStateRoot, + result_digest: input.resultDigest ?? await receiptResultDigest( + input.kind, + input.outcome, + denialCodes, + input.nonce, + ), + timestamp_ms: input.timestampMs, + expires_at_ms: input.expiresAtMs, + nonce: input.nonce, + outcome: input.outcome, + denial_codes: denialCodes, + } + const receipt = await buildCrossRuntimeReceiptEnvelopeV1({ + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + receipt_kind: input.kind, + receipt_body: body, + proof, + }, signer.privateKey) + receipts.push(receipt) + return receipt + } + + await append({ + kind: 'LEASE_ISSUANCE_DENIED', + outcome: 'DENIED', + leaseId: H('4'), + leaseGeneration: '1', + fencingToken: ZERO, + authorityReceiptHash: ZERO, + leaseAuthorizationReceiptHash: ZERO, + observedStateRoot: H('f'), + expectedStateRoot: H('f'), + actionDigest: H('1'), + timestampMs: '1000', + expiresAtMs: '900', + nonce: 'vector-lease-denied-01', + denialCodes: ['LEASE_EXPIRY_INVALID'], + }) + + const leaseOneFence = await domainHash('AEGIS_AUTHORITATIVE_FENCE_V1', { + authority_domain: AUTHORITY_DOMAIN, + lease_id: H('5'), + lease_generation: '1', + parent_receipt_hash: receipts.at(-1)!.receipt_id, + nonce: 'vector-lease-issued-01', + }) + await append({ + kind: 'LEASE_ISSUED', + outcome: 'ADMITTED', + leaseId: H('5'), + leaseGeneration: '1', + fencingToken: leaseOneFence, + authorityReceiptHash: ZERO, + leaseAuthorizationReceiptHash: ZERO, + observedStateRoot: H('a'), + expectedStateRoot: H('a'), + actionDigest: H('b'), + timestampMs: '1100', + expiresAtMs: '3000', + nonce: 'vector-lease-issued-01', + }) + await append({ + kind: 'LEASE_RENEWAL_DENIED', + outcome: 'DENIED', + leaseId: H('5'), + leaseGeneration: '0', + fencingToken: H('9'), + authorityReceiptHash: ZERO, + leaseAuthorizationReceiptHash: ZERO, + observedStateRoot: H('a'), + expectedStateRoot: H('a'), + actionDigest: H('b'), + timestampMs: '1200', + expiresAtMs: '4000', + nonce: 'vector-renew-denied-01', + denialCodes: ['STALE_FENCING_TOKEN', 'STALE_LEASE_GENERATION'], + }) + + const leaseTwoFence = await domainHash('AEGIS_AUTHORITATIVE_FENCE_V1', { + authority_domain: AUTHORITY_DOMAIN, + lease_id: H('5'), + lease_generation: '2', + parent_receipt_hash: receipts.at(-1)!.receipt_id, + nonce: 'vector-lease-renewed-1', + }) + const renewed = await append({ + kind: 'LEASE_RENEWED', + outcome: 'ADMITTED', + leaseId: H('5'), + leaseGeneration: '2', + fencingToken: leaseTwoFence, + authorityReceiptHash: ZERO, + leaseAuthorizationReceiptHash: ZERO, + observedStateRoot: H('a'), + expectedStateRoot: H('a'), + actionDigest: H('b'), + timestampMs: '1300', + expiresAtMs: '4000', + nonce: 'vector-lease-renewed-1', + }) + await append({ + kind: 'MUTATION_DENIED', + outcome: 'DENIED', + leaseId: H('5'), + leaseGeneration: '2', + fencingToken: leaseTwoFence, + authorityReceiptHash: H('c'), + leaseAuthorizationReceiptHash: renewed.receipt_id, + observedStateRoot: H('a'), + expectedStateRoot: H('a'), + actionDigest: H('d'), + resultDigest: H('4'), + timestampMs: '1400', + expiresAtMs: '4000', + nonce: 'vector-mutation-deny-01', + denialCodes: ['POLICY_DENIED'], + }) + await append({ + kind: 'MUTATION_ADMITTED', + outcome: 'ADMITTED', + leaseId: H('5'), + leaseGeneration: '2', + fencingToken: leaseTwoFence, + authorityReceiptHash: H('c'), + leaseAuthorizationReceiptHash: renewed.receipt_id, + observedStateRoot: H('a'), + expectedStateRoot: H('a'), + actionDigest: H('b'), + timestampMs: '1500', + expiresAtMs: '4000', + nonce: 'vector-mutation-admit-1', + }) + await append({ + kind: 'MUTATION_COMPLETED', + outcome: 'COMPLETED', + leaseId: H('5'), + leaseGeneration: '2', + fencingToken: leaseTwoFence, + authorityReceiptHash: H('c'), + leaseAuthorizationReceiptHash: renewed.receipt_id, + observedStateRoot: H('a'), + expectedStateRoot: H('a'), + actionDigest: H('b'), + afterStateRoot: H('e'), + resultDigest: H('f'), + timestampMs: '1600', + expiresAtMs: '4000', + nonce: 'vector-mutation-done-01', + }) + + const cancelFence = await domainHash('AEGIS_AUTHORITATIVE_FENCE_V1', { + authority_domain: AUTHORITY_DOMAIN, + lease_id: H('6'), + lease_generation: '3', + parent_receipt_hash: receipts.at(-1)!.receipt_id, + nonce: 'vector-cancel-lease-001', + }) + const cancelLease = await append({ + kind: 'LEASE_ISSUED', + outcome: 'ADMITTED', + leaseId: H('6'), + leaseGeneration: '3', + fencingToken: cancelFence, + authorityReceiptHash: ZERO, + leaseAuthorizationReceiptHash: ZERO, + observedStateRoot: H('e'), + expectedStateRoot: H('e'), + actionDigest: H('7'), + timestampMs: '1700', + expiresAtMs: '2000', + nonce: 'vector-cancel-lease-001', + }) + await append({ + kind: 'MUTATION_ADMITTED', + outcome: 'ADMITTED', + leaseId: H('6'), + leaseGeneration: '3', + fencingToken: cancelFence, + authorityReceiptHash: H('c'), + leaseAuthorizationReceiptHash: cancelLease.receipt_id, + observedStateRoot: H('e'), + expectedStateRoot: H('e'), + actionDigest: H('7'), + timestampMs: '1800', + expiresAtMs: '2000', + nonce: 'vector-cancel-admit-01', + }) + await append({ + kind: 'LEASE_EXPIRED', + outcome: 'EXPIRED', + leaseId: H('6'), + leaseGeneration: '3', + fencingToken: cancelFence, + authorityReceiptHash: ZERO, + leaseAuthorizationReceiptHash: ZERO, + observedStateRoot: H('e'), + expectedStateRoot: H('e'), + actionDigest: H('7'), + timestampMs: '2000', + expiresAtMs: '2000', + nonce: 'vector-lease-expired-1', + denialCodes: ['LEASE_EXPIRED'], + }) + await append({ + kind: 'MUTATION_CANCELLED', + outcome: 'CANCELLED', + leaseId: H('6'), + leaseGeneration: '3', + fencingToken: cancelFence, + authorityReceiptHash: H('c'), + leaseAuthorizationReceiptHash: cancelLease.receipt_id, + observedStateRoot: H('e'), + expectedStateRoot: H('e'), + actionDigest: H('7'), + resultDigest: H('8'), + timestampMs: '2100', + expiresAtMs: '2000', + nonce: 'vector-mutation-cancel1', + denialCodes: ['CANCELLED_AFTER_EXPIRY'], + }) + + const failureFence = await domainHash('AEGIS_AUTHORITATIVE_FENCE_V1', { + authority_domain: AUTHORITY_DOMAIN, + lease_id: H('9'), + lease_generation: '4', + parent_receipt_hash: receipts.at(-1)!.receipt_id, + nonce: 'vector-failure-lease-1', + }) + const failureLease = await append({ + kind: 'LEASE_ISSUED', + outcome: 'ADMITTED', + leaseId: H('9'), + leaseGeneration: '4', + fencingToken: failureFence, + authorityReceiptHash: ZERO, + leaseAuthorizationReceiptHash: ZERO, + observedStateRoot: H('e'), + expectedStateRoot: H('e'), + actionDigest: H('a'), + timestampMs: '2200', + expiresAtMs: '4000', + nonce: 'vector-failure-lease-1', + }) + await append({ + kind: 'MUTATION_ADMITTED', + outcome: 'ADMITTED', + leaseId: H('9'), + leaseGeneration: '4', + fencingToken: failureFence, + authorityReceiptHash: H('c'), + leaseAuthorizationReceiptHash: failureLease.receipt_id, + observedStateRoot: H('e'), + expectedStateRoot: H('e'), + actionDigest: H('a'), + timestampMs: '2300', + expiresAtMs: '4000', + nonce: 'vector-failure-admit-1', + }) + await append({ + kind: 'LEASE_REVOKED', + outcome: 'REVOKED', + leaseId: H('9'), + leaseGeneration: '4', + fencingToken: failureFence, + authorityReceiptHash: ZERO, + leaseAuthorizationReceiptHash: ZERO, + observedStateRoot: H('e'), + expectedStateRoot: H('e'), + actionDigest: H('a'), + timestampMs: '2400', + expiresAtMs: '4000', + nonce: 'vector-lease-revoked-1', + denialCodes: ['OPERATOR_REVOKED'], + }) + const terminal = await append({ + kind: 'MUTATION_FAILED', + outcome: 'FAILED', + leaseId: H('9'), + leaseGeneration: '4', + fencingToken: failureFence, + authorityReceiptHash: H('c'), + leaseAuthorizationReceiptHash: failureLease.receipt_id, + observedStateRoot: H('e'), + expectedStateRoot: H('e'), + actionDigest: H('a'), + resultDigest: H('b'), + timestampMs: '2500', + expiresAtMs: '4000', + nonce: 'vector-mutation-fail-01', + denialCodes: ['FAILED_AFTER_REVOCATION'], + }) + + const vector = { + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + operator_public_key: OPERATOR_PUBLIC, + registry, + receipts, + terminal_receipt_id: terminal.receipt_id, + context: { + operator_key_id: OPERATOR_KEY_ID, + accepted_registry_roots: [registry.registry_root], + observed_at_ms: '3000', + max_clock_skew_ms: '0', + expected_actor_identity_root: H('1'), + expected_session_identity_root: H('2'), + expected_workspace_identity_root: H('3'), + expected_holon_identity_root: H('4'), + expected_authority_domain: AUTHORITY_DOMAIN, + expected_authority_level: 'D2', + expected_observed_state_root: H('e'), + expected_action_digest: H('a'), + }, + } + return vector +} + +async function main(): Promise { + const outputFlag = process.argv.indexOf('--output') + if (outputFlag < 0 || process.argv[outputFlag + 1] === undefined) { + throw new Error('usage: generate-authoritative-receipt-vector.ts --output ') + } + const output = resolve(process.argv[outputFlag + 1]!) + const vector = await buildTypeScriptCrossRuntimeVectorV1() + mkdirSync(dirname(output), { recursive: true }) + writeFileSync(output, Buffer.from(canonicalizeJCS(vector))) + writeFileSync(output, '\n', { flag: 'a' }) +} + +if ( + process.argv[1] !== undefined + && import.meta.url === pathToFileURL(resolve(process.argv[1])).href +) { + await main() +} diff --git a/sovereign-omega-v2/src/metacognition/authoritative-outcome-evidence-replay.ts b/sovereign-omega-v2/src/metacognition/authoritative-outcome-evidence-replay.ts new file mode 100644 index 000000000..5cbe57f9f --- /dev/null +++ b/sovereign-omega-v2/src/metacognition/authoritative-outcome-evidence-replay.ts @@ -0,0 +1,289 @@ +// ============================================================ +// SOVEREIGN OMEGA - Authoritative Receipt Provenance Replay +// PROVENANCE ASSURANCE: T2->T3 implemented; comparator remains advisory +// +// Resolves an independently signed terminal receipt chain before allowing the +// existing authenticated outcome-evidence replay to reach persistence. This +// adapter verifies provenance bindings only. It grants no authority, executes +// no mutation, and does not change the comparator's epistemic status. +// ============================================================ + +import { assertIJsonValue } from '../core/i-json.js' +import { deepFreeze } from '../core/immutable.js' +import type { SequenceNumber, SHA256Hex } from '../core/types.js' +import type { + CrossRuntimeReceiptSourceV1, + CrossRuntimeReceiptVerificationDecisionV1, + TrustedReceiptResolutionContextV1, +} from '../provenance/receipt-resolver.js' +import { + resolveAndVerifyCrossRuntimeReceiptChainV1, + verifyCrossRuntimeReceiptVerificationDecisionDigestV1, +} from '../provenance/receipt-resolver.js' +import type { MetacognitiveLoop } from './loop.js' +import type { ReadableOutcomeEvidenceArtifactStore } from './outcome-evidence-artifact-store.js' +import type { + DurableTerminalStatus, + TerminalExecutionOutcome, +} from './outcome-comparator.js' +import { + replayAuthenticatedOutcomeEvidenceV1, +} from './outcome-evidence-replay.js' +import type { + OutcomeEvidenceReplayResultV1, + OutcomeReplayEvidenceV1, + TrustedOutcomeReplayContextV1, +} from './outcome-evidence-replay.js' + +export const AUTHORITATIVE_RECEIPT_PROVENANCE_VERIFIED = + 'AUTHORITATIVE_RECEIPT_PROVENANCE_VERIFIED' as const + +export interface AuthoritativeOutcomeEvidenceReplayResultV1 + extends OutcomeEvidenceReplayResultV1 { + readonly provenance_status: typeof AUTHORITATIVE_RECEIPT_PROVENANCE_VERIFIED + readonly provenance_decision: CrossRuntimeReceiptVerificationDecisionV1 +} + +export class AuthoritativeOutcomeEvidenceReplayError extends Error { + override readonly name = 'AuthoritativeOutcomeEvidenceReplayError' + + constructor(message: string) { + super(message) + Object.setPrototypeOf(this, new.target.prototype) + } +} + +/** + * Resolve authoritative receipt provenance first, bind it to the signed + * outcome bundle, and only then invoke the existing persistence-capable replay. + */ +export async function replayAuthoritativeOutcomeEvidenceV1( + loop: MetacognitiveLoop, + artifactStore: ReadableOutcomeEvidenceArtifactStore, + allocatedSequence: SequenceNumber, + trustedOutcomeContext: TrustedOutcomeReplayContextV1, + evidence: OutcomeReplayEvidenceV1, + receiptSource: CrossRuntimeReceiptSourceV1, + terminalReceiptId: SHA256Hex, + trustedReceiptContext: TrustedReceiptResolutionContextV1, +): Promise { + // This must remain the first fallible boundary: unverifiable provenance may + // never reach the outcome artifact store or advance the metacognitive loop. + const resolvedDecision = await resolveAndVerifyCrossRuntimeReceiptChainV1( + receiptSource, + terminalReceiptId, + trustedReceiptContext, + ) + const decision = await verifyCrossRuntimeReceiptVerificationDecisionDigestV1( + resolvedDecision, + ) + + const evidenceSnapshot = snapshotIJson(evidence, 'authoritative outcome replay evidence') + const terminal = evidenceSnapshot.input.terminal_execution + if (terminal === undefined) { + fail('authoritative receipt provenance requires terminal execution evidence') + } + + const expectedTerminal = expectedTerminalDisposition(decision) + const authority = evidenceSnapshot.input.authority + const baseline = evidenceSnapshot.input.baseline.snapshot + const proposal = evidenceSnapshot.input.baseline.proposal + const post = evidenceSnapshot.input.post_snapshot + + assertBinding( + decision.authority_level, + proposal.consequence_class, + 'receipt authority level and proposal consequence class', + ) + assertBinding( + decision.actor_identity_root, + authority.execution_identity_root, + 'receipt actor and authority execution identity', + ) + assertBinding( + decision.actor_identity_root, + terminal.execution_identity_root, + 'receipt actor and terminal execution identity', + ) + assertBinding( + decision.workspace_identity_root, + authority.workspace_binding, + 'receipt and authority workspace', + ) + assertBinding( + decision.workspace_identity_root, + terminal.workspace_binding, + 'receipt and terminal workspace', + ) + assertBinding( + decision.action_digest, + authority.requested_action_digest, + 'receipt and authority action', + ) + assertBinding( + decision.action_digest, + terminal.requested_action_digest, + 'receipt and terminal action', + ) + assertBinding( + 'ADMITTED', + authority.outcome, + 'legacy authority admission outcome', + ) + assertBinding( + 'ADMITTED', + terminal.lease_outcome, + 'legacy lease admission outcome', + ) + assertBinding( + decision.authority_receipt_hash, + authority.authority_receipt_root, + 'authoritative and legacy authority receipt', + ) + assertBinding( + decision.authority_receipt_hash, + terminal.authority_receipt_root, + 'authoritative and terminal authority receipt', + ) + assertBinding( + decision.lease_authorization_receipt_hash, + terminal.lease_authorization_receipt_root, + 'authoritative and legacy lease authorization receipt', + ) + assertBinding( + decision.before_state_root, + baseline.state_root, + 'receipt pre-state and evidence baseline', + ) + assertBinding( + decision.before_state_root, + terminal.pre_state_root, + 'receipt and terminal pre-state', + ) + assertBinding( + decision.after_state_root, + post.state_root, + 'receipt post-state and evidence post snapshot', + ) + assertBinding( + decision.after_state_root, + terminal.post_state_root, + 'receipt and terminal post-state', + ) + assertBinding( + decision.result_digest, + terminal.provider_result_digest, + 'receipt and provider result', + ) + assertBinding( + decision.terminal_receipt_id, + terminal.mutation_receipt_root, + 'authoritative and legacy mutation receipt', + ) + assertBinding( + 'VERIFIED', + terminal.receipt_chain_status, + 'authoritative receipt-chain status', + ) + assertBinding( + decision.chain_digest, + terminal.receipt_chain_verification_root, + 'authoritative receipt-chain digest', + ) + assertBinding( + expectedTerminal.durableStatus, + terminal.durable_status, + 'receipt and terminal durable status', + ) + assertBinding( + expectedTerminal.outcome, + terminal.outcome, + 'receipt and terminal outcome', + ) + + // Reconstruct all terminal fields available from the resolver. Equality was + // checked above so the verifier certificate remains bound to these exact + // authoritative values; the subsequent replay authenticates that signature. + const authoritativeEvidence = deepFreeze({ + input: { + ...evidenceSnapshot.input, + terminal_execution: { + ...terminal, + execution_identity_root: decision.actor_identity_root, + workspace_binding: decision.workspace_identity_root, + authority_receipt_root: decision.authority_receipt_hash, + requested_action_digest: decision.action_digest, + lease_authorization_receipt_root: decision.lease_authorization_receipt_hash, + mutation_receipt_root: decision.terminal_receipt_id, + receipt_chain_status: 'VERIFIED' as const, + receipt_chain_verification_root: decision.chain_digest, + durable_status: expectedTerminal.durableStatus, + outcome: expectedTerminal.outcome, + pre_state_root: decision.before_state_root, + post_state_root: decision.after_state_root, + provider_result_digest: decision.result_digest, + }, + }, + trust_policy: evidenceSnapshot.trust_policy, + }) + + const result = await replayAuthenticatedOutcomeEvidenceV1( + loop, + artifactStore, + allocatedSequence, + trustedOutcomeContext, + authoritativeEvidence, + ) + return Object.freeze({ + ...result, + provenance_status: AUTHORITATIVE_RECEIPT_PROVENANCE_VERIFIED, + provenance_decision: decision, + }) +} + +function expectedTerminalDisposition( + decision: CrossRuntimeReceiptVerificationDecisionV1, +): { readonly durableStatus: DurableTerminalStatus; readonly outcome: TerminalExecutionOutcome } { + switch (decision.terminal_receipt_kind) { + case 'MUTATION_COMPLETED': + if (decision.terminal_outcome === 'COMPLETED') { + return { durableStatus: 'COMPLETED', outcome: 'SUCCEEDED' } + } + break + case 'MUTATION_DENIED': + if (decision.terminal_outcome === 'DENIED') { + return { durableStatus: 'DENIED', outcome: 'DENIED' } + } + break + case 'MUTATION_FAILED': + if (decision.terminal_outcome === 'FAILED') { + return { durableStatus: 'FAILED', outcome: 'FAILED' } + } + break + case 'MUTATION_CANCELLED': + fail('cancelled mutation receipt chains cannot advance outcome replay') + default: + fail('receipt chain does not terminate in mutation outcome evidence') + } + return fail('receipt terminal kind and outcome are inconsistent') +} + +function snapshotIJson(value: T, label: string): Readonly { + try { + assertIJsonValue(value, label) + const snapshot = structuredClone(value) as T + assertIJsonValue(snapshot, label) + return deepFreeze(snapshot) + } catch (error) { + if (error instanceof AuthoritativeOutcomeEvidenceReplayError) throw error + fail(`${label} is not a closed I-JSON value: ${error instanceof Error ? error.message : String(error)}`) + } +} + +function assertBinding(actual: string, expected: string, label: string): void { + if (actual !== expected) fail(`${label} binding mismatch`) +} + +function fail(message: string): never { + throw new AuthoritativeOutcomeEvidenceReplayError(message) +} diff --git a/sovereign-omega-v2/src/provenance/cross-runtime-receipts.ts b/sovereign-omega-v2/src/provenance/cross-runtime-receipts.ts new file mode 100644 index 000000000..13b4f8505 --- /dev/null +++ b/sovereign-omega-v2/src/provenance/cross-runtime-receipts.ts @@ -0,0 +1,733 @@ +// ============================================================ +// AEGIS Cross-Runtime Authoritative Receipts V1 +// PROVENANCE ASSURANCE: T2->T3 implemented; projection admission pending +// +// The JSON schemas in /schemas are the normative wire contract. Every value +// crosses the strict I-JSON boundary before the repository's sole JCS encoder +// is used. No receipt or registry value is inferred from model output. +// ============================================================ + +import { canonicalizeJCS } from '../core/canonicalize.js' +import { sha256Hex } from '../core/hashing.js' +import { assertIJsonValue } from '../core/i-json.js' +import { deepFreeze } from '../core/immutable.js' +import type { SHA256Hex } from '../core/types.js' +import { signBytes } from '../consensus/crypto.js' + +export const CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION = '1.0.0' as const +export const RECEIPT_SIGNATURE_DOMAIN = 'AEGIS_CROSS_RUNTIME_RECEIPT_SIGNATURE_V1' as const +export const RECEIPT_ID_DOMAIN = 'AEGIS_CROSS_RUNTIME_RECEIPT_ID_V1' as const +export const REGISTRY_SIGNATURE_DOMAIN = 'AEGIS_RECEIPT_TRUST_REGISTRY_SIGNATURE_V1' as const +export const REGISTRY_ROOT_DOMAIN = 'AEGIS_RECEIPT_TRUST_REGISTRY_ROOT_V1' as const + +export type CrossRuntimeReceiptKindV1 = + | 'LEASE_ISSUED' + | 'LEASE_ISSUANCE_DENIED' + | 'LEASE_RENEWED' + | 'LEASE_RENEWAL_DENIED' + | 'LEASE_EXPIRED' + | 'LEASE_REVOKED' + | 'MUTATION_ADMITTED' + | 'MUTATION_DENIED' + | 'MUTATION_COMPLETED' + | 'MUTATION_CANCELLED' + | 'MUTATION_FAILED' + +export type CrossRuntimeReceiptOutcomeV1 = + | 'ADMITTED' + | 'DENIED' + | 'COMPLETED' + | 'CANCELLED' + | 'FAILED' + | 'EXPIRED' + | 'REVOKED' + +export type AuthorityLevelV1 = 'D0' | 'D1' | 'D2' | 'D3' | 'D4' +export type DecimalStringV1 = string + +export interface CrossRuntimeReceiptBodyV1 { + readonly receipt_sequence: DecimalStringV1 + readonly actor_identity_root: SHA256Hex + readonly session_identity_root: SHA256Hex + readonly workspace_identity_root: SHA256Hex + readonly holon_identity_root: SHA256Hex + readonly authority_domain: string + readonly authority_level: AuthorityLevelV1 + readonly authority_receipt_hash: SHA256Hex + readonly lease_id: SHA256Hex + readonly lease_generation: DecimalStringV1 + readonly fencing_token: SHA256Hex + readonly lease_authorization_receipt_hash: SHA256Hex + readonly parent_receipt_hash: SHA256Hex + readonly observed_state_root: SHA256Hex + readonly expected_state_root: SHA256Hex + readonly action_digest: SHA256Hex + readonly before_state_root: SHA256Hex + readonly after_state_root: SHA256Hex + readonly result_digest: SHA256Hex + readonly timestamp_ms: DecimalStringV1 + readonly expires_at_ms: DecimalStringV1 + readonly nonce: string + readonly outcome: CrossRuntimeReceiptOutcomeV1 + readonly denial_codes: readonly string[] +} + +export interface CrossRuntimeReceiptProofV1 { + readonly algorithm: 'Ed25519' + readonly signer_key_id: string + readonly verifier_identity_root: SHA256Hex + readonly trust_registry_version: DecimalStringV1 + readonly trust_registry_root: SHA256Hex + readonly signature: string +} + +export interface CrossRuntimeReceiptEnvelopeV1 { + readonly schema_version: typeof CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION + readonly receipt_kind: CrossRuntimeReceiptKindV1 + readonly receipt_body: CrossRuntimeReceiptBodyV1 + readonly proof: CrossRuntimeReceiptProofV1 + readonly receipt_id: SHA256Hex +} + +export interface CrossRuntimeReceiptDraftV1 { + readonly schema_version: typeof CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION + readonly receipt_kind: CrossRuntimeReceiptKindV1 + readonly receipt_body: CrossRuntimeReceiptBodyV1 + readonly proof: Omit +} + +export type ReceiptTrustKeyStatusV1 = 'ACTIVE' | 'REVOKED' + +export interface ReceiptTrustKeyEntryV1 { + readonly key_id: string + readonly public_key: string + readonly verifier_identity_root: SHA256Hex + readonly valid_from_ms: DecimalStringV1 + readonly expires_at_ms: DecimalStringV1 + readonly status: ReceiptTrustKeyStatusV1 + readonly authority_domains: readonly string[] + readonly receipt_kinds: readonly CrossRuntimeReceiptKindV1[] +} + +export interface ReceiptTrustRegistryBodyV1 { + readonly registry_version: DecimalStringV1 + readonly previous_registry_root: SHA256Hex + readonly issued_at_ms: DecimalStringV1 + readonly valid_from_ms: DecimalStringV1 + readonly expires_at_ms: DecimalStringV1 + readonly operator_key_id: string + readonly keys: readonly ReceiptTrustKeyEntryV1[] +} + +export interface ReceiptTrustRegistryV1 { + readonly schema_version: typeof CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION + readonly registry_body: ReceiptTrustRegistryBodyV1 + readonly proof: { + readonly algorithm: 'Ed25519' + readonly signature: string + } + readonly registry_root: SHA256Hex +} + +export class CrossRuntimeReceiptValidationError extends Error { + override readonly name = 'CrossRuntimeReceiptValidationError' + + constructor(message: string) { + super(message) + Object.setPrototypeOf(this, new.target.prototype) + } +} + +const HASH_PATTERN = /^[0-9a-f]{64}$/ +const SIGNATURE_PATTERN = /^[0-9a-f]{128}$/ +const DECIMAL_PATTERN = /^(0|[1-9][0-9]*)$/ +const SAFE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/ +const NONCE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{15,127}$/ +const ZERO_HASH = '0'.repeat(64) +const RECEIPT_KINDS = new Set([ + 'LEASE_ISSUED', + 'LEASE_ISSUANCE_DENIED', + 'LEASE_RENEWED', + 'LEASE_RENEWAL_DENIED', + 'LEASE_EXPIRED', + 'LEASE_REVOKED', + 'MUTATION_ADMITTED', + 'MUTATION_DENIED', + 'MUTATION_COMPLETED', + 'MUTATION_CANCELLED', + 'MUTATION_FAILED', +]) +const OUTCOMES = new Set([ + 'ADMITTED', 'DENIED', 'COMPLETED', 'CANCELLED', 'FAILED', 'EXPIRED', 'REVOKED', +]) +const AUTHORITY_LEVELS = new Set(['D0', 'D1', 'D2', 'D3', 'D4']) +const KEY_STATUSES = new Set(['ACTIVE', 'REVOKED']) + +const RECEIPT_KEYS = [ + 'proof', 'receipt_body', 'receipt_id', 'receipt_kind', 'schema_version', +] as const +const RECEIPT_BODY_KEYS = [ + 'action_digest', + 'actor_identity_root', + 'after_state_root', + 'authority_domain', + 'authority_level', + 'authority_receipt_hash', + 'before_state_root', + 'denial_codes', + 'expected_state_root', + 'expires_at_ms', + 'fencing_token', + 'holon_identity_root', + 'lease_authorization_receipt_hash', + 'lease_generation', + 'lease_id', + 'nonce', + 'observed_state_root', + 'outcome', + 'parent_receipt_hash', + 'receipt_sequence', + 'result_digest', + 'session_identity_root', + 'timestamp_ms', + 'workspace_identity_root', +] as const +const RECEIPT_PROOF_KEYS = [ + 'algorithm', 'signature', 'signer_key_id', 'trust_registry_root', + 'trust_registry_version', 'verifier_identity_root', +] as const +const RECEIPT_UNSIGNED_PROOF_KEYS = [ + 'algorithm', 'signer_key_id', 'trust_registry_root', 'trust_registry_version', + 'verifier_identity_root', +] as const +const REGISTRY_KEYS = ['proof', 'registry_body', 'registry_root', 'schema_version'] as const +const REGISTRY_BODY_KEYS = [ + 'expires_at_ms', 'issued_at_ms', 'keys', 'operator_key_id', + 'previous_registry_root', 'registry_version', 'valid_from_ms', +] as const +const REGISTRY_KEY_ENTRY_KEYS = [ + 'authority_domains', 'expires_at_ms', 'key_id', 'public_key', 'receipt_kinds', + 'status', 'valid_from_ms', 'verifier_identity_root', +] as const + +export function normalizeCrossRuntimeReceiptEnvelopeV1( + value: unknown, +): CrossRuntimeReceiptEnvelopeV1 { + const snapshot = snapshotIJson(value, 'cross-runtime receipt envelope') + const envelope = asObject('receipt', snapshot) + assertExactKeys('receipt', envelope, RECEIPT_KEYS) + if (envelope.schema_version !== CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION) { + fail('receipt.schema_version is unsupported') + } + const receipt_kind = assertReceiptKind('receipt.receipt_kind', envelope.receipt_kind) + const receipt_body = normalizeReceiptBody(envelope.receipt_body) + assertReceiptKindBodySemantics(receipt_kind, receipt_body) + const proof = normalizeReceiptProof(envelope.proof) + const receipt_id = assertHash('receipt.receipt_id', envelope.receipt_id) + return deepFreeze({ + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + receipt_kind, + receipt_body, + proof, + receipt_id, + }) +} + +export function normalizeReceiptTrustRegistryV1(value: unknown): ReceiptTrustRegistryV1 { + const snapshot = snapshotIJson(value, 'receipt trust registry') + const registry = asObject('registry', snapshot) + assertExactKeys('registry', registry, REGISTRY_KEYS) + if (registry.schema_version !== CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION) { + fail('registry.schema_version is unsupported') + } + const registry_body = normalizeRegistryBody(registry.registry_body) + const proofObject = asObject('registry.proof', registry.proof) + assertExactKeys('registry.proof', proofObject, ['algorithm', 'signature']) + if (proofObject.algorithm !== 'Ed25519') fail('registry.proof.algorithm is unsupported') + const signature = assertSignature('registry.proof.signature', proofObject.signature) + const registry_root = assertNonZeroHash('registry.registry_root', registry.registry_root) + return deepFreeze({ + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + registry_body, + proof: { algorithm: 'Ed25519', signature }, + registry_root, + }) +} + +export function canonicalizeCrossRuntimeReceiptSignatureMessageV1( + value: CrossRuntimeReceiptDraftV1, +): Uint8Array { + const draft = normalizeReceiptDraft(value) + return canonicalizeJCS({ + domain: RECEIPT_SIGNATURE_DOMAIN, + schema_version: draft.schema_version, + receipt_kind: draft.receipt_kind, + receipt_body: draft.receipt_body, + proof: draft.proof, + }) +} + +export async function deriveCrossRuntimeReceiptIdV1( + value: Omit, +): Promise { + const signed = normalizeSignedReceiptWithoutId(value) + return sha256Hex(canonicalizeJCS({ + domain: RECEIPT_ID_DOMAIN, + envelope: signed, + })) +} + +export async function buildCrossRuntimeReceiptEnvelopeV1( + draft: CrossRuntimeReceiptDraftV1, + privateKey: Uint8Array, +): Promise { + const normalized = normalizeReceiptDraft(draft) + const signature = await signBytes( + privateKey, + canonicalizeCrossRuntimeReceiptSignatureMessageV1(normalized), + ) + const signed = deepFreeze({ + schema_version: normalized.schema_version, + receipt_kind: normalized.receipt_kind, + receipt_body: normalized.receipt_body, + proof: { ...normalized.proof, signature }, + }) + const receipt_id = await deriveCrossRuntimeReceiptIdV1(signed) + return normalizeCrossRuntimeReceiptEnvelopeV1({ ...signed, receipt_id }) +} + +export function canonicalizeReceiptTrustRegistrySignatureMessageV1( + body: ReceiptTrustRegistryBodyV1, +): Uint8Array { + const registryBody = normalizeRegistryBody(snapshotIJson(body, 'registry body')) + return canonicalizeJCS({ + domain: REGISTRY_SIGNATURE_DOMAIN, + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + registry_body: registryBody, + proof: { algorithm: 'Ed25519' }, + }) +} + +export async function deriveReceiptTrustRegistryRootV1( + value: Omit, +): Promise { + const registry = normalizeRegistryWithoutRoot(value) + return sha256Hex(canonicalizeJCS({ + domain: REGISTRY_ROOT_DOMAIN, + registry, + })) +} + +export async function buildReceiptTrustRegistryV1( + body: ReceiptTrustRegistryBodyV1, + operatorPrivateKey: Uint8Array, +): Promise { + const registry_body = normalizeRegistryBody(snapshotIJson(body, 'registry body')) + const signature = await signBytes( + operatorPrivateKey, + canonicalizeReceiptTrustRegistrySignatureMessageV1(registry_body), + ) + const unsignedRoot = deepFreeze({ + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + registry_body, + proof: { algorithm: 'Ed25519' as const, signature }, + }) + const registry_root = await deriveReceiptTrustRegistryRootV1(unsignedRoot) + return normalizeReceiptTrustRegistryV1({ ...unsignedRoot, registry_root }) +} + +export async function assertCrossRuntimeReceiptIdV1( + envelope: CrossRuntimeReceiptEnvelopeV1, +): Promise { + const normalized = normalizeCrossRuntimeReceiptEnvelopeV1(envelope) + const { receipt_id: _receiptId, ...signed } = normalized + const expected = await deriveCrossRuntimeReceiptIdV1(signed) + if (normalized.receipt_id !== expected) fail('receipt.receipt_id does not match its signed content') +} + +export async function assertReceiptTrustRegistryRootV1( + registry: ReceiptTrustRegistryV1, +): Promise { + const normalized = normalizeReceiptTrustRegistryV1(registry) + const { registry_root: _registryRoot, ...signed } = normalized + const expected = await deriveReceiptTrustRegistryRootV1(signed) + if (normalized.registry_root !== expected) fail('registry.registry_root does not match its signed content') +} + +function normalizeReceiptDraft(value: unknown): CrossRuntimeReceiptDraftV1 { + const snapshot = snapshotIJson(value, 'cross-runtime receipt draft') + const draft = asObject('receipt draft', snapshot) + assertExactKeys('receipt draft', draft, ['proof', 'receipt_body', 'receipt_kind', 'schema_version']) + if (draft.schema_version !== CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION) { + fail('receipt draft.schema_version is unsupported') + } + const proofObject = asObject('receipt draft.proof', draft.proof) + assertExactKeys('receipt draft.proof', proofObject, RECEIPT_UNSIGNED_PROOF_KEYS) + const receiptKind = assertReceiptKind('receipt draft.receipt_kind', draft.receipt_kind) + const receiptBody = normalizeReceiptBody(draft.receipt_body) + assertReceiptKindBodySemantics(receiptKind, receiptBody) + return deepFreeze({ + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + receipt_kind: receiptKind, + receipt_body: receiptBody, + proof: normalizeUnsignedReceiptProof(proofObject), + }) +} + +function normalizeSignedReceiptWithoutId( + value: unknown, +): Omit { + const snapshot = snapshotIJson(value, 'signed receipt') + const signed = asObject('signed receipt', snapshot) + assertExactKeys('signed receipt', signed, ['proof', 'receipt_body', 'receipt_kind', 'schema_version']) + if (signed.schema_version !== CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION) { + fail('signed receipt.schema_version is unsupported') + } + const receiptKind = assertReceiptKind('signed receipt.receipt_kind', signed.receipt_kind) + const receiptBody = normalizeReceiptBody(signed.receipt_body) + assertReceiptKindBodySemantics(receiptKind, receiptBody) + return deepFreeze({ + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + receipt_kind: receiptKind, + receipt_body: receiptBody, + proof: normalizeReceiptProof(signed.proof), + }) +} + +function normalizeRegistryWithoutRoot( + value: unknown, +): Omit { + const snapshot = snapshotIJson(value, 'signed receipt trust registry') + const registry = asObject('signed registry', snapshot) + assertExactKeys('signed registry', registry, ['proof', 'registry_body', 'schema_version']) + if (registry.schema_version !== CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION) { + fail('signed registry.schema_version is unsupported') + } + const proof = asObject('signed registry.proof', registry.proof) + assertExactKeys('signed registry.proof', proof, ['algorithm', 'signature']) + if (proof.algorithm !== 'Ed25519') fail('signed registry.proof.algorithm is unsupported') + return deepFreeze({ + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + registry_body: normalizeRegistryBody(registry.registry_body), + proof: { + algorithm: 'Ed25519', + signature: assertSignature('signed registry.proof.signature', proof.signature), + }, + }) +} + +function normalizeReceiptBody(value: unknown): CrossRuntimeReceiptBodyV1 { + const body = asObject('receipt.receipt_body', value) + assertExactKeys('receipt.receipt_body', body, RECEIPT_BODY_KEYS) + const denialCodes = asArray('receipt.receipt_body.denial_codes', body.denial_codes) + if (denialCodes.length > 32) fail('receipt.receipt_body.denial_codes has too many entries') + const normalizedCodes = denialCodes.map((code, index) => + assertSafeId(`receipt.receipt_body.denial_codes[${index}]`, code)) + if (new Set(normalizedCodes).size !== normalizedCodes.length) { + fail('receipt.receipt_body.denial_codes must be unique') + } + assertSortedUtf8('receipt.receipt_body.denial_codes', normalizedCodes) + const authorityLevel = body.authority_level + if (typeof authorityLevel !== 'string' || !AUTHORITY_LEVELS.has(authorityLevel as AuthorityLevelV1)) { + fail('receipt.receipt_body.authority_level is invalid') + } + const outcome = body.outcome + if (typeof outcome !== 'string' || !OUTCOMES.has(outcome as CrossRuntimeReceiptOutcomeV1)) { + fail('receipt.receipt_body.outcome is invalid') + } + return deepFreeze({ + receipt_sequence: assertDecimal('receipt.receipt_body.receipt_sequence', body.receipt_sequence), + actor_identity_root: assertNonZeroHash('receipt.receipt_body.actor_identity_root', body.actor_identity_root), + session_identity_root: assertNonZeroHash('receipt.receipt_body.session_identity_root', body.session_identity_root), + workspace_identity_root: assertNonZeroHash('receipt.receipt_body.workspace_identity_root', body.workspace_identity_root), + holon_identity_root: assertNonZeroHash('receipt.receipt_body.holon_identity_root', body.holon_identity_root), + authority_domain: assertSafeId('receipt.receipt_body.authority_domain', body.authority_domain), + authority_level: authorityLevel as AuthorityLevelV1, + authority_receipt_hash: assertHash('receipt.receipt_body.authority_receipt_hash', body.authority_receipt_hash), + lease_id: assertNonZeroHash('receipt.receipt_body.lease_id', body.lease_id), + lease_generation: assertDecimal('receipt.receipt_body.lease_generation', body.lease_generation), + fencing_token: assertHash('receipt.receipt_body.fencing_token', body.fencing_token), + lease_authorization_receipt_hash: assertHash( + 'receipt.receipt_body.lease_authorization_receipt_hash', + body.lease_authorization_receipt_hash, + ), + parent_receipt_hash: assertHash('receipt.receipt_body.parent_receipt_hash', body.parent_receipt_hash), + observed_state_root: assertNonZeroHash('receipt.receipt_body.observed_state_root', body.observed_state_root), + expected_state_root: assertNonZeroHash('receipt.receipt_body.expected_state_root', body.expected_state_root), + action_digest: assertNonZeroHash('receipt.receipt_body.action_digest', body.action_digest), + before_state_root: assertNonZeroHash('receipt.receipt_body.before_state_root', body.before_state_root), + after_state_root: assertNonZeroHash('receipt.receipt_body.after_state_root', body.after_state_root), + result_digest: assertNonZeroHash('receipt.receipt_body.result_digest', body.result_digest), + timestamp_ms: assertDecimal('receipt.receipt_body.timestamp_ms', body.timestamp_ms), + expires_at_ms: assertDecimal('receipt.receipt_body.expires_at_ms', body.expires_at_ms), + nonce: assertNonce('receipt.receipt_body.nonce', body.nonce), + outcome: outcome as CrossRuntimeReceiptOutcomeV1, + denial_codes: normalizedCodes, + }) +} + +function normalizeReceiptProof(value: unknown): CrossRuntimeReceiptProofV1 { + const proof = asObject('receipt.proof', value) + assertExactKeys('receipt.proof', proof, RECEIPT_PROOF_KEYS) + return deepFreeze({ + ...normalizeUnsignedReceiptProof(proof), + signature: assertSignature('receipt.proof.signature', proof.signature), + }) +} + +function normalizeUnsignedReceiptProof( + proof: Record, +): Omit { + if (proof.algorithm !== 'Ed25519') fail('receipt.proof.algorithm is unsupported') + return deepFreeze({ + algorithm: 'Ed25519', + signer_key_id: assertSafeId('receipt.proof.signer_key_id', proof.signer_key_id), + verifier_identity_root: assertNonZeroHash( + 'receipt.proof.verifier_identity_root', + proof.verifier_identity_root, + ), + trust_registry_version: assertDecimal( + 'receipt.proof.trust_registry_version', + proof.trust_registry_version, + ), + trust_registry_root: assertNonZeroHash( + 'receipt.proof.trust_registry_root', + proof.trust_registry_root, + ), + }) +} + +function normalizeRegistryBody(value: unknown): ReceiptTrustRegistryBodyV1 { + const body = asObject('registry.registry_body', value) + assertExactKeys('registry.registry_body', body, REGISTRY_BODY_KEYS) + const entries = asArray('registry.registry_body.keys', body.keys) + if (entries.length < 1 || entries.length > 128) { + fail('registry.registry_body.keys must contain between 1 and 128 entries') + } + const keys = entries.map((entry, index) => normalizeRegistryKeyEntry(entry, index)) + assertSortedUtf8('registry.registry_body.keys', keys.map(entry => entry.key_id)) + if (new Set(keys.map(entry => entry.key_id)).size !== keys.length) { + fail('registry.registry_body.keys must have unique key_id values') + } + if (new Set(keys.map(entry => entry.public_key)).size !== keys.length) { + fail('registry.registry_body.keys must have unique public_key values') + } + const issuedAt = assertDecimal('registry.registry_body.issued_at_ms', body.issued_at_ms) + const validFrom = assertDecimal('registry.registry_body.valid_from_ms', body.valid_from_ms) + const expiresAt = assertDecimal('registry.registry_body.expires_at_ms', body.expires_at_ms) + if (BigInt(issuedAt) > BigInt(validFrom) || BigInt(validFrom) >= BigInt(expiresAt)) { + fail('registry.registry_body validity interval is invalid or empty') + } + for (const [index, key] of keys.entries()) { + if (BigInt(key.valid_from_ms) < BigInt(validFrom) || + BigInt(key.expires_at_ms) > BigInt(expiresAt)) { + fail(`registry.registry_body.keys[${index}] validity must be contained by the registry window`) + } + } + return deepFreeze({ + registry_version: assertDecimal('registry.registry_body.registry_version', body.registry_version), + previous_registry_root: assertHash( + 'registry.registry_body.previous_registry_root', + body.previous_registry_root, + ), + issued_at_ms: issuedAt, + valid_from_ms: validFrom, + expires_at_ms: expiresAt, + operator_key_id: assertSafeId('registry.registry_body.operator_key_id', body.operator_key_id), + keys, + }) +} + +function normalizeRegistryKeyEntry(value: unknown, index: number): ReceiptTrustKeyEntryV1 { + const field = `registry.registry_body.keys[${index}]` + const entry = asObject(field, value) + assertExactKeys(field, entry, REGISTRY_KEY_ENTRY_KEYS) + const authorityDomains = asArray(`${field}.authority_domains`, entry.authority_domains) + if (authorityDomains.length === 0) fail(`${field}.authority_domains must not be empty`) + const normalizedDomains = authorityDomains.map((domain, domainIndex) => + assertSafeId(`${field}.authority_domains[${domainIndex}]`, domain)) + if (new Set(normalizedDomains).size !== normalizedDomains.length) { + fail(`${field}.authority_domains must be unique`) + } + assertSortedUtf8(`${field}.authority_domains`, normalizedDomains) + const kinds = asArray(`${field}.receipt_kinds`, entry.receipt_kinds) + if (kinds.length === 0) fail(`${field}.receipt_kinds must not be empty`) + const normalizedKinds = kinds.map((kind, kindIndex) => + assertReceiptKind(`${field}.receipt_kinds[${kindIndex}]`, kind)) + if (new Set(normalizedKinds).size !== normalizedKinds.length) { + fail(`${field}.receipt_kinds must be unique`) + } + assertSortedUtf8(`${field}.receipt_kinds`, normalizedKinds) + const status = entry.status + if (typeof status !== 'string' || !KEY_STATUSES.has(status as ReceiptTrustKeyStatusV1)) { + fail(`${field}.status is invalid`) + } + const validFrom = assertDecimal(`${field}.valid_from_ms`, entry.valid_from_ms) + const expiresAt = assertDecimal(`${field}.expires_at_ms`, entry.expires_at_ms) + if (BigInt(validFrom) >= BigInt(expiresAt)) fail(`${field} validity interval is invalid or empty`) + return deepFreeze({ + key_id: assertSafeId(`${field}.key_id`, entry.key_id), + public_key: assertHash(`${field}.public_key`, entry.public_key), + verifier_identity_root: assertNonZeroHash( + `${field}.verifier_identity_root`, + entry.verifier_identity_root, + ), + valid_from_ms: validFrom, + expires_at_ms: expiresAt, + status: status as ReceiptTrustKeyStatusV1, + authority_domains: normalizedDomains, + receipt_kinds: normalizedKinds, + }) +} + +function snapshotIJson(value: unknown, label: string): unknown { + try { + assertIJsonValue(value, label) + const snapshot = structuredClone(value) as unknown + assertIJsonValue(snapshot, label) + return snapshot + } catch (error) { + if (error instanceof CrossRuntimeReceiptValidationError) throw error + fail(`${label} is not a closed I-JSON value: ${error instanceof Error ? error.message : String(error)}`) + } +} + +function asObject(field: string, value: unknown): Record { + if (value === null || typeof value !== 'object' || Array.isArray(value)) fail(`${field} must be an object`) + return value as Record +} + +function asArray(field: string, value: unknown): readonly unknown[] { + if (!Array.isArray(value)) fail(`${field} must be an array`) + return value +} + +function assertExactKeys( + field: string, + value: Record, + expectedKeys: readonly string[], +): void { + const actual = Object.keys(value).sort(compareUtf8) + const expected = [...expectedKeys].sort(compareUtf8) + if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) { + fail(`${field} has unexpected or missing fields`) + } +} + +function assertReceiptKind(field: string, value: unknown): CrossRuntimeReceiptKindV1 { + if (typeof value !== 'string' || !RECEIPT_KINDS.has(value as CrossRuntimeReceiptKindV1)) { + fail(`${field} is invalid`) + } + return value as CrossRuntimeReceiptKindV1 +} + +function assertHash(field: string, value: unknown): SHA256Hex { + if (typeof value !== 'string' || !HASH_PATTERN.test(value)) fail(`${field} must be lowercase SHA-256 hex`) + return value as SHA256Hex +} + +function assertNonZeroHash(field: string, value: unknown): SHA256Hex { + const hash = assertHash(field, value) + if (hash === ZERO_HASH) fail(`${field} must be a non-zero SHA-256 root`) + return hash +} + +function assertSignature(field: string, value: unknown): string { + if (typeof value !== 'string' || !SIGNATURE_PATTERN.test(value)) { + fail(`${field} must be 64-byte lowercase Ed25519 hex`) + } + return value +} + +function assertDecimal(field: string, value: unknown): DecimalStringV1 { + if (typeof value !== 'string' || value.length > 20 || !DECIMAL_PATTERN.test(value)) { + fail(`${field} must be a canonical decimal string of at most 20 digits`) + } + return value +} + +function assertSafeId(field: string, value: unknown): string { + if (typeof value !== 'string' || !SAFE_ID_PATTERN.test(value)) fail(`${field} is not a canonical safe identifier`) + return value +} + +function assertNonce(field: string, value: unknown): string { + if (typeof value !== 'string' || !NONCE_PATTERN.test(value)) fail(`${field} is not a canonical nonce`) + return value +} + +function compareUtf8(left: string, right: string): number { + const leftBytes = new TextEncoder().encode(left) + const rightBytes = new TextEncoder().encode(right) + const length = Math.min(leftBytes.length, rightBytes.length) + for (let index = 0; index < length; index += 1) { + const difference = leftBytes[index]! - rightBytes[index]! + if (difference !== 0) return difference + } + return leftBytes.length - rightBytes.length +} + +function assertSortedUtf8(field: string, values: readonly string[]): void { + for (let index = 1; index < values.length; index += 1) { + if (compareUtf8(values[index - 1]!, values[index]!) >= 0) { + fail(`${field} must be strictly sorted by UTF-8 bytes`) + } + } +} + +function assertReceiptKindBodySemantics( + kind: CrossRuntimeReceiptKindV1, + body: CrossRuntimeReceiptBodyV1, +): void { + const expectedOutcomes: Readonly> = { + LEASE_ISSUED: 'ADMITTED', + LEASE_ISSUANCE_DENIED: 'DENIED', + LEASE_RENEWED: 'ADMITTED', + LEASE_RENEWAL_DENIED: 'DENIED', + LEASE_EXPIRED: 'EXPIRED', + LEASE_REVOKED: 'REVOKED', + MUTATION_ADMITTED: 'ADMITTED', + MUTATION_DENIED: 'DENIED', + MUTATION_COMPLETED: 'COMPLETED', + MUTATION_CANCELLED: 'CANCELLED', + MUTATION_FAILED: 'FAILED', + } + if (body.outcome !== expectedOutcomes[kind]) { + fail(`receipt.receipt_body.outcome is invalid for ${kind}`) + } + const denialRequired = new Set([ + 'LEASE_ISSUANCE_DENIED', + 'LEASE_RENEWAL_DENIED', + 'LEASE_EXPIRED', + 'LEASE_REVOKED', + 'MUTATION_DENIED', + 'MUTATION_CANCELLED', + 'MUTATION_FAILED', + ]).has(kind) + if (denialRequired && body.denial_codes.length === 0) { + fail(`receipt.receipt_body.denial_codes must not be empty for ${kind}`) + } + if (!denialRequired && body.denial_codes.length !== 0) { + fail(`receipt.receipt_body.denial_codes must be empty for ${kind}`) + } + if (kind === 'LEASE_ISSUANCE_DENIED') { + if (body.fencing_token !== ZERO_HASH) { + fail('receipt.receipt_body.fencing_token must be unresolved for denied lease issuance') + } + } else if (body.fencing_token === ZERO_HASH) { + fail(`receipt.receipt_body.fencing_token must be resolved for ${kind}`) + } + if (kind.startsWith('LEASE_')) { + if (body.authority_receipt_hash !== ZERO_HASH || + body.lease_authorization_receipt_hash !== ZERO_HASH) { + fail(`${kind} must not carry mutation authority receipt roots`) + } + } else if (body.authority_receipt_hash === ZERO_HASH || + body.lease_authorization_receipt_hash === ZERO_HASH) { + fail(`${kind} must resolve authority and lease-authorization receipt roots`) + } +} + +function fail(message: string): never { + throw new CrossRuntimeReceiptValidationError(message) +} diff --git a/sovereign-omega-v2/src/provenance/indexeddb-receipt-source.ts b/sovereign-omega-v2/src/provenance/indexeddb-receipt-source.ts new file mode 100644 index 000000000..e4bc926b2 --- /dev/null +++ b/sovereign-omega-v2/src/provenance/indexeddb-receipt-source.ts @@ -0,0 +1,455 @@ +// ============================================================ +// AEGIS Add-Only Cross-Runtime Receipt Source V1 +// PROVENANCE ASSURANCE: T2->T3 implemented; projection admission pending +// ============================================================ + +import { canonicalizeJCS } from '../core/canonicalize.js' +import { assertIJsonValue } from '../core/i-json.js' +import type { SHA256Hex } from '../core/types.js' +import { + assertCrossRuntimeReceiptIdV1, + assertReceiptTrustRegistryRootV1, + normalizeCrossRuntimeReceiptEnvelopeV1, + normalizeReceiptTrustRegistryV1, +} from './cross-runtime-receipts.js' +import type { + CrossRuntimeReceiptEnvelopeV1, + ReceiptTrustRegistryV1, +} from './cross-runtime-receipts.js' +import type { CrossRuntimeReceiptSourceV1 } from './receipt-resolver.js' + +const DEFAULT_DATABASE_NAME = 'sovereign-omega-authoritative-receipts' +const DATABASE_VERSION = 1 +const RECEIPTS_STORE = 'cross-runtime-receipts' +const REGISTRIES_STORE = 'receipt-trust-registries' +const HASH_PATTERN = /^[0-9a-f]{64}$/ + +interface StoredReceiptRecord { + readonly receipt_id: SHA256Hex + readonly envelope: CrossRuntimeReceiptEnvelopeV1 + readonly nonce_key: string + readonly chain_slot: string + readonly mutation_action_key?: string +} + +interface StoredRegistryRecord { + readonly registry_root: SHA256Hex + readonly registry: ReceiptTrustRegistryV1 + readonly registry_version_key: string +} + +export interface ReceiptSourcePersistenceResultV1 { + readonly receipt_ids: readonly SHA256Hex[] + readonly registry_roots: readonly SHA256Hex[] +} + +export class IndexedDBCrossRuntimeReceiptSourceError extends Error { + override readonly name = 'IndexedDBCrossRuntimeReceiptSourceError' + + constructor(message: string) { + super(message) + Object.setPrototypeOf(this, new.target.prototype) + } +} + +export class IndexedDBCrossRuntimeReceiptSourceV1 implements CrossRuntimeReceiptSourceV1 { + private database: IDBDatabase | null = null + private opening: Promise | null = null + private closeGeneration = 0 + + constructor(private readonly databaseName = DEFAULT_DATABASE_NAME) { + if (databaseName.trim() === '') fail('database name must not be empty') + } + + async open(): Promise { + if (this.database !== null) return + if (this.opening === null) { + const generation = this.closeGeneration + this.opening = openDatabase(this.databaseName).then(database => { + if (generation !== this.closeGeneration) { + database.close() + fail('receipt source closed while opening') + } + database.onversionchange = () => { + database.close() + if (this.database === database) this.database = null + } + this.database = database + return database + }).finally(() => { + this.opening = null + }) + } + await this.opening + } + + close(): void { + this.closeGeneration += 1 + this.database?.close() + this.database = null + } + + async persistReceipt(receipt: CrossRuntimeReceiptEnvelopeV1): Promise { + const result = await this.persistBatch([], [receipt]) + return result.receipt_ids[0]! + } + + async persistTrustRegistry(registry: ReceiptTrustRegistryV1): Promise { + const result = await this.persistBatch([registry], []) + return result.registry_roots[0]! + } + + async persistBatch( + registries: readonly ReceiptTrustRegistryV1[], + receipts: readonly CrossRuntimeReceiptEnvelopeV1[], + ): Promise { + const database = this.requireDatabase() + const batch = snapshotBatch(registries, receipts) + const normalizedRegistries = await Promise.all(batch.registries.map(async value => { + const registry = normalizeReceiptTrustRegistryV1(value) + await assertReceiptTrustRegistryRootV1(registry) + return registry + })) + const normalizedReceipts = await Promise.all(batch.receipts.map(async value => { + const receipt = normalizeCrossRuntimeReceiptEnvelopeV1(value) + await assertCrossRuntimeReceiptIdV1(receipt) + return receipt + })) + assertBatchUnique(normalizedRegistries, normalizedReceipts) + + const registryRecords = normalizedRegistries.map(toStoredRegistry) + const receiptRecords = normalizedReceipts.map(toStoredReceipt) + const missingRegistries: StoredRegistryRecord[] = [] + const missingReceipts: StoredReceiptRecord[] = [] + for (const record of registryRecords) { + const existing = await readRecord(database, REGISTRIES_STORE, record.registry_root) + if (existing === null) missingRegistries.push(record) + else await assertIdenticalRegistryRecord(existing, record) + } + for (const record of receiptRecords) { + const existing = await readRecord(database, RECEIPTS_STORE, record.receipt_id) + if (existing === null) missingReceipts.push(record) + else await assertIdenticalReceiptRecord(existing, record) + } + + if (missingRegistries.length > 0 || missingReceipts.length > 0) { + try { + await addBatch(database, missingRegistries, missingReceipts) + } catch (error) { + if (!isConstraintError(error)) throw error + await assertBatchNowPersisted(database, registryRecords, receiptRecords) + } + } + await assertBatchNowPersisted(database, registryRecords, receiptRecords) + return Object.freeze({ + registry_roots: Object.freeze(normalizedRegistries.map(value => value.registry_root)), + receipt_ids: Object.freeze(normalizedReceipts.map(value => value.receipt_id)), + }) + } + + async resolveReceipt(receiptId: SHA256Hex): Promise { + assertResolvedHash('receipt id', receiptId) + const record = await readRecord( + this.requireDatabase(), RECEIPTS_STORE, receiptId, + ) + if (record === null) return null + const normalized = normalizeStoredReceipt(record) + await assertCrossRuntimeReceiptIdV1(normalized.envelope) + if (normalized.receipt_id !== receiptId) fail('stored receipt primary key mismatch') + return normalized.envelope + } + + async resolveTrustRegistry(registryRoot: SHA256Hex): Promise { + assertResolvedHash('registry root', registryRoot) + const record = await readRecord( + this.requireDatabase(), REGISTRIES_STORE, registryRoot, + ) + if (record === null) return null + const normalized = normalizeStoredRegistry(record) + await assertReceiptTrustRegistryRootV1(normalized.registry) + if (normalized.registry_root !== registryRoot) fail('stored registry primary key mismatch') + return normalized.registry + } + + private requireDatabase(): IDBDatabase { + if (this.database === null) fail('receipt source is not open') + return this.database + } +} + +async function assertBatchNowPersisted( + database: IDBDatabase, + registries: readonly StoredRegistryRecord[], + receipts: readonly StoredReceiptRecord[], +): Promise { + for (const expected of registries) { + const actual = await readRecord( + database, REGISTRIES_STORE, expected.registry_root, + ) + if (actual === null) fail('registry batch read-back failed') + await assertIdenticalRegistryRecord(actual, expected) + } + for (const expected of receipts) { + const actual = await readRecord(database, RECEIPTS_STORE, expected.receipt_id) + if (actual === null) fail('receipt batch read-back failed') + await assertIdenticalReceiptRecord(actual, expected) + } +} + +async function assertIdenticalReceiptRecord( + actualValue: unknown, + expected: StoredReceiptRecord, +): Promise { + const actual = normalizeStoredReceipt(actualValue) + await assertCrossRuntimeReceiptIdV1(actual.envelope) + if (!equalBytes(canonicalizeJCS(actual), canonicalizeJCS(expected))) { + fail('receipt id collision or stored receipt mismatch') + } +} + +async function assertIdenticalRegistryRecord( + actualValue: unknown, + expected: StoredRegistryRecord, +): Promise { + const actual = normalizeStoredRegistry(actualValue) + await assertReceiptTrustRegistryRootV1(actual.registry) + if (!equalBytes(canonicalizeJCS(actual), canonicalizeJCS(expected))) { + fail('registry root collision or stored registry mismatch') + } +} + +function normalizeStoredReceipt(value: unknown): StoredReceiptRecord { + const snapshot = snapshotIJson(value, 'stored receipt record') + if (snapshot === null || typeof snapshot !== 'object' || Array.isArray(snapshot)) { + fail('stored receipt record must be an object') + } + const record = snapshot as Record + const expectedKeys = ['chain_slot', 'envelope', 'nonce_key', 'receipt_id'] + if ('mutation_action_key' in record) expectedKeys.push('mutation_action_key') + assertExactKeys('stored receipt record', record, expectedKeys) + const envelope = normalizeCrossRuntimeReceiptEnvelopeV1(record.envelope) + const expected = toStoredReceipt(envelope) + if (record.receipt_id !== expected.receipt_id || record.nonce_key !== expected.nonce_key || + record.chain_slot !== expected.chain_slot || + record.mutation_action_key !== expected.mutation_action_key) { + fail('stored receipt indexes do not match their receipt derivation') + } + return expected +} + +function normalizeStoredRegistry(value: unknown): StoredRegistryRecord { + const snapshot = snapshotIJson(value, 'stored registry record') + if (snapshot === null || typeof snapshot !== 'object' || Array.isArray(snapshot)) { + fail('stored registry record must be an object') + } + const record = snapshot as Record + assertExactKeys('stored registry record', record, [ + 'registry', 'registry_root', 'registry_version_key', + ]) + const registry = normalizeReceiptTrustRegistryV1(record.registry) + const expected = toStoredRegistry(registry) + if (record.registry_root !== expected.registry_root || + record.registry_version_key !== expected.registry_version_key) { + fail('stored registry indexes do not match their registry derivation') + } + return expected +} + +function toStoredReceipt(envelope: CrossRuntimeReceiptEnvelopeV1): StoredReceiptRecord { + const body = envelope.receipt_body + const base = { + receipt_id: envelope.receipt_id, + envelope, + nonce_key: `${envelope.proof.trust_registry_root}\u0000${envelope.proof.signer_key_id}\u0000${body.nonce}`, + chain_slot: `${body.parent_receipt_hash}\u0000${body.receipt_sequence}`, + } + if (envelope.receipt_kind === 'MUTATION_ADMITTED') { + return { + ...base, + mutation_action_key: mutationActionKeyFor(body), + } + } + return base +} + +function toStoredRegistry(registry: ReceiptTrustRegistryV1): StoredRegistryRecord { + return { + registry_root: registry.registry_root, + registry, + registry_version_key: `${registry.registry_body.operator_key_id}\u0000${registry.registry_body.registry_version}`, + } +} + +function snapshotBatch( + registries: readonly ReceiptTrustRegistryV1[], + receipts: readonly CrossRuntimeReceiptEnvelopeV1[], +): { registries: ReceiptTrustRegistryV1[]; receipts: CrossRuntimeReceiptEnvelopeV1[] } { + const value = { registries, receipts } + const snapshot = snapshotIJson(value, 'receipt persistence batch') as { + registries: ReceiptTrustRegistryV1[] + receipts: CrossRuntimeReceiptEnvelopeV1[] + } + return snapshot +} + +function assertBatchUnique( + registries: readonly ReceiptTrustRegistryV1[], + receipts: readonly CrossRuntimeReceiptEnvelopeV1[], +): void { + assertUnique('registry roots', registries.map(value => value.registry_root)) + assertUnique( + 'registry versions', + registries.map(value => `${value.registry_body.operator_key_id}\u0000${value.registry_body.registry_version}`), + ) + assertUnique('receipt ids', receipts.map(value => value.receipt_id)) + assertUnique( + 'receipt nonces', + receipts.map(value => + `${value.proof.trust_registry_root}\u0000${value.proof.signer_key_id}\u0000${value.receipt_body.nonce}`), + ) + assertUnique( + 'receipt chain slots', + receipts.map(value => `${value.receipt_body.parent_receipt_hash}\u0000${value.receipt_body.receipt_sequence}`), + ) + assertUnique( + 'mutation actions', + receipts + .filter(value => value.receipt_kind === 'MUTATION_ADMITTED') + .map(value => mutationActionKeyFor(value.receipt_body)), + ) +} + +function mutationActionKeyFor(body: CrossRuntimeReceiptEnvelopeV1['receipt_body']): string { + return [ + body.actor_identity_root, + body.session_identity_root, + body.workspace_identity_root, + body.holon_identity_root, + body.authority_domain, + body.action_digest, + ].join('\u0000') +} + +function assertUnique(label: string, values: readonly string[]): void { + if (new Set(values).size !== values.length) fail(`persistence batch contains duplicate ${label}`) +} + +function openDatabase(name: string): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(name, DATABASE_VERSION) + request.onupgradeneeded = () => { + const database = request.result + if (!database.objectStoreNames.contains(RECEIPTS_STORE)) { + const receipts = database.createObjectStore(RECEIPTS_STORE, { keyPath: 'receipt_id' }) + receipts.createIndex('by_nonce', 'nonce_key', { unique: true }) + receipts.createIndex('by_chain_slot', 'chain_slot', { unique: true }) + receipts.createIndex('by_mutation_action', 'mutation_action_key', { unique: true }) + } + if (!database.objectStoreNames.contains(REGISTRIES_STORE)) { + const registries = database.createObjectStore(REGISTRIES_STORE, { keyPath: 'registry_root' }) + registries.createIndex('by_registry_version', 'registry_version_key', { unique: true }) + } + } + request.onsuccess = () => resolve(request.result) + request.onerror = () => reject(new IndexedDBCrossRuntimeReceiptSourceError( + `failed to open receipt source: ${request.error?.message ?? 'unknown error'}`, + )) + }) +} + +function addBatch( + database: IDBDatabase, + registries: readonly StoredRegistryRecord[], + receipts: readonly StoredReceiptRecord[], +): Promise { + return new Promise((resolve, reject) => { + const transaction = database.transaction([REGISTRIES_STORE, RECEIPTS_STORE], 'readwrite') + const registryStore = transaction.objectStore(REGISTRIES_STORE) + const receiptStore = transaction.objectStore(RECEIPTS_STORE) + let operationError: DOMException | null = null + for (const registry of registries) { + const request = registryStore.add(registry) + request.onerror = () => { operationError ??= request.error } + } + for (const receipt of receipts) { + const request = receiptStore.add(receipt) + request.onerror = () => { operationError ??= request.error } + } + transaction.oncomplete = () => resolve() + transaction.onerror = () => { operationError ??= transaction.error } + transaction.onabort = () => reject( + operationError ?? transaction.error ?? + new IndexedDBCrossRuntimeReceiptSourceError('receipt batch insert aborted'), + ) + }) +} + +function readRecord( + database: IDBDatabase, + storeName: string, + key: IDBValidKey, +): Promise { + return new Promise((resolve, reject) => { + const transaction = database.transaction(storeName, 'readonly') + const request = transaction.objectStore(storeName).get(key) + let result: T | null = null + let settled = false + const rejectOnce = (message: string, error: DOMException | null) => { + if (settled) return + settled = true + reject(new IndexedDBCrossRuntimeReceiptSourceError( + `${message}: ${error?.message ?? 'unknown error'}`, + )) + } + request.onsuccess = () => { result = (request.result as T | undefined) ?? null } + request.onerror = () => rejectOnce('receipt source read failed', request.error) + transaction.oncomplete = () => { + if (settled) return + settled = true + resolve(result) + } + transaction.onerror = () => rejectOnce('receipt source read transaction failed', transaction.error) + transaction.onabort = () => rejectOnce('receipt source read transaction aborted', transaction.error) + }) +} + +function snapshotIJson(value: unknown, label: string): unknown { + try { + assertIJsonValue(value, label) + const snapshot = structuredClone(value) as unknown + assertIJsonValue(snapshot, label) + return snapshot + } catch (error) { + fail(`${label} is not closed I-JSON: ${error instanceof Error ? error.message : String(error)}`) + } +} + +function assertExactKeys(field: string, value: Record, expected: readonly string[]): void { + const actual = Object.keys(value).sort() + const sortedExpected = [...expected].sort() + if (actual.length !== sortedExpected.length || actual.some((key, index) => key !== sortedExpected[index])) { + fail(`${field} has unexpected or missing fields`) + } +} + +function assertResolvedHash(field: string, value: unknown): asserts value is SHA256Hex { + if (typeof value !== 'string' || !HASH_PATTERN.test(value) || value === '0'.repeat(64)) { + fail(`${field} must be a non-zero lowercase SHA-256 root`) + } +} + +function equalBytes(left: Uint8Array, right: Uint8Array): boolean { + if (left.byteLength !== right.byteLength) return false + for (let index = 0; index < left.byteLength; index += 1) { + if (left[index] !== right[index]) return false + } + return true +} + +function isConstraintError(error: unknown): boolean { + return error !== null && typeof error === 'object' && 'name' in error && error.name === 'ConstraintError' +} + +function fail(message: string): never { + throw new IndexedDBCrossRuntimeReceiptSourceError(message) +} diff --git a/sovereign-omega-v2/src/provenance/receipt-resolver.ts b/sovereign-omega-v2/src/provenance/receipt-resolver.ts new file mode 100644 index 000000000..880c66ec3 --- /dev/null +++ b/sovereign-omega-v2/src/provenance/receipt-resolver.ts @@ -0,0 +1,1009 @@ +// ============================================================ +// AEGIS Cross-Runtime Receipt Resolver V1 +// PROVENANCE ASSURANCE: T2->T3 implemented; projection admission pending +// +// Resolution is read-only. It verifies an operator-pinned registry and every +// signed receipt from genesis through the requested terminal receipt, then +// replays lease, fencing, state, and mutation invariants. It never grants +// authority, executes a mutation, or projects an authoritative UI status. +// ============================================================ + +import { canonicalizeJCS } from '../core/canonicalize.js' +import { sha256Hex } from '../core/hashing.js' +import { assertIJsonValue } from '../core/i-json.js' +import { deepFreeze } from '../core/immutable.js' +import type { SHA256Hex } from '../core/types.js' +import { verifyBytes } from '../consensus/crypto.js' +import { + CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + assertCrossRuntimeReceiptIdV1, + assertReceiptTrustRegistryRootV1, + canonicalizeCrossRuntimeReceiptSignatureMessageV1, + canonicalizeReceiptTrustRegistrySignatureMessageV1, + normalizeCrossRuntimeReceiptEnvelopeV1, + normalizeReceiptTrustRegistryV1, +} from './cross-runtime-receipts.js' +import type { + AuthorityLevelV1, + CrossRuntimeReceiptBodyV1, + CrossRuntimeReceiptEnvelopeV1, + CrossRuntimeReceiptKindV1, + CrossRuntimeReceiptOutcomeV1, + DecimalStringV1, + ReceiptTrustKeyEntryV1, + ReceiptTrustRegistryV1, +} from './cross-runtime-receipts.js' + +const ZERO_HASH = '0'.repeat(64) as SHA256Hex +const HASH_PATTERN = /^[0-9a-f]{64}$/ +const DECIMAL_PATTERN = /^(0|[1-9][0-9]*)$/ +const SAFE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/ +const MAX_CHAIN_LENGTH = 4096 +const MAX_CLOCK_SKEW_MS = 300_000n +const VERIFICATION_DECISION_DOMAIN = 'AEGIS_CROSS_RUNTIME_RECEIPT_VERIFICATION_DECISION_V1' +const VERIFIED_CHAIN_DOMAIN = 'AEGIS_CROSS_RUNTIME_VERIFIED_RECEIPT_CHAIN_V1' + +export interface CrossRuntimeReceiptSourceV1 { + resolveReceipt(receiptId: SHA256Hex): Promise + resolveTrustRegistry(registryRoot: SHA256Hex): Promise +} + +export interface TrustedReceiptResolutionContextV1 { + readonly operator_key_id: string + readonly operator_public_key: string + readonly accepted_registry_roots: readonly SHA256Hex[] + readonly observed_at_ms: DecimalStringV1 + readonly max_clock_skew_ms: DecimalStringV1 + readonly expected_actor_identity_root: SHA256Hex + readonly expected_session_identity_root: SHA256Hex + readonly expected_workspace_identity_root: SHA256Hex + readonly expected_holon_identity_root: SHA256Hex + readonly expected_authority_domain: string + readonly expected_authority_level: AuthorityLevelV1 + readonly expected_observed_state_root: SHA256Hex + readonly expected_action_digest: SHA256Hex +} + +export interface VerifiedReceiptTrustRegistryV1 { + readonly registry: ReceiptTrustRegistryV1 + readonly operator_key_id: string + readonly operator_public_key: string +} + +export interface CrossRuntimeReceiptVerificationDecisionV1 { + readonly schema_version: typeof CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION + readonly decision_kind: 'AEGIS_CROSS_RUNTIME_RECEIPT_VERIFICATION_DECISION_V1' + readonly decision: 'VERIFIED' + readonly terminal_receipt_id: SHA256Hex + readonly terminal_receipt_kind: CrossRuntimeReceiptKindV1 + readonly terminal_outcome: CrossRuntimeReceiptOutcomeV1 + readonly chain_digest: SHA256Hex + readonly receipt_count: DecimalStringV1 + readonly registry_roots: readonly SHA256Hex[] + readonly actor_identity_root: SHA256Hex + readonly session_identity_root: SHA256Hex + readonly workspace_identity_root: SHA256Hex + readonly holon_identity_root: SHA256Hex + readonly authority_domain: string + readonly authority_level: AuthorityLevelV1 + readonly authority_receipt_hash: SHA256Hex + readonly lease_id: SHA256Hex + readonly lease_generation: DecimalStringV1 + readonly fencing_token: SHA256Hex + readonly lease_authorization_receipt_hash: SHA256Hex + readonly action_digest: SHA256Hex + readonly before_state_root: SHA256Hex + readonly after_state_root: SHA256Hex + readonly result_digest: SHA256Hex + readonly observed_at_ms: DecimalStringV1 + readonly max_clock_skew_ms: DecimalStringV1 + readonly grants_authority: false + readonly executes_mutation: false + readonly decision_digest: SHA256Hex +} + +export class CrossRuntimeReceiptResolutionError extends Error { + override readonly name = 'CrossRuntimeReceiptResolutionError' + + constructor(message: string) { + super(message) + Object.setPrototypeOf(this, new.target.prototype) + } +} + +interface NormalizedResolutionContext extends TrustedReceiptResolutionContextV1 { + readonly observedAt: bigint + readonly maxClockSkew: bigint +} + +interface ActiveLease { + readonly body: CrossRuntimeReceiptBodyV1 + readonly receiptId: SHA256Hex + readonly expiresAt: bigint +} + +interface MutationState { + readonly admission: CrossRuntimeReceiptEnvelopeV1 + terminal: CrossRuntimeReceiptEnvelopeV1 | null +} + +interface VerifiedRegistryCacheEntry { + readonly verified: VerifiedReceiptTrustRegistryV1 + readonly ancestryRoots: ReadonlySet +} + +export async function verifyReceiptTrustRegistryV1( + value: unknown, + context: TrustedReceiptResolutionContextV1, +): Promise { + const trusted = normalizeResolutionContext(context) + return verifyReceiptTrustRegistryWithNormalizedContextV1(value, trusted) +} + +async function verifyReceiptTrustRegistryWithNormalizedContextV1( + value: unknown, + trusted: NormalizedResolutionContext, +): Promise { + const registry = normalizeReceiptTrustRegistryV1(value) + await assertReceiptTrustRegistryRootV1(registry) + if (!trusted.accepted_registry_roots.includes(registry.registry_root)) { + fail('receipt trust registry root is not explicitly accepted') + } + if (registry.registry_body.operator_key_id !== trusted.operator_key_id) { + fail('receipt trust registry operator key id does not match the pinned key') + } + const version = BigInt(registry.registry_body.registry_version) + if (version < 1n) fail('receipt trust registry version must be positive') + if (version === 1n && registry.registry_body.previous_registry_root !== ZERO_HASH) { + fail('receipt trust registry version 1 must have the genesis previous root') + } + if (version > 1n && registry.registry_body.previous_registry_root === ZERO_HASH) { + fail('rotated receipt trust registry must resolve its previous root') + } + const issuedAt = BigInt(registry.registry_body.issued_at_ms) + if (issuedAt > trusted.observedAt + trusted.maxClockSkew) { + fail('receipt trust registry was issued beyond the allowed clock skew') + } + const valid = await verifyBytes( + trusted.operator_public_key, + canonicalizeReceiptTrustRegistrySignatureMessageV1(registry.registry_body), + registry.proof.signature, + ) + if (!valid) fail('receipt trust registry operator signature is invalid') + return deepFreeze({ + registry, + operator_key_id: trusted.operator_key_id, + operator_public_key: trusted.operator_public_key, + }) +} + +export async function resolveAndVerifyCrossRuntimeReceiptChainV1( + source: CrossRuntimeReceiptSourceV1, + terminalReceiptId: SHA256Hex, + context: TrustedReceiptResolutionContextV1, +): Promise { + assertSource(source) + const trusted = normalizeResolutionContext(context) + const requestedTerminal = assertNonZeroHash('terminal receipt id', terminalReceiptId) + const reverseChain: CrossRuntimeReceiptEnvelopeV1[] = [] + const seenIds = new Set() + let cursor: SHA256Hex = requestedTerminal + while (cursor !== ZERO_HASH) { + if (reverseChain.length >= MAX_CHAIN_LENGTH) fail('receipt chain exceeds the replay bound') + if (seenIds.has(cursor)) fail('receipt parent chain contains a cycle') + seenIds.add(cursor) + const raw = await source.resolveReceipt(cursor) + if (raw === null) fail(`receipt ${cursor} cannot be resolved`) + const receipt = normalizeCrossRuntimeReceiptEnvelopeV1(raw) + await assertCrossRuntimeReceiptIdV1(receipt) + if (receipt.receipt_id !== cursor) fail('resolved receipt does not match its requested content hash') + reverseChain.push(receipt) + cursor = receipt.receipt_body.parent_receipt_hash + } + const chain = reverseChain.reverse() + if (chain.length === 0) fail('receipt chain must not be empty') + + const registryCache = new Map() + let previousRegistryRoot: SHA256Hex | null = null + let previousRegistryVersion = -1n + for (const receipt of chain) { + const verifiedRegistry = await verifyReceiptAgainstRegistry(receipt, source, trusted, registryCache) + const registryVersion = BigInt(verifiedRegistry.verified.registry.registry_body.registry_version) + if (registryVersion < previousRegistryVersion) { + fail('receipt chain rolls back its trust registry version') + } + if (registryVersion === previousRegistryVersion && previousRegistryRoot !== null && + receipt.proof.trust_registry_root !== previousRegistryRoot) { + fail('receipt chain changes registry roots without advancing the registry version') + } + if (registryVersion > previousRegistryVersion && previousRegistryRoot !== null && + !verifiedRegistry.ancestryRoots.has(previousRegistryRoot)) { + fail('receipt chain trust registry rotation does not descend from the prior registry root') + } + previousRegistryRoot = receipt.proof.trust_registry_root + previousRegistryVersion = registryVersion + } + replayReceiptChain(chain) + + const terminal = chain[chain.length - 1]! + if (!isTerminalKind(terminal.receipt_kind)) { + fail(`receipt ${terminal.receipt_kind} is not terminal evidence`) + } + assertTerminalContextBindings(terminal.receipt_body, trusted) + const registry_roots = [...new Set(chain.map(receipt => receipt.proof.trust_registry_root))] + .sort(compareUtf8) as SHA256Hex[] + const chain_digest = await sha256Hex(canonicalizeJCS({ + domain: VERIFIED_CHAIN_DOMAIN, + receipt_ids: chain.map(receipt => receipt.receipt_id), + })) + const body = terminal.receipt_body + const unsigned = deepFreeze({ + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + decision_kind: 'AEGIS_CROSS_RUNTIME_RECEIPT_VERIFICATION_DECISION_V1' as const, + decision: 'VERIFIED' as const, + terminal_receipt_id: terminal.receipt_id, + terminal_receipt_kind: terminal.receipt_kind, + terminal_outcome: body.outcome, + chain_digest, + receipt_count: String(chain.length), + registry_roots, + actor_identity_root: body.actor_identity_root, + session_identity_root: body.session_identity_root, + workspace_identity_root: body.workspace_identity_root, + holon_identity_root: body.holon_identity_root, + authority_domain: body.authority_domain, + authority_level: body.authority_level, + authority_receipt_hash: body.authority_receipt_hash, + lease_id: body.lease_id, + lease_generation: body.lease_generation, + fencing_token: body.fencing_token, + lease_authorization_receipt_hash: body.lease_authorization_receipt_hash, + action_digest: body.action_digest, + before_state_root: body.before_state_root, + after_state_root: body.after_state_root, + result_digest: body.result_digest, + observed_at_ms: trusted.observed_at_ms, + max_clock_skew_ms: trusted.max_clock_skew_ms, + grants_authority: false as const, + executes_mutation: false as const, + }) + assertIJsonValue(unsigned, 'receipt verification decision') + const decision_digest = await sha256Hex(canonicalizeJCS({ + domain: VERIFICATION_DECISION_DOMAIN, + decision: unsigned, + })) + return deepFreeze({ ...unsigned, decision_digest }) +} + +export function normalizeCrossRuntimeReceiptVerificationDecisionV1( + value: unknown, +): CrossRuntimeReceiptVerificationDecisionV1 { + const snapshot = snapshotIJson(value, 'cross-runtime receipt verification decision') + const decision = asObject('receipt verification decision', snapshot) + assertExactKeys('receipt verification decision', decision, [ + 'action_digest', + 'actor_identity_root', + 'after_state_root', + 'authority_domain', + 'authority_level', + 'authority_receipt_hash', + 'before_state_root', + 'chain_digest', + 'decision', + 'decision_digest', + 'decision_kind', + 'executes_mutation', + 'fencing_token', + 'grants_authority', + 'holon_identity_root', + 'lease_authorization_receipt_hash', + 'lease_generation', + 'lease_id', + 'max_clock_skew_ms', + 'observed_at_ms', + 'receipt_count', + 'registry_roots', + 'result_digest', + 'schema_version', + 'session_identity_root', + 'terminal_outcome', + 'terminal_receipt_id', + 'terminal_receipt_kind', + 'workspace_identity_root', + ]) + if (decision.schema_version !== CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION || + decision.decision_kind !== 'AEGIS_CROSS_RUNTIME_RECEIPT_VERIFICATION_DECISION_V1' || + decision.decision !== 'VERIFIED') { + fail('receipt verification decision schema or kind is unsupported') + } + if (decision.grants_authority !== false || decision.executes_mutation !== false) { + fail('receipt verification decision must remain non-authoritative') + } + const terminalKind = assertDecisionReceiptKind(decision.terminal_receipt_kind) + if (!isTerminalKind(terminalKind)) fail('receipt verification decision does not reference terminal evidence') + const terminalOutcome = assertDecisionOutcome(decision.terminal_outcome) + assertTerminalKindOutcome(terminalKind, terminalOutcome) + const registryRoots = asArray('receipt verification decision.registry_roots', decision.registry_roots) + .map((root, index) => assertNonZeroHash(`receipt verification decision.registry_roots[${index}]`, root)) + if (registryRoots.length === 0 || new Set(registryRoots).size !== registryRoots.length) { + fail('receipt verification decision registry roots must be non-empty and unique') + } + assertSortedUtf8('receipt verification decision.registry_roots', registryRoots) + const authorityLevel = decision.authority_level + if (!['D0', 'D1', 'D2', 'D3', 'D4'].includes(String(authorityLevel))) { + fail('receipt verification decision authority level is invalid') + } + const receiptCount = assertDecimal('receipt verification decision.receipt_count', decision.receipt_count) + if (BigInt(receiptCount) < 1n) fail('receipt verification decision receipt count must be positive') + const maxClockSkew = assertDecimal( + 'receipt verification decision.max_clock_skew_ms', decision.max_clock_skew_ms, + ) + if (BigInt(maxClockSkew) > MAX_CLOCK_SKEW_MS) { + fail('receipt verification decision clock skew exceeds the fail-closed bound') + } + return deepFreeze({ + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + decision_kind: 'AEGIS_CROSS_RUNTIME_RECEIPT_VERIFICATION_DECISION_V1', + decision: 'VERIFIED', + terminal_receipt_id: assertNonZeroHash( + 'receipt verification decision.terminal_receipt_id', decision.terminal_receipt_id, + ), + terminal_receipt_kind: terminalKind, + terminal_outcome: terminalOutcome, + chain_digest: assertNonZeroHash('receipt verification decision.chain_digest', decision.chain_digest), + receipt_count: receiptCount, + registry_roots: registryRoots, + actor_identity_root: assertNonZeroHash( + 'receipt verification decision.actor_identity_root', decision.actor_identity_root, + ), + session_identity_root: assertNonZeroHash( + 'receipt verification decision.session_identity_root', decision.session_identity_root, + ), + workspace_identity_root: assertNonZeroHash( + 'receipt verification decision.workspace_identity_root', decision.workspace_identity_root, + ), + holon_identity_root: assertNonZeroHash( + 'receipt verification decision.holon_identity_root', decision.holon_identity_root, + ), + authority_domain: assertSafeId( + 'receipt verification decision.authority_domain', decision.authority_domain, + ), + authority_level: authorityLevel as AuthorityLevelV1, + authority_receipt_hash: assertNonZeroHash( + 'receipt verification decision.authority_receipt_hash', decision.authority_receipt_hash, + ), + lease_id: assertNonZeroHash('receipt verification decision.lease_id', decision.lease_id), + lease_generation: assertDecimal( + 'receipt verification decision.lease_generation', decision.lease_generation, + ), + fencing_token: assertNonZeroHash( + 'receipt verification decision.fencing_token', decision.fencing_token, + ), + lease_authorization_receipt_hash: assertNonZeroHash( + 'receipt verification decision.lease_authorization_receipt_hash', + decision.lease_authorization_receipt_hash, + ), + action_digest: assertNonZeroHash( + 'receipt verification decision.action_digest', decision.action_digest, + ), + before_state_root: assertNonZeroHash( + 'receipt verification decision.before_state_root', decision.before_state_root, + ), + after_state_root: assertNonZeroHash( + 'receipt verification decision.after_state_root', decision.after_state_root, + ), + result_digest: assertNonZeroHash( + 'receipt verification decision.result_digest', decision.result_digest, + ), + observed_at_ms: assertDecimal( + 'receipt verification decision.observed_at_ms', decision.observed_at_ms, + ), + max_clock_skew_ms: maxClockSkew, + grants_authority: false, + executes_mutation: false, + decision_digest: assertNonZeroHash( + 'receipt verification decision.decision_digest', decision.decision_digest, + ), + }) +} + +export async function verifyCrossRuntimeReceiptVerificationDecisionDigestV1( + value: unknown, +): Promise { + const decision = normalizeCrossRuntimeReceiptVerificationDecisionV1(value) + const { decision_digest: _decisionDigest, ...unsigned } = decision + const expected = await sha256Hex(canonicalizeJCS({ + domain: VERIFICATION_DECISION_DOMAIN, + decision: unsigned, + })) + if (decision.decision_digest !== expected) fail('receipt verification decision digest is invalid') + return decision +} + +async function verifyReceiptAgainstRegistry( + receipt: CrossRuntimeReceiptEnvelopeV1, + source: CrossRuntimeReceiptSourceV1, + trusted: NormalizedResolutionContext, + cache: Map, +): Promise { + let cached = cache.get(receipt.proof.trust_registry_root) + if (cached === undefined) { + const raw = await source.resolveTrustRegistry(receipt.proof.trust_registry_root) + if (raw === null) fail(`receipt trust registry ${receipt.proof.trust_registry_root} cannot be resolved`) + const verified = await verifyReceiptTrustRegistryWithNormalizedContextV1(raw, trusted) + const ancestryRoots = await verifyRegistryAncestry(verified.registry, source, trusted) + cached = { verified, ancestryRoots } + cache.set(receipt.proof.trust_registry_root, cached) + } + const registry = cached.verified.registry + if (receipt.proof.trust_registry_version !== registry.registry_body.registry_version) { + fail('receipt trust registry version does not match its content root') + } + const key = registry.registry_body.keys.find(entry => entry.key_id === receipt.proof.signer_key_id) + if (key === undefined) fail('receipt signer key is absent from the trusted registry') + assertReceiptKeyPermission(receipt, registry, key) + const { signature: _signature, ...proof } = receipt.proof + const valid = await verifyBytes( + key.public_key, + canonicalizeCrossRuntimeReceiptSignatureMessageV1({ + schema_version: receipt.schema_version, + receipt_kind: receipt.receipt_kind, + receipt_body: receipt.receipt_body, + proof, + }), + receipt.proof.signature, + ) + if (!valid) fail(`receipt ${receipt.receipt_id} signature is invalid`) + const timestamp = BigInt(receipt.receipt_body.timestamp_ms) + if (timestamp > trusted.observedAt + trusted.maxClockSkew) { + fail(`receipt ${receipt.receipt_id} timestamp exceeds the allowed clock skew`) + } + return cached +} + +async function verifyRegistryAncestry( + registry: ReceiptTrustRegistryV1, + source: CrossRuntimeReceiptSourceV1, + trusted: NormalizedResolutionContext, +): Promise> { + let current = registry + const seen = new Set([current.registry_root]) + for (let depth = 0; depth < 128; depth += 1) { + const version = BigInt(current.registry_body.registry_version) + if (version === 1n) { + if (current.registry_body.previous_registry_root !== ZERO_HASH) { + fail('registry lineage genesis root is broken') + } + return seen + } + const parentRoot = current.registry_body.previous_registry_root + if (parentRoot === ZERO_HASH || seen.has(parentRoot)) fail('registry lineage is broken or cyclic') + seen.add(parentRoot) + const raw = await source.resolveTrustRegistry(parentRoot) + if (raw === null) fail(`previous receipt trust registry ${parentRoot} cannot be resolved`) + const accepted = [...new Set([...trusted.accepted_registry_roots, parentRoot])] + .sort(compareUtf8) as SHA256Hex[] + const parent = await verifyReceiptTrustRegistryWithNormalizedContextV1(raw, { + ...trusted, + accepted_registry_roots: accepted, + }) + if (BigInt(parent.registry.registry_body.registry_version) !== version - 1n) { + fail('registry lineage version is not contiguous') + } + current = parent.registry + } + fail('registry lineage exceeds the replay bound') +} + +function assertReceiptKeyPermission( + receipt: CrossRuntimeReceiptEnvelopeV1, + registry: ReceiptTrustRegistryV1, + key: ReceiptTrustKeyEntryV1, +): void { + if (key.status !== 'ACTIVE') fail('receipt signer key is revoked') + if (key.verifier_identity_root !== receipt.proof.verifier_identity_root) { + fail('receipt verifier identity does not match the trusted key') + } + if (!key.authority_domains.includes(receipt.receipt_body.authority_domain)) { + fail('receipt signer key is not permitted for the authority domain') + } + if (!key.receipt_kinds.includes(receipt.receipt_kind)) { + fail('receipt signer key is not permitted for the receipt kind') + } + const timestamp = BigInt(receipt.receipt_body.timestamp_ms) + const registryValidFrom = BigInt(registry.registry_body.valid_from_ms) + const registryExpiresAt = BigInt(registry.registry_body.expires_at_ms) + if (timestamp < registryValidFrom || timestamp >= registryExpiresAt) { + fail('receipt timestamp is outside the trusted registry validity window') + } + const keyValidFrom = BigInt(key.valid_from_ms) + const keyExpiresAt = BigInt(key.expires_at_ms) + if (timestamp < keyValidFrom || timestamp >= keyExpiresAt) { + fail('receipt timestamp is outside the signer key validity window') + } +} + +function replayReceiptChain( + chain: readonly CrossRuntimeReceiptEnvelopeV1[], +): void { + const activeLeases = new Map() + const lastLeaseGeneration = new Map() + const usedLeaseIds = new Set() + const currentState = new Map() + const mutations = new Map() + const actionClaims = new Set() + const nonces = new Set() + let previousId = ZERO_HASH + let previousTimestamp = -1n + let previousRegistryVersion = -1n + let previousRegistryRoot: SHA256Hex | null = null + + for (let index = 0; index < chain.length; index += 1) { + const receipt = chain[index]! + const body = receipt.receipt_body + const sequence = BigInt(body.receipt_sequence) + const timestamp = BigInt(body.timestamp_ms) + if (sequence !== BigInt(index)) fail('receipt chain sequence is not contiguous from genesis') + if (body.parent_receipt_hash !== previousId) fail('receipt chain parent hash is broken') + if (timestamp < previousTimestamp) fail('receipt chain timestamps are not monotonic') + const registryVersion = BigInt(receipt.proof.trust_registry_version) + if (registryVersion < previousRegistryVersion) fail('receipt chain rolls back its trust registry version') + if (registryVersion === previousRegistryVersion && previousRegistryRoot !== null && + receipt.proof.trust_registry_root !== previousRegistryRoot) { + fail('receipt chain changes registry roots without advancing the registry version') + } + if (nonces.has(body.nonce)) fail('receipt chain reuses a signed nonce') + nonces.add(body.nonce) + previousId = receipt.receipt_id + previousTimestamp = timestamp + previousRegistryVersion = registryVersion + previousRegistryRoot = receipt.proof.trust_registry_root + + const scopeKey = scopeKeyFor(body) + const knownState = currentState.get(scopeKey) + if (knownState === undefined && + receipt.receipt_kind !== 'LEASE_ISSUED' && + receipt.receipt_kind !== 'LEASE_ISSUANCE_DENIED') { + fail('receipt scope has no initialized canonical state') + } + if (knownState !== undefined && body.observed_state_root !== knownState) { + fail('receipt observed state is stale relative to the replayed workspace state') + } + if (body.before_state_root !== body.observed_state_root) { + fail('receipt before state does not match the observed canonical state') + } + if (receipt.receipt_kind !== 'MUTATION_COMPLETED' && body.after_state_root !== body.before_state_root) { + fail(`${receipt.receipt_kind} must leave the canonical state root unchanged`) + } + // A denied first lease attempt attests the presented roots but cannot + // initialize canonical state. Only successful lease issuance establishes + // a new replay scope; all later receipts operate on an existing root. + if (knownState !== undefined || receipt.receipt_kind === 'LEASE_ISSUED') { + currentState.set(scopeKey, body.after_state_root) + } + + if (receipt.receipt_kind.startsWith('LEASE_')) { + if (body.authority_receipt_hash !== ZERO_HASH || body.lease_authorization_receipt_hash !== ZERO_HASH) { + fail('lease receipt must not manufacture authority or lease-authorization receipt roots') + } + } else if (body.authority_receipt_hash === ZERO_HASH || + body.lease_authorization_receipt_hash === ZERO_HASH) { + fail('mutation receipt must resolve authority and lease-authorization receipt roots') + } + + const active = activeLeases.get(scopeKey) + switch (receipt.receipt_kind) { + case 'LEASE_ISSUED': { + if (active !== undefined) fail('lease issuance conflicts with an active writer') + if (usedLeaseIds.has(body.lease_id)) fail('lease id is replayed') + const previousGeneration = lastLeaseGeneration.get(scopeKey) ?? 0n + if (BigInt(body.lease_generation) !== previousGeneration + 1n) { + fail('lease generation does not monotonically fence the previous writer') + } + if (body.fencing_token === ZERO_HASH) fail('issued lease fencing token is unresolved') + if (body.observed_state_root !== body.expected_state_root) { + fail('issued lease expected state is stale') + } + const expiresAt = BigInt(body.expires_at_ms) + if (expiresAt <= timestamp) fail('issued lease is already expired') + activeLeases.set(scopeKey, { body, receiptId: receipt.receipt_id, expiresAt }) + usedLeaseIds.add(body.lease_id) + lastLeaseGeneration.set(scopeKey, BigInt(body.lease_generation)) + break + } + case 'LEASE_ISSUANCE_DENIED': + break + case 'LEASE_RENEWED': { + if (active === undefined) fail('lease renewal has no active lease') + assertRenewalBindings(active.body, body) + if (timestamp >= active.expiresAt) fail('expired lease cannot be renewed') + const expiresAt = BigInt(body.expires_at_ms) + if (expiresAt <= active.expiresAt) fail('lease renewal must extend the expiry') + if (body.observed_state_root !== body.expected_state_root) fail('renewed lease expected state is stale') + activeLeases.set(scopeKey, { body, receiptId: receipt.receipt_id, expiresAt }) + lastLeaseGeneration.set(scopeKey, BigInt(body.lease_generation)) + break + } + case 'LEASE_RENEWAL_DENIED': + break + case 'LEASE_EXPIRED': { + const current = requireActiveLease(active, body, 'lease expiry') + if (timestamp < current.expiresAt) fail('lease expiry receipt predates the active lease expiry') + activeLeases.delete(scopeKey) + break + } + case 'LEASE_REVOKED': + requireActiveLease(active, body, 'lease revocation') + activeLeases.delete(scopeKey) + break + case 'MUTATION_ADMITTED': { + const current = requireActiveLease(active, body, 'mutation admission') + if (timestamp >= current.expiresAt) fail('expired lease cannot admit a mutation') + if (body.observed_state_root !== body.expected_state_root) fail('mutation expected state is stale') + assertLeaseToMutationBindings(current, body) + const mutationKey = mutationKeyFor(body) + const actionKey = actionKeyFor(body) + if (mutations.has(mutationKey) || actionClaims.has(actionKey)) { + fail('duplicate or replayed mutation action') + } + mutations.set(mutationKey, { admission: receipt, terminal: null }) + actionClaims.add(actionKey) + break + } + case 'MUTATION_DENIED': { + const mutationKey = mutationKeyFor(body) + const actionKey = actionKeyFor(body) + const replayed = mutations.has(mutationKey) || actionClaims.has(actionKey) + if (replayed && !body.denial_codes.includes('MUTATION_REPLAY')) { + fail('replayed mutation denial does not attest MUTATION_REPLAY') + } + if (!replayed) { + actionClaims.add(actionKey) + } + break + } + case 'MUTATION_COMPLETED': + case 'MUTATION_CANCELLED': + case 'MUTATION_FAILED': { + const mutation = mutations.get(mutationKeyFor(body)) + if (mutation === undefined) fail('terminal mutation has no admitted parent action') + if (mutation.terminal !== null) fail('mutation action has more than one terminal receipt') + assertMutationAttemptBindings(mutation.admission.receipt_body, body) + if (receipt.receipt_kind === 'MUTATION_COMPLETED') { + const current = requireActiveLease(active, body, 'completed mutation') + if (timestamp >= current.expiresAt) fail('expired lease cannot complete a successful mutation') + if (body.observed_state_root !== body.expected_state_root) { + fail('completed mutation expected state is stale') + } + activeLeases.delete(scopeKey) + } else if (active !== undefined) { + requireActiveLease(active, body, 'terminal mutation') + activeLeases.delete(scopeKey) + } + mutation.terminal = receipt + break + } + } + } +} + +function requireActiveLease( + active: ActiveLease | undefined, + body: CrossRuntimeReceiptBodyV1, + operation: string, +): ActiveLease { + if (active === undefined) fail(`${operation} has no active lease`) + const expected = active.body + if (body.lease_id !== expected.lease_id || + body.lease_generation !== expected.lease_generation || + body.fencing_token !== expected.fencing_token) { + fail(`${operation} presents a stale lease or fencing token`) + } + assertIdentityAndAuthorityBindings(expected, body, operation) + return active +} + +function assertLeaseToMutationBindings( + lease: ActiveLease, + mutation: CrossRuntimeReceiptBodyV1, +): void { + assertIdentityAndAuthorityBindings(lease.body, mutation, 'lease-to-mutation binding') + if (lease.body.action_digest !== mutation.action_digest) fail('lease-to-mutation action digest mismatch') + if (mutation.lease_authorization_receipt_hash !== lease.receiptId) { + fail('mutation lease authorization does not resolve to the active issued or renewed lease receipt') + } +} + +function assertRenewalBindings( + current: CrossRuntimeReceiptBodyV1, + renewal: CrossRuntimeReceiptBodyV1, +): void { + for (const field of [ + 'actor_identity_root', + 'session_identity_root', + 'workspace_identity_root', + 'holon_identity_root', + 'authority_domain', + 'authority_level', + 'lease_id', + 'action_digest', + ] as const) { + if (renewal[field] !== current[field]) fail(`lease renewal ${field} mismatch`) + } + if (BigInt(renewal.lease_generation) !== BigInt(current.lease_generation) + 1n) { + fail('lease renewal must advance the lease generation exactly once') + } + if (renewal.fencing_token === ZERO_HASH || renewal.fencing_token === current.fencing_token) { + fail('lease renewal must issue a new non-zero fencing token') + } +} + +function assertMutationAttemptBindings( + admission: CrossRuntimeReceiptBodyV1, + terminal: CrossRuntimeReceiptBodyV1, +): void { + assertIdentityAndAuthorityBindings(admission, terminal, 'mutation terminal binding') + for (const [actual, expected, label] of [ + [terminal.action_digest, admission.action_digest, 'action digest'], + [terminal.authority_receipt_hash, admission.authority_receipt_hash, 'authority receipt'], + [terminal.lease_authorization_receipt_hash, admission.lease_authorization_receipt_hash, 'lease authorization'], + ] as const) { + if (actual !== expected) fail(`mutation terminal ${label} mismatch`) + } +} + +function assertIdentityAndAuthorityBindings( + expected: CrossRuntimeReceiptBodyV1, + actual: CrossRuntimeReceiptBodyV1, + label: string, +): void { + for (const field of [ + 'actor_identity_root', + 'session_identity_root', + 'workspace_identity_root', + 'holon_identity_root', + 'authority_domain', + 'authority_level', + 'lease_id', + 'lease_generation', + 'fencing_token', + 'action_digest', + ] as const) { + if (actual[field] !== expected[field]) fail(`${label} ${field} mismatch`) + } +} + +function assertTerminalContextBindings( + body: CrossRuntimeReceiptBodyV1, + context: NormalizedResolutionContext, +): void { + const bindings = [ + [body.actor_identity_root, context.expected_actor_identity_root, 'actor identity'], + [body.session_identity_root, context.expected_session_identity_root, 'session identity'], + [body.workspace_identity_root, context.expected_workspace_identity_root, 'workspace identity'], + [body.holon_identity_root, context.expected_holon_identity_root, 'holon identity'], + [body.authority_domain, context.expected_authority_domain, 'authority domain'], + [body.authority_level, context.expected_authority_level, 'authority level'], + [body.observed_state_root, context.expected_observed_state_root, 'observed state'], + [body.action_digest, context.expected_action_digest, 'action digest'], + ] as const + for (const [actual, expected, label] of bindings) { + if (actual !== expected) fail(`terminal receipt ${label} does not match trusted context`) + } +} + +function mutationKeyFor(body: CrossRuntimeReceiptBodyV1): string { + return `${actionScopeKeyFor(body)}\u0000${body.lease_id}` +} + +function actionKeyFor(body: CrossRuntimeReceiptBodyV1): string { + return actionScopeKeyFor(body) +} + +function scopeKeyFor(body: CrossRuntimeReceiptBodyV1): string { + return [ + body.workspace_identity_root, + body.holon_identity_root, + body.authority_domain, + ].join('\u0000') +} + +function actionScopeKeyFor(body: CrossRuntimeReceiptBodyV1): string { + return [ + body.actor_identity_root, + body.session_identity_root, + body.workspace_identity_root, + body.holon_identity_root, + body.authority_domain, + body.action_digest, + ].join('\u0000') +} + +function isTerminalKind(kind: CrossRuntimeReceiptKindV1): boolean { + return !['LEASE_ISSUED', 'LEASE_RENEWED', 'MUTATION_ADMITTED'].includes(kind) +} + +function assertDecisionReceiptKind(value: unknown): CrossRuntimeReceiptKindV1 { + const kinds: readonly CrossRuntimeReceiptKindV1[] = [ + 'LEASE_ISSUED', 'LEASE_ISSUANCE_DENIED', 'LEASE_RENEWED', 'LEASE_RENEWAL_DENIED', + 'LEASE_EXPIRED', 'LEASE_REVOKED', 'MUTATION_ADMITTED', 'MUTATION_DENIED', + 'MUTATION_COMPLETED', 'MUTATION_CANCELLED', 'MUTATION_FAILED', + ] + if (typeof value !== 'string' || !kinds.includes(value as CrossRuntimeReceiptKindV1)) { + fail('receipt verification decision terminal kind is invalid') + } + return value as CrossRuntimeReceiptKindV1 +} + +function assertDecisionOutcome(value: unknown): CrossRuntimeReceiptOutcomeV1 { + const outcomes: readonly CrossRuntimeReceiptOutcomeV1[] = [ + 'ADMITTED', 'DENIED', 'COMPLETED', 'CANCELLED', 'FAILED', 'EXPIRED', 'REVOKED', + ] + if (typeof value !== 'string' || !outcomes.includes(value as CrossRuntimeReceiptOutcomeV1)) { + fail('receipt verification decision terminal outcome is invalid') + } + return value as CrossRuntimeReceiptOutcomeV1 +} + +function assertTerminalKindOutcome( + kind: CrossRuntimeReceiptKindV1, + outcome: CrossRuntimeReceiptOutcomeV1, +): void { + const expected: Readonly> = { + LEASE_ISSUED: 'ADMITTED', + LEASE_ISSUANCE_DENIED: 'DENIED', + LEASE_RENEWED: 'ADMITTED', + LEASE_RENEWAL_DENIED: 'DENIED', + LEASE_EXPIRED: 'EXPIRED', + LEASE_REVOKED: 'REVOKED', + MUTATION_ADMITTED: 'ADMITTED', + MUTATION_DENIED: 'DENIED', + MUTATION_COMPLETED: 'COMPLETED', + MUTATION_CANCELLED: 'CANCELLED', + MUTATION_FAILED: 'FAILED', + } + if (outcome !== expected[kind]) fail('receipt verification decision terminal kind/outcome mismatch') +} + +function normalizeResolutionContext(value: TrustedReceiptResolutionContextV1): NormalizedResolutionContext { + const snapshot = snapshotIJson(value, 'trusted receipt resolution context') + const context = asObject('trusted receipt resolution context', snapshot) + assertExactKeys('trusted receipt resolution context', context, [ + 'accepted_registry_roots', + 'expected_action_digest', + 'expected_actor_identity_root', + 'expected_authority_domain', + 'expected_authority_level', + 'expected_holon_identity_root', + 'expected_observed_state_root', + 'expected_session_identity_root', + 'expected_workspace_identity_root', + 'max_clock_skew_ms', + 'observed_at_ms', + 'operator_key_id', + 'operator_public_key', + ]) + const roots = asArray('accepted_registry_roots', context.accepted_registry_roots) + .map((root, index) => assertNonZeroHash(`accepted_registry_roots[${index}]`, root)) + if (roots.length === 0 || new Set(roots).size !== roots.length) { + fail('accepted_registry_roots must be non-empty and unique') + } + assertSortedUtf8('accepted_registry_roots', roots) + const observed_at_ms = assertDecimal('observed_at_ms', context.observed_at_ms) + const max_clock_skew_ms = assertDecimal('max_clock_skew_ms', context.max_clock_skew_ms) + const maxClockSkew = BigInt(max_clock_skew_ms) + if (maxClockSkew > MAX_CLOCK_SKEW_MS) fail('max_clock_skew_ms exceeds the fail-closed bound') + const authorityLevel = context.expected_authority_level + if (!['D0', 'D1', 'D2', 'D3', 'D4'].includes(String(authorityLevel))) { + fail('expected_authority_level is invalid') + } + return deepFreeze({ + operator_key_id: assertSafeId('operator_key_id', context.operator_key_id), + operator_public_key: assertNonZeroHash('operator_public_key', context.operator_public_key), + accepted_registry_roots: roots, + observed_at_ms, + max_clock_skew_ms, + expected_actor_identity_root: assertNonZeroHash( + 'expected_actor_identity_root', context.expected_actor_identity_root, + ), + expected_session_identity_root: assertNonZeroHash( + 'expected_session_identity_root', context.expected_session_identity_root, + ), + expected_workspace_identity_root: assertNonZeroHash( + 'expected_workspace_identity_root', context.expected_workspace_identity_root, + ), + expected_holon_identity_root: assertNonZeroHash( + 'expected_holon_identity_root', context.expected_holon_identity_root, + ), + expected_authority_domain: assertSafeId( + 'expected_authority_domain', context.expected_authority_domain, + ), + expected_authority_level: authorityLevel as AuthorityLevelV1, + expected_observed_state_root: assertNonZeroHash( + 'expected_observed_state_root', context.expected_observed_state_root, + ), + expected_action_digest: assertNonZeroHash( + 'expected_action_digest', context.expected_action_digest, + ), + observedAt: BigInt(observed_at_ms), + maxClockSkew, + }) +} + +function assertSource(source: CrossRuntimeReceiptSourceV1): void { + if (source === null || typeof source !== 'object' || + typeof source.resolveReceipt !== 'function' || + typeof source.resolveTrustRegistry !== 'function') { + fail('cross-runtime receipt source is unavailable') + } +} + +function snapshotIJson(value: unknown, label: string): unknown { + try { + assertIJsonValue(value, label) + const snapshot = structuredClone(value) as unknown + assertIJsonValue(snapshot, label) + return snapshot + } catch (error) { + fail(`${label} is not closed I-JSON: ${error instanceof Error ? error.message : String(error)}`) + } +} + +function asObject(field: string, value: unknown): Record { + if (value === null || typeof value !== 'object' || Array.isArray(value)) fail(`${field} must be an object`) + return value as Record +} + +function asArray(field: string, value: unknown): readonly unknown[] { + if (!Array.isArray(value)) fail(`${field} must be an array`) + return value +} + +function assertExactKeys(field: string, value: Record, expected: readonly string[]): void { + const actualKeys = Object.keys(value).sort(compareUtf8) + const expectedKeys = [...expected].sort(compareUtf8) + if (actualKeys.length !== expectedKeys.length || + actualKeys.some((key, index) => key !== expectedKeys[index])) { + fail(`${field} has unexpected or missing fields`) + } +} + +function assertNonZeroHash(field: string, value: unknown): SHA256Hex { + if (typeof value !== 'string' || !HASH_PATTERN.test(value) || value === ZERO_HASH) { + fail(`${field} must be a non-zero lowercase SHA-256 root`) + } + return value as SHA256Hex +} + +function assertDecimal(field: string, value: unknown): DecimalStringV1 { + if (typeof value !== 'string' || value.length > 20 || !DECIMAL_PATTERN.test(value)) { + fail(`${field} must be a canonical decimal string of at most 20 digits`) + } + return value +} + +function assertSafeId(field: string, value: unknown): string { + if (typeof value !== 'string' || !SAFE_ID_PATTERN.test(value)) fail(`${field} is invalid`) + return value +} + +function assertSortedUtf8(field: string, values: readonly string[]): void { + for (let index = 1; index < values.length; index += 1) { + if (compareUtf8(values[index - 1]!, values[index]!) >= 0) fail(`${field} must be UTF-8 sorted`) + } +} + +function compareUtf8(left: string, right: string): number { + const leftBytes = new TextEncoder().encode(left) + const rightBytes = new TextEncoder().encode(right) + const length = Math.min(leftBytes.length, rightBytes.length) + for (let index = 0; index < length; index += 1) { + const difference = leftBytes[index]! - rightBytes[index]! + if (difference !== 0) return difference + } + return leftBytes.length - rightBytes.length +} + +function fail(message: string): never { + throw new CrossRuntimeReceiptResolutionError(message) +} diff --git a/sovereign-omega-v2/test/integration/authoritative-outcome-evidence-replay.test.ts b/sovereign-omega-v2/test/integration/authoritative-outcome-evidence-replay.test.ts new file mode 100644 index 000000000..024568b92 --- /dev/null +++ b/sovereign-omega-v2/test/integration/authoritative-outcome-evidence-replay.test.ts @@ -0,0 +1,477 @@ +import { describe, expect, it } from 'vitest' +import type { SHA256Hex } from '../../src/core/types.js' +import { generateKeypair } from '../../src/consensus/crypto.js' +import { MetacognitiveLoop } from '../../src/metacognition/loop.js' +import type { + ReadableOutcomeEvidenceArtifactStore, +} from '../../src/metacognition/outcome-evidence-artifact-store.js' +import { + AUTHORITATIVE_RECEIPT_PROVENANCE_VERIFIED, + replayAuthoritativeOutcomeEvidenceV1, +} from '../../src/metacognition/authoritative-outcome-evidence-replay.js' +import type { + OutcomeEvidenceArtifactV1, +} from '../../src/metacognition/outcome-comparator.js' +import type { + OutcomeReplayEvidenceV1, +} from '../../src/metacognition/outcome-evidence-replay.js' +import { + buildCrossRuntimeReceiptEnvelopeV1, + buildReceiptTrustRegistryV1, +} from '../../src/provenance/cross-runtime-receipts.js' +import type { + CrossRuntimeReceiptBodyV1, + CrossRuntimeReceiptEnvelopeV1, + CrossRuntimeReceiptKindV1, + ReceiptTrustRegistryBodyV1, +} from '../../src/provenance/cross-runtime-receipts.js' +import { + resolveAndVerifyCrossRuntimeReceiptChainV1, + verifyCrossRuntimeReceiptVerificationDecisionDigestV1, +} from '../../src/provenance/receipt-resolver.js' +import type { + CrossRuntimeReceiptSourceV1, + TrustedReceiptResolutionContextV1, +} from '../../src/provenance/receipt-resolver.js' +import { + H, + SEQ, + certifyOutcomeInput, + createOutcomeClosureFixture, + outcomeReplayEvidence, + trustedOutcomeReplayContext, +} from '../helpers/outcome-evidence-fixture.js' +import type { + OutcomeClosureFixture, +} from '../helpers/outcome-evidence-fixture.js' + +const ZERO_HASH = '0'.repeat(64) as SHA256Hex + +interface ReceiptChainFixture { + readonly source: CrossRuntimeReceiptSourceV1 + readonly receipts: Map + readonly terminalReceiptId: SHA256Hex + readonly admissionReceiptId: SHA256Hex + readonly context: TrustedReceiptResolutionContextV1 +} + +interface ReceiptChainOptions { + readonly terminalKind?: 'MUTATION_COMPLETED' | 'MUTATION_CANCELLED' + readonly terminalTimestamp?: string + readonly terminalStateRoot?: SHA256Hex +} + +function trackingStore() { + let persistCalls = 0 + const artifacts = new Map() + const store: ReadableOutcomeEvidenceArtifactStore = { + async persist(artifact) { + persistCalls += 1 + artifacts.set(artifact.artifact_root, artifact) + return { + artifact_root: artifact.artifact_root, + artifact_reference: `memory:${artifact.artifact_root}`, + } + }, + async read(artifactRoot) { + return artifacts.get(artifactRoot) ?? null + }, + } + return { store, persistCalls: () => persistCalls } +} + +function receiptBody( + fixture: OutcomeClosureFixture, + overrides: Partial, +): CrossRuntimeReceiptBodyV1 { + return { + receipt_sequence: '0', + actor_identity_root: fixture.input.authority.execution_identity_root, + session_identity_root: H('1'), + workspace_identity_root: fixture.input.authority.workspace_binding, + holon_identity_root: H('6'), + authority_domain: 'aegis.outcome', + authority_level: 'D2', + authority_receipt_hash: ZERO_HASH, + lease_id: H('5'), + lease_generation: '1', + fencing_token: H('6'), + lease_authorization_receipt_hash: ZERO_HASH, + parent_receipt_hash: ZERO_HASH, + observed_state_root: fixture.input.baseline.snapshot.state_root, + expected_state_root: fixture.input.baseline.snapshot.state_root, + action_digest: fixture.input.authority.requested_action_digest, + before_state_root: fixture.input.baseline.snapshot.state_root, + after_state_root: fixture.input.baseline.snapshot.state_root, + result_digest: H('1'), + timestamp_ms: '1100', + expires_at_ms: '4000', + nonce: 'nonce-lease-issued-0001', + outcome: 'ADMITTED', + denial_codes: [], + ...overrides, + } +} + +async function createReceiptChain( + fixture: OutcomeClosureFixture, + options: ReceiptChainOptions = {}, +): Promise { + const operatorKeypair = await generateKeypair(new Uint8Array(32).fill(41)) + const receiptKeypair = await generateKeypair(new Uint8Array(32).fill(43)) + const verifierIdentityRoot = H('f') + const receiptKinds: CrossRuntimeReceiptKindV1[] = [ + 'LEASE_ISSUED', + 'MUTATION_ADMITTED', + 'MUTATION_CANCELLED', + 'MUTATION_COMPLETED', + ].sort() as CrossRuntimeReceiptKindV1[] + const registryBody: ReceiptTrustRegistryBodyV1 = { + registry_version: '1', + previous_registry_root: ZERO_HASH, + issued_at_ms: '900', + valid_from_ms: '1000', + expires_at_ms: '5000', + operator_key_id: 'receipt-operator-key', + keys: [{ + key_id: 'receipt-signer-key', + public_key: receiptKeypair.publicKey, + verifier_identity_root: verifierIdentityRoot, + valid_from_ms: '1000', + expires_at_ms: '5000', + status: 'ACTIVE', + authority_domains: ['aegis.outcome'], + receipt_kinds: receiptKinds, + }], + } + const registry = await buildReceiptTrustRegistryV1(registryBody, operatorKeypair.privateKey) + const proof = { + algorithm: 'Ed25519' as const, + signer_key_id: 'receipt-signer-key', + verifier_identity_root: verifierIdentityRoot, + trust_registry_version: '1', + trust_registry_root: registry.registry_root, + } + const signReceipt = async ( + receipt_kind: CrossRuntimeReceiptKindV1, + body: CrossRuntimeReceiptBodyV1, + ): Promise => buildCrossRuntimeReceiptEnvelopeV1({ + schema_version: '1.0.0', + receipt_kind, + receipt_body: body, + proof, + }, receiptKeypair.privateKey) + + const lease = await signReceipt('LEASE_ISSUED', receiptBody(fixture, {})) + const admission = await signReceipt('MUTATION_ADMITTED', receiptBody(fixture, { + receipt_sequence: '1', + authority_receipt_hash: fixture.input.authority.authority_receipt_root, + lease_authorization_receipt_hash: lease.receipt_id, + parent_receipt_hash: lease.receipt_id, + result_digest: H('2'), + timestamp_ms: '1200', + nonce: 'nonce-mutation-admit-0002', + })) + const terminalKind = options.terminalKind ?? 'MUTATION_COMPLETED' + const terminalState = options.terminalStateRoot ?? fixture.input.baseline.snapshot.state_root + const cancelled = terminalKind === 'MUTATION_CANCELLED' + const terminal = await signReceipt(terminalKind, receiptBody(fixture, { + receipt_sequence: '2', + authority_receipt_hash: fixture.input.authority.authority_receipt_root, + lease_authorization_receipt_hash: lease.receipt_id, + parent_receipt_hash: admission.receipt_id, + observed_state_root: terminalState, + expected_state_root: terminalState, + before_state_root: terminalState, + after_state_root: cancelled ? terminalState : fixture.input.post_snapshot.state_root, + result_digest: fixture.input.terminal_execution?.provider_result_digest ?? H('e'), + timestamp_ms: options.terminalTimestamp ?? '1300', + nonce: cancelled ? 'nonce-mutation-cancel-0003' : 'nonce-mutation-complete-003', + outcome: cancelled ? 'CANCELLED' : 'COMPLETED', + denial_codes: cancelled ? ['OPERATOR_CANCELLED'] : [], + })) + + const receipts = new Map([ + [lease.receipt_id, lease], + [admission.receipt_id, admission], + [terminal.receipt_id, terminal], + ]) + const registries = new Map([[registry.registry_root, registry]]) + const source: CrossRuntimeReceiptSourceV1 = { + async resolveReceipt(receiptId) { + return receipts.get(receiptId) ?? null + }, + async resolveTrustRegistry(registryRoot) { + return registries.get(registryRoot) ?? null + }, + } + const context: TrustedReceiptResolutionContextV1 = { + operator_key_id: 'receipt-operator-key', + operator_public_key: operatorKeypair.publicKey, + accepted_registry_roots: [registry.registry_root], + observed_at_ms: '4500', + max_clock_skew_ms: '0', + expected_actor_identity_root: fixture.input.authority.execution_identity_root, + expected_session_identity_root: H('1'), + expected_workspace_identity_root: fixture.input.authority.workspace_binding, + expected_holon_identity_root: H('6'), + expected_authority_domain: 'aegis.outcome', + expected_authority_level: 'D2', + expected_observed_state_root: fixture.input.baseline.snapshot.state_root, + expected_action_digest: fixture.input.authority.requested_action_digest, + } + return { + source, + receipts, + terminalReceiptId: terminal.receipt_id, + admissionReceiptId: admission.receipt_id, + context, + } +} + +async function createBoundScenario() { + const fixture = await createOutcomeClosureFixture() + const receiptChain = await createReceiptChain(fixture) + const decision = await resolveAndVerifyCrossRuntimeReceiptChainV1( + receiptChain.source, + receiptChain.terminalReceiptId, + receiptChain.context, + ) + const terminal = fixture.input.terminal_execution + if (terminal === undefined) throw new Error('outcome fixture terminal evidence is unavailable') + const { evidence_certificate: _certificate, ...unsigned } = fixture.input + const input = await certifyOutcomeInput({ + ...unsigned, + terminal_execution: { + ...terminal, + execution_identity_root: decision.actor_identity_root, + workspace_binding: decision.workspace_identity_root, + authority_receipt_root: decision.authority_receipt_hash, + requested_action_digest: decision.action_digest, + lease_authorization_receipt_root: decision.lease_authorization_receipt_hash, + mutation_receipt_root: decision.terminal_receipt_id, + receipt_chain_status: 'VERIFIED', + receipt_chain_verification_root: decision.chain_digest, + durable_status: 'COMPLETED', + outcome: 'SUCCEEDED', + pre_state_root: decision.before_state_root, + post_state_root: decision.after_state_root, + provider_result_digest: decision.result_digest, + }, + }, fixture.verifier, fixture.verifierKeypair) + return { fixture, receiptChain, decision, evidence: outcomeReplayEvidence(fixture, input) } +} + +async function expectRejectedWithoutPersistence( + fixture: OutcomeClosureFixture, + chain: ReceiptChainFixture, + terminalReceiptId: SHA256Hex = chain.terminalReceiptId, + context: TrustedReceiptResolutionContextV1 = chain.context, +): Promise { + const tracked = trackingStore() + const loop = MetacognitiveLoop.empty() + await expect(replayAuthoritativeOutcomeEvidenceV1( + loop, + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture), + chain.source, + terminalReceiptId, + context, + )).rejects.toThrow() + expect(tracked.persistCalls()).toBe(0) + expect(loop.length).toBe(0) +} + +describe('authoritative outcome evidence replay', () => { + it('resolves completion provenance before persisting and remains non-authoritative', async () => { + const scenario = await createBoundScenario() + const tracked = trackingStore() + const observed = await replayAuthoritativeOutcomeEvidenceV1( + MetacognitiveLoop.empty(), + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(scenario.fixture), + scenario.evidence, + scenario.receiptChain.source, + scenario.receiptChain.terminalReceiptId, + scenario.receiptChain.context, + ) + + expect(observed.provenance_status).toBe(AUTHORITATIVE_RECEIPT_PROVENANCE_VERIFIED) + expect(observed.provenance_decision).toEqual(scenario.decision) + expect(observed.provenance_decision.grants_authority).toBe(false) + expect(observed.provenance_decision.executes_mutation).toBe(false) + expect(observed.assessment.grants_authority).toBe(false) + expect(observed.assessment.executes_mutation).toBe(false) + expect(observed.assessment.updates_competence).toBe(false) + expect(tracked.persistCalls()).toBe(1) + expect(observed.loop.length).toBe(1) + }) + + it('returns a verification decision that survives JSON round-trip and digest verification', async () => { + const scenario = await createBoundScenario() + const tracked = trackingStore() + const observed = await replayAuthoritativeOutcomeEvidenceV1( + MetacognitiveLoop.empty(), + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(scenario.fixture), + scenario.evidence, + scenario.receiptChain.source, + scenario.receiptChain.terminalReceiptId, + scenario.receiptChain.context, + ) + const roundTripped: unknown = JSON.parse(JSON.stringify(observed.provenance_decision)) + await expect(verifyCrossRuntimeReceiptVerificationDecisionDigestV1(roundTripped)) + .resolves.toEqual(observed.provenance_decision) + }) + + it('rejects a tampered terminal receipt before artifact persistence', async () => { + const fixture = await createOutcomeClosureFixture() + const chain = await createReceiptChain(fixture) + const stored = chain.receipts.get(chain.terminalReceiptId) as CrossRuntimeReceiptEnvelopeV1 + chain.receipts.set(chain.terminalReceiptId, { + ...stored, + receipt_body: { ...stored.receipt_body, result_digest: H('4') }, + }) + await expectRejectedWithoutPersistence(fixture, chain) + }) + + it('rejects an untrusted registry before artifact persistence', async () => { + const fixture = await createOutcomeClosureFixture() + const chain = await createReceiptChain(fixture) + await expectRejectedWithoutPersistence(fixture, chain, chain.terminalReceiptId, { + ...chain.context, + accepted_registry_roots: [H('4')], + }) + }) + + it('rejects completion under an expired lease before artifact persistence', async () => { + const fixture = await createOutcomeClosureFixture() + const chain = await createReceiptChain(fixture, { terminalTimestamp: '4000' }) + await expectRejectedWithoutPersistence(fixture, chain) + }) + + it('rejects a stale terminal state before artifact persistence', async () => { + const fixture = await createOutcomeClosureFixture() + const chain = await createReceiptChain(fixture, { terminalStateRoot: H('4') }) + await expectRejectedWithoutPersistence(fixture, chain) + }) + + it('rejects a broken parent chain before artifact persistence', async () => { + const fixture = await createOutcomeClosureFixture() + const chain = await createReceiptChain(fixture) + chain.receipts.delete(chain.admissionReceiptId) + await expectRejectedWithoutPersistence(fixture, chain) + }) + + it('rejects outcome evidence not exactly bound to the verified receipt decision', async () => { + const fixture = await createOutcomeClosureFixture() + const chain = await createReceiptChain(fixture) + const tracked = trackingStore() + const loop = MetacognitiveLoop.empty() + await expect(replayAuthoritativeOutcomeEvidenceV1( + loop, + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(fixture), + outcomeReplayEvidence(fixture), + chain.source, + chain.terminalReceiptId, + chain.context, + )).rejects.toThrow('authoritative and legacy lease authorization receipt binding mismatch') + expect(tracked.persistCalls()).toBe(0) + expect(loop.length).toBe(0) + }) + + it('rejects non-admitted legacy authority or lease outcomes with zero writes', async () => { + const scenario = await createBoundScenario() + const deniedAuthority: OutcomeReplayEvidenceV1 = { + ...scenario.evidence, + input: { + ...scenario.evidence.input, + authority: { + ...scenario.evidence.input.authority, + outcome: 'DENIED', + denial_codes: ['APPROVAL_MISSING'], + }, + }, + } + const deniedLease: OutcomeReplayEvidenceV1 = { + ...scenario.evidence, + input: { + ...scenario.evidence.input, + terminal_execution: { + ...scenario.evidence.input.terminal_execution!, + lease_outcome: 'DENIED', + }, + }, + } + + for (const [evidence, message] of [ + [deniedAuthority, 'legacy authority admission outcome binding mismatch'], + [deniedLease, 'legacy lease admission outcome binding mismatch'], + ] as const) { + const tracked = trackingStore() + const loop = MetacognitiveLoop.empty() + await expect(replayAuthoritativeOutcomeEvidenceV1( + loop, + tracked.store, + SEQ(1), + trustedOutcomeReplayContext(scenario.fixture), + evidence, + scenario.receiptChain.source, + scenario.receiptChain.terminalReceiptId, + scenario.receiptChain.context, + )).rejects.toThrow(message) + expect(tracked.persistCalls()).toBe(0) + expect(loop.length).toBe(0) + } + }) + + it('rejects cancelled and incomplete chains before artifact persistence', async () => { + const cancelledFixture = await createOutcomeClosureFixture() + const cancelled = await createReceiptChain(cancelledFixture, { + terminalKind: 'MUTATION_CANCELLED', + }) + await expectRejectedWithoutPersistence(cancelledFixture, cancelled) + + const incompleteFixture = await createOutcomeClosureFixture() + const incomplete = await createReceiptChain(incompleteFixture) + await expectRejectedWithoutPersistence( + incompleteFixture, + incomplete, + incomplete.admissionReceiptId, + ) + }) + + it('rejects replay against an advanced loop with zero new artifact writes', async () => { + const scenario = await createBoundScenario() + const initialStore = trackingStore() + const first = await replayAuthoritativeOutcomeEvidenceV1( + MetacognitiveLoop.empty(), + initialStore.store, + SEQ(1), + trustedOutcomeReplayContext(scenario.fixture), + scenario.evidence, + scenario.receiptChain.source, + scenario.receiptChain.terminalReceiptId, + scenario.receiptChain.context, + ) + const replayStore = trackingStore() + await expect(replayAuthoritativeOutcomeEvidenceV1( + first.loop, + replayStore.store, + SEQ(2), + trustedOutcomeReplayContext(scenario.fixture), + scenario.evidence, + scenario.receiptChain.source, + scenario.receiptChain.terminalReceiptId, + scenario.receiptChain.context, + )).rejects.toThrow() + expect(replayStore.persistCalls()).toBe(0) + expect(first.loop.length).toBe(1) + }) +}) diff --git a/sovereign-omega-v2/test/unit/cross-runtime-receipt-provenance.test.ts b/sovereign-omega-v2/test/unit/cross-runtime-receipt-provenance.test.ts new file mode 100644 index 000000000..0a3bbabc2 --- /dev/null +++ b/sovereign-omega-v2/test/unit/cross-runtime-receipt-provenance.test.ts @@ -0,0 +1,813 @@ +import 'fake-indexeddb/auto' +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { describe, expect, it } from 'vitest' +import { canonicalizeJCS } from '../../src/core/canonicalize.js' +import type { SHA256Hex } from '../../src/core/types.js' +import { generateKeypair } from '../../src/consensus/crypto.js' +import { + CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + buildCrossRuntimeReceiptEnvelopeV1, + buildReceiptTrustRegistryV1, + deriveCrossRuntimeReceiptIdV1, + normalizeCrossRuntimeReceiptEnvelopeV1, + type CrossRuntimeReceiptBodyV1, + type CrossRuntimeReceiptDraftV1, + type CrossRuntimeReceiptEnvelopeV1, + type CrossRuntimeReceiptKindV1, + type ReceiptTrustRegistryV1, +} from '../../src/provenance/cross-runtime-receipts.js' +import { + resolveAndVerifyCrossRuntimeReceiptChainV1, + verifyCrossRuntimeReceiptVerificationDecisionDigestV1, + type CrossRuntimeReceiptSourceV1, + type TrustedReceiptResolutionContextV1, +} from '../../src/provenance/receipt-resolver.js' +import { IndexedDBCrossRuntimeReceiptSourceV1 } from '../../src/provenance/indexeddb-receipt-source.js' +import { buildTypeScriptCrossRuntimeVectorV1 } from '../../scripts/generate-authoritative-receipt-vector.js' + +const H = (digit: string): SHA256Hex => digit.repeat(64) as SHA256Hex +const ZERO = H('0') +const ALL_KINDS: readonly CrossRuntimeReceiptKindV1[] = [ + 'LEASE_ISSUED', + 'LEASE_ISSUANCE_DENIED', + 'LEASE_RENEWED', + 'LEASE_RENEWAL_DENIED', + 'LEASE_EXPIRED', + 'LEASE_REVOKED', + 'MUTATION_ADMITTED', + 'MUTATION_DENIED', + 'MUTATION_COMPLETED', + 'MUTATION_CANCELLED', + 'MUTATION_FAILED', +].sort() as CrossRuntimeReceiptKindV1[] + +interface Fixture { + readonly operatorPrivateKey: Uint8Array + readonly receiptPrivateKey: Uint8Array + readonly registry: ReceiptTrustRegistryV1 + readonly receipts: readonly [ + CrossRuntimeReceiptEnvelopeV1, + CrossRuntimeReceiptEnvelopeV1, + CrossRuntimeReceiptEnvelopeV1, + ] + readonly context: TrustedReceiptResolutionContextV1 +} + +class MemorySource implements CrossRuntimeReceiptSourceV1 { + readonly receipts = new Map() + readonly registries = new Map() + + resolveReceipt(receiptId: SHA256Hex): Promise { + return Promise.resolve(this.receipts.get(receiptId) ?? null) + } + + resolveTrustRegistry(registryRoot: SHA256Hex): Promise { + return Promise.resolve(this.registries.get(registryRoot) ?? null) + } +} + +async function fixture(): Promise { + const operator = await generateKeypair(new Uint8Array(32).fill(1)) + const receiptSigner = await generateKeypair(new Uint8Array(32).fill(2)) + const verifierIdentity = H('e') + const registry = await buildReceiptTrustRegistryV1({ + registry_version: '1', + previous_registry_root: ZERO, + issued_at_ms: '500', + valid_from_ms: '1000', + expires_at_ms: '100000', + operator_key_id: 'operator-key-1', + keys: [{ + key_id: 'receipt-key-1', + public_key: receiptSigner.publicKey, + verifier_identity_root: verifierIdentity, + valid_from_ms: '1000', + expires_at_ms: '100000', + status: 'ACTIVE', + authority_domains: ['repo/main'], + receipt_kinds: ALL_KINDS, + }], + }, operator.privateKey) + const proof: CrossRuntimeReceiptDraftV1['proof'] = { + algorithm: 'Ed25519', + signer_key_id: 'receipt-key-1', + verifier_identity_root: verifierIdentity, + trust_registry_version: '1', + trust_registry_root: registry.registry_root, + } + const issuedBody = body({ + receipt_sequence: '0', + parent_receipt_hash: ZERO, + timestamp_ms: '2000', + expires_at_ms: '4500', + nonce: 'nonce-receipt-0001', + authority_receipt_hash: ZERO, + lease_authorization_receipt_hash: ZERO, + }) + const issued = await buildCrossRuntimeReceiptEnvelopeV1({ + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + receipt_kind: 'LEASE_ISSUED', + receipt_body: issuedBody, + proof, + }, receiptSigner.privateKey) + const admittedBody = body({ + receipt_sequence: '1', + parent_receipt_hash: issued.receipt_id, + timestamp_ms: '3000', + expires_at_ms: '4500', + nonce: 'nonce-receipt-0002', + authority_receipt_hash: H('a'), + lease_authorization_receipt_hash: issued.receipt_id, + }) + const admitted = await buildCrossRuntimeReceiptEnvelopeV1({ + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + receipt_kind: 'MUTATION_ADMITTED', + receipt_body: admittedBody, + proof, + }, receiptSigner.privateKey) + const completedBody = body({ + receipt_sequence: '2', + parent_receipt_hash: admitted.receipt_id, + timestamp_ms: '4000', + expires_at_ms: '4500', + nonce: 'nonce-receipt-0003', + authority_receipt_hash: H('a'), + lease_authorization_receipt_hash: issued.receipt_id, + after_state_root: H('c'), + result_digest: H('d'), + outcome: 'COMPLETED', + }) + const completed = await buildCrossRuntimeReceiptEnvelopeV1({ + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + receipt_kind: 'MUTATION_COMPLETED', + receipt_body: completedBody, + proof, + }, receiptSigner.privateKey) + return { + operatorPrivateKey: operator.privateKey, + receiptPrivateKey: receiptSigner.privateKey, + registry, + receipts: [issued, admitted, completed], + context: { + operator_key_id: 'operator-key-1', + operator_public_key: operator.publicKey, + accepted_registry_roots: [registry.registry_root], + observed_at_ms: '5000', + max_clock_skew_ms: '100', + expected_actor_identity_root: H('1'), + expected_session_identity_root: H('2'), + expected_workspace_identity_root: H('3'), + expected_holon_identity_root: H('4'), + expected_authority_domain: 'repo/main', + expected_authority_level: 'D2', + expected_observed_state_root: H('7'), + expected_action_digest: H('8'), + }, + } +} + +function body(overrides: Partial = {}): CrossRuntimeReceiptBodyV1 { + return { + receipt_sequence: '0', + actor_identity_root: H('1'), + session_identity_root: H('2'), + workspace_identity_root: H('3'), + holon_identity_root: H('4'), + authority_domain: 'repo/main', + authority_level: 'D2', + authority_receipt_hash: ZERO, + lease_id: H('5'), + lease_generation: '1', + fencing_token: H('6'), + lease_authorization_receipt_hash: ZERO, + parent_receipt_hash: ZERO, + observed_state_root: H('7'), + expected_state_root: H('7'), + action_digest: H('8'), + before_state_root: H('7'), + after_state_root: H('7'), + result_digest: H('9'), + timestamp_ms: '2000', + expires_at_ms: '9000', + nonce: 'nonce-receipt-0001', + outcome: 'ADMITTED', + denial_codes: [], + ...overrides, + } +} + +function memorySource(value: Fixture): MemorySource { + const source = new MemorySource() + source.registries.set(value.registry.registry_root, value.registry) + for (const receipt of value.receipts) source.receipts.set(receipt.receipt_id, receipt) + return source +} + +async function rebuild( + original: CrossRuntimeReceiptEnvelopeV1, + privateKey: Uint8Array, + updates: { + readonly kind?: CrossRuntimeReceiptKindV1 + readonly body?: Partial + }, +): Promise { + const { signature: _signature, ...proof } = original.proof + return buildCrossRuntimeReceiptEnvelopeV1({ + schema_version: original.schema_version, + receipt_kind: updates.kind ?? original.receipt_kind, + receipt_body: { ...original.receipt_body, ...updates.body }, + proof, + }, privateKey) +} + +async function rebuildUnderRegistry( + original: CrossRuntimeReceiptEnvelopeV1, + registry: ReceiptTrustRegistryV1, + privateKey: Uint8Array, + updates: { + readonly kind?: CrossRuntimeReceiptKindV1 + readonly body?: Partial + } = {}, +): Promise { + const { signature: _signature, ...proof } = original.proof + return buildCrossRuntimeReceiptEnvelopeV1({ + schema_version: original.schema_version, + receipt_kind: updates.kind ?? original.receipt_kind, + receipt_body: { ...original.receipt_body, ...updates.body }, + proof: { + ...proof, + trust_registry_version: registry.registry_body.registry_version, + trust_registry_root: registry.registry_root, + }, + }, privateKey) +} + +describe('cross-runtime authoritative receipt provenance', () => { + it('builds, independently verifies, and round-trips a deterministic success decision', async () => { + const value = await fixture() + const source = memorySource(value) + const decision = await resolveAndVerifyCrossRuntimeReceiptChainV1( + source, value.receipts[2].receipt_id, value.context, + ) + expect(decision.decision).toBe('VERIFIED') + expect(decision.receipt_count).toBe('3') + expect(decision.authority_receipt_hash).toBe(H('a')) + expect(decision.lease_authorization_receipt_hash).toBe(value.receipts[0].receipt_id) + expect(decision.before_state_root).toBe(H('7')) + expect(decision.after_state_root).toBe(H('c')) + expect(decision.result_digest).toBe(H('d')) + expect(decision.grants_authority).toBe(false) + expect(decision.executes_mutation).toBe(false) + await expect(verifyCrossRuntimeReceiptVerificationDecisionDigestV1( + JSON.parse(JSON.stringify(decision)), + )).resolves.toEqual(decision) + await expect(verifyCrossRuntimeReceiptVerificationDecisionDigestV1({ + ...decision, + result_digest: H('f'), + })).rejects.toThrow('decision digest is invalid') + }) + + it('rejects non-I-JSON values, schema drift, and noncanonical signed arrays', async () => { + const value = await fixture() + expect(() => normalizeCrossRuntimeReceiptEnvelopeV1({ + ...value.receipts[0], + unsigned_extension: true, + })).toThrow('unexpected or missing fields') + expect(() => normalizeCrossRuntimeReceiptEnvelopeV1({ + ...value.receipts[0], + receipt_body: { ...value.receipts[0].receipt_body, denial_codes: undefined }, + })).toThrow('closed I-JSON') + const denied = { + ...value.receipts[0], + receipt_kind: 'LEASE_ISSUANCE_DENIED', + receipt_body: { + ...value.receipts[0].receipt_body, + outcome: 'DENIED', + denial_codes: ['Z_REASON', 'A_REASON'], + }, + } + expect(() => normalizeCrossRuntimeReceiptEnvelopeV1(denied)).toThrow('strictly sorted') + await expect(rebuild(value.receipts[1], value.receiptPrivateKey, { + body: { fencing_token: ZERO }, + })).rejects.toThrow('fencing_token must be resolved') + await expect(rebuild(value.receipts[0], value.receiptPrivateKey, { + body: { authority_receipt_hash: H('a') }, + })).rejects.toThrow('must not carry mutation authority') + }) + + it('rejects signature tampering even when the attacker recomputes the content id', async () => { + const value = await fixture() + const terminal = value.receipts[2] + const { receipt_id: _receiptId, ...signed } = terminal + const signature = terminal.proof.signature + const tamperedSigned = { + ...signed, + proof: { + ...signed.proof, + signature: `${signature[0] === '0' ? '1' : '0'}${signature.slice(1)}`, + }, + } + const tampered = normalizeCrossRuntimeReceiptEnvelopeV1({ + ...tamperedSigned, + receipt_id: await deriveCrossRuntimeReceiptIdV1(tamperedSigned), + }) + const source = memorySource(value) + source.receipts.set(tampered.receipt_id, tampered) + await expect(resolveAndVerifyCrossRuntimeReceiptChainV1( + source, tampered.receipt_id, value.context, + )).rejects.toThrow('signature is invalid') + }) + + it.each([ + ['stale fence', { fencing_token: H('f') }, 'stale lease or fencing token'], + ['expired lease', { timestamp_ms: '4500' }, 'expired lease cannot admit'], + ['stale expected state', { expected_state_root: H('f') }, 'expected state is stale'], + ] as const)('rejects a signed %s mutation admission', async (_label, updates, message) => { + const value = await fixture() + const admitted = await rebuild(value.receipts[1], value.receiptPrivateKey, { body: updates }) + const source = new MemorySource() + source.registries.set(value.registry.registry_root, value.registry) + source.receipts.set(value.receipts[0].receipt_id, value.receipts[0]) + source.receipts.set(admitted.receipt_id, admitted) + await expect(resolveAndVerifyCrossRuntimeReceiptChainV1( + source, admitted.receipt_id, value.context, + )).rejects.toThrow(message) + }) + + it('rejects broken parents, unknown trust roots, and future receipts', async () => { + const value = await fixture() + const broken = await rebuild(value.receipts[2], value.receiptPrivateKey, { + body: { parent_receipt_hash: H('f') }, + }) + const brokenSource = memorySource(value) + brokenSource.receipts.set(broken.receipt_id, broken) + await expect(resolveAndVerifyCrossRuntimeReceiptChainV1( + brokenSource, broken.receipt_id, value.context, + )).rejects.toThrow('cannot be resolved') + + await expect(resolveAndVerifyCrossRuntimeReceiptChainV1( + memorySource(value), + value.receipts[2].receipt_id, + { ...value.context, accepted_registry_roots: [H('f')] }, + )).rejects.toThrow('not explicitly accepted') + + const future = await rebuild(value.receipts[2], value.receiptPrivateKey, { + body: { timestamp_ms: '5200' }, + }) + const futureSource = memorySource(value) + futureSource.receipts.set(future.receipt_id, future) + await expect(resolveAndVerifyCrossRuntimeReceiptChainV1( + futureSource, future.receipt_id, value.context, + )).rejects.toThrow('clock skew') + }) + + it('verifies a terminal denial and proves the canonical state root is unchanged', async () => { + const value = await fixture() + const denied = await rebuild(value.receipts[1], value.receiptPrivateKey, { + kind: 'MUTATION_DENIED', + body: { + outcome: 'DENIED', + denial_codes: ['STALE_EXPECTED_STATE'], + expected_state_root: H('f'), + after_state_root: H('7'), + }, + }) + const source = new MemorySource() + source.registries.set(value.registry.registry_root, value.registry) + source.receipts.set(value.receipts[0].receipt_id, value.receipts[0]) + source.receipts.set(denied.receipt_id, denied) + const decision = await resolveAndVerifyCrossRuntimeReceiptChainV1( + source, denied.receipt_id, value.context, + ) + expect(decision.terminal_outcome).toBe('DENIED') + expect(decision.before_state_root).toBe(H('7')) + expect(decision.after_state_root).toBe(H('7')) + }) + + it('verifies and restart-resolves a signed replay denial without admitting a second mutation', async () => { + const value = await fixture() + const replayDenied = await rebuild(value.receipts[2], value.receiptPrivateKey, { + kind: 'MUTATION_DENIED', + body: { + receipt_sequence: '3', + parent_receipt_hash: value.receipts[2].receipt_id, + observed_state_root: H('c'), + expected_state_root: H('c'), + before_state_root: H('c'), + after_state_root: H('c'), + timestamp_ms: '4100', + nonce: 'nonce-replay-denied1', + outcome: 'DENIED', + denial_codes: ['MUTATION_REPLAY'], + }, + }) + const context = { + ...value.context, + expected_observed_state_root: H('c'), + } + const source = memorySource(value) + source.receipts.set(replayDenied.receipt_id, replayDenied) + await expect(resolveAndVerifyCrossRuntimeReceiptChainV1( + source, + replayDenied.receipt_id, + context, + )).resolves.toMatchObject({ + terminal_receipt_kind: 'MUTATION_DENIED', + before_state_root: H('c'), + after_state_root: H('c'), + }) + + const databaseName = `receipt-replay-${crypto.randomUUID()}` + const store = new IndexedDBCrossRuntimeReceiptSourceV1(databaseName) + await store.open() + await store.persistBatch([value.registry], [...value.receipts, replayDenied]) + store.close() + const reopened = new IndexedDBCrossRuntimeReceiptSourceV1(databaseName) + await reopened.open() + await expect(resolveAndVerifyCrossRuntimeReceiptChainV1( + reopened, + replayDenied.receipt_id, + context, + )).resolves.toMatchObject({ terminal_receipt_kind: 'MUTATION_DENIED' }) + reopened.close() + }) + + it('rejects reuse of a previously issued lease id after the original lease closes', async () => { + const value = await fixture() + const reused = await rebuild(value.receipts[2], value.receiptPrivateKey, { + kind: 'LEASE_ISSUED', + body: { + receipt_sequence: '3', + parent_receipt_hash: value.receipts[2].receipt_id, + observed_state_root: H('c'), + expected_state_root: H('c'), + before_state_root: H('c'), + after_state_root: H('c'), + action_digest: H('f'), + lease_generation: '2', + fencing_token: H('f'), + authority_receipt_hash: ZERO, + lease_authorization_receipt_hash: ZERO, + timestamp_ms: '4100', + expires_at_ms: '6000', + nonce: 'nonce-lease-reuse-01', + outcome: 'ADMITTED', + denial_codes: [], + }, + }) + const revoked = await rebuild(reused, value.receiptPrivateKey, { + kind: 'LEASE_REVOKED', + body: { + receipt_sequence: '4', + parent_receipt_hash: reused.receipt_id, + timestamp_ms: '4200', + nonce: 'nonce-lease-reuse-r1', + outcome: 'REVOKED', + denial_codes: ['LEASE_REVOKED'], + }, + }) + const source = memorySource(value) + source.receipts.set(reused.receipt_id, reused) + source.receipts.set(revoked.receipt_id, revoked) + await expect(resolveAndVerifyCrossRuntimeReceiptChainV1( + source, + revoked.receipt_id, + { + ...value.context, + expected_observed_state_root: H('c'), + expected_action_digest: H('f'), + }, + )).rejects.toThrow('lease id is replayed') + }) + + it('rejects cancellation that presents a stale fence after lease renewal', async () => { + const value = await fixture() + const renewed = await rebuild(value.receipts[1], value.receiptPrivateKey, { + kind: 'LEASE_RENEWED', + body: { + receipt_sequence: '2', + parent_receipt_hash: value.receipts[1].receipt_id, + lease_generation: '2', + fencing_token: H('f'), + authority_receipt_hash: ZERO, + lease_authorization_receipt_hash: ZERO, + timestamp_ms: '3500', + expires_at_ms: '5500', + nonce: 'nonce-renew-before-c1', + }, + }) + const cancelled = await rebuild(value.receipts[1], value.receiptPrivateKey, { + kind: 'MUTATION_CANCELLED', + body: { + receipt_sequence: '3', + parent_receipt_hash: renewed.receipt_id, + timestamp_ms: '3600', + nonce: 'nonce-stale-cancel-1', + outcome: 'CANCELLED', + denial_codes: ['MUTATION_CANCELLED'], + }, + }) + const source = memorySource(value) + source.receipts.set(renewed.receipt_id, renewed) + source.receipts.set(cancelled.receipt_id, cancelled) + await expect(resolveAndVerifyCrossRuntimeReceiptChainV1( + source, + cancelled.receipt_id, + value.context, + )).rejects.toThrow('stale lease or fencing token') + }) + + it('does not let a denied genesis lease attempt initialize canonical state', async () => { + const value = await fixture() + const { signature: _signature, ...proof } = value.receipts[0].proof + const denied = await buildCrossRuntimeReceiptEnvelopeV1({ + schema_version: CROSS_RUNTIME_RECEIPT_SCHEMA_VERSION, + receipt_kind: 'LEASE_ISSUANCE_DENIED', + receipt_body: body({ + receipt_sequence: '0', + parent_receipt_hash: ZERO, + observed_state_root: H('f'), + expected_state_root: H('f'), + before_state_root: H('f'), + after_state_root: H('f'), + fencing_token: ZERO, + expires_at_ms: '1999', + nonce: 'nonce-denied-genesis', + outcome: 'DENIED', + denial_codes: ['LEASE_EXPIRY_INVALID'], + }), + proof, + }, value.receiptPrivateKey) + const issued = await rebuild(value.receipts[0], value.receiptPrivateKey, { + body: { + receipt_sequence: '1', + parent_receipt_hash: denied.receipt_id, + nonce: 'nonce-issued-after-denial', + }, + }) + const revoked = await rebuild(issued, value.receiptPrivateKey, { + kind: 'LEASE_REVOKED', + body: { + receipt_sequence: '2', + parent_receipt_hash: issued.receipt_id, + timestamp_ms: '3000', + nonce: 'nonce-revoke-after-denial', + outcome: 'REVOKED', + denial_codes: ['OPERATOR_REVOKED'], + }, + }) + const source = new MemorySource() + source.registries.set(value.registry.registry_root, value.registry) + for (const receipt of [denied, issued, revoked]) { + source.receipts.set(receipt.receipt_id, receipt) + } + + const decision = await resolveAndVerifyCrossRuntimeReceiptChainV1( + source, + revoked.receipt_id, + value.context, + ) + expect(decision.before_state_root).toBe(H('7')) + expect(decision.after_state_root).toBe(H('7')) + }) + + it.each([ + ['expiry', 'LEASE_EXPIRED', 'EXPIRED', 'MUTATION_CANCELLED', 'CANCELLED'], + ['revocation', 'LEASE_REVOKED', 'REVOKED', 'MUTATION_FAILED', 'FAILED'], + ] as const)( + 'allows a %s receipt to be followed by a resolvable mutation terminal receipt', + async (_label, leaseKind, leaseOutcome, terminalKind, terminalOutcome) => { + const value = await fixture() + const leaseTerminal = await rebuild(value.receipts[2], value.receiptPrivateKey, { + kind: leaseKind, + body: { + parent_receipt_hash: value.receipts[1].receipt_id, + timestamp_ms: leaseKind === 'LEASE_EXPIRED' ? '4500' : '3500', + authority_receipt_hash: ZERO, + lease_authorization_receipt_hash: ZERO, + after_state_root: H('7'), + outcome: leaseOutcome, + denial_codes: [leaseKind === 'LEASE_EXPIRED' ? 'LEASE_WINDOW_ELAPSED' : 'LEASE_REVOKED_BY_POLICY'], + nonce: 'nonce-receipt-0003', + }, + }) + const mutationTerminal = await rebuild(value.receipts[2], value.receiptPrivateKey, { + kind: terminalKind, + body: { + receipt_sequence: '3', + parent_receipt_hash: leaseTerminal.receipt_id, + timestamp_ms: leaseKind === 'LEASE_EXPIRED' ? '4600' : '3600', + after_state_root: H('7'), + outcome: terminalOutcome, + denial_codes: [terminalKind === 'MUTATION_CANCELLED' ? 'CANCELLED_AFTER_EXPIRY' : 'FAILED_AFTER_REVOCATION'], + nonce: 'nonce-receipt-0004', + }, + }) + const source = new MemorySource() + source.registries.set(value.registry.registry_root, value.registry) + for (const receipt of [value.receipts[0], value.receipts[1], leaseTerminal, mutationTerminal]) { + source.receipts.set(receipt.receipt_id, receipt) + } + await expect(resolveAndVerifyCrossRuntimeReceiptChainV1( + source, mutationTerminal.receipt_id, value.context, + )).resolves.toMatchObject({ + terminal_receipt_kind: terminalKind, + terminal_outcome: terminalOutcome, + before_state_root: H('7'), + after_state_root: H('7'), + }) + }, + ) + + it('accepts an ancestral registry rotation and rejects a divergent signed registry branch', async () => { + const value = await fixture() + const rotated = await buildReceiptTrustRegistryV1({ + ...value.registry.registry_body, + registry_version: '2', + previous_registry_root: value.registry.registry_root, + issued_at_ms: '600', + }, value.operatorPrivateKey) + const rotatedAdmitted = await rebuildUnderRegistry( + value.receipts[1], rotated, value.receiptPrivateKey, + ) + const rotatedCompleted = await rebuildUnderRegistry( + value.receipts[2], rotated, value.receiptPrivateKey, + { body: { parent_receipt_hash: rotatedAdmitted.receipt_id } }, + ) + const acceptedRoots = [value.registry.registry_root, rotated.registry_root].sort() as SHA256Hex[] + const validSource = new MemorySource() + for (const registry of [value.registry, rotated]) { + validSource.registries.set(registry.registry_root, registry) + } + for (const receipt of [value.receipts[0], rotatedAdmitted, rotatedCompleted]) { + validSource.receipts.set(receipt.receipt_id, receipt) + } + await expect(resolveAndVerifyCrossRuntimeReceiptChainV1( + validSource, + rotatedCompleted.receipt_id, + { ...value.context, accepted_registry_roots: acceptedRoots }, + )).resolves.toMatchObject({ terminal_outcome: 'COMPLETED', receipt_count: '3' }) + + const alternateGenesis = await buildReceiptTrustRegistryV1({ + ...value.registry.registry_body, + issued_at_ms: '501', + }, value.operatorPrivateKey) + const divergent = await buildReceiptTrustRegistryV1({ + ...rotated.registry_body, + previous_registry_root: alternateGenesis.registry_root, + }, value.operatorPrivateKey) + const divergentAdmitted = await rebuildUnderRegistry( + value.receipts[1], divergent, value.receiptPrivateKey, + ) + const divergentCompleted = await rebuildUnderRegistry( + value.receipts[2], divergent, value.receiptPrivateKey, + { body: { parent_receipt_hash: divergentAdmitted.receipt_id } }, + ) + const divergentSource = new MemorySource() + for (const registry of [value.registry, alternateGenesis, divergent]) { + divergentSource.registries.set(registry.registry_root, registry) + } + for (const receipt of [value.receipts[0], divergentAdmitted, divergentCompleted]) { + divergentSource.receipts.set(receipt.receipt_id, receipt) + } + await expect(resolveAndVerifyCrossRuntimeReceiptChainV1( + divergentSource, + divergentCompleted.receipt_id, + { + ...value.context, + accepted_registry_roots: [value.registry.registry_root, divergent.registry_root] + .sort() as SHA256Hex[], + }, + )).rejects.toThrow('does not descend from the prior registry root') + }) + + it('persists an atomic batch, reopens, reads back, and resolves the chain', async () => { + const value = await fixture() + const databaseName = `receipt-source-${crypto.randomUUID()}` + const store = new IndexedDBCrossRuntimeReceiptSourceV1(databaseName) + await store.open() + await store.persistBatch([value.registry], value.receipts) + store.close() + + const reopened = new IndexedDBCrossRuntimeReceiptSourceV1(databaseName) + await reopened.open() + await expect(reopened.resolveReceipt(value.receipts[2].receipt_id)).resolves.toEqual(value.receipts[2]) + await expect(reopened.resolveTrustRegistry(value.registry.registry_root)).resolves.toEqual(value.registry) + await expect(resolveAndVerifyCrossRuntimeReceiptChainV1( + reopened, value.receipts[2].receipt_id, value.context, + )).resolves.toMatchObject({ decision: 'VERIFIED', receipt_count: '3' }) + reopened.close() + }) + + it('aborts a conflicting batch without partially persisting its registry or receipts', async () => { + const value = await fixture() + const deniedOne = await rebuild(value.receipts[1], value.receiptPrivateKey, { + kind: 'MUTATION_DENIED', + body: { outcome: 'DENIED', denial_codes: ['DENIED_ONE'] }, + }) + const deniedTwo = await rebuild(value.receipts[1], value.receiptPrivateKey, { + kind: 'MUTATION_DENIED', + body: { outcome: 'DENIED', denial_codes: ['DENIED_TWO'], nonce: 'nonce-receipt-0002' }, + }) + const store = new IndexedDBCrossRuntimeReceiptSourceV1(`receipt-source-${crypto.randomUUID()}`) + await store.open() + await expect(store.persistBatch( + [value.registry], [value.receipts[0], deniedOne, deniedTwo], + )).rejects.toThrow('duplicate') + await expect(store.resolveTrustRegistry(value.registry.registry_root)).resolves.toBeNull() + await expect(store.resolveReceipt(value.receipts[0].receipt_id)).resolves.toBeNull() + store.close() + }) + + it('rolls back earlier writes when a later IndexedDB uniqueness constraint aborts the transaction', async () => { + const value = await fixture() + const databaseName = `receipt-transaction-${crypto.randomUUID()}` + const store = new IndexedDBCrossRuntimeReceiptSourceV1(databaseName) + await store.open() + await store.persistBatch([value.registry], [value.receipts[0]]) + const colliding = await rebuild(value.receipts[0], value.receiptPrivateKey, { + body: { action_digest: H('f') }, + }) + + await expect(store.persistBatch([], [value.receipts[1], colliding])).rejects.toThrow() + await expect(store.resolveReceipt(value.receipts[0].receipt_id)).resolves.toEqual(value.receipts[0]) + await expect(store.resolveReceipt(value.receipts[1].receipt_id)).resolves.toBeNull() + await expect(store.resolveReceipt(colliding.receipt_id)).resolves.toBeNull() + store.close() + + const reopened = new IndexedDBCrossRuntimeReceiptSourceV1(databaseName) + await reopened.open() + await expect(reopened.resolveReceipt(value.receipts[0].receipt_id)).resolves.toEqual(value.receipts[0]) + await expect(reopened.resolveReceipt(value.receipts[1].receipt_id)).resolves.toBeNull() + reopened.close() + }) +}) + +interface PythonGoldenFixture { + readonly operator_public_key: string + readonly registry: ReceiptTrustRegistryV1 + readonly receipts: readonly CrossRuntimeReceiptEnvelopeV1[] + readonly terminal_receipt_id: SHA256Hex + readonly context: Omit + readonly expected_decision_digest?: SHA256Hex +} + +const pythonFixturePath = resolve('test/vectors/python-cross-runtime-receipt-v1.json') +const typescriptFixturePath = resolve('test/vectors/typescript-cross-runtime-receipt-v1.json') + +describe('Python cross-runtime golden fixture', () => { + it( + 'verifies Python-generated registry, receipts, signatures, ids, and decision digest', + async () => { + const golden = JSON.parse(readFileSync(pythonFixturePath, 'utf8')) as PythonGoldenFixture + const source = new MemorySource() + source.registries.set(golden.registry.registry_root, golden.registry) + for (const receipt of golden.receipts) source.receipts.set(receipt.receipt_id, receipt) + const decision = await resolveAndVerifyCrossRuntimeReceiptChainV1( + source, + golden.terminal_receipt_id, + { ...golden.context, operator_public_key: golden.operator_public_key }, + ) + expect(decision.decision).toBe('VERIFIED') + if (golden.expected_decision_digest !== undefined) { + expect(decision.decision_digest).toBe(golden.expected_decision_digest) + } + }, + ) + + it('persists, reopens, and verifies every Python-generated receipt kind', async () => { + const golden = JSON.parse(readFileSync(pythonFixturePath, 'utf8')) as PythonGoldenFixture + const databaseName = `receipt-all-kinds-${crypto.randomUUID()}` + const store = new IndexedDBCrossRuntimeReceiptSourceV1(databaseName) + await store.open() + await store.persistBatch([golden.registry], golden.receipts) + store.close() + + const reopened = new IndexedDBCrossRuntimeReceiptSourceV1(databaseName) + await reopened.open() + for (const receipt of golden.receipts) { + await expect(reopened.resolveReceipt(receipt.receipt_id)).resolves.toEqual(receipt) + } + await expect(resolveAndVerifyCrossRuntimeReceiptChainV1( + reopened, + golden.terminal_receipt_id, + { ...golden.context, operator_public_key: golden.operator_public_key }, + )).resolves.toMatchObject({ + decision: 'VERIFIED', + receipt_count: String(golden.receipts.length), + }) + reopened.close() + }) + + it('independently regenerates byte-identical Python and TypeScript all-kind vectors', async () => { + const pythonBytes = readFileSync(pythonFixturePath) + const typescriptBytes = readFileSync(typescriptFixturePath) + const regenerated = Buffer.concat([ + Buffer.from(canonicalizeJCS(await buildTypeScriptCrossRuntimeVectorV1())), + Buffer.from('\n'), + ]) + expect(typescriptBytes.equals(regenerated)).toBe(true) + expect(pythonBytes.equals(regenerated)).toBe(true) + }) +}) diff --git a/sovereign-omega-v2/test/vectors/python-cross-runtime-receipt-v1.json b/sovereign-omega-v2/test/vectors/python-cross-runtime-receipt-v1.json new file mode 100644 index 000000000..280a16405 --- /dev/null +++ b/sovereign-omega-v2/test/vectors/python-cross-runtime-receipt-v1.json @@ -0,0 +1 @@ +{"context":{"accepted_registry_roots":["0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc"],"expected_action_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expected_actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","expected_authority_domain":"repository:mutation","expected_authority_level":"D2","expected_holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","expected_observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expected_session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","expected_workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333","max_clock_skew_ms":"0","observed_at_ms":"3000","operator_key_id":"operator-root-v1"},"operator_public_key":"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a","receipts":[{"proof":{"algorithm":"Ed25519","signature":"871f8e45783fb72e97c91848203bf3efb3b526c6d42b2b233bc98c4dc9a71f095550a3d6eeb53f62c96dc66c4aee831fc654cd219712283bc7fbc4c9f957de0f","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"1111111111111111111111111111111111111111111111111111111111111111","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","denial_codes":["LEASE_EXPIRY_INVALID"],"expected_state_root":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","expires_at_ms":"900","fencing_token":"0000000000000000000000000000000000000000000000000000000000000000","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"1","lease_id":"4444444444444444444444444444444444444444444444444444444444444444","nonce":"vector-lease-denied-01","observed_state_root":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","outcome":"DENIED","parent_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","receipt_sequence":"0","result_digest":"963785c83297ca4675afed7f347f87990035109218c057aefdfa0192fa39c2fc","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1000","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"6bb342ab3fcb97394927c960f4304098f2756f93885f404eab9676eee0a8ecc8","receipt_kind":"LEASE_ISSUANCE_DENIED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"b543789bc67c85d8f2339bec366992042823e05bf7d9e937ed74545d28ad332455424e226e4fb78d3b82085b9ddf6a1b8a2af8e89994ae7dc322233eab552607","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","denial_codes":[],"expected_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expires_at_ms":"3000","fencing_token":"3c9a24575a0085c421f6d8f7a8c810f840612332dd75466d9d2e8865f7c9b133","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"1","lease_id":"5555555555555555555555555555555555555555555555555555555555555555","nonce":"vector-lease-issued-01","observed_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","outcome":"ADMITTED","parent_receipt_hash":"6bb342ab3fcb97394927c960f4304098f2756f93885f404eab9676eee0a8ecc8","receipt_sequence":"1","result_digest":"04c7915297737da3d509fbb63ec5a0080755ecd344a8c37b162c74a3551222dc","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1100","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"6934f49acebf5fa8a4d4a8c91ed2304e4a1d23aa17b3f3023f81a13426242e30","receipt_kind":"LEASE_ISSUED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"0c3ee1e46eafc6a5ac6d7e7a6ea4e494437a2eef12733b7cd5a895965c551629fcdcd2ce12eeff9b9b1208f364dff9fe6fd0c130695be4877c0402d667fbb104","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","denial_codes":["STALE_FENCING_TOKEN","STALE_LEASE_GENERATION"],"expected_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expires_at_ms":"4000","fencing_token":"9999999999999999999999999999999999999999999999999999999999999999","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"0","lease_id":"5555555555555555555555555555555555555555555555555555555555555555","nonce":"vector-renew-denied-01","observed_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","outcome":"DENIED","parent_receipt_hash":"6934f49acebf5fa8a4d4a8c91ed2304e4a1d23aa17b3f3023f81a13426242e30","receipt_sequence":"2","result_digest":"f67654d8260b411ced3e0991df2d5a38529e855cf7878c9f23762ccfb6124cba","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1200","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"4adb45af070e7df83ee06849db7e185c20ba703024baa220b03382d860583263","receipt_kind":"LEASE_RENEWAL_DENIED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"381a4ce817162d26adfe3075a68f4de686957e88e650711abfff127d7afbd9f90dc0c24cc3a921d2193cd9dfeba340ee5870ecc9f7ba4142ad890dcd372c9907","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","denial_codes":[],"expected_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expires_at_ms":"4000","fencing_token":"a2c9574e3116e2090557a4ef0fc4be7cec100063f146a07b5d602d56dc99728e","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"2","lease_id":"5555555555555555555555555555555555555555555555555555555555555555","nonce":"vector-lease-renewed-1","observed_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","outcome":"ADMITTED","parent_receipt_hash":"4adb45af070e7df83ee06849db7e185c20ba703024baa220b03382d860583263","receipt_sequence":"3","result_digest":"3c5d1a6318b144186dfeec02a5d7b23fe5b895e78cb60ea1ba1b215fa778bbf2","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1300","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"8e9f2ab4beb28f12b53bee1fb37165796292ef64b0d8035301a91d74f9462c48","receipt_kind":"LEASE_RENEWED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"8a0d925247de82d454c900ab920060621c98ca93aef3fade37046d0b17d2ff00b6324b6789911efa2dc1806471bda6d0204a9dad96183cfd689ac04bc1c4eb04","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","before_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","denial_codes":["POLICY_DENIED"],"expected_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expires_at_ms":"4000","fencing_token":"a2c9574e3116e2090557a4ef0fc4be7cec100063f146a07b5d602d56dc99728e","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"8e9f2ab4beb28f12b53bee1fb37165796292ef64b0d8035301a91d74f9462c48","lease_generation":"2","lease_id":"5555555555555555555555555555555555555555555555555555555555555555","nonce":"vector-mutation-deny-01","observed_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","outcome":"DENIED","parent_receipt_hash":"8e9f2ab4beb28f12b53bee1fb37165796292ef64b0d8035301a91d74f9462c48","receipt_sequence":"4","result_digest":"4444444444444444444444444444444444444444444444444444444444444444","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1400","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"623e4adecf9bbbb0a3aaebc3bd14f11a1f2372c3ec9173bd02751487b9291d27","receipt_kind":"MUTATION_DENIED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"a8630ea62961a90b0b85a6aa77139c11b673a4122bf90676153c7604f2deba8a09b9fbc7f99d2476e0160f594a9918a927c069a87df4097111a36e366b128109","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","before_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","denial_codes":[],"expected_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expires_at_ms":"4000","fencing_token":"a2c9574e3116e2090557a4ef0fc4be7cec100063f146a07b5d602d56dc99728e","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"8e9f2ab4beb28f12b53bee1fb37165796292ef64b0d8035301a91d74f9462c48","lease_generation":"2","lease_id":"5555555555555555555555555555555555555555555555555555555555555555","nonce":"vector-mutation-admit-1","observed_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","outcome":"ADMITTED","parent_receipt_hash":"623e4adecf9bbbb0a3aaebc3bd14f11a1f2372c3ec9173bd02751487b9291d27","receipt_sequence":"5","result_digest":"d4240561b6516018339b001a69eaee16cf7bd1a2469cbdd3ebb92c4d66418e2b","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1500","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"7b4796a691162874042a0413a90b549896a6cd37aa2f8374216603704a88bd6b","receipt_kind":"MUTATION_ADMITTED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"c6fa50e76e980624a57f147aec66defe1756c2928627f63fe4b23706c2b01a4474d9ccb493b726b32495863cd9741c99ac7608a1237f2582c23f80dfce6d6401","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","before_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","denial_codes":[],"expected_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expires_at_ms":"4000","fencing_token":"a2c9574e3116e2090557a4ef0fc4be7cec100063f146a07b5d602d56dc99728e","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"8e9f2ab4beb28f12b53bee1fb37165796292ef64b0d8035301a91d74f9462c48","lease_generation":"2","lease_id":"5555555555555555555555555555555555555555555555555555555555555555","nonce":"vector-mutation-done-01","observed_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","outcome":"COMPLETED","parent_receipt_hash":"7b4796a691162874042a0413a90b549896a6cd37aa2f8374216603704a88bd6b","receipt_sequence":"6","result_digest":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1600","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"bfb5ef32cbf55c05b7e774b41dd68b90af87a6fe95663da45a9b20f824556cbd","receipt_kind":"MUTATION_COMPLETED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"fa37b5581241c761cd9f6563e611510e459586e0f62ae083178703f6d67e4704d69e2f3e0fcbb487d6300cc7a657ef6dd7576f5e50516f15031a75fd3e170901","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"7777777777777777777777777777777777777777777777777777777777777777","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":[],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"2000","fencing_token":"50a4c73a70f17526db94424b5c0311d474822919a857b272211921d4cc028bd8","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"3","lease_id":"6666666666666666666666666666666666666666666666666666666666666666","nonce":"vector-cancel-lease-001","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"ADMITTED","parent_receipt_hash":"bfb5ef32cbf55c05b7e774b41dd68b90af87a6fe95663da45a9b20f824556cbd","receipt_sequence":"7","result_digest":"449669cb0ebdfe41afe288181ad645876689a06eeb7008e5ef0883c225e6d382","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1700","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"c491db7fe925c0bc0539f474cd5a20e9b9111c4635bd2b01b6504ac9a5f698ef","receipt_kind":"LEASE_ISSUED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"346871a53eae226eccc67bd4f10e7d097451162efb121cc855e5ec8d24361c6152e2f978df07c233a959eedf65ffed0f800ec64f9898d5b504c70f5c1a434d04","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"7777777777777777777777777777777777777777777777777777777777777777","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":[],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"2000","fencing_token":"50a4c73a70f17526db94424b5c0311d474822919a857b272211921d4cc028bd8","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"c491db7fe925c0bc0539f474cd5a20e9b9111c4635bd2b01b6504ac9a5f698ef","lease_generation":"3","lease_id":"6666666666666666666666666666666666666666666666666666666666666666","nonce":"vector-cancel-admit-01","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"ADMITTED","parent_receipt_hash":"c491db7fe925c0bc0539f474cd5a20e9b9111c4635bd2b01b6504ac9a5f698ef","receipt_sequence":"8","result_digest":"863249f58597648853997a055b13fb6ac26a87f598a80da99d7450ae0d096234","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1800","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"41bae5943b0b5f1db4ba66b3aa2e48f49cb2a8a78ba7839ccdef5015bbb8ba92","receipt_kind":"MUTATION_ADMITTED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"e3b0000414be558fd22abffcb4f1b946e69c3a176821d69d1f3de174d0e942f0aeff6b83fcce4d52328b3d1572eed381627bcfefe2d7757dc42d2e9f3eade808","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"7777777777777777777777777777777777777777777777777777777777777777","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":["LEASE_EXPIRED"],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"2000","fencing_token":"50a4c73a70f17526db94424b5c0311d474822919a857b272211921d4cc028bd8","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"3","lease_id":"6666666666666666666666666666666666666666666666666666666666666666","nonce":"vector-lease-expired-1","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"EXPIRED","parent_receipt_hash":"41bae5943b0b5f1db4ba66b3aa2e48f49cb2a8a78ba7839ccdef5015bbb8ba92","receipt_sequence":"9","result_digest":"ef3794d82c98afb411f6e4b9c8d0f702c3c9420ead2fb5a82d5fd00ecc394fee","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"2000","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"99a5c5320ce760023f9641d400f2c22da41c13116ec0f5a4b9b882901b98dfaa","receipt_kind":"LEASE_EXPIRED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"6708e4958f77f52e21af4e60b623d69cdadae4bcded1cee5e7ba7aa5426f84f8d8fb40d3905aaddea384ee265413c452c2a41d9900c0c750a66371c7747f6009","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"7777777777777777777777777777777777777777777777777777777777777777","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":["CANCELLED_AFTER_EXPIRY"],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"2000","fencing_token":"50a4c73a70f17526db94424b5c0311d474822919a857b272211921d4cc028bd8","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"c491db7fe925c0bc0539f474cd5a20e9b9111c4635bd2b01b6504ac9a5f698ef","lease_generation":"3","lease_id":"6666666666666666666666666666666666666666666666666666666666666666","nonce":"vector-mutation-cancel1","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"CANCELLED","parent_receipt_hash":"99a5c5320ce760023f9641d400f2c22da41c13116ec0f5a4b9b882901b98dfaa","receipt_sequence":"10","result_digest":"8888888888888888888888888888888888888888888888888888888888888888","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"2100","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"521fcc189051b97803db9ce5b5fc8c5684c1ef33fd5e63a9a0118be56912379c","receipt_kind":"MUTATION_CANCELLED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"d4d9ee72dbe350e00e44efcbd9ffd480c47780b931fb8426f834d4433eccd593064dd58f06e45677bd738a0c4be13a6a5b427571e3531d3df32ee5975bef9d04","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":[],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"4000","fencing_token":"0a1f086f7c8c78c9cbae01801dc420f03f7b65e9c90156681468afe6e917b00a","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"4","lease_id":"9999999999999999999999999999999999999999999999999999999999999999","nonce":"vector-failure-lease-1","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"ADMITTED","parent_receipt_hash":"521fcc189051b97803db9ce5b5fc8c5684c1ef33fd5e63a9a0118be56912379c","receipt_sequence":"11","result_digest":"0c66bcd2d45722a813d73560207a834b627d0499a74135b92c493620b8a769fb","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"2200","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"3dc404163319a5664741e04563487018ac0e64b3036c5f468532728dc8aba12c","receipt_kind":"LEASE_ISSUED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"acc8909a5f2f02b88884ade575958b28034d23fd0fd4887c656e7a63ec139e90e961cc38adda259c3ccce2a8b818ec1519d7bb87ee23843d3bf8b32303398201","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":[],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"4000","fencing_token":"0a1f086f7c8c78c9cbae01801dc420f03f7b65e9c90156681468afe6e917b00a","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"3dc404163319a5664741e04563487018ac0e64b3036c5f468532728dc8aba12c","lease_generation":"4","lease_id":"9999999999999999999999999999999999999999999999999999999999999999","nonce":"vector-failure-admit-1","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"ADMITTED","parent_receipt_hash":"3dc404163319a5664741e04563487018ac0e64b3036c5f468532728dc8aba12c","receipt_sequence":"12","result_digest":"003fa4b3f622382438699c4b990cd115eb81dec7d3b196de7b8cf910efc322d4","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"2300","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"22e3263341b6aef19b6286b55339089d00cc579b40572bc47df26dc940b8353a","receipt_kind":"MUTATION_ADMITTED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"6ceb3b512fe0bf8e9270975a7e9de90bcfe207c4ed064d6853a9ec2b4da500b92f9af03a30d392dc383028b03134236d11aaa2d0143220334712e5982a64ed07","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":["OPERATOR_REVOKED"],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"4000","fencing_token":"0a1f086f7c8c78c9cbae01801dc420f03f7b65e9c90156681468afe6e917b00a","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"4","lease_id":"9999999999999999999999999999999999999999999999999999999999999999","nonce":"vector-lease-revoked-1","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"REVOKED","parent_receipt_hash":"22e3263341b6aef19b6286b55339089d00cc579b40572bc47df26dc940b8353a","receipt_sequence":"13","result_digest":"5ac543ce57c8bc9e6455824e7352997fa44f2cc62f72a6046c05d2f7ce1b18c4","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"2400","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"c18ee227a33ca44619cf547bcd7e75ed15636b1df2211993bacfb76b883d589a","receipt_kind":"LEASE_REVOKED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"cfab1097e7f88cf34f99ced60db895f557af746e3eb1bac27099920a53c82d0c9a274beb053cc4030b611da3284242cfd9ad616e7df166b51d1ac6a256c19f0e","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":["FAILED_AFTER_REVOCATION"],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"4000","fencing_token":"0a1f086f7c8c78c9cbae01801dc420f03f7b65e9c90156681468afe6e917b00a","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"3dc404163319a5664741e04563487018ac0e64b3036c5f468532728dc8aba12c","lease_generation":"4","lease_id":"9999999999999999999999999999999999999999999999999999999999999999","nonce":"vector-mutation-fail-01","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"FAILED","parent_receipt_hash":"c18ee227a33ca44619cf547bcd7e75ed15636b1df2211993bacfb76b883d589a","receipt_sequence":"14","result_digest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"2500","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"16721fb05a8bce9d886a17b34e737ac9b7c2963ac8fa304bf67fe32c170152a5","receipt_kind":"MUTATION_FAILED","schema_version":"1.0.0"}],"registry":{"proof":{"algorithm":"Ed25519","signature":"4b3d01d39159a1e9a8a9fe3784c2f9c25f3f92e43222ae0be425ae6c9c4fc79ccbbce45e8e684e07a9ad1942ee3302344d357eed01ebe83f47047228192f2c01"},"registry_body":{"expires_at_ms":"10000","issued_at_ms":"90","keys":[{"authority_domains":["repository:mutation"],"expires_at_ms":"9000","key_id":"cross-runtime-witness-v1","public_key":"3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c","receipt_kinds":["LEASE_EXPIRED","LEASE_ISSUANCE_DENIED","LEASE_ISSUED","LEASE_RENEWAL_DENIED","LEASE_RENEWED","LEASE_REVOKED","MUTATION_ADMITTED","MUTATION_CANCELLED","MUTATION_COMPLETED","MUTATION_DENIED","MUTATION_FAILED"],"status":"ACTIVE","valid_from_ms":"100","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"}],"operator_key_id":"operator-root-v1","previous_registry_root":"0000000000000000000000000000000000000000000000000000000000000000","registry_version":"1","valid_from_ms":"100"},"registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","schema_version":"1.0.0"},"schema_version":"1.0.0","terminal_receipt_id":"16721fb05a8bce9d886a17b34e737ac9b7c2963ac8fa304bf67fe32c170152a5"} diff --git a/sovereign-omega-v2/test/vectors/typescript-cross-runtime-receipt-v1.json b/sovereign-omega-v2/test/vectors/typescript-cross-runtime-receipt-v1.json new file mode 100644 index 000000000..280a16405 --- /dev/null +++ b/sovereign-omega-v2/test/vectors/typescript-cross-runtime-receipt-v1.json @@ -0,0 +1 @@ +{"context":{"accepted_registry_roots":["0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc"],"expected_action_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expected_actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","expected_authority_domain":"repository:mutation","expected_authority_level":"D2","expected_holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","expected_observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expected_session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","expected_workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333","max_clock_skew_ms":"0","observed_at_ms":"3000","operator_key_id":"operator-root-v1"},"operator_public_key":"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a","receipts":[{"proof":{"algorithm":"Ed25519","signature":"871f8e45783fb72e97c91848203bf3efb3b526c6d42b2b233bc98c4dc9a71f095550a3d6eeb53f62c96dc66c4aee831fc654cd219712283bc7fbc4c9f957de0f","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"1111111111111111111111111111111111111111111111111111111111111111","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","denial_codes":["LEASE_EXPIRY_INVALID"],"expected_state_root":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","expires_at_ms":"900","fencing_token":"0000000000000000000000000000000000000000000000000000000000000000","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"1","lease_id":"4444444444444444444444444444444444444444444444444444444444444444","nonce":"vector-lease-denied-01","observed_state_root":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","outcome":"DENIED","parent_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","receipt_sequence":"0","result_digest":"963785c83297ca4675afed7f347f87990035109218c057aefdfa0192fa39c2fc","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1000","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"6bb342ab3fcb97394927c960f4304098f2756f93885f404eab9676eee0a8ecc8","receipt_kind":"LEASE_ISSUANCE_DENIED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"b543789bc67c85d8f2339bec366992042823e05bf7d9e937ed74545d28ad332455424e226e4fb78d3b82085b9ddf6a1b8a2af8e89994ae7dc322233eab552607","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","denial_codes":[],"expected_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expires_at_ms":"3000","fencing_token":"3c9a24575a0085c421f6d8f7a8c810f840612332dd75466d9d2e8865f7c9b133","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"1","lease_id":"5555555555555555555555555555555555555555555555555555555555555555","nonce":"vector-lease-issued-01","observed_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","outcome":"ADMITTED","parent_receipt_hash":"6bb342ab3fcb97394927c960f4304098f2756f93885f404eab9676eee0a8ecc8","receipt_sequence":"1","result_digest":"04c7915297737da3d509fbb63ec5a0080755ecd344a8c37b162c74a3551222dc","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1100","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"6934f49acebf5fa8a4d4a8c91ed2304e4a1d23aa17b3f3023f81a13426242e30","receipt_kind":"LEASE_ISSUED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"0c3ee1e46eafc6a5ac6d7e7a6ea4e494437a2eef12733b7cd5a895965c551629fcdcd2ce12eeff9b9b1208f364dff9fe6fd0c130695be4877c0402d667fbb104","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","denial_codes":["STALE_FENCING_TOKEN","STALE_LEASE_GENERATION"],"expected_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expires_at_ms":"4000","fencing_token":"9999999999999999999999999999999999999999999999999999999999999999","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"0","lease_id":"5555555555555555555555555555555555555555555555555555555555555555","nonce":"vector-renew-denied-01","observed_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","outcome":"DENIED","parent_receipt_hash":"6934f49acebf5fa8a4d4a8c91ed2304e4a1d23aa17b3f3023f81a13426242e30","receipt_sequence":"2","result_digest":"f67654d8260b411ced3e0991df2d5a38529e855cf7878c9f23762ccfb6124cba","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1200","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"4adb45af070e7df83ee06849db7e185c20ba703024baa220b03382d860583263","receipt_kind":"LEASE_RENEWAL_DENIED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"381a4ce817162d26adfe3075a68f4de686957e88e650711abfff127d7afbd9f90dc0c24cc3a921d2193cd9dfeba340ee5870ecc9f7ba4142ad890dcd372c9907","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","denial_codes":[],"expected_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expires_at_ms":"4000","fencing_token":"a2c9574e3116e2090557a4ef0fc4be7cec100063f146a07b5d602d56dc99728e","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"2","lease_id":"5555555555555555555555555555555555555555555555555555555555555555","nonce":"vector-lease-renewed-1","observed_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","outcome":"ADMITTED","parent_receipt_hash":"4adb45af070e7df83ee06849db7e185c20ba703024baa220b03382d860583263","receipt_sequence":"3","result_digest":"3c5d1a6318b144186dfeec02a5d7b23fe5b895e78cb60ea1ba1b215fa778bbf2","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1300","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"8e9f2ab4beb28f12b53bee1fb37165796292ef64b0d8035301a91d74f9462c48","receipt_kind":"LEASE_RENEWED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"8a0d925247de82d454c900ab920060621c98ca93aef3fade37046d0b17d2ff00b6324b6789911efa2dc1806471bda6d0204a9dad96183cfd689ac04bc1c4eb04","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","before_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","denial_codes":["POLICY_DENIED"],"expected_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expires_at_ms":"4000","fencing_token":"a2c9574e3116e2090557a4ef0fc4be7cec100063f146a07b5d602d56dc99728e","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"8e9f2ab4beb28f12b53bee1fb37165796292ef64b0d8035301a91d74f9462c48","lease_generation":"2","lease_id":"5555555555555555555555555555555555555555555555555555555555555555","nonce":"vector-mutation-deny-01","observed_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","outcome":"DENIED","parent_receipt_hash":"8e9f2ab4beb28f12b53bee1fb37165796292ef64b0d8035301a91d74f9462c48","receipt_sequence":"4","result_digest":"4444444444444444444444444444444444444444444444444444444444444444","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1400","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"623e4adecf9bbbb0a3aaebc3bd14f11a1f2372c3ec9173bd02751487b9291d27","receipt_kind":"MUTATION_DENIED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"a8630ea62961a90b0b85a6aa77139c11b673a4122bf90676153c7604f2deba8a09b9fbc7f99d2476e0160f594a9918a927c069a87df4097111a36e366b128109","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","before_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","denial_codes":[],"expected_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expires_at_ms":"4000","fencing_token":"a2c9574e3116e2090557a4ef0fc4be7cec100063f146a07b5d602d56dc99728e","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"8e9f2ab4beb28f12b53bee1fb37165796292ef64b0d8035301a91d74f9462c48","lease_generation":"2","lease_id":"5555555555555555555555555555555555555555555555555555555555555555","nonce":"vector-mutation-admit-1","observed_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","outcome":"ADMITTED","parent_receipt_hash":"623e4adecf9bbbb0a3aaebc3bd14f11a1f2372c3ec9173bd02751487b9291d27","receipt_sequence":"5","result_digest":"d4240561b6516018339b001a69eaee16cf7bd1a2469cbdd3ebb92c4d66418e2b","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1500","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"7b4796a691162874042a0413a90b549896a6cd37aa2f8374216603704a88bd6b","receipt_kind":"MUTATION_ADMITTED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"c6fa50e76e980624a57f147aec66defe1756c2928627f63fe4b23706c2b01a4474d9ccb493b726b32495863cd9741c99ac7608a1237f2582c23f80dfce6d6401","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","before_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","denial_codes":[],"expected_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","expires_at_ms":"4000","fencing_token":"a2c9574e3116e2090557a4ef0fc4be7cec100063f146a07b5d602d56dc99728e","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"8e9f2ab4beb28f12b53bee1fb37165796292ef64b0d8035301a91d74f9462c48","lease_generation":"2","lease_id":"5555555555555555555555555555555555555555555555555555555555555555","nonce":"vector-mutation-done-01","observed_state_root":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","outcome":"COMPLETED","parent_receipt_hash":"7b4796a691162874042a0413a90b549896a6cd37aa2f8374216603704a88bd6b","receipt_sequence":"6","result_digest":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1600","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"bfb5ef32cbf55c05b7e774b41dd68b90af87a6fe95663da45a9b20f824556cbd","receipt_kind":"MUTATION_COMPLETED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"fa37b5581241c761cd9f6563e611510e459586e0f62ae083178703f6d67e4704d69e2f3e0fcbb487d6300cc7a657ef6dd7576f5e50516f15031a75fd3e170901","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"7777777777777777777777777777777777777777777777777777777777777777","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":[],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"2000","fencing_token":"50a4c73a70f17526db94424b5c0311d474822919a857b272211921d4cc028bd8","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"3","lease_id":"6666666666666666666666666666666666666666666666666666666666666666","nonce":"vector-cancel-lease-001","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"ADMITTED","parent_receipt_hash":"bfb5ef32cbf55c05b7e774b41dd68b90af87a6fe95663da45a9b20f824556cbd","receipt_sequence":"7","result_digest":"449669cb0ebdfe41afe288181ad645876689a06eeb7008e5ef0883c225e6d382","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1700","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"c491db7fe925c0bc0539f474cd5a20e9b9111c4635bd2b01b6504ac9a5f698ef","receipt_kind":"LEASE_ISSUED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"346871a53eae226eccc67bd4f10e7d097451162efb121cc855e5ec8d24361c6152e2f978df07c233a959eedf65ffed0f800ec64f9898d5b504c70f5c1a434d04","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"7777777777777777777777777777777777777777777777777777777777777777","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":[],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"2000","fencing_token":"50a4c73a70f17526db94424b5c0311d474822919a857b272211921d4cc028bd8","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"c491db7fe925c0bc0539f474cd5a20e9b9111c4635bd2b01b6504ac9a5f698ef","lease_generation":"3","lease_id":"6666666666666666666666666666666666666666666666666666666666666666","nonce":"vector-cancel-admit-01","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"ADMITTED","parent_receipt_hash":"c491db7fe925c0bc0539f474cd5a20e9b9111c4635bd2b01b6504ac9a5f698ef","receipt_sequence":"8","result_digest":"863249f58597648853997a055b13fb6ac26a87f598a80da99d7450ae0d096234","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"1800","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"41bae5943b0b5f1db4ba66b3aa2e48f49cb2a8a78ba7839ccdef5015bbb8ba92","receipt_kind":"MUTATION_ADMITTED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"e3b0000414be558fd22abffcb4f1b946e69c3a176821d69d1f3de174d0e942f0aeff6b83fcce4d52328b3d1572eed381627bcfefe2d7757dc42d2e9f3eade808","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"7777777777777777777777777777777777777777777777777777777777777777","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":["LEASE_EXPIRED"],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"2000","fencing_token":"50a4c73a70f17526db94424b5c0311d474822919a857b272211921d4cc028bd8","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"3","lease_id":"6666666666666666666666666666666666666666666666666666666666666666","nonce":"vector-lease-expired-1","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"EXPIRED","parent_receipt_hash":"41bae5943b0b5f1db4ba66b3aa2e48f49cb2a8a78ba7839ccdef5015bbb8ba92","receipt_sequence":"9","result_digest":"ef3794d82c98afb411f6e4b9c8d0f702c3c9420ead2fb5a82d5fd00ecc394fee","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"2000","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"99a5c5320ce760023f9641d400f2c22da41c13116ec0f5a4b9b882901b98dfaa","receipt_kind":"LEASE_EXPIRED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"6708e4958f77f52e21af4e60b623d69cdadae4bcded1cee5e7ba7aa5426f84f8d8fb40d3905aaddea384ee265413c452c2a41d9900c0c750a66371c7747f6009","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"7777777777777777777777777777777777777777777777777777777777777777","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":["CANCELLED_AFTER_EXPIRY"],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"2000","fencing_token":"50a4c73a70f17526db94424b5c0311d474822919a857b272211921d4cc028bd8","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"c491db7fe925c0bc0539f474cd5a20e9b9111c4635bd2b01b6504ac9a5f698ef","lease_generation":"3","lease_id":"6666666666666666666666666666666666666666666666666666666666666666","nonce":"vector-mutation-cancel1","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"CANCELLED","parent_receipt_hash":"99a5c5320ce760023f9641d400f2c22da41c13116ec0f5a4b9b882901b98dfaa","receipt_sequence":"10","result_digest":"8888888888888888888888888888888888888888888888888888888888888888","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"2100","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"521fcc189051b97803db9ce5b5fc8c5684c1ef33fd5e63a9a0118be56912379c","receipt_kind":"MUTATION_CANCELLED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"d4d9ee72dbe350e00e44efcbd9ffd480c47780b931fb8426f834d4433eccd593064dd58f06e45677bd738a0c4be13a6a5b427571e3531d3df32ee5975bef9d04","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":[],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"4000","fencing_token":"0a1f086f7c8c78c9cbae01801dc420f03f7b65e9c90156681468afe6e917b00a","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"4","lease_id":"9999999999999999999999999999999999999999999999999999999999999999","nonce":"vector-failure-lease-1","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"ADMITTED","parent_receipt_hash":"521fcc189051b97803db9ce5b5fc8c5684c1ef33fd5e63a9a0118be56912379c","receipt_sequence":"11","result_digest":"0c66bcd2d45722a813d73560207a834b627d0499a74135b92c493620b8a769fb","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"2200","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"3dc404163319a5664741e04563487018ac0e64b3036c5f468532728dc8aba12c","receipt_kind":"LEASE_ISSUED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"acc8909a5f2f02b88884ade575958b28034d23fd0fd4887c656e7a63ec139e90e961cc38adda259c3ccce2a8b818ec1519d7bb87ee23843d3bf8b32303398201","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":[],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"4000","fencing_token":"0a1f086f7c8c78c9cbae01801dc420f03f7b65e9c90156681468afe6e917b00a","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"3dc404163319a5664741e04563487018ac0e64b3036c5f468532728dc8aba12c","lease_generation":"4","lease_id":"9999999999999999999999999999999999999999999999999999999999999999","nonce":"vector-failure-admit-1","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"ADMITTED","parent_receipt_hash":"3dc404163319a5664741e04563487018ac0e64b3036c5f468532728dc8aba12c","receipt_sequence":"12","result_digest":"003fa4b3f622382438699c4b990cd115eb81dec7d3b196de7b8cf910efc322d4","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"2300","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"22e3263341b6aef19b6286b55339089d00cc579b40572bc47df26dc940b8353a","receipt_kind":"MUTATION_ADMITTED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"6ceb3b512fe0bf8e9270975a7e9de90bcfe207c4ed064d6853a9ec2b4da500b92f9af03a30d392dc383028b03134236d11aaa2d0143220334712e5982a64ed07","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":["OPERATOR_REVOKED"],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"4000","fencing_token":"0a1f086f7c8c78c9cbae01801dc420f03f7b65e9c90156681468afe6e917b00a","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"0000000000000000000000000000000000000000000000000000000000000000","lease_generation":"4","lease_id":"9999999999999999999999999999999999999999999999999999999999999999","nonce":"vector-lease-revoked-1","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"REVOKED","parent_receipt_hash":"22e3263341b6aef19b6286b55339089d00cc579b40572bc47df26dc940b8353a","receipt_sequence":"13","result_digest":"5ac543ce57c8bc9e6455824e7352997fa44f2cc62f72a6046c05d2f7ce1b18c4","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"2400","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"c18ee227a33ca44619cf547bcd7e75ed15636b1df2211993bacfb76b883d589a","receipt_kind":"LEASE_REVOKED","schema_version":"1.0.0"},{"proof":{"algorithm":"Ed25519","signature":"cfab1097e7f88cf34f99ced60db895f557af746e3eb1bac27099920a53c82d0c9a274beb053cc4030b611da3284242cfd9ad616e7df166b51d1ac6a256c19f0e","signer_key_id":"cross-runtime-witness-v1","trust_registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","trust_registry_version":"1","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"},"receipt_body":{"action_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","actor_identity_root":"1111111111111111111111111111111111111111111111111111111111111111","after_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","authority_domain":"repository:mutation","authority_level":"D2","authority_receipt_hash":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","before_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","denial_codes":["FAILED_AFTER_REVOCATION"],"expected_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","expires_at_ms":"4000","fencing_token":"0a1f086f7c8c78c9cbae01801dc420f03f7b65e9c90156681468afe6e917b00a","holon_identity_root":"4444444444444444444444444444444444444444444444444444444444444444","lease_authorization_receipt_hash":"3dc404163319a5664741e04563487018ac0e64b3036c5f468532728dc8aba12c","lease_generation":"4","lease_id":"9999999999999999999999999999999999999999999999999999999999999999","nonce":"vector-mutation-fail-01","observed_state_root":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","outcome":"FAILED","parent_receipt_hash":"c18ee227a33ca44619cf547bcd7e75ed15636b1df2211993bacfb76b883d589a","receipt_sequence":"14","result_digest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","session_identity_root":"2222222222222222222222222222222222222222222222222222222222222222","timestamp_ms":"2500","workspace_identity_root":"3333333333333333333333333333333333333333333333333333333333333333"},"receipt_id":"16721fb05a8bce9d886a17b34e737ac9b7c2963ac8fa304bf67fe32c170152a5","receipt_kind":"MUTATION_FAILED","schema_version":"1.0.0"}],"registry":{"proof":{"algorithm":"Ed25519","signature":"4b3d01d39159a1e9a8a9fe3784c2f9c25f3f92e43222ae0be425ae6c9c4fc79ccbbce45e8e684e07a9ad1942ee3302344d357eed01ebe83f47047228192f2c01"},"registry_body":{"expires_at_ms":"10000","issued_at_ms":"90","keys":[{"authority_domains":["repository:mutation"],"expires_at_ms":"9000","key_id":"cross-runtime-witness-v1","public_key":"3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c","receipt_kinds":["LEASE_EXPIRED","LEASE_ISSUANCE_DENIED","LEASE_ISSUED","LEASE_RENEWAL_DENIED","LEASE_RENEWED","LEASE_REVOKED","MUTATION_ADMITTED","MUTATION_CANCELLED","MUTATION_COMPLETED","MUTATION_DENIED","MUTATION_FAILED"],"status":"ACTIVE","valid_from_ms":"100","verifier_identity_root":"7777777777777777777777777777777777777777777777777777777777777777"}],"operator_key_id":"operator-root-v1","previous_registry_root":"0000000000000000000000000000000000000000000000000000000000000000","registry_version":"1","valid_from_ms":"100"},"registry_root":"0ec07349e6769cf25429ccac582c761c0d1c95cd3d4d369f6a6ffd59a1f6e3dc","schema_version":"1.0.0"},"schema_version":"1.0.0","terminal_receipt_id":"16721fb05a8bce9d886a17b34e737ac9b7c2963ac8fa304bf67fe32c170152a5"} From 2de5fe9fb5c2b846263848e5d9d46583f5c80d89 Mon Sep 17 00:00:00 2001 From: tarikskalic33 <228550385+tarikskalic33@users.noreply.github.com> Date: Tue, 28 Jul 2026 14:46:32 +0200 Subject: [PATCH 42/89] feat(projection): compile receipt-backed holonngram feedback --- ...untime-authoritative-receipt-provenance.md | 6 + ...023-holonngram-visual-feedback-compiler.md | 255 +++ .../holonngram-visual-feedback.v1.schema.json | 758 ++++++++ sovereign-omega-v2/package-lock.json | 86 +- sovereign-omega-v2/package.json | 1 + .../src/projection/holonngram-compiler.ts | 1541 +++++++++++++++++ .../test/unit/holonngram-compiler.test.ts | 538 ++++++ studio/src/App.tsx | 30 +- .../holonogram-surface/HolonogramSurface.tsx | 610 +++++++ 9 files changed, 3802 insertions(+), 23 deletions(-) create mode 100644 docs/adr/ADR-0023-holonngram-visual-feedback-compiler.md create mode 100644 schemas/holonngram-visual-feedback.v1.schema.json create mode 100644 sovereign-omega-v2/src/projection/holonngram-compiler.ts create mode 100644 sovereign-omega-v2/test/unit/holonngram-compiler.test.ts create mode 100644 studio/src/holonogram-surface/HolonogramSurface.tsx diff --git a/docs/adr/ADR-0022-cross-runtime-authoritative-receipt-provenance.md b/docs/adr/ADR-0022-cross-runtime-authoritative-receipt-provenance.md index b125a713d..e98c0f1fe 100644 --- a/docs/adr/ADR-0022-cross-runtime-authoritative-receipt-provenance.md +++ b/docs/adr/ADR-0022-cross-runtime-authoritative-receipt-provenance.md @@ -298,6 +298,12 @@ No cockpit, MCP, game, or other product projection may label receipt provenance Until that admission condition is recorded, the schemas and implementation are proposed infrastructure, not an authoritative cockpit or MCP claim. +ADR-0023 defines a receipt-derived Holonñgram visual-feedback compiler. Its +frames remain explicitly `DERIVED_NON_AUTHORITATIVE`, and the initial Studio +surface deliberately displays unresolved/demo values rather than consuming the +compiler output. This preserves the prohibition above: a visual compiler is not +itself cockpit, MCP, or game projection admission. + ## Consequences and remaining integration blockers This design removes private process memory and chat history from receipt verification, makes signer rotation explicit, and turns receipt identity into a stable cross-runtime content address. It also requires durable receipt and registry transport, trusted caller time, operator key provisioning, atomic chain-head coordination, and complete lifecycle evidence. diff --git a/docs/adr/ADR-0023-holonngram-visual-feedback-compiler.md b/docs/adr/ADR-0023-holonngram-visual-feedback-compiler.md new file mode 100644 index 000000000..a39a35ac9 --- /dev/null +++ b/docs/adr/ADR-0023-holonngram-visual-feedback-compiler.md @@ -0,0 +1,255 @@ +# ADR-0023: Holonñgram visual feedback compiler + +Status: Accepted for projection-only implementation; live projection admission pending + +Depends on: ADR-0021, ADR-0022 + +## Context + +AEGIS already has formulas, runtime traces, state roots, signed lifecycle +receipts, and a read-only Studio. It lacked one closed visual grammar that turns +those objects into an inspectable state-transition surface without allowing the +surface to become an authority source. + +Static mathematical notation is insufficient for this role. The projection +must show the pressure produced by a formula: the receipt-backed transition, +expected and observed roots, terminal outcome, feedback signal, measured edge +changes, next-route suggestion, and chain horizon. At the same time, a visual +frame must never manufacture changed fields, scores, receipt beads, or +provenance that its verified source does not contain. + +## Decision + +The normative wire contract is +`schemas/holonngram-visual-feedback.v1.schema.json`. The TypeScript compiler is +`sovereign-omega-v2/src/projection/holonngram-compiler.ts`. + +Protocol identifiers use the ASCII spelling `holonngram`. Product copy may use +the name “Holonñgram.” + +The compiler accepts only: + +1. a terminal receipt ID; +2. a separately supplied trusted receipt-resolution context; +3. a content-addressed receipt/trust-registry source; and +4. a closed I-JSON formula projection observation. + +It invokes `resolveAndVerifyCrossRuntimeReceiptChainV1`, then reads the terminal +receipt back from the same content-addressed source. It recomputes the terminal +receipt ID and binds its kind, outcome, identities, authority, lease, fence, +action, state roots, and result to the resolver decision. A malformed, +unresolvable, unsigned, unknown-root, stale, replayed, or mismatched source +produces no frame. + +A caller-supplied verification decision is not a compiler input. A valid +`decision_digest` proves decision self-consistency, not fresh receipt-chain +resolution. + +## Canonical frame + +The exact top-level fields are: + +```text +schema_version = "1.0.0" +artifact_kind = "AEGIS_HOLONNGRAM_VISUAL_FEEDBACK_V1" +compiler_version = "holonngram-compiler-v1" +topology_id = "HOLONNGRAM_19_V1" +epistemic_status = "DERIVED_NON_AUTHORITATIVE" +source +formula_trace +state_comparison +feedback +visual +safety +frame_digest +``` + +`source` copies the verified decision bindings and the authenticated terminal +receipt fields needed by the visual grammar: + +```text +provenance_status +decision_digest +terminal_receipt_id +terminal_receipt_kind +terminal_outcome +chain_digest +receipt_count +registry_roots +actor_identity_root +session_identity_root +workspace_identity_root +holon_identity_root +authority_domain +authority_level +authority_receipt_hash +lease_id +lease_generation +fencing_token +lease_authorization_receipt_hash +parent_receipt_hash +observed_state_root +expected_state_root +action_digest +before_state_root +after_state_root +result_digest +terminal_timestamp_ms +terminal_nonce +denial_codes +verifier_identity_root +observed_at_ms +max_clock_skew_ms +``` + +`formula_trace` contains the caller-supplied formula ID, version, definition +digest, transition ID, content-derived projection trace ID, formula +input/output digests, execution status, and measurement status. This slice does +not resolve a signed or content-addressed formula execution artifact. It +therefore emits only `NOT_EXECUTED` or `UNVERIFIED_CALLER_INPUT`; it never +claims that a formula executed successfully. Formula hashes use separate +domains: + +```text +input_digest = + SHA256(JCS({ + domain: "AEGIS_HOLONNGRAM_FORMULA_INPUT_V1", + source + })) + +output_digest = + SHA256(JCS({ + domain: "AEGIS_HOLONNGRAM_FORMULA_OUTPUT_V1", + state_comparison, + feedback, + edge_updates, + next_route + })) + +trace_id = + SHA256(JCS({ + domain: "AEGIS_HOLONNGRAM_FORMULA_TRACE_V1", + formula_id, + formula_version, + formula_definition_digest, + input_digest, + output_digest + })) +``` + +The frame content address is: + +```text +frame_digest = + SHA256(JCS({ + domain: "AEGIS_HOLONNGRAM_VISUAL_FEEDBACK_FRAME_V1", + frame: + })) +``` + +All serialization uses the existing strict I-JSON boundary and the repository's +sole RFC 8785 JCS encoder. No visual-feedback serializer is introduced. + +## State comparison and feedback + +State comparison is roots-only. It carries authenticated observed, expected, +before, and after roots and labels field-level status `ROOTS_ONLY`. A hash +difference cannot prove which fields changed, so `changed_fields` is +intentionally absent. + +Only `MUTATION_COMPLETED` may produce `STATE_CHANGED`. Denied, cancelled, +failed, expired, and revoked terminals must preserve +`before_state_root == after_state_root`. The deterministic feedback map is: + +| Terminal | Signal | Severity | Boundary | +|---|---|---|---| +| Mutation completed | `REINFORCE` | `INFO` | `NONE` | +| Mutation cancelled | `ROLLBACK` | `WARNING` | `CANCELLATION` | +| Mutation failed | `ROLLBACK` | `CRITICAL` | `EXECUTION` | +| Lease expired/revoked | `FAIL_CLOSED` | `CRITICAL` | `LEASE` | +| Denial | versioned denial-code classification | at least `WARNING` | classified boundary | + +Unknown denial codes fall back to `NEEDS_REVIEW`; they never produce a more +permissive signal. When a denial contains codes from multiple categories, the +compiler selects the strongest fail-closed category before any repair or review +category. Equal critical categories use the versioned deterministic order +`TRUST`, `FENCING`, `LEASE`, then `REPLAY`. + +Scores and edge deltas are canonical fixed-point decimal strings in parts per +million, bounded to ±1,000,000. Caller-provided values are explicitly labelled +`CALLER_SUPPLIED_UNVERIFIED`; hashing them gives deterministic integrity, not +formula-execution provenance. If no formula measurement exists, the status is +`NOT_COMPUTED`, values are `null`, and edge updates are empty. The compiler +does not invent a zero score. A future trace-backed measurement status requires +a separately resolvable formula execution artifact and trust rule. + +## Fixed topology + +`HOLONNGRAM_19_V1` has one center node, six inner roles, and twelve outer +witnesses: + +```text +C0 CURRENT_ENVELOPE +I1 INTERPRETER +I2 ASSESSOR +I3 LEASE_GUARD +I4 EXECUTOR +I5 VERIFIER +I6 COMMITTER +O1 ACTOR_WITNESS +O2 SESSION_WITNESS +O3 WORKSPACE_WITNESS +O4 HOLON_WITNESS +O5 AUTHORITY_WITNESS +O6 LEASE_WITNESS +O7 FENCE_WITNESS +O8 EXPECTED_STATE_WITNESS +O9 OBSERVED_STATE_WITNESS +O10 ACTION_WITNESS +O11 RESULT_WITNESS +O12 TRUST_CHAIN_WITNESS +``` + +Node order, ring, role, state, and content-addressed source references are +validated on compilation and integrity read-back. Where a signed lease +lifecycle field legitimately uses the protocol's zero sentinel, its visual +node references the non-zero terminal receipt ID instead of presenting the +sentinel as evidence. A receipt timeline contains only the terminal receipt ID, +chain digest, and receipt count because the current resolver decision does not +expose the ordered receipt IDs. The compiler does not fabricate intermediate +ledger beads. + +## Non-authority invariant + +Every frame contains: + +```text +safety.grants_authority = false +safety.executes_mutation = false +safety.promotes_evidence = false +safety.claims_authoritative_provenance = false +safety.route_adjustment_authorized = false +``` + +The source may truthfully say that receipt provenance was verified. The visual +artifact itself remains derived and non-authoritative. `next_route` is a display +suggestion, never an executable route decision. + +## Studio boundary + +The first Studio Holonñgram surface implements the seven requested visual +regions and the 19-node layout, but it is intentionally not connected to the +receipt resolver. Bridge telemetry is labelled unverified display input. +Formula IDs, roots, receipts, trust, and edge measurements remain visibly +unresolved. This gives operators an inspectable grammar without weakening +ADR-0022's projection prohibition. + +`verifyHolonngramVisualFeedbackFrameIntegrityV1` validates strict shape and +attacker-detecting consistency only when the expected digest is already +trusted; because an untrusted party can recompute an unsigned frame digest, the +function does not authenticate receipt provenance. Live binding requires a +confined read-only transport that delivers a +`HolonngramVisualFeedbackFrameV1` and invokes +`resolveAndCompileHolonngramVisualFeedbackV1` again against current +operator-pinned trust and time context. Cockpit, MCP, game, provider mutation, +and route actuation remain out of scope. diff --git a/schemas/holonngram-visual-feedback.v1.schema.json b/schemas/holonngram-visual-feedback.v1.schema.json new file mode 100644 index 000000000..93b4e7ea1 --- /dev/null +++ b/schemas/holonngram-visual-feedback.v1.schema.json @@ -0,0 +1,758 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://aegis.local/schemas/holonngram-visual-feedback.v1.schema.json", + "title": "AEGIS Holonngram Visual Feedback Frame V1", + "description": "A deterministic, receipt-derived, non-authoritative visual projection. This artifact never grants authority, executes mutations, promotes evidence, claims authoritative provenance, or authorizes route changes.", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "artifact_kind", + "compiler_version", + "topology_id", + "epistemic_status", + "source", + "formula_trace", + "state_comparison", + "feedback", + "visual", + "safety", + "frame_digest" + ], + "properties": { + "schema_version": { "const": "1.0.0" }, + "artifact_kind": { "const": "AEGIS_HOLONNGRAM_VISUAL_FEEDBACK_V1" }, + "compiler_version": { "const": "holonngram-compiler-v1" }, + "topology_id": { "const": "HOLONNGRAM_19_V1" }, + "epistemic_status": { "const": "DERIVED_NON_AUTHORITATIVE" }, + "source": { "$ref": "#/$defs/source" }, + "formula_trace": { "$ref": "#/$defs/formulaTrace" }, + "state_comparison": { "$ref": "#/$defs/stateComparison" }, + "feedback": { "$ref": "#/$defs/feedback" }, + "visual": { "$ref": "#/$defs/visual" }, + "safety": { + "type": "object", + "additionalProperties": false, + "required": [ + "grants_authority", + "executes_mutation", + "promotes_evidence", + "claims_authoritative_provenance", + "route_adjustment_authorized" + ], + "properties": { + "grants_authority": { "const": false }, + "executes_mutation": { "const": false }, + "promotes_evidence": { "const": false }, + "claims_authoritative_provenance": { "const": false }, + "route_adjustment_authorized": { "const": false } + } + }, + "frame_digest": { "$ref": "#/$defs/nonZeroHash" } + }, + "allOf": [ + { + "if": { + "properties": { + "formula_trace": { + "properties": { + "measurement_status": { "const": "NOT_COMPUTED" } + } + } + } + }, + "then": { + "properties": { + "feedback": { + "properties": { + "resonance": { + "properties": { + "measurement_status": { "const": "NOT_COMPUTED" } + } + }, + "value": { + "properties": { + "measurement_status": { "const": "NOT_COMPUTED" } + } + } + } + }, + "visual": { + "properties": { + "edge_updates": { "maxItems": 0 } + } + } + } + }, + "else": { + "properties": { + "feedback": { + "properties": { + "resonance": { + "properties": { + "measurement_status": { + "const": "CALLER_SUPPLIED_UNVERIFIED" + } + } + }, + "value": { + "properties": { + "measurement_status": { + "const": "CALLER_SUPPLIED_UNVERIFIED" + } + } + } + } + }, + "visual": { + "properties": { + "edge_updates": { + "items": { + "properties": { + "measurement_status": { + "const": "CALLER_SUPPLIED_UNVERIFIED" + } + } + } + } + } + } + } + } + } + ], + "$defs": { + "hash": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "nonZeroHash": { + "type": "string", + "pattern": "^(?!0{64}$)[0-9a-f]{64}$" + }, + "safeId": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$" + }, + "decimal": { + "type": "string", + "pattern": "^(0|[1-9][0-9]{0,19})$" + }, + "positiveDecimal": { + "type": "string", + "pattern": "^[1-9][0-9]{0,19}$" + }, + "ppm": { + "type": "string", + "pattern": "^(0|[1-9][0-9]{0,5}|1000000)$" + }, + "signedPpm": { + "type": "string", + "pattern": "^-?(0|[1-9][0-9]{0,5}|1000000)$", + "not": { "const": "-0" } + }, + "receiptKind": { + "enum": [ + "LEASE_ISSUED", + "LEASE_ISSUANCE_DENIED", + "LEASE_RENEWED", + "LEASE_RENEWAL_DENIED", + "LEASE_EXPIRED", + "LEASE_REVOKED", + "MUTATION_ADMITTED", + "MUTATION_DENIED", + "MUTATION_COMPLETED", + "MUTATION_CANCELLED", + "MUTATION_FAILED" + ] + }, + "receiptOutcome": { + "enum": [ + "ADMITTED", + "DENIED", + "COMPLETED", + "CANCELLED", + "FAILED", + "EXPIRED", + "REVOKED" + ] + }, + "source": { + "type": "object", + "additionalProperties": false, + "required": [ + "provenance_status", + "decision_digest", + "terminal_receipt_id", + "terminal_receipt_kind", + "terminal_outcome", + "chain_digest", + "receipt_count", + "registry_roots", + "actor_identity_root", + "session_identity_root", + "workspace_identity_root", + "holon_identity_root", + "authority_domain", + "authority_level", + "authority_receipt_hash", + "lease_id", + "lease_generation", + "fencing_token", + "lease_authorization_receipt_hash", + "parent_receipt_hash", + "observed_state_root", + "expected_state_root", + "action_digest", + "before_state_root", + "after_state_root", + "result_digest", + "terminal_timestamp_ms", + "terminal_nonce", + "denial_codes", + "verifier_identity_root", + "observed_at_ms", + "max_clock_skew_ms" + ], + "properties": { + "provenance_status": { + "const": "AUTHORITATIVE_RECEIPT_PROVENANCE_VERIFIED" + }, + "decision_digest": { "$ref": "#/$defs/nonZeroHash" }, + "terminal_receipt_id": { "$ref": "#/$defs/nonZeroHash" }, + "terminal_receipt_kind": { "$ref": "#/$defs/receiptKind" }, + "terminal_outcome": { "$ref": "#/$defs/receiptOutcome" }, + "chain_digest": { "$ref": "#/$defs/nonZeroHash" }, + "receipt_count": { "$ref": "#/$defs/positiveDecimal" }, + "registry_roots": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "$ref": "#/$defs/nonZeroHash" } + }, + "actor_identity_root": { "$ref": "#/$defs/nonZeroHash" }, + "session_identity_root": { "$ref": "#/$defs/nonZeroHash" }, + "workspace_identity_root": { "$ref": "#/$defs/nonZeroHash" }, + "holon_identity_root": { "$ref": "#/$defs/nonZeroHash" }, + "authority_domain": { "$ref": "#/$defs/safeId" }, + "authority_level": { "enum": ["D0", "D1", "D2", "D3", "D4"] }, + "authority_receipt_hash": { "$ref": "#/$defs/hash" }, + "lease_id": { "$ref": "#/$defs/nonZeroHash" }, + "lease_generation": { "$ref": "#/$defs/decimal" }, + "fencing_token": { "$ref": "#/$defs/hash" }, + "lease_authorization_receipt_hash": { "$ref": "#/$defs/hash" }, + "parent_receipt_hash": { "$ref": "#/$defs/hash" }, + "observed_state_root": { "$ref": "#/$defs/nonZeroHash" }, + "expected_state_root": { "$ref": "#/$defs/nonZeroHash" }, + "action_digest": { "$ref": "#/$defs/nonZeroHash" }, + "before_state_root": { "$ref": "#/$defs/nonZeroHash" }, + "after_state_root": { "$ref": "#/$defs/nonZeroHash" }, + "result_digest": { "$ref": "#/$defs/nonZeroHash" }, + "terminal_timestamp_ms": { "$ref": "#/$defs/decimal" }, + "terminal_nonce": { "$ref": "#/$defs/safeId" }, + "denial_codes": { + "type": "array", + "uniqueItems": true, + "items": { "$ref": "#/$defs/safeId" } + }, + "verifier_identity_root": { "$ref": "#/$defs/nonZeroHash" }, + "observed_at_ms": { "$ref": "#/$defs/decimal" }, + "max_clock_skew_ms": { "$ref": "#/$defs/decimal" } + } + }, + "formulaTrace": { + "type": "object", + "additionalProperties": false, + "required": [ + "formula_id", + "formula_version", + "formula_definition_digest", + "transition_id", + "trace_id", + "input_digest", + "output_digest", + "execution_status", + "measurement_status" + ], + "properties": { + "formula_id": { "$ref": "#/$defs/safeId" }, + "formula_version": { "$ref": "#/$defs/safeId" }, + "formula_definition_digest": { "$ref": "#/$defs/nonZeroHash" }, + "transition_id": { "$ref": "#/$defs/safeId" }, + "trace_id": { "$ref": "#/$defs/nonZeroHash" }, + "input_digest": { "$ref": "#/$defs/nonZeroHash" }, + "output_digest": { "$ref": "#/$defs/nonZeroHash" }, + "execution_status": { + "enum": ["NOT_EXECUTED", "UNVERIFIED_CALLER_INPUT"] + }, + "measurement_status": { + "enum": ["NOT_COMPUTED", "CALLER_SUPPLIED_UNVERIFIED"] + } + }, + "allOf": [ + { + "if": { + "properties": { + "measurement_status": { "const": "NOT_COMPUTED" } + } + }, + "then": { + "properties": { + "execution_status": { "const": "NOT_EXECUTED" } + } + }, + "else": { + "properties": { + "execution_status": { "const": "UNVERIFIED_CALLER_INPUT" } + } + } + } + ] + }, + "stateComparison": { + "type": "object", + "additionalProperties": false, + "required": [ + "observed_state_root", + "expected_state_root", + "before_state_root", + "after_state_root", + "field_diff_status", + "delta_type" + ], + "properties": { + "observed_state_root": { "$ref": "#/$defs/nonZeroHash" }, + "expected_state_root": { "$ref": "#/$defs/nonZeroHash" }, + "before_state_root": { "$ref": "#/$defs/nonZeroHash" }, + "after_state_root": { "$ref": "#/$defs/nonZeroHash" }, + "field_diff_status": { "const": "ROOTS_ONLY" }, + "delta_type": { + "enum": [ + "MATCH", + "STATE_CHANGED", + "STALE_EXPECTATION", + "DENIED", + "CANCELLED", + "FAILED", + "EXPIRED", + "REVOKED" + ] + } + } + }, + "measuredResonance": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["measurement_status", "ppm"], + "properties": { + "measurement_status": { "const": "NOT_COMPUTED" }, + "ppm": { "type": "null" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["measurement_status", "ppm"], + "properties": { + "measurement_status": { "const": "CALLER_SUPPLIED_UNVERIFIED" }, + "ppm": { "$ref": "#/$defs/ppm" } + } + } + ] + }, + "measuredValue": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["measurement_status", "delta_ppm"], + "properties": { + "measurement_status": { "const": "NOT_COMPUTED" }, + "delta_ppm": { "type": "null" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["measurement_status", "delta_ppm"], + "properties": { + "measurement_status": { "const": "CALLER_SUPPLIED_UNVERIFIED" }, + "delta_ppm": { "$ref": "#/$defs/signedPpm" } + } + } + ] + }, + "feedback": { + "type": "object", + "additionalProperties": false, + "required": [ + "signal", + "severity", + "boundary", + "rationale_codes", + "resonance", + "value" + ], + "properties": { + "signal": { + "enum": [ + "REINFORCE", + "NEEDS_REVIEW", + "REQUEST_GRANT", + "REPAIR_SCHEMA", + "ROLLBACK", + "FAIL_CLOSED" + ] + }, + "severity": { + "enum": ["INFO", "REVIEW", "WARNING", "CRITICAL", "FATAL"] + }, + "boundary": { + "enum": [ + "NONE", + "AUTHORITY", + "LEASE", + "FENCING", + "STATE", + "SCHEMA", + "TRUST", + "REPLAY", + "CANCELLATION", + "EXECUTION" + ] + }, + "rationale_codes": { + "type": "array", + "uniqueItems": true, + "items": { "$ref": "#/$defs/safeId" } + }, + "resonance": { "$ref": "#/$defs/measuredResonance" }, + "value": { "$ref": "#/$defs/measuredValue" } + } + }, + "nodeState": { + "enum": [ + "IDLE", + "ACTIVE", + "VERIFIED", + "DENIED", + "EXPIRED", + "REVOKED", + "CANCELLED", + "FAILED", + "CHANGED", + "UNCHANGED" + ] + }, + "nodeBase": { + "type": "object", + "additionalProperties": false, + "required": ["node_id", "ring", "role", "state", "source_refs"], + "properties": { + "node_id": { "type": "string" }, + "ring": { "enum": ["CENTER", "INNER", "OUTER"] }, + "role": { "type": "string" }, + "state": { "$ref": "#/$defs/nodeState" }, + "source_refs": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "$ref": "#/$defs/nonZeroHash" } + } + } + }, + "edgeUpdate": { + "type": "object", + "additionalProperties": false, + "required": [ + "from_node", + "to_node", + "edge_kind", + "measurement_status", + "trust_delta_ppm", + "risk_delta_ppm", + "schema_delta_ppm", + "authority_delta_ppm", + "basis_codes" + ], + "properties": { + "from_node": { + "enum": [ + "C0", "I1", "I2", "I3", "I4", "I5", "I6", + "O1", "O2", "O3", "O4", "O5", "O6", + "O7", "O8", "O9", "O10", "O11", "O12" + ] + }, + "to_node": { + "enum": [ + "C0", "I1", "I2", "I3", "I4", "I5", "I6", + "O1", "O2", "O3", "O4", "O5", "O6", + "O7", "O8", "O9", "O10", "O11", "O12" + ] + }, + "edge_kind": { + "enum": [ + "FLOW", + "AUTHORITY", + "TRUST", + "STATE", + "PROVENANCE", + "FEEDBACK" + ] + }, + "measurement_status": { + "enum": ["NOT_COMPUTED", "CALLER_SUPPLIED_UNVERIFIED"] + }, + "trust_delta_ppm": { + "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/signedPpm" }] + }, + "risk_delta_ppm": { + "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/signedPpm" }] + }, + "schema_delta_ppm": { + "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/signedPpm" }] + }, + "authority_delta_ppm": { + "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/signedPpm" }] + }, + "basis_codes": { + "type": "array", + "uniqueItems": true, + "items": { "$ref": "#/$defs/safeId" } + } + }, + "allOf": [ + { + "if": { + "properties": { + "measurement_status": { "const": "NOT_COMPUTED" } + } + }, + "then": { + "properties": { + "trust_delta_ppm": { "type": "null" }, + "risk_delta_ppm": { "type": "null" }, + "schema_delta_ppm": { "type": "null" }, + "authority_delta_ppm": { "type": "null" }, + "basis_codes": { "maxItems": 0 } + } + }, + "else": { + "properties": { + "trust_delta_ppm": { "$ref": "#/$defs/signedPpm" }, + "risk_delta_ppm": { "$ref": "#/$defs/signedPpm" }, + "schema_delta_ppm": { "$ref": "#/$defs/signedPpm" }, + "authority_delta_ppm": { "$ref": "#/$defs/signedPpm" } + } + } + } + ] + }, + "visual": { + "type": "object", + "additionalProperties": false, + "required": [ + "nodes", + "edge_updates", + "event", + "receipt_timeline", + "next_route" + ], + "properties": { + "nodes": { + "type": "array", + "minItems": 19, + "maxItems": 19, + "prefixItems": [ + { "$ref": "#/$defs/nodeC0" }, + { "$ref": "#/$defs/nodeI1" }, + { "$ref": "#/$defs/nodeI2" }, + { "$ref": "#/$defs/nodeI3" }, + { "$ref": "#/$defs/nodeI4" }, + { "$ref": "#/$defs/nodeI5" }, + { "$ref": "#/$defs/nodeI6" }, + { "$ref": "#/$defs/nodeO1" }, + { "$ref": "#/$defs/nodeO2" }, + { "$ref": "#/$defs/nodeO3" }, + { "$ref": "#/$defs/nodeO4" }, + { "$ref": "#/$defs/nodeO5" }, + { "$ref": "#/$defs/nodeO6" }, + { "$ref": "#/$defs/nodeO7" }, + { "$ref": "#/$defs/nodeO8" }, + { "$ref": "#/$defs/nodeO9" }, + { "$ref": "#/$defs/nodeO10" }, + { "$ref": "#/$defs/nodeO11" }, + { "$ref": "#/$defs/nodeO12" } + ], + "items": false + }, + "edge_updates": { + "type": "array", + "items": { "$ref": "#/$defs/edgeUpdate" } + }, + "event": { + "type": "object", + "additionalProperties": false, + "required": [ + "transition_id", + "terminal_receipt_id", + "terminal_receipt_kind", + "terminal_outcome", + "severity", + "signal" + ], + "properties": { + "transition_id": { "$ref": "#/$defs/safeId" }, + "terminal_receipt_id": { "$ref": "#/$defs/nonZeroHash" }, + "terminal_receipt_kind": { "$ref": "#/$defs/receiptKind" }, + "terminal_outcome": { "$ref": "#/$defs/receiptOutcome" }, + "severity": { + "enum": ["INFO", "REVIEW", "WARNING", "CRITICAL", "FATAL"] + }, + "signal": { + "enum": [ + "REINFORCE", + "NEEDS_REVIEW", + "REQUEST_GRANT", + "REPAIR_SCHEMA", + "ROLLBACK", + "FAIL_CLOSED" + ] + } + } + }, + "receipt_timeline": { + "type": "object", + "additionalProperties": false, + "required": [ + "terminal_receipt_id", + "chain_digest", + "receipt_count" + ], + "properties": { + "terminal_receipt_id": { "$ref": "#/$defs/nonZeroHash" }, + "chain_digest": { "$ref": "#/$defs/nonZeroHash" }, + "receipt_count": { "$ref": "#/$defs/positiveDecimal" } + } + }, + "next_route": { "$ref": "#/$defs/safeId" } + } + }, + "nodeC0": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "C0" }, "ring": { "const": "CENTER" }, "role": { "const": "CURRENT_ENVELOPE" } } } + ] + }, + "nodeI1": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "I1" }, "ring": { "const": "INNER" }, "role": { "const": "INTERPRETER" } } } + ] + }, + "nodeI2": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "I2" }, "ring": { "const": "INNER" }, "role": { "const": "ASSESSOR" } } } + ] + }, + "nodeI3": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "I3" }, "ring": { "const": "INNER" }, "role": { "const": "LEASE_GUARD" } } } + ] + }, + "nodeI4": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "I4" }, "ring": { "const": "INNER" }, "role": { "const": "EXECUTOR" } } } + ] + }, + "nodeI5": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "I5" }, "ring": { "const": "INNER" }, "role": { "const": "VERIFIER" } } } + ] + }, + "nodeI6": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "I6" }, "ring": { "const": "INNER" }, "role": { "const": "COMMITTER" } } } + ] + }, + "nodeO1": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "O1" }, "ring": { "const": "OUTER" }, "role": { "const": "ACTOR_WITNESS" } } } + ] + }, + "nodeO2": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "O2" }, "ring": { "const": "OUTER" }, "role": { "const": "SESSION_WITNESS" } } } + ] + }, + "nodeO3": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "O3" }, "ring": { "const": "OUTER" }, "role": { "const": "WORKSPACE_WITNESS" } } } + ] + }, + "nodeO4": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "O4" }, "ring": { "const": "OUTER" }, "role": { "const": "HOLON_WITNESS" } } } + ] + }, + "nodeO5": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "O5" }, "ring": { "const": "OUTER" }, "role": { "const": "AUTHORITY_WITNESS" } } } + ] + }, + "nodeO6": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "O6" }, "ring": { "const": "OUTER" }, "role": { "const": "LEASE_WITNESS" } } } + ] + }, + "nodeO7": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "O7" }, "ring": { "const": "OUTER" }, "role": { "const": "FENCE_WITNESS" } } } + ] + }, + "nodeO8": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "O8" }, "ring": { "const": "OUTER" }, "role": { "const": "EXPECTED_STATE_WITNESS" } } } + ] + }, + "nodeO9": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "O9" }, "ring": { "const": "OUTER" }, "role": { "const": "OBSERVED_STATE_WITNESS" } } } + ] + }, + "nodeO10": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "O10" }, "ring": { "const": "OUTER" }, "role": { "const": "ACTION_WITNESS" } } } + ] + }, + "nodeO11": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "O11" }, "ring": { "const": "OUTER" }, "role": { "const": "RESULT_WITNESS" } } } + ] + }, + "nodeO12": { + "allOf": [ + { "$ref": "#/$defs/nodeBase" }, + { "properties": { "node_id": { "const": "O12" }, "ring": { "const": "OUTER" }, "role": { "const": "TRUST_CHAIN_WITNESS" } } } + ] + } + } +} diff --git a/sovereign-omega-v2/package-lock.json b/sovereign-omega-v2/package-lock.json index 7d66eb650..706ef5491 100644 --- a/sovereign-omega-v2/package-lock.json +++ b/sovereign-omega-v2/package-lock.json @@ -22,6 +22,7 @@ "@types/uuid": "^10.0.0", "@vitejs/plugin-react": "^6.0.3", "@vitest/coverage-v8": "^4.1.6", + "ajv": "8.17.1", "autoprefixer": "^10.4.20", "eslint": "^9.9.0", "fake-indexeddb": "^6.2.5", @@ -481,6 +482,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/js": { "version": "9.39.4", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", @@ -1276,16 +1301,16 @@ } }, "node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -1929,6 +1954,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -2080,6 +2129,23 @@ "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", "license": "Unlicense" }, + "node_modules/fast-uri": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -2531,9 +2597,9 @@ } }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, diff --git a/sovereign-omega-v2/package.json b/sovereign-omega-v2/package.json index 7e7e6ceaa..e05e3669f 100644 --- a/sovereign-omega-v2/package.json +++ b/sovereign-omega-v2/package.json @@ -32,6 +32,7 @@ "@types/uuid": "^10.0.0", "@vitejs/plugin-react": "^6.0.3", "@vitest/coverage-v8": "^4.1.6", + "ajv": "8.17.1", "autoprefixer": "^10.4.20", "eslint": "^9.9.0", "fake-indexeddb": "^6.2.5", diff --git a/sovereign-omega-v2/src/projection/holonngram-compiler.ts b/sovereign-omega-v2/src/projection/holonngram-compiler.ts new file mode 100644 index 000000000..bbeb22f03 --- /dev/null +++ b/sovereign-omega-v2/src/projection/holonngram-compiler.ts @@ -0,0 +1,1541 @@ +// ============================================================ +// AEGIS Holonñgram Visual Feedback Compiler V1 +// EPISTEMIC STATUS: DERIVED_NON_AUTHORITATIVE +// +// A fresh, independently verified receipt chain is the only admissible source. +// The output is a read-only visual projection: it cannot grant authority, +// execute a mutation, promote evidence, or authorize a route adjustment. +// ============================================================ + +import { canonicalizeJCS, canonicalizeJCSString } from '../core/canonicalize.js' +import { sha256Hex } from '../core/hashing.js' +import { assertIJsonValue } from '../core/i-json.js' +import { deepFreeze } from '../core/immutable.js' +import type { SHA256Hex } from '../core/types.js' +import { + assertCrossRuntimeReceiptIdV1, + normalizeCrossRuntimeReceiptEnvelopeV1, +} from '../provenance/cross-runtime-receipts.js' +import type { + AuthorityLevelV1, + CrossRuntimeReceiptEnvelopeV1, + CrossRuntimeReceiptKindV1, + CrossRuntimeReceiptOutcomeV1, + DecimalStringV1, +} from '../provenance/cross-runtime-receipts.js' +import { + resolveAndVerifyCrossRuntimeReceiptChainV1, +} from '../provenance/receipt-resolver.js' +import type { + CrossRuntimeReceiptSourceV1, + CrossRuntimeReceiptVerificationDecisionV1, + TrustedReceiptResolutionContextV1, +} from '../provenance/receipt-resolver.js' + +export const HOLONNGRAM_VISUAL_FEEDBACK_SCHEMA_VERSION = '1.0.0' as const +export const HOLONNGRAM_ARTIFACT_KIND = + 'AEGIS_HOLONNGRAM_VISUAL_FEEDBACK_V1' as const +export const HOLONNGRAM_COMPILER_VERSION = 'holonngram-compiler-v1' as const +export const HOLONNGRAM_TOPOLOGY_ID = 'HOLONNGRAM_19_V1' as const +export const HOLONNGRAM_FRAME_DOMAIN = + 'AEGIS_HOLONNGRAM_VISUAL_FEEDBACK_FRAME_V1' as const +export const HOLONNGRAM_FORMULA_INPUT_DOMAIN = + 'AEGIS_HOLONNGRAM_FORMULA_INPUT_V1' as const +export const HOLONNGRAM_FORMULA_OUTPUT_DOMAIN = + 'AEGIS_HOLONNGRAM_FORMULA_OUTPUT_V1' as const +export const HOLONNGRAM_FORMULA_TRACE_DOMAIN = + 'AEGIS_HOLONNGRAM_FORMULA_TRACE_V1' as const + +export const HOLONNGRAM_NODE_DEFINITIONS_V1 = deepFreeze([ + { node_id: 'C0', ring: 'CENTER', role: 'CURRENT_ENVELOPE' }, + { node_id: 'I1', ring: 'INNER', role: 'INTERPRETER' }, + { node_id: 'I2', ring: 'INNER', role: 'ASSESSOR' }, + { node_id: 'I3', ring: 'INNER', role: 'LEASE_GUARD' }, + { node_id: 'I4', ring: 'INNER', role: 'EXECUTOR' }, + { node_id: 'I5', ring: 'INNER', role: 'VERIFIER' }, + { node_id: 'I6', ring: 'INNER', role: 'COMMITTER' }, + { node_id: 'O1', ring: 'OUTER', role: 'ACTOR_WITNESS' }, + { node_id: 'O2', ring: 'OUTER', role: 'SESSION_WITNESS' }, + { node_id: 'O3', ring: 'OUTER', role: 'WORKSPACE_WITNESS' }, + { node_id: 'O4', ring: 'OUTER', role: 'HOLON_WITNESS' }, + { node_id: 'O5', ring: 'OUTER', role: 'AUTHORITY_WITNESS' }, + { node_id: 'O6', ring: 'OUTER', role: 'LEASE_WITNESS' }, + { node_id: 'O7', ring: 'OUTER', role: 'FENCE_WITNESS' }, + { node_id: 'O8', ring: 'OUTER', role: 'EXPECTED_STATE_WITNESS' }, + { node_id: 'O9', ring: 'OUTER', role: 'OBSERVED_STATE_WITNESS' }, + { node_id: 'O10', ring: 'OUTER', role: 'ACTION_WITNESS' }, + { node_id: 'O11', ring: 'OUTER', role: 'RESULT_WITNESS' }, + { node_id: 'O12', ring: 'OUTER', role: 'TRUST_CHAIN_WITNESS' }, +] as const) + +export type HolonngramNodeIdV1 = + typeof HOLONNGRAM_NODE_DEFINITIONS_V1[number]['node_id'] +export type HolonngramNodeRingV1 = + typeof HOLONNGRAM_NODE_DEFINITIONS_V1[number]['ring'] +export type HolonngramNodeRoleV1 = + typeof HOLONNGRAM_NODE_DEFINITIONS_V1[number]['role'] +export type HolonngramNodeStateV1 = + | 'IDLE' + | 'ACTIVE' + | 'VERIFIED' + | 'DENIED' + | 'EXPIRED' + | 'REVOKED' + | 'CANCELLED' + | 'FAILED' + | 'CHANGED' + | 'UNCHANGED' +export type HolonngramMeasurementStatusV1 = + | 'NOT_COMPUTED' + | 'CALLER_SUPPLIED_UNVERIFIED' +export type HolonngramFormulaExecutionStatusV1 = + | 'NOT_EXECUTED' + | 'UNVERIFIED_CALLER_INPUT' +export type HolonngramDeltaTypeV1 = + | 'MATCH' + | 'STATE_CHANGED' + | 'STALE_EXPECTATION' + | 'DENIED' + | 'CANCELLED' + | 'FAILED' + | 'EXPIRED' + | 'REVOKED' +export type HolonngramFeedbackSignalV1 = + | 'REINFORCE' + | 'NEEDS_REVIEW' + | 'REQUEST_GRANT' + | 'REPAIR_SCHEMA' + | 'ROLLBACK' + | 'FAIL_CLOSED' +export type HolonngramSeverityV1 = + | 'INFO' + | 'REVIEW' + | 'WARNING' + | 'CRITICAL' + | 'FATAL' +export type HolonngramBoundaryV1 = + | 'NONE' + | 'AUTHORITY' + | 'LEASE' + | 'FENCING' + | 'STATE' + | 'SCHEMA' + | 'TRUST' + | 'REPLAY' + | 'CANCELLATION' + | 'EXECUTION' +export type HolonngramEdgeKindV1 = + | 'FLOW' + | 'AUTHORITY' + | 'TRUST' + | 'STATE' + | 'PROVENANCE' + | 'FEEDBACK' + +export interface HolonngramCompilerInputV1 { + readonly schema_version: typeof HOLONNGRAM_VISUAL_FEEDBACK_SCHEMA_VERSION + readonly compiler_version: typeof HOLONNGRAM_COMPILER_VERSION + readonly formula_id: string + readonly formula_version: string + readonly formula_definition_digest: SHA256Hex + readonly transition_id: string + readonly measurement: { + readonly status: HolonngramMeasurementStatusV1 + readonly resonance_ppm: DecimalStringV1 | null + readonly value_delta_ppm: DecimalStringV1 | null + } + readonly edge_updates: readonly HolonngramEdgeUpdateV1[] + readonly next_route: string +} + +export interface HolonngramEdgeUpdateV1 { + readonly from_node: HolonngramNodeIdV1 + readonly to_node: HolonngramNodeIdV1 + readonly edge_kind: HolonngramEdgeKindV1 + readonly measurement_status: HolonngramMeasurementStatusV1 + readonly trust_delta_ppm: DecimalStringV1 | null + readonly risk_delta_ppm: DecimalStringV1 | null + readonly schema_delta_ppm: DecimalStringV1 | null + readonly authority_delta_ppm: DecimalStringV1 | null + readonly basis_codes: readonly string[] +} + +export interface HolonngramSourceV1 { + readonly provenance_status: 'AUTHORITATIVE_RECEIPT_PROVENANCE_VERIFIED' + readonly decision_digest: SHA256Hex + readonly terminal_receipt_id: SHA256Hex + readonly terminal_receipt_kind: CrossRuntimeReceiptKindV1 + readonly terminal_outcome: CrossRuntimeReceiptOutcomeV1 + readonly chain_digest: SHA256Hex + readonly receipt_count: DecimalStringV1 + readonly registry_roots: readonly SHA256Hex[] + readonly actor_identity_root: SHA256Hex + readonly session_identity_root: SHA256Hex + readonly workspace_identity_root: SHA256Hex + readonly holon_identity_root: SHA256Hex + readonly authority_domain: string + readonly authority_level: AuthorityLevelV1 + readonly authority_receipt_hash: SHA256Hex + readonly lease_id: SHA256Hex + readonly lease_generation: DecimalStringV1 + readonly fencing_token: SHA256Hex + readonly lease_authorization_receipt_hash: SHA256Hex + readonly parent_receipt_hash: SHA256Hex + readonly observed_state_root: SHA256Hex + readonly expected_state_root: SHA256Hex + readonly action_digest: SHA256Hex + readonly before_state_root: SHA256Hex + readonly after_state_root: SHA256Hex + readonly result_digest: SHA256Hex + readonly terminal_timestamp_ms: DecimalStringV1 + readonly terminal_nonce: string + readonly denial_codes: readonly string[] + readonly verifier_identity_root: SHA256Hex + readonly observed_at_ms: DecimalStringV1 + readonly max_clock_skew_ms: DecimalStringV1 +} + +export interface HolonngramFormulaTraceV1 { + readonly formula_id: string + readonly formula_version: string + readonly formula_definition_digest: SHA256Hex + readonly transition_id: string + readonly trace_id: SHA256Hex + readonly input_digest: SHA256Hex + readonly output_digest: SHA256Hex + readonly execution_status: HolonngramFormulaExecutionStatusV1 + readonly measurement_status: HolonngramMeasurementStatusV1 +} + +export interface HolonngramStateComparisonV1 { + readonly observed_state_root: SHA256Hex + readonly expected_state_root: SHA256Hex + readonly before_state_root: SHA256Hex + readonly after_state_root: SHA256Hex + readonly field_diff_status: 'ROOTS_ONLY' + readonly delta_type: HolonngramDeltaTypeV1 +} + +export interface HolonngramFeedbackV1 { + readonly signal: HolonngramFeedbackSignalV1 + readonly severity: HolonngramSeverityV1 + readonly boundary: HolonngramBoundaryV1 + readonly rationale_codes: readonly string[] + readonly resonance: { + readonly measurement_status: HolonngramMeasurementStatusV1 + readonly ppm: DecimalStringV1 | null + } + readonly value: { + readonly measurement_status: HolonngramMeasurementStatusV1 + readonly delta_ppm: DecimalStringV1 | null + } +} + +export interface HolonngramVisualNodeV1 { + readonly node_id: HolonngramNodeIdV1 + readonly ring: HolonngramNodeRingV1 + readonly role: HolonngramNodeRoleV1 + readonly state: HolonngramNodeStateV1 + readonly source_refs: readonly SHA256Hex[] +} + +export interface HolonngramVisualV1 { + readonly nodes: readonly HolonngramVisualNodeV1[] + readonly edge_updates: readonly HolonngramEdgeUpdateV1[] + readonly event: { + readonly transition_id: string + readonly terminal_receipt_id: SHA256Hex + readonly terminal_receipt_kind: CrossRuntimeReceiptKindV1 + readonly terminal_outcome: CrossRuntimeReceiptOutcomeV1 + readonly severity: HolonngramSeverityV1 + readonly signal: HolonngramFeedbackSignalV1 + } + readonly receipt_timeline: { + readonly terminal_receipt_id: SHA256Hex + readonly chain_digest: SHA256Hex + readonly receipt_count: DecimalStringV1 + } + readonly next_route: string +} + +export interface HolonngramVisualFeedbackFrameV1 { + readonly schema_version: typeof HOLONNGRAM_VISUAL_FEEDBACK_SCHEMA_VERSION + readonly artifact_kind: typeof HOLONNGRAM_ARTIFACT_KIND + readonly compiler_version: typeof HOLONNGRAM_COMPILER_VERSION + readonly topology_id: typeof HOLONNGRAM_TOPOLOGY_ID + readonly epistemic_status: 'DERIVED_NON_AUTHORITATIVE' + readonly source: HolonngramSourceV1 + readonly formula_trace: HolonngramFormulaTraceV1 + readonly state_comparison: HolonngramStateComparisonV1 + readonly feedback: HolonngramFeedbackV1 + readonly visual: HolonngramVisualV1 + readonly safety: { + readonly grants_authority: false + readonly executes_mutation: false + readonly promotes_evidence: false + readonly claims_authoritative_provenance: false + readonly route_adjustment_authorized: false + } + readonly frame_digest: SHA256Hex +} + +export class HolonngramCompilerError extends Error { + override readonly name = 'HolonngramCompilerError' + + constructor(message: string) { + super(message) + Object.setPrototypeOf(this, new.target.prototype) + } +} + +const HASH_PATTERN = /^[0-9a-f]{64}$/ +const ZERO_HASH = '0'.repeat(64) +const SAFE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/ +const DECIMAL_PATTERN = /^(0|[1-9][0-9]*)$/ +const SIGNED_DECIMAL_PATTERN = /^-?(0|[1-9][0-9]*)$/ +const MAX_DECIMAL_DIGITS = 20 +const PPM_LIMIT = 1_000_000n +const MEASUREMENT_STATUSES = new Set([ + 'NOT_COMPUTED', 'CALLER_SUPPLIED_UNVERIFIED', +]) +const EDGE_KINDS = new Set([ + 'FLOW', 'AUTHORITY', 'TRUST', 'STATE', 'PROVENANCE', 'FEEDBACK', +]) +const NODE_STATES = new Set([ + 'IDLE', 'ACTIVE', 'VERIFIED', 'DENIED', 'EXPIRED', 'REVOKED', + 'CANCELLED', 'FAILED', 'CHANGED', 'UNCHANGED', +]) +const NODE_IDS = new Set( + HOLONNGRAM_NODE_DEFINITIONS_V1.map(definition => definition.node_id), +) + +/** + * Resolve the full signed chain, bind its terminal receipt, and compile a + * deterministic visual projection. Verification failure produces no frame. + */ +export async function resolveAndCompileHolonngramVisualFeedbackV1( + source: CrossRuntimeReceiptSourceV1, + terminalReceiptId: SHA256Hex, + context: TrustedReceiptResolutionContextV1, + inputValue: unknown, +): Promise { + const input = normalizeHolonngramCompilerInputV1(inputValue) + const decision = await resolveAndVerifyCrossRuntimeReceiptChainV1( + source, + terminalReceiptId, + context, + ) + const terminalValue = await source.resolveReceipt(terminalReceiptId) + if (terminalValue === null) fail('verified terminal receipt disappeared before compilation') + const terminal = normalizeCrossRuntimeReceiptEnvelopeV1(terminalValue) + await assertCrossRuntimeReceiptIdV1(terminal) + assertTerminalBinding(decision, terminal) + + const receiptSource = sourceFrom(decision, terminal) + const stateComparison = deriveStateComparison(terminal) + const feedback = deriveFeedback(terminal, input.measurement) + const edgeUpdates = input.edge_updates + const inputDigest = await sha256Hex(canonicalizeJCS({ + domain: HOLONNGRAM_FORMULA_INPUT_DOMAIN, + source: receiptSource, + })) + const outputDigest = await sha256Hex(canonicalizeJCS({ + domain: HOLONNGRAM_FORMULA_OUTPUT_DOMAIN, + state_comparison: stateComparison, + feedback, + edge_updates: edgeUpdates, + next_route: input.next_route, + })) + const traceId = await sha256Hex(canonicalizeJCS({ + domain: HOLONNGRAM_FORMULA_TRACE_DOMAIN, + formula_id: input.formula_id, + formula_version: input.formula_version, + formula_definition_digest: input.formula_definition_digest, + input_digest: inputDigest, + output_digest: outputDigest, + })) + const formulaTrace = deepFreeze({ + formula_id: input.formula_id, + formula_version: input.formula_version, + formula_definition_digest: input.formula_definition_digest, + transition_id: input.transition_id, + trace_id: traceId, + input_digest: inputDigest, + output_digest: outputDigest, + execution_status: input.measurement.status === 'NOT_COMPUTED' + ? 'NOT_EXECUTED' as const + : 'UNVERIFIED_CALLER_INPUT' as const, + measurement_status: input.measurement.status, + }) + const visual = deepFreeze({ + nodes: deriveNodes(receiptSource, stateComparison), + edge_updates: edgeUpdates, + event: { + transition_id: input.transition_id, + terminal_receipt_id: receiptSource.terminal_receipt_id, + terminal_receipt_kind: receiptSource.terminal_receipt_kind, + terminal_outcome: receiptSource.terminal_outcome, + severity: feedback.severity, + signal: feedback.signal, + }, + receipt_timeline: { + terminal_receipt_id: receiptSource.terminal_receipt_id, + chain_digest: receiptSource.chain_digest, + receipt_count: receiptSource.receipt_count, + }, + next_route: input.next_route, + }) + const unsigned = deepFreeze({ + schema_version: HOLONNGRAM_VISUAL_FEEDBACK_SCHEMA_VERSION, + artifact_kind: HOLONNGRAM_ARTIFACT_KIND, + compiler_version: HOLONNGRAM_COMPILER_VERSION, + topology_id: HOLONNGRAM_TOPOLOGY_ID, + epistemic_status: 'DERIVED_NON_AUTHORITATIVE' as const, + source: receiptSource, + formula_trace: formulaTrace, + state_comparison: stateComparison, + feedback, + visual, + safety: { + grants_authority: false as const, + executes_mutation: false as const, + promotes_evidence: false as const, + claims_authoritative_provenance: false as const, + route_adjustment_authorized: false as const, + }, + }) + const frameDigest = await sha256Hex(canonicalizeJCS({ + domain: HOLONNGRAM_FRAME_DOMAIN, + frame: unsigned, + })) + return normalizeHolonngramVisualFeedbackFrameV1({ + ...unsigned, + frame_digest: frameDigest, + }) +} + +export function normalizeHolonngramCompilerInputV1( + value: unknown, +): HolonngramCompilerInputV1 { + const input = asObject('Holonñgram compiler input', snapshotIJson( + value, 'Holonñgram compiler input', + )) + assertExactKeys('Holonñgram compiler input', input, [ + 'compiler_version', + 'edge_updates', + 'formula_definition_digest', + 'formula_id', + 'formula_version', + 'measurement', + 'next_route', + 'schema_version', + 'transition_id', + ]) + if (input.schema_version !== HOLONNGRAM_VISUAL_FEEDBACK_SCHEMA_VERSION || + input.compiler_version !== HOLONNGRAM_COMPILER_VERSION) { + fail('Holonñgram compiler input version is unsupported') + } + const measurement = normalizeMeasurement(input.measurement, 'input.measurement') + const edgeUpdates = asArray('input.edge_updates', input.edge_updates) + .map((edge, index) => normalizeEdgeUpdate(edge, `input.edge_updates[${index}]`)) + assertEdgesSortedUnique(edgeUpdates) + if (measurement.status === 'NOT_COMPUTED' && edgeUpdates.length !== 0) { + fail('NOT_COMPUTED input cannot claim edge measurements') + } + if (edgeUpdates.some(edge => edge.measurement_status !== measurement.status)) { + fail('edge measurement status must match the formula measurement status') + } + return deepFreeze({ + schema_version: HOLONNGRAM_VISUAL_FEEDBACK_SCHEMA_VERSION, + compiler_version: HOLONNGRAM_COMPILER_VERSION, + formula_id: assertSafeId('input.formula_id', input.formula_id), + formula_version: assertSafeId('input.formula_version', input.formula_version), + formula_definition_digest: assertNonZeroHash( + 'input.formula_definition_digest', input.formula_definition_digest, + ), + transition_id: assertSafeId('input.transition_id', input.transition_id), + measurement, + edge_updates: edgeUpdates, + next_route: assertSafeId('input.next_route', input.next_route), + }) +} + +export function normalizeHolonngramVisualFeedbackFrameV1( + value: unknown, +): HolonngramVisualFeedbackFrameV1 { + const frame = asObject('Holonñgram frame', snapshotIJson(value, 'Holonñgram frame')) + assertExactKeys('Holonñgram frame', frame, [ + 'artifact_kind', + 'compiler_version', + 'epistemic_status', + 'feedback', + 'formula_trace', + 'frame_digest', + 'safety', + 'schema_version', + 'source', + 'state_comparison', + 'topology_id', + 'visual', + ]) + if (frame.schema_version !== HOLONNGRAM_VISUAL_FEEDBACK_SCHEMA_VERSION || + frame.artifact_kind !== HOLONNGRAM_ARTIFACT_KIND || + frame.compiler_version !== HOLONNGRAM_COMPILER_VERSION || + frame.topology_id !== HOLONNGRAM_TOPOLOGY_ID || + frame.epistemic_status !== 'DERIVED_NON_AUTHORITATIVE') { + fail('Holonñgram frame header is unsupported') + } + const source = normalizeSource(frame.source) + const stateComparison = normalizeStateComparison(frame.state_comparison) + assertStateSemantics(source, stateComparison) + const feedback = normalizeFeedback(frame.feedback) + const formulaTrace = normalizeFormulaTrace(frame.formula_trace) + if (formulaTrace.measurement_status !== feedback.resonance.measurement_status || + formulaTrace.measurement_status !== feedback.value.measurement_status) { + fail('formula and feedback measurement statuses disagree') + } + const visual = normalizeVisual(frame.visual, source, feedback) + if (formulaTrace.transition_id !== visual.event.transition_id) { + fail('formula trace transition id does not match the visual event') + } + if (visual.edge_updates.some( + edge => edge.measurement_status !== formulaTrace.measurement_status, + )) { + fail('visual edge measurements are not bound to the formula trace status') + } + if (formulaTrace.measurement_status === 'NOT_COMPUTED' && + visual.edge_updates.length !== 0) { + fail('NOT_COMPUTED frames cannot contain edge updates') + } + const safety = asObject('frame.safety', frame.safety) + assertExactKeys('frame.safety', safety, [ + 'claims_authoritative_provenance', + 'executes_mutation', + 'grants_authority', + 'promotes_evidence', + 'route_adjustment_authorized', + ]) + if (safety.grants_authority !== false || + safety.executes_mutation !== false || + safety.promotes_evidence !== false || + safety.claims_authoritative_provenance !== false || + safety.route_adjustment_authorized !== false) { + fail('Holonñgram safety boundary must remain entirely non-authoritative') + } + return deepFreeze({ + schema_version: HOLONNGRAM_VISUAL_FEEDBACK_SCHEMA_VERSION, + artifact_kind: HOLONNGRAM_ARTIFACT_KIND, + compiler_version: HOLONNGRAM_COMPILER_VERSION, + topology_id: HOLONNGRAM_TOPOLOGY_ID, + epistemic_status: 'DERIVED_NON_AUTHORITATIVE', + source, + formula_trace: formulaTrace, + state_comparison: stateComparison, + feedback, + visual, + safety: { + grants_authority: false, + executes_mutation: false, + promotes_evidence: false, + claims_authoritative_provenance: false, + route_adjustment_authorized: false, + }, + frame_digest: assertNonZeroHash('frame.frame_digest', frame.frame_digest), + }) +} + +/** + * Verify strict shape and deterministic digest integrity of a stored frame. + * + * This function does not authenticate the embedded receipt provenance. Live + * admission must call resolveAndCompileHolonngramVisualFeedbackV1 so the signed + * receipt chain and current trust context are resolved again. + */ +export async function verifyHolonngramVisualFeedbackFrameIntegrityV1( + value: unknown, +): Promise { + const frame = normalizeHolonngramVisualFeedbackFrameV1(value) + const expectedInputDigest = await sha256Hex(canonicalizeJCS({ + domain: HOLONNGRAM_FORMULA_INPUT_DOMAIN, + source: frame.source, + })) + const expectedOutputDigest = await sha256Hex(canonicalizeJCS({ + domain: HOLONNGRAM_FORMULA_OUTPUT_DOMAIN, + state_comparison: frame.state_comparison, + feedback: frame.feedback, + edge_updates: frame.visual.edge_updates, + next_route: frame.visual.next_route, + })) + const expectedTraceId = await sha256Hex(canonicalizeJCS({ + domain: HOLONNGRAM_FORMULA_TRACE_DOMAIN, + formula_id: frame.formula_trace.formula_id, + formula_version: frame.formula_trace.formula_version, + formula_definition_digest: frame.formula_trace.formula_definition_digest, + input_digest: expectedInputDigest, + output_digest: expectedOutputDigest, + })) + if (frame.formula_trace.input_digest !== expectedInputDigest || + frame.formula_trace.output_digest !== expectedOutputDigest || + frame.formula_trace.trace_id !== expectedTraceId) { + fail('Holonñgram formula trace is not bound to its source and visual output') + } + const expectedFeedback = feedbackFromSource(frame.source, { + status: frame.formula_trace.measurement_status, + resonance_ppm: frame.feedback.resonance.ppm, + value_delta_ppm: frame.feedback.value.delta_ppm, + }) + if (canonicalizeJCSString(frame.feedback) !== canonicalizeJCSString(expectedFeedback)) { + fail('Holonñgram feedback does not follow the deterministic terminal map') + } + const expectedNodes = deriveNodes(frame.source, frame.state_comparison) + if (canonicalizeJCSString(frame.visual.nodes) !== canonicalizeJCSString(expectedNodes)) { + fail('Holonñgram nodes do not match their verified source bindings') + } + const { frame_digest: _frameDigest, ...unsigned } = frame + const expected = await sha256Hex(canonicalizeJCS({ + domain: HOLONNGRAM_FRAME_DOMAIN, + frame: unsigned, + })) + if (frame.frame_digest !== expected) fail('Holonñgram frame digest is invalid') + return frame +} + +function sourceFrom( + decision: CrossRuntimeReceiptVerificationDecisionV1, + terminal: CrossRuntimeReceiptEnvelopeV1, +): HolonngramSourceV1 { + const body = terminal.receipt_body + return deepFreeze({ + provenance_status: 'AUTHORITATIVE_RECEIPT_PROVENANCE_VERIFIED', + decision_digest: decision.decision_digest, + terminal_receipt_id: decision.terminal_receipt_id, + terminal_receipt_kind: decision.terminal_receipt_kind, + terminal_outcome: decision.terminal_outcome, + chain_digest: decision.chain_digest, + receipt_count: decision.receipt_count, + registry_roots: decision.registry_roots, + actor_identity_root: decision.actor_identity_root, + session_identity_root: decision.session_identity_root, + workspace_identity_root: decision.workspace_identity_root, + holon_identity_root: decision.holon_identity_root, + authority_domain: decision.authority_domain, + authority_level: decision.authority_level, + authority_receipt_hash: decision.authority_receipt_hash, + lease_id: decision.lease_id, + lease_generation: decision.lease_generation, + fencing_token: decision.fencing_token, + lease_authorization_receipt_hash: decision.lease_authorization_receipt_hash, + parent_receipt_hash: body.parent_receipt_hash, + observed_state_root: body.observed_state_root, + expected_state_root: body.expected_state_root, + action_digest: decision.action_digest, + before_state_root: decision.before_state_root, + after_state_root: decision.after_state_root, + result_digest: decision.result_digest, + terminal_timestamp_ms: body.timestamp_ms, + terminal_nonce: body.nonce, + denial_codes: body.denial_codes, + verifier_identity_root: terminal.proof.verifier_identity_root, + observed_at_ms: decision.observed_at_ms, + max_clock_skew_ms: decision.max_clock_skew_ms, + }) +} + +function assertTerminalBinding( + decision: CrossRuntimeReceiptVerificationDecisionV1, + terminal: CrossRuntimeReceiptEnvelopeV1, +): void { + const body = terminal.receipt_body + const pairs: readonly (readonly [unknown, unknown, string])[] = [ + [terminal.receipt_id, decision.terminal_receipt_id, 'receipt id'], + [terminal.receipt_kind, decision.terminal_receipt_kind, 'receipt kind'], + [body.outcome, decision.terminal_outcome, 'outcome'], + [body.actor_identity_root, decision.actor_identity_root, 'actor identity'], + [body.session_identity_root, decision.session_identity_root, 'session identity'], + [body.workspace_identity_root, decision.workspace_identity_root, 'workspace identity'], + [body.holon_identity_root, decision.holon_identity_root, 'holon identity'], + [body.authority_domain, decision.authority_domain, 'authority domain'], + [body.authority_level, decision.authority_level, 'authority level'], + [body.authority_receipt_hash, decision.authority_receipt_hash, 'authority receipt'], + [body.lease_id, decision.lease_id, 'lease id'], + [body.lease_generation, decision.lease_generation, 'lease generation'], + [body.fencing_token, decision.fencing_token, 'fencing token'], + [ + body.lease_authorization_receipt_hash, + decision.lease_authorization_receipt_hash, + 'lease authorization', + ], + [body.action_digest, decision.action_digest, 'action digest'], + [body.before_state_root, decision.before_state_root, 'before state'], + [body.after_state_root, decision.after_state_root, 'after state'], + [body.result_digest, decision.result_digest, 'result digest'], + ] + for (const [actual, expected, label] of pairs) { + if (actual !== expected) fail(`terminal receipt ${label} does not match verified decision`) + } + if (!decision.registry_roots.includes(terminal.proof.trust_registry_root)) { + fail('terminal receipt trust registry root is absent from the verified decision') + } +} + +function deriveStateComparison( + terminal: CrossRuntimeReceiptEnvelopeV1, +): HolonngramStateComparisonV1 { + const body = terminal.receipt_body + return deepFreeze({ + observed_state_root: body.observed_state_root, + expected_state_root: body.expected_state_root, + before_state_root: body.before_state_root, + after_state_root: body.after_state_root, + field_diff_status: 'ROOTS_ONLY' as const, + delta_type: deltaTypeFor(terminal), + }) +} + +function deltaTypeFor(terminal: CrossRuntimeReceiptEnvelopeV1): HolonngramDeltaTypeV1 { + switch (terminal.receipt_kind) { + case 'MUTATION_COMPLETED': + if (terminal.receipt_body.expected_state_root !== + terminal.receipt_body.observed_state_root) return 'STALE_EXPECTATION' + return terminal.receipt_body.before_state_root === terminal.receipt_body.after_state_root + ? 'MATCH' + : 'STATE_CHANGED' + case 'MUTATION_CANCELLED': return 'CANCELLED' + case 'MUTATION_FAILED': return 'FAILED' + case 'LEASE_EXPIRED': return 'EXPIRED' + case 'LEASE_REVOKED': return 'REVOKED' + case 'LEASE_ISSUANCE_DENIED': + case 'LEASE_RENEWAL_DENIED': + case 'MUTATION_DENIED': + return 'DENIED' + case 'LEASE_ISSUED': + case 'LEASE_RENEWED': + case 'MUTATION_ADMITTED': + fail('non-terminal receipt cannot produce a Holonñgram frame') + } +} + +function deriveFeedback( + terminal: CrossRuntimeReceiptEnvelopeV1, + measurement: HolonngramCompilerInputV1['measurement'], +): HolonngramFeedbackV1 { + return feedbackFromSource({ + terminal_receipt_kind: terminal.receipt_kind, + denial_codes: terminal.receipt_body.denial_codes, + }, measurement) +} + +function feedbackFromSource( + source: Pick, + measurement: HolonngramCompilerInputV1['measurement'], +): HolonngramFeedbackV1 { + const kind = source.terminal_receipt_kind + let signal: HolonngramFeedbackSignalV1 + let severity: HolonngramSeverityV1 + let boundary: HolonngramBoundaryV1 + if (kind === 'MUTATION_COMPLETED') { + signal = 'REINFORCE' + severity = 'INFO' + boundary = 'NONE' + } else if (kind === 'MUTATION_CANCELLED') { + signal = 'ROLLBACK' + severity = 'WARNING' + boundary = 'CANCELLATION' + } else if (kind === 'MUTATION_FAILED') { + signal = 'ROLLBACK' + severity = 'CRITICAL' + boundary = 'EXECUTION' + } else if (kind === 'LEASE_EXPIRED' || kind === 'LEASE_REVOKED') { + signal = 'FAIL_CLOSED' + severity = 'CRITICAL' + boundary = 'LEASE' + } else { + const denial = classifyHolonngramDenialCodesV1(source.denial_codes) + signal = denial.signal + severity = denial.severity + boundary = denial.boundary + } + return deepFreeze({ + signal, + severity, + boundary, + rationale_codes: source.denial_codes, + resonance: { + measurement_status: measurement.status, + ppm: measurement.resonance_ppm, + }, + value: { + measurement_status: measurement.status, + delta_ppm: measurement.value_delta_ppm, + }, + }) +} + +export function classifyHolonngramDenialCodesV1( + codes: readonly string[], +): Pick { + const joined = codes.join('\u0000') + // Fail-closed categories always outrank repair/review categories. The order + // below is also the deterministic boundary tie-break for mixed critical codes. + if (/TRUST|SIGNATURE|UNSIGNED|REGISTRY|KEY/.test(joined)) { + return { signal: 'FAIL_CLOSED', severity: 'CRITICAL', boundary: 'TRUST' } + } + if (/FENC/.test(joined)) { + return { signal: 'FAIL_CLOSED', severity: 'CRITICAL', boundary: 'FENCING' } + } + if (/LEASE|EXPIR/.test(joined)) { + return { signal: 'FAIL_CLOSED', severity: 'CRITICAL', boundary: 'LEASE' } + } + if (/REPLAY|DUPLICATE/.test(joined)) { + return { signal: 'FAIL_CLOSED', severity: 'CRITICAL', boundary: 'REPLAY' } + } + if (/SCHEMA/.test(joined)) { + return { signal: 'REPAIR_SCHEMA', severity: 'WARNING', boundary: 'SCHEMA' } + } + if (/AUTHORITY|GRANT|SCOPE|POLICY/.test(joined)) { + return { signal: 'REQUEST_GRANT', severity: 'WARNING', boundary: 'AUTHORITY' } + } + if (/STATE|STALE/.test(joined)) { + return { signal: 'NEEDS_REVIEW', severity: 'WARNING', boundary: 'STATE' } + } + return { signal: 'NEEDS_REVIEW', severity: 'WARNING', boundary: 'NONE' } +} + +function deriveNodes( + source: HolonngramSourceV1, + comparison: HolonngramStateComparisonV1, +): readonly HolonngramVisualNodeV1[] { + const terminalState = nodeStateForOutcome(source.terminal_outcome) + const stateState: HolonngramNodeStateV1 = + comparison.before_state_root === comparison.after_state_root ? 'UNCHANGED' : 'CHANGED' + const refs = (...values: readonly SHA256Hex[]): readonly SHA256Hex[] => + deepFreeze([...new Set(values.map( + value => value === ZERO_HASH ? source.terminal_receipt_id : value, + ))].sort(compareUtf8)) + const refsByNode: Readonly> = { + C0: refs(source.terminal_receipt_id), + I1: refs(source.action_digest), + I2: refs(source.decision_digest), + I3: refs(source.lease_id, source.lease_authorization_receipt_hash), + I4: refs(source.result_digest), + I5: refs(source.chain_digest), + I6: refs(source.after_state_root), + O1: refs(source.actor_identity_root), + O2: refs(source.session_identity_root), + O3: refs(source.workspace_identity_root), + O4: refs(source.holon_identity_root), + O5: refs(source.authority_receipt_hash), + O6: refs(source.lease_id), + O7: refs(source.fencing_token), + O8: refs(source.expected_state_root), + O9: refs(source.observed_state_root), + O10: refs(source.action_digest), + O11: refs(source.result_digest), + O12: refs(source.chain_digest, ...source.registry_roots), + } + return deepFreeze(HOLONNGRAM_NODE_DEFINITIONS_V1.map(definition => { + let state: HolonngramNodeStateV1 = 'VERIFIED' + if (definition.node_id === 'C0') state = terminalState + if (definition.node_id === 'I4' || definition.node_id === 'I6' || + definition.node_id === 'O8' || definition.node_id === 'O9') { + state = stateState + } + if (definition.node_id === 'I3' && + (terminalState === 'EXPIRED' || terminalState === 'REVOKED')) { + state = terminalState + } + return { + ...definition, + state, + source_refs: sortUnique(refsByNode[definition.node_id]), + } + })) +} + +function nodeStateForOutcome(outcome: CrossRuntimeReceiptOutcomeV1): HolonngramNodeStateV1 { + switch (outcome) { + case 'COMPLETED': return 'ACTIVE' + case 'DENIED': return 'DENIED' + case 'CANCELLED': return 'CANCELLED' + case 'FAILED': return 'FAILED' + case 'EXPIRED': return 'EXPIRED' + case 'REVOKED': return 'REVOKED' + case 'ADMITTED': fail('non-terminal admitted outcome cannot produce a Holonñgram frame') + } +} + +function normalizeSource(value: unknown): HolonngramSourceV1 { + const source = asObject('frame.source', value) + assertExactKeys('frame.source', source, [ + 'action_digest', + 'actor_identity_root', + 'after_state_root', + 'authority_domain', + 'authority_level', + 'authority_receipt_hash', + 'before_state_root', + 'chain_digest', + 'decision_digest', + 'denial_codes', + 'expected_state_root', + 'fencing_token', + 'holon_identity_root', + 'lease_authorization_receipt_hash', + 'lease_generation', + 'lease_id', + 'max_clock_skew_ms', + 'observed_at_ms', + 'observed_state_root', + 'parent_receipt_hash', + 'provenance_status', + 'receipt_count', + 'registry_roots', + 'result_digest', + 'session_identity_root', + 'terminal_nonce', + 'terminal_outcome', + 'terminal_receipt_id', + 'terminal_receipt_kind', + 'terminal_timestamp_ms', + 'verifier_identity_root', + 'workspace_identity_root', + ]) + if (source.provenance_status !== 'AUTHORITATIVE_RECEIPT_PROVENANCE_VERIFIED') { + fail('frame source provenance status is invalid') + } + const registryRoots = asArray('frame.source.registry_roots', source.registry_roots) + .map((root, index) => assertNonZeroHash(`frame.source.registry_roots[${index}]`, root)) + assertSortedUnique('frame.source.registry_roots', registryRoots) + const denialCodes = asArray('frame.source.denial_codes', source.denial_codes) + .map((code, index) => assertSafeId(`frame.source.denial_codes[${index}]`, code)) + assertSortedUnique('frame.source.denial_codes', denialCodes) + const terminalReceiptKind = assertReceiptKind(source.terminal_receipt_kind) + const terminalOutcome = assertReceiptOutcome(source.terminal_outcome) + assertTerminalKindOutcome(terminalReceiptKind, terminalOutcome) + return deepFreeze({ + provenance_status: 'AUTHORITATIVE_RECEIPT_PROVENANCE_VERIFIED', + decision_digest: assertNonZeroHash('frame.source.decision_digest', source.decision_digest), + terminal_receipt_id: assertNonZeroHash( + 'frame.source.terminal_receipt_id', source.terminal_receipt_id, + ), + terminal_receipt_kind: terminalReceiptKind, + terminal_outcome: terminalOutcome, + chain_digest: assertNonZeroHash('frame.source.chain_digest', source.chain_digest), + receipt_count: assertPositiveDecimal('frame.source.receipt_count', source.receipt_count), + registry_roots: registryRoots, + actor_identity_root: assertNonZeroHash( + 'frame.source.actor_identity_root', source.actor_identity_root, + ), + session_identity_root: assertNonZeroHash( + 'frame.source.session_identity_root', source.session_identity_root, + ), + workspace_identity_root: assertNonZeroHash( + 'frame.source.workspace_identity_root', source.workspace_identity_root, + ), + holon_identity_root: assertNonZeroHash( + 'frame.source.holon_identity_root', source.holon_identity_root, + ), + authority_domain: assertSafeId('frame.source.authority_domain', source.authority_domain), + authority_level: assertAuthorityLevel(source.authority_level), + authority_receipt_hash: assertHash( + 'frame.source.authority_receipt_hash', source.authority_receipt_hash, + ), + lease_id: assertNonZeroHash('frame.source.lease_id', source.lease_id), + lease_generation: assertDecimal('frame.source.lease_generation', source.lease_generation), + fencing_token: assertHash('frame.source.fencing_token', source.fencing_token), + lease_authorization_receipt_hash: assertHash( + 'frame.source.lease_authorization_receipt_hash', + source.lease_authorization_receipt_hash, + ), + parent_receipt_hash: assertHash( + 'frame.source.parent_receipt_hash', source.parent_receipt_hash, + ), + observed_state_root: assertNonZeroHash( + 'frame.source.observed_state_root', source.observed_state_root, + ), + expected_state_root: assertNonZeroHash( + 'frame.source.expected_state_root', source.expected_state_root, + ), + action_digest: assertNonZeroHash('frame.source.action_digest', source.action_digest), + before_state_root: assertNonZeroHash( + 'frame.source.before_state_root', source.before_state_root, + ), + after_state_root: assertNonZeroHash( + 'frame.source.after_state_root', source.after_state_root, + ), + result_digest: assertNonZeroHash('frame.source.result_digest', source.result_digest), + terminal_timestamp_ms: assertDecimal( + 'frame.source.terminal_timestamp_ms', source.terminal_timestamp_ms, + ), + terminal_nonce: assertSafeId('frame.source.terminal_nonce', source.terminal_nonce), + denial_codes: denialCodes, + verifier_identity_root: assertNonZeroHash( + 'frame.source.verifier_identity_root', source.verifier_identity_root, + ), + observed_at_ms: assertDecimal('frame.source.observed_at_ms', source.observed_at_ms), + max_clock_skew_ms: assertDecimal( + 'frame.source.max_clock_skew_ms', source.max_clock_skew_ms, + ), + }) +} + +function normalizeFormulaTrace(value: unknown): HolonngramFormulaTraceV1 { + const trace = asObject('frame.formula_trace', value) + assertExactKeys('frame.formula_trace', trace, [ + 'execution_status', + 'formula_definition_digest', + 'formula_id', + 'formula_version', + 'input_digest', + 'measurement_status', + 'output_digest', + 'trace_id', + 'transition_id', + ]) + const executionStatus = assertEnum( + 'frame.formula_trace.execution_status', + trace.execution_status, + new Set([ + 'NOT_EXECUTED', 'UNVERIFIED_CALLER_INPUT', + ]), + ) + const measurementStatus = assertMeasurementStatus( + 'frame.formula_trace.measurement_status', trace.measurement_status, + ) + const expectedExecutionStatus: HolonngramFormulaExecutionStatusV1 = + measurementStatus === 'NOT_COMPUTED' + ? 'NOT_EXECUTED' + : 'UNVERIFIED_CALLER_INPUT' + if (executionStatus !== expectedExecutionStatus) { + fail('formula projection status overstates execution or measurement provenance') + } + return deepFreeze({ + formula_id: assertSafeId('frame.formula_trace.formula_id', trace.formula_id), + formula_version: assertSafeId('frame.formula_trace.formula_version', trace.formula_version), + formula_definition_digest: assertNonZeroHash( + 'frame.formula_trace.formula_definition_digest', + trace.formula_definition_digest, + ), + transition_id: assertSafeId( + 'frame.formula_trace.transition_id', trace.transition_id, + ), + trace_id: assertNonZeroHash('frame.formula_trace.trace_id', trace.trace_id), + input_digest: assertNonZeroHash('frame.formula_trace.input_digest', trace.input_digest), + output_digest: assertNonZeroHash('frame.formula_trace.output_digest', trace.output_digest), + execution_status: executionStatus, + measurement_status: measurementStatus, + }) +} + +function normalizeStateComparison(value: unknown): HolonngramStateComparisonV1 { + const comparison = asObject('frame.state_comparison', value) + assertExactKeys('frame.state_comparison', comparison, [ + 'after_state_root', + 'before_state_root', + 'delta_type', + 'expected_state_root', + 'field_diff_status', + 'observed_state_root', + ]) + if (comparison.field_diff_status !== 'ROOTS_ONLY') { + fail('field-level differences are not proven by state roots') + } + const deltaTypes = new Set([ + 'MATCH', 'STATE_CHANGED', 'STALE_EXPECTATION', 'DENIED', + 'CANCELLED', 'FAILED', 'EXPIRED', 'REVOKED', + ]) + return deepFreeze({ + observed_state_root: assertNonZeroHash( + 'frame.state_comparison.observed_state_root', comparison.observed_state_root, + ), + expected_state_root: assertNonZeroHash( + 'frame.state_comparison.expected_state_root', comparison.expected_state_root, + ), + before_state_root: assertNonZeroHash( + 'frame.state_comparison.before_state_root', comparison.before_state_root, + ), + after_state_root: assertNonZeroHash( + 'frame.state_comparison.after_state_root', comparison.after_state_root, + ), + field_diff_status: 'ROOTS_ONLY', + delta_type: assertEnum( + 'frame.state_comparison.delta_type', comparison.delta_type, deltaTypes, + ), + }) +} + +function normalizeFeedback(value: unknown): HolonngramFeedbackV1 { + const feedback = asObject('frame.feedback', value) + assertExactKeys('frame.feedback', feedback, [ + 'boundary', 'rationale_codes', 'resonance', 'severity', 'signal', 'value', + ]) + const rationaleCodes = asArray('frame.feedback.rationale_codes', feedback.rationale_codes) + .map((code, index) => assertSafeId(`frame.feedback.rationale_codes[${index}]`, code)) + assertSortedUnique('frame.feedback.rationale_codes', rationaleCodes) + const resonance = normalizeMeasuredValue( + feedback.resonance, 'frame.feedback.resonance', 'ppm', + ) + const measuredValue = normalizeMeasuredValue( + feedback.value, 'frame.feedback.value', 'delta_ppm', + ) + const signals = new Set([ + 'REINFORCE', 'NEEDS_REVIEW', 'REQUEST_GRANT', + 'REPAIR_SCHEMA', 'ROLLBACK', 'FAIL_CLOSED', + ]) + const severities = new Set([ + 'INFO', 'REVIEW', 'WARNING', 'CRITICAL', 'FATAL', + ]) + const boundaries = new Set([ + 'NONE', 'AUTHORITY', 'LEASE', 'FENCING', 'STATE', + 'SCHEMA', 'TRUST', 'REPLAY', 'CANCELLATION', 'EXECUTION', + ]) + return deepFreeze({ + signal: assertEnum('frame.feedback.signal', feedback.signal, signals), + severity: assertEnum('frame.feedback.severity', feedback.severity, severities), + boundary: assertEnum('frame.feedback.boundary', feedback.boundary, boundaries), + rationale_codes: rationaleCodes, + resonance: { + measurement_status: resonance.status, + ppm: resonance.value, + }, + value: { + measurement_status: measuredValue.status, + delta_ppm: measuredValue.value, + }, + }) +} + +function normalizeVisual( + value: unknown, + source: HolonngramSourceV1, + feedback: HolonngramFeedbackV1, +): HolonngramVisualV1 { + const visual = asObject('frame.visual', value) + assertExactKeys('frame.visual', visual, [ + 'edge_updates', 'event', 'next_route', 'nodes', 'receipt_timeline', + ]) + const nodes = asArray('frame.visual.nodes', visual.nodes) + .map((node, index) => normalizeNode(node, index)) + assertExactTopology(nodes) + const edges = asArray('frame.visual.edge_updates', visual.edge_updates) + .map((edge, index) => normalizeEdgeUpdate(edge, `frame.visual.edge_updates[${index}]`)) + assertEdgesSortedUnique(edges) + const event = asObject('frame.visual.event', visual.event) + assertExactKeys('frame.visual.event', event, [ + 'severity', 'signal', 'terminal_outcome', + 'terminal_receipt_id', 'terminal_receipt_kind', 'transition_id', + ]) + const timeline = asObject('frame.visual.receipt_timeline', visual.receipt_timeline) + assertExactKeys('frame.visual.receipt_timeline', timeline, [ + 'chain_digest', 'receipt_count', 'terminal_receipt_id', + ]) + const eventTransition = assertSafeId( + 'frame.visual.event.transition_id', event.transition_id, + ) + if (event.terminal_receipt_id !== source.terminal_receipt_id || + event.terminal_receipt_kind !== source.terminal_receipt_kind || + event.terminal_outcome !== source.terminal_outcome || + event.severity !== feedback.severity || + event.signal !== feedback.signal || + timeline.terminal_receipt_id !== source.terminal_receipt_id || + timeline.chain_digest !== source.chain_digest || + timeline.receipt_count !== source.receipt_count) { + fail('visual event or timeline is not bound to verified source evidence') + } + return deepFreeze({ + nodes, + edge_updates: edges, + event: { + transition_id: eventTransition, + terminal_receipt_id: source.terminal_receipt_id, + terminal_receipt_kind: source.terminal_receipt_kind, + terminal_outcome: source.terminal_outcome, + severity: feedback.severity, + signal: feedback.signal, + }, + receipt_timeline: { + terminal_receipt_id: source.terminal_receipt_id, + chain_digest: source.chain_digest, + receipt_count: source.receipt_count, + }, + next_route: assertSafeId('frame.visual.next_route', visual.next_route), + }) +} + +function normalizeNode(value: unknown, index: number): HolonngramVisualNodeV1 { + const node = asObject(`frame.visual.nodes[${index}]`, value) + assertExactKeys(`frame.visual.nodes[${index}]`, node, [ + 'node_id', 'ring', 'role', 'source_refs', 'state', + ]) + const sourceRefs = asArray( + `frame.visual.nodes[${index}].source_refs`, node.source_refs, + ).map((root, rootIndex) => assertNonZeroHash( + `frame.visual.nodes[${index}].source_refs[${rootIndex}]`, root, + )) + assertSortedUnique(`frame.visual.nodes[${index}].source_refs`, sourceRefs) + const nodeId = assertNodeId(`frame.visual.nodes[${index}].node_id`, node.node_id) + const definition = HOLONNGRAM_NODE_DEFINITIONS_V1[index] + if (definition === undefined || + nodeId !== definition.node_id || + node.ring !== definition.ring || + node.role !== definition.role) { + fail('Holonñgram nodes must preserve the fixed 19-node order, rings, and roles') + } + return deepFreeze({ + node_id: nodeId, + ring: definition.ring, + role: definition.role, + state: assertEnum(`frame.visual.nodes[${index}].state`, node.state, NODE_STATES), + source_refs: sourceRefs, + }) +} + +function normalizeEdgeUpdate(value: unknown, field: string): HolonngramEdgeUpdateV1 { + const edge = asObject(field, value) + assertExactKeys(field, edge, [ + 'authority_delta_ppm', + 'basis_codes', + 'edge_kind', + 'from_node', + 'measurement_status', + 'risk_delta_ppm', + 'schema_delta_ppm', + 'to_node', + 'trust_delta_ppm', + ]) + const status = assertMeasurementStatus(`${field}.measurement_status`, edge.measurement_status) + const basisCodes = asArray(`${field}.basis_codes`, edge.basis_codes) + .map((code, index) => assertSafeId(`${field}.basis_codes[${index}]`, code)) + assertSortedUnique(`${field}.basis_codes`, basisCodes) + const normalizeDelta = (name: string, delta: unknown): DecimalStringV1 | null => + status === 'NOT_COMPUTED' + ? assertNull(`${field}.${name}`, delta) + : assertSignedPpm(`${field}.${name}`, delta) + if (status === 'NOT_COMPUTED' && basisCodes.length !== 0) { + fail(`${field} cannot claim basis codes when measurements were not computed`) + } + return deepFreeze({ + from_node: assertNodeId(`${field}.from_node`, edge.from_node), + to_node: assertNodeId(`${field}.to_node`, edge.to_node), + edge_kind: assertEnum(`${field}.edge_kind`, edge.edge_kind, EDGE_KINDS), + measurement_status: status, + trust_delta_ppm: normalizeDelta('trust_delta_ppm', edge.trust_delta_ppm), + risk_delta_ppm: normalizeDelta('risk_delta_ppm', edge.risk_delta_ppm), + schema_delta_ppm: normalizeDelta('schema_delta_ppm', edge.schema_delta_ppm), + authority_delta_ppm: normalizeDelta( + 'authority_delta_ppm', edge.authority_delta_ppm, + ), + basis_codes: basisCodes, + }) +} + +function normalizeMeasurement( + value: unknown, + field: string, +): HolonngramCompilerInputV1['measurement'] { + const measurement = asObject(field, value) + assertExactKeys(field, measurement, ['resonance_ppm', 'status', 'value_delta_ppm']) + const status = assertMeasurementStatus(`${field}.status`, measurement.status) + if (status === 'NOT_COMPUTED') { + return deepFreeze({ + status, + resonance_ppm: assertNull(`${field}.resonance_ppm`, measurement.resonance_ppm), + value_delta_ppm: assertNull( + `${field}.value_delta_ppm`, measurement.value_delta_ppm, + ), + }) + } + return deepFreeze({ + status, + resonance_ppm: assertUnsignedPpm( + `${field}.resonance_ppm`, measurement.resonance_ppm, + ), + value_delta_ppm: assertSignedPpm( + `${field}.value_delta_ppm`, measurement.value_delta_ppm, + ), + }) +} + +function normalizeMeasuredValue( + value: unknown, + field: string, + valueKey: 'ppm' | 'delta_ppm', +): { readonly status: HolonngramMeasurementStatusV1; readonly value: string | null } { + const measured = asObject(field, value) + assertExactKeys(field, measured, ['measurement_status', valueKey]) + const status = assertMeasurementStatus(`${field}.measurement_status`, measured.measurement_status) + const raw = measured[valueKey] + return deepFreeze({ + status, + value: status === 'NOT_COMPUTED' + ? assertNull(`${field}.${valueKey}`, raw) + : valueKey === 'ppm' + ? assertUnsignedPpm(`${field}.${valueKey}`, raw) + : assertSignedPpm(`${field}.${valueKey}`, raw), + }) +} + +function assertStateSemantics( + source: HolonngramSourceV1, + comparison: HolonngramStateComparisonV1, +): void { + if (comparison.observed_state_root !== source.observed_state_root || + comparison.expected_state_root !== source.expected_state_root || + comparison.before_state_root !== source.before_state_root || + comparison.after_state_root !== source.after_state_root) { + fail('state comparison does not mirror verified source roots') + } + const stateChanged = source.before_state_root !== source.after_state_root + if (source.terminal_receipt_kind !== 'MUTATION_COMPLETED' && stateChanged) { + fail('non-completed terminal evidence must leave canonical state unchanged') + } + if (comparison.delta_type === 'STATE_CHANGED' && + (source.terminal_receipt_kind !== 'MUTATION_COMPLETED' || !stateChanged)) { + fail('STATE_CHANGED is only valid for a state-changing mutation completion') + } + const expectedDelta = deltaTypeForSource(source) + if (comparison.delta_type !== expectedDelta) { + fail('state comparison delta type does not follow the verified terminal receipt') + } +} + +function deltaTypeForSource( + source: Pick< + HolonngramSourceV1, + | 'terminal_receipt_kind' + | 'expected_state_root' + | 'observed_state_root' + | 'before_state_root' + | 'after_state_root' + >, +): HolonngramDeltaTypeV1 { + switch (source.terminal_receipt_kind) { + case 'MUTATION_COMPLETED': + if (source.expected_state_root !== source.observed_state_root) return 'STALE_EXPECTATION' + return source.before_state_root === source.after_state_root ? 'MATCH' : 'STATE_CHANGED' + case 'MUTATION_CANCELLED': return 'CANCELLED' + case 'MUTATION_FAILED': return 'FAILED' + case 'LEASE_EXPIRED': return 'EXPIRED' + case 'LEASE_REVOKED': return 'REVOKED' + case 'LEASE_ISSUANCE_DENIED': + case 'LEASE_RENEWAL_DENIED': + case 'MUTATION_DENIED': + return 'DENIED' + case 'LEASE_ISSUED': + case 'LEASE_RENEWED': + case 'MUTATION_ADMITTED': + fail('non-terminal source cannot produce a Holonñgram state comparison') + } +} + +function assertExactTopology(nodes: readonly HolonngramVisualNodeV1[]): void { + if (nodes.length !== HOLONNGRAM_NODE_DEFINITIONS_V1.length) { + fail('Holonñgram topology must contain exactly 19 nodes') + } + for (let index = 0; index < HOLONNGRAM_NODE_DEFINITIONS_V1.length; index += 1) { + const node = nodes[index] + const definition = HOLONNGRAM_NODE_DEFINITIONS_V1[index] + if (node === undefined || definition === undefined || + node.node_id !== definition.node_id || + node.ring !== definition.ring || + node.role !== definition.role) { + fail('Holonñgram topology does not match HOLONNGRAM_19_V1') + } + } +} + +function assertEdgesSortedUnique(edges: readonly HolonngramEdgeUpdateV1[]): void { + const keys = edges.map(edge => `${edge.from_node}\u0000${edge.to_node}\u0000${edge.edge_kind}`) + assertSortedUnique('edge update tuples', keys) +} + +function snapshotIJson(value: unknown, label: string): unknown { + try { + assertIJsonValue(value, label) + const snapshot = structuredClone(value) as unknown + assertIJsonValue(snapshot, label) + return snapshot + } catch (error) { + if (error instanceof HolonngramCompilerError) throw error + fail(`${label} is not a closed I-JSON value: ${ + error instanceof Error ? error.message : String(error) + }`) + } +} + +function asObject(field: string, value: unknown): Record { + if (value === null || typeof value !== 'object' || Array.isArray(value)) { + fail(`${field} must be an object`) + } + return value as Record +} + +function asArray(field: string, value: unknown): readonly unknown[] { + if (!Array.isArray(value)) fail(`${field} must be an array`) + return value +} + +function assertExactKeys( + field: string, + value: Record, + expectedKeys: readonly string[], +): void { + const actual = Object.keys(value).sort(compareUtf8) + const expected = [...expectedKeys].sort(compareUtf8) + if (actual.length !== expected.length || + actual.some((key, index) => key !== expected[index])) { + fail(`${field} has unexpected or missing fields`) + } +} + +function assertHash(field: string, value: unknown): SHA256Hex { + if (typeof value !== 'string' || !HASH_PATTERN.test(value)) { + fail(`${field} must be lowercase SHA-256 hex`) + } + return value as SHA256Hex +} + +function assertNonZeroHash(field: string, value: unknown): SHA256Hex { + const hash = assertHash(field, value) + if (hash === ZERO_HASH) fail(`${field} must be non-zero`) + return hash +} + +function assertSafeId(field: string, value: unknown): string { + if (typeof value !== 'string' || !SAFE_ID_PATTERN.test(value)) { + fail(`${field} is not a canonical safe identifier`) + } + return value +} + +function assertDecimal(field: string, value: unknown): DecimalStringV1 { + if (typeof value !== 'string' || value.length > MAX_DECIMAL_DIGITS || + !DECIMAL_PATTERN.test(value)) { + fail(`${field} must be a canonical unsigned decimal string`) + } + return value +} + +function assertPositiveDecimal(field: string, value: unknown): DecimalStringV1 { + const decimal = assertDecimal(field, value) + if (BigInt(decimal) < 1n) fail(`${field} must be positive`) + return decimal +} + +function assertUnsignedPpm(field: string, value: unknown): DecimalStringV1 { + const decimal = assertDecimal(field, value) + if (BigInt(decimal) > PPM_LIMIT) fail(`${field} exceeds 1000000 ppm`) + return decimal +} + +function assertSignedPpm(field: string, value: unknown): DecimalStringV1 { + if (typeof value !== 'string' || value.length > MAX_DECIMAL_DIGITS + 1 || + !SIGNED_DECIMAL_PATTERN.test(value) || value === '-0') { + fail(`${field} must be a canonical signed decimal string`) + } + const amount = BigInt(value) + if (amount < -PPM_LIMIT || amount > PPM_LIMIT) { + fail(`${field} exceeds the signed 1000000 ppm bound`) + } + return value +} + +function assertNull(field: string, value: unknown): null { + if (value !== null) fail(`${field} must be null`) + return null +} + +function assertEnum( + field: string, + value: unknown, + allowed: ReadonlySet, +): T { + if (typeof value !== 'string' || !allowed.has(value as T)) { + fail(`${field} is invalid`) + } + return value as T +} + +function assertMeasurementStatus( + field: string, + value: unknown, +): HolonngramMeasurementStatusV1 { + return assertEnum(field, value, MEASUREMENT_STATUSES) +} + +function assertNodeId(field: string, value: unknown): HolonngramNodeIdV1 { + if (typeof value !== 'string' || !NODE_IDS.has(value)) { + fail(`${field} is not a HOLONNGRAM_19_V1 node`) + } + return value as HolonngramNodeIdV1 +} + +function assertAuthorityLevel(value: unknown): AuthorityLevelV1 { + const levels = new Set(['D0', 'D1', 'D2', 'D3', 'D4']) + return assertEnum('frame.source.authority_level', value, levels) +} + +function assertReceiptKind(value: unknown): CrossRuntimeReceiptKindV1 { + const kinds = new Set([ + 'LEASE_ISSUED', 'LEASE_ISSUANCE_DENIED', 'LEASE_RENEWED', + 'LEASE_RENEWAL_DENIED', 'LEASE_EXPIRED', 'LEASE_REVOKED', + 'MUTATION_ADMITTED', 'MUTATION_DENIED', 'MUTATION_COMPLETED', + 'MUTATION_CANCELLED', 'MUTATION_FAILED', + ]) + return assertEnum('frame.source.terminal_receipt_kind', value, kinds) +} + +function assertReceiptOutcome(value: unknown): CrossRuntimeReceiptOutcomeV1 { + const outcomes = new Set([ + 'ADMITTED', 'DENIED', 'COMPLETED', 'CANCELLED', + 'FAILED', 'EXPIRED', 'REVOKED', + ]) + return assertEnum('frame.source.terminal_outcome', value, outcomes) +} + +function assertTerminalKindOutcome( + kind: CrossRuntimeReceiptKindV1, + outcome: CrossRuntimeReceiptOutcomeV1, +): void { + const expected: Readonly> = { + LEASE_ISSUED: 'ADMITTED', + LEASE_ISSUANCE_DENIED: 'DENIED', + LEASE_RENEWED: 'ADMITTED', + LEASE_RENEWAL_DENIED: 'DENIED', + LEASE_EXPIRED: 'EXPIRED', + LEASE_REVOKED: 'REVOKED', + MUTATION_ADMITTED: 'ADMITTED', + MUTATION_DENIED: 'DENIED', + MUTATION_COMPLETED: 'COMPLETED', + MUTATION_CANCELLED: 'CANCELLED', + MUTATION_FAILED: 'FAILED', + } + if (outcome !== expected[kind]) fail('terminal receipt kind/outcome mismatch') +} + +function sortUnique(values: readonly SHA256Hex[]): readonly SHA256Hex[] { + return [...new Set(values)].sort(compareUtf8) +} + +function assertSortedUnique(field: string, values: readonly string[]): void { + for (let index = 1; index < values.length; index += 1) { + if (compareUtf8(values[index - 1]!, values[index]!) >= 0) { + fail(`${field} must be unique and strictly sorted by UTF-8 bytes`) + } + } +} + +function compareUtf8(left: string, right: string): number { + const leftBytes = new TextEncoder().encode(left) + const rightBytes = new TextEncoder().encode(right) + const length = Math.min(leftBytes.length, rightBytes.length) + for (let index = 0; index < length; index += 1) { + const difference = leftBytes[index]! - rightBytes[index]! + if (difference !== 0) return difference + } + return leftBytes.length - rightBytes.length +} + +function fail(message: string): never { + throw new HolonngramCompilerError(message) +} diff --git a/sovereign-omega-v2/test/unit/holonngram-compiler.test.ts b/sovereign-omega-v2/test/unit/holonngram-compiler.test.ts new file mode 100644 index 000000000..14f29f2ba --- /dev/null +++ b/sovereign-omega-v2/test/unit/holonngram-compiler.test.ts @@ -0,0 +1,538 @@ +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import Ajv2020 from 'ajv/dist/2020.js' +import { describe, expect, it } from 'vitest' +import type { SHA256Hex } from '../../src/core/types.js' +import { + HOLONNGRAM_COMPILER_VERSION, + HOLONNGRAM_VISUAL_FEEDBACK_SCHEMA_VERSION, + HolonngramCompilerError, + classifyHolonngramDenialCodesV1, + resolveAndCompileHolonngramVisualFeedbackV1, + verifyHolonngramVisualFeedbackFrameIntegrityV1, + type HolonngramCompilerInputV1, +} from '../../src/projection/holonngram-compiler.js' +import type { + CrossRuntimeReceiptSourceV1, + TrustedReceiptResolutionContextV1, +} from '../../src/provenance/receipt-resolver.js' + +const H = (digit: string): SHA256Hex => digit.repeat(64) as SHA256Hex + +interface VectorFile { + readonly context: Omit + readonly operator_public_key: string + readonly receipts: readonly Record[] + readonly registry: Record & { readonly registry_root: SHA256Hex } + readonly terminal_receipt_id: SHA256Hex +} + +class MemorySource implements CrossRuntimeReceiptSourceV1 { + readonly receipts = new Map() + readonly registries = new Map() + + resolveReceipt(receiptId: SHA256Hex): Promise { + return Promise.resolve(this.receipts.get(receiptId) ?? null) + } + + resolveTrustRegistry(registryRoot: SHA256Hex): Promise { + return Promise.resolve(this.registries.get(registryRoot) ?? null) + } +} + +function loadVector(): VectorFile { + return JSON.parse(readFileSync( + resolve(process.cwd(), 'test/vectors/python-cross-runtime-receipt-v1.json'), + 'utf8', + )) as VectorFile +} + +function sourceFor(vector: VectorFile): MemorySource { + const source = new MemorySource() + source.registries.set(vector.registry.registry_root, vector.registry) + for (const receipt of vector.receipts) { + source.receipts.set(String(receipt.receipt_id), receipt) + } + return source +} + +function compileVisualSchema() { + const schema = JSON.parse(readFileSync( + resolve(process.cwd(), '../schemas/holonngram-visual-feedback.v1.schema.json'), + 'utf8', + )) as Record + return new Ajv2020({ allErrors: true }).compile(schema) +} + +function contextFor( + vector: VectorFile, + overrides: Partial = {}, +): TrustedReceiptResolutionContextV1 { + return { + ...vector.context, + operator_public_key: vector.operator_public_key, + ...overrides, + } +} + +function contextForReceipt( + vector: VectorFile, + receipt: Record, +): TrustedReceiptResolutionContextV1 { + const body = receipt.receipt_body as Record + return contextFor(vector, { + expected_action_digest: body.action_digest as SHA256Hex, + expected_observed_state_root: body.observed_state_root as SHA256Hex, + observed_at_ms: String(body.timestamp_ms), + }) +} + +function input( + overrides: Partial = {}, +): HolonngramCompilerInputV1 { + return { + schema_version: HOLONNGRAM_VISUAL_FEEDBACK_SCHEMA_VERSION, + compiler_version: HOLONNGRAM_COMPILER_VERSION, + formula_id: 'FORMULA-001', + formula_version: 'v1', + formula_definition_digest: H('f'), + transition_id: 'STU-001', + measurement: { + status: 'NOT_COMPUTED', + resonance_ppm: null, + value_delta_ppm: null, + }, + edge_updates: [], + next_route: 'route.review', + ...overrides, + } +} + +describe('Holonñgram visual feedback compiler', () => { + it('compiles only after resolving the signed chain and terminal receipt', async () => { + const vector = loadVector() + const frame = await resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), + vector.terminal_receipt_id, + contextFor(vector), + input(), + ) + + expect(frame.source.terminal_receipt_kind).toBe('MUTATION_FAILED') + expect(frame.source.provenance_status).toBe( + 'AUTHORITATIVE_RECEIPT_PROVENANCE_VERIFIED', + ) + expect(frame.state_comparison.delta_type).toBe('FAILED') + expect(frame.feedback.signal).toBe('ROLLBACK') + expect(frame.visual.nodes).toHaveLength(19) + expect(frame.visual.receipt_timeline.receipt_count).toBe('15') + expect(frame.epistemic_status).toBe('DERIVED_NON_AUTHORITATIVE') + expect(frame.safety).toEqual({ + grants_authority: false, + executes_mutation: false, + promotes_evidence: false, + claims_authoritative_provenance: false, + route_adjustment_authorized: false, + }) + expect(frame.formula_trace.execution_status).toBe('NOT_EXECUTED') + await expect( + verifyHolonngramVisualFeedbackFrameIntegrityV1(frame), + ).resolves.toEqual(frame) + }) + + it('labels caller-supplied measurements as unverified', async () => { + const vector = loadVector() + const terminal = vector.receipts.find( + receipt => receipt.receipt_kind === 'MUTATION_COMPLETED', + ) + if (terminal === undefined) throw new Error('completion receipt missing from vector') + const terminalId = terminal.receipt_id as SHA256Hex + const frame = await resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), + terminalId, + contextFor(vector, { + expected_action_digest: H('b'), + expected_observed_state_root: H('a'), + observed_at_ms: '1700', + }), + input({ + measurement: { + status: 'CALLER_SUPPLIED_UNVERIFIED', + resonance_ppm: '875000', + value_delta_ppm: '12500', + }, + edge_updates: [{ + from_node: 'I5', + to_node: 'I6', + edge_kind: 'FEEDBACK', + measurement_status: 'CALLER_SUPPLIED_UNVERIFIED', + trust_delta_ppm: '12000', + risk_delta_ppm: '-7000', + schema_delta_ppm: '0', + authority_delta_ppm: '0', + basis_codes: ['FORMULA_TRACE'], + }], + next_route: 'route.commit', + }), + ) + + expect(frame.source.terminal_receipt_kind).toBe('MUTATION_COMPLETED') + expect(frame.state_comparison.delta_type).toBe('STATE_CHANGED') + expect(frame.feedback.signal).toBe('REINFORCE') + expect(frame.feedback.resonance.ppm).toBe('875000') + expect(frame.formula_trace.execution_status).toBe('UNVERIFIED_CALLER_INPUT') + expect(frame.visual.edge_updates).toHaveLength(1) + expect(frame.formula_trace.trace_id).toMatch(/^[0-9a-f]{64}$/) + }) + + it('proves denied actions leave the canonical state root unchanged', async () => { + const vector = loadVector() + const terminal = vector.receipts.find( + receipt => receipt.receipt_kind === 'MUTATION_DENIED', + ) + if (terminal === undefined) throw new Error('denial receipt missing from vector') + const frame = await resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), + terminal.receipt_id as SHA256Hex, + contextFor(vector, { + expected_action_digest: H('d'), + expected_observed_state_root: H('a'), + observed_at_ms: '1500', + }), + input(), + ) + + expect(frame.state_comparison.delta_type).toBe('DENIED') + expect(frame.state_comparison.before_state_root).toBe(H('a')) + expect(frame.state_comparison.after_state_root).toBe(H('a')) + expect(frame.feedback.signal).toBe('REQUEST_GRANT') + expect(frame.feedback.boundary).toBe('AUTHORITY') + }) + + it.each([ + 'LEASE_ISSUANCE_DENIED', + 'LEASE_RENEWAL_DENIED', + 'LEASE_EXPIRED', + 'LEASE_REVOKED', + ] as const)('compiles terminal lease evidence without zero-sentinel visual refs: %s', + async receiptKind => { + const vector = loadVector() + const terminal = vector.receipts.find( + receipt => receipt.receipt_kind === receiptKind, + ) + if (terminal === undefined) throw new Error(`${receiptKind} missing from vector`) + const frame = await resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), + terminal.receipt_id as SHA256Hex, + contextForReceipt(vector, terminal), + input(), + ) + + expect(frame.source.terminal_receipt_kind).toBe(receiptKind) + expect(frame.state_comparison.before_state_root).toBe( + frame.state_comparison.after_state_root, + ) + expect(frame.visual.nodes).toHaveLength(19) + expect(frame.visual.nodes.flatMap(node => node.source_refs)).not.toContain(H('0')) + await expect( + verifyHolonngramVisualFeedbackFrameIntegrityV1(frame), + ).resolves.toEqual(frame) + }) + + it('is deterministic across caller property insertion order', async () => { + const vector = loadVector() + const canonical = input() + const reordered = { + next_route: canonical.next_route, + edge_updates: canonical.edge_updates, + measurement: canonical.measurement, + transition_id: canonical.transition_id, + formula_definition_digest: canonical.formula_definition_digest, + formula_version: canonical.formula_version, + formula_id: canonical.formula_id, + compiler_version: canonical.compiler_version, + schema_version: canonical.schema_version, + } + const first = await resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), vector.terminal_receipt_id, contextFor(vector), canonical, + ) + const second = await resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), vector.terminal_receipt_id, contextFor(vector), reordered, + ) + expect(second.frame_digest).toBe(first.frame_digest) + expect(second.formula_trace.trace_id).toBe(first.formula_trace.trace_id) + }) + + it('survives restart-style structured read-back integrity validation', async () => { + const vector = loadVector() + const frame = await resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), vector.terminal_receipt_id, contextFor(vector), input(), + ) + const readBack = structuredClone(frame) + const verified = await verifyHolonngramVisualFeedbackFrameIntegrityV1(readBack) + expect(verified).toEqual(frame) + expect(Object.isFrozen(verified)).toBe(true) + expect(Object.isFrozen(verified.visual.nodes)).toBe(true) + }) + + it('rejects an overstated formula execution claim during integrity read-back', async () => { + const vector = loadVector() + const frame = await resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), vector.terminal_receipt_id, contextFor(vector), input(), + ) + await expect(verifyHolonngramVisualFeedbackFrameIntegrityV1({ + ...frame, + formula_trace: { + ...frame.formula_trace, + execution_status: 'UNVERIFIED_CALLER_INPUT', + }, + })).rejects.toThrow(/overstates execution or measurement provenance/) + }) + + it('rejects a missing trust root without producing a frame', async () => { + const vector = loadVector() + await expect(resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), + vector.terminal_receipt_id, + contextFor(vector, { accepted_registry_roots: [H('9')] }), + input(), + )).rejects.toThrow(/explicitly accepted/) + }) + + it('rejects a terminal receipt that is tampered after persistence', async () => { + const vector = loadVector() + const source = sourceFor(vector) + const terminal = structuredClone( + source.receipts.get(vector.terminal_receipt_id), + ) as Record + const body = terminal.receipt_body as Record + body.after_state_root = H('1') + source.receipts.set(vector.terminal_receipt_id, terminal) + + await expect(resolveAndCompileHolonngramVisualFeedbackV1( + source, vector.terminal_receipt_id, contextFor(vector), input(), + )).rejects.toThrow() + }) + + it('rejects malformed and non-I-JSON compiler input', async () => { + const vector = loadVector() + const malformed = { + ...input(), + measurement: { + status: 'CALLER_SUPPLIED_UNVERIFIED', + resonance_ppm: -0, + value_delta_ppm: '0', + }, + } + await expect(resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), vector.terminal_receipt_id, contextFor(vector), malformed, + )).rejects.toThrow(HolonngramCompilerError) + }) + + it('rejects unexpected input fields', async () => { + const vector = loadVector() + await expect(resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), + vector.terminal_receipt_id, + contextFor(vector), + { ...input(), hidden_authority: true }, + )).rejects.toThrow(/unexpected or missing fields/) + }) + + it('rejects measurements when the formula trace says not computed', async () => { + const vector = loadVector() + await expect(resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), + vector.terminal_receipt_id, + contextFor(vector), + input({ + measurement: { + status: 'NOT_COMPUTED', + resonance_ppm: '0', + value_delta_ppm: null, + }, + }), + )).rejects.toThrow(/must be null/) + }) + + it('prioritizes fail-closed evidence across mixed denial codes', () => { + expect(classifyHolonngramDenialCodesV1([ + 'AUTHORITY_SCOPE_MISSING', + 'UNSIGNED_RECEIPT', + ])).toEqual({ + signal: 'FAIL_CLOSED', + severity: 'CRITICAL', + boundary: 'TRUST', + }) + }) + + it('rejects out-of-bound fixed-point measurements', async () => { + const vector = loadVector() + await expect(resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), + vector.terminal_receipt_id, + contextFor(vector), + input({ + measurement: { + status: 'CALLER_SUPPLIED_UNVERIFIED', + resonance_ppm: '1000001', + value_delta_ppm: '0', + }, + }), + )).rejects.toThrow(/exceeds 1000000 ppm/) + }) + + it('rejects unsorted or duplicate edge tuples', async () => { + const vector = loadVector() + const edge = { + from_node: 'I5' as const, + to_node: 'I6' as const, + edge_kind: 'FEEDBACK' as const, + measurement_status: 'CALLER_SUPPLIED_UNVERIFIED' as const, + trust_delta_ppm: '0', + risk_delta_ppm: '0', + schema_delta_ppm: '0', + authority_delta_ppm: '0', + basis_codes: ['TRACE'], + } + await expect(resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), + vector.terminal_receipt_id, + contextFor(vector), + input({ + measurement: { + status: 'CALLER_SUPPLIED_UNVERIFIED', + resonance_ppm: '0', + value_delta_ppm: '0', + }, + edge_updates: [edge, edge], + }), + )).rejects.toThrow(/unique and strictly sorted/) + }) + + it('rejects edge updates in a not-computed frame during integrity read-back', async () => { + const vector = loadVector() + const frame = await resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), vector.terminal_receipt_id, contextFor(vector), input(), + ) + await expect(verifyHolonngramVisualFeedbackFrameIntegrityV1({ + ...frame, + visual: { + ...frame.visual, + edge_updates: [{ + from_node: 'I5', + to_node: 'I6', + edge_kind: 'FEEDBACK', + measurement_status: 'NOT_COMPUTED', + trust_delta_ppm: null, + risk_delta_ppm: null, + schema_delta_ppm: null, + authority_delta_ppm: null, + basis_codes: [], + }], + }, + })).rejects.toThrow(/NOT_COMPUTED frames cannot contain edge updates/) + }) + + it('schema rejects formula and feedback measurement-status mismatches', async () => { + const vector = loadVector() + const frame = await resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), vector.terminal_receipt_id, contextFor(vector), input(), + ) + const validate = compileVisualSchema() + expect(validate(frame), JSON.stringify(validate.errors)).toBe(true) + expect(validate({ + ...frame, + feedback: { + ...frame.feedback, + resonance: { + measurement_status: 'CALLER_SUPPLIED_UNVERIFIED', + ppm: '0', + }, + value: { + measurement_status: 'CALLER_SUPPLIED_UNVERIFIED', + delta_ppm: '0', + }, + }, + })).toBe(false) + }) + + it('schema rejects formula and edge measurement-status mismatches', async () => { + const vector = loadVector() + const frame = await resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), + vector.terminal_receipt_id, + contextFor(vector), + input({ + measurement: { + status: 'CALLER_SUPPLIED_UNVERIFIED', + resonance_ppm: '0', + value_delta_ppm: '0', + }, + edge_updates: [{ + from_node: 'I5', + to_node: 'I6', + edge_kind: 'FEEDBACK', + measurement_status: 'CALLER_SUPPLIED_UNVERIFIED', + trust_delta_ppm: '0', + risk_delta_ppm: '0', + schema_delta_ppm: '0', + authority_delta_ppm: '0', + basis_codes: ['TRACE'], + }], + }), + ) + const validate = compileVisualSchema() + expect(validate(frame), JSON.stringify(validate.errors)).toBe(true) + expect(validate({ + ...frame, + visual: { + ...frame.visual, + edge_updates: [{ + ...frame.visual.edge_updates[0], + measurement_status: 'NOT_COMPUTED', + trust_delta_ppm: null, + risk_delta_ppm: null, + schema_delta_ppm: null, + authority_delta_ppm: null, + basis_codes: [], + }], + }, + })).toBe(false) + }) + + it('rejects frame digest tampering', async () => { + const vector = loadVector() + const frame = await resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), vector.terminal_receipt_id, contextFor(vector), input(), + ) + await expect(verifyHolonngramVisualFeedbackFrameIntegrityV1({ + ...frame, + frame_digest: H('1'), + })).rejects.toThrow(/frame digest is invalid/) + }) + + it('rejects any attempt to promote the visual projection', async () => { + const vector = loadVector() + const frame = await resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), vector.terminal_receipt_id, contextFor(vector), input(), + ) + await expect(verifyHolonngramVisualFeedbackFrameIntegrityV1({ + ...frame, + safety: { ...frame.safety, promotes_evidence: true }, + })).rejects.toThrow(/non-authoritative/) + }) + + it('rejects fixed-topology tampering', async () => { + const vector = loadVector() + const frame = await resolveAndCompileHolonngramVisualFeedbackV1( + sourceFor(vector), vector.terminal_receipt_id, contextFor(vector), input(), + ) + const nodes = [...structuredClone(frame.visual.nodes)] + nodes.reverse() + await expect(verifyHolonngramVisualFeedbackFrameIntegrityV1({ + ...frame, + visual: { ...frame.visual, nodes }, + })).rejects.toThrow(/fixed 19-node order/) + }) +}) diff --git a/studio/src/App.tsx b/studio/src/App.tsx index e3caea441..329958b0f 100644 --- a/studio/src/App.tsx +++ b/studio/src/App.tsx @@ -12,11 +12,14 @@ import { ObservabilitySurface } from './observability-surface/ObservabilitySurfa import { GovernanceSurface } from './governance-surface/GovernanceSurface.js' import { SwarmSurface } from './swarm-surface/SwarmSurface.js' import { HolographicSubstrate } from './holographic-surface/HolographicSubstrate.js' +import { HolonogramSurface } from './holonogram-surface/HolonogramSurface.js' type Surface = 'replay' | 'epoch' | 'divergence' | 'rollback' | 'lineage' | - 'topology' | 'ownership' | 'capsule' | 'observability' | 'governance' | 'swarm' | 'holographic' + 'topology' | 'ownership' | 'capsule' | 'observability' | 'governance' | 'swarm' | + 'holographic' | 'holonogram' const NAV: Array<{ id: Surface; label: string }> = [ + { id: 'holonogram', label: 'Holonñgram' }, { id: 'replay', label: 'Replay' }, { id: 'epoch', label: 'Epoch' }, { id: 'divergence', label: 'Divergence' }, @@ -32,7 +35,7 @@ const NAV: Array<{ id: Surface; label: string }> = [ ] export function App() { - const [active, setActive] = useState('replay') + const [active, setActive] = useState('holonogram') const { snapshot, error } = useTelemetry() return ( @@ -49,27 +52,27 @@ export function App() {
Constitutional Observability · Projection Only
- {error ? ( - - bridge offline - - ) : snapshot ? ( + {snapshot ? ( <> - - - live · epoch {snapshot.epoch_sequence} + + + unverified input · epoch {snapshot.epoch_sequence} - {snapshot.pgcs_passes ? 'PGCS PASS' : 'PGCS FAIL'} + reported PGCS {snapshot.pgcs_passes ? 'pass' : 'fail'} + ) : error ? ( + + demo · bridge offline + ) : ( - awaiting bridge… + awaiting unverified input… )}
@@ -94,6 +97,7 @@ export function App() { ))}
+ {active === 'holonogram' && } {active === 'replay' && } {active === 'epoch' && } {active === 'divergence' && } diff --git a/studio/src/holonogram-surface/HolonogramSurface.tsx b/studio/src/holonogram-surface/HolonogramSurface.tsx new file mode 100644 index 000000000..ec64b3b3b --- /dev/null +++ b/studio/src/holonogram-surface/HolonogramSurface.tsx @@ -0,0 +1,610 @@ +import { useMemo, useState, type ReactNode } from 'react' +import type { TelemetrySnapshot } from '../types.js' + +interface Props { + snapshot: TelemetrySnapshot | null +} + +type NodeStatus = 'input' | 'review' | 'unknown' | 'blocked' + +interface HolonNode { + id: string + label: string + ring: 'core' | 'inner' | 'outer' + status: NodeStatus + description: string +} + +interface Point { + x: number + y: number +} + +const CENTER: Point = { x: 300, y: 250 } +const STATUS_COLOR: Record = { + input: '#60A5FA', + review: '#C8A96E', + unknown: '#52525B', + blocked: '#F87171', +} + +const INNER_ROLES = [ + ['I1', 'Interpreter', 'Maps the unverified input into display primitives.'], + ['I2', 'Assessor', 'Displays local assessment without promoting evidence.'], + ['I3', 'Lease guard', 'Lease verification is not connected to this surface.'], + ['I4', 'Executor', 'Studio cannot execute or mutate canonical state.'], + ['I5', 'Verifier', 'Receipt verification is not connected to this surface.'], + ['I6', 'Committer', 'Studio cannot commit or mutate canonical state.'], +] as const + +const OUTER_ROLES = [ + ['O1', 'Actor witness', 'Actor identity binding is unavailable.'], + ['O2', 'Session witness', 'Session identity binding is unavailable.'], + ['O3', 'Workspace witness', 'Workspace identity binding is unavailable.'], + ['O4', 'Holon witness', 'Holon identity binding is unavailable.'], + ['O5', 'Authority witness', 'Authority receipt evidence is unavailable.'], + ['O6', 'Lease witness', 'Lease evidence is unavailable.'], + ['O7', 'Fence witness', 'Fencing-token evidence is unavailable.'], + ['O8', 'Expected-state witness', 'Expected state root is unavailable.'], + ['O9', 'Observed-state witness', 'Observed state root is unavailable.'], + ['O10', 'Action witness', 'Action digest is unavailable.'], + ['O11', 'Result witness', 'Result digest is unavailable.'], + ['O12', 'Trust-chain witness', 'Receipt and trust-chain resolution is unavailable.'], +] as const + +function polarPoint(radius: number, index: number, total: number): Point { + const angle = -Math.PI / 2 + (index / total) * Math.PI * 2 + return { + x: CENTER.x + Math.cos(angle) * radius, + y: CENTER.y + Math.sin(angle) * radius, + } +} + +function isFiniteNumber(value: unknown): value is number { + return typeof value === 'number' && Number.isFinite(value) +} + +function formatNumber(value: unknown, digits = 4): string { + return isFiniteNumber(value) ? value.toFixed(digits) : 'unavailable' +} + +function formatInteger(value: unknown): string { + return isFiniteNumber(value) ? Math.trunc(value).toLocaleString() : 'unavailable' +} + +function Panel({ + number, + title, + subtitle, + className = '', + children, +}: { + number: string + title: string + subtitle: string + className?: string + children: ReactNode +}) { + return ( +
+
+ {number} +
+

{title}

+

{subtitle}

+
+
+ {children} +
+ ) +} + +function BoundaryBanner({ hasInput }: { hasInput: boolean }) { + return ( +
+
+
+ Projection-only boundary +
+

+ Visual compilation is a read-only display. It cannot verify receipts, grant authority, + mutate state, adjust runtime routes, or promote evidence. +

+
+
+ + {hasInput ? 'unverified bridge input' : 'demo / no bridge input'} + + + no authority + + + no receipt resolver + +
+
+ ) +} + +function TransitionEnvelope({ snapshot }: Props) { + return ( + +
+ formula_id + UNRESOLVED + transition_id + UNRESOLVED + trace_id + UNRESOLVED + input_source + + {snapshot ? 'GET /telemetry · unverified' : 'none · demo boundary'} + + reported_epoch + {formatInteger(snapshot?.epoch_sequence)} + state_roots + unavailable +
+
+

+ Compilation status +

+

+ Telemetry can populate visual pressure, but no formula event, identity binding, or signed + transition envelope is available to authenticate it. +

+
+
+ ) +} + +function buildNodes(hasInput: boolean): HolonNode[] { + const inputStatus: NodeStatus = hasInput ? 'input' : 'unknown' + const innerStatuses: readonly NodeStatus[] = [ + inputStatus, 'blocked', 'blocked', 'blocked', 'unknown', inputStatus, + ] + const outerStatuses: readonly NodeStatus[] = [ + inputStatus, 'unknown', 'unknown', 'blocked', 'blocked', 'unknown', + 'review', 'unknown', 'blocked', 'review', 'blocked', 'blocked', + ] + + return [ + { + id: 'C0', + label: 'Envelope', + ring: 'core', + status: inputStatus, + description: hasInput + ? 'Contains unverified bridge telemetry for display only.' + : 'No live input is present; the surface is in demo mode.', + }, + ...INNER_ROLES.map(([id, label, description], index) => ({ + id, + label, + description, + ring: 'inner' as const, + status: innerStatuses[index] ?? 'unknown', + })), + ...OUTER_ROLES.map(([id, label, description], index) => ({ + id, + label, + description, + ring: 'outer' as const, + status: outerStatuses[index] ?? 'unknown', + })), + ] +} + +function Holonogram({ snapshot }: Props) { + const nodes = useMemo(() => buildNodes(snapshot !== null), [snapshot]) + const [selectedId, setSelectedId] = useState('C0') + const selected = nodes.find((node) => node.id === selectedId) ?? nodes[0]! + const inner = nodes.filter((node) => node.ring === 'inner') + const outer = nodes.filter((node) => node.ring === 'outer') + const points = new Map([ + ['C0', CENTER], + ...inner.map((node, index) => [node.id, polarPoint(104, index, inner.length)] as const), + ...outer.map((node, index) => [node.id, polarPoint(196, index, outer.length)] as const), + ]) + + return ( + +
+ + + ) +} + +function ExpectedActual({ snapshot }: Props) { + const rows = [ + { + label: 'PGCS report', + expected: 'true', + actual: snapshot ? String(snapshot.pgcs_passes) : 'unavailable', + match: snapshot?.pgcs_passes === true, + }, + { + label: 'Corruption report', + expected: '0', + actual: snapshot ? formatInteger(snapshot.corruption_count) : 'unavailable', + match: snapshot?.corruption_count === 0, + }, + { + label: 'Drift display guardrail', + expected: '< 0.2000', + actual: formatNumber(snapshot?.drift_index), + match: isFiniteNumber(snapshot?.drift_index) && snapshot.drift_index < 0.2, + }, + { + label: 'VCG display guardrail', + expected: '< 1.0000', + actual: formatNumber(snapshot?.vcg_error), + match: isFiniteNumber(snapshot?.vcg_error) && snapshot.vcg_error < 1, + }, + ] + + return ( + +
+ {rows.map((row) => ( +
+ {row.label} + {row.expected} + + {row.actual} + +
+ ))} +
+
+ changed_fields and canonical root delta: UNAVAILABLE +
+
+ ) +} + +function FeedbackSignal({ snapshot }: Props) { + const displayReview = snapshot === null || + snapshot.pgcs_passes !== true || + snapshot.corruption_count !== 0 || + !isFiniteNumber(snapshot.drift_index) || + snapshot.drift_index >= 0.2 + + return ( + +
+
+
+ display signal +
+
+ {displayReview ? 'Review input' : 'Observe'} +
+

+ {snapshot + ? 'Derived from unverified telemetry and local display guardrails.' + : 'No bridge input is present; no feedback can be compiled.'} +

+
+
+
+ resonance_score + not computed +
+
+ value_delta + not computed +
+
+
+
+ ) +} + +function MiddlewareEdge() { + return ( + +
+
+
+
I5
+
Verifier
+
+
+
display only
+
+
no write
+
+
+
I6
+
Committer
+
+
+
+
+ trust_delta not applied +
+
+ risk_delta not applied +
+
+
+ + ) +} + +function ReceiptChain() { + const rows = [ + ['Trust registry', 'not connected'], + ['Parent receipt', 'unresolved'], + ['Terminal receipt', 'unresolved'], + ['Promotion state', 'prohibited'], + ] as const + + return ( + +
+
+ {rows.map(([label], index) => ( +
+
+ {index + 1} +
+ {index < rows.length - 1 &&
} +
+ ))} +
+
+ {rows.map(([label, value]) => ( +
+ {label} + {value} +
+ ))} +
+
+ + ) +} + +function TimeHorizon({ snapshot }: Props) { + const observedTime = isFiniteNumber(snapshot?.timestamp_ms) + ? Math.trunc(snapshot.timestamp_ms).toLocaleString() + : 'unavailable' + + return ( + +
+
+ {[ + ['T-1', 'not provided', 'unknown'], + ['T0', snapshot ? `epoch ${formatInteger(snapshot.epoch_sequence)}` : 'no input', snapshot ? 'input' : 'unknown'], + ['T+1', 'not predicted', 'unknown'], + ].map(([slice, value, status]) => ( +
+
+
{slice}
+
{value}
+
+ ))} +
+
+ reported timestamp_ms + {observedTime} +
+

+ This is not trusted clock evidence and is not a receipt timeline. +

+
+ + ) +} + +export function HolonogramSurface({ snapshot }: Props) { + return ( +
+
+
+
+
+ Visual compiled feedback layer +
+

+ Holonñgram Compiler +

+

+ Mathematics as inspectable state pressure: envelope, lattice, comparison, feedback, + route proposal, provenance boundary, and time horizon. +

+
+
+ mathematics → trace → display → operator inspection +
+
+ + + +
+ + + +
+ +
+ + + + +
+
+
+ ) +} From d072d0365f4dc78dc0e419a1217173fe0dc82fe3 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Sat, 1 Aug 2026 20:40:07 +0200 Subject: [PATCH 43/89] fix(authority): canonicalize workspace observation remote --- sovereign-omega-v2/mcp-server/src/authority-response.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sovereign-omega-v2/mcp-server/src/authority-response.ts b/sovereign-omega-v2/mcp-server/src/authority-response.ts index b240377f9..b05af71f1 100644 --- a/sovereign-omega-v2/mcp-server/src/authority-response.ts +++ b/sovereign-omega-v2/mcp-server/src/authority-response.ts @@ -249,7 +249,7 @@ function expectedWorkspaceEvidence( declared_project: projectIdentity, actual_cwd: actualCwd, resolved_repository_root: resolvedRoot, - remote_origin: workspace.remote_origin, + remote_origin: remote, mutation_target: mutationTarget, path_views: workspace.path_views ?? {}, } From 0b87cffe3f749a7132c692c5ffc7ce37a3a5d08a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 18:40:27 +0000 Subject: [PATCH 44/89] chore(manifest): refresh cognitive-state anchors --- .claude.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude.json b/.claude.json index 6b29bdf53..2630a6343 100644 --- a/.claude.json +++ b/.claude.json @@ -10,7 +10,7 @@ "generator": "scripts/build-cognitive-manifest.py", "repository": "Aegis-Omega/AEGIS-OMEGA", "source_ref": "feat/sol-cross-platform-control-plane", - "parent_state_hash": "410bcd49c721e65050382ae759db7af2f41fa9f572e625174252c72a8748ea93", + "parent_state_hash": "e9f0ec153b0b320a1e791092f73209442ec43a982e503203d9c101ec40949cba", "signature_mode": "GITHUB_OIDC_ATTESTATION" }, "hashing": { @@ -488,5 +488,5 @@ "on_success": "broadcast-attested-verified-event-stream" } }, - "state_hash": "6b8012c545f8a49fc41c378729f75e4eba07574b05ce38b0b86df826124b1ab4" + "state_hash": "1726a5ac63348cbb6e5175588cd98de01ecaa2867556aa8f5086bb77cc6675fe" } From 8e223957b84983838ff1c01a1ed7679af3920fa0 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Sat, 1 Aug 2026 20:42:11 +0200 Subject: [PATCH 45/89] fix(edge): patch audited transitive dependencies --- platform/sol/cloudflare/worker/package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/platform/sol/cloudflare/worker/package.json b/platform/sol/cloudflare/worker/package.json index 799803821..82a7db0d7 100644 --- a/platform/sol/cloudflare/worker/package.json +++ b/platform/sol/cloudflare/worker/package.json @@ -22,6 +22,9 @@ "devDependencies": { "typescript": "5.9.3", "vitest": "3.2.7", - "wrangler": "4.112.0" + "wrangler": "4.114.0" + }, + "overrides": { + "@hono/node-server": "2.0.12" } } From faaacb550f1207ae7a99edaebfbb2f7f3547e0ed Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Sat, 1 Aug 2026 21:04:05 +0200 Subject: [PATCH 46/89] ci(sol): regenerate and verify edge lockfile --- .github/workflows/sol-lockfile-refresh.yml | 66 ++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/sol-lockfile-refresh.yml diff --git a/.github/workflows/sol-lockfile-refresh.yml b/.github/workflows/sol-lockfile-refresh.yml new file mode 100644 index 000000000..413b1e0ae --- /dev/null +++ b/.github/workflows/sol-lockfile-refresh.yml @@ -0,0 +1,66 @@ +name: SOL Edge Lockfile Refresh + +on: + push: + branches: + - feat/sol-cross-platform-control-plane + paths: + - 'platform/sol/cloudflare/worker/package.json' + - '.github/workflows/sol-lockfile-refresh.yml' + +permissions: + contents: write + +concurrency: + group: sol-edge-lockfile-refresh + cancel-in-progress: false + +jobs: + regenerate-verify-and-commit: + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + working-directory: platform/sol/cloudflare/worker + steps: + - name: Checkout canonical work branch + uses: actions/checkout@v4 + with: + ref: feat/sol-cross-platform-control-plane + fetch-depth: 0 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '24.11.0' + + - name: Regenerate exact lockfile + run: npm install --package-lock-only --ignore-scripts + + - name: Verify clean install + run: npm ci --ignore-scripts + + - name: Typecheck + run: npm run typecheck + + - name: Test + run: npm test + + - name: Audit high-severity dependency graph + run: npm audit --audit-level=high + + - name: Commit verified lockfile and remove one-shot workflow + working-directory: . + shell: bash + run: | + set -euo pipefail + git config user.name 'aegis-ci' + git config user.email 'aegis-ci@users.noreply.github.com' + rm .github/workflows/sol-lockfile-refresh.yml + git add platform/sol/cloudflare/worker/package-lock.json .github/workflows/sol-lockfile-refresh.yml + if git diff --cached --quiet; then + echo 'No lockfile change produced.' + exit 1 + fi + git commit -m 'build(edge): commit verified dependency lockfile' + git push origin HEAD:feat/sol-cross-platform-control-plane From f620e979698d1176e1114df80070b00f5ed28dba Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Sat, 1 Aug 2026 21:07:48 +0200 Subject: [PATCH 47/89] ci(sol): allow explicit PR revalidation trigger --- .github/workflows/sol-lockfile-refresh.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/sol-lockfile-refresh.yml b/.github/workflows/sol-lockfile-refresh.yml index 413b1e0ae..54f3753b0 100644 --- a/.github/workflows/sol-lockfile-refresh.yml +++ b/.github/workflows/sol-lockfile-refresh.yml @@ -7,6 +7,14 @@ on: paths: - 'platform/sol/cloudflare/worker/package.json' - '.github/workflows/sol-lockfile-refresh.yml' + pull_request: + branches: + - main + types: + - reopened + paths: + - 'platform/sol/cloudflare/worker/package.json' + - '.github/workflows/sol-lockfile-refresh.yml' permissions: contents: write @@ -17,6 +25,7 @@ concurrency: jobs: regenerate-verify-and-commit: + if: github.event_name == 'push' || github.event.pull_request.head.ref == 'feat/sol-cross-platform-control-plane' runs-on: ubuntu-latest timeout-minutes: 20 defaults: From 3452ea2d62beb7c4c7c298f1c838e3dd27539d9c Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Sat, 1 Aug 2026 21:15:07 +0200 Subject: [PATCH 48/89] chore(replay): align cognitive manifest with current main --- .claude.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude.json b/.claude.json index 2630a6343..db426975c 100644 --- a/.claude.json +++ b/.claude.json @@ -9,8 +9,8 @@ "provenance": { "generator": "scripts/build-cognitive-manifest.py", "repository": "Aegis-Omega/AEGIS-OMEGA", - "source_ref": "feat/sol-cross-platform-control-plane", - "parent_state_hash": "e9f0ec153b0b320a1e791092f73209442ec43a982e503203d9c101ec40949cba", + "source_ref": "claude/blissful-rubin-mt9jS", + "parent_state_hash": "410bcd49c721e65050382ae759db7af2f41fa9f572e625174252c72a8748ea93", "signature_mode": "GITHUB_OIDC_ATTESTATION" }, "hashing": { @@ -488,5 +488,5 @@ "on_success": "broadcast-attested-verified-event-stream" } }, - "state_hash": "1726a5ac63348cbb6e5175588cd98de01ecaa2867556aa8f5086bb77cc6675fe" + "state_hash": "e9f0ec153b0b320a1e791092f73209442ec43a982e503203d9c101ec40949cba" } From 4f8f6cc155c86ba685a126f7ca1e13cde7a1021f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 19:18:37 +0000 Subject: [PATCH 49/89] chore(manifest): refresh cognitive-state anchors --- .claude.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude.json b/.claude.json index db426975c..2630a6343 100644 --- a/.claude.json +++ b/.claude.json @@ -9,8 +9,8 @@ "provenance": { "generator": "scripts/build-cognitive-manifest.py", "repository": "Aegis-Omega/AEGIS-OMEGA", - "source_ref": "claude/blissful-rubin-mt9jS", - "parent_state_hash": "410bcd49c721e65050382ae759db7af2f41fa9f572e625174252c72a8748ea93", + "source_ref": "feat/sol-cross-platform-control-plane", + "parent_state_hash": "e9f0ec153b0b320a1e791092f73209442ec43a982e503203d9c101ec40949cba", "signature_mode": "GITHUB_OIDC_ATTESTATION" }, "hashing": { @@ -488,5 +488,5 @@ "on_success": "broadcast-attested-verified-event-stream" } }, - "state_hash": "e9f0ec153b0b320a1e791092f73209442ec43a982e503203d9c101ec40949cba" + "state_hash": "1726a5ac63348cbb6e5175588cd98de01ecaa2867556aa8f5086bb77cc6675fe" } From 27f190cf662692369f2f4bc7f61c9c7a4d7db472 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Sat, 1 Aug 2026 21:54:30 +0200 Subject: [PATCH 50/89] ci(sol): regenerate audited edge lockfile --- .github/workflows/sol-lockfile-repair.yml | 51 +++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/sol-lockfile-repair.yml diff --git a/.github/workflows/sol-lockfile-repair.yml b/.github/workflows/sol-lockfile-repair.yml new file mode 100644 index 000000000..b5bfda82d --- /dev/null +++ b/.github/workflows/sol-lockfile-repair.yml @@ -0,0 +1,51 @@ +name: SOL Lockfile Repair + +on: + push: + branches: + - feat/sol-cross-platform-control-plane + paths: + - 'platform/sol/cloudflare/worker/package.json' + - '.github/workflows/sol-lockfile-repair.yml' + workflow_dispatch: + +permissions: + contents: write + +jobs: + regenerate-lockfile: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout exact branch head + uses: actions/checkout@v4 + with: + ref: feat/sol-cross-platform-control-plane + fetch-depth: 0 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '24.11.0' + + - name: Regenerate exact lockfile + working-directory: platform/sol/cloudflare/worker + run: npm install --package-lock-only --ignore-scripts + + - name: Verify high-severity audit gate + working-directory: platform/sol/cloudflare/worker + run: npm audit --audit-level=high + + - name: Commit generated lockfile + shell: bash + run: | + set -euo pipefail + git config user.name 'aegis-ci' + git config user.email 'aegis-ci@users.noreply.github.com' + git add platform/sol/cloudflare/worker/package-lock.json + if git diff --cached --quiet; then + echo 'LOCKFILE_UNCHANGED' + exit 0 + fi + git commit -m 'fix(edge): regenerate audited dependency lockfile' + git push origin HEAD:feat/sol-cross-platform-control-plane From b12ca419cce740ba7fc6ef26a065d867a0a62bb4 Mon Sep 17 00:00:00 2001 From: aegis-ci Date: Sat, 1 Aug 2026 19:54:59 +0000 Subject: [PATCH 51/89] fix(edge): regenerate audited dependency lockfile --- .../sol/cloudflare/worker/package-lock.json | 5168 +++++++++++++++++ 1 file changed, 5168 insertions(+) create mode 100644 platform/sol/cloudflare/worker/package-lock.json diff --git a/platform/sol/cloudflare/worker/package-lock.json b/platform/sol/cloudflare/worker/package-lock.json new file mode 100644 index 000000000..c85f5f1ea --- /dev/null +++ b/platform/sol/cloudflare/worker/package-lock.json @@ -0,0 +1,5168 @@ +{ + "name": "@aegis/sol-edge-mcp", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@aegis/sol-edge-mcp", + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/sdk": "1.29.0", + "agents": "0.17.4", + "zod": "4.4.3" + }, + "devDependencies": { + "typescript": "5.9.3", + "vitest": "3.2.7", + "wrangler": "4.114.0" + }, + "engines": { + "node": ">=24.11" + } + }, + "node_modules/@babel/code-frame": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0.tgz", + "integrity": "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^8.0.0", + "js-tokens": "^10.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.0.tgz", + "integrity": "sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/core": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.1.tgz", + "integrity": "sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/generator": "^8.0.0", + "@babel/helper-compilation-targets": "^8.0.0", + "@babel/helpers": "^8.0.0", + "@babel/parser": "^8.0.0", + "@babel/template": "^8.0.0", + "@babel/traverse": "^8.0.0", + "@babel/types": "^8.0.0", + "@types/gensync": "^1.0.5", + "convert-source-map": "^2.0.0", + "empathic": "^2.0.1", + "gensync": "^1.0.0-beta.2", + "import-meta-resolve": "^4.2.0", + "json5": "^2.2.3", + "obug": "^2.1.1", + "semver": "^7.7.3" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz", + "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz", + "integrity": "sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.0.tgz", + "integrity": "sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^8.0.0", + "@babel/helper-validator-option": "^8.0.0", + "browserslist": "^4.24.0", + "lru-cache": "^11.0.0", + "semver": "^7.7.3" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-8.0.1.tgz", + "integrity": "sha512-++t3ZktzlLmASAxIlxeXQK9Z2YwUafYGYcvGBFevqOqt16HozVHStUoQvWD09fzAZOb/uJGpUTBuGK41AJAuOA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^8.0.0", + "@babel/helper-member-expression-to-functions": "^8.0.0", + "@babel/helper-optimise-call-expression": "^8.0.0", + "@babel/helper-replace-supers": "^8.0.1", + "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0", + "@babel/traverse": "^8.0.0", + "semver": "^7.7.3" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.0.tgz", + "integrity": "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-8.0.0.tgz", + "integrity": "sha512-xkXrMbtk87Gk7+oKBVmBc6EORg/Qwx++AHESldmHkpvG8wgccdhJJFwrzqlF382Fk8wfXhJHWE/g/43QvEGNPQ==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-8.0.0.tgz", + "integrity": "sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-8.0.1.tgz", + "integrity": "sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-8.0.1.tgz", + "integrity": "sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^8.0.0", + "@babel/helper-optimise-call-expression": "^8.0.0", + "@babel/traverse": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-8.0.0.tgz", + "integrity": "sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", + "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz", + "integrity": "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/parser": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz", + "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==", + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-8.0.2.tgz", + "integrity": "sha512-+C6O6KKXU7BBq1GNaIkFJxrALUVGRcr+WeWm4OcuRl3h+l/CmNfcTLMrT2Lm3uvGBimBH/8pEBRrXJFLoO67Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^8.0.1", + "@babel/helper-plugin-utils": "^8.0.1", + "@babel/plugin-syntax-decorators": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-8.0.1.tgz", + "integrity": "sha512-NI+0S/6MvR6GlcQFwjDZ+WIc2qvG6TXN534lYs9llNldwW4b7Dh6KTtk030FA0xWdYGs4t1lWo+OEWN8wGB+Nw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.7.tgz", + "integrity": "sha512-ppj9ouYku+RX0ljtgZd+KMO5mkM2bCqg8H2PYAFWnLsHEIKIdRojqbJ2i3eVHrisuxy7nOFCmngTDdWtUCdXUQ==", + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.48.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz", + "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/traverse": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.4.tgz", + "integrity": "sha512-bZnmqzGG8UZneG1lLxBoWIH0G6Gr1D846Yu4/3XnY6FhCndMR49u26nTY08u/dAxWmLWF9vGQOuC+84FfIUoeg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/generator": "^8.0.0", + "@babel/helper-globals": "^8.0.0", + "@babel/parser": "^8.0.4", + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.4", + "obug": "^2.1.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/types": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", + "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.4" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "license": "MIT", + "peer": true + }, + "node_modules/@cloudflare/codemode": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@cloudflare/codemode/-/codemode-0.4.4.tgz", + "integrity": "sha512-GL0IF7NiHwAC222riEYGX75NHcnhTornAPfRI0Mj3Ig7C1y9H89FL0xs+pdflIF74eecNkzynsA+JdCIkGvKeA==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.15", + "acorn": "^8.17.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.0", + "@tanstack/ai": ">=0.8.0 <1.0.0", + "ai": "^6.0.0", + "zod": "^4.0.0" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + }, + "@tanstack/ai": { + "optional": true + }, + "ai": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz", + "integrity": "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.1.tgz", + "integrity": "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.24", + "workerd": ">1.20260305.0 <2.0.0-0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20260722.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260722.1.tgz", + "integrity": "sha512-vZOP8vIS3NwnuaO+gz0FZ7kIGeiO3bZmxV35Ph9zOXKSREhDFlH7wQ7mkCdhW3O4jnXsew+XT7b+DNEI2CcJGQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20260722.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260722.1.tgz", + "integrity": "sha512-EmIQymihDq6WNdER4+LF8Qn80yqayBUpJ+tkOO7wmY8pmgfyXjIUFNXotl21AHovTeu2seR7HdVUgeN/BilCWw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20260722.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260722.1.tgz", + "integrity": "sha512-jvZ3k9fxcnEn04s80CgIYxQfpOyAiz/8qC42DP8EBa9tR27qWyg9wmm31zIobVlrgBZn/+8NfdP73avRGcQOjQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20260722.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260722.1.tgz", + "integrity": "sha512-BOSB55SMNdy+DA5uj2WirgiNanpHGis5PVvXH1wSfvjRKr4JGgWK+EZzxz0RFUo6QjjQQC/NimEzNZ7va7jmKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20260722.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260722.1.tgz", + "integrity": "sha512-sYM8YgUpKnRz2xjvdJLX1Ojzoi4MlA4gk8WTTExhGydjYB2UTs5NIbv0ZmpKgMoK9io3ixgmiW56ZnTbcWOdiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workers-types": { + "version": "5.20260801.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-5.20260801.1.tgz", + "integrity": "sha512-XCv5xWi47WQOK0LpLa6997Mrpz8Ct+nZmp/M5Xp8Z4BFsarf7nYjkznGOcOoYK5m1GfbMFEEuQ2OIZnbIWoe9A==", + "license": "MIT OR Apache-2.0", + "peer": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@emnapi/core": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", + "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "2.0.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz", + "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz", + "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.12.tgz", + "integrity": "sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.2.tgz", + "integrity": "sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.1" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.2.tgz", + "integrity": "sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.1" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.2.tgz", + "integrity": "sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.2" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.1.tgz", + "integrity": "sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.1.tgz", + "integrity": "sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.1.tgz", + "integrity": "sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.1.tgz", + "integrity": "sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.1.tgz", + "integrity": "sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.1.tgz", + "integrity": "sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.1.tgz", + "integrity": "sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.1.tgz", + "integrity": "sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.1.tgz", + "integrity": "sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.1.tgz", + "integrity": "sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.2.tgz", + "integrity": "sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.1" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.2.tgz", + "integrity": "sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.1" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.2.tgz", + "integrity": "sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.1" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.2.tgz", + "integrity": "sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.1" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.2.tgz", + "integrity": "sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.1" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.2.tgz", + "integrity": "sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.1" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.2.tgz", + "integrity": "sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.1" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.2.tgz", + "integrity": "sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.1" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.2.tgz", + "integrity": "sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==", + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-wasm32/node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.2.tgz", + "integrity": "sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.2" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.2.tgz", + "integrity": "sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.2.tgz", + "integrity": "sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.2.tgz", + "integrity": "sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", + "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@poppinss/colors": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.1.tgz", + "integrity": "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.1.tgz", + "integrity": "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.1.tgz", + "integrity": "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.1.tgz", + "integrity": "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.1.tgz", + "integrity": "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.1.tgz", + "integrity": "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.1.tgz", + "integrity": "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.1.tgz", + "integrity": "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.1.tgz", + "integrity": "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.1.tgz", + "integrity": "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.1.tgz", + "integrity": "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.1.tgz", + "integrity": "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.1.tgz", + "integrity": "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "2.0.0-alpha.3", + "@emnapi/runtime": "2.0.0-alpha.3", + "@napi-rs/wasm-runtime": "^1.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.1.tgz", + "integrity": "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.1.tgz", + "integrity": "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/plugin-babel": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/plugin-babel/-/plugin-babel-0.2.3.tgz", + "integrity": "sha512-+zEk16yGlz1F9STiRr6uG9hmIXb6nprjLczV/htGptYuLoCuxb+itZ03RKCEeOhBpDDd1NU7qF6x1VLMUp62bw==", + "license": "MIT", + "dependencies": { + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=22.12.0 || ^24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.29.0 || ^8.0.0-rc.1", + "@babel/plugin-transform-runtime": "^7.29.0 || ^8.0.0-rc.1", + "@babel/runtime": "^7.27.0 || ^8.0.0-rc.1", + "rolldown": "^1.0.0-rc.5", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@babel/plugin-transform-runtime": { + "optional": true + }, + "@babel/runtime": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@speed-highlight/core": { + "version": "1.2.20", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.20.tgz", + "integrity": "sha512-biXn20UJkNJ9cB35lPXJKfgUl1NQfhbApt78oePVjC0gB4+hlJyjkUz1hoLHLk7dEl+mSbW0VtX4C2IlRIDoHA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/gensync": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.5.tgz", + "integrity": "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==", + "license": "MIT" + }, + "node_modules/@types/jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.7", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agents": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/agents/-/agents-0.17.4.tgz", + "integrity": "sha512-K6YRbpD3VcwdTOPBlDgI4dILAwkhXo5cdxTlVF0IvUwQEKfMPawmH8E/QMXTN8CPGHqVYgYFACxTyk6nKlK+vg==", + "license": "MIT", + "dependencies": { + "@babel/plugin-proposal-decorators": "^8.0.2", + "@cfworker/json-schema": "^4.1.1", + "@cloudflare/codemode": "^0.4.3", + "@modelcontextprotocol/sdk": "1.29.0", + "@rolldown/plugin-babel": "^0.2.3", + "cron-schedule": "^6.0.0", + "esbuild": "^0.28.1", + "mimetext": "^3.0.28", + "nanoid": "^5.1.16", + "partyserver": "^0.5.8", + "partysocket": "1.3.0", + "yaml": "^2.9.0", + "yargs": "^18.0.0" + }, + "bin": { + "agents": "dist/cli/index.js" + }, + "peerDependencies": { + "@ai-sdk/react": "^3.0.204", + "@tanstack/ai": ">=0.10.2 <1.0.0", + "@x402/core": "^2.0.0", + "@x402/evm": "^2.0.0", + "ai": "^6.0.0", + "chat": "^4.29.0", + "just-bash": "^3.0.0", + "react": "^19.0.0", + "vite": ">=6.0.0 <9.0.0", + "zod": "^4.0.0" + }, + "peerDependenciesMeta": { + "@ai-sdk/react": { + "optional": true + }, + "@tanstack/ai": { + "optional": true + }, + "@x402/core": { + "optional": true + }, + "@x402/evm": { + "optional": true + }, + "ai": { + "optional": true + }, + "chat": { + "optional": true + }, + "just-bash": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.10.tgz", + "integrity": "sha512-35JEvJ5/KKlbCHjMCsONI2w6HE88STjVdHk+C7d8LtcFxUjZR1KeLP9izofn2qs0KUxX5r4z73bwH/rd+JHacw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/browserslist": { + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-js-pure": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.49.0.tgz", + "integrity": "sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cron-schedule": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cron-schedule/-/cron-schedule-6.0.0.tgz", + "integrity": "sha512-BoZaseYGXOo5j5HUwTaegIog3JJbuH4BbrY9A1ArLjXpy+RWb3mV28F/9Gv1dDA7E2L8kngWva4NWisnLTyfgQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.399", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.399.tgz", + "integrity": "sha512-lEcqhErbHjXRvd41rnWLpzbyU/IXfIYo7QwaFWmxGeLiLyY2TBCdHnWY88vB+p3ubnihRypDm66panXl7TylLA==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/empathic": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", + "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-polyfill": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/event-target-polyfill/-/event-target-polyfill-0.0.4.tgz", + "integrity": "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==", + "license": "MIT" + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.1.tgz", + "integrity": "sha512-0D493aP61w0TJ2A0wy27riRsO7FMQ7FK+KUHOKCSfPvYo0R55aiC6emCVgFUeShH0fq0ICPVzNcgoS+BsbXQCA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.33", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.33.tgz", + "integrity": "sha512-+SwvkaiJtxsiPjhy9LivY/1m7UsNqCJetM1BrZl9A5DkQhlbHQDU730mMiDPWjnoCYOM8Chf3WrCJw27kNTPFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz", + "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.7", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.7.tgz", + "integrity": "sha512-hq1OB1bALKfydZNoViyg6hPVGV4i93ny9Op+n4zP5RSf7SCZEXa/TsG2O3IEr7+WlHRTPnpqDmHfMH6qXAD60w==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-base64": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.9.2.tgz", + "integrity": "sha512-6zayE8QlUdiweYI6cETD/XBSqFcoCUlufn/29PJR99r82x1yDnIprRca0YvAYpAW+ez0GuQkVBC6xG5QkD7OjA==", + "license": "BSD-3-Clause" + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimetext": { + "version": "3.0.28", + "resolved": "https://registry.npmjs.org/mimetext/-/mimetext-3.0.28.tgz", + "integrity": "sha512-eQXpbNrtxLCjUtiVbR/qR09dbPgZ2o+KR1uA7QKqGhbn8QV7HIL16mXXsobBL4/8TqoYh1us31kfz+dNfCev9g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@babel/runtime-corejs3": "^7.26.0", + "js-base64": "^3.7.7", + "mime-types": "^2.1.35" + }, + "funding": { + "type": "patreon", + "url": "https://patreon.com/muratgozel" + } + }, + "node_modules/mimetext/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimetext/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/miniflare": { + "version": "4.20260722.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260722.0.tgz", + "integrity": "sha512-LW6ABMhCx/yIEFBLC/DO4yAhdm2T/G7jp7pr5T2kj895+CCIaHZqpMXdW9O6YE48LcYcCJChwWc8aEs1vpbTXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "sharp": "0.35.2", + "undici": "7.28.0", + "workerd": "1.20260722.1", + "ws": "8.21.0", + "youch": "4.1.0-beta.10" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", + "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/partyserver": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/partyserver/-/partyserver-0.5.9.tgz", + "integrity": "sha512-0T1OGQD9Ggllrlp8Bt8XLOglEEktH4bui28Z1dbSGEX/iFmZnEWR148Qmvc1Od3rl64OAOfVtr2sG4XOMI9Y6Q==", + "license": "ISC", + "dependencies": { + "nanoid": "^5.1.9" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20260424.1 || ^5.20260703.1" + } + }, + "node_modules/partysocket": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/partysocket/-/partysocket-1.3.0.tgz", + "integrity": "sha512-1zToNyolZFK/7nuAw/K2bZrNzFqaZyRoCEkS+9vG6WSC5ikrN6qWRe96q6ImU51uptz2r+dAwSkwhJVdQi4LiA==", + "license": "MIT", + "dependencies": { + "event-target-polyfill": "^0.0.4" + }, + "peerDependencies": { + "react": ">=17" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz", + "integrity": "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@oxc-project/types": "=0.142.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.2.1", + "@rolldown/binding-darwin-arm64": "1.2.1", + "@rolldown/binding-darwin-x64": "1.2.1", + "@rolldown/binding-freebsd-x64": "1.2.1", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.1", + "@rolldown/binding-linux-arm64-gnu": "1.2.1", + "@rolldown/binding-linux-arm64-musl": "1.2.1", + "@rolldown/binding-linux-ppc64-gnu": "1.2.1", + "@rolldown/binding-linux-s390x-gnu": "1.2.1", + "@rolldown/binding-linux-x64-gnu": "1.2.1", + "@rolldown/binding-linux-x64-musl": "1.2.1", + "@rolldown/binding-openharmony-arm64": "1.2.1", + "@rolldown/binding-wasm32-wasi": "1.2.1", + "@rolldown/binding-win32-arm64-msvc": "1.2.1", + "@rolldown/binding-win32-x64-msvc": "1.2.1" + } + }, + "node_modules/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sharp": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.2.tgz", + "integrity": "sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.4" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.2", + "@img/sharp-darwin-x64": "0.35.2", + "@img/sharp-freebsd-wasm32": "0.35.2", + "@img/sharp-libvips-darwin-arm64": "1.3.1", + "@img/sharp-libvips-darwin-x64": "1.3.1", + "@img/sharp-libvips-linux-arm": "1.3.1", + "@img/sharp-libvips-linux-arm64": "1.3.1", + "@img/sharp-libvips-linux-ppc64": "1.3.1", + "@img/sharp-libvips-linux-riscv64": "1.3.1", + "@img/sharp-libvips-linux-s390x": "1.3.1", + "@img/sharp-libvips-linux-x64": "1.3.1", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.1", + "@img/sharp-libvips-linuxmusl-x64": "1.3.1", + "@img/sharp-linux-arm": "0.35.2", + "@img/sharp-linux-arm64": "0.35.2", + "@img/sharp-linux-ppc64": "0.35.2", + "@img/sharp-linux-riscv64": "0.35.2", + "@img/sharp-linux-s390x": "0.35.2", + "@img/sharp-linux-x64": "0.35.2", + "@img/sharp-linuxmusl-arm64": "0.35.2", + "@img/sharp-linuxmusl-x64": "0.35.2", + "@img/sharp-webcontainers-wasm32": "0.35.2", + "@img/sharp-win32-arm64": "0.35.2", + "@img/sharp-win32-ia32": "0.35.2", + "@img/sharp-win32-x64": "0.35.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unenv": { + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "pathe": "^2.0.3" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", + "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.7", + "@vitest/mocker": "3.2.7", + "@vitest/pretty-format": "^3.2.7", + "@vitest/runner": "3.2.7", + "@vitest/snapshot": "3.2.7", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.7", + "@vitest/ui": "3.2.7", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/workerd": { + "version": "1.20260722.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260722.1.tgz", + "integrity": "sha512-NycKuc1x2onvsRfGGpM093vRlLFU2zHDAM0+APpccfg4+gZxDGCH27RmdDvkeBuoZyYqgLo3oAfF6re4mvC3vQ==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20260722.1", + "@cloudflare/workerd-darwin-arm64": "1.20260722.1", + "@cloudflare/workerd-linux-64": "1.20260722.1", + "@cloudflare/workerd-linux-arm64": "1.20260722.1", + "@cloudflare/workerd-windows-64": "1.20260722.1" + } + }, + "node_modules/wrangler": { + "version": "4.114.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.114.0.tgz", + "integrity": "sha512-M65P25t5UHA1TIJfgZXDcj+YzVobgKdRguM2QPz0xnxLFuOcuE3ErgllDht0iaho7MS4o0g/Bb4YK2+GT+bibg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.5.0", + "@cloudflare/unenv-preset": "2.16.1", + "blake3-wasm": "2.1.5", + "esbuild": "0.28.1", + "miniflare": "4.20260722.0", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.24", + "workerd": "1.20260722.1" + }, + "bin": { + "cf-wrangler": "bin/cf-wrangler.js", + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=22.0.0" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^5.20260722.1" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/wrangler/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", + "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } + }, + "node_modules/youch/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} From 05bb721e641583a49df8478bbe0e3ca08f4a27af Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Sat, 1 Aug 2026 21:55:39 +0200 Subject: [PATCH 52/89] chore(ci): remove temporary lockfile repair workflow --- .github/workflows/sol-lockfile-repair.yml | 51 ----------------------- 1 file changed, 51 deletions(-) delete mode 100644 .github/workflows/sol-lockfile-repair.yml diff --git a/.github/workflows/sol-lockfile-repair.yml b/.github/workflows/sol-lockfile-repair.yml deleted file mode 100644 index b5bfda82d..000000000 --- a/.github/workflows/sol-lockfile-repair.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: SOL Lockfile Repair - -on: - push: - branches: - - feat/sol-cross-platform-control-plane - paths: - - 'platform/sol/cloudflare/worker/package.json' - - '.github/workflows/sol-lockfile-repair.yml' - workflow_dispatch: - -permissions: - contents: write - -jobs: - regenerate-lockfile: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout exact branch head - uses: actions/checkout@v4 - with: - ref: feat/sol-cross-platform-control-plane - fetch-depth: 0 - - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: '24.11.0' - - - name: Regenerate exact lockfile - working-directory: platform/sol/cloudflare/worker - run: npm install --package-lock-only --ignore-scripts - - - name: Verify high-severity audit gate - working-directory: platform/sol/cloudflare/worker - run: npm audit --audit-level=high - - - name: Commit generated lockfile - shell: bash - run: | - set -euo pipefail - git config user.name 'aegis-ci' - git config user.email 'aegis-ci@users.noreply.github.com' - git add platform/sol/cloudflare/worker/package-lock.json - if git diff --cached --quiet; then - echo 'LOCKFILE_UNCHANGED' - exit 0 - fi - git commit -m 'fix(edge): regenerate audited dependency lockfile' - git push origin HEAD:feat/sol-cross-platform-control-plane From 3a88ff8fc6aab1727029b35a1c06ce857763276f Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Sat, 1 Aug 2026 22:06:43 +0200 Subject: [PATCH 53/89] chore(ci): remove residual lockfile write workflow --- .github/workflows/sol-lockfile-refresh.yml | 75 ---------------------- 1 file changed, 75 deletions(-) delete mode 100644 .github/workflows/sol-lockfile-refresh.yml diff --git a/.github/workflows/sol-lockfile-refresh.yml b/.github/workflows/sol-lockfile-refresh.yml deleted file mode 100644 index 54f3753b0..000000000 --- a/.github/workflows/sol-lockfile-refresh.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: SOL Edge Lockfile Refresh - -on: - push: - branches: - - feat/sol-cross-platform-control-plane - paths: - - 'platform/sol/cloudflare/worker/package.json' - - '.github/workflows/sol-lockfile-refresh.yml' - pull_request: - branches: - - main - types: - - reopened - paths: - - 'platform/sol/cloudflare/worker/package.json' - - '.github/workflows/sol-lockfile-refresh.yml' - -permissions: - contents: write - -concurrency: - group: sol-edge-lockfile-refresh - cancel-in-progress: false - -jobs: - regenerate-verify-and-commit: - if: github.event_name == 'push' || github.event.pull_request.head.ref == 'feat/sol-cross-platform-control-plane' - runs-on: ubuntu-latest - timeout-minutes: 20 - defaults: - run: - working-directory: platform/sol/cloudflare/worker - steps: - - name: Checkout canonical work branch - uses: actions/checkout@v4 - with: - ref: feat/sol-cross-platform-control-plane - fetch-depth: 0 - - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: '24.11.0' - - - name: Regenerate exact lockfile - run: npm install --package-lock-only --ignore-scripts - - - name: Verify clean install - run: npm ci --ignore-scripts - - - name: Typecheck - run: npm run typecheck - - - name: Test - run: npm test - - - name: Audit high-severity dependency graph - run: npm audit --audit-level=high - - - name: Commit verified lockfile and remove one-shot workflow - working-directory: . - shell: bash - run: | - set -euo pipefail - git config user.name 'aegis-ci' - git config user.email 'aegis-ci@users.noreply.github.com' - rm .github/workflows/sol-lockfile-refresh.yml - git add platform/sol/cloudflare/worker/package-lock.json .github/workflows/sol-lockfile-refresh.yml - if git diff --cached --quiet; then - echo 'No lockfile change produced.' - exit 1 - fi - git commit -m 'build(edge): commit verified dependency lockfile' - git push origin HEAD:feat/sol-cross-platform-control-plane From 4fa19c2e1a854016728702ea666382293b5a45e5 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Sat, 1 Aug 2026 22:11:04 +0200 Subject: [PATCH 54/89] feat(governance): declare workflow write-capability allowlist --- .github/write-capability-allowlist.yml | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/write-capability-allowlist.yml diff --git a/.github/write-capability-allowlist.yml b/.github/write-capability-allowlist.yml new file mode 100644 index 000000000..cf09b45ff --- /dev/null +++ b/.github/write-capability-allowlist.yml @@ -0,0 +1,49 @@ +{ + "schema_version": "1.0.0", + "policy": { + "default": "DENY_UNDECLARED_WRITE_CAPABILITY", + "branch_write_requires": [ + "AEGIS_WRITER_LEASE", + "AEGIS_FENCING_TOKEN", + "AEGIS_WRITE_RECEIPT" + ] + }, + "workflows": { + ".github/workflows/automaton-2.yml": { + "allowed_permissions": ["contents", "id-token"], + "may_git_push": true, + "authority_domain": "governance:anchor-promotion", + "lease_fencing_required": true + }, + ".github/workflows/cognitive-manifest-refresh.yml": { + "allowed_permissions": ["contents"], + "may_git_push": true, + "authority_domain": "governance:cognitive-manifest", + "lease_fencing_required": true + }, + ".github/workflows/automaton-3.yml": { + "allowed_permissions": ["id-token"], + "may_git_push": false, + "authority_domain": "authority:automaton-3", + "lease_fencing_required": false + }, + ".github/workflows/experiment-admission.yml": { + "allowed_permissions": ["id-token"], + "may_git_push": false, + "authority_domain": "governance:experiment-admission", + "lease_fencing_required": false + }, + ".github/workflows/deploy-cloud-run.yml": { + "allowed_permissions": ["id-token"], + "may_git_push": false, + "authority_domain": "deployment:gcp-cloud-run", + "lease_fencing_required": false + }, + ".github/workflows/jekyll-gh-pages.yml": { + "allowed_permissions": ["id-token"], + "may_git_push": false, + "authority_domain": "deployment:github-pages", + "lease_fencing_required": false + } + } +} From 2e08689d85d88e9b145ac6ba3b39c8b1b6449607 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Sat, 1 Aug 2026 22:11:28 +0200 Subject: [PATCH 55/89] feat(governance): add fail-closed workflow capability audit --- scripts/audit-workflow-write-capabilities.py | 100 +++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 scripts/audit-workflow-write-capabilities.py diff --git a/scripts/audit-workflow-write-capabilities.py b/scripts/audit-workflow-write-capabilities.py new file mode 100644 index 000000000..03cc577cb --- /dev/null +++ b/scripts/audit-workflow-write-capabilities.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Fail closed on undeclared or unreceipted GitHub Actions write capability.""" +from __future__ import annotations + +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +WORKFLOWS = ROOT / ".github" / "workflows" +ALLOWLIST = ROOT / ".github" / "write-capability-allowlist.yml" +PRIVILEGED = ("contents", "id-token") +REQUIRED_MARKERS = ( + "AEGIS_WRITER_LEASE", + "AEGIS_FENCING_TOKEN", + "AEGIS_WRITE_RECEIPT", +) + + +def permission_is_write(text: str, permission: str) -> bool: + escaped = re.escape(permission) + line_form = re.compile(rf"(?m)^\s*{escaped}\s*:\s*write\s*(?:#.*)?$") + inline_form = re.compile(rf"[{{,]\s*{escaped}\s*:\s*write\s*[,}}]") + return bool(line_form.search(text) or inline_form.search(text)) + + +def load_allowlist() -> dict[str, object]: + try: + data = json.loads(ALLOWLIST.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise SystemExit(f"WRITE_CAPABILITY_ALLOWLIST_INVALID: {exc}") from exc + if data.get("schema_version") != "1.0.0" or not isinstance(data.get("workflows"), dict): + raise SystemExit("WRITE_CAPABILITY_ALLOWLIST_SCHEMA_INVALID") + return data + + +def main() -> int: + allowlist = load_allowlist() + entries = allowlist["workflows"] + assert isinstance(entries, dict) + errors: list[str] = [] + observed_privileged: set[str] = set() + + for path in sorted((*WORKFLOWS.glob("*.yml"), *WORKFLOWS.glob("*.yaml"))): + relative = path.relative_to(ROOT).as_posix() + text = path.read_text(encoding="utf-8") + permissions = {name for name in PRIVILEGED if permission_is_write(text, name)} + pushes = bool(re.search(r"(?m)\bgit\s+push\b", text)) + + if not permissions and not pushes: + continue + + observed_privileged.add(relative) + entry = entries.get(relative) + if not isinstance(entry, dict): + errors.append(f"UNALLOWLISTED_WRITE_CAPABILITY:{relative}") + continue + + allowed_permissions = entry.get("allowed_permissions") + if not isinstance(allowed_permissions, list) or any(item not in PRIVILEGED for item in allowed_permissions): + errors.append(f"ALLOWLIST_PERMISSION_SCHEMA_INVALID:{relative}") + continue + unexpected = sorted(permissions - set(allowed_permissions)) + if unexpected: + errors.append(f"UNAPPROVED_WRITE_PERMISSION:{relative}:{','.join(unexpected)}") + + may_push = entry.get("may_git_push") is True + if pushes and not may_push: + errors.append(f"UNAPPROVED_GIT_PUSH:{relative}") + if may_push and not pushes: + errors.append(f"STALE_GIT_PUSH_ALLOWLIST:{relative}") + + if pushes: + if entry.get("lease_fencing_required") is not True: + errors.append(f"PUSH_WITHOUT_LEASE_POLICY:{relative}") + missing = [marker for marker in REQUIRED_MARKERS if marker not in text] + if missing: + errors.append(f"UNRECEIPTED_BRANCH_WRITER:{relative}:missing={','.join(missing)}") + if not isinstance(entry.get("authority_domain"), str) or not entry.get("authority_domain"): + errors.append(f"WRITER_AUTHORITY_DOMAIN_MISSING:{relative}") + + stale = sorted(set(entries) - observed_privileged) + for relative in stale: + errors.append(f"STALE_WRITE_CAPABILITY_ALLOWLIST:{relative}") + + if errors: + print("WRITE_CAPABILITY_GATE=FAILED") + for error in errors: + print(error) + return 1 + + print("WRITE_CAPABILITY_GATE=PASSED") + for relative in sorted(observed_privileged): + print(f"ALLOWLISTED:{relative}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 12e2c88473a7017378dab31e078b25f991ece397 Mon Sep 17 00:00:00 2001 From: tarikskalic Date: Sat, 1 Aug 2026 22:11:46 +0200 Subject: [PATCH 56/89] ci(governance): enforce workflow write-capability gate --- .github/workflows/write-capability-gate.yml | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/write-capability-gate.yml diff --git a/.github/workflows/write-capability-gate.yml b/.github/workflows/write-capability-gate.yml new file mode 100644 index 000000000..6d8db37a2 --- /dev/null +++ b/.github/workflows/write-capability-gate.yml @@ -0,0 +1,37 @@ +name: Workflow Write Capability Gate + +on: + pull_request: + branches: + - main + paths: + - '.github/workflows/**' + - '.github/write-capability-allowlist.yml' + - 'scripts/audit-workflow-write-capabilities.py' + push: + branches: + - feat/sol-cross-platform-control-plane + paths: + - '.github/workflows/**' + - '.github/write-capability-allowlist.yml' + - 'scripts/audit-workflow-write-capabilities.py' + workflow_dispatch: + +permissions: + contents: read + +jobs: + audit-workflow-write-capabilities: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout exact candidate + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Audit privileged workflows and branch writers + run: python scripts/audit-workflow-write-capabilities.py From 80dbc35d55fbe0602faa76d0266b9f43550549b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Sat, 15 Aug 2026 19:19:49 +0200 Subject: [PATCH 57/89] fix(admission): rebind PR 225 to canonical parent --- .../pr-225-sol-cross-platform-control-plane-v1.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json b/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json index 978594ae7..feae6b827 100644 --- a/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json +++ b/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json @@ -5,8 +5,8 @@ "repository": "Aegis-Omega/AEGIS-OMEGA", "execution_class": "EXPERIMENT", "evidence_tier": "T1", - "expected_parent_sha": "ed426124475cc9ee8b4d138b2928ae547352e97b", - "expected_parent_state_root": "d7b8078980dbeb2b71f9df3d499e61199157ccd4f43f0dadd40bf215b8eda931", + "expected_parent_sha": "0bdffe75b56e5cd27c0632e1ba166620da327494", + "expected_parent_state_root": "3f329a67616158e776ad0a74d28d5ddedf0f94655c1cac7bbed7c72cfe8463f0", "constitution": { "path": "CONSTITUTIONAL_DECLARATION.md", "blob_id": "d0c210443e03313113e43da46c4d98269494baeb" From 595c2d0cfae8dd1f75af5a5433357cd37fbbdbe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Sat, 15 Aug 2026 21:20:51 +0200 Subject: [PATCH 58/89] fix(admission): rebind PR 225 to current canonical main --- .../pr-225-sol-cross-platform-control-plane-v1.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json b/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json index feae6b827..026611d14 100644 --- a/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json +++ b/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json @@ -5,8 +5,8 @@ "repository": "Aegis-Omega/AEGIS-OMEGA", "execution_class": "EXPERIMENT", "evidence_tier": "T1", - "expected_parent_sha": "0bdffe75b56e5cd27c0632e1ba166620da327494", - "expected_parent_state_root": "3f329a67616158e776ad0a74d28d5ddedf0f94655c1cac7bbed7c72cfe8463f0", + "expected_parent_sha": "001fcb3aa92efd18a226ac5eb5b5bd6ccd0d512a", + "expected_parent_state_root": "98a96e746e60d80d075d67cbd7759cac8c9a4c954319386eb17bc245fe9f3a1b", "constitution": { "path": "CONSTITUTIONAL_DECLARATION.md", "blob_id": "d0c210443e03313113e43da46c4d98269494baeb" From 26aea793f3440cf443980caeadebc3638be13242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Sat, 15 Aug 2026 21:21:21 +0200 Subject: [PATCH 59/89] fix(domain): carry canonical Cloudflare binding guard into PR 225 --- scripts/wire-custom-domain.sh | 229 ++++------------------------------ 1 file changed, 24 insertions(+), 205 deletions(-) diff --git a/scripts/wire-custom-domain.sh b/scripts/wire-custom-domain.sh index 88a4e7b61..e8f0f810e 100755 --- a/scripts/wire-custom-domain.sh +++ b/scripts/wire-custom-domain.sh @@ -1,221 +1,40 @@ #!/usr/bin/env bash # wire-custom-domain.sh # -# Wires aegis-vertex.aegisomega.com → aegis-vertex Cloud Run (europe-west3) -# via Global HTTPS Load Balancer + Serverless NEG. +# RETIRED / FAIL-CLOSED GUARD # -# Domain mappings are unsupported in europe-west3 — this is the correct path. +# aegis-vertex.aegisomega.com is canonically owned by the Cloudflare Worker +# declared in /wrangler.jsonc: # -# Run from Cloud Shell: -# bash scripts/wire-custom-domain.sh +# { "pattern": "aegis-vertex.aegisomega.com", "custom_domain": true } # -# After the script completes: -# 1. Add a DNS A record in Cloudflare: -# aegis-vertex.aegisomega.com → -# (or AAAA for IPv6 — the script prints both if available) -# 2. Certificate provisioning takes 10–20 min after DNS propagates. -# 3. Smoke test: curl -I https://aegis-vertex.aegisomega.com/health +# The previous version of this script provisioned a GCP HTTPS load balancer and +# instructed operators to point the same hostname at its static IP. That creates +# two competing authorities for one production hostname and can break the +# Cloudflare Worker custom-domain binding/TLS provisioning. # -# The script is idempotent — re-running skips resources that already exist. +# This file intentionally refuses to recreate or rebind the legacy GCP route. +# Existing GCP resources are NOT deleted here; deletion is a separate, +# explicitly-authorized infrastructure action. set -euo pipefail -PROJECT="aegisomegav1" -REGION="europe-west3" -SERVICE="aegis-vertex" DOMAIN="aegis-vertex.aegisomega.com" +CANONICAL_OWNER="Cloudflare Worker: aegisomega" +CANONICAL_CONFIG="wrangler.jsonc" -# Resource names (stable — never change after creation) -NEG_NAME="aegis-vertex-neg" -BACKEND_NAME="aegis-vertex-backend" -URLMAP_NAME="aegis-vertex-urlmap" -CERT_NAME="aegis-vertex-cert" -PROXY_NAME="aegis-vertex-https-proxy" -RULE_NAME="aegis-vertex-fwd-rule" -IP_NAME="aegis-vertex-ip" +cat >&2 </dev/null; then - echo " → already exists, skipping" -else - gcloud compute addresses create "$IP_NAME" \ - --global \ - --ip-version=IPV4 \ - --project "$PROJECT" - echo " → created" -fi +Reason: binding this hostname to the legacy GCP load balancer would conflict +with the production Cloudflare Worker custom domain and can reintroduce DNS/TLS +failures. -LB_IP=$(gcloud compute addresses describe "$IP_NAME" \ - --global --project "$PROJECT" --format="get(address)") -echo " → IP: $LB_IP" +No infrastructure was changed. +EOF -# ── 2. Create Serverless NEG for Cloud Run ─────────────────────────────────── -echo "[2/8] Creating Serverless NEG..." -if gcloud compute network-endpoint-groups describe "$NEG_NAME" \ - --region "$REGION" --project "$PROJECT" &>/dev/null; then - echo " → already exists, skipping" -else - gcloud compute network-endpoint-groups create "$NEG_NAME" \ - --region="$REGION" \ - --network-endpoint-type=SERVERLESS \ - --cloud-run-service="$SERVICE" \ - --project "$PROJECT" - echo " → created" -fi - -# ── 3. Create backend service ──────────────────────────────────────────────── -echo "[3/8] Creating backend service..." -if gcloud compute backend-services describe "$BACKEND_NAME" \ - --global --project "$PROJECT" &>/dev/null; then - echo " → already exists, skipping" -else - gcloud compute backend-services create "$BACKEND_NAME" \ - --global \ - --load-balancing-scheme=EXTERNAL_MANAGED \ - --project "$PROJECT" - echo " → created" -fi - -# Add NEG to backend service (idempotent check via backend list) -EXISTING_BACKEND=$(gcloud compute backend-services describe "$BACKEND_NAME" \ - --global --project "$PROJECT" --format="get(backends)" 2>/dev/null || echo "") -if echo "$EXISTING_BACKEND" | grep -q "$NEG_NAME"; then - echo " → NEG already attached, skipping" -else - gcloud compute backend-services add-backend "$BACKEND_NAME" \ - --global \ - --network-endpoint-group="$NEG_NAME" \ - --network-endpoint-group-region="$REGION" \ - --project "$PROJECT" - echo " → NEG attached" -fi - -# ── 4. Create URL map ──────────────────────────────────────────────────────── -echo "[4/8] Creating URL map..." -if gcloud compute url-maps describe "$URLMAP_NAME" \ - --global --project "$PROJECT" &>/dev/null; then - echo " → already exists, skipping" -else - gcloud compute url-maps create "$URLMAP_NAME" \ - --default-service="$BACKEND_NAME" \ - --global \ - --project "$PROJECT" - echo " → created" -fi - -# ── 5. Provision Google-managed SSL certificate ────────────────────────────── -echo "[5/8] Provisioning managed SSL certificate for $DOMAIN..." -if gcloud compute ssl-certificates describe "$CERT_NAME" \ - --global --project "$PROJECT" &>/dev/null; then - echo " → already exists, skipping" -else - gcloud compute ssl-certificates create "$CERT_NAME" \ - --domains="$DOMAIN" \ - --global \ - --project "$PROJECT" - echo " → created (will become ACTIVE once DNS points to $LB_IP)" -fi - -# ── 6. Create HTTPS target proxy ───────────────────────────────────────────── -echo "[6/8] Creating HTTPS target proxy..." -if gcloud compute target-https-proxies describe "$PROXY_NAME" \ - --global --project "$PROJECT" &>/dev/null; then - echo " → already exists, skipping" -else - gcloud compute target-https-proxies create "$PROXY_NAME" \ - --url-map="$URLMAP_NAME" \ - --ssl-certificates="$CERT_NAME" \ - --global \ - --project "$PROJECT" - echo " → created" -fi - -# ── 7. Create HTTP→HTTPS redirect ───────────────────────────────────────── -# (URL map for the redirect) -HTTP_URLMAP_NAME="aegis-vertex-http-redirect" -HTTP_PROXY_NAME="aegis-vertex-http-proxy" -HTTP_RULE_NAME="aegis-vertex-http-fwd-rule" - -echo "[7/8] Creating HTTP→HTTPS redirect..." -if ! gcloud compute url-maps describe "$HTTP_URLMAP_NAME" \ - --global --project "$PROJECT" &>/dev/null; then - gcloud compute url-maps import "$HTTP_URLMAP_NAME" \ - --global --project "$PROJECT" <<'YAML' -name: aegis-vertex-http-redirect -defaultUrlRedirect: - redirectResponseCode: MOVED_PERMANENTLY_DEFAULT - httpsRedirect: true -YAML - echo " → HTTP redirect URL map created" -else - echo " → HTTP redirect URL map already exists, skipping" -fi - -if ! gcloud compute target-http-proxies describe "$HTTP_PROXY_NAME" \ - --global --project "$PROJECT" &>/dev/null; then - gcloud compute target-http-proxies create "$HTTP_PROXY_NAME" \ - --url-map="$HTTP_URLMAP_NAME" \ - --global \ - --project "$PROJECT" - echo " → HTTP proxy created" -else - echo " → HTTP proxy already exists, skipping" -fi - -if ! gcloud compute forwarding-rules describe "$HTTP_RULE_NAME" \ - --global --project "$PROJECT" &>/dev/null; then - gcloud compute forwarding-rules create "$HTTP_RULE_NAME" \ - --address="$LB_IP" \ - --global \ - --target-http-proxy="$HTTP_PROXY_NAME" \ - --ports=80 \ - --load-balancing-scheme=EXTERNAL_MANAGED \ - --project "$PROJECT" - echo " → HTTP forwarding rule created" -else - echo " → HTTP forwarding rule already exists, skipping" -fi - -# ── 8. Create HTTPS forwarding rule ───────────────────────────────────────── -echo "[8/8] Creating HTTPS forwarding rule..." -if gcloud compute forwarding-rules describe "$RULE_NAME" \ - --global --project "$PROJECT" &>/dev/null; then - echo " → already exists, skipping" -else - gcloud compute forwarding-rules create "$RULE_NAME" \ - --address="$LB_IP" \ - --global \ - --target-https-proxy="$PROXY_NAME" \ - --ports=443 \ - --load-balancing-scheme=EXTERNAL_MANAGED \ - --project "$PROJECT" - echo " → created" -fi - -echo "" -echo "═══════════════════════════════════════════════════════" -echo " Load balancer ready." -echo "" -echo " NEXT: Add this DNS record in Cloudflare (proxied OFF):" -echo "" -echo " Type : A" -echo " Name : aegis-vertex" -echo " Value: $LB_IP" -echo " TTL : Auto" -echo " Proxy: DNS only (grey cloud)" -echo "" -echo " Certificate provisioning starts once DNS propagates." -echo " Check status in ~15 min:" -echo " gcloud compute ssl-certificates describe $CERT_NAME --global --project $PROJECT" -echo "" -echo " Smoke test (after cert goes ACTIVE):" -echo " curl -I https://$DOMAIN/health" -echo "═══════════════════════════════════════════════════════" +exit 64 From a8ecfa4a8857abdcff75aab1e7ce9c3d9485f45f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:15:38 +0200 Subject: [PATCH 60/89] test(organism): define persistent company-loop contract --- agents/tests/test_organism_loop.py | 130 +++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 agents/tests/test_organism_loop.py diff --git a/agents/tests/test_organism_loop.py b/agents/tests/test_organism_loop.py new file mode 100644 index 000000000..a245c881d --- /dev/null +++ b/agents/tests/test_organism_loop.py @@ -0,0 +1,130 @@ +from __future__ import annotations + +import asyncio +import json +import tempfile +import unittest +from pathlib import Path +from types import SimpleNamespace + +from agents.organism import OrganismStore, OrganizationOrganism, WorkStatus + + +class FakeDispatcher: + def __init__(self, *, valid: bool = True, raises: bool = False): + self.valid = valid + self.raises = raises + self.calls: list[tuple[str, dict]] = [] + + async def __call__(self, event_type: str, payload: dict): + self.calls.append((event_type, payload)) + if self.raises: + raise RuntimeError("dispatcher boom") + return [SimpleNamespace(is_valid=self.valid, role=SimpleNamespace(value="engineering"))] + + +class OrganismLoopTests(unittest.TestCase): + def make_org(self, dispatcher=None): + td = tempfile.TemporaryDirectory() + self.addCleanup(td.cleanup) + path = Path(td.name) / "organism.json" + store = OrganismStore(path) + return path, OrganizationOrganism(store, dispatcher=dispatcher or FakeDispatcher()) + + def test_submit_persists_and_survives_restart(self): + path, org = self.make_org() + org.submit("w1", "github_issue_opened", {"number": 1}, consequence_class="D1") + restarted = OrganizationOrganism(OrganismStore(path), dispatcher=FakeDispatcher()) + self.assertEqual(restarted.get("w1").status, WorkStatus.QUEUED) + + def test_duplicate_work_id_is_idempotent(self): + _, org = self.make_org() + first = org.submit("w1", "market_opportunity", {"x": 1}, consequence_class="D1") + second = org.submit("w1", "market_opportunity", {"x": 999}, consequence_class="D1") + self.assertEqual(first.to_dict(), second.to_dict()) + self.assertEqual(len(org.orders()), 1) + + def test_d1_runs_without_operator_and_records_execution(self): + dispatcher = FakeDispatcher(valid=True) + _, org = self.make_org(dispatcher) + org.submit("w1", "market_opportunity", {"market": "agentic-ai"}, consequence_class="D1") + result = asyncio.run(org.tick()) + self.assertEqual(result.status, WorkStatus.EXECUTED) + self.assertEqual(len(dispatcher.calls), 1) + self.assertEqual(org.operator_inbox(), []) + + def test_d3_waits_for_operator_without_dispatch(self): + dispatcher = FakeDispatcher() + _, org = self.make_org(dispatcher) + order = org.submit("w1", "deployment_event", {"service": "prod"}, consequence_class="D3") + self.assertEqual(order.status, WorkStatus.WAITING_OPERATOR) + self.assertIsNone(asyncio.run(org.tick())) + self.assertEqual(dispatcher.calls, []) + self.assertEqual([w.work_id for w in org.operator_inbox()], ["w1"]) + + def test_d3_approval_requeues_then_executes(self): + dispatcher = FakeDispatcher(valid=True) + _, org = self.make_org(dispatcher) + org.submit("w1", "deployment_event", {"service": "prod"}, consequence_class="D3") + approved = org.approve("w1", approval_ref="operator:explicit-approval-001") + self.assertTrue(approved) + self.assertEqual(org.get("w1").status, WorkStatus.QUEUED) + result = asyncio.run(org.tick()) + self.assertEqual(result.status, WorkStatus.EXECUTED) + self.assertEqual(len(dispatcher.calls), 1) + + def test_d4_is_denied_and_cannot_be_approved(self): + dispatcher = FakeDispatcher() + _, org = self.make_org(dispatcher) + order = org.submit("w1", "unknown_high_consequence", {}, consequence_class="D4") + self.assertEqual(order.status, WorkStatus.DENIED) + self.assertFalse(org.approve("w1", approval_ref="operator:anything")) + self.assertEqual(dispatcher.calls, []) + + def test_dispatch_exception_retries_bounded_then_fails(self): + dispatcher = FakeDispatcher(raises=True) + _, org = self.make_org(dispatcher) + org.submit("w1", "github_ci_failure", {"branch": "x"}, consequence_class="D1", max_attempts=2) + first = asyncio.run(org.tick()) + self.assertEqual(first.status, WorkStatus.QUEUED) + second = asyncio.run(org.tick()) + self.assertEqual(second.status, WorkStatus.FAILED) + self.assertEqual(len(dispatcher.calls), 2) + + def test_invalid_agent_result_never_becomes_executed(self): + dispatcher = FakeDispatcher(valid=False) + _, org = self.make_org(dispatcher) + org.submit("w1", "github_pr_opened", {"number": 2}, consequence_class="D1") + result = asyncio.run(org.tick()) + self.assertEqual(result.status, WorkStatus.FAILED) + + def test_empty_dispatch_is_blocked_authority_not_success(self): + async def empty_dispatch(_event_type, _payload): + return [] + + _, org = self.make_org(empty_dispatch) + org.submit("w1", "github_pr_opened", {"number": 3}, consequence_class="D1") + result = asyncio.run(org.tick()) + self.assertEqual(result.status, WorkStatus.BLOCKED_AUTHORITY) + + def test_hash_chain_tamper_fails_closed(self): + path, org = self.make_org() + org.submit("w1", "market_opportunity", {"x": 1}, consequence_class="D1") + state = json.loads(path.read_text()) + state["journal"][0]["event_type"] = "TAMPERED" + path.write_text(json.dumps(state)) + with self.assertRaises(ValueError): + OrganismStore(path) + + def test_run_until_idle_processes_fifo(self): + dispatcher = FakeDispatcher(valid=True) + _, org = self.make_org(dispatcher) + org.submit("w1", "github_issue_opened", {"number": 1}, consequence_class="D1") + org.submit("w2", "github_issue_opened", {"number": 2}, consequence_class="D1") + done = asyncio.run(org.run_until_idle(max_ticks=10)) + self.assertEqual([w.work_id for w in done], ["w1", "w2"]) + self.assertEqual([p[1]["number"] for p in dispatcher.calls], [1, 2]) + + +if __name__ == "__main__": + unittest.main() From 8d6dc2fd1eb007239dff562706264ec83f54c70d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:18:29 +0200 Subject: [PATCH 61/89] feat(organism): add durable governed company loop --- agents/organism.py | 313 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 agents/organism.py diff --git a/agents/organism.py b/agents/organism.py new file mode 100644 index 000000000..59c9395fe --- /dev/null +++ b/agents/organism.py @@ -0,0 +1,313 @@ +"""Durable, fail-closed organizational work loop for AEGIS Ω. + +This module is intentionally small: it turns the existing governed coordinator +into a persistent company loop. It does not grant new authority. D1/D2 work may +enter the existing Automaton-3-gated dispatcher; D3 waits for explicit operator +approval; D4 is denied. State is persisted with a hash-chained journal so a +restart cannot silently forget or rewrite prior work. +""" +from __future__ import annotations + +import asyncio +import hashlib +import json +import os +import tempfile +import time +from dataclasses import asdict, dataclass +from enum import Enum +from pathlib import Path +from typing import Any, Awaitable, Callable, Iterable + + +GENESIS = "0" * 64 +STORE_VERSION = "AEGIS_ORGANISM_STORE_V1" +JOURNAL_DOMAIN = "AEGIS_ORGANISM_JOURNAL_V1" + + +class WorkStatus(str, Enum): + QUEUED = "QUEUED" + WAITING_OPERATOR = "WAITING_OPERATOR" + RUNNING = "RUNNING" + EXECUTED = "EXECUTED" + BLOCKED_AUTHORITY = "BLOCKED_AUTHORITY" + FAILED = "FAILED" + DENIED = "DENIED" + + +TERMINAL = {WorkStatus.EXECUTED, WorkStatus.BLOCKED_AUTHORITY, WorkStatus.FAILED, WorkStatus.DENIED} + + +@dataclass +class WorkOrder: + work_id: str + event_type: str + payload: dict[str, Any] + consequence_class: str + status: WorkStatus + max_attempts: int = 3 + attempts: int = 0 + created_ms: int = 0 + updated_ms: int = 0 + approval_ref: str | None = None + last_error: str | None = None + contribution_refs: tuple[str, ...] = () + + def to_dict(self) -> dict[str, Any]: + d = asdict(self) + d["status"] = self.status.value + d["contribution_refs"] = list(self.contribution_refs) + return d + + @classmethod + def from_dict(cls, d: dict[str, Any]) -> "WorkOrder": + x = dict(d) + x["status"] = WorkStatus(x["status"]) + x["contribution_refs"] = tuple(x.get("contribution_refs", ())) + return cls(**x) + + +Dispatcher = Callable[[str, dict[str, Any]], Awaitable[Iterable[Any]]] + + +def _canonical(value: Any) -> bytes: + return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8") + + +def _hash_event(prev: str, seq: int, event_type: str, body: dict[str, Any]) -> str: + h = hashlib.sha256() + h.update(JOURNAL_DOMAIN.encode("ascii")) + h.update(b"\0") + h.update(prev.encode("ascii")) + h.update(b"\0") + h.update(str(seq).encode("ascii")) + h.update(b"\0") + h.update(event_type.encode("utf-8")) + h.update(b"\0") + h.update(_canonical(body)) + return h.hexdigest() + + +class OrganismStore: + def __init__(self, path: str | Path): + self.path = Path(path) + if self.path.exists(): + self._state = json.loads(self.path.read_text(encoding="utf-8")) + self._validate() + else: + self._state = {"store_version": STORE_VERSION, "orders": {}, "journal": []} + self._persist() + + def _validate(self) -> None: + if self._state.get("store_version") != STORE_VERSION: + raise ValueError("ORGANISM_STORE_VERSION_MISMATCH") + prev = GENESIS + for seq, entry in enumerate(self._state.get("journal", [])): + if entry.get("seq") != seq: + raise ValueError("ORGANISM_JOURNAL_SEQUENCE_MISMATCH") + expected = _hash_event(prev, seq, entry.get("event_type", ""), entry.get("body", {})) + if entry.get("prev_hash") != prev or entry.get("event_hash") != expected: + raise ValueError("ORGANISM_JOURNAL_TAMPER_DETECTED") + prev = expected + + def _persist(self) -> None: + self.path.parent.mkdir(parents=True, exist_ok=True) + data = json.dumps(self._state, sort_keys=True, indent=2, ensure_ascii=False) + fd, tmp = tempfile.mkstemp(prefix=self.path.name + ".", suffix=".tmp", dir=str(self.path.parent)) + try: + with os.fdopen(fd, "w", encoding="utf-8") as f: + f.write(data) + f.flush() + os.fsync(f.fileno()) + os.replace(tmp, self.path) + finally: + if os.path.exists(tmp): + os.unlink(tmp) + + def _append(self, event_type: str, body: dict[str, Any]) -> None: + journal = self._state["journal"] + prev = journal[-1]["event_hash"] if journal else GENESIS + seq = len(journal) + event_hash = _hash_event(prev, seq, event_type, body) + journal.append({"seq": seq, "event_type": event_type, "body": body, "prev_hash": prev, "event_hash": event_hash}) + + def save_order(self, order: WorkOrder, event_type: str) -> None: + body = order.to_dict() + self._state["orders"][order.work_id] = body + self._append(event_type, body) + self._persist() + + def get(self, work_id: str) -> WorkOrder | None: + raw = self._state["orders"].get(work_id) + return WorkOrder.from_dict(raw) if raw else None + + def orders(self) -> list[WorkOrder]: + return [WorkOrder.from_dict(x) for x in self._state["orders"].values()] + + def journal(self) -> list[dict[str, Any]]: + return list(self._state["journal"]) + + +class OrganizationOrganism: + def __init__(self, store: OrganismStore, dispatcher: Dispatcher | None = None): + self.store = store + self.dispatcher = dispatcher or self._default_dispatcher + + @staticmethod + async def _default_dispatcher(event_type: str, payload: dict[str, Any]): + from agents.coordinator import dispatch_event + return await dispatch_event(event_type, payload) + + def orders(self) -> list[WorkOrder]: + return sorted(self.store.orders(), key=lambda w: (w.created_ms, w.work_id)) + + def get(self, work_id: str) -> WorkOrder: + order = self.store.get(work_id) + if order is None: + raise KeyError(work_id) + return order + + def submit( + self, + work_id: str, + event_type: str, + payload: dict[str, Any], + *, + consequence_class: str, + max_attempts: int = 3, + ) -> WorkOrder: + existing = self.store.get(work_id) + if existing is not None: + return existing + if not work_id or not event_type: + raise ValueError("WORK_ID_AND_EVENT_REQUIRED") + if max_attempts < 1: + raise ValueError("MAX_ATTEMPTS_INVALID") + cc = consequence_class.upper() + now = int(time.time() * 1000) + if cc in {"D0", "D1", "D2"}: + status = WorkStatus.QUEUED + elif cc == "D3": + status = WorkStatus.WAITING_OPERATOR + else: + status = WorkStatus.DENIED + order = WorkOrder(work_id, event_type, dict(payload), cc, status, max_attempts=max_attempts, created_ms=now, updated_ms=now) + self.store.save_order(order, "WORK_SUBMITTED") + return order + + def operator_inbox(self) -> list[WorkOrder]: + return [w for w in self.orders() if w.status == WorkStatus.WAITING_OPERATOR] + + def approve(self, work_id: str, *, approval_ref: str) -> bool: + order = self.get(work_id) + if order.consequence_class != "D3" or order.status != WorkStatus.WAITING_OPERATOR: + return False + if not approval_ref: + return False + order.approval_ref = approval_ref + order.status = WorkStatus.QUEUED + order.updated_ms = int(time.time() * 1000) + self.store.save_order(order, "OPERATOR_APPROVED") + return True + + def _next_queued(self) -> WorkOrder | None: + for order in self.orders(): + if order.status == WorkStatus.QUEUED: + return order + return None + + async def tick(self) -> WorkOrder | None: + order = self._next_queued() + if order is None: + return None + if order.consequence_class == "D3" and not order.approval_ref: + order.status = WorkStatus.WAITING_OPERATOR + order.updated_ms = int(time.time() * 1000) + self.store.save_order(order, "OPERATOR_APPROVAL_REQUIRED") + return order + if order.consequence_class == "D4": + order.status = WorkStatus.DENIED + order.updated_ms = int(time.time() * 1000) + self.store.save_order(order, "WORK_DENIED") + return order + + order.status = WorkStatus.RUNNING + order.attempts += 1 + order.updated_ms = int(time.time() * 1000) + self.store.save_order(order, "WORK_DISPATCH_STARTED") + try: + results = list(await self.dispatcher(order.event_type, dict(order.payload))) + if not results: + order.status = WorkStatus.BLOCKED_AUTHORITY + order.last_error = "NO_ADMITTED_DISPATCH_RESULT" + elif all(bool(getattr(r, "is_valid", False)) for r in results): + order.status = WorkStatus.EXECUTED + refs: list[str] = [] + for r in results: + role = getattr(getattr(r, "role", None), "value", str(getattr(r, "role", "unknown"))) + refs.append(f"agent:{role}:task:{getattr(r, 'task_id', order.work_id)}") + order.contribution_refs = tuple(refs) + order.last_error = None + else: + order.status = WorkStatus.FAILED + order.last_error = "INVALID_AGENT_RESULT" + except Exception as exc: # fail closed; bounded retry below + order.last_error = f"DISPATCH_ERROR:{type(exc).__name__}:{exc}" + order.status = WorkStatus.QUEUED if order.attempts < order.max_attempts else WorkStatus.FAILED + + order.updated_ms = int(time.time() * 1000) + self.store.save_order(order, "WORK_DISPATCH_RESULT") + return order + + async def run_until_idle(self, *, max_ticks: int = 100) -> list[WorkOrder]: + completed: list[WorkOrder] = [] + for _ in range(max_ticks): + result = await self.tick() + if result is None: + break + if result.status in TERMINAL: + completed.append(result) + return completed + + +def default_store_path() -> Path: + return Path(os.environ.get("AEGIS_ORGANISM_STORE", ".aegis/runtime/organism.json")) + + +def main() -> None: + import argparse + parser = argparse.ArgumentParser(description="AEGIS Ω durable organization organism") + sub = parser.add_subparsers(dest="command", required=True) + p_submit = sub.add_parser("submit") + p_submit.add_argument("--id", required=True) + p_submit.add_argument("--event", required=True) + p_submit.add_argument("--payload", default="{}") + p_submit.add_argument("--consequence", default="D1") + p_tick = sub.add_parser("tick") + p_run = sub.add_parser("run") + p_run.add_argument("--max-ticks", type=int, default=100) + p_inbox = sub.add_parser("inbox") + p_approve = sub.add_parser("approve") + p_approve.add_argument("--id", required=True) + p_approve.add_argument("--approval-ref", required=True) + sub.add_parser("status") + args = parser.parse_args() + + org = OrganizationOrganism(OrganismStore(default_store_path())) + if args.command == "submit": + print(json.dumps(org.submit(args.id, args.event, json.loads(args.payload), consequence_class=args.consequence).to_dict(), sort_keys=True)) + elif args.command == "tick": + result = asyncio.run(org.tick()) + print(json.dumps(result.to_dict() if result else {"status": "IDLE"}, sort_keys=True)) + elif args.command == "run": + print(json.dumps([w.to_dict() for w in asyncio.run(org.run_until_idle(max_ticks=args.max_ticks))], sort_keys=True)) + elif args.command == "inbox": + print(json.dumps([w.to_dict() for w in org.operator_inbox()], sort_keys=True)) + elif args.command == "approve": + print(json.dumps({"approved": org.approve(args.id, approval_ref=args.approval_ref)}, sort_keys=True)) + elif args.command == "status": + print(json.dumps({"orders": [w.to_dict() for w in org.orders()], "journal_length": len(org.store.journal())}, sort_keys=True)) + + +if __name__ == "__main__": + main() From fe847d3befa5b5f9698a6a29a2012238d3143bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:22:53 +0200 Subject: [PATCH 62/89] test(organism): define provider contribution contract --- agents/tests/test_provider_contribution.py | 67 ++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 agents/tests/test_provider_contribution.py diff --git a/agents/tests/test_provider_contribution.py b/agents/tests/test_provider_contribution.py new file mode 100644 index 000000000..796ce6cca --- /dev/null +++ b/agents/tests/test_provider_contribution.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +import hashlib +import tempfile +import unittest +from pathlib import Path + +from agents.organism import OrganismStore, OrganizationOrganism, WorkStatus + + +class ProviderContributionTests(unittest.TestCase): + def make_org(self): + td = tempfile.TemporaryDirectory() + self.addCleanup(td.cleanup) + return OrganizationOrganism(OrganismStore(Path(td.name) / "organism.json")) + + def test_provider_can_record_digest_on_existing_work_without_authority_promotion(self): + org = self.make_org() + org.submit("w1", "research_request", {"topic": "effect verification"}, consequence_class="D1") + digest = hashlib.sha256(b"provider-output").hexdigest() + ref = org.record_contribution("w1", provider="openai", model="gpt-5.6-sol", artifact_digest=digest, source_ref="mcp:openai") + order = org.get("w1") + self.assertEqual(order.status, WorkStatus.QUEUED) + self.assertEqual(order.contribution_refs, (ref,)) + self.assertIn(digest, ref) + + def test_same_contribution_is_idempotent(self): + org = self.make_org() + org.submit("w1", "research_request", {}, consequence_class="D1") + digest = hashlib.sha256(b"x").hexdigest() + first = org.record_contribution("w1", provider="gemini", model="gemini-3", artifact_digest=digest, source_ref="mcp:gemini") + second = org.record_contribution("w1", provider="gemini", model="gemini-3", artifact_digest=digest, source_ref="mcp:gemini") + self.assertEqual(first, second) + self.assertEqual(org.get("w1").contribution_refs, (first,)) + + def test_unknown_work_fails_closed(self): + org = self.make_org() + digest = hashlib.sha256(b"x").hexdigest() + with self.assertRaises(KeyError): + org.record_contribution("missing", provider="claude", model="opus", artifact_digest=digest, source_ref="mcp:claude") + + def test_invalid_digest_is_rejected(self): + org = self.make_org() + org.submit("w1", "research_request", {}, consequence_class="D1") + with self.assertRaises(ValueError): + org.record_contribution("w1", provider="deepseek", model="r1", artifact_digest="abc", source_ref="mcp:deepseek") + + def test_provider_identity_fields_are_bounded(self): + org = self.make_org() + org.submit("w1", "research_request", {}, consequence_class="D1") + digest = hashlib.sha256(b"x").hexdigest() + with self.assertRaises(ValueError): + org.record_contribution("w1", provider="bad provider\n", model="m", artifact_digest=digest, source_ref="mcp:x") + + def test_journal_contains_non_authoritative_contribution_event(self): + org = self.make_org() + org.submit("w1", "research_request", {}, consequence_class="D1") + digest = hashlib.sha256(b"x").hexdigest() + org.record_contribution("w1", provider="local", model="llama", artifact_digest=digest, source_ref="mcp:local") + contribution = [e for e in org.store.journal() if e["event_type"] == "PROVIDER_CONTRIBUTION_RECORDED"] + self.assertEqual(len(contribution), 1) + self.assertEqual(contribution[0]["body"]["authority"], "NON_AUTHORITATIVE_EVIDENCE") + self.assertEqual(org.get("w1").status, WorkStatus.QUEUED) + + +if __name__ == "__main__": + unittest.main() From 983dde28d5e0461ebbe776649a56b0317f8331f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:24:18 +0200 Subject: [PATCH 63/89] feat(organism): record cross-provider contributions --- agents/organism.py | 78 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/agents/organism.py b/agents/organism.py index 59c9395fe..a38f75373 100644 --- a/agents/organism.py +++ b/agents/organism.py @@ -1,10 +1,10 @@ """Durable, fail-closed organizational work loop for AEGIS Ω. -This module is intentionally small: it turns the existing governed coordinator -into a persistent company loop. It does not grant new authority. D1/D2 work may -enter the existing Automaton-3-gated dispatcher; D3 waits for explicit operator -approval; D4 is denied. State is persisted with a hash-chained journal so a -restart cannot silently forget or rewrite prior work. +Turns the existing governed coordinator into a persistent company loop. It does +not grant new authority. D1/D2 work may enter the existing Automaton-3-gated +dispatcher; D3 waits for explicit operator approval; D4 is denied. Provider +contributions are recorded as non-authoritative evidence and cannot promote a +work order by themselves. State is persisted with a hash-chained journal. """ from __future__ import annotations @@ -12,6 +12,7 @@ import hashlib import json import os +import re import tempfile import time from dataclasses import asdict, dataclass @@ -19,10 +20,11 @@ from pathlib import Path from typing import Any, Awaitable, Callable, Iterable - GENESIS = "0" * 64 STORE_VERSION = "AEGIS_ORGANISM_STORE_V1" JOURNAL_DOMAIN = "AEGIS_ORGANISM_JOURNAL_V1" +_IDENTITY_RE = re.compile(r"^[A-Za-z0-9._:/@+\-]{1,128}$") +_SHA256_RE = re.compile(r"^[0-9a-f]{64}$") class WorkStatus(str, Enum): @@ -131,10 +133,9 @@ def _append(self, event_type: str, body: dict[str, Any]) -> None: event_hash = _hash_event(prev, seq, event_type, body) journal.append({"seq": seq, "event_type": event_type, "body": body, "prev_hash": prev, "event_hash": event_hash}) - def save_order(self, order: WorkOrder, event_type: str) -> None: - body = order.to_dict() - self._state["orders"][order.work_id] = body - self._append(event_type, body) + def save_order(self, order: WorkOrder, event_type: str, *, event_body: dict[str, Any] | None = None) -> None: + self._state["orders"][order.work_id] = order.to_dict() + self._append(event_type, event_body if event_body is not None else order.to_dict()) self._persist() def get(self, work_id: str) -> WorkOrder | None: @@ -167,15 +168,7 @@ def get(self, work_id: str) -> WorkOrder: raise KeyError(work_id) return order - def submit( - self, - work_id: str, - event_type: str, - payload: dict[str, Any], - *, - consequence_class: str, - max_attempts: int = 3, - ) -> WorkOrder: + def submit(self, work_id: str, event_type: str, payload: dict[str, Any], *, consequence_class: str, max_attempts: int = 3) -> WorkOrder: existing = self.store.get(work_id) if existing is not None: return existing @@ -195,6 +188,34 @@ def submit( self.store.save_order(order, "WORK_SUBMITTED") return order + def record_contribution(self, work_id: str, *, provider: str, model: str, artifact_digest: str, source_ref: str) -> str: + order = self.get(work_id) + for value, code in ((provider, "PROVIDER_ID_INVALID"), (model, "MODEL_ID_INVALID"), (source_ref, "SOURCE_REF_INVALID")): + if not _IDENTITY_RE.fullmatch(value): + raise ValueError(code) + if not _SHA256_RE.fullmatch(artifact_digest): + raise ValueError("ARTIFACT_DIGEST_INVALID") + contribution_ref = f"provider:{provider}:model:{model}:sha256:{artifact_digest}:source:{source_ref}" + if contribution_ref in order.contribution_refs: + return contribution_ref + order.contribution_refs = (*order.contribution_refs, contribution_ref) + order.updated_ms = int(time.time() * 1000) + self.store.save_order( + order, + "PROVIDER_CONTRIBUTION_RECORDED", + event_body={ + "work_id": work_id, + "provider": provider, + "model": model, + "artifact_digest": artifact_digest, + "source_ref": source_ref, + "contribution_ref": contribution_ref, + "authority": "NON_AUTHORITATIVE_EVIDENCE", + "status_after": order.status.value, + }, + ) + return contribution_ref + def operator_inbox(self) -> list[WorkOrder]: return [w for w in self.orders() if w.status == WorkStatus.WAITING_OPERATOR] @@ -242,16 +263,16 @@ async def tick(self) -> WorkOrder | None: order.last_error = "NO_ADMITTED_DISPATCH_RESULT" elif all(bool(getattr(r, "is_valid", False)) for r in results): order.status = WorkStatus.EXECUTED - refs: list[str] = [] + refs: list[str] = list(order.contribution_refs) for r in results: role = getattr(getattr(r, "role", None), "value", str(getattr(r, "role", "unknown"))) refs.append(f"agent:{role}:task:{getattr(r, 'task_id', order.work_id)}") - order.contribution_refs = tuple(refs) + order.contribution_refs = tuple(dict.fromkeys(refs)) order.last_error = None else: order.status = WorkStatus.FAILED order.last_error = "INVALID_AGENT_RESULT" - except Exception as exc: # fail closed; bounded retry below + except Exception as exc: order.last_error = f"DISPATCH_ERROR:{type(exc).__name__}:{exc}" order.status = WorkStatus.QUEUED if order.attempts < order.max_attempts else WorkStatus.FAILED @@ -283,13 +304,19 @@ def main() -> None: p_submit.add_argument("--event", required=True) p_submit.add_argument("--payload", default="{}") p_submit.add_argument("--consequence", default="D1") - p_tick = sub.add_parser("tick") + sub.add_parser("tick") p_run = sub.add_parser("run") p_run.add_argument("--max-ticks", type=int, default=100) - p_inbox = sub.add_parser("inbox") + sub.add_parser("inbox") p_approve = sub.add_parser("approve") p_approve.add_argument("--id", required=True) p_approve.add_argument("--approval-ref", required=True) + p_contrib = sub.add_parser("contribute") + p_contrib.add_argument("--id", required=True) + p_contrib.add_argument("--provider", required=True) + p_contrib.add_argument("--model", required=True) + p_contrib.add_argument("--artifact-digest", required=True) + p_contrib.add_argument("--source-ref", required=True) sub.add_parser("status") args = parser.parse_args() @@ -305,6 +332,9 @@ def main() -> None: print(json.dumps([w.to_dict() for w in org.operator_inbox()], sort_keys=True)) elif args.command == "approve": print(json.dumps({"approved": org.approve(args.id, approval_ref=args.approval_ref)}, sort_keys=True)) + elif args.command == "contribute": + ref = org.record_contribution(args.id, provider=args.provider, model=args.model, artifact_digest=args.artifact_digest, source_ref=args.source_ref) + print(json.dumps({"contribution_ref": ref, "authority": "NON_AUTHORITATIVE_EVIDENCE", "work": org.get(args.id).to_dict()}, sort_keys=True)) elif args.command == "status": print(json.dumps({"orders": [w.to_dict() for w in org.orders()], "journal_length": len(org.store.journal())}, sort_keys=True)) From 24757bc306fd510ca76dd429bdeb446da2f32e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:25:01 +0200 Subject: [PATCH 64/89] feat(mcp): add durable organism contribution client --- .../mcp-server/src/organism-client.ts | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 sovereign-omega-v2/mcp-server/src/organism-client.ts diff --git a/sovereign-omega-v2/mcp-server/src/organism-client.ts b/sovereign-omega-v2/mcp-server/src/organism-client.ts new file mode 100644 index 000000000..63aa93249 --- /dev/null +++ b/sovereign-omega-v2/mcp-server/src/organism-client.ts @@ -0,0 +1,79 @@ +import { spawnSync } from 'node:child_process' + +const IDENTITY_RE = /^[A-Za-z0-9._:/@+\-]{1,128}$/ +const SHA256_RE = /^[0-9a-f]{64}$/ + +export type ProviderContributionInput = { + workId: string + provider: string + model: string + artifactDigest: string + sourceRef: string +} + +export class OrganismClientError extends Error { + constructor(public readonly code: string, message?: string) { + super(message ?? code) + this.name = 'OrganismClientError' + } +} + +function boundedIdentity(value: string, code: string): void { + if (!IDENTITY_RE.test(value)) throw new OrganismClientError(code) +} + +export function recordProviderContribution(root: string, input: ProviderContributionInput): Record { + boundedIdentity(input.workId, 'WORK_ID_INVALID') + boundedIdentity(input.provider, 'PROVIDER_ID_INVALID') + boundedIdentity(input.model, 'MODEL_ID_INVALID') + boundedIdentity(input.sourceRef, 'SOURCE_REF_INVALID') + if (!SHA256_RE.test(input.artifactDigest)) throw new OrganismClientError('ARTIFACT_DIGEST_INVALID') + + const python = process.env['AEGIS_PYTHON'] ?? 'python3' + const result = spawnSync( + python, + [ + '-m', 'agents.organism', 'contribute', + '--id', input.workId, + '--provider', input.provider, + '--model', input.model, + '--artifact-digest', input.artifactDigest, + '--source-ref', input.sourceRef, + ], + { cwd: root, env: process.env, encoding: 'utf8', timeout: 10_000, maxBuffer: 1_048_576 }, + ) + if (result.error || result.signal || result.status !== 0) { + throw new OrganismClientError('ORGANISM_CONTRIBUTION_FAILED', result.stderr || result.error?.message) + } + let parsed: unknown + try { parsed = JSON.parse(result.stdout) } catch { throw new OrganismClientError('ORGANISM_RESPONSE_MALFORMED') } + if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { + throw new OrganismClientError('ORGANISM_RESPONSE_MALFORMED') + } + const record = parsed as Record + if (record['authority'] !== 'NON_AUTHORITATIVE_EVIDENCE') { + throw new OrganismClientError('ORGANISM_AUTHORITY_BOUNDARY_VIOLATION') + } + const ref = record['contribution_ref'] + if (typeof ref !== 'string' || !ref.includes(input.artifactDigest)) { + throw new OrganismClientError('ORGANISM_CONTRIBUTION_BINDING_MISMATCH') + } + return record +} + +export function readOrganismStatus(root: string): Record { + const python = process.env['AEGIS_PYTHON'] ?? 'python3' + const result = spawnSync(python, ['-m', 'agents.organism', 'status'], { + cwd: root, env: process.env, encoding: 'utf8', timeout: 10_000, maxBuffer: 1_048_576, + }) + if (result.error || result.signal || result.status !== 0) { + throw new OrganismClientError('ORGANISM_STATUS_FAILED', result.stderr || result.error?.message) + } + try { + const parsed: unknown = JSON.parse(result.stdout) + if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) throw new Error('shape') + return parsed as Record + } catch { + throw new OrganismClientError('ORGANISM_RESPONSE_MALFORMED') + } +} From 4dd061cb3d0b06771da51c7012a380fc0c88677a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:26:12 +0200 Subject: [PATCH 65/89] feat(mcp): expose organism status and provider contributions --- sovereign-omega-v2/mcp-server/src/index.ts | 58 ++++++++++++++-------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/sovereign-omega-v2/mcp-server/src/index.ts b/sovereign-omega-v2/mcp-server/src/index.ts index 26d456d16..ea7dece78 100644 --- a/sovereign-omega-v2/mcp-server/src/index.ts +++ b/sovereign-omega-v2/mcp-server/src/index.ts @@ -19,11 +19,12 @@ import { type ActionClass, type VerifiedAuthorityDecision, } from './authority-response.js' +import { OrganismClientError, readOrganismStatus, recordProviderContribution } from './organism-client.js' const BRIDGE = (process.env['AEGIS_BRIDGE_URL'] ?? 'http://localhost:7890').replace(/\/$/, '') const API_KEY = process.env['AEGIS_API_KEY'] ?? '' -const server = new McpServer({ name: 'aegis-constitutional-swarm', version: '0.2.0' }) +const server = new McpServer({ name: 'aegis-constitutional-swarm', version: '0.3.0' }) async function bridgeGet(path: string, apiKey = false): Promise { const headers: Record = { 'Content-Type': 'application/json' } @@ -109,13 +110,7 @@ function authorizeAction(input: { } let bindings try { - bindings = buildAuthorityResponseBindings( - identity, - input, - boundWorkspace, - root, - trustedAuthorityKeys, - ) + bindings = buildAuthorityResponseBindings(identity, input, boundWorkspace, root, trustedAuthorityKeys) } catch (error) { return localDenial(error instanceof AuthorityResponseError ? error.code : 'EXECUTION_IDENTITY_MALFORMED') } @@ -149,13 +144,7 @@ function authorizeAction(input: { env: process.env, timeout: 15_000, maxBuffer: 1_048_576, }) try { - return parseAuthorityProcessResult({ - status: result.status, - signal: result.signal, - stdout: result.stdout, - stderr: result.stderr, - error: result.error, - }, bindings) + return parseAuthorityProcessResult({ status: result.status, signal: result.signal, stdout: result.stdout, stderr: result.stderr, error: result.error }, bindings) } catch (error) { return localDenial(error instanceof AuthorityResponseError ? error.code : 'AUTHORITY_RESPONSE_MALFORMED') } @@ -170,12 +159,7 @@ function denialResponse(decision: DeniedAuthorityDecision): { content: Array<{ t } function terminalAdapterUnavailable(authority: VerifiedAuthorityDecision) { - return text({ - authority, - outcome: 'DENIED', - denial_codes: ['TERMINAL_EXECUTION_ADAPTER_UNAVAILABLE'], - external_effect: 'NOT_EXECUTED', - }) + return text({ authority, outcome: 'DENIED', denial_codes: ['TERMINAL_EXECUTION_ADAPTER_UNAVAILABLE'], external_effect: 'NOT_EXECUTED' }) } server.tool('aegis_health', 'Check AEGIS constitutional health: t0_verdict, corruption_count, hash chain status.', {}, async () => { @@ -192,6 +176,38 @@ server.tool('aegis_platform_status', 'Get AEGIS platform status through a D0 aut return text({ authority, result: await bridgeGet('/platform/status', true) }) }) +server.tool('aegis_organism_status', 'Read the durable AEGIS organization work ledger. Read-only; provider outputs remain non-authoritative.', {}, async () => { + const authority = authorizeAction({ actionClass: 'D0', authorityDomain: 'organism:read', requestedCapability: 'mcp.organism.status', tool: 'aegis_organism_status', target: '.aegis/runtime/organism.json', action: { operation: 'read-organism-status' } }) + if (isDenied(authority)) return denialResponse(authority) + try { return text({ authority, organism: readOrganismStatus(repoRoot()) }) } + catch (error) { return text({ authority, outcome: 'ERROR', code: error instanceof OrganismClientError ? error.code : 'ORGANISM_STATUS_ERROR' }) } +}) + +server.tool( + 'aegis_contribute', + 'Attach a provider/model artifact digest to an existing AEGIS work order. This records NON_AUTHORITATIVE_EVIDENCE only and cannot approve, verify, or admit the work.', + { + work_id: z.string().regex(/^[A-Za-z0-9._:/@+\-]{1,128}$/), + provider: z.string().regex(/^[A-Za-z0-9._:/@+\-]{1,128}$/), + model: z.string().regex(/^[A-Za-z0-9._:/@+\-]{1,128}$/), + artifact_digest: z.string().regex(/^[0-9a-f]{64}$/), + source_ref: z.string().regex(/^[A-Za-z0-9._:/@+\-]{1,128}$/), + }, + async ({ work_id, provider, model, artifact_digest, source_ref }) => { + const authority = authorizeAction({ + actionClass: 'D1', authorityDomain: 'organism:contribution', requestedCapability: 'mcp.organism.contribute', tool: 'aegis_contribute', target: '.aegis/runtime/organism.json', + action: { operation: 'record-provider-contribution', work_id, provider, model, artifact_digest, source_ref }, + }) + if (isDenied(authority)) return denialResponse(authority) + try { + const contribution = recordProviderContribution(repoRoot(), { workId: work_id, provider, model, artifactDigest: artifact_digest, sourceRef: source_ref }) + return text({ authority, contribution, epistemic_status: 'NON_AUTHORITATIVE_EVIDENCE', admission_effect: 'NONE' }) + } catch (error) { + return text({ authority, outcome: 'ERROR', code: error instanceof OrganismClientError ? error.code : 'ORGANISM_CONTRIBUTION_ERROR', admission_effect: 'NONE' }) + } + }, +) + server.tool( 'aegis_collaborate', 'Run the governed swarm. Requires API key, execution identity, observed capability, workspace binding, and D2 approval.', From f0f00bca8d06ab3c6aab4e58a82d6cdc3a2f8b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:26:30 +0200 Subject: [PATCH 66/89] test(mcp): verify provider contribution client --- .../mcp-server/test/organism-contribution.mjs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 sovereign-omega-v2/mcp-server/test/organism-contribution.mjs diff --git a/sovereign-omega-v2/mcp-server/test/organism-contribution.mjs b/sovereign-omega-v2/mcp-server/test/organism-contribution.mjs new file mode 100644 index 000000000..34c0c78d9 --- /dev/null +++ b/sovereign-omega-v2/mcp-server/test/organism-contribution.mjs @@ -0,0 +1,49 @@ +import assert from 'node:assert/strict' +import { mkdtempSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join, resolve } from 'node:path' +import { spawnSync } from 'node:child_process' +import { fileURLToPath } from 'node:url' +import { dirname } from 'node:path' +import { recordProviderContribution, readOrganismStatus } from '../dist/organism-client.js' + +const here = dirname(fileURLToPath(import.meta.url)) +const repoRoot = resolve(here, '../../..') +const temp = mkdtempSync(join(tmpdir(), 'aegis-organism-mcp-')) +const store = join(temp, 'organism.json') +process.env.AEGIS_ORGANISM_STORE = store +process.env.AEGIS_PYTHON = process.env.AEGIS_PYTHON || 'python3' + +try { + const submitted = spawnSync(process.env.AEGIS_PYTHON, ['-m', 'agents.organism', 'submit', '--id', 'mcp-work-1', '--event', 'research_request', '--payload', '{"topic":"cross-provider"}', '--consequence', 'D1'], { cwd: repoRoot, env: process.env, encoding: 'utf8' }) + assert.equal(submitted.status, 0, submitted.stderr) + + const digest = 'a'.repeat(64) + const contribution = recordProviderContribution(repoRoot, { + workId: 'mcp-work-1', + provider: 'openai', + model: 'gpt-5.6-sol', + artifactDigest: digest, + sourceRef: 'mcp:openai', + }) + assert.equal(contribution.authority, 'NON_AUTHORITATIVE_EVIDENCE') + assert.match(String(contribution.contribution_ref), /provider:openai:model:gpt-5\.6-sol/) + assert.match(String(contribution.contribution_ref), new RegExp(digest)) + + const status = readOrganismStatus(repoRoot) + assert.equal(Array.isArray(status.orders), true) + assert.equal(status.orders.length, 1) + assert.equal(status.orders[0].status, 'QUEUED') + assert.equal(status.orders[0].contribution_refs.length, 1) + + const again = recordProviderContribution(repoRoot, { + workId: 'mcp-work-1', provider: 'openai', model: 'gpt-5.6-sol', artifactDigest: digest, sourceRef: 'mcp:openai', + }) + assert.equal(again.contribution_ref, contribution.contribution_ref) + const status2 = readOrganismStatus(repoRoot) + assert.equal(status2.orders[0].contribution_refs.length, 1) + + console.log('MCP_ORGANISM_PROVIDER_CONTRIBUTION=PASS') +} finally { + rmSync(temp, { recursive: true, force: true }) +} From 65416445a72c2a166afee7b178e80f2243b1066e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:26:40 +0200 Subject: [PATCH 67/89] test(mcp): add organism contribution integration script --- sovereign-omega-v2/mcp-server/package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sovereign-omega-v2/mcp-server/package.json b/sovereign-omega-v2/mcp-server/package.json index bc6d82ea9..5ae402a98 100644 --- a/sovereign-omega-v2/mcp-server/package.json +++ b/sovereign-omega-v2/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "@aegis/mcp-server", - "version": "0.2.0", + "version": "0.3.0", "description": "AEGIS constitutional agent swarm — MCP server", "type": "module", "main": "dist/index.js", @@ -10,7 +10,8 @@ "start": "node dist/index.js", "dev": "tsx src/index.ts", "test:resources": "npm run build && node test/resources.mjs", - "test:automaton3": "npm run build && node test/authority-response.mjs && node test/automaton3-authority.mjs" + "test:automaton3": "npm run build && node test/authority-response.mjs && node test/automaton3-authority.mjs", + "test:organism": "npm run build && node test/organism-contribution.mjs" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1" From cb8cafcfb2db5aef03bd7c759cd954301ba7a323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:37:48 +0200 Subject: [PATCH 68/89] feat(organism): persist content-addressed provider artifacts --- agents/organism.py | 108 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 89 insertions(+), 19 deletions(-) diff --git a/agents/organism.py b/agents/organism.py index a38f75373..080be2f5d 100644 --- a/agents/organism.py +++ b/agents/organism.py @@ -3,8 +3,8 @@ Turns the existing governed coordinator into a persistent company loop. It does not grant new authority. D1/D2 work may enter the existing Automaton-3-gated dispatcher; D3 waits for explicit operator approval; D4 is denied. Provider -contributions are recorded as non-authoritative evidence and cannot promote a -work order by themselves. State is persisted with a hash-chained journal. +contributions are content-addressed, recorded as non-authoritative evidence, and +cannot promote a work order by themselves. State uses an append-only hash chain. """ from __future__ import annotations @@ -23,8 +23,11 @@ GENESIS = "0" * 64 STORE_VERSION = "AEGIS_ORGANISM_STORE_V1" JOURNAL_DOMAIN = "AEGIS_ORGANISM_JOURNAL_V1" +CONTRIBUTION_SCHEMA = "AEGIS_PROVIDER_CONTRIBUTION_ARTIFACT_V1" +MAX_TEXT_CONTRIBUTION_BYTES = 262_144 _IDENTITY_RE = re.compile(r"^[A-Za-z0-9._:/@+\-]{1,128}$") _SHA256_RE = re.compile(r"^[0-9a-f]{64}$") +_ALLOWED_TEXT_MEDIA = frozenset({"text/plain", "text/markdown", "application/json"}) class WorkStatus(str, Enum): @@ -90,6 +93,63 @@ def _hash_event(prev: str, seq: int, event_type: str, body: dict[str, Any]) -> s return h.hexdigest() +def _atomic_write(path: Path, data: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + fd, tmp = tempfile.mkstemp(prefix=path.name + ".", suffix=".tmp", dir=str(path.parent)) + try: + with os.fdopen(fd, "w", encoding="utf-8") as f: + f.write(data) + f.flush() + os.fsync(f.fileno()) + os.replace(tmp, path) + finally: + if os.path.exists(tmp): + os.unlink(tmp) + + +class ContributionArtifactStore: + """Content-addressed text evidence. Artifact existence never grants authority.""" + + def __init__(self, root: str | Path): + self.root = Path(root) + + def put_text(self, text: str, *, media_type: str = "text/markdown") -> dict[str, Any]: + if media_type not in _ALLOWED_TEXT_MEDIA: + raise ValueError("CONTRIBUTION_MEDIA_TYPE_INVALID") + raw = text.encode("utf-8") + if not raw: + raise ValueError("CONTRIBUTION_EMPTY") + if len(raw) > MAX_TEXT_CONTRIBUTION_BYTES: + raise ValueError("CONTRIBUTION_TOO_LARGE") + digest = hashlib.sha256(raw).hexdigest() + path = self.root / digest[:2] / f"{digest}.json" + record = { + "schema_version": CONTRIBUTION_SCHEMA, + "sha256": digest, + "media_type": media_type, + "byte_length": len(raw), + "content": text, + "authority": "NON_AUTHORITATIVE_EVIDENCE", + } + rendered = json.dumps(record, sort_keys=True, indent=2, ensure_ascii=False) + if path.exists(): + existing = json.loads(path.read_text(encoding="utf-8")) + if existing != record: + raise ValueError("CONTRIBUTION_CONTENT_ADDRESS_COLLISION") + else: + _atomic_write(path, rendered) + return {**record, "artifact_path": str(path)} + + def get(self, digest: str) -> dict[str, Any]: + if not _SHA256_RE.fullmatch(digest): + raise ValueError("ARTIFACT_DIGEST_INVALID") + path = self.root / digest[:2] / f"{digest}.json" + record = json.loads(path.read_text(encoding="utf-8")) + if record.get("sha256") != digest or hashlib.sha256(str(record.get("content", "")).encode("utf-8")).hexdigest() != digest: + raise ValueError("CONTRIBUTION_ARTIFACT_TAMPER_DETECTED") + return record + + class OrganismStore: def __init__(self, path: str | Path): self.path = Path(path) @@ -113,18 +173,7 @@ def _validate(self) -> None: prev = expected def _persist(self) -> None: - self.path.parent.mkdir(parents=True, exist_ok=True) - data = json.dumps(self._state, sort_keys=True, indent=2, ensure_ascii=False) - fd, tmp = tempfile.mkstemp(prefix=self.path.name + ".", suffix=".tmp", dir=str(self.path.parent)) - try: - with os.fdopen(fd, "w", encoding="utf-8") as f: - f.write(data) - f.flush() - os.fsync(f.fileno()) - os.replace(tmp, self.path) - finally: - if os.path.exists(tmp): - os.unlink(tmp) + _atomic_write(self.path, json.dumps(self._state, sort_keys=True, indent=2, ensure_ascii=False)) def _append(self, event_type: str, body: dict[str, Any]) -> None: journal = self._state["journal"] @@ -150,9 +199,10 @@ def journal(self) -> list[dict[str, Any]]: class OrganizationOrganism: - def __init__(self, store: OrganismStore, dispatcher: Dispatcher | None = None): + def __init__(self, store: OrganismStore, dispatcher: Dispatcher | None = None, contribution_store: ContributionArtifactStore | None = None): self.store = store self.dispatcher = dispatcher or self._default_dispatcher + self.contribution_store = contribution_store or ContributionArtifactStore(store.path.parent / "contributions") @staticmethod async def _default_dispatcher(event_type: str, payload: dict[str, Any]): @@ -168,6 +218,11 @@ def get(self, work_id: str) -> WorkOrder: raise KeyError(work_id) return order + def next_work(self, *, limit: int = 10) -> list[WorkOrder]: + if limit < 1 or limit > 100: + raise ValueError("NEXT_WORK_LIMIT_INVALID") + return [w for w in self.orders() if w.status == WorkStatus.QUEUED][:limit] + def submit(self, work_id: str, event_type: str, payload: dict[str, Any], *, consequence_class: str, max_attempts: int = 3) -> WorkOrder: existing = self.store.get(work_id) if existing is not None: @@ -216,6 +271,11 @@ def record_contribution(self, work_id: str, *, provider: str, model: str, artifa ) return contribution_ref + def contribute_text(self, work_id: str, *, provider: str, model: str, text: str, source_ref: str, media_type: str = "text/markdown") -> dict[str, Any]: + artifact = self.contribution_store.put_text(text, media_type=media_type) + ref = self.record_contribution(work_id, provider=provider, model=model, artifact_digest=artifact["sha256"], source_ref=source_ref) + return {"contribution_ref": ref, "artifact": artifact, "authority": "NON_AUTHORITATIVE_EVIDENCE", "work": self.get(work_id).to_dict()} + def operator_inbox(self) -> list[WorkOrder]: return [w for w in self.orders() if w.status == WorkStatus.WAITING_OPERATOR] @@ -232,10 +292,8 @@ def approve(self, work_id: str, *, approval_ref: str) -> bool: return True def _next_queued(self) -> WorkOrder | None: - for order in self.orders(): - if order.status == WorkStatus.QUEUED: - return order - return None + items = self.next_work(limit=1) + return items[0] if items else None async def tick(self) -> WorkOrder | None: order = self._next_queued() @@ -304,6 +362,8 @@ def main() -> None: p_submit.add_argument("--event", required=True) p_submit.add_argument("--payload", default="{}") p_submit.add_argument("--consequence", default="D1") + p_next = sub.add_parser("next") + p_next.add_argument("--limit", type=int, default=10) sub.add_parser("tick") p_run = sub.add_parser("run") p_run.add_argument("--max-ticks", type=int, default=100) @@ -317,12 +377,15 @@ def main() -> None: p_contrib.add_argument("--model", required=True) p_contrib.add_argument("--artifact-digest", required=True) p_contrib.add_argument("--source-ref", required=True) + sub.add_parser("contribute-json") sub.add_parser("status") args = parser.parse_args() org = OrganizationOrganism(OrganismStore(default_store_path())) if args.command == "submit": print(json.dumps(org.submit(args.id, args.event, json.loads(args.payload), consequence_class=args.consequence).to_dict(), sort_keys=True)) + elif args.command == "next": + print(json.dumps([w.to_dict() for w in org.next_work(limit=args.limit)], sort_keys=True)) elif args.command == "tick": result = asyncio.run(org.tick()) print(json.dumps(result.to_dict() if result else {"status": "IDLE"}, sort_keys=True)) @@ -335,6 +398,13 @@ def main() -> None: elif args.command == "contribute": ref = org.record_contribution(args.id, provider=args.provider, model=args.model, artifact_digest=args.artifact_digest, source_ref=args.source_ref) print(json.dumps({"contribution_ref": ref, "authority": "NON_AUTHORITATIVE_EVIDENCE", "work": org.get(args.id).to_dict()}, sort_keys=True)) + elif args.command == "contribute-json": + body = json.loads(__import__("sys").stdin.read()) + result = org.contribute_text( + body["work_id"], provider=body["provider"], model=body["model"], text=body["text"], + source_ref=body["source_ref"], media_type=body.get("media_type", "text/markdown"), + ) + print(json.dumps(result, sort_keys=True)) elif args.command == "status": print(json.dumps({"orders": [w.to_dict() for w in org.orders()], "journal_length": len(org.store.journal())}, sort_keys=True)) From f97b2a82e579d19a7d790b621fe62eebf8844bd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:38:21 +0200 Subject: [PATCH 69/89] test(organism): cover content-addressed cross-provider work --- agents/tests/test_provider_contribution.py | 55 +++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/agents/tests/test_provider_contribution.py b/agents/tests/test_provider_contribution.py index 796ce6cca..3bdae1f68 100644 --- a/agents/tests/test_provider_contribution.py +++ b/agents/tests/test_provider_contribution.py @@ -1,11 +1,12 @@ from __future__ import annotations import hashlib +import json import tempfile import unittest from pathlib import Path -from agents.organism import OrganismStore, OrganizationOrganism, WorkStatus +from agents.organism import MAX_TEXT_CONTRIBUTION_BYTES, OrganismStore, OrganizationOrganism, WorkStatus class ProviderContributionTests(unittest.TestCase): @@ -62,6 +63,58 @@ def test_journal_contains_non_authoritative_contribution_event(self): self.assertEqual(contribution[0]["body"]["authority"], "NON_AUTHORITATIVE_EVIDENCE") self.assertEqual(org.get("w1").status, WorkStatus.QUEUED) + def test_text_contribution_is_content_addressed_and_persisted(self): + org = self.make_org() + org.submit("w1", "research_request", {}, consequence_class="D1") + result = org.contribute_text("w1", provider="openai", model="gpt-5.6-sol", text="# useful work\nproof body", source_ref="mcp:openai") + digest = hashlib.sha256(b"# useful work\nproof body").hexdigest() + self.assertEqual(result["artifact"]["sha256"], digest) + self.assertEqual(result["artifact"]["authority"], "NON_AUTHORITATIVE_EVIDENCE") + artifact_path = Path(result["artifact"]["artifact_path"]) + self.assertTrue(artifact_path.is_file()) + stored = json.loads(artifact_path.read_text()) + self.assertEqual(stored["content"], "# useful work\nproof body") + self.assertEqual(org.get("w1").status, WorkStatus.QUEUED) + + def test_text_contribution_is_idempotent(self): + org = self.make_org() + org.submit("w1", "research_request", {}, consequence_class="D1") + first = org.contribute_text("w1", provider="gemini", model="gemini-3", text="same", source_ref="mcp:gemini") + second = org.contribute_text("w1", provider="gemini", model="gemini-3", text="same", source_ref="mcp:gemini") + self.assertEqual(first["contribution_ref"], second["contribution_ref"]) + self.assertEqual(org.get("w1").contribution_refs, (first["contribution_ref"],)) + + def test_text_contribution_size_is_bounded(self): + org = self.make_org() + org.submit("w1", "research_request", {}, consequence_class="D1") + with self.assertRaisesRegex(ValueError, "CONTRIBUTION_TOO_LARGE"): + org.contribute_text("w1", provider="claude", model="opus", text="x" * (MAX_TEXT_CONTRIBUTION_BYTES + 1), source_ref="mcp:claude") + + def test_artifact_tamper_is_detected(self): + org = self.make_org() + org.submit("w1", "research_request", {}, consequence_class="D1") + result = org.contribute_text("w1", provider="local", model="llama", text="original", source_ref="mcp:local") + path = Path(result["artifact"]["artifact_path"]) + record = json.loads(path.read_text()) + record["content"] = "tampered" + path.write_text(json.dumps(record)) + with self.assertRaisesRegex(ValueError, "CONTRIBUTION_ARTIFACT_TAMPER_DETECTED"): + org.contribution_store.get(result["artifact"]["sha256"]) + + def test_next_work_exposes_only_queued_non_operator_items(self): + org = self.make_org() + org.submit("q1", "research_request", {"topic": "a"}, consequence_class="D1") + org.submit("q2", "code_request", {"topic": "b"}, consequence_class="D2") + org.submit("wait", "deployment_event", {}, consequence_class="D3") + org.submit("deny", "forbidden", {}, consequence_class="D4") + self.assertEqual([w.work_id for w in org.next_work()], ["q1", "q2"]) + + def test_empty_text_contribution_is_rejected(self): + org = self.make_org() + org.submit("w1", "research_request", {}, consequence_class="D1") + with self.assertRaisesRegex(ValueError, "CONTRIBUTION_EMPTY"): + org.contribute_text("w1", provider="openai", model="gpt-5.6-sol", text="", source_ref="mcp:openai") + if __name__ == "__main__": unittest.main() From 7a748b7cdebbc3624ea4e0da7f4f36db72001252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:38:53 +0200 Subject: [PATCH 70/89] feat(mcp): add next-work and text contribution client --- .../mcp-server/src/organism-client.ts | 131 +++++++++++++----- 1 file changed, 93 insertions(+), 38 deletions(-) diff --git a/sovereign-omega-v2/mcp-server/src/organism-client.ts b/sovereign-omega-v2/mcp-server/src/organism-client.ts index 63aa93249..d5aff25d5 100644 --- a/sovereign-omega-v2/mcp-server/src/organism-client.ts +++ b/sovereign-omega-v2/mcp-server/src/organism-client.ts @@ -2,6 +2,8 @@ import { spawnSync } from 'node:child_process' const IDENTITY_RE = /^[A-Za-z0-9._:/@+\-]{1,128}$/ const SHA256_RE = /^[0-9a-f]{64}$/ +const MAX_TEXT_BYTES = 262_144 +const ALLOWED_MEDIA = new Set(['text/plain', 'text/markdown', 'application/json']) export type ProviderContributionInput = { workId: string @@ -11,6 +13,15 @@ export type ProviderContributionInput = { sourceRef: string } +export type ProviderTextContributionInput = { + workId: string + provider: string + model: string + text: string + sourceRef: string + mediaType?: 'text/plain' | 'text/markdown' | 'application/json' +} + export class OrganismClientError extends Error { constructor(public readonly code: string, message?: string) { super(message ?? code) @@ -22,58 +33,102 @@ function boundedIdentity(value: string, code: string): void { if (!IDENTITY_RE.test(value)) throw new OrganismClientError(code) } -export function recordProviderContribution(root: string, input: ProviderContributionInput): Record { - boundedIdentity(input.workId, 'WORK_ID_INVALID') - boundedIdentity(input.provider, 'PROVIDER_ID_INVALID') - boundedIdentity(input.model, 'MODEL_ID_INVALID') - boundedIdentity(input.sourceRef, 'SOURCE_REF_INVALID') - if (!SHA256_RE.test(input.artifactDigest)) throw new OrganismClientError('ARTIFACT_DIGEST_INVALID') - +function runPython(root: string, args: string[], input?: string): string { const python = process.env['AEGIS_PYTHON'] ?? 'python3' - const result = spawnSync( - python, - [ - '-m', 'agents.organism', 'contribute', - '--id', input.workId, - '--provider', input.provider, - '--model', input.model, - '--artifact-digest', input.artifactDigest, - '--source-ref', input.sourceRef, - ], - { cwd: root, env: process.env, encoding: 'utf8', timeout: 10_000, maxBuffer: 1_048_576 }, - ) + const result = spawnSync(python, args, { + cwd: root, + env: process.env, + encoding: 'utf8', + input, + timeout: 10_000, + maxBuffer: 2_097_152, + }) if (result.error || result.signal || result.status !== 0) { - throw new OrganismClientError('ORGANISM_CONTRIBUTION_FAILED', result.stderr || result.error?.message) + throw new OrganismClientError('ORGANISM_COMMAND_FAILED', result.stderr || result.error?.message || `status=${result.status}`) } + return result.stdout +} + +function parseObject(raw: string): Record { let parsed: unknown - try { parsed = JSON.parse(result.stdout) } catch { throw new OrganismClientError('ORGANISM_RESPONSE_MALFORMED') } - if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { - throw new OrganismClientError('ORGANISM_RESPONSE_MALFORMED') - } - const record = parsed as Record + try { parsed = JSON.parse(raw) } catch { throw new OrganismClientError('ORGANISM_RESPONSE_MALFORMED') } + if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) throw new OrganismClientError('ORGANISM_RESPONSE_MALFORMED') + return parsed as Record +} + +function assertContributionBoundary(record: Record, digest?: string): void { if (record['authority'] !== 'NON_AUTHORITATIVE_EVIDENCE') { throw new OrganismClientError('ORGANISM_AUTHORITY_BOUNDARY_VIOLATION') } const ref = record['contribution_ref'] - if (typeof ref !== 'string' || !ref.includes(input.artifactDigest)) { + if (typeof ref !== 'string' || (digest !== undefined && !ref.includes(digest))) { throw new OrganismClientError('ORGANISM_CONTRIBUTION_BINDING_MISMATCH') } +} + +export function recordProviderContribution(root: string, input: ProviderContributionInput): Record { + boundedIdentity(input.workId, 'WORK_ID_INVALID') + boundedIdentity(input.provider, 'PROVIDER_ID_INVALID') + boundedIdentity(input.model, 'MODEL_ID_INVALID') + boundedIdentity(input.sourceRef, 'SOURCE_REF_INVALID') + if (!SHA256_RE.test(input.artifactDigest)) throw new OrganismClientError('ARTIFACT_DIGEST_INVALID') + + const raw = runPython(root, [ + '-m', 'agents.organism', 'contribute', + '--id', input.workId, + '--provider', input.provider, + '--model', input.model, + '--artifact-digest', input.artifactDigest, + '--source-ref', input.sourceRef, + ]) + const record = parseObject(raw) + assertContributionBoundary(record, input.artifactDigest) return record } -export function readOrganismStatus(root: string): Record { - const python = process.env['AEGIS_PYTHON'] ?? 'python3' - const result = spawnSync(python, ['-m', 'agents.organism', 'status'], { - cwd: root, env: process.env, encoding: 'utf8', timeout: 10_000, maxBuffer: 1_048_576, - }) - if (result.error || result.signal || result.status !== 0) { - throw new OrganismClientError('ORGANISM_STATUS_FAILED', result.stderr || result.error?.message) +export function recordProviderTextContribution(root: string, input: ProviderTextContributionInput): Record { + boundedIdentity(input.workId, 'WORK_ID_INVALID') + boundedIdentity(input.provider, 'PROVIDER_ID_INVALID') + boundedIdentity(input.model, 'MODEL_ID_INVALID') + boundedIdentity(input.sourceRef, 'SOURCE_REF_INVALID') + const mediaType = input.mediaType ?? 'text/markdown' + if (!ALLOWED_MEDIA.has(mediaType)) throw new OrganismClientError('CONTRIBUTION_MEDIA_TYPE_INVALID') + const bytes = Buffer.byteLength(input.text, 'utf8') + if (bytes < 1) throw new OrganismClientError('CONTRIBUTION_EMPTY') + if (bytes > MAX_TEXT_BYTES) throw new OrganismClientError('CONTRIBUTION_TOO_LARGE') + + const raw = runPython(root, ['-m', 'agents.organism', 'contribute-json'], JSON.stringify({ + work_id: input.workId, + provider: input.provider, + model: input.model, + text: input.text, + source_ref: input.sourceRef, + media_type: mediaType, + })) + const record = parseObject(raw) + const artifact = record['artifact'] + if (typeof artifact !== 'object' || artifact === null || Array.isArray(artifact)) { + throw new OrganismClientError('ORGANISM_ARTIFACT_MALFORMED') } - try { - const parsed: unknown = JSON.parse(result.stdout) - if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) throw new Error('shape') - return parsed as Record - } catch { + const digest = (artifact as Record)['sha256'] + if (typeof digest !== 'string' || !SHA256_RE.test(digest)) { + throw new OrganismClientError('ORGANISM_ARTIFACT_DIGEST_MALFORMED') + } + assertContributionBoundary(record, digest) + return record +} + +export function readNextWork(root: string, limit = 10): Array> { + if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) throw new OrganismClientError('NEXT_WORK_LIMIT_INVALID') + const raw = runPython(root, ['-m', 'agents.organism', 'next', '--limit', String(limit)]) + let parsed: unknown + try { parsed = JSON.parse(raw) } catch { throw new OrganismClientError('ORGANISM_RESPONSE_MALFORMED') } + if (!Array.isArray(parsed) || parsed.some((x) => typeof x !== 'object' || x === null || Array.isArray(x))) { throw new OrganismClientError('ORGANISM_RESPONSE_MALFORMED') } + return parsed as Array> +} + +export function readOrganismStatus(root: string): Record { + return parseObject(runPython(root, ['-m', 'agents.organism', 'status'])) } From 01d6fd8d43afc7805b52a32c70f36a556b3ffde4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:41:27 +0200 Subject: [PATCH 71/89] feat(authority): map organism MCP capabilities --- harness/policies/capability-map.v1.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/harness/policies/capability-map.v1.json b/harness/policies/capability-map.v1.json index 4f61adb4f..2041c292d 100644 --- a/harness/policies/capability-map.v1.json +++ b/harness/policies/capability-map.v1.json @@ -3,6 +3,9 @@ "capabilities": { "coordinator.dispatch": {"skill_id": "orchestration_routing", "allowed_action_classes": ["D1"], "allowed_tools": ["agents.coordinator:dispatch"]}, "mcp.platform.status": {"skill_id": "python_bridge_development", "allowed_action_classes": ["D0"], "allowed_tools": ["aegis_platform_status"]}, + "mcp.organism.status": {"skill_id": "orchestration_routing", "allowed_action_classes": ["D0"], "allowed_tools": ["aegis_organism_status"]}, + "mcp.organism.next": {"skill_id": "orchestration_routing", "allowed_action_classes": ["D0"], "allowed_tools": ["aegis_next_work"]}, + "mcp.organism.contribute": {"skill_id": "orchestration_routing", "allowed_action_classes": ["D1"], "allowed_tools": ["aegis_contribute", "aegis_contribute_text"]}, "mcp.execution.read": {"skill_id": "orchestration_routing", "allowed_action_classes": ["D0"], "allowed_tools": ["aegis_get_execution"]}, "mcp.collaborate": {"skill_id": "orchestration_routing", "allowed_action_classes": ["D2"], "allowed_tools": ["aegis_collaborate"]}, "mcp.execution.start": {"skill_id": "orchestration_routing", "allowed_action_classes": ["D2"], "allowed_tools": ["aegis_start_execution"]}, From 5a70a1a2a51faea2733eaf2b0927906234eeeab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:43:08 +0200 Subject: [PATCH 72/89] feat(organism): bind contributions to prepared pre-state --- agents/organism.py | 62 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/agents/organism.py b/agents/organism.py index 080be2f5d..e827d592b 100644 --- a/agents/organism.py +++ b/agents/organism.py @@ -1,10 +1,9 @@ """Durable, fail-closed organizational work loop for AEGIS Ω. -Turns the existing governed coordinator into a persistent company loop. It does -not grant new authority. D1/D2 work may enter the existing Automaton-3-gated -dispatcher; D3 waits for explicit operator approval; D4 is denied. Provider -contributions are content-addressed, recorded as non-authoritative evidence, and -cannot promote a work order by themselves. State uses an append-only hash chain. +Provider contributions are content-addressed NON_AUTHORITATIVE_EVIDENCE. A D1 +contribution can be prepared against an exact journal/order pre-state and the +write rejects if that pre-state changes before application. This gives the MCP +a concrete rollback/pre-state reference instead of a decorative policy string. """ from __future__ import annotations @@ -79,6 +78,10 @@ def _canonical(value: Any) -> bytes: return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8") +def _domain_hash(domain: str, value: Any) -> str: + return hashlib.sha256(_canonical({"domain": domain, "value": value})).hexdigest() + + def _hash_event(prev: str, seq: int, event_type: str, body: dict[str, Any]) -> str: h = hashlib.sha256() h.update(JOURNAL_DOMAIN.encode("ascii")) @@ -182,6 +185,10 @@ def _append(self, event_type: str, body: dict[str, Any]) -> None: event_hash = _hash_event(prev, seq, event_type, body) journal.append({"seq": seq, "event_type": event_type, "body": body, "prev_hash": prev, "event_hash": event_hash}) + def state_root(self) -> str: + journal = self._state["journal"] + return journal[-1]["event_hash"] if journal else GENESIS + def save_order(self, order: WorkOrder, event_type: str, *, event_body: dict[str, Any] | None = None) -> None: self._state["orders"][order.work_id] = order.to_dict() self._append(event_type, event_body if event_body is not None else order.to_dict()) @@ -243,7 +250,21 @@ def submit(self, work_id: str, event_type: str, payload: dict[str, Any], *, cons self.store.save_order(order, "WORK_SUBMITTED") return order - def record_contribution(self, work_id: str, *, provider: str, model: str, artifact_digest: str, source_ref: str) -> str: + def prepare_contribution(self, work_id: str) -> dict[str, str]: + order = self.get(work_id) + order_digest = _domain_hash("AEGIS_ORGANISM_ORDER_PRESTATE_V1", order.to_dict()) + state_root = self.store.state_root() + rollback_reference = f"organism:{work_id}:order:{order_digest}:state:{state_root}" + return {"work_id": work_id, "order_digest": order_digest, "state_root": state_root, "rollback_reference": rollback_reference} + + def _verify_contribution_prestate(self, work_id: str, rollback_reference: str | None) -> dict[str, str]: + prepared = self.prepare_contribution(work_id) + if rollback_reference is not None and rollback_reference != prepared["rollback_reference"]: + raise ValueError("CONTRIBUTION_PRESTATE_STALE") + return prepared + + def record_contribution(self, work_id: str, *, provider: str, model: str, artifact_digest: str, source_ref: str, rollback_reference: str | None = None) -> str: + prepared = self._verify_contribution_prestate(work_id, rollback_reference) order = self.get(work_id) for value, code in ((provider, "PROVIDER_ID_INVALID"), (model, "MODEL_ID_INVALID"), (source_ref, "SOURCE_REF_INVALID")): if not _IDENTITY_RE.fullmatch(value): @@ -266,15 +287,22 @@ def record_contribution(self, work_id: str, *, provider: str, model: str, artifa "source_ref": source_ref, "contribution_ref": contribution_ref, "authority": "NON_AUTHORITATIVE_EVIDENCE", + "pre_state_root": prepared["state_root"], + "pre_order_digest": prepared["order_digest"], + "rollback_reference": prepared["rollback_reference"], "status_after": order.status.value, }, ) return contribution_ref - def contribute_text(self, work_id: str, *, provider: str, model: str, text: str, source_ref: str, media_type: str = "text/markdown") -> dict[str, Any]: + def contribute_text(self, work_id: str, *, provider: str, model: str, text: str, source_ref: str, media_type: str = "text/markdown", rollback_reference: str | None = None) -> dict[str, Any]: + prepared = self._verify_contribution_prestate(work_id, rollback_reference) artifact = self.contribution_store.put_text(text, media_type=media_type) - ref = self.record_contribution(work_id, provider=provider, model=model, artifact_digest=artifact["sha256"], source_ref=source_ref) - return {"contribution_ref": ref, "artifact": artifact, "authority": "NON_AUTHORITATIVE_EVIDENCE", "work": self.get(work_id).to_dict()} + ref = self.record_contribution( + work_id, provider=provider, model=model, artifact_digest=artifact["sha256"], source_ref=source_ref, + rollback_reference=prepared["rollback_reference"], + ) + return {"contribution_ref": ref, "artifact": artifact, "rollback_reference": prepared["rollback_reference"], "authority": "NON_AUTHORITATIVE_EVIDENCE", "work": self.get(work_id).to_dict()} def operator_inbox(self) -> list[WorkOrder]: return [w for w in self.orders() if w.status == WorkStatus.WAITING_OPERATOR] @@ -355,6 +383,7 @@ def default_store_path() -> Path: def main() -> None: import argparse + import sys parser = argparse.ArgumentParser(description="AEGIS Ω durable organization organism") sub = parser.add_subparsers(dest="command", required=True) p_submit = sub.add_parser("submit") @@ -364,6 +393,8 @@ def main() -> None: p_submit.add_argument("--consequence", default="D1") p_next = sub.add_parser("next") p_next.add_argument("--limit", type=int, default=10) + p_prepare = sub.add_parser("prepare-contribution") + p_prepare.add_argument("--id", required=True) sub.add_parser("tick") p_run = sub.add_parser("run") p_run.add_argument("--max-ticks", type=int, default=100) @@ -377,6 +408,7 @@ def main() -> None: p_contrib.add_argument("--model", required=True) p_contrib.add_argument("--artifact-digest", required=True) p_contrib.add_argument("--source-ref", required=True) + p_contrib.add_argument("--rollback-reference") sub.add_parser("contribute-json") sub.add_parser("status") args = parser.parse_args() @@ -386,6 +418,8 @@ def main() -> None: print(json.dumps(org.submit(args.id, args.event, json.loads(args.payload), consequence_class=args.consequence).to_dict(), sort_keys=True)) elif args.command == "next": print(json.dumps([w.to_dict() for w in org.next_work(limit=args.limit)], sort_keys=True)) + elif args.command == "prepare-contribution": + print(json.dumps(org.prepare_contribution(args.id), sort_keys=True)) elif args.command == "tick": result = asyncio.run(org.tick()) print(json.dumps(result.to_dict() if result else {"status": "IDLE"}, sort_keys=True)) @@ -396,17 +430,21 @@ def main() -> None: elif args.command == "approve": print(json.dumps({"approved": org.approve(args.id, approval_ref=args.approval_ref)}, sort_keys=True)) elif args.command == "contribute": - ref = org.record_contribution(args.id, provider=args.provider, model=args.model, artifact_digest=args.artifact_digest, source_ref=args.source_ref) + ref = org.record_contribution( + args.id, provider=args.provider, model=args.model, artifact_digest=args.artifact_digest, + source_ref=args.source_ref, rollback_reference=args.rollback_reference, + ) print(json.dumps({"contribution_ref": ref, "authority": "NON_AUTHORITATIVE_EVIDENCE", "work": org.get(args.id).to_dict()}, sort_keys=True)) elif args.command == "contribute-json": - body = json.loads(__import__("sys").stdin.read()) + body = json.loads(sys.stdin.read()) result = org.contribute_text( body["work_id"], provider=body["provider"], model=body["model"], text=body["text"], source_ref=body["source_ref"], media_type=body.get("media_type", "text/markdown"), + rollback_reference=body.get("rollback_reference"), ) print(json.dumps(result, sort_keys=True)) elif args.command == "status": - print(json.dumps({"orders": [w.to_dict() for w in org.orders()], "journal_length": len(org.store.journal())}, sort_keys=True)) + print(json.dumps({"orders": [w.to_dict() for w in org.orders()], "journal_length": len(org.store.journal()), "state_root": org.store.state_root()}, sort_keys=True)) if __name__ == "__main__": From c52f85eeba6aa7ee3e385859248e6ca735353146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:43:48 +0200 Subject: [PATCH 73/89] test(organism): cover contribution pre-state fencing --- agents/tests/test_provider_contribution.py | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/agents/tests/test_provider_contribution.py b/agents/tests/test_provider_contribution.py index 3bdae1f68..0df84fd1c 100644 --- a/agents/tests/test_provider_contribution.py +++ b/agents/tests/test_provider_contribution.py @@ -115,6 +115,29 @@ def test_empty_text_contribution_is_rejected(self): with self.assertRaisesRegex(ValueError, "CONTRIBUTION_EMPTY"): org.contribute_text("w1", provider="openai", model="gpt-5.6-sol", text="", source_ref="mcp:openai") + def test_prepare_contribution_binds_order_and_journal_prestate(self): + org = self.make_org() + org.submit("w1", "research_request", {}, consequence_class="D1") + prepared = org.prepare_contribution("w1") + self.assertEqual(len(prepared["order_digest"]), 64) + self.assertEqual(len(prepared["state_root"]), 64) + self.assertIn(prepared["order_digest"], prepared["rollback_reference"]) + self.assertIn(prepared["state_root"], prepared["rollback_reference"]) + + def test_stale_prestate_rejects_contribution(self): + org = self.make_org() + org.submit("w1", "research_request", {}, consequence_class="D1") + prepared = org.prepare_contribution("w1") + org.record_contribution( + "w1", provider="gemini", model="gemini-3", artifact_digest=hashlib.sha256(b"first").hexdigest(), + source_ref="mcp:gemini", rollback_reference=prepared["rollback_reference"], + ) + with self.assertRaisesRegex(ValueError, "CONTRIBUTION_PRESTATE_STALE"): + org.record_contribution( + "w1", provider="openai", model="gpt-5.6-sol", artifact_digest=hashlib.sha256(b"second").hexdigest(), + source_ref="mcp:openai", rollback_reference=prepared["rollback_reference"], + ) + if __name__ == "__main__": unittest.main() From c72c27546822fd251a92b49a6212c461ca2fb504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:44:15 +0200 Subject: [PATCH 74/89] feat(mcp): expose prepared contribution pre-state --- .../mcp-server/src/organism-client.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/sovereign-omega-v2/mcp-server/src/organism-client.ts b/sovereign-omega-v2/mcp-server/src/organism-client.ts index d5aff25d5..4dc43f9bf 100644 --- a/sovereign-omega-v2/mcp-server/src/organism-client.ts +++ b/sovereign-omega-v2/mcp-server/src/organism-client.ts @@ -11,6 +11,7 @@ export type ProviderContributionInput = { model: string artifactDigest: string sourceRef: string + rollbackReference?: string } export type ProviderTextContributionInput = { @@ -20,6 +21,14 @@ export type ProviderTextContributionInput = { text: string sourceRef: string mediaType?: 'text/plain' | 'text/markdown' | 'application/json' + rollbackReference?: string +} + +export type PreparedContribution = { + work_id: string + order_digest: string + state_root: string + rollback_reference: string } export class OrganismClientError extends Error { @@ -66,12 +75,28 @@ function assertContributionBoundary(record: Record, digest?: st } } +export function prepareProviderContribution(root: string, workId: string): PreparedContribution { + boundedIdentity(workId, 'WORK_ID_INVALID') + const record = parseObject(runPython(root, ['-m', 'agents.organism', 'prepare-contribution', '--id', workId])) + const orderDigest = record['order_digest'] + const stateRoot = record['state_root'] + const rollbackReference = record['rollback_reference'] + if (record['work_id'] !== workId || typeof orderDigest !== 'string' || !SHA256_RE.test(orderDigest) + || typeof stateRoot !== 'string' || !SHA256_RE.test(stateRoot) + || typeof rollbackReference !== 'string' || !rollbackReference.includes(orderDigest) || !rollbackReference.includes(stateRoot)) { + throw new OrganismClientError('ORGANISM_PREPARE_BINDING_MALFORMED') + } + return { work_id: workId, order_digest: orderDigest, state_root: stateRoot, rollback_reference: rollbackReference } +} + export function recordProviderContribution(root: string, input: ProviderContributionInput): Record { boundedIdentity(input.workId, 'WORK_ID_INVALID') boundedIdentity(input.provider, 'PROVIDER_ID_INVALID') boundedIdentity(input.model, 'MODEL_ID_INVALID') boundedIdentity(input.sourceRef, 'SOURCE_REF_INVALID') if (!SHA256_RE.test(input.artifactDigest)) throw new OrganismClientError('ARTIFACT_DIGEST_INVALID') + const prepared = input.rollbackReference === undefined ? prepareProviderContribution(root, input.workId) : undefined + const rollbackReference = input.rollbackReference ?? prepared!.rollback_reference const raw = runPython(root, [ '-m', 'agents.organism', 'contribute', @@ -80,6 +105,7 @@ export function recordProviderContribution(root: string, input: ProviderContribu '--model', input.model, '--artifact-digest', input.artifactDigest, '--source-ref', input.sourceRef, + '--rollback-reference', rollbackReference, ]) const record = parseObject(raw) assertContributionBoundary(record, input.artifactDigest) @@ -96,6 +122,8 @@ export function recordProviderTextContribution(root: string, input: ProviderText const bytes = Buffer.byteLength(input.text, 'utf8') if (bytes < 1) throw new OrganismClientError('CONTRIBUTION_EMPTY') if (bytes > MAX_TEXT_BYTES) throw new OrganismClientError('CONTRIBUTION_TOO_LARGE') + const prepared = input.rollbackReference === undefined ? prepareProviderContribution(root, input.workId) : undefined + const rollbackReference = input.rollbackReference ?? prepared!.rollback_reference const raw = runPython(root, ['-m', 'agents.organism', 'contribute-json'], JSON.stringify({ work_id: input.workId, @@ -104,6 +132,7 @@ export function recordProviderTextContribution(root: string, input: ProviderText text: input.text, source_ref: input.sourceRef, media_type: mediaType, + rollback_reference: rollbackReference, })) const record = parseObject(raw) const artifact = record['artifact'] @@ -114,6 +143,9 @@ export function recordProviderTextContribution(root: string, input: ProviderText if (typeof digest !== 'string' || !SHA256_RE.test(digest)) { throw new OrganismClientError('ORGANISM_ARTIFACT_DIGEST_MALFORMED') } + if (record['rollback_reference'] !== rollbackReference) { + throw new OrganismClientError('ORGANISM_ROLLBACK_BINDING_MISMATCH') + } assertContributionBoundary(record, digest) return record } From 3fc4ec879bea6690c578adf8c961f3591a9a3277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:44:55 +0200 Subject: [PATCH 75/89] feat(authority): bootstrap provider-bound execution identity --- harness/sdk/provider_session.py | 176 ++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 harness/sdk/provider_session.py diff --git a/harness/sdk/provider_session.py b/harness/sdk/provider_session.py new file mode 100644 index 000000000..2987fddeb --- /dev/null +++ b/harness/sdk/provider_session.py @@ -0,0 +1,176 @@ +"""Provider-neutral execution-identity bootstrap for the AEGIS MCP surface. + +The bootstrap binds a provider/model/session to the live repository HEAD, +commit-bound authority roots, exact requested action and current organism state. +It never creates approvals, authority signer keys or policy decisions. +""" +from __future__ import annotations + +import json +import os +import subprocess +from dataclasses import asdict +from pathlib import Path +from typing import Any + +from agents.organism import GENESIS, OrganismStore, default_store_path +from harness.sdk.sovereign_execution import ( + ExecutionIdentityEnvelope, + canonical_hash, + compute_workspace_binding, + git_head, + git_remote, + load_capability_registry_from_commit, + load_policy_from_commit, +) + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +def _branch_ref(root: Path, source_commit: str) -> str: + result = subprocess.run( + ["git", "-C", str(root), "rev-parse", "--abbrev-ref", "HEAD"], + check=False, + capture_output=True, + text=True, + timeout=5, + ) + branch = result.stdout.strip() if result.returncode == 0 else "HEAD" + return f"refs/heads/{branch}" if branch and branch != "HEAD" else f"detached:{source_commit[:16]}" + + +def _approval_reference() -> str: + raw = os.environ.get("AEGIS_APPROVAL_GRANT_JSON") + if not raw: + return "NONE" + try: + value = json.loads(raw) + except json.JSONDecodeError as exc: + raise ValueError("APPROVAL_MALFORMED") from exc + reference = value.get("reference") if isinstance(value, dict) else None + if not isinstance(reference, str) or not reference: + raise ValueError("APPROVAL_REFERENCE_MISSING") + return reference + + +def _organism_state_root() -> str: + path = default_store_path() + if not path.is_absolute(): + path = REPO_ROOT / path + if not path.exists(): + return GENESIS + return OrganismStore(path).state_root() + + +def build_provider_session(payload: dict[str, Any]) -> dict[str, Any]: + provider = payload["provider"] + model = payload["model"] + session = payload["session"] + action_class = payload["action_class"] + authority_domain = payload["authority_domain"] + requested_capability = payload["requested_capability"] + tool = payload["tool"] + target = payload["target"] + action = payload["action"] + if not isinstance(action, dict): + raise ValueError("ACTION_MUST_BE_OBJECT") + + source_commit = git_head(REPO_ROOT) + repository_identity = git_remote(REPO_ROOT) + policy, policy_root = load_policy_from_commit( + repository_root=REPO_ROOT, + source_commit=source_commit, + policy_path="harness/policies/consequence-policy.v1.json", + ) + registry, skills_root, registry_root = load_capability_registry_from_commit( + repository_root=REPO_ROOT, + source_commit=source_commit, + skill_tree_path="harness/skill_tree.json", + capability_map_path="harness/policies/capability-map.v1.json", + ) + del policy + if requested_capability not in registry: + raise ValueError("UNMAPPED_CAPABILITY") + + approval_reference = _approval_reference() + workspace_binding = compute_workspace_binding( + repository_remote=repository_identity, + repository_root=".", + project_identity="AEGIS-OMEGA", + source_commit=source_commit, + operator_authorization=approval_reference, + ) + state_root = _organism_state_root() + action_digest = canonical_hash("AEGIS_REQUESTED_ACTION_V1", action) + input_digest = canonical_hash( + "AEGIS_PROVIDER_ACTION_INPUT_V1", + { + "provider": provider, + "model": model, + "session": session, + "action_class": action_class, + "authority_domain": authority_domain, + "requested_capability": requested_capability, + "tool": tool, + "target": target, + "action_digest": action_digest, + "state_root": state_root, + }, + ) + nonce_root = canonical_hash( + "AEGIS_PROVIDER_SESSION_NONCE_V1", + { + "source_commit": source_commit, + "provider": provider, + "model": model, + "session": session, + "tool": tool, + "action_digest": action_digest, + "state_root": state_root, + }, + ) + identity = ExecutionIdentityEnvelope( + schema_version="1.0.0", + repository_identity=repository_identity, + repository_root=".", + source_commit=source_commit, + branch_or_ref=_branch_ref(REPO_ROOT, source_commit), + project_identity="AEGIS-OMEGA", + workspace_root=".", + workspace_binding=workspace_binding, + parent_state_root=state_root, + skills_root=skills_root, + registry_root=registry_root, + policy_root=policy_root, + actor_class="provider-agent", + actor_identity=f"provider:{provider}", + model_identity=f"model:{model}", + session_identity=f"session:{session}", + physical_executor="executor:aegis-mcp", + tool_identity=tool, + workflow_identity="workflow:cross-provider-organism", + authority_domain=authority_domain, + requested_capability=requested_capability, + observed_authority="0.000000", + approval_reference=approval_reference, + input_digest=input_digest, + action_digest=action_digest, + expected_pre_state=state_root, + deterministic_nonce=f"nonce:{nonce_root}", + ) + # Validation is deliberately performed before any result is returned. + identity_root = identity.root + workspace = { + "actual_cwd": str(REPO_ROOT), + "remote_origin": repository_identity, + "mutation_target": str(REPO_ROOT / payload.get("mutation_target", ".")), + "path_views": {}, + } + return { + "identity": asdict(identity), + "identity_root": identity_root, + "workspace": workspace, + "state_root": state_root, + "capability": requested_capability, + "authority": "IDENTITY_ONLY_NOT_AUTHORIZATION", + } From c7dde44f411cf31ccf2af6676d9555c4e3b7b648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:45:07 +0200 Subject: [PATCH 76/89] feat(authority): add provider session bootstrap CLI --- scripts/provider-session-bootstrap.py | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 scripts/provider-session-bootstrap.py diff --git a/scripts/provider-session-bootstrap.py b/scripts/provider-session-bootstrap.py new file mode 100644 index 000000000..661d93e57 --- /dev/null +++ b/scripts/provider-session-bootstrap.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import json +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) + +from harness.sdk.provider_session import build_provider_session # noqa: E402 + + +def main() -> int: + try: + payload = json.loads(sys.stdin.read()) + if not isinstance(payload, dict): + raise ValueError("PAYLOAD_MUST_BE_OBJECT") + result = build_provider_session(payload) + except Exception as exc: + sys.stdout.write(json.dumps({"outcome": "DENIED", "code": str(exc)}, sort_keys=True) + "\n") + return 3 + sys.stdout.write(json.dumps(result, sort_keys=True) + "\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 0d146932f3a3a30807712f3391b1d457974f6a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:45:39 +0200 Subject: [PATCH 77/89] test(authority): verify provider session bootstrap bindings --- agents/tests/test_provider_session.py | 89 +++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 agents/tests/test_provider_session.py diff --git a/agents/tests/test_provider_session.py b/agents/tests/test_provider_session.py new file mode 100644 index 000000000..582f660c8 --- /dev/null +++ b/agents/tests/test_provider_session.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import os +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +from agents.organism import OrganismStore, OrganizationOrganism +from harness.sdk.provider_session import build_provider_session +from harness.sdk.sovereign_execution import ExecutionIdentityEnvelope, git_head + + +BASE = { + "provider": "openai", + "model": "gpt-5.6-sol", + "session": "cross-provider-001", + "action_class": "D0", + "authority_domain": "organism:read", + "requested_capability": "mcp.organism.status", + "tool": "aegis_organism_status", + "target": ".aegis/runtime/organism.json", + "mutation_target": ".", + "action": {"operation": "read-organism-status"}, +} + + +class ProviderSessionBootstrapTests(unittest.TestCase): + def build(self, **changes): + payload = dict(BASE) + payload.update(changes) + return build_provider_session(payload) + + def test_bootstrap_binds_live_exact_head_and_valid_identity(self): + result = self.build() + identity = ExecutionIdentityEnvelope(**result["identity"]) + self.assertEqual(identity.source_commit, git_head(Path(__file__).resolve().parents[2])) + self.assertEqual(identity.root, result["identity_root"]) + self.assertEqual(result["authority"], "IDENTITY_ONLY_NOT_AUTHORIZATION") + + def test_provider_model_and_session_are_bound(self): + result = self.build(provider="gemini", model="gemini-3.5-flash", session="g-001") + identity = result["identity"] + self.assertEqual(identity["actor_identity"], "provider:gemini") + self.assertEqual(identity["model_identity"], "model:gemini-3.5-flash") + self.assertEqual(identity["session_identity"], "session:g-001") + + def test_same_state_and_action_are_deterministic(self): + first = self.build() + second = self.build() + self.assertEqual(first["identity_root"], second["identity_root"]) + self.assertEqual(first["identity"]["deterministic_nonce"], second["identity"]["deterministic_nonce"]) + + def test_action_change_changes_identity(self): + first = self.build() + second = self.build(action={"operation": "read-organism-status", "scope": "different"}) + self.assertNotEqual(first["identity_root"], second["identity_root"]) + self.assertNotEqual(first["identity"]["action_digest"], second["identity"]["action_digest"]) + + def test_organism_state_root_is_bound(self): + with tempfile.TemporaryDirectory() as td: + store_path = Path(td) / "organism.json" + with patch.dict(os.environ, {"AEGIS_ORGANISM_STORE": str(store_path)}, clear=False): + org = OrganizationOrganism(OrganismStore(store_path)) + org.submit("work-1", "research_request", {"topic": "continuity"}, consequence_class="D1") + expected = org.store.state_root() + result = self.build() + self.assertEqual(result["state_root"], expected) + self.assertEqual(result["identity"]["expected_pre_state"], expected) + self.assertEqual(result["identity"]["parent_state_root"], expected) + + def test_unmapped_capability_fails_closed(self): + with self.assertRaisesRegex(ValueError, "UNMAPPED_CAPABILITY"): + self.build(requested_capability="mcp.does.not.exist") + + def test_unsafe_provider_identity_is_rejected(self): + with self.assertRaises(Exception): + self.build(provider="bad provider") + + def test_bootstrap_never_creates_approval_or_authority(self): + with patch.dict(os.environ, {}, clear=False): + result = self.build() + self.assertEqual(result["identity"]["approval_reference"], "NONE") + self.assertEqual(result["identity"]["observed_authority"], "0.000000") + self.assertEqual(result["authority"], "IDENTITY_ONLY_NOT_AUTHORIZATION") + + +if __name__ == "__main__": + unittest.main() From 66d74aa619fb991f4fc9f68e023adbc7fab237ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:46:04 +0200 Subject: [PATCH 78/89] feat(mcp): bootstrap provider session from live repository state --- .../mcp-server/src/provider-session-client.ts | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 sovereign-omega-v2/mcp-server/src/provider-session-client.ts diff --git a/sovereign-omega-v2/mcp-server/src/provider-session-client.ts b/sovereign-omega-v2/mcp-server/src/provider-session-client.ts new file mode 100644 index 000000000..08166ac4a --- /dev/null +++ b/sovereign-omega-v2/mcp-server/src/provider-session-client.ts @@ -0,0 +1,77 @@ +import { spawnSync } from 'node:child_process' +import { join } from 'node:path' + +const SAFE_ID = /^[A-Za-z0-9._:/@+\-]{1,128}$/ + +export type ProviderActionBootstrapInput = { + actionClass: string + authorityDomain: string + requestedCapability: string + tool: string + target: string + action: Record + mutationTarget?: string +} + +export type ProviderSessionBootstrap = { + identity: Record + identity_root: string + workspace: Record + state_root: string + capability: string + authority: 'IDENTITY_ONLY_NOT_AUTHORIZATION' +} + +export class ProviderSessionError extends Error { + constructor(public readonly code: string, message?: string) { + super(message ?? code) + this.name = 'ProviderSessionError' + } +} + +function requiredProviderEnv(name: string): string { + const value = process.env[name] + if (!value || !SAFE_ID.test(value)) throw new ProviderSessionError(`${name}_UNAVAILABLE_OR_INVALID`) + return value +} + +export function providerSessionConfigured(): boolean { + return Boolean(process.env['AEGIS_PROVIDER_ID'] && process.env['AEGIS_MODEL_ID'] && process.env['AEGIS_PROVIDER_SESSION_ID']) +} + +export function bootstrapProviderAction(root: string, input: ProviderActionBootstrapInput): ProviderSessionBootstrap { + const provider = requiredProviderEnv('AEGIS_PROVIDER_ID') + const model = requiredProviderEnv('AEGIS_MODEL_ID') + const session = requiredProviderEnv('AEGIS_PROVIDER_SESSION_ID') + const python = process.env['AEGIS_PYTHON'] ?? 'python3' + const result = spawnSync(python, [join(root, 'scripts', 'provider-session-bootstrap.py')], { + cwd: root, + env: process.env, + encoding: 'utf8', + input: JSON.stringify({ + provider, + model, + session, + action_class: input.actionClass, + authority_domain: input.authorityDomain, + requested_capability: input.requestedCapability, + tool: input.tool, + target: input.target, + mutation_target: input.mutationTarget ?? '.', + action: input.action, + }), + timeout: 15_000, + maxBuffer: 1_048_576, + }) + if (result.error || result.signal || result.status !== 0) { + throw new ProviderSessionError('PROVIDER_SESSION_BOOTSTRAP_FAILED', result.stderr || result.stdout || result.error?.message) + } + let parsed: unknown + try { parsed = JSON.parse(result.stdout) } catch { throw new ProviderSessionError('PROVIDER_SESSION_BOOTSTRAP_MALFORMED') } + if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) throw new ProviderSessionError('PROVIDER_SESSION_BOOTSTRAP_MALFORMED') + const record = parsed as Record + if (record['authority'] !== 'IDENTITY_ONLY_NOT_AUTHORIZATION') throw new ProviderSessionError('PROVIDER_SESSION_AUTHORITY_BOUNDARY_VIOLATION') + if (typeof record['identity'] !== 'object' || record['identity'] === null || Array.isArray(record['identity'])) throw new ProviderSessionError('PROVIDER_SESSION_IDENTITY_MALFORMED') + if (typeof record['workspace'] !== 'object' || record['workspace'] === null || Array.isArray(record['workspace'])) throw new ProviderSessionError('PROVIDER_SESSION_WORKSPACE_MALFORMED') + return record as unknown as ProviderSessionBootstrap +} From 5e9ae26315128f5d978948e4da353e1db3a16d11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:51:53 +0200 Subject: [PATCH 79/89] feat(mcp): bind provider sessions to durable organism contributions --- sovereign-omega-v2/mcp-server/src/index.ts | 123 ++++++++++++++++++--- 1 file changed, 109 insertions(+), 14 deletions(-) diff --git a/sovereign-omega-v2/mcp-server/src/index.ts b/sovereign-omega-v2/mcp-server/src/index.ts index ea7dece78..ad1dbd0f0 100644 --- a/sovereign-omega-v2/mcp-server/src/index.ts +++ b/sovereign-omega-v2/mcp-server/src/index.ts @@ -19,12 +19,24 @@ import { type ActionClass, type VerifiedAuthorityDecision, } from './authority-response.js' -import { OrganismClientError, readOrganismStatus, recordProviderContribution } from './organism-client.js' +import { + OrganismClientError, + prepareProviderContribution, + readNextWork, + readOrganismStatus, + recordProviderContribution, + recordProviderTextContribution, +} from './organism-client.js' +import { + ProviderSessionError, + bootstrapProviderAction, + providerSessionConfigured, +} from './provider-session-client.js' const BRIDGE = (process.env['AEGIS_BRIDGE_URL'] ?? 'http://localhost:7890').replace(/\/$/, '') const API_KEY = process.env['AEGIS_API_KEY'] ?? '' -const server = new McpServer({ name: 'aegis-constitutional-swarm', version: '0.3.0' }) +const server = new McpServer({ name: 'aegis-constitutional-swarm', version: '0.4.0' }) async function bridgeGet(path: string, apiKey = false): Promise { const headers: Record = { 'Content-Type': 'application/json' } @@ -63,21 +75,40 @@ function authorizeAction(input: { tool: string target: string action: Record + mutationTarget?: string rollbackReference?: string idempotencyKey?: string compensationReference?: string }): AuthorityDecision { + const root = repoRoot() const identityRaw = process.env['AEGIS_EXECUTION_IDENTITY_JSON'] - if (!identityRaw) return localDenial('IDENTITY_UNAVAILABLE') let identity: unknown let workspace: unknown let approval: unknown let trustedAuthorityKeys: Record + let bootstrappedProviderSession = false try { - identity = JSON.parse(identityRaw) - const workspaceRaw = process.env['AEGIS_WORKSPACE_OBSERVATION_JSON'] - if (!workspaceRaw) return localDenial('WORKSPACE_OBSERVATION_UNAVAILABLE') - workspace = JSON.parse(workspaceRaw) + if (identityRaw) { + identity = JSON.parse(identityRaw) + const workspaceRaw = process.env['AEGIS_WORKSPACE_OBSERVATION_JSON'] + if (!workspaceRaw) return localDenial('WORKSPACE_OBSERVATION_UNAVAILABLE') + workspace = JSON.parse(workspaceRaw) + } else if (providerSessionConfigured()) { + const bootstrap = bootstrapProviderAction(root, { + actionClass: input.actionClass, + authorityDomain: input.authorityDomain, + requestedCapability: input.requestedCapability, + tool: input.tool, + target: input.target, + action: input.action, + mutationTarget: input.mutationTarget ?? '.', + }) + identity = bootstrap.identity + workspace = bootstrap.workspace + bootstrappedProviderSession = true + } else { + return localDenial('IDENTITY_UNAVAILABLE') + } if (typeof workspace !== 'object' || workspace === null || Array.isArray(workspace)) { return localDenial('WORKSPACE_OBSERVATION_MALFORMED') } @@ -91,10 +122,10 @@ function authorizeAction(input: { return localDenial('AUTHORITY_VERIFY_KEYS_MALFORMED') } trustedAuthorityKeys = parsedAuthorityKeys as Record - } catch { + } catch (error) { + if (error instanceof ProviderSessionError) return localDenial(error.code) return localDenial('AUTHORITY_ENVIRONMENT_MALFORMED') } - const root = repoRoot() const sourceState = spawnSync('git', ['-C', root, 'rev-parse', 'HEAD'], { encoding: 'utf8', timeout: 5_000, maxBuffer: 65_536, }) @@ -104,8 +135,9 @@ function authorizeAction(input: { if (sourceState.status !== 0 || sourceState.signal || sourceState.error) return localDenial('SOURCE_COMMIT_UNAVAILABLE') if (remoteState.status !== 0 || remoteState.signal || remoteState.error) return localDenial('REMOTE_ORIGIN_UNAVAILABLE') const workspaceRecord = workspace as Record - if (workspaceRecord['remote_origin'] !== remoteState.stdout.trim()) return localDenial('WORKSPACE_REMOTE_CLAIM_MISMATCH') - const boundWorkspace = { ...workspaceRecord, remote_origin: remoteState.stdout.trim() } as { + const actualRemote = remoteState.stdout.trim() + if (!bootstrappedProviderSession && workspaceRecord['remote_origin'] !== actualRemote) return localDenial('WORKSPACE_REMOTE_CLAIM_MISMATCH') + const boundWorkspace = { ...workspaceRecord, remote_origin: actualRemote } as { actual_cwd: string; remote_origin: string; mutation_target: string; path_views?: Record } let bindings @@ -162,6 +194,13 @@ function terminalAdapterUnavailable(authority: VerifiedAuthorityDecision) { return text({ authority, outcome: 'DENIED', denial_codes: ['TERMINAL_EXECUTION_ADAPTER_UNAVAILABLE'], external_effect: 'NOT_EXECUTED' }) } +function configuredProviderIdentity(): { provider: string; model: string; session: string } | null { + const provider = process.env['AEGIS_PROVIDER_ID'] + const model = process.env['AEGIS_MODEL_ID'] + const session = process.env['AEGIS_PROVIDER_SESSION_ID'] + return provider && model && session ? { provider, model, session } : null +} + server.tool('aegis_health', 'Check AEGIS constitutional health: t0_verdict, corruption_count, hash chain status.', {}, async () => { const [health, node] = await Promise.all([bridgeGet('/health'), bridgeGet('/node')]) const ok = (node as Record)['t0_verdict'] === true && (node as Record)['corruption_count'] === 0 @@ -183,6 +222,13 @@ server.tool('aegis_organism_status', 'Read the durable AEGIS organization work l catch (error) { return text({ authority, outcome: 'ERROR', code: error instanceof OrganismClientError ? error.code : 'ORGANISM_STATUS_ERROR' }) } }) +server.tool('aegis_next_work', 'Return queued AEGIS work available to this provider session. This is read-only and grants no claim, lease, or authority.', { limit: z.number().int().min(1).max(100).default(10) }, async ({ limit }) => { + const authority = authorizeAction({ actionClass: 'D0', authorityDomain: 'organism:read', requestedCapability: 'mcp.organism.next', tool: 'aegis_next_work', target: '.aegis/runtime/organism.json', action: { operation: 'read-next-work', limit } }) + if (isDenied(authority)) return denialResponse(authority) + try { return text({ authority, work: readNextWork(repoRoot(), limit), lease: 'NONE', claim: 'NONE' }) } + catch (error) { return text({ authority, outcome: 'ERROR', code: error instanceof OrganismClientError ? error.code : 'ORGANISM_NEXT_WORK_ERROR' }) } +}) + server.tool( 'aegis_contribute', 'Attach a provider/model artifact digest to an existing AEGIS work order. This records NON_AUTHORITATIVE_EVIDENCE only and cannot approve, verify, or admit the work.', @@ -194,13 +240,62 @@ server.tool( source_ref: z.string().regex(/^[A-Za-z0-9._:/@+\-]{1,128}$/), }, async ({ work_id, provider, model, artifact_digest, source_ref }) => { + const configured = configuredProviderIdentity() + if (configured && (configured.provider !== provider || configured.model !== model)) return denialResponse(localDenial('PROVIDER_IDENTITY_MISMATCH')) + const root = repoRoot() + let prepared + try { prepared = prepareProviderContribution(root, work_id) } + catch (error) { return text({ outcome: 'ERROR', code: error instanceof OrganismClientError ? error.code : 'ORGANISM_PREPARE_ERROR', admission_effect: 'NONE' }) } + const action = { + operation: 'record-provider-contribution', work_id, provider, model, artifact_digest, source_ref, + pre_state_root: prepared.state_root, pre_order_digest: prepared.order_digest, rollback_reference: prepared.rollback_reference, + } + const authority = authorizeAction({ + actionClass: 'D1', authorityDomain: 'organism:contribution', requestedCapability: 'mcp.organism.contribute', tool: 'aegis_contribute', target: '.aegis/runtime/organism.json', mutationTarget: '.aegis/runtime', + action, rollbackReference: prepared.rollback_reference, + }) + if (isDenied(authority)) return denialResponse(authority) + try { + const contribution = recordProviderContribution(root, { workId: work_id, provider, model, artifactDigest: artifact_digest, sourceRef: source_ref, rollbackReference: prepared.rollback_reference }) + return text({ authority, contribution, epistemic_status: 'NON_AUTHORITATIVE_EVIDENCE', admission_effect: 'NONE' }) + } catch (error) { + return text({ authority, outcome: 'ERROR', code: error instanceof OrganismClientError ? error.code : 'ORGANISM_CONTRIBUTION_ERROR', admission_effect: 'NONE' }) + } + }, +) + +server.tool( + 'aegis_contribute_text', + 'Persist this provider session output as a content-addressed AEGIS artifact and attach it to an existing work order as NON_AUTHORITATIVE_EVIDENCE.', + { + work_id: z.string().regex(/^[A-Za-z0-9._:/@+\-]{1,128}$/), + text: z.string().min(1).max(262144), + media_type: z.enum(['text/plain', 'text/markdown', 'application/json']).default('text/markdown'), + }, + async ({ work_id, text: contributionText, media_type }) => { + const configured = configuredProviderIdentity() + if (!configured) return denialResponse(localDenial('PROVIDER_SESSION_IDENTITY_REQUIRED')) + const root = repoRoot() + let prepared + try { prepared = prepareProviderContribution(root, work_id) } + catch (error) { return text({ outcome: 'ERROR', code: error instanceof OrganismClientError ? error.code : 'ORGANISM_PREPARE_ERROR', admission_effect: 'NONE' }) } + const textDigest = createHash('sha256').update(contributionText, 'utf8').digest('hex') + const sourceRef = `mcp:${configured.provider}` + const action = { + operation: 'record-provider-text-contribution', work_id, provider: configured.provider, model: configured.model, + text_digest: textDigest, byte_length: Buffer.byteLength(contributionText, 'utf8'), media_type, source_ref: sourceRef, + pre_state_root: prepared.state_root, pre_order_digest: prepared.order_digest, rollback_reference: prepared.rollback_reference, + } const authority = authorizeAction({ - actionClass: 'D1', authorityDomain: 'organism:contribution', requestedCapability: 'mcp.organism.contribute', tool: 'aegis_contribute', target: '.aegis/runtime/organism.json', - action: { operation: 'record-provider-contribution', work_id, provider, model, artifact_digest, source_ref }, + actionClass: 'D1', authorityDomain: 'organism:contribution', requestedCapability: 'mcp.organism.contribute', tool: 'aegis_contribute_text', target: '.aegis/runtime/organism.json', mutationTarget: '.aegis/runtime', + action, rollbackReference: prepared.rollback_reference, }) if (isDenied(authority)) return denialResponse(authority) try { - const contribution = recordProviderContribution(repoRoot(), { workId: work_id, provider, model, artifactDigest: artifact_digest, sourceRef: source_ref }) + const contribution = recordProviderTextContribution(root, { + workId: work_id, provider: configured.provider, model: configured.model, text: contributionText, + sourceRef, mediaType: media_type, rollbackReference: prepared.rollback_reference, + }) return text({ authority, contribution, epistemic_status: 'NON_AUTHORITATIVE_EVIDENCE', admission_effect: 'NONE' }) } catch (error) { return text({ authority, outcome: 'ERROR', code: error instanceof OrganismClientError ? error.code : 'ORGANISM_CONTRIBUTION_ERROR', admission_effect: 'NONE' }) From 74b3b01ea8f3dec393ce9bbfae4a328d306f872a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:52:19 +0200 Subject: [PATCH 80/89] test(mcp): verify durable provider artifact workflow --- .../mcp-server/test/organism-contribution.mjs | 51 +++++++++++++++---- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/sovereign-omega-v2/mcp-server/test/organism-contribution.mjs b/sovereign-omega-v2/mcp-server/test/organism-contribution.mjs index 34c0c78d9..afb0bca6c 100644 --- a/sovereign-omega-v2/mcp-server/test/organism-contribution.mjs +++ b/sovereign-omega-v2/mcp-server/test/organism-contribution.mjs @@ -1,11 +1,17 @@ import assert from 'node:assert/strict' -import { mkdtempSync, rmSync } from 'node:fs' +import { mkdtempSync, readFileSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join, resolve } from 'node:path' import { spawnSync } from 'node:child_process' import { fileURLToPath } from 'node:url' import { dirname } from 'node:path' -import { recordProviderContribution, readOrganismStatus } from '../dist/organism-client.js' +import { + prepareProviderContribution, + readNextWork, + readOrganismStatus, + recordProviderContribution, + recordProviderTextContribution, +} from '../dist/organism-client.js' const here = dirname(fileURLToPath(import.meta.url)) const repoRoot = resolve(here, '../../..') @@ -18,6 +24,14 @@ try { const submitted = spawnSync(process.env.AEGIS_PYTHON, ['-m', 'agents.organism', 'submit', '--id', 'mcp-work-1', '--event', 'research_request', '--payload', '{"topic":"cross-provider"}', '--consequence', 'D1'], { cwd: repoRoot, env: process.env, encoding: 'utf8' }) assert.equal(submitted.status, 0, submitted.stderr) + const available = readNextWork(repoRoot, 10) + assert.deepEqual(available.map((x) => x.work_id), ['mcp-work-1']) + + const prepared = prepareProviderContribution(repoRoot, 'mcp-work-1') + assert.match(prepared.order_digest, /^[0-9a-f]{64}$/) + assert.match(prepared.state_root, /^[0-9a-f]{64}$/) + assert.match(prepared.rollback_reference, /organism:mcp-work-1:/) + const digest = 'a'.repeat(64) const contribution = recordProviderContribution(repoRoot, { workId: 'mcp-work-1', @@ -25,25 +39,42 @@ try { model: 'gpt-5.6-sol', artifactDigest: digest, sourceRef: 'mcp:openai', + rollbackReference: prepared.rollback_reference, }) assert.equal(contribution.authority, 'NON_AUTHORITATIVE_EVIDENCE') assert.match(String(contribution.contribution_ref), /provider:openai:model:gpt-5\.6-sol/) assert.match(String(contribution.contribution_ref), new RegExp(digest)) + const stale = spawnSync(process.env.AEGIS_PYTHON, ['-m', 'agents.organism', 'contribute', '--id', 'mcp-work-1', '--provider', 'gemini', '--model', 'gemini-3.5-flash', '--artifact-digest', 'b'.repeat(64), '--source-ref', 'mcp:gemini', '--rollback-reference', prepared.rollback_reference], { cwd: repoRoot, env: process.env, encoding: 'utf8' }) + assert.notEqual(stale.status, 0) + + const preparedText = prepareProviderContribution(repoRoot, 'mcp-work-1') + const textContribution = recordProviderTextContribution(repoRoot, { + workId: 'mcp-work-1', + provider: 'claude', + model: 'opus', + text: '# Claude contribution\nThis survives the chat.', + sourceRef: 'mcp:claude', + mediaType: 'text/markdown', + rollbackReference: preparedText.rollback_reference, + }) + assert.equal(textContribution.authority, 'NON_AUTHORITATIVE_EVIDENCE') + assert.equal(textContribution.artifact.authority, 'NON_AUTHORITATIVE_EVIDENCE') + assert.match(String(textContribution.artifact.sha256), /^[0-9a-f]{64}$/) + const stored = JSON.parse(readFileSync(String(textContribution.artifact.artifact_path), 'utf8')) + assert.equal(stored.content, '# Claude contribution\nThis survives the chat.') + const status = readOrganismStatus(repoRoot) assert.equal(Array.isArray(status.orders), true) assert.equal(status.orders.length, 1) assert.equal(status.orders[0].status, 'QUEUED') - assert.equal(status.orders[0].contribution_refs.length, 1) - - const again = recordProviderContribution(repoRoot, { - workId: 'mcp-work-1', provider: 'openai', model: 'gpt-5.6-sol', artifactDigest: digest, sourceRef: 'mcp:openai', - }) - assert.equal(again.contribution_ref, contribution.contribution_ref) - const status2 = readOrganismStatus(repoRoot) - assert.equal(status2.orders[0].contribution_refs.length, 1) + assert.equal(status.orders[0].contribution_refs.length, 2) + assert.match(String(status.state_root), /^[0-9a-f]{64}$/) console.log('MCP_ORGANISM_PROVIDER_CONTRIBUTION=PASS') + console.log('MCP_ORGANISM_NEXT_WORK=PASS') + console.log('MCP_ORGANISM_CONTENT_ADDRESSED_TEXT=PASS') + console.log('MCP_ORGANISM_PRESTATE_FENCE=PASS') } finally { rmSync(temp, { recursive: true, force: true }) } From 344c386ee9753c18a658352e8a0a2b5aac6d7870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:54:58 +0200 Subject: [PATCH 81/89] fix(mcp): narrow local denial type --- sovereign-omega-v2/mcp-server/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sovereign-omega-v2/mcp-server/src/index.ts b/sovereign-omega-v2/mcp-server/src/index.ts index ad1dbd0f0..dfa91501b 100644 --- a/sovereign-omega-v2/mcp-server/src/index.ts +++ b/sovereign-omega-v2/mcp-server/src/index.ts @@ -64,7 +64,7 @@ function repoRoot(): string { type DeniedAuthorityDecision = { outcome: 'DENIED'; denial_codes: string[] } type AuthorityDecision = VerifiedAuthorityDecision | DeniedAuthorityDecision -function localDenial(code: string): AuthorityDecision { +function localDenial(code: string): DeniedAuthorityDecision { return { outcome: 'DENIED', denial_codes: [code] } } From dff5c143334ea57b1445f124f8f06c526409dde6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:58:42 +0200 Subject: [PATCH 82/89] test(mcp): add provider-session organism E2E --- .../mcp-server/test/provider-organism-e2e.mjs | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 sovereign-omega-v2/mcp-server/test/provider-organism-e2e.mjs diff --git a/sovereign-omega-v2/mcp-server/test/provider-organism-e2e.mjs b/sovereign-omega-v2/mcp-server/test/provider-organism-e2e.mjs new file mode 100644 index 000000000..b5e6007e6 --- /dev/null +++ b/sovereign-omega-v2/mcp-server/test/provider-organism-e2e.mjs @@ -0,0 +1,104 @@ +import assert from 'node:assert/strict' +import { mkdtempSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { dirname, join, resolve } from 'node:path' +import { spawnSync } from 'node:child_process' +import { fileURLToPath } from 'node:url' +import process from 'node:process' + +import { Client } from '@modelcontextprotocol/sdk/client/index.js' +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' + +const here = dirname(fileURLToPath(import.meta.url)) +const repoRoot = resolve(here, '../../..') +const temp = mkdtempSync(join(tmpdir(), 'aegis-provider-e2e-')) +const organismStore = join(temp, 'organism.json') + +const authorityKeyId = 'authority-test-key' +const authorityPrivateSeed = '4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb' +const authorityPublicKey = '3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c' +const providerEnv = { + ...process.env, + AEGIS_PYTHON: process.env.AEGIS_PYTHON || 'python3', + AEGIS_ORGANISM_STORE: organismStore, + AEGIS_PROVIDER_ID: 'openai', + AEGIS_MODEL_ID: 'gpt-5.6-sol', + AEGIS_PROVIDER_SESSION_ID: 'e2e-openai-001', + AEGIS_AUTHORITY_ISSUER_KEY_ID: authorityKeyId, + AEGIS_AUTHORITY_SIGNING_KEY_HEX: authorityPrivateSeed, + AEGIS_AUTHORITY_VERIFY_KEYS_JSON: JSON.stringify({ [authorityKeyId]: authorityPublicKey }), + AEGIS_TRUSTED_OPERATOR_KEYS_JSON: '{}', +} +delete providerEnv.AEGIS_EXECUTION_IDENTITY_JSON +delete providerEnv.AEGIS_WORKSPACE_OBSERVATION_JSON + +autoSubmit() + +function autoSubmit() { + const submitted = spawnSync( + providerEnv.AEGIS_PYTHON, + ['-m', 'agents.organism', 'submit', '--id', 'provider-e2e-work', '--event', 'research_request', '--payload', '{"topic":"cross-provider durable contribution"}', '--consequence', 'D1'], + { cwd: repoRoot, env: providerEnv, encoding: 'utf8' }, + ) + assert.equal(submitted.status, 0, submitted.stderr || submitted.stdout) +} + +function parseTool(result) { + assert.equal(Array.isArray(result.content), true) + assert.equal(result.content.length > 0, true) + assert.equal(result.content[0].type, 'text') + return JSON.parse(result.content[0].text) +} + +const transport = new StdioClientTransport({ + command: process.execPath, + args: ['dist/index.js'], + env: providerEnv, + stderr: 'pipe', +}) +const client = new Client({ name: 'aegis-provider-organism-e2e', version: '1.0.0' }) + +try { + await client.connect(transport) + + const listed = await client.listTools() + const names = listed.tools.map((tool) => tool.name) + assert(names.includes('aegis_next_work')) + assert(names.includes('aegis_contribute_text')) + + const next = parseTool(await client.callTool({ name: 'aegis_next_work', arguments: { limit: 10 } })) + assert.equal(next.authority.outcome, 'ADMITTED', JSON.stringify(next)) + assert.equal(next.work.length, 1) + assert.equal(next.work[0].work_id, 'provider-e2e-work') + assert.equal(next.lease, 'NONE') + assert.equal(next.claim, 'NONE') + + const contributionText = '# Provider contribution\nOpenAI worker completed bounded research output.' + const contributed = parseTool(await client.callTool({ + name: 'aegis_contribute_text', + arguments: { work_id: 'provider-e2e-work', text: contributionText, media_type: 'text/markdown' }, + })) + assert.equal(contributed.authority.outcome, 'ADMITTED', JSON.stringify(contributed)) + assert.equal(contributed.epistemic_status, 'NON_AUTHORITATIVE_EVIDENCE') + assert.equal(contributed.admission_effect, 'NONE') + assert.equal(contributed.contribution.authority, 'NON_AUTHORITATIVE_EVIDENCE') + assert.equal(contributed.contribution.artifact.content, contributionText) + assert.match(contributed.contribution.artifact.sha256, /^[0-9a-f]{64}$/) + assert.match(contributed.contribution.rollback_reference, /^organism:/) + + const status = spawnSync(providerEnv.AEGIS_PYTHON, ['-m', 'agents.organism', 'status'], { cwd: repoRoot, env: providerEnv, encoding: 'utf8' }) + assert.equal(status.status, 0, status.stderr) + const organism = JSON.parse(status.stdout) + assert.equal(organism.orders.length, 1) + assert.equal(organism.orders[0].status, 'QUEUED') + assert.equal(organism.orders[0].contribution_refs.length, 1) + assert.match(organism.state_root, /^[0-9a-f]{64}$/) + + console.log('MCP_PROVIDER_SESSION_BOOTSTRAP_E2E=PASS') + console.log('MCP_PROVIDER_NEXT_WORK_E2E=PASS') + console.log('MCP_PROVIDER_CONTRIBUTE_TEXT_E2E=PASS') + console.log('MCP_PROVIDER_AUTHORITY_SELF_PROMOTION=ABSENT') +} finally { + await client.close().catch(() => {}) + rmSync(temp, { recursive: true, force: true }) +} From 6617730fe330751315aa552ddc63018144c3f07a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 14:58:37 +0200 Subject: [PATCH 83/89] test(mcp): wire provider organism E2E --- sovereign-omega-v2/mcp-server/package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sovereign-omega-v2/mcp-server/package.json b/sovereign-omega-v2/mcp-server/package.json index 5ae402a98..7e53ab28e 100644 --- a/sovereign-omega-v2/mcp-server/package.json +++ b/sovereign-omega-v2/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "@aegis/mcp-server", - "version": "0.3.0", + "version": "0.4.0", "description": "AEGIS constitutional agent swarm — MCP server", "type": "module", "main": "dist/index.js", @@ -11,7 +11,8 @@ "dev": "tsx src/index.ts", "test:resources": "npm run build && node test/resources.mjs", "test:automaton3": "npm run build && node test/authority-response.mjs && node test/automaton3-authority.mjs", - "test:organism": "npm run build && node test/organism-contribution.mjs" + "test:organism": "npm run build && node test/organism-contribution.mjs", + "test:provider-e2e": "npm run build && node test/provider-organism-e2e.mjs" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1" From 1f9be70092e1b568025fb6dedce979bd5de350cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:57:43 +0200 Subject: [PATCH 84/89] fix(sol): patch vulnerable edge dependency chain --- platform/sol/cloudflare/worker/package.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/platform/sol/cloudflare/worker/package.json b/platform/sol/cloudflare/worker/package.json index 82a7db0d7..a6ebd42af 100644 --- a/platform/sol/cloudflare/worker/package.json +++ b/platform/sol/cloudflare/worker/package.json @@ -22,9 +22,12 @@ "devDependencies": { "typescript": "5.9.3", "vitest": "3.2.7", - "wrangler": "4.114.0" + "wrangler": "4.124.0" }, "overrides": { - "@hono/node-server": "2.0.12" + "@hono/node-server": "2.0.12", + "hono": "4.12.34", + "nanoid": "3.3.18", + "undici": "7.29.0" } } From cbfd43d357c61b2926c34506be170c190028eb0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:59:16 +0200 Subject: [PATCH 85/89] fix(admission): rebind PR 225 to canonical main --- .../pr-225-sol-cross-platform-control-plane-v1.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json b/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json index 026611d14..c10705699 100644 --- a/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json +++ b/.aegis/experiments/pr-225-sol-cross-platform-control-plane-v1.json @@ -5,8 +5,8 @@ "repository": "Aegis-Omega/AEGIS-OMEGA", "execution_class": "EXPERIMENT", "evidence_tier": "T1", - "expected_parent_sha": "001fcb3aa92efd18a226ac5eb5b5bd6ccd0d512a", - "expected_parent_state_root": "98a96e746e60d80d075d67cbd7759cac8c9a4c954319386eb17bc245fe9f3a1b", + "expected_parent_sha": "32b7eb6a37fb69d19dd80189390b6641c5004ef1", + "expected_parent_state_root": "7ae848d409f35b2804fbcb009a43df1d9da0a5fbd348564b81d771863223e253", "constitution": { "path": "CONSTITUTIONAL_DECLARATION.md", "blob_id": "d0c210443e03313113e43da46c4d98269494baeb" From 0da74dabc14ca1b27fdfeac0ae65d2aa7ce328ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:04:12 +0200 Subject: [PATCH 86/89] fix(governance): remove unleased Automaton-2 branch writes --- .github/workflows/automaton-2.yml | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/.github/workflows/automaton-2.yml b/.github/workflows/automaton-2.yml index 31144a7aa..004cc1c4d 100644 --- a/.github/workflows/automaton-2.yml +++ b/.github/workflows/automaton-2.yml @@ -9,7 +9,7 @@ on: branches: [main] permissions: - contents: write + contents: read id-token: write attestations: write artifact-metadata: write @@ -76,23 +76,12 @@ jobs: if-no-files-found: error retention-days: 90 - - name: Promote anchors on same-repository PR - if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} + - name: Verify committed anchors match governed candidate shell: bash run: | set -euo pipefail - if cmp -s "$RUNNER_TEMP/cognitive-anchors/.claude.json" .claude.json 2>/dev/null && \ - cmp -s "$RUNNER_TEMP/cognitive-anchors/skill-hashes.sha256" skill-hashes.sha256 2>/dev/null; then - echo "Committed anchors already current." - exit 0 - fi - cp "$RUNNER_TEMP/cognitive-anchors/.claude.json" .claude.json - cp "$RUNNER_TEMP/cognitive-anchors/skill-hashes.sha256" skill-hashes.sha256 - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add .claude.json skill-hashes.sha256 - git commit -m "chore(manifest): promote governed cognitive anchors" - git push origin "HEAD:${{ github.event.pull_request.head.ref }}" + cmp "$RUNNER_TEMP/cognitive-anchors/.claude.json" .claude.json + cmp "$RUNNER_TEMP/cognitive-anchors/skill-hashes.sha256" skill-hashes.sha256 - name: Run Automaton-2 negative and determinism tests run: python sovereign-omega-v2/python/tests/test_automaton2.py From 1b2c2c523ed263de55262b2209567ab918447151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:04:28 +0200 Subject: [PATCH 87/89] fix(governance): make cognitive refresh mutation-free --- .../workflows/cognitive-manifest-refresh.yml | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/cognitive-manifest-refresh.yml b/.github/workflows/cognitive-manifest-refresh.yml index 95ff05cfa..6214b2d7f 100644 --- a/.github/workflows/cognitive-manifest-refresh.yml +++ b/.github/workflows/cognitive-manifest-refresh.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: permissions: - contents: write + contents: read concurrency: group: cognitive-manifest-refresh-${{ github.ref }} @@ -40,26 +40,26 @@ jobs: fi echo "state_hash=$parent_hash" >> "$GITHUB_OUTPUT" - - name: Generate deterministic anchors + - name: Generate deterministic anchors without mutating branch + shell: bash run: | + set -euo pipefail + out="$RUNNER_TEMP/cognitive-anchors" python scripts/build-cognitive-manifest.py \ --ref "$GITHUB_REF_NAME" \ - --parent-state-hash "${{ steps.parent.outputs.state_hash }}" + --parent-state-hash "${{ steps.parent.outputs.state_hash }}" \ + --output-dir "$out" python scripts/build-cognitive-manifest.py \ --check \ --ref "$GITHUB_REF_NAME" \ - --parent-state-hash "${{ steps.parent.outputs.state_hash }}" + --parent-state-hash "${{ steps.parent.outputs.state_hash }}" \ + --output-dir "$out" - - name: Commit refreshed anchors - shell: bash - run: | - set -euo pipefail - if git diff --quiet -- .claude.json skill-hashes.sha256; then - echo "Manifest already current." - exit 0 - fi - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add .claude.json skill-hashes.sha256 - git commit -m "chore(manifest): refresh cognitive-state anchors" - git push origin "HEAD:${GITHUB_REF_NAME}" + - name: Upload proposed anchors for governed admission + uses: actions/upload-artifact@v4 + with: + name: aegis-cognitive-anchor-candidate-${{ github.sha }} + path: ${{ runner.temp }}/cognitive-anchors + include-hidden-files: true + if-no-files-found: error + retention-days: 90 From f61eb2cffdb8323e90f20c35372dffa5f401b70e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:04:42 +0200 Subject: [PATCH 88/89] fix(governance): reconcile workflow write capability allowlist --- .github/write-capability-allowlist.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/write-capability-allowlist.yml b/.github/write-capability-allowlist.yml index cf09b45ff..48718ff22 100644 --- a/.github/write-capability-allowlist.yml +++ b/.github/write-capability-allowlist.yml @@ -10,16 +10,10 @@ }, "workflows": { ".github/workflows/automaton-2.yml": { - "allowed_permissions": ["contents", "id-token"], - "may_git_push": true, - "authority_domain": "governance:anchor-promotion", - "lease_fencing_required": true - }, - ".github/workflows/cognitive-manifest-refresh.yml": { - "allowed_permissions": ["contents"], - "may_git_push": true, - "authority_domain": "governance:cognitive-manifest", - "lease_fencing_required": true + "allowed_permissions": ["id-token"], + "may_git_push": false, + "authority_domain": "governance:anchor-verification", + "lease_fencing_required": false }, ".github/workflows/automaton-3.yml": { "allowed_permissions": ["id-token"], @@ -39,6 +33,12 @@ "authority_domain": "deployment:gcp-cloud-run", "lease_fencing_required": false }, + ".github/workflows/deploy.yml": { + "allowed_permissions": ["id-token"], + "may_git_push": false, + "authority_domain": "deployment:gcp-cloud-run", + "lease_fencing_required": false + }, ".github/workflows/jekyll-gh-pages.yml": { "allowed_permissions": ["id-token"], "may_git_push": false, From 476c236548386ef46cd33bea4e1f5ec801202cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:06:38 +0200 Subject: [PATCH 89/89] ci(sol): expose and enforce resolved lockfile --- .github/workflows/sol-integration.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/sol-integration.yml b/.github/workflows/sol-integration.yml index 6671fbe76..19554f033 100644 --- a/.github/workflows/sol-integration.yml +++ b/.github/workflows/sol-integration.yml @@ -88,3 +88,14 @@ jobs: - name: Audit installed dependency graph run: npm audit --audit-level=high + + - name: Upload resolved lockfile for reconciliation + uses: actions/upload-artifact@v4 + with: + name: sol-edge-resolved-lock-${{ github.event.pull_request.head.sha || github.sha }} + path: platform/sol/cloudflare/worker/package-lock.json + if-no-files-found: error + retention-days: 7 + + - name: Require resolved lockfile to be committed + run: git diff --exit-code -- package-lock.json