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..c10705699 --- /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": "32b7eb6a37fb69d19dd80189390b6641c5004ef1", + "expected_parent_state_root": "7ae848d409f35b2804fbcb009a43df1d9da0a5fbd348564b81d771863223e253", + "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": "dbe281ec13d2ac473ed9ecb44677d6d3e4450c1924db1d55f3dee81bf196ab73", + "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 + } +} 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" } 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 diff --git a/.github/workflows/automaton-3.yml b/.github/workflows/automaton-3.yml index 77e1b60d1..715593ac2 100644 --- a/.github/workflows/automaton-3.yml +++ b/.github/workflows/automaton-3.yml @@ -47,10 +47,12 @@ 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 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 @@ -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/.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 diff --git a/.github/workflows/sol-integration.yml b/.github/workflows/sol-integration.yml new file mode 100644 index 000000000..19554f033 --- /dev/null +++ b/.github/workflows/sol-integration.yml @@ -0,0 +1,101 @@ +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 . + + 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 + + - 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 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 diff --git a/.github/write-capability-allowlist.yml b/.github/write-capability-allowlist.yml new file mode 100644 index 000000000..48718ff22 --- /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": ["id-token"], + "may_git_push": false, + "authority_domain": "governance:anchor-verification", + "lease_fencing_required": false + }, + ".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/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, + "authority_domain": "deployment:github-pages", + "lease_fencing_required": false + } + } +} 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/agents/organism.py b/agents/organism.py new file mode 100644 index 000000000..e827d592b --- /dev/null +++ b/agents/organism.py @@ -0,0 +1,451 @@ +"""Durable, fail-closed organizational work loop for AEGIS Ω. + +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 + +import asyncio +import hashlib +import json +import os +import re +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" +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): + 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 _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")) + 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() + + +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) + 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: + _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"] + 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 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()) + 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, 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]): + 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 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: + 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 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): + 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", + "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", 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, + 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] + + 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: + items = self.next_work(limit=1) + return items[0] if items else 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] = 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(dict.fromkeys(refs)) + order.last_error = None + else: + order.status = WorkStatus.FAILED + order.last_error = "INVALID_AGENT_RESULT" + 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 + + 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 + import sys + 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_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) + 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) + p_contrib.add_argument("--rollback-reference") + 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 == "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)) + 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 == "contribute": + 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(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()), "state_root": org.store.state_root()}, sort_keys=True)) + + +if __name__ == "__main__": + main() 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() diff --git a/agents/tests/test_provider_contribution.py b/agents/tests/test_provider_contribution.py new file mode 100644 index 000000000..0df84fd1c --- /dev/null +++ b/agents/tests/test_provider_contribution.py @@ -0,0 +1,143 @@ +from __future__ import annotations + +import hashlib +import json +import tempfile +import unittest +from pathlib import Path + +from agents.organism import MAX_TEXT_CONTRIBUTION_BYTES, 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) + + 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") + + 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() 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() diff --git a/docs/adr/ADR-0021-automaton-3-sovereign-execution.md b/docs/adr/ADR-0021-automaton-3-sovereign-execution.md index d54c0eaa9..c041d4332 100644 --- a/docs/adr/ADR-0021-automaton-3-sovereign-execution.md +++ b/docs/adr/ADR-0021-automaton-3-sovereign-execution.md @@ -11,13 +11,27 @@ 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 -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 @@ -26,10 +40,35 @@ 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. ## 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. + +## 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. + +## Projection prohibition + +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..e98c0f1fe --- /dev/null +++ b/docs/adr/ADR-0022-cross-runtime-authoritative-receipt-provenance.md @@ -0,0 +1,311 @@ +# 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. + +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. + +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/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/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/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"]}, 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/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", + } 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/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. 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 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, + } 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, + } 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. 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 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" + } + } + } +} diff --git a/platform/sol/cloudflare/worker/package.json b/platform/sol/cloudflare/worker/package.json new file mode 100644 index 000000000..a6ebd42af --- /dev/null +++ b/platform/sol/cloudflare/worker/package.json @@ -0,0 +1,33 @@ +{ + "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.124.0" + }, + "overrides": { + "@hono/node-server": "2.0.12", + "hono": "4.12.34", + "nanoid": "3.3.18", + "undici": "7.29.0" + } +} 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', + }, + }) +} 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 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) + }) +}) 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" + ] +} 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" + } + ] +} 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 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 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 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"] } + } + } + } +} 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. 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" + ] +} 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) 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. 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 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 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() 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() diff --git a/platform/sol/tests/test_ogemma_adapter.py b/platform/sol/tests/test_ogemma_adapter.py new file mode 100644 index 000000000..d3daf35b5 --- /dev/null +++ b/platform/sol/tests/test_ogemma_adapter.py @@ -0,0 +1,106 @@ +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 ogemma import ( # noqa: E402 + 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() 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() 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), + } 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 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. 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/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/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/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/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/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()) 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/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()) diff --git a/scripts/run-automaton3-tests.py b/scripts/run-automaton3-tests.py index 6f2dcb114..de83a95b7 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 @@ -13,8 +14,45 @@ 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 = { + "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", + "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", + "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", + }, + "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", + }, +} + def main() -> int: parser = argparse.ArgumentParser() @@ -26,7 +64,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 +73,46 @@ 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 = 81 + 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"], + "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, "normalized_log_sha256": hashlib.sha256(log.encode()).hexdigest(), } diff --git a/scripts/validate-automaton3.py b/scripts/validate-automaton3.py index 5297fa378..d33e42580 100644 --- a/scripts/validate-automaton3.py +++ b/scripts/validate-automaton3.py @@ -17,21 +17,42 @@ "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", + "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", @@ -124,14 +145,20 @@ 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") != 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") 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: 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/mcp-server/package.json b/sovereign-omega-v2/mcp-server/package.json index 652332bb6..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.2.0", + "version": "0.4.0", "description": "AEGIS constitutional agent swarm — MCP server", "type": "module", "main": "dist/index.js", @@ -10,7 +10,9 @@ "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", + "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" 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..b05af71f1 --- /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: remote, + 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..dfa91501b 100644 --- a/sovereign-omega-v2/mcp-server/src/index.ts +++ b/sovereign-omega-v2/mcp-server/src/index.ts @@ -12,11 +12,31 @@ 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' +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.2.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' } @@ -26,17 +46,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 +61,99 @@ 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 { +function localDenial(code: string): DeniedAuthorityDecision { 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 + 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 workspace: unknown let approval: unknown + let trustedAuthorityKeys: Record + let bootstrappedProviderSession = false try { - identity = JSON.parse(identityRaw) - workspace = JSON.parse(process.env['AEGIS_WORKSPACE_OBSERVATION_JSON'] ?? '{}') + 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') + } const approvalRaw = process.env['AEGIS_APPROVAL_GRANT_JSON'] approval = approvalRaw ? JSON.parse(approvalRaw) : undefined - } catch { + 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 (error) { + if (error instanceof ProviderSessionError) return localDenial(error.code) return localDenial('AUTHORITY_ENVIRONMENT_MALFORMED') } + 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 + 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 + 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 +162,43 @@ 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' }) +} + +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 () => { @@ -131,37 +211,125 @@ 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_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_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.', + { + 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 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_text', target: '.aegis/runtime/organism.json', mutationTarget: '.aegis/runtime', + action, rollbackReference: prepared.rollback_reference, + }) + if (isDenied(authority)) return denialResponse(authority) + try { + 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' }) + } + }, +) + 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 +339,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/src/organism-client.ts b/sovereign-omega-v2/mcp-server/src/organism-client.ts new file mode 100644 index 000000000..4dc43f9bf --- /dev/null +++ b/sovereign-omega-v2/mcp-server/src/organism-client.ts @@ -0,0 +1,166 @@ +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 + provider: string + model: string + artifactDigest: string + sourceRef: string + rollbackReference?: string +} + +export type ProviderTextContributionInput = { + workId: string + provider: string + model: string + 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 { + 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) +} + +function runPython(root: string, args: string[], input?: string): string { + const python = process.env['AEGIS_PYTHON'] ?? 'python3' + 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_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(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' || (digest !== undefined && !ref.includes(digest))) { + throw new OrganismClientError('ORGANISM_CONTRIBUTION_BINDING_MISMATCH') + } +} + +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', + '--id', input.workId, + '--provider', input.provider, + '--model', input.model, + '--artifact-digest', input.artifactDigest, + '--source-ref', input.sourceRef, + '--rollback-reference', rollbackReference, + ]) + const record = parseObject(raw) + assertContributionBoundary(record, input.artifactDigest) + return record +} + +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 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, + provider: input.provider, + model: input.model, + text: input.text, + source_ref: input.sourceRef, + media_type: mediaType, + rollback_reference: rollbackReference, + })) + const record = parseObject(raw) + const artifact = record['artifact'] + if (typeof artifact !== 'object' || artifact === null || Array.isArray(artifact)) { + throw new OrganismClientError('ORGANISM_ARTIFACT_MALFORMED') + } + const digest = (artifact as Record)['sha256'] + 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 +} + +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'])) +} 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 +} 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/mcp-server/test/organism-contribution.mjs b/sovereign-omega-v2/mcp-server/test/organism-contribution.mjs new file mode 100644 index 000000000..afb0bca6c --- /dev/null +++ b/sovereign-omega-v2/mcp-server/test/organism-contribution.mjs @@ -0,0 +1,80 @@ +import assert from 'node:assert/strict' +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 { + prepareProviderContribution, + readNextWork, + readOrganismStatus, + recordProviderContribution, + recordProviderTextContribution, +} 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 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', + provider: 'openai', + 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, 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 }) +} 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 }) +} 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/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/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/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/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/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/metacognition/outcome-comparator.ts b/sovereign-omega-v2/src/metacognition/outcome-comparator.ts new file mode 100644 index 000000000..a339f120a --- /dev/null +++ b/sovereign-omega-v2/src/metacognition/outcome-comparator.ts @@ -0,0 +1,1141 @@ +// ============================================================ +// 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 + readonly trust_policy: OutcomeVerifierTrustPolicyV1 +} + +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 + /** 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 + 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: 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 +} + +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 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`) + } +} + +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 { + 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[] { + 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) => { + 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`) + } + 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 { + 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') + } + 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 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: 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 +} + +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', + 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) { + assertResolvedHash(`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`) + } + 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 normalized = normalizeOutcomeEvidenceInputV1(input) + return hashValue({ + domain: 'AEGIS_OUTCOME_EVIDENCE_BUNDLE_V1', + evidence: { + ...normalized, + terminal_execution: normalized.terminal_execution ?? null, + }, + }) +} + +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_authenticated = false + 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_authenticated = anchorPolicyMatches && + anchorTrustRootMatches && + verifierIsTrusted && + verifierIdentityMatches && + verifierIsIndependent && + bundleMatches && + signatureValid && + observationIdentitiesMatch + evidence_certificate_verified = evidence_certificate_authenticated && 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_authenticated, + 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 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, + 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 normalizedInput = normalizeAdaptationOutcomeInputV1(input) + 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, + trust_policy: trustAnchor.trust_policy, + }, + 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/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 new file mode 100644 index 000000000..a4f0e5634 --- /dev/null +++ b/sovereign-omega-v2/src/metacognition/self-regulator.ts @@ -0,0 +1,431 @@ +// ============================================================ +// 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' +import { compareUtf8 } from '../core/ordering.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 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 SHA256Hex[] +} + +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 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]:/ +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', + '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 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) => { + assertNonEmpty(`${field}[${index}]`, value) + return value.trim() + }) + const unique = [...new Set(normalized)].sort(compareUtf8) + if (unique.length !== normalized.length) throw new SelfRegulationError(`${field} must be unique`) + return unique +} + +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 { + 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) + 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') + } + 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 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) => { + 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`) + 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) => { + assertResolvedHash(`gaps[${index}].evidence_refs[${evidenceIndex}]`, reference) + return reference + }) + return { + gap_id: gap.gap_id, + kind: gap.kind, + severity: gap.severity, + evidence_refs, + } + }) + return normalized.sort((a, b) => compareUtf8(a.gap_id, b.gap_id)) +} + +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) => { + 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, 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(), + 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, + ...(rollback_reference === undefined ? {} : { rollback_reference }), + ...(operator_approval_reference === undefined ? {} : { operator_approval_reference }), + ...(constitutional_change_reference === undefined ? {} : { 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 { 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, + 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 = normalizeAdaptationProposal(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.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 (['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) { + 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 }) +} 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/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/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/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/integration/metacognitive-outcome-closure.test.ts b/sovereign-omega-v2/test/integration/metacognitive-outcome-closure.test.ts new file mode 100644 index 000000000..2cfe671cb --- /dev/null +++ b/sovereign-omega-v2/test/integration/metacognitive-outcome-closure.test.ts @@ -0,0 +1,682 @@ +import 'fake-indexeddb/auto' + +import { describe, expect, it } from 'vitest' +import { hashValue } from '../../src/core/hashing.js' +import { + MetacognitiveLoop, + certifyMetacognitiveLoop, +} from '../../src/metacognition/loop.js' +import { + 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, +} from '../../src/metacognition/outcome-comparator.js' +import { regulateSelf } from '../../src/metacognition/self-regulator.js' +import { + 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) + }, + } + return { store, persistCalls: () => persistCalls } +} + +describe('metacognitive adaptation outcome closure', () => { + 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 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 + } + 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 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() + }) + + 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() + } + }) + + 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 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 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 + } + 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 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 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() + + 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), + ) + 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 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/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/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/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 new file mode 100644 index 000000000..2940c2c16 --- /dev/null +++ b/sovereign-omega-v2/test/unit/outcome-comparator.test.ts @@ -0,0 +1,677 @@ +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.evidence_certificate_authenticated).toBe(true) + expect(assessment.evidence_certificate_verified).toBe(true) + 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.evidence_certificate_authenticated).toBe(true) + expect(assessment.evidence_certificate_verified).toBe(false) + 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 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({ + ...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/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 new file mode 100644 index 000000000..d7ef48bd5 --- /dev/null +++ b/sovereign-omega-v2/test/unit/self-regulator.test.ts @@ -0,0 +1,354 @@ +// ============================================================ +// SOVEREIGN OMEGA — Metacognitive Self-Regulator tests +// ============================================================ + +import { describe, expect, it } from 'vitest' +import type { SHA256Hex } from '../../src/core/types.js' +import { + hashSelfModelStateRootV1, + 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 + +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, + membrane_intact: true, + entropy_bounded: true, + ...overrides, + }, + } + return { state_root: await hashSelfModelStateRootV1(components), ...components } +} + +const GAP: KnowledgeGap = { + gap_id: 'gap.self-model.001', + kind: 'CAPABILITY_DEFICIT', + severity: 'HIGH', + evidence_refs: [H('8')], +} + +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: parentStateRoot, + 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, + } +} + +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: 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: 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: model, + gaps: [GAP], + proposal: proposal(model.state_root), + }) + 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 model = await snapshot({ entropy_bounded: false }) + const decision = await regulateSelf({ + snapshot: model, + gaps: [GAP], + 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: model, + gaps: [GAP], + 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: model, + gaps: [GAP], + 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: model, + gaps: [GAP], + 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('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({ + snapshot: model, + gaps: [GAP], + proposal: proposal(model.state_root), + }) + 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 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) + expect(first.decision_digest).toBe(second.decision_digest) + }) + + it('fails closed on malformed self-model roots', async () => { + 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', + ) + }) +}) 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"} 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 +
+
+ + + +
+ + + +
+ +
+ + + + +
+
+
+ ) +}