diff --git a/.github/workflows/sourceos-interaction-evidence-binding.yml b/.github/workflows/sourceos-interaction-evidence-binding.yml new file mode 100644 index 0000000..bb944a4 --- /dev/null +++ b/.github/workflows/sourceos-interaction-evidence-binding.yml @@ -0,0 +1,40 @@ +name: SourceOS Interaction Evidence Binding + +on: + pull_request: + branches: ["main"] + paths: + - "docs/integration/sourceos-interaction-evidence-binding.md" + - "schemas/integration/sourceos-interaction-evidence-binding.v0.1.schema.json" + - "tests/fixtures/integration/sourceos-interaction-evidence-binding*.json" + - "tools/validate_sourceos_interaction_evidence_binding.py" + - ".github/workflows/sourceos-interaction-evidence-binding.yml" + push: + branches: ["main", "work/sourceos-interaction-evidence-251"] + paths: + - "docs/integration/sourceos-interaction-evidence-binding.md" + - "schemas/integration/sourceos-interaction-evidence-binding.v0.1.schema.json" + - "tests/fixtures/integration/sourceos-interaction-evidence-binding*.json" + - "tools/validate_sourceos_interaction_evidence_binding.py" + - ".github/workflows/sourceos-interaction-evidence-binding.yml" + +permissions: + contents: read + +jobs: + validate-sourceos-interaction-evidence-binding: + name: Validate SourceOS interaction evidence binding + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install validator dependency + run: python -m pip install --upgrade pip jsonschema + + - name: Validate binding fixtures + run: python tools/validate_sourceos_interaction_evidence_binding.py diff --git a/docs/integration/sourceos-interaction-evidence-binding.md b/docs/integration/sourceos-interaction-evidence-binding.md new file mode 100644 index 0000000..ecea56c --- /dev/null +++ b/docs/integration/sourceos-interaction-evidence-binding.md @@ -0,0 +1,81 @@ +# SourceOSInteractionEvent evidence binding + +## Status + +Fixture, schema, and validation binding only. This document does not add runtime execution behavior. + +AgentPlane owns execution evidence, replay artifacts, validation artifacts, placement artifacts, and run artifacts. It may attach those authoritative references to a `SourceOSInteractionEvent` governance trace so Noetica, AgentTerm, and Superconscious can render the same interaction lifecycle without owning execution evidence semantics. + +## Canonical interaction contract + +`SourceOSInteractionEvent` is owned by `SourceOS-Linux/sourceos-spec`: + +- `schemas/SourceOSInteractionEvent.json` +- `generated/typescript/sourceos-interaction-event.ts` +- `generated/python/sourceos_interaction_event.py` + +AgentPlane does not own this schema. AgentPlane owns the evidence artifacts referenced by the interaction event. + +## Required AgentPlane references + +A SourceOS interaction evidence binding records: + +- `source_interaction_event_ref` +- `result_interaction_event_ref` +- `agentplane_run_ref` +- `validation_artifact_ref` +- `placement_decision_ref` +- `run_artifact_ref` +- `evidence_artifact_refs` +- `replay_ref` +- `context_pack_refs` +- `policy_decision_refs` +- `redaction_refs` + +The binding exists so an interaction event can carry AgentPlane evidence references without copying raw execution logs, unrestricted stdout/stderr, secrets, credentials, or private chain-of-thought. + +## Authority boundaries + +AgentPlane owns: + +- execution evidence; +- run artifacts; +- replay artifacts; +- validation artifacts; +- placement artifacts; +- evidence bundle refs. + +AgentPlane does not own: + +- browser, terminal, Matrix, or Noetica UI surfaces; +- Policy Fabric policy admission; +- Agent Registry identity, grants, sessions, or revocation; +- Memory Mesh durable memory or context-pack semantics; +- SourceOSInteractionEvent schema ownership. + +## Expected flow + +```text +Noetica / AgentTerm / Superconscious source event + -> SourceOSInteractionEvent ref + -> AgentPlane validation / placement / run / evidence / replay + -> SourceOS interaction evidence binding + -> result SourceOSInteractionEvent ref with AgentPlane refs attached + -> AgentTerm or Noetica renders the governance trace +``` + +## Payload posture + +The binding is reference-oriented. It must not contain raw execution logs, unrestricted shell output, unrestricted transcripts, credentials, secrets, private chain-of-thought, or unredacted stdout/stderr. + +Use artifact refs and hashes instead. + +## Validation + +Run: + +```bash +python3 tools/validate_sourceos_interaction_evidence_binding.py tests/fixtures/integration/sourceos-interaction-evidence-binding.valid.json +``` + +Invalid fixtures prove that missing replay refs and raw log leakage fail closed. diff --git a/schemas/integration/sourceos-interaction-evidence-binding.v0.1.schema.json b/schemas/integration/sourceos-interaction-evidence-binding.v0.1.schema.json new file mode 100644 index 0000000..2fe936c --- /dev/null +++ b/schemas/integration/sourceos-interaction-evidence-binding.v0.1.schema.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.socioprophet.org/agentplane/integration/sourceos-interaction-evidence-binding.v0.1.schema.json", + "title": "SourceOS Interaction Evidence Binding v0.1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "recordType", + "binding_id", + "source_interaction_event_ref", + "result_interaction_event_ref", + "agentplane_run_ref", + "validation_artifact_ref", + "placement_decision_ref", + "run_artifact_ref", + "evidence_artifact_refs", + "replay_ref", + "policy_decision_refs", + "context_pack_refs", + "redaction_refs", + "authority_boundaries", + "payload_mode", + "claim_boundary", + "issued_at" + ], + "properties": { + "schemaVersion": {"const": "agentplane.sourceos-interaction-evidence-binding.v0.1"}, + "recordType": {"const": "SourceOSInteractionEvidenceBinding"}, + "binding_id": {"type": "string", "pattern": "^urn:srcos:agentplane:sourceos-interaction-evidence-binding:"}, + "source_interaction_event_ref": {"type": "string", "pattern": "^urn:srcos:interaction-event:"}, + "result_interaction_event_ref": {"type": "string", "pattern": "^urn:srcos:interaction-event:"}, + "agentplane_run_ref": {"type": "string", "minLength": 1}, + "validation_artifact_ref": {"type": "string", "minLength": 1}, + "placement_decision_ref": {"type": "string", "minLength": 1}, + "run_artifact_ref": {"type": "string", "minLength": 1}, + "evidence_artifact_refs": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}, + "replay_ref": {"type": "string", "minLength": 1}, + "policy_decision_refs": {"type": "array", "items": {"type": "string", "minLength": 1}}, + "context_pack_refs": {"type": "array", "items": {"type": "string", "minLength": 1}}, + "redaction_refs": {"type": "array", "items": {"type": "string", "minLength": 1}}, + "authority_boundaries": { + "type": "object", + "additionalProperties": false, + "required": [ + "agentplane", + "sourceos_spec", + "policy_fabric", + "agent_registry", + "memory_mesh", + "noetica", + "agent_term", + "superconscious" + ], + "properties": { + "agentplane": {"const": "execution-evidence-replay-authority"}, + "sourceos_spec": {"const": "canonical-interaction-schema-owner"}, + "policy_fabric": {"const": "policy-admission-authority"}, + "agent_registry": {"const": "identity-grant-authority"}, + "memory_mesh": {"const": "memory-context-pack-authority"}, + "noetica": {"const": "browser-chat-surface"}, + "agent_term": {"const": "terminal-operator-surface"}, + "superconscious": {"const": "task-cognition-coordinator"} + } + }, + "payload_mode": {"type": "string", "enum": ["metadata-only", "summary", "ref-only", "inline-bounded", "redacted"]}, + "claim_boundary": {"type": "string", "minLength": 1}, + "issued_at": {"type": "string", "minLength": 1} + } +} diff --git a/tests/fixtures/integration/sourceos-interaction-evidence-binding.missing-replay.invalid.json b/tests/fixtures/integration/sourceos-interaction-evidence-binding.missing-replay.invalid.json new file mode 100644 index 0000000..40aad32 --- /dev/null +++ b/tests/fixtures/integration/sourceos-interaction-evidence-binding.missing-replay.invalid.json @@ -0,0 +1,30 @@ +{ + "schemaVersion": "agentplane.sourceos-interaction-evidence-binding.v0.1", + "recordType": "SourceOSInteractionEvidenceBinding", + "binding_id": "urn:srcos:agentplane:sourceos-interaction-evidence-binding:missing-replay-0001", + "source_interaction_event_ref": "urn:srcos:interaction-event:noetica-standalone-complete-0001", + "result_interaction_event_ref": "urn:srcos:interaction-event:agentplane-run-completed-0001", + "agentplane_run_ref": "urn:srcos:agentplane:run:noetica-superconscious-run-0001", + "validation_artifact_ref": "artifact://agentplane/noetica-superconscious-run-0001/validation-artifact.json#sha256:1111111111111111111111111111111111111111111111111111111111111111", + "placement_decision_ref": "artifact://agentplane/noetica-superconscious-run-0001/placement-decision.json#sha256:2222222222222222222222222222222222222222222222222222222222222222", + "run_artifact_ref": "artifact://agentplane/noetica-superconscious-run-0001/run-artifact.json#sha256:3333333333333333333333333333333333333333333333333333333333333333", + "evidence_artifact_refs": [ + "artifact://agentplane/noetica-superconscious-run-0001/evidence-bundle.json#sha256:4444444444444444444444444444444444444444444444444444444444444444" + ], + "policy_decision_refs": [], + "context_pack_refs": [], + "redaction_refs": [], + "authority_boundaries": { + "agentplane": "execution-evidence-replay-authority", + "sourceos_spec": "canonical-interaction-schema-owner", + "policy_fabric": "policy-admission-authority", + "agent_registry": "identity-grant-authority", + "memory_mesh": "memory-context-pack-authority", + "noetica": "browser-chat-surface", + "agent_term": "terminal-operator-surface", + "superconscious": "task-cognition-coordinator" + }, + "payload_mode": "ref-only", + "claim_boundary": "Invalid fixture missing replay reference.", + "issued_at": "2026-05-30T22:20:00Z" +} diff --git a/tests/fixtures/integration/sourceos-interaction-evidence-binding.raw-log.invalid.json b/tests/fixtures/integration/sourceos-interaction-evidence-binding.raw-log.invalid.json new file mode 100644 index 0000000..e8264d5 --- /dev/null +++ b/tests/fixtures/integration/sourceos-interaction-evidence-binding.raw-log.invalid.json @@ -0,0 +1,31 @@ +{ + "schemaVersion": "agentplane.sourceos-interaction-evidence-binding.v0.1", + "recordType": "SourceOSInteractionEvidenceBinding", + "binding_id": "urn:srcos:agentplane:sourceos-interaction-evidence-binding:raw-log-leak-0001", + "source_interaction_event_ref": "urn:srcos:interaction-event:noetica-standalone-complete-0001", + "result_interaction_event_ref": "urn:srcos:interaction-event:agentplane-run-completed-0001", + "agentplane_run_ref": "urn:srcos:agentplane:run:noetica-superconscious-run-0001", + "validation_artifact_ref": "artifact://agentplane/noetica-superconscious-run-0001/validation-artifact.json#sha256:1111111111111111111111111111111111111111111111111111111111111111", + "placement_decision_ref": "artifact://agentplane/noetica-superconscious-run-0001/placement-decision.json#sha256:2222222222222222222222222222222222222222222222222222222222222222", + "run_artifact_ref": "raw stdout: token=secret unrestricted shell output", + "evidence_artifact_refs": [ + "artifact://agentplane/noetica-superconscious-run-0001/evidence-bundle.json#sha256:4444444444444444444444444444444444444444444444444444444444444444" + ], + "replay_ref": "artifact://agentplane/noetica-superconscious-run-0001/replay-artifact.json#sha256:5555555555555555555555555555555555555555555555555555555555555555", + "policy_decision_refs": [], + "context_pack_refs": [], + "redaction_refs": [], + "authority_boundaries": { + "agentplane": "execution-evidence-replay-authority", + "sourceos_spec": "canonical-interaction-schema-owner", + "policy_fabric": "policy-admission-authority", + "agent_registry": "identity-grant-authority", + "memory_mesh": "memory-context-pack-authority", + "noetica": "browser-chat-surface", + "agent_term": "terminal-operator-surface", + "superconscious": "task-cognition-coordinator" + }, + "payload_mode": "ref-only", + "claim_boundary": "Invalid fixture leaks raw execution logs.", + "issued_at": "2026-05-30T22:20:00Z" +} diff --git a/tests/fixtures/integration/sourceos-interaction-evidence-binding.valid.json b/tests/fixtures/integration/sourceos-interaction-evidence-binding.valid.json new file mode 100644 index 0000000..2a0b077 --- /dev/null +++ b/tests/fixtures/integration/sourceos-interaction-evidence-binding.valid.json @@ -0,0 +1,35 @@ +{ + "schemaVersion": "agentplane.sourceos-interaction-evidence-binding.v0.1", + "recordType": "SourceOSInteractionEvidenceBinding", + "binding_id": "urn:srcos:agentplane:sourceos-interaction-evidence-binding:noetica-superconscious-run-0001", + "source_interaction_event_ref": "urn:srcos:interaction-event:noetica-standalone-complete-0001", + "result_interaction_event_ref": "urn:srcos:interaction-event:agentplane-run-completed-0001", + "agentplane_run_ref": "urn:srcos:agentplane:run:noetica-superconscious-run-0001", + "validation_artifact_ref": "artifact://agentplane/noetica-superconscious-run-0001/validation-artifact.json#sha256:1111111111111111111111111111111111111111111111111111111111111111", + "placement_decision_ref": "artifact://agentplane/noetica-superconscious-run-0001/placement-decision.json#sha256:2222222222222222222222222222222222222222222222222222222222222222", + "run_artifact_ref": "artifact://agentplane/noetica-superconscious-run-0001/run-artifact.json#sha256:3333333333333333333333333333333333333333333333333333333333333333", + "evidence_artifact_refs": [ + "artifact://agentplane/noetica-superconscious-run-0001/evidence-bundle.json#sha256:4444444444444444444444444444444444444444444444444444444444444444" + ], + "replay_ref": "artifact://agentplane/noetica-superconscious-run-0001/replay-artifact.json#sha256:5555555555555555555555555555555555555555555555555555555555555555", + "policy_decision_refs": [ + "urn:srcos:decision:noetica-standalone-admit-0001" + ], + "context_pack_refs": [ + "urn:srcos:context-pack:noetica-session-local-bounded-0001" + ], + "redaction_refs": [], + "authority_boundaries": { + "agentplane": "execution-evidence-replay-authority", + "sourceos_spec": "canonical-interaction-schema-owner", + "policy_fabric": "policy-admission-authority", + "agent_registry": "identity-grant-authority", + "memory_mesh": "memory-context-pack-authority", + "noetica": "browser-chat-surface", + "agent_term": "terminal-operator-surface", + "superconscious": "task-cognition-coordinator" + }, + "payload_mode": "ref-only", + "claim_boundary": "AgentPlane attaches execution, validation, placement, evidence, and replay references to SourceOSInteractionEvent governance traces. It does not own Policy Fabric admission, Agent Registry grants, Memory Mesh context-pack semantics, Noetica UI state, AgentTerm terminal state, or SourceOSInteractionEvent schema ownership.", + "issued_at": "2026-05-30T22:20:00Z" +} diff --git a/tools/validate_sourceos_interaction_evidence_binding.py b/tools/validate_sourceos_interaction_evidence_binding.py new file mode 100644 index 0000000..b52838e --- /dev/null +++ b/tools/validate_sourceos_interaction_evidence_binding.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import json +import sys +from pathlib import Path +from typing import Any + +try: + import jsonschema +except ImportError as exc: # pragma: no cover + raise SystemExit("jsonschema is required: python3 -m pip install jsonschema") from exc + +ROOT = Path(__file__).resolve().parents[1] +SCHEMA = ROOT / "schemas" / "integration" / "sourceos-interaction-evidence-binding.v0.1.schema.json" +FIXTURE_DIR = ROOT / "tests" / "fixtures" / "integration" +VALID = FIXTURE_DIR / "sourceos-interaction-evidence-binding.valid.json" +INVALIDS = [ + FIXTURE_DIR / "sourceos-interaction-evidence-binding.missing-replay.invalid.json", + FIXTURE_DIR / "sourceos-interaction-evidence-binding.raw-log.invalid.json", +] + +REQUIRED_BOUNDARIES = { + "agentplane": "execution-evidence-replay-authority", + "sourceos_spec": "canonical-interaction-schema-owner", + "policy_fabric": "policy-admission-authority", + "agent_registry": "identity-grant-authority", + "memory_mesh": "memory-context-pack-authority", + "noetica": "browser-chat-surface", + "agent_term": "terminal-operator-surface", + "superconscious": "task-cognition-coordinator", +} + +FORBIDDEN_TERMS = { + "raw stdout", + "raw stderr", + "unrestricted shell output", + "token=", + "api key", + "credential", + "secret", + "private chain-of-thought", + "private reasoning", + "unrestricted transcript", +} + +REQUIRED_CLAIM_PHRASES = ( + "AgentPlane attaches execution", + "does not own Policy Fabric admission", + "Agent Registry grants", + "Memory Mesh context-pack semantics", + "Noetica UI state", + "AgentTerm terminal state", + "SourceOSInteractionEvent schema ownership", +) + + +def load(path: Path) -> dict[str, Any]: + data = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(data, dict): + raise ValueError(f"{path}: root must be object") + return data + + +def check(record: dict[str, Any]) -> None: + schema = load(SCHEMA) + jsonschema.validate(record, schema) + + if record["source_interaction_event_ref"] == record["result_interaction_event_ref"]: + raise ValueError("source/result interaction event refs must be distinct") + + for field in ( + "validation_artifact_ref", + "placement_decision_ref", + "run_artifact_ref", + "replay_ref", + ): + value = str(record[field]) + if "#sha256:" not in value: + raise ValueError(f"{field} must carry hash-qualified artifact ref") + + if not record["evidence_artifact_refs"]: + raise ValueError("evidence_artifact_refs required") + + boundaries = record["authority_boundaries"] + for key, expected in REQUIRED_BOUNDARIES.items(): + if boundaries.get(key) != expected: + raise ValueError(f"authority boundary drift for {key}: expected {expected!r}") + + if record["payload_mode"] not in {"metadata-only", "summary", "ref-only", "inline-bounded", "redacted"}: + raise ValueError("invalid payload_mode") + + serialized = json.dumps(record, sort_keys=True).lower() + for term in FORBIDDEN_TERMS: + if term in serialized: + raise ValueError(f"forbidden raw/sensitive payload term present: {term}") + + claim_boundary = record["claim_boundary"] + for phrase in REQUIRED_CLAIM_PHRASES: + if phrase not in claim_boundary: + raise ValueError(f"claim_boundary must include {phrase!r}") + + +def validate_file(path: Path) -> None: + check(load(path)) + + +def main(argv: list[str]) -> int: + if len(argv) == 2: + validate_file(Path(argv[1])) + print("OK: SourceOS interaction evidence binding validated") + return 0 + + if len(argv) != 1: + print("usage: validate_sourceos_interaction_evidence_binding.py [fixture.json]", file=sys.stderr) + return 2 + + validate_file(VALID) + unexpected: list[str] = [] + for path in INVALIDS: + try: + validate_file(path) + except Exception: + continue + unexpected.append(path.name) + if unexpected: + raise SystemExit("invalid SourceOS interaction evidence fixtures passed: " + ", ".join(unexpected)) + print("OK: SourceOS interaction evidence binding fixtures validated") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv))