diff --git a/Makefile b/Makefile index 8856a654..0c43d982 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -.PHONY: validate test validate-agent-cycle-health validate-authority-dependency-evidence validate-prometheus-sr validate-reasoning-failure-traces validate-governance-context validate-lattice-data-governai-execution-refs validate-lattice-runtime-profile-refs validate-network-native-assistant-evidence validate-guardrail-evidence-artifacts validate-stop-gate-evaluator validate-guarded-workcell-artifact validate-guarded-workcell-executor validate-guarded-invocation-artifact validate-guarded-invocation validate-agentic-pr-work-order validate-semantic-enterprise-agent-boundary validate-ops-history-contracts validate-action-contracts validate-agent-operation-contract validate-superconscious-reasoning-import validate-agent-harness-runtime-contracts validate-bounded-action-loop agentplane-evidence-receipt-composition-tier2-binding-ci lawful-learning-phase9-contract-ci validate-evidence-receipt-binding validate-semantic-activation-receipt validate-governed-run-contract validate-preflight-receipt validate-attempt-admission-receipt validate-verification-execution-receipt validate-synthetic-verification-receipt validate-governed-runner-v0-2-contract-chain validate-budget-settlement-receipt validate-rollback-receipts validate-run-dossier validate-governed-runner-readonly validate-workroom-context-evidence validate-wallguard-collaboration-admission validate-prophet-mesh-agentplane-adapter +.PHONY: validate test validate-agent-cycle-health validate-authority-dependency-evidence validate-prometheus-sr validate-reasoning-failure-traces validate-governance-context validate-lattice-data-governai-execution-refs validate-lattice-runtime-profile-refs validate-network-native-assistant-evidence validate-guardrail-evidence-artifacts validate-stop-gate-evaluator validate-guarded-workcell-artifact validate-guarded-workcell-executor validate-guarded-invocation-artifact validate-guarded-invocation validate-agentic-pr-work-order validate-semantic-enterprise-agent-boundary validate-ops-history-contracts validate-action-contracts validate-agent-operation-contract validate-superconscious-reasoning-import validate-agent-harness-runtime-contracts validate-bounded-action-loop agentplane-evidence-receipt-composition-tier2-binding-ci lawful-learning-phase9-contract-ci validate-evidence-receipt-binding validate-semantic-activation-receipt validate-governed-run-contract validate-preflight-receipt validate-attempt-admission-receipt validate-verification-execution-receipt validate-synthetic-verification-receipt validate-governed-runner-v0-2-contract-chain validate-budget-settlement-receipt validate-rollback-receipts validate-run-dossier validate-governed-runner-readonly validate-workroom-context-evidence validate-wallguard-collaboration-admission validate-prophet-mesh-agentplane-adapter validate-civic-stack-runtime-evidence validate-conversational-evidence -validate: validate-agent-cycle-health validate-authority-dependency-evidence validate-prometheus-sr validate-reasoning-failure-traces validate-governance-context validate-lattice-data-governai-execution-refs validate-lattice-runtime-profile-refs validate-network-native-assistant-evidence validate-guardrail-evidence-artifacts validate-stop-gate-evaluator validate-guarded-workcell-artifact validate-guarded-workcell-executor validate-guarded-invocation-artifact validate-guarded-invocation validate-agentic-pr-work-order validate-semantic-enterprise-agent-boundary validate-ops-history-contracts validate-action-contracts validate-agent-operation-contract validate-superconscious-reasoning-import validate-agent-harness-runtime-contracts validate-bounded-action-loop agentplane-evidence-receipt-composition-tier2-binding-ci lawful-learning-phase9-contract-ci validate-evidence-receipt-binding validate-semantic-activation-receipt validate-governed-run-contract validate-preflight-receipt validate-attempt-admission-receipt validate-verification-execution-receipt validate-synthetic-verification-receipt validate-governed-runner-v0-2-contract-chain validate-budget-settlement-receipt validate-rollback-receipts validate-run-dossier validate-governed-runner-readonly validate-workroom-context-evidence validate-wallguard-collaboration-admission validate-prophet-mesh-agentplane-adapter +validate: validate-agent-cycle-health validate-authority-dependency-evidence validate-prometheus-sr validate-reasoning-failure-traces validate-governance-context validate-lattice-data-governai-execution-refs validate-lattice-runtime-profile-refs validate-network-native-assistant-evidence validate-guardrail-evidence-artifacts validate-stop-gate-evaluator validate-guarded-workcell-artifact validate-guarded-workcell-executor validate-guarded-invocation-artifact validate-guarded-invocation validate-agentic-pr-work-order validate-semantic-enterprise-agent-boundary validate-ops-history-contracts validate-action-contracts validate-agent-operation-contract validate-superconscious-reasoning-import validate-agent-harness-runtime-contracts validate-bounded-action-loop agentplane-evidence-receipt-composition-tier2-binding-ci lawful-learning-phase9-contract-ci validate-evidence-receipt-binding validate-semantic-activation-receipt validate-governed-run-contract validate-preflight-receipt validate-attempt-admission-receipt validate-verification-execution-receipt validate-synthetic-verification-receipt validate-governed-runner-v0-2-contract-chain validate-budget-settlement-receipt validate-rollback-receipts validate-run-dossier validate-governed-runner-readonly validate-workroom-context-evidence validate-wallguard-collaboration-admission validate-prophet-mesh-agentplane-adapter validate-civic-stack-runtime-evidence validate-conversational-evidence python3 tools/validate_execution_timing.py validate-governance-context: @@ -248,6 +248,15 @@ validate-prophet-mesh-agentplane-adapter: python3 -m json.tool contracts/prophet-mesh/prophet-mesh-agentplane-adapter.v0.1.json >/dev/null python3 tools/validate_prophet_mesh_agentplane_adapter.py +validate-civic-stack-runtime-evidence: + python3 -m json.tool schemas/civic-stack-run-capsule.schema.v0.1.json >/dev/null + python3 tools/validate_civic_stack_runtime_evidence.py + +validate-conversational-evidence: + python3 -m json.tool schemas/conversational-action-evidence.schema.v0.1.json >/dev/null + python3 -m json.tool schemas/conversational-replay-record.schema.v0.1.json >/dev/null + python3 tools/validate_conversational_evidence.py + validate-agent-cycle-health: python3 tools/validate_agent_cycle_health.py diff --git a/docs/doctrine/tensegrity-runtime-contract.md b/docs/doctrine/tensegrity-runtime-contract.md new file mode 100644 index 00000000..4c89fa59 --- /dev/null +++ b/docs/doctrine/tensegrity-runtime-contract.md @@ -0,0 +1,55 @@ +# Tensegrity Runtime Contract + +## Purpose + +AgentPlane's execution model is a **tensegrity structure**: agents, tools, services, models, repos, and hosts are **compression members** — structural elements that do work. They are stabilized by continuous **tension members**: policy, identity, provenance, tests, signatures, audits, ledgers, capability grants, replay, and revocation. + +Neither class functions alone. A compression member without tension yields ungoverned execution. A tension member without a compression member yields policy theater with no work done. + +This contract defines how AgentPlane enforces tensegrity at runtime. + +## Compression Members + +| Compression Member | Description | +|--------------------|-------------| +| Agent | Execution actor with bounded capability radius | +| Tool | Callable surface scoped by tool grant and CGRM decision | +| Service | External or internal service endpoint with policy gate | +| Model | Inference engine with model-routing lane decision | +| Repo | Source repository with branch and GitOps audit chain | +| Host | Execution environment with resource scope and capability radius R5 guard | + +## Tension Members + +| Tension Member | Description | +|----------------|-------------| +| Policy | Policy decision ref from PolicyFabric; required on every execution artifact | +| Identity | Actor ref and post/authority binding; required for all dispatches | +| Provenance | Hash-chain of inputs, prior artifacts, and upstream anchors | +| Tests | Validation receipts and verification execution receipts | +| Signatures | Attestation events and cryptographic seals on receipts | +| Audits | Audit trail refs on intervention outcomes and blocked dispatches | +| Ledgers | Evidence ledger refs and budget settlement receipts | +| Capability Grants | Tool grants scoped by CGRM and capability radius level | +| Replay | Replay artifact ref required on all governed runs | +| Revocation | Revocation path declared at compression member registration | + +## Tensegrity Invariants + +1. **No compression member executes without a policy tension member.** Every agent action, tool invocation, service call, and model routing decision must carry a `policy_decision_ref`. + +2. **Tension members must form a closed chain.** Policy → Identity → Provenance → Evidence → Replay → Revocation must each reference the same run or be transitively linkable through `upstream_anchors`. + +3. **Revocation dissolves a tension member's grip immediately.** A revoked capability grant, expired policy decision, or invalidated identity ref causes the dependent compression member to transition to `blocked` or `deferred` — not to `completed`. + +4. **Replay seals the tensegrity loop.** A governed run without a `replay_artifact_ref` is structurally incomplete. Replay verifies that the compression-plus-tension envelope produces the same result under rerun, or surfaces a `divergence_record` for escalation. + +5. **Oversteer detection is a governance obligation, not an optimization.** See `cybernetic-oversteer-v0.md`. + +## Integration Points + +- `ConversationalActionEvidence` — tension: policy, identity, replay_linkage +- `CivicStackRunCapsule` — tension: policy, provenance_refs, rationalgrl_trace, hellgraph_evidence_refs +- `BoundaryCalculusEvidenceEnvelope` — tension: promotion_gate, policy_result, attribution_discriminating_evidence_refs +- `GovernedRunContract` — tension: policy, budget, verifier chain, replay_artifact_ref +- `CapabilityRadiusProfile` — defines tension member scope per compression member level (R0–R5) diff --git a/docs/specs/agent-action-tension-members-v0.md b/docs/specs/agent-action-tension-members-v0.md new file mode 100644 index 00000000..9f965c20 --- /dev/null +++ b/docs/specs/agent-action-tension-members-v0.md @@ -0,0 +1,47 @@ +# Agent Action Tension Members v0 + +## Purpose + +Defines how a single agent action declares its compression role and the tension members that stabilize it. + +## Structure + +Every agent action artifact in AgentPlane should carry the following tension member declarations: + +``` +action_id — unique identifier for this action +compression_member — agent | tool | service | model | repo | host +policy_ref — policy decision ref (PolicyFabric) +identity_ref — actor or post/authority ref +provenance_refs — hash chain: prior action, run capsule, upstream anchors +evidence_refs — evidence artifacts emitted by or consumed for this action +replay_ref — replay artifact ref (required for governed runs) +revocation_path — revocation URI; if revoked, action transitions to blocked +audit_ref — audit trail ref (required on interventions and blocked outcomes) +``` + +## Tension Member Obligations by Action Type + +| Action Type | Policy | Identity | Provenance | Evidence | Replay | Revocation | Audit | +|----------------------|--------|----------|------------|----------|--------|------------|-------| +| observe | ✓ | ✓ | ✓ | ✓ | — | optional | — | +| query | ✓ | ✓ | ✓ | ✓ | — | optional | — | +| transform | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | +| write | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | +| deploy | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| revoke | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| escalate | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| trigger_execution | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | +| approval_denial | ✓ | ✓ | ✓ | ✓ | ✓ | optional | ✓ | + +## Structural Rules + +1. **`policy_ref` is always required.** No action without a PolicyFabric decision ref is structurally valid. +2. **`replay_ref` is required for actions that mutate state.** transform, write, deploy, revoke, escalate, trigger_execution, approval_denial. +3. **`audit_ref` is required for denied or blocked outcomes** and for any intervention (modified, blocked, escalated) per the bounded-action-loop contract. +4. **`revocation_path` is required for actions at R2 or above** (capability radius). See `capability-radius-v0.md`. +5. **`provenance_refs` must include at least one upstream anchor** linking this action to a run capsule, governed run, or admission artifact. + +## Example + +See `examples/tensegrity/agent-action-tension-members.example.json`. diff --git a/docs/specs/capability-radius-v0.md b/docs/specs/capability-radius-v0.md new file mode 100644 index 00000000..153fa8c1 --- /dev/null +++ b/docs/specs/capability-radius-v0.md @@ -0,0 +1,28 @@ +# Capability Radius v0 + +## Purpose + +Defines the six capability radius levels (R0–R5) that bound what an agent, tool, or service can do within the tensegrity runtime. Radius is a tension member: it scopes tool grants, CGRM decisions, and revocation paths. + +## Radius Levels + +| Level | Name | Scope | Tension Members Required | +|-------|------------------------|-----------------------------------------------------------------------------------------|---------------------------------------------| +| R0 | observe-local | Read from in-process or local context; no side effects outside the execution envelope | policy, identity | +| R1 | query-bounded | Query services or data stores with read-only scope; results stay in-process | policy, identity, provenance | +| R2 | transform-scoped | Produce or modify artifacts within a governed workspace; no direct writes to shared state | policy, identity, provenance, evidence, replay | +| R3 | write-governed | Write to governed repositories, ledgers, or evidence stores; requires signed receipt | policy, identity, provenance, evidence, replay, revocation | +| R4 | deploy-staged | Deploy to staged or sandboxed environments; Signadot or equivalent runtime gate required | policy, identity, provenance, evidence, replay, revocation, audit | +| R5 | deployment-host-mutation | Mutate production hosts, release branches, or live infrastructure; requires explicit admission gate and senior authority ref | policy, identity, provenance, evidence, replay, revocation, audit, post_authority_ref | + +## Radius and Tool Grants + +A tool grant may not exceed the actor's declared capability radius. Attempting to invoke a tool with a radius higher than the actor's current grant level causes the dispatch to transition to `blocked` with a RationalGRL defeater. + +## Radius and Oversteer + +Rapid radius escalation (R0 → R3 in a single session without intermediate evidence) is an oversteer indicator. See `cybernetic-oversteer-v0.md`. + +## Radius Profile + +The live capability radius profile for an actor or service is declared in `examples/reachability/agent-capability-radius.example.json`. diff --git a/docs/specs/cybernetic-oversteer-v0.md b/docs/specs/cybernetic-oversteer-v0.md new file mode 100644 index 00000000..b791bf87 --- /dev/null +++ b/docs/specs/cybernetic-oversteer-v0.md @@ -0,0 +1,40 @@ +# Cybernetic Oversteer v0 + +## Purpose + +Defines oversteer as a first-class governance condition in the tensegrity runtime. Oversteer occurs when the execution system is self-correcting faster than evidence-gathering can validate. It signals that tension members are under strain: policy is reversing, evidence is not accumulating, or the actor is outrunning its capability radius. + +## Oversteer Indicators + +Each indicator maps to a pattern in the execution record or governance signal stream. + +| Indicator | Description | Tension Member Under Strain | +|-------------------------------|--------------------------------------------------------------------------------------------------|-------------------------------| +| repeated_reversals | The same decision (approve/deny, dispatch/block) is reversed three or more times in a session | Policy | +| patch_churn | More than N patches to the same artifact within a bounded time window without advancing the evidence chain | Provenance, Evidence | +| issue_churn | Issues are opened and closed on the same scope without resolution propagating to execution artifacts | Evidence, Audit | +| branch_churn | More than N branch create/delete cycles on the same base without a merged artifact | Repo, Provenance | +| oscillating_decisions | Policy decisions flip between allow and deny on the same request profile without new evidence | Policy | +| policy_flip_flops | A policy decision is overridden, reinstated, and overridden again within one run capsule | Policy | +| repeated_failed_validations | The same validation check fails three or more consecutive times without a new evidence artifact | Tests, Evidence | +| excessive_retry_no_evidence | Retries exceed threshold with no new evidence_refs added to the run capsule | Evidence, Replay | +| rapid_radius_escalation | Actor capability radius jumps two or more levels without intermediate evidence and policy gates | Capability Grants | +| tension_member_gap | A required tension member (e.g., replay_ref) is absent from a mutation-class action | Varies | + +## Detection Contract + +Oversteer indicators are emitted as `OvensteerIndicator` fields in the `OversteerGovernanceSignal` artifact (see `examples/governance/oversteer-indicators.example.json`). They do not block execution directly but: + +1. Are emitted to HellGraph/Prophet Core as evidence. +2. Trigger a `delivery_excellence_signal_ref` with a degraded score. +3. Elevate the next policy decision request to `escalate` if two or more indicators fire simultaneously. +4. Are included in the RationalGRL trace as softgoal degradation events. + +## Oversteer vs. Error + +An error is a single-point failure with a clear revocation path. Oversteer is a systemic pattern. Errors resolve through repair and evidence. Oversteer resolves through tension member reinforcement: adding evidence, slowing radius expansion, or requiring human authority at R4/R5. + +## Non-Claims + +- This spec does not define the thresholds N for churn indicators; those are set by PolicyFabric configuration per org and repo. +- This spec does not prescribe automatic execution halt; that is a policy gate decision. diff --git a/examples/governance/oversteer-indicators.example.json b/examples/governance/oversteer-indicators.example.json new file mode 100644 index 00000000..566e8e75 --- /dev/null +++ b/examples/governance/oversteer-indicators.example.json @@ -0,0 +1,42 @@ +{ + "kind": "OversteerGovernanceSignalExample", + "description": "Example of an oversteer governance signal fired during a run that exhibited policy flip-flops and repeated failed validations.", + "signal_id": "oversteer-signal:example-run-policy-flipflop-001", + "run_capsule_ref": "civic-stack-run:oversteer-example-001", + "actor_ref": "agent://agentplane/civic-stack-agent/v1", + "session_window": { + "started_at": "2024-01-15T10:00:00Z", + "ended_at": "2024-01-15T10:45:00Z" + }, + "indicators_fired": [ + { + "indicator": "policy_flip_flops", + "count": 3, + "evidence": "Policy decision pd-civic-run-001 was overridden to deny, reinstated to allow, overridden to deny again within the same run capsule.", + "tension_member_under_strain": "policy", + "first_detected_at": "2024-01-15T10:12:00Z" + }, + { + "indicator": "repeated_failed_validations", + "count": 4, + "evidence": "validate-civic-stack-runtime-evidence failed 4 consecutive times on the same input without new evidence_refs added.", + "tension_member_under_strain": "evidence", + "first_detected_at": "2024-01-15T10:22:00Z" + } + ], + "simultaneous_indicators": 2, + "policy_escalation_triggered": true, + "next_policy_decision_elevated_to": "escalate", + "hellgraph_evidence_refs": [ + "evidence://hellgraph/oversteer/signal-example-run-policy-flipflop-001" + ], + "delivery_excellence_signal_ref": "de://signal/oversteer/example-run-policy-flipflop-001/degraded", + "rationalgrl_softgoal_degradation_refs": [ + "goal://rationalgrl/civic-service-delivery/quality" + ], + "non_claims": [ + "This signal does not halt execution; it elevates the next policy decision to escalate.", + "Threshold values for indicator counts are set by PolicyFabric configuration, not this signal." + ], + "issued_at": "2024-01-15T10:45:05Z" +} diff --git a/examples/reachability/agent-capability-radius.example.json b/examples/reachability/agent-capability-radius.example.json new file mode 100644 index 00000000..91e60a13 --- /dev/null +++ b/examples/reachability/agent-capability-radius.example.json @@ -0,0 +1,45 @@ +{ + "kind": "AgentCapabilityRadiusExample", + "description": "Capability radius profile for a governed evidence-writing agent in AgentPlane. Current level R3 with conditional R4 gate.", + "actor_ref": "agent://agentplane/evidence-writer/v2", + "capability_radius": { + "current_level": "R3", + "allowed_levels": ["R0", "R1", "R2", "R3"], + "conditional_levels": [ + { + "level": "R4", + "condition": "Explicit post_authority_ref from senior analyst and signed PolicyFabric R4 grant", + "gate": "policy-fabric://gate/R4-deploy-staged" + } + ], + "denied_levels": ["R5"] + }, + "tool_grants_in_scope": [ + { + "tool_id": "tool://evidence-store/write/v2", + "radius_required": "R3", + "policy_decision_ref": "policy-fabric://decision/pd-tool-grant-evidence-write-v2", + "cgrm_decision_ref": "cgrm://decision/cgrm-evidence-write-v2" + }, + { + "tool_id": "tool://hellgraph/emit/v1", + "radius_required": "R3", + "policy_decision_ref": "policy-fabric://decision/pd-tool-grant-hellgraph-emit-v1", + "cgrm_decision_ref": "cgrm://decision/cgrm-hellgraph-emit-v1" + } + ], + "tension_members_at_current_radius": { + "policy": "required", + "identity": "required", + "provenance": "required", + "evidence": "required", + "replay": "required", + "revocation": "required", + "audit": "required_on_intervention" + }, + "oversteer_guards": { + "max_radius_jump_per_session": 1, + "require_intermediate_evidence_between_jumps": true, + "rapid_radius_escalation_indicator": "rapid_radius_escalation" + } +} diff --git a/examples/tensegrity/agent-action-tension-members.example.json b/examples/tensegrity/agent-action-tension-members.example.json new file mode 100644 index 00000000..6c7aa211 --- /dev/null +++ b/examples/tensegrity/agent-action-tension-members.example.json @@ -0,0 +1,33 @@ +{ + "kind": "AgentActionTensionMembersExample", + "description": "Example demonstrating full tension member declaration for a write-governed action at R3 capability radius.", + "action_id": "action:tensegrity-example-write-governed-001", + "compression_member": "agent", + "action_type": "write", + "capability_radius": "R3", + "policy_ref": "policy-fabric://decision/pd-write-governed-example-001", + "identity_ref": "agent://agentplane/evidence-writer/v2", + "provenance_refs": [ + "provenance://run-capsule/run-tensegrity-example-001", + "provenance://governed-run/governed-run-tensegrity-example-001", + "upstream:agentplane#136" + ], + "evidence_refs": [ + "evidence://hellgraph/tensegrity-example-write/evidence-001", + "evidence://prophet-core/tensegrity-example-write/emission-001" + ], + "replay_ref": "replay://agentplane/tensegrity-example-write/replay-001", + "revocation_path": "revocation://agentplane/capability-grants/evidence-writer-v2/revoke", + "audit_ref": null, + "tension_member_status": { + "policy": "satisfied", + "identity": "satisfied", + "provenance": "satisfied", + "evidence": "satisfied", + "replay": "satisfied", + "revocation": "satisfied", + "audit": "not_required" + }, + "tensegrity_valid": true, + "notes": "All required tension members for R3 write action are present. audit_ref not required because outcome is not denied or blocked." +} diff --git a/schemas/civic-stack-run-capsule.schema.v0.1.json b/schemas/civic-stack-run-capsule.schema.v0.1.json new file mode 100644 index 00000000..00dffbcd --- /dev/null +++ b/schemas/civic-stack-run-capsule.schema.v0.1.json @@ -0,0 +1,270 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://socioprophet.io/schemas/agentplane/civic-stack-run-capsule/v0.1", + "title": "CivicStackRunCapsule", + "description": "AgentPlane runtime evidence capsule for Seven-Model Civic Operating Architecture execution. Emits OQL plan acceptance, OAC compiler invocation, tool grants, action dispatch, and RationalGRL trace links. AgentPlane emits governed runtime evidence; it does not own the ontology, policy evaluation, or Delivery Excellence scoring.", + "type": "object", + "required": [ + "kind", + "run_id", + "actor_ref", + "oql_plan_id", + "artifact_manifest_id", + "policy_decision_id", + "timestamps", + "provenance_refs", + "rationalgrl_trace", + "hellgraph_evidence_refs", + "issued_at" + ], + "additionalProperties": false, + "properties": { + "kind": { "type": "string", "const": "CivicStackRunCapsule" }, + "run_id": { "type": "string", "minLength": 1 }, + "actor_ref": { "type": "string", "description": "Agent or human actor reference" }, + "post_authority_ref": { + "type": "string", + "description": "Post or authority binding reference, if applicable" + }, + "oql_plan_id": { "type": "string", "minLength": 1 }, + "artifact_manifest_id": { "type": "string", "minLength": 1 }, + "service_id": { "type": "string" }, + "policy_decision_id": { "type": "string", "minLength": 1 }, + "policy_decision_outcome": { + "type": "string", + "enum": ["allow", "allow_with_constraints", "deny", "escalate"] + }, + "dataset_ids": { + "type": "array", + "items": { "type": "string" } + }, + "resource_ids": { + "type": "array", + "items": { "type": "string" } + }, + "tool_grants": { + "type": "array", + "items": { "$ref": "#/$defs/ToolGrant" } + }, + "action_dispatch_records": { + "type": "array", + "items": { "$ref": "#/$defs/ActionDispatchRecord" } + }, + "oql_plan_acceptance": { "$ref": "#/$defs/OQLPlanAcceptance" }, + "oac_compiler_invocation": { "$ref": "#/$defs/OACCompilerInvocation" }, + "subagent_delegations": { + "type": "array", + "items": { "$ref": "#/$defs/SubagentDelegation" } + }, + "attestation_events": { + "type": "array", + "items": { "$ref": "#/$defs/AttestationEvent" } + }, + "timestamps": { + "type": "object", + "required": ["started_at", "completed_at"], + "additionalProperties": false, + "properties": { + "started_at": { "type": "string", "format": "date-time" }, + "completed_at": { "type": "string", "format": "date-time" } + } + }, + "provenance_refs": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "rationalgrl_trace": { "$ref": "#/$defs/RationalGRLTrace" }, + "hellgraph_evidence_refs": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "description": "Evidence refs emitted to HellGraph/Prophet Core" + }, + "delivery_excellence_signal_ref": { + "type": "string", + "description": "Score signal ref emitted to Delivery Excellence" + }, + "upstream_anchors": { + "type": "array", + "items": { "type": "string" } + }, + "issued_at": { "type": "string", "format": "date-time" } + }, + "$defs": { + "ToolGrant": { + "type": "object", + "required": ["grant_id", "tool_id", "granted_to", "policy_decision_ref"], + "additionalProperties": false, + "properties": { + "grant_id": { "type": "string", "minLength": 1 }, + "tool_id": { "type": "string", "minLength": 1 }, + "granted_to": { "type": "string", "minLength": 1 }, + "policy_decision_ref": { "type": "string", "minLength": 1 }, + "cgrm_decision_ref": { "type": "string" }, + "scope_constraints": { + "type": "array", + "items": { "type": "string" } + } + } + }, + "ActionDispatchRecord": { + "type": "object", + "required": ["dispatch_id", "action_type", "policy_decision_ref", "dispatch_status"], + "additionalProperties": false, + "properties": { + "dispatch_id": { "type": "string", "minLength": 1 }, + "action_type": { "type": "string", "minLength": 1 }, + "policy_decision_ref": { "type": "string", "minLength": 1 }, + "dispatch_status": { + "type": "string", + "enum": ["dispatched", "blocked", "deferred", "completed"] + }, + "rationalgrl_task_ref": { "type": "string" }, + "defeater_reason": { + "type": "string", + "description": "RationalGRL defeater description when dispatch_status=blocked" + } + } + }, + "OQLPlanAcceptance": { + "type": "object", + "required": ["acceptance_id", "oql_plan_id", "acceptance_status"], + "additionalProperties": false, + "properties": { + "acceptance_id": { "type": "string", "minLength": 1 }, + "oql_plan_id": { "type": "string", "minLength": 1 }, + "acceptance_status": { + "type": "string", + "enum": ["accepted", "rejected", "partial", "pending_review"] + }, + "interpretation_ref": { "type": "string" }, + "oql_task_mappings": { + "type": "array", + "items": { "$ref": "#/$defs/OQLTaskMapping" } + } + } + }, + "OQLTaskMapping": { + "type": "object", + "required": ["task_id", "oql_task_ref", "agent_action_ref"], + "additionalProperties": false, + "properties": { + "task_id": { "type": "string" }, + "oql_task_ref": { "type": "string" }, + "agent_action_ref": { "type": "string" } + } + }, + "OACCompilerInvocation": { + "type": "object", + "required": ["invocation_id", "compiler_id", "artifact_manifest_id", "invocation_status"], + "additionalProperties": false, + "properties": { + "invocation_id": { "type": "string", "minLength": 1 }, + "compiler_id": { "type": "string", "minLength": 1 }, + "artifact_manifest_id": { "type": "string", "minLength": 1 }, + "invocation_status": { + "type": "string", + "enum": ["success", "failure", "partial"] + }, + "artifact_emission_refs": { + "type": "array", + "items": { "type": "string" } + } + } + }, + "SubagentDelegation": { + "type": "object", + "required": ["delegation_id", "delegated_to", "task_ref", "policy_decision_ref"], + "additionalProperties": false, + "properties": { + "delegation_id": { "type": "string", "minLength": 1 }, + "delegated_to": { "type": "string", "minLength": 1 }, + "task_ref": { "type": "string", "minLength": 1 }, + "policy_decision_ref": { "type": "string", "minLength": 1 } + } + }, + "AttestationEvent": { + "type": "object", + "required": ["attestation_id", "attestation_type", "attested_by"], + "additionalProperties": false, + "properties": { + "attestation_id": { "type": "string", "minLength": 1 }, + "attestation_type": { + "type": "string", + "enum": ["policy_compliance", "provenance_chain", "dataset_access", "tool_grant", "oql_plan_acceptance"] + }, + "attested_by": { "type": "string", "minLength": 1 }, + "ref": { "type": "string" } + } + }, + "RationalGRLTrace": { + "type": "object", + "required": ["trace_id", "goals_addressed", "tasks_executed"], + "additionalProperties": false, + "properties": { + "trace_id": { "type": "string", "minLength": 1 }, + "goals_addressed": { + "type": "array", + "items": { + "type": "object", + "required": ["goal_ref", "goal_type", "satisfaction_status"], + "additionalProperties": false, + "properties": { + "goal_ref": { "type": "string" }, + "goal_type": { + "type": "string", + "enum": ["goal", "softgoal"] + }, + "satisfaction_status": { + "type": "string", + "enum": ["satisfied", "partially_satisfied", "denied", "unknown"] + }, + "contribution_refs": { + "type": "array", + "items": { "type": "string" } + } + } + } + }, + "tasks_executed": { + "type": "array", + "items": { + "type": "object", + "required": ["task_ref", "execution_status"], + "additionalProperties": false, + "properties": { + "task_ref": { "type": "string" }, + "execution_status": { + "type": "string", + "enum": ["completed", "blocked", "delegated", "deferred"] + }, + "resource_used_refs": { + "type": "array", + "items": { "type": "string" } + }, + "dependency_satisfied_refs": { + "type": "array", + "items": { "type": "string" } + }, + "defeater_reason": { "type": "string" } + } + } + }, + "dependencies_blocked": { + "type": "array", + "items": { + "type": "object", + "required": ["dependency_ref", "blocking_policy_ref"], + "additionalProperties": false, + "properties": { + "dependency_ref": { "type": "string" }, + "blocking_policy_ref": { "type": "string" }, + "defeater_reason": { "type": "string" } + } + } + } + } + } + } +} diff --git a/schemas/conversational-action-evidence.schema.v0.1.json b/schemas/conversational-action-evidence.schema.v0.1.json new file mode 100644 index 00000000..94cce056 --- /dev/null +++ b/schemas/conversational-action-evidence.schema.v0.1.json @@ -0,0 +1,109 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://socioprophet.io/schemas/agentplane/conversational-action-evidence/v0.1", + "title": "ConversationalActionEvidence", + "description": "Evidence artifact for a conversational action that enters the AgentPlane execution and evidence plane. Binds a conversational turn or multi-turn session to governed execution work — replay linkage, policy decision refs, and HellGraph evidence emission. AgentPlane owns the execution boundary; conversational services own the turn record; this artifact is the seam.", + "type": "object", + "required": [ + "kind", + "evidence_id", + "conversation_session_id", + "turn_id", + "action_type", + "actor_ref", + "policy_decision_ref", + "execution_artifact_ref", + "replay_linkage", + "hellgraph_evidence_refs", + "issued_at" + ], + "additionalProperties": false, + "properties": { + "kind": { "type": "string", "const": "ConversationalActionEvidence" }, + "evidence_id": { "type": "string", "minLength": 1 }, + "conversation_session_id": { "type": "string", "minLength": 1 }, + "turn_id": { "type": "string", "minLength": 1 }, + "action_type": { + "type": "string", + "enum": [ + "trigger_execution", + "query", + "annotation", + "approval_grant", + "approval_denial", + "escalation", + "cancellation", + "replay_request" + ] + }, + "actor_ref": { "type": "string", "minLength": 1 }, + "service_ref": { "type": "string" }, + "policy_decision_ref": { "type": "string", "minLength": 1 }, + "policy_outcome": { + "type": "string", + "enum": ["allow", "allow_with_constraints", "deny", "escalate"] + }, + "execution_artifact_ref": { + "type": "string", + "minLength": 1, + "description": "Ref to the AgentPlane execution artifact (run, admission, governed run, etc.) triggered or referenced by this conversational action" + }, + "execution_artifact_kind": { + "type": "string", + "description": "Kind discriminator of the bound execution artifact" + }, + "replay_linkage": { "$ref": "#/$defs/ReplayLinkage" }, + "annotation_refs": { + "type": "array", + "items": { "type": "string" }, + "description": "Optional annotation or comment refs that informed this action" + }, + "non_claims": { + "type": "array", + "items": { "type": "string" }, + "description": "Explicit non-claim declarations scoping this evidence artifact" + }, + "hellgraph_evidence_refs": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "upstream_anchors": { + "type": "array", + "items": { "type": "string" } + }, + "issued_at": { "type": "string", "format": "date-time" } + }, + "$defs": { + "ReplayLinkage": { + "type": "object", + "required": ["replay_eligible", "replay_artifact_ref"], + "additionalProperties": false, + "properties": { + "replay_eligible": { "type": "boolean" }, + "replay_artifact_ref": { "type": "string", "minLength": 1 }, + "replay_verified": { "type": "boolean" }, + "replay_divergence_detected": { "type": "boolean" }, + "replay_divergence_ref": { + "type": "string", + "description": "Ref to divergence record when replay_divergence_detected is true" + }, + "replay_scope": { + "type": "string", + "enum": ["turn", "session", "execution_artifact"], + "description": "What scope the replay covers" + } + } + } + }, + "if": { + "properties": { "action_type": { "const": "approval_denial" } } + }, + "then": { + "properties": { + "policy_outcome": { + "enum": ["deny", "escalate"] + } + } + } +} diff --git a/schemas/conversational-replay-record.schema.v0.1.json b/schemas/conversational-replay-record.schema.v0.1.json new file mode 100644 index 00000000..4e7a40ea --- /dev/null +++ b/schemas/conversational-replay-record.schema.v0.1.json @@ -0,0 +1,79 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://socioprophet.io/schemas/agentplane/conversational-replay-record/v0.1", + "title": "ConversationalReplayRecord", + "description": "Replay record linking a conversational session or turn to an AgentPlane governed replay execution. Provides the replay artifact for ConversationalActionEvidence.replay_linkage. AgentPlane emits this on replay completion; the conversational service is the upstream consumer.", + "type": "object", + "required": [ + "kind", + "replay_record_id", + "conversation_session_id", + "replay_trigger_ref", + "replay_execution_ref", + "replay_status", + "replay_scope", + "policy_decision_ref", + "non_claims", + "issued_at" + ], + "additionalProperties": false, + "properties": { + "kind": { "type": "string", "const": "ConversationalReplayRecord" }, + "replay_record_id": { "type": "string", "minLength": 1 }, + "conversation_session_id": { "type": "string", "minLength": 1 }, + "turn_id": { "type": "string" }, + "replay_trigger_ref": { + "type": "string", + "minLength": 1, + "description": "Ref to the ConversationalActionEvidence that triggered this replay" + }, + "replay_execution_ref": { + "type": "string", + "minLength": 1, + "description": "Ref to the AgentPlane governed execution artifact for this replay" + }, + "replay_status": { + "type": "string", + "enum": ["completed", "failed", "diverged", "pending"] + }, + "replay_scope": { + "type": "string", + "enum": ["turn", "session", "execution_artifact"] + }, + "policy_decision_ref": { "type": "string", "minLength": 1 }, + "divergence_record": { + "type": "object", + "required": ["detected_at", "divergence_type", "original_ref", "replay_ref"], + "additionalProperties": false, + "properties": { + "detected_at": { "type": "string", "format": "date-time" }, + "divergence_type": { + "type": "string", + "enum": ["output_mismatch", "policy_change", "data_drift", "actor_change", "tool_unavailable"] + }, + "original_ref": { "type": "string", "minLength": 1 }, + "replay_ref": { "type": "string", "minLength": 1 }, + "resolution_status": { + "type": "string", + "enum": ["unresolved", "accepted", "escalated", "invalidated"] + } + } + }, + "non_claims": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "upstream_anchors": { + "type": "array", + "items": { "type": "string" } + }, + "issued_at": { "type": "string", "format": "date-time" } + }, + "if": { + "properties": { "replay_status": { "const": "diverged" } } + }, + "then": { + "required": ["divergence_record"] + } +} diff --git a/tests/fixtures/civic-stack/reject.civic-stack-run-capsule.missing-hellgraph-refs.json b/tests/fixtures/civic-stack/reject.civic-stack-run-capsule.missing-hellgraph-refs.json new file mode 100644 index 00000000..ca7893c9 --- /dev/null +++ b/tests/fixtures/civic-stack/reject.civic-stack-run-capsule.missing-hellgraph-refs.json @@ -0,0 +1,20 @@ +{ + "_reject_reason": "hellgraph_evidence_refs is required but absent", + "kind": "CivicStackRunCapsule", + "run_id": "civic-stack-run:reject-no-hellgraph", + "actor_ref": "agent://agentplane/civic-stack-agent/v1", + "oql_plan_id": "oql://plan/service-delivery-v1/step-1", + "artifact_manifest_id": "manifest://civic-stack/artifacts/reject-no-hellgraph", + "policy_decision_id": "policy-fabric://decision/pd-reject-no-hellgraph", + "timestamps": { + "started_at": "2024-01-15T10:00:00Z", + "completed_at": "2024-01-15T10:01:00Z" + }, + "provenance_refs": ["provenance://sociosphere/civic-stack-run/reject-no-hellgraph"], + "rationalgrl_trace": { + "trace_id": "rationalgrl-trace:reject-no-hellgraph", + "goals_addressed": [], + "tasks_executed": [] + }, + "issued_at": "2024-01-15T10:01:01Z" +} diff --git a/tests/fixtures/civic-stack/reject.civic-stack-run-capsule.missing-provenance-refs.json b/tests/fixtures/civic-stack/reject.civic-stack-run-capsule.missing-provenance-refs.json new file mode 100644 index 00000000..3808fbbd --- /dev/null +++ b/tests/fixtures/civic-stack/reject.civic-stack-run-capsule.missing-provenance-refs.json @@ -0,0 +1,21 @@ +{ + "_reject_reason": "provenance_refs is required (minItems: 1) but empty", + "kind": "CivicStackRunCapsule", + "run_id": "civic-stack-run:reject-empty-provenance", + "actor_ref": "agent://agentplane/civic-stack-agent/v1", + "oql_plan_id": "oql://plan/service-delivery-v1/step-1", + "artifact_manifest_id": "manifest://civic-stack/artifacts/reject-empty-provenance", + "policy_decision_id": "policy-fabric://decision/pd-reject-empty-provenance", + "timestamps": { + "started_at": "2024-01-15T10:00:00Z", + "completed_at": "2024-01-15T10:01:00Z" + }, + "provenance_refs": [], + "rationalgrl_trace": { + "trace_id": "rationalgrl-trace:reject-empty-provenance", + "goals_addressed": [], + "tasks_executed": [] + }, + "hellgraph_evidence_refs": ["evidence://hellgraph/reject-empty-provenance/run-capsule"], + "issued_at": "2024-01-15T10:01:01Z" +} diff --git a/tests/fixtures/civic-stack/reject.civic-stack-run-capsule.wrong-kind.json b/tests/fixtures/civic-stack/reject.civic-stack-run-capsule.wrong-kind.json new file mode 100644 index 00000000..f4a267d7 --- /dev/null +++ b/tests/fixtures/civic-stack/reject.civic-stack-run-capsule.wrong-kind.json @@ -0,0 +1,21 @@ +{ + "_reject_reason": "kind must be CivicStackRunCapsule (const violation)", + "kind": "CivicStackRunCapsuleV2", + "run_id": "civic-stack-run:reject-wrong-kind", + "actor_ref": "agent://agentplane/civic-stack-agent/v1", + "oql_plan_id": "oql://plan/service-delivery-v1/step-1", + "artifact_manifest_id": "manifest://civic-stack/artifacts/reject-wrong-kind", + "policy_decision_id": "policy-fabric://decision/pd-reject-wrong-kind", + "timestamps": { + "started_at": "2024-01-15T10:00:00Z", + "completed_at": "2024-01-15T10:01:00Z" + }, + "provenance_refs": ["provenance://sociosphere/civic-stack-run/reject-wrong-kind"], + "rationalgrl_trace": { + "trace_id": "rationalgrl-trace:reject-wrong-kind", + "goals_addressed": [], + "tasks_executed": [] + }, + "hellgraph_evidence_refs": ["evidence://hellgraph/reject-wrong-kind/run-capsule"], + "issued_at": "2024-01-15T10:01:01Z" +} diff --git a/tests/fixtures/civic-stack/valid.civic-stack-run-capsule.json b/tests/fixtures/civic-stack/valid.civic-stack-run-capsule.json new file mode 100644 index 00000000..f87d52d1 --- /dev/null +++ b/tests/fixtures/civic-stack/valid.civic-stack-run-capsule.json @@ -0,0 +1,160 @@ +{ + "kind": "CivicStackRunCapsule", + "run_id": "civic-stack-run:a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "actor_ref": "agent://agentplane/civic-stack-agent/v1", + "post_authority_ref": "post://authority/civic-oversight-post/v1", + "oql_plan_id": "oql://plan/service-delivery-v1/step-3", + "artifact_manifest_id": "manifest://civic-stack/artifacts/2024-01-15-run-a1b2", + "service_id": "civic-service:social-case-management", + "policy_decision_id": "policy-fabric://decision/pd-civic-run-a1b2", + "policy_decision_outcome": "allow", + "dataset_ids": [ + "dataset://case-management/beneficiary-records/batch-001", + "dataset://case-management/service-catalog/v4" + ], + "resource_ids": [ + "resource://compute/civic-agent-executor/slot-7", + "resource://storage/case-output/run-a1b2" + ], + "tool_grants": [ + { + "grant_id": "grant:civic-run-a1b2-tool-1", + "tool_id": "tool://case-lookup/v2", + "granted_to": "agent://agentplane/civic-stack-agent/v1", + "policy_decision_ref": "policy-fabric://decision/pd-civic-run-a1b2", + "cgrm_decision_ref": "cgrm://decision/cgrm-civic-run-a1b2-read", + "scope_constraints": ["read_only", "beneficiary_scope_limited"] + }, + { + "grant_id": "grant:civic-run-a1b2-tool-2", + "tool_id": "tool://service-eligibility-check/v3", + "granted_to": "agent://agentplane/civic-stack-agent/v1", + "policy_decision_ref": "policy-fabric://decision/pd-civic-run-a1b2", + "cgrm_decision_ref": "cgrm://decision/cgrm-civic-run-a1b2-eligibility" + } + ], + "action_dispatch_records": [ + { + "dispatch_id": "dispatch:civic-run-a1b2-action-1", + "action_type": "case.lookup", + "policy_decision_ref": "policy-fabric://decision/pd-civic-run-a1b2", + "dispatch_status": "completed", + "rationalgrl_task_ref": "task://rationalgrl/case-lookup-task/v1" + }, + { + "dispatch_id": "dispatch:civic-run-a1b2-action-2", + "action_type": "eligibility.check", + "policy_decision_ref": "policy-fabric://decision/pd-civic-run-a1b2", + "dispatch_status": "completed", + "rationalgrl_task_ref": "task://rationalgrl/eligibility-check-task/v1" + } + ], + "oql_plan_acceptance": { + "acceptance_id": "oql-acceptance:civic-run-a1b2", + "oql_plan_id": "oql://plan/service-delivery-v1/step-3", + "acceptance_status": "accepted", + "interpretation_ref": "oql://interpretation/service-delivery-v1/step-3-interp", + "oql_task_mappings": [ + { + "task_id": "oql-task-map:1", + "oql_task_ref": "oql://task/case-lookup", + "agent_action_ref": "dispatch:civic-run-a1b2-action-1" + }, + { + "task_id": "oql-task-map:2", + "oql_task_ref": "oql://task/eligibility-check", + "agent_action_ref": "dispatch:civic-run-a1b2-action-2" + } + ] + }, + "oac_compiler_invocation": { + "invocation_id": "oac-invocation:civic-run-a1b2", + "compiler_id": "oac://compiler/civic-stack/v1", + "artifact_manifest_id": "manifest://civic-stack/artifacts/2024-01-15-run-a1b2", + "invocation_status": "success", + "artifact_emission_refs": [ + "artifact://case-eligibility-result/run-a1b2/output-001", + "artifact://service-delivery-report/run-a1b2/output-002" + ] + }, + "subagent_delegations": [ + { + "delegation_id": "delegation:civic-run-a1b2-sub-1", + "delegated_to": "agent://agentplane/case-lookup-subagent/v1", + "task_ref": "task://rationalgrl/case-lookup-task/v1", + "policy_decision_ref": "policy-fabric://decision/pd-civic-run-a1b2-sub" + } + ], + "attestation_events": [ + { + "attestation_id": "attestation:civic-run-a1b2-policy", + "attestation_type": "policy_compliance", + "attested_by": "agent://agentplane/civic-stack-agent/v1", + "ref": "policy-fabric://decision/pd-civic-run-a1b2" + }, + { + "attestation_id": "attestation:civic-run-a1b2-oql", + "attestation_type": "oql_plan_acceptance", + "attested_by": "agent://agentplane/civic-stack-agent/v1", + "ref": "oql-acceptance:civic-run-a1b2" + } + ], + "timestamps": { + "started_at": "2024-01-15T10:00:00Z", + "completed_at": "2024-01-15T10:04:33Z" + }, + "provenance_refs": [ + "provenance://sociosphere/civic-stack-run/a1b2c3d4", + "provenance://ontogenesis/civic-run-lineage/a1b2" + ], + "rationalgrl_trace": { + "trace_id": "rationalgrl-trace:civic-run-a1b2", + "goals_addressed": [ + { + "goal_ref": "goal://rationalgrl/civic-service-delivery/primary", + "goal_type": "goal", + "satisfaction_status": "satisfied", + "contribution_refs": [ + "dispatch:civic-run-a1b2-action-1", + "dispatch:civic-run-a1b2-action-2" + ] + }, + { + "goal_ref": "goal://rationalgrl/civic-service-delivery/quality", + "goal_type": "softgoal", + "satisfaction_status": "partially_satisfied", + "contribution_refs": [ + "dispatch:civic-run-a1b2-action-1" + ] + } + ], + "tasks_executed": [ + { + "task_ref": "task://rationalgrl/case-lookup-task/v1", + "execution_status": "completed", + "resource_used_refs": ["resource://compute/civic-agent-executor/slot-7"], + "dependency_satisfied_refs": [] + }, + { + "task_ref": "task://rationalgrl/eligibility-check-task/v1", + "execution_status": "completed", + "resource_used_refs": ["resource://compute/civic-agent-executor/slot-7"], + "dependency_satisfied_refs": ["task://rationalgrl/case-lookup-task/v1"] + } + ], + "dependencies_blocked": [] + }, + "hellgraph_evidence_refs": [ + "evidence://hellgraph/civic-stack-run-a1b2/run-capsule", + "evidence://prophet-core/civic-stack-run-a1b2/emission" + ], + "delivery_excellence_signal_ref": "de://signal/civic-stack-run-a1b2/score", + "upstream_anchors": [ + "ontogenesis#80", + "ontogenesis#81", + "policy-fabric#72", + "sociosphere#323", + "delivery-excellence#28" + ], + "issued_at": "2024-01-15T10:04:35Z" +} diff --git a/tests/fixtures/civic-stack/valid.civic-stack-run-capsule.policy-blocked.json b/tests/fixtures/civic-stack/valid.civic-stack-run-capsule.policy-blocked.json new file mode 100644 index 00000000..727ec9b7 --- /dev/null +++ b/tests/fixtures/civic-stack/valid.civic-stack-run-capsule.policy-blocked.json @@ -0,0 +1,69 @@ +{ + "kind": "CivicStackRunCapsule", + "run_id": "civic-stack-run:blocked-b2c3d4e5-f6a7-8901-bcde-f12345678901", + "actor_ref": "agent://agentplane/civic-stack-agent/v1", + "oql_plan_id": "oql://plan/restricted-data-access-v1/step-1", + "artifact_manifest_id": "manifest://civic-stack/artifacts/2024-01-15-run-b2c3-blocked", + "policy_decision_id": "policy-fabric://decision/pd-civic-run-b2c3-deny", + "policy_decision_outcome": "deny", + "dataset_ids": ["dataset://restricted/pii-records/batch-009"], + "resource_ids": [], + "tool_grants": [], + "action_dispatch_records": [ + { + "dispatch_id": "dispatch:civic-run-b2c3-blocked-action-1", + "action_type": "pii.access", + "policy_decision_ref": "policy-fabric://decision/pd-civic-run-b2c3-deny", + "dispatch_status": "blocked", + "rationalgrl_task_ref": "task://rationalgrl/pii-access-task/v1", + "defeater_reason": "PolicyFabric denied PII access: insufficient authorization scope for beneficiary PII" + } + ], + "oql_plan_acceptance": { + "acceptance_id": "oql-acceptance:civic-run-b2c3-blocked", + "oql_plan_id": "oql://plan/restricted-data-access-v1/step-1", + "acceptance_status": "rejected", + "interpretation_ref": "oql://interpretation/restricted-data-access-v1/step-1-interp" + }, + "timestamps": { + "started_at": "2024-01-15T11:00:00Z", + "completed_at": "2024-01-15T11:00:04Z" + }, + "provenance_refs": [ + "provenance://sociosphere/civic-stack-run/b2c3-blocked" + ], + "rationalgrl_trace": { + "trace_id": "rationalgrl-trace:civic-run-b2c3-blocked", + "goals_addressed": [ + { + "goal_ref": "goal://rationalgrl/restricted-data-access/primary", + "goal_type": "goal", + "satisfaction_status": "denied", + "contribution_refs": [] + } + ], + "tasks_executed": [ + { + "task_ref": "task://rationalgrl/pii-access-task/v1", + "execution_status": "blocked", + "defeater_reason": "RationalGRL defeater: PolicyFabric deny decision prevents task execution" + } + ], + "dependencies_blocked": [ + { + "dependency_ref": "task://rationalgrl/pii-access-task/v1", + "blocking_policy_ref": "policy-fabric://decision/pd-civic-run-b2c3-deny", + "defeater_reason": "PII access blocked: authorization scope insufficient" + } + ] + }, + "hellgraph_evidence_refs": [ + "evidence://hellgraph/civic-stack-run-b2c3/run-capsule-blocked", + "evidence://prophet-core/civic-stack-run-b2c3/policy-deny-evidence" + ], + "upstream_anchors": [ + "ontogenesis#80", + "policy-fabric#72" + ], + "issued_at": "2024-01-15T11:00:05Z" +} diff --git a/tests/fixtures/conversational-evidence/reject.conversational-action-evidence.missing-hellgraph-refs.json b/tests/fixtures/conversational-evidence/reject.conversational-action-evidence.missing-hellgraph-refs.json new file mode 100644 index 00000000..e91cbf26 --- /dev/null +++ b/tests/fixtures/conversational-evidence/reject.conversational-action-evidence.missing-hellgraph-refs.json @@ -0,0 +1,16 @@ +{ + "_reject_reason": "hellgraph_evidence_refs is required but absent", + "kind": "ConversationalActionEvidence", + "evidence_id": "conv-evidence:reject-no-hellgraph", + "conversation_session_id": "conv-session:reject-sess-001", + "turn_id": "conv-turn:reject-turn-01", + "action_type": "query", + "actor_ref": "agent://agentplane/conversational-gateway/v1", + "policy_decision_ref": "policy-fabric://decision/pd-reject-no-hellgraph", + "execution_artifact_ref": "governed-run://agentplane/runs/reject-no-hellgraph", + "replay_linkage": { + "replay_eligible": false, + "replay_artifact_ref": "replay://agentplane/conversational/replay-reject" + }, + "issued_at": "2024-01-15T14:00:00Z" +} diff --git a/tests/fixtures/conversational-evidence/reject.conversational-replay-record.diverged-without-record.json b/tests/fixtures/conversational-evidence/reject.conversational-replay-record.diverged-without-record.json new file mode 100644 index 00000000..7e69c8fd --- /dev/null +++ b/tests/fixtures/conversational-evidence/reject.conversational-replay-record.diverged-without-record.json @@ -0,0 +1,13 @@ +{ + "_reject_reason": "replay_status=diverged requires divergence_record (conditional allOf)", + "kind": "ConversationalReplayRecord", + "replay_record_id": "conv-replay:reject-diverged-no-record", + "conversation_session_id": "conv-session:reject-sess-002", + "replay_trigger_ref": "conv-evidence:reject-trigger", + "replay_execution_ref": "governed-run://agentplane/runs/reject-diverged", + "replay_status": "diverged", + "replay_scope": "turn", + "policy_decision_ref": "policy-fabric://decision/pd-reject-diverged", + "non_claims": ["placeholder"], + "issued_at": "2024-01-15T16:00:00Z" +} diff --git a/tests/fixtures/conversational-evidence/valid.conversational-action-evidence.approval-denial.json b/tests/fixtures/conversational-evidence/valid.conversational-action-evidence.approval-denial.json new file mode 100644 index 00000000..219d3796 --- /dev/null +++ b/tests/fixtures/conversational-evidence/valid.conversational-action-evidence.approval-denial.json @@ -0,0 +1,27 @@ +{ + "kind": "ConversationalActionEvidence", + "evidence_id": "conv-evidence:b2c3d4-approval-denial", + "conversation_session_id": "conv-session:escalation-review-2024-01-15-sess-002", + "turn_id": "conv-turn:sess-002-turn-03", + "action_type": "approval_denial", + "actor_ref": "user://operator/senior-analyst/badge-7841", + "service_ref": "service://conversational-mesh/escalation-review/v1", + "policy_decision_ref": "policy-fabric://decision/pd-conv-approval-denial-b2c3", + "policy_outcome": "deny", + "execution_artifact_ref": "governed-run://agentplane/runs/run-b2c3d4e5-denied", + "execution_artifact_kind": "GovernedRunContract", + "replay_linkage": { + "replay_eligible": true, + "replay_artifact_ref": "replay://agentplane/conversational/replay-b2c3d4", + "replay_verified": false, + "replay_scope": "turn" + }, + "non_claims": [ + "This artifact does not record the reason for denial beyond the policy decision ref.", + "This artifact does not represent the outcome of any subsequent appeal." + ], + "hellgraph_evidence_refs": [ + "evidence://hellgraph/conversational/conv-evidence-b2c3d4" + ], + "issued_at": "2024-01-15T14:12:10Z" +} diff --git a/tests/fixtures/conversational-evidence/valid.conversational-action-evidence.trigger-execution.json b/tests/fixtures/conversational-evidence/valid.conversational-action-evidence.trigger-execution.json new file mode 100644 index 00000000..6846d16f --- /dev/null +++ b/tests/fixtures/conversational-evidence/valid.conversational-action-evidence.trigger-execution.json @@ -0,0 +1,36 @@ +{ + "kind": "ConversationalActionEvidence", + "evidence_id": "conv-evidence:a1b2c3-trigger-exec", + "conversation_session_id": "conv-session:case-review-2024-01-15-sess-001", + "turn_id": "conv-turn:sess-001-turn-07", + "action_type": "trigger_execution", + "actor_ref": "agent://agentplane/conversational-gateway/v1", + "service_ref": "service://conversational-mesh/case-review-service/v2", + "policy_decision_ref": "policy-fabric://decision/pd-conv-trigger-exec-a1b2", + "policy_outcome": "allow", + "execution_artifact_ref": "governed-run://agentplane/runs/run-a1b2c3d4-case-analysis", + "execution_artifact_kind": "GovernedRunContract", + "replay_linkage": { + "replay_eligible": true, + "replay_artifact_ref": "replay://agentplane/conversational/replay-a1b2c3", + "replay_verified": true, + "replay_divergence_detected": false, + "replay_scope": "execution_artifact" + }, + "annotation_refs": [ + "annotation://case-review/turn-06-analyst-note" + ], + "non_claims": [ + "This artifact does not claim to represent the full conversation transcript.", + "This artifact does not certify outcome quality; it records the execution trigger only." + ], + "hellgraph_evidence_refs": [ + "evidence://hellgraph/conversational/conv-evidence-a1b2c3", + "evidence://prophet-core/conversational/turn-07-exec-trigger" + ], + "upstream_anchors": [ + "standards-storage#conversational-standards", + "agentplane#149" + ], + "issued_at": "2024-01-15T14:07:55Z" +} diff --git a/tests/fixtures/conversational-evidence/valid.conversational-replay-record.completed.json b/tests/fixtures/conversational-evidence/valid.conversational-replay-record.completed.json new file mode 100644 index 00000000..dec63102 --- /dev/null +++ b/tests/fixtures/conversational-evidence/valid.conversational-replay-record.completed.json @@ -0,0 +1,20 @@ +{ + "kind": "ConversationalReplayRecord", + "replay_record_id": "conv-replay:a1b2c3-completed", + "conversation_session_id": "conv-session:case-review-2024-01-15-sess-001", + "turn_id": "conv-turn:sess-001-turn-07", + "replay_trigger_ref": "conv-evidence:a1b2c3-trigger-exec", + "replay_execution_ref": "governed-run://agentplane/runs/replay-run-a1b2c3d4", + "replay_status": "completed", + "replay_scope": "execution_artifact", + "policy_decision_ref": "policy-fabric://decision/pd-conv-replay-a1b2c3", + "non_claims": [ + "This replay record does not certify semantic equivalence of replay output to original.", + "This record does not represent live execution state." + ], + "upstream_anchors": [ + "standards-storage#conversational-standards", + "agentplane#149" + ], + "issued_at": "2024-01-15T15:30:00Z" +} diff --git a/tests/fixtures/conversational-evidence/valid.conversational-replay-record.diverged.json b/tests/fixtures/conversational-evidence/valid.conversational-replay-record.diverged.json new file mode 100644 index 00000000..f7cf9fdd --- /dev/null +++ b/tests/fixtures/conversational-evidence/valid.conversational-replay-record.diverged.json @@ -0,0 +1,23 @@ +{ + "kind": "ConversationalReplayRecord", + "replay_record_id": "conv-replay:c3d4e5-diverged", + "conversation_session_id": "conv-session:policy-migration-sess-003", + "turn_id": "conv-turn:sess-003-turn-02", + "replay_trigger_ref": "conv-evidence:c3d4e5-replay-request", + "replay_execution_ref": "governed-run://agentplane/runs/replay-run-c3d4e5", + "replay_status": "diverged", + "replay_scope": "execution_artifact", + "policy_decision_ref": "policy-fabric://decision/pd-conv-replay-c3d4e5", + "divergence_record": { + "detected_at": "2024-01-15T16:05:22Z", + "divergence_type": "policy_change", + "original_ref": "governed-run://agentplane/runs/run-c3d4e5-original", + "replay_ref": "governed-run://agentplane/runs/replay-run-c3d4e5", + "resolution_status": "escalated" + }, + "non_claims": [ + "This record does not prescribe a resolution path for the detected divergence.", + "Divergence escalation outcome is tracked separately by the policy fabric." + ], + "issued_at": "2024-01-15T16:05:25Z" +} diff --git a/tools/validate_civic_stack_runtime_evidence.py b/tools/validate_civic_stack_runtime_evidence.py new file mode 100644 index 00000000..f4c48502 --- /dev/null +++ b/tools/validate_civic_stack_runtime_evidence.py @@ -0,0 +1,123 @@ +#!/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: + raise SystemExit("jsonschema is required: python3 -m pip install jsonschema") from exc + +ROOT = Path(__file__).resolve().parents[1] +SCHEMA = ROOT / "schemas" / "civic-stack-run-capsule.schema.v0.1.json" +FIXTURES = ROOT / "tests" / "fixtures" / "civic-stack" + +ALLOWED_POLICY_OUTCOMES = {"allow", "allow_with_constraints", "deny", "escalate"} +ALLOWED_DISPATCH_STATUSES = {"dispatched", "blocked", "deferred", "completed"} + + +def load_json(path: Path) -> dict[str, Any]: + data = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(data, dict): + raise ValueError("root must be object") + return data + + +def check_policy_gates(data: dict[str, Any]) -> list[str]: + problems: list[str] = [] + + outcome = data.get("policy_decision_outcome") + if outcome and outcome not in ALLOWED_POLICY_OUTCOMES: + problems.append(f"policy_decision_outcome invalid: {outcome}") + + # deny outcome: tool_grants must be empty and all dispatches must be blocked + if outcome == "deny": + grants = data.get("tool_grants", []) + if grants: + problems.append("deny policy_decision_outcome must have no tool_grants") + dispatches = data.get("action_dispatch_records", []) + non_blocked = [d for d in dispatches if d.get("dispatch_status") != "blocked"] + if non_blocked: + problems.append("deny policy_decision_outcome requires all dispatches blocked") + + # blocked dispatches must have defeater_reason + for dispatch in data.get("action_dispatch_records", []): + if dispatch.get("dispatch_status") == "blocked" and not dispatch.get("defeater_reason"): + problems.append(f"blocked dispatch {dispatch.get('dispatch_id')} requires defeater_reason") + + # rationalgrl_trace: blocked tasks must have defeater_reason + trace = data.get("rationalgrl_trace", {}) + for task in trace.get("tasks_executed", []): + if task.get("execution_status") == "blocked" and not task.get("defeater_reason"): + problems.append(f"blocked task {task.get('task_ref')} in rationalgrl_trace requires defeater_reason") + + # hellgraph_evidence_refs required and non-empty (belt-and-suspenders on schema minItems) + hg_refs = data.get("hellgraph_evidence_refs", []) + if not hg_refs: + problems.append("hellgraph_evidence_refs must not be empty") + + # provenance_refs required and non-empty + prov_refs = data.get("provenance_refs", []) + if not prov_refs: + problems.append("provenance_refs must not be empty") + + # oac_compiler_invocation: failure status should have no artifact_emission_refs + oac = data.get("oac_compiler_invocation") + if oac and oac.get("invocation_status") == "failure": + if oac.get("artifact_emission_refs"): + problems.append("oac_compiler_invocation failure must not have artifact_emission_refs") + + return problems + + +def validate_file(path: Path, schema: dict[str, Any]) -> list[str]: + try: + data = load_json(path) + except Exception as exc: + return [f"parse error: {exc}"] + try: + jsonschema.validate(data, schema) + except jsonschema.ValidationError as exc: + return [f"schema: {exc.message}"] + return check_policy_gates(data) + + +def main() -> int: + schema = load_json(SCHEMA) + failed = False + + valids = sorted(FIXTURES.glob("valid.*.json")) + if not valids: + raise SystemExit("missing valid civic-stack fixtures") + + for path in valids: + problems = validate_file(path, schema) + if problems: + print(f"FAIL (valid): {path.name}") + for p in problems: + print(f" - {p}") + failed = True + else: + print(f"ok: {path.name}") + + rejects = sorted(FIXTURES.glob("reject.*.json")) + if not rejects: + raise SystemExit("missing reject civic-stack fixtures") + + for path in rejects: + problems = validate_file(path, schema) + if not problems: + print(f"FAIL (reject should have failed): {path.name}") + failed = True + else: + print(f"ok (rejected as expected): {path.name}") + + print(("PASS" if not failed else "FAIL") + f": civic-stack runtime evidence — {len(valids)} valid, {len(rejects)} reject") + return 0 if not failed else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/validate_conversational_evidence.py b/tools/validate_conversational_evidence.py new file mode 100644 index 00000000..a99aef72 --- /dev/null +++ b/tools/validate_conversational_evidence.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +try: + import jsonschema +except ImportError as exc: + raise SystemExit("jsonschema is required: python3 -m pip install jsonschema") from exc + +ROOT = Path(__file__).resolve().parents[1] +SCHEMA_CAE = ROOT / "schemas" / "conversational-action-evidence.schema.v0.1.json" +SCHEMA_CRR = ROOT / "schemas" / "conversational-replay-record.schema.v0.1.json" +FIXTURES = ROOT / "tests" / "fixtures" / "conversational-evidence" + + +def load_json(path: Path) -> dict[str, Any]: + data = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(data, dict): + raise ValueError("root must be object") + return data + + +def schema_for(data: dict[str, Any], schemas: dict[str, Any]) -> dict[str, Any]: + kind = data.get("kind", "") + if kind == "ConversationalActionEvidence": + return schemas["cae"] + if kind == "ConversationalReplayRecord": + return schemas["crr"] + raise ValueError(f"unknown kind: {kind}") + + +def check_policy_gates(data: dict[str, Any]) -> list[str]: + problems: list[str] = [] + kind = data.get("kind") + + if kind == "ConversationalActionEvidence": + hg_refs = data.get("hellgraph_evidence_refs", []) + if not hg_refs: + problems.append("hellgraph_evidence_refs must not be empty") + + replay = data.get("replay_linkage", {}) + if replay.get("replay_divergence_detected") and not replay.get("replay_divergence_ref"): + problems.append("replay_divergence_detected=true requires replay_divergence_ref") + + action_type = data.get("action_type") + policy_outcome = data.get("policy_outcome") + if action_type == "approval_denial" and policy_outcome not in ("deny", "escalate"): + problems.append("approval_denial action_type requires deny or escalate policy_outcome") + + if kind == "ConversationalReplayRecord": + non_claims = data.get("non_claims", []) + if not non_claims: + problems.append("non_claims must not be empty") + + if data.get("replay_status") == "diverged" and not data.get("divergence_record"): + problems.append("replay_status=diverged requires divergence_record") + + div_rec = data.get("divergence_record") + if div_rec and data.get("replay_status") != "diverged": + problems.append("divergence_record present but replay_status is not diverged") + + return problems + + +def validate_file(path: Path, schemas: dict[str, Any]) -> list[str]: + try: + data = load_json(path) + except Exception as exc: + return [f"parse error: {exc}"] + try: + schema = schema_for(data, schemas) + jsonschema.validate(data, schema) + except (jsonschema.ValidationError, ValueError) as exc: + return [f"schema: {exc}"] + return check_policy_gates(data) + + +def main() -> int: + schemas = { + "cae": load_json(SCHEMA_CAE), + "crr": load_json(SCHEMA_CRR), + } + failed = False + + valids = sorted(FIXTURES.glob("valid.*.json")) + if not valids: + raise SystemExit("missing valid conversational-evidence fixtures") + + for path in valids: + problems = validate_file(path, schemas) + if problems: + print(f"FAIL (valid): {path.name}") + for p in problems: + print(f" - {p}") + failed = True + else: + print(f"ok: {path.name}") + + rejects = sorted(FIXTURES.glob("reject.*.json")) + if not rejects: + raise SystemExit("missing reject conversational-evidence fixtures") + + for path in rejects: + problems = validate_file(path, schemas) + if not problems: + print(f"FAIL (reject should have failed): {path.name}") + failed = True + else: + print(f"ok (rejected as expected): {path.name}") + + print(("PASS" if not failed else "FAIL") + f": conversational evidence — {len(valids)} valid, {len(rejects)} reject") + return 0 if not failed else 1 + + +if __name__ == "__main__": + raise SystemExit(main())