feat(runner): wire provider-neutral PRP receipts

Add versioned semantic-tool and budget stop receipts with replay parity, conformance fixtures, and normalized adapter evidence. Score eval trace completeness from PRP receipts while preserving the existing live evidence fallback.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-11 19:54:37 +00:00
1 parent 3cf6db82c1
commit 90caa73b95
49 files changed
+3816 -76

No files matched your search

+1
View File
@@ -164,6 +164,7 @@ and JSON content; see the protocol-server tutorial for direct `curl` examples.
- [Capability live runnerd/Codex loop](docs/capability-live-runnerd-codex.md)
- [Capability issue-thread UI](docs/capability-issue-thread-ui.md)
- [PRP compatibility/versioning policy](docs/protocol-compatibility.md)
- [PRP v1 expressiveness audit](spec/prp-v1-expressiveness-audit.md)
- [Cumulative end-to-end tutorial](docs/tutorials/end-to-end.md)
- [OKF knowledge bundle](knowledge/)
- [Normative native-runner contract](spec/native-runner-contract.md)
+1
View File
@@ -24,6 +24,7 @@ Each tutorial explains the capability it exercises and the contract it proves.
- [Architecture and dependency boundary](architecture.md)
- [PRP compatibility and versioning policy](protocol-compatibility.md)
- [PRP v1 expressiveness audit](../spec/prp-v1-expressiveness-audit.md)
- [ADR 0001: runner, testing, and eval package boundaries](adr/0001-runner-testing-eval-package-boundaries.md)
- [Local protocol and supervision](local-runner.md)
- [Durable transport and recovery](durable-recovery.md)
@@ -21,6 +21,9 @@ production control-plane behavior.
| `protocolVersion` | `1` | Required. Negotiate the highest overlapping version; no overlap fails closed. |
| `fixtureVersion` | `1` | Required by the conformance corpus. Unknown values fail closed. |
| `event.schemaVersion` | `1` | Required on every event. Unknown values fail closed before reduction. |
| `capabilities.semanticTools.schemaVersion` | `1` | Optional advertisement. When present, an unknown required version fails closed. |
| `payload.semantic_tool.schemaVersion` | `1` | Optional on paired semantic tool input/result events. When present, an unknown required version fails closed. |
| `terminal.stopReason.schemaVersion` | `1` | Optional budget/cost receipt. When present, an unknown required version fails closed. |
| Typed `schema` discriminators | `*.v1` | Required. Unknown required schema identities fail JSON Schema validation. |
Wire protocol versions and fixture-corpus versions are independent. A fixture
@@ -45,6 +48,35 @@ The forward-compatibility fixture proves that optional fields survive validation
without changing the v1 snapshot. The unsupported-version fixture proves that a
required v2 protocol cannot be replayed by this consumer.
## Provider-neutral semantic receipts
- `capabilities.semanticTools` advertises stable operation IDs, availability,
required claims, and redaction disposition without naming a provider API.
- `mcp_app.tool_input` and `mcp_app.tool_result` may carry paired
`semantic_tool` envelopes. Correlation IDs must match the containing event;
operation ID and idempotency key must match across the pair.
- Content is represented by a canonical SHA-256 digest plus allowlisted typed
references. Raw credentials, provider payloads, and hidden identifiers do not
belong on the wire.
- Result receipts distinguish success, denial, conflict, exact duplicate,
unavailable, and failure. They can name the authorization boundary, safe
revision, artifact/work-product refs, immutable governed targets, and bounded
wake/monitor causality.
- `terminal.stopReason` records budget/cost kind, stable code, retryability,
limit class, safe aggregate, and decision receipt.
These fields are trace evidence only. The v1 reducer ignores `semantic_tool`
payloads, so adding or extending the optional envelope has no projection
effect. Eval `trace_completeness` treats PRP wire receipts as authoritative when
present and retains the pre-existing scalar fallback for live evidence that has
not yet emitted them.
Seven conformance fixtures cover artifact success, redacted denial without
fallback, stale conflict plus duplicate retry, governed target and continuation
causality, budget/cost stop, unknown optional fields, and rejection of an
unknown required version. The six accepted fixtures have shared TypeScript and
Rust golden parity summaries.
## Replay semantics
- Events are applied in fixture order and ordered independently by
@@ -0,0 +1,55 @@
{
"schema": "paperclip.prp.fixture.v1",
"fixtureVersion": 1,
"protocolVersion": 1,
"name": "Budget cost stop reason",
"description": "A terminal budget hard stop carries a safe usage aggregate, decision id, and stable stop receipt.",
"identity": {
"schema": "paperclip.prp.identity.v1", "companyId": "company_budget_stop", "issueId": "issue_budget_stop", "runId": "run_budget_stop",
"environmentLeaseId": "lease_budget_stop", "runnerInstanceId": "runner_budget_stop", "normalizedSessionId": "session_budget_stop", "driverSessionId": "driver_budget_stop"
},
"capabilities": {
"schema": "paperclip.prp.capabilities.v1", "sessionReusePolicy": "new_per_run", "driver": { "kind": "provider-neutral", "version": "1.0.0" },
"steer": false, "interrupt": true, "resume": true, "runtimeRequests": false, "structuredResult": true, "typedEvents": true, "unsupported": []
},
"commands": [],
"events": [
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "budget_stop_01", "sourceSeq": 1, "sourceInstanceId": "runner_budget_stop",
"sourceKind": "runner", "runId": "run_budget_stop", "normalizedSessionId": "session_budget_stop", "turnId": "turn_budget_stop",
"eventType": "run.result.proposed", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:40:00.000Z",
"payload": {
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "blocked", "summary": "The actor budget reached its hard limit.",
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": false, "criteria": [], "remainingWork": [{ "description": "The budget owner must raise the limit or wait for reset.", "blocksCompletion": true }] },
"evidence": [{ "decisionId": "budget_decision_stop" }],
"verification": [{ "commandOrCheck": "budget aggregate receipt", "status": "passed" }],
"blocker": { "reasonCode": "budget_hard_stop", "owner": { "kind": "budget_owner" }, "unblockAction": "Raise the actor budget or wait for the monthly reset.", "scope": "task_wide" },
"attentionRequests": [], "artifacts": []
}
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "budget_stop_02", "sourceSeq": 2, "sourceInstanceId": "runner_budget_stop",
"sourceKind": "runner", "runId": "run_budget_stop", "normalizedSessionId": "session_budget_stop", "turnId": "turn_budget_stop",
"eventType": "run.terminal", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:40:00.010Z",
"payload": {
"schema": "paperclip.prp.terminal.v1", "turnTerminalState": "cancelled", "runTerminalState": "cancelled", "reportedWorkDisposition": "blocked",
"workAssessmentId": "assessment_budget_stop", "statusDecisionId": "decision_budget_stop",
"stopReason": {
"schema": "paperclip.prp.stop_reason.v1", "schemaVersion": 1, "receiptId": "stop_receipt_budget", "kind": "budget",
"code": "budget_hard_stop", "retryable": true, "decisionId": "budget_decision_stop", "limitClass": "actor_monthly",
"aggregate": { "unit": "cents", "observed": 10000, "limit": 10000, "window": "monthly_utc" },
"relatedReceiptIds": []
}
}
}
],
"requests": [],
"result": {
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "blocked", "summary": "The actor budget reached its hard limit.",
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": false, "criteria": [], "remainingWork": [{ "description": "The budget owner must raise the limit or wait for reset.", "blocksCompletion": true }] },
"evidence": [{ "decisionId": "budget_decision_stop" }],
"verification": [{ "commandOrCheck": "budget aggregate receipt", "status": "passed" }],
"blocker": { "reasonCode": "budget_hard_stop", "owner": { "kind": "budget_owner" }, "unblockAction": "Raise the actor budget or wait for the monthly reset.", "scope": "task_wide" },
"attentionRequests": [], "artifacts": []
}
}
@@ -0,0 +1,126 @@
{
"schema": "paperclip.prp.session-snapshot.v1",
"fixtureName": "Budget cost stop reason",
"identity": {
"schema": "paperclip.prp.identity.v1",
"companyId": "company_budget_stop",
"issueId": "issue_budget_stop",
"runId": "run_budget_stop",
"environmentLeaseId": "lease_budget_stop",
"runnerInstanceId": "runner_budget_stop",
"normalizedSessionId": "session_budget_stop",
"driverSessionId": "driver_budget_stop"
},
"capabilities": {
"schema": "paperclip.prp.capabilities.v1",
"sessionReusePolicy": "new_per_run",
"driver": {
"kind": "provider-neutral",
"version": "1.0.0"
},
"steer": false,
"interrupt": true,
"resume": true,
"runtimeRequests": false,
"structuredResult": true,
"typedEvents": true,
"unsupported": []
},
"runPhase": "terminal",
"sessionState": "not_started",
"turnState": "not_started",
"activeTurnId": null,
"items": [],
"requests": [],
"proposedResult": {
"schema": "paperclip.run_result.v1",
"reportedWorkDisposition": "blocked",
"summary": "The actor budget reached its hard limit.",
"completionClaim": {
"contractRevision": "semantic-receipts-v1",
"objectiveSatisfied": false,
"criteria": [],
"remainingWork": [
{
"description": "The budget owner must raise the limit or wait for reset.",
"blocksCompletion": true
}
]
},
"evidence": [
{
"decisionId": "budget_decision_stop"
}
],
"verification": [
{
"commandOrCheck": "budget aggregate receipt",
"status": "passed"
}
],
"blocker": {
"reasonCode": "budget_hard_stop",
"owner": {
"kind": "budget_owner"
},
"unblockAction": "Raise the actor budget or wait for the monthly reset.",
"scope": "task_wide"
},
"attentionRequests": [],
"artifacts": []
},
"terminal": {
"schema": "paperclip.prp.terminal.v1",
"turnTerminalState": "cancelled",
"runTerminalState": "cancelled",
"reportedWorkDisposition": "blocked",
"workAssessmentId": "assessment_budget_stop",
"statusDecisionId": "decision_budget_stop",
"stopReason": {
"schema": "paperclip.prp.stop_reason.v1",
"schemaVersion": 1,
"receiptId": "stop_receipt_budget",
"kind": "budget",
"code": "budget_hard_stop",
"retryable": true,
"decisionId": "budget_decision_stop",
"limitClass": "actor_monthly",
"aggregate": {
"unit": "cents",
"observed": 10000,
"limit": 10000,
"window": "monthly_utc"
},
"relatedReceiptIds": []
}
},
"timeline": [
{
"position": 1,
"sourceEventId": "budget_stop_01",
"sourceSeq": 1,
"eventType": "run.result.proposed",
"emittedAt": "2026-08-11T10:40:00.000Z",
"summary": "The actor budget reached its hard limit."
},
{
"position": 2,
"sourceEventId": "budget_stop_02",
"sourceSeq": 2,
"eventType": "run.terminal",
"emittedAt": "2026-08-11T10:40:00.010Z",
"summary": "Run cancelled"
}
],
"sourceCursors": {
"runner:runner_budget_stop": 2
},
"processedEventIds": [
"budget_stop_01",
"budget_stop_02"
],
"duplicateEventIds": [],
"outOfOrderEventIds": [],
"gaps": [],
"integrity": "complete"
}
@@ -0,0 +1,9 @@
{
"runId": "run_budget_stop",
"integrity": "complete",
"timelineCount": 2,
"duplicateEventIds": [],
"gaps": [],
"turnTerminalState": "cancelled",
"runTerminalState": "cancelled"
}
@@ -0,0 +1,136 @@
{
"schema": "paperclip.prp.session-snapshot.v1",
"fixtureName": "Semantic tool artifact happy path",
"identity": {
"schema": "paperclip.prp.identity.v1",
"companyId": "company_semantic_happy",
"issueId": "issue_semantic_happy",
"runId": "run_semantic_happy",
"environmentLeaseId": "lease_semantic_happy",
"runnerInstanceId": "runner_semantic_happy",
"normalizedSessionId": "session_semantic_happy",
"driverSessionId": "driver_semantic_happy"
},
"capabilities": {
"schema": "paperclip.prp.capabilities.v1",
"sessionReusePolicy": "new_per_run",
"driver": {
"kind": "provider-neutral",
"version": "1.0.0"
},
"steer": true,
"interrupt": true,
"resume": true,
"runtimeRequests": true,
"structuredResult": true,
"typedEvents": true,
"semanticTools": {
"schema": "paperclip.prp.semantic_tools.v1",
"schemaVersion": 1,
"operations": [
{
"operationId": "register_deliverable",
"version": 1,
"availability": "available",
"redactionDisposition": "digest_only",
"requiredClaims": []
}
]
},
"unsupported": []
},
"runPhase": "terminal",
"sessionState": "not_started",
"turnState": "not_started",
"activeTurnId": null,
"items": [],
"requests": [],
"proposedResult": {
"schema": "paperclip.run_result.v1",
"reportedWorkDisposition": "done",
"summary": "The artifact was registered through the semantic tool receipt.",
"completionClaim": {
"contractRevision": "semantic-receipts-v1",
"objectiveSatisfied": true,
"criteria": [],
"remainingWork": []
},
"evidence": [
{
"receiptId": "receipt_semantic_happy"
}
],
"verification": [
{
"commandOrCheck": "semantic artifact receipt",
"status": "passed",
"artifactRef": "artifact_semantic_happy"
}
],
"attentionRequests": [],
"artifacts": [
{
"kind": "artifact",
"ref": "artifact_semantic_happy",
"title": "Semantic receipt report"
}
]
},
"terminal": {
"schema": "paperclip.prp.terminal.v1",
"turnTerminalState": "completed",
"runTerminalState": "succeeded",
"reportedWorkDisposition": "done",
"workAssessmentId": "assessment_semantic_happy",
"statusDecisionId": "decision_semantic_happy"
},
"timeline": [
{
"position": 1,
"sourceEventId": "semantic_happy_01",
"sourceSeq": 1,
"eventType": "mcp_app.tool_input",
"emittedAt": "2026-08-11T10:00:00.000Z",
"itemId": "item_semantic_happy",
"summary": "mcp_app tool_input"
},
{
"position": 2,
"sourceEventId": "semantic_happy_02",
"sourceSeq": 2,
"eventType": "mcp_app.tool_result",
"emittedAt": "2026-08-11T10:00:00.010Z",
"itemId": "item_semantic_happy",
"summary": "mcp_app tool_result"
},
{
"position": 3,
"sourceEventId": "semantic_happy_03",
"sourceSeq": 3,
"eventType": "run.result.proposed",
"emittedAt": "2026-08-11T10:00:00.020Z",
"summary": "The artifact was registered through the semantic tool receipt."
},
{
"position": 4,
"sourceEventId": "semantic_happy_04",
"sourceSeq": 4,
"eventType": "run.terminal",
"emittedAt": "2026-08-11T10:00:00.030Z",
"summary": "Run succeeded"
}
],
"sourceCursors": {
"runner:runner_semantic_happy": 4
},
"processedEventIds": [
"semantic_happy_01",
"semantic_happy_02",
"semantic_happy_03",
"semantic_happy_04"
],
"duplicateEventIds": [],
"outOfOrderEventIds": [],
"gaps": [],
"integrity": "complete"
}
@@ -0,0 +1,9 @@
{
"runId": "run_semantic_happy",
"integrity": "complete",
"timelineCount": 4,
"duplicateEventIds": [],
"gaps": [],
"turnTerminalState": "completed",
"runTerminalState": "succeeded"
}
@@ -0,0 +1,172 @@
{
"schema": "paperclip.prp.session-snapshot.v1",
"fixtureName": "Semantic tool conflict and duplicate retry",
"identity": {
"schema": "paperclip.prp.identity.v1",
"companyId": "company_semantic_retry",
"issueId": "issue_semantic_retry",
"runId": "run_semantic_retry",
"environmentLeaseId": "lease_semantic_retry",
"runnerInstanceId": "runner_semantic_retry",
"normalizedSessionId": "session_semantic_retry",
"driverSessionId": "driver_semantic_retry"
},
"capabilities": {
"schema": "paperclip.prp.capabilities.v1",
"sessionReusePolicy": "new_per_run",
"driver": {
"kind": "provider-neutral",
"version": "1.0.0"
},
"steer": false,
"interrupt": true,
"resume": true,
"runtimeRequests": true,
"structuredResult": true,
"typedEvents": true,
"semanticTools": {
"schema": "paperclip.prp.semantic_tools.v1",
"schemaVersion": 1,
"operations": [
{
"operationId": "write_document",
"version": 1,
"availability": "available",
"redactionDisposition": "digest_only",
"requiredClaims": []
}
]
},
"unsupported": []
},
"runPhase": "terminal",
"sessionState": "not_started",
"turnState": "not_started",
"activeTurnId": null,
"items": [],
"requests": [],
"proposedResult": {
"schema": "paperclip.run_result.v1",
"reportedWorkDisposition": "done",
"summary": "The revision-safe retry committed once.",
"completionClaim": {
"contractRevision": "semantic-receipts-v1",
"objectiveSatisfied": true,
"criteria": [],
"remainingWork": []
},
"evidence": [
{
"receiptId": "receipt_semantic_conflict"
},
{
"receiptId": "receipt_semantic_document_commit"
}
],
"verification": [
{
"commandOrCheck": "conflict then exact duplicate retry",
"status": "passed"
}
],
"attentionRequests": [],
"artifacts": []
},
"terminal": {
"schema": "paperclip.prp.terminal.v1",
"turnTerminalState": "completed",
"runTerminalState": "succeeded",
"reportedWorkDisposition": "done",
"workAssessmentId": "assessment_semantic_retry",
"statusDecisionId": "decision_semantic_retry"
},
"timeline": [
{
"position": 1,
"sourceEventId": "semantic_retry_01",
"sourceSeq": 1,
"eventType": "mcp_app.tool_input",
"emittedAt": "2026-08-11T10:20:00.000Z",
"itemId": "item_semantic_conflict",
"summary": "mcp_app tool_input"
},
{
"position": 2,
"sourceEventId": "semantic_retry_02",
"sourceSeq": 2,
"eventType": "mcp_app.tool_result",
"emittedAt": "2026-08-11T10:20:00.010Z",
"itemId": "item_semantic_conflict",
"summary": "mcp_app tool_result"
},
{
"position": 3,
"sourceEventId": "semantic_retry_03",
"sourceSeq": 3,
"eventType": "mcp_app.tool_input",
"emittedAt": "2026-08-11T10:20:00.020Z",
"itemId": "item_semantic_fresh",
"summary": "mcp_app tool_input"
},
{
"position": 4,
"sourceEventId": "semantic_retry_04",
"sourceSeq": 4,
"eventType": "mcp_app.tool_result",
"emittedAt": "2026-08-11T10:20:00.030Z",
"itemId": "item_semantic_fresh",
"summary": "mcp_app tool_result"
},
{
"position": 5,
"sourceEventId": "semantic_retry_05",
"sourceSeq": 5,
"eventType": "mcp_app.tool_input",
"emittedAt": "2026-08-11T10:20:00.040Z",
"itemId": "item_semantic_duplicate",
"summary": "mcp_app tool_input"
},
{
"position": 6,
"sourceEventId": "semantic_retry_06",
"sourceSeq": 6,
"eventType": "mcp_app.tool_result",
"emittedAt": "2026-08-11T10:20:00.050Z",
"itemId": "item_semantic_duplicate",
"summary": "mcp_app tool_result"
},
{
"position": 7,
"sourceEventId": "semantic_retry_07",
"sourceSeq": 7,
"eventType": "run.result.proposed",
"emittedAt": "2026-08-11T10:20:00.060Z",
"summary": "The revision-safe retry committed once."
},
{
"position": 8,
"sourceEventId": "semantic_retry_08",
"sourceSeq": 8,
"eventType": "run.terminal",
"emittedAt": "2026-08-11T10:20:00.070Z",
"summary": "Run succeeded"
}
],
"sourceCursors": {
"runner:runner_semantic_retry": 8
},
"processedEventIds": [
"semantic_retry_01",
"semantic_retry_02",
"semantic_retry_03",
"semantic_retry_04",
"semantic_retry_05",
"semantic_retry_06",
"semantic_retry_07",
"semantic_retry_08"
],
"duplicateEventIds": [],
"outOfOrderEventIds": [],
"gaps": [],
"integrity": "complete"
}
@@ -0,0 +1,9 @@
{
"runId": "run_semantic_retry",
"integrity": "complete",
"timelineCount": 8,
"duplicateEventIds": [],
"gaps": [],
"turnTerminalState": "completed",
"runTerminalState": "succeeded"
}
@@ -0,0 +1,137 @@
{
"schema": "paperclip.prp.session-snapshot.v1",
"fixtureName": "Semantic tool denial and redaction",
"identity": {
"schema": "paperclip.prp.identity.v1",
"companyId": "company_semantic_denied",
"issueId": "issue_semantic_denied",
"runId": "run_semantic_denied",
"environmentLeaseId": "lease_semantic_denied",
"runnerInstanceId": "runner_semantic_denied",
"normalizedSessionId": "session_semantic_denied",
"driverSessionId": "driver_semantic_denied"
},
"capabilities": {
"schema": "paperclip.prp.capabilities.v1",
"sessionReusePolicy": "new_per_run",
"driver": {
"kind": "provider-neutral",
"version": "1.0.0"
},
"steer": false,
"interrupt": true,
"resume": true,
"runtimeRequests": true,
"structuredResult": true,
"typedEvents": true,
"semanticTools": {
"schema": "paperclip.prp.semantic_tools.v1",
"schemaVersion": 1,
"operations": [
{
"operationId": "decide_approval",
"version": 1,
"availability": "denied",
"redactionDisposition": "digest_only",
"requiredClaims": [
"governance:approvals:decide"
],
"reasonCode": "required_claim_missing"
}
]
},
"unsupported": []
},
"runPhase": "terminal",
"sessionState": "not_started",
"turnState": "not_started",
"activeTurnId": null,
"items": [],
"requests": [],
"proposedResult": {
"schema": "paperclip.run_result.v1",
"reportedWorkDisposition": "needs_review",
"summary": "The governed action was denied without fallback.",
"completionClaim": {
"contractRevision": "semantic-receipts-v1",
"objectiveSatisfied": false,
"criteria": [],
"remainingWork": [
{
"description": "An authorized approver must decide.",
"blocksCompletion": true
}
]
},
"evidence": [
{
"receiptId": "receipt_semantic_denied"
}
],
"verification": [
{
"commandOrCheck": "no fallback semantic call",
"status": "passed"
}
],
"attentionRequests": [],
"artifacts": []
},
"terminal": {
"schema": "paperclip.prp.terminal.v1",
"turnTerminalState": "completed",
"runTerminalState": "succeeded",
"reportedWorkDisposition": "needs_review",
"workAssessmentId": "assessment_semantic_denied",
"statusDecisionId": "decision_semantic_denied"
},
"timeline": [
{
"position": 1,
"sourceEventId": "semantic_denied_01",
"sourceSeq": 1,
"eventType": "mcp_app.tool_input",
"emittedAt": "2026-08-11T10:10:00.000Z",
"itemId": "item_semantic_denied",
"summary": "mcp_app tool_input"
},
{
"position": 2,
"sourceEventId": "semantic_denied_02",
"sourceSeq": 2,
"eventType": "mcp_app.tool_result",
"emittedAt": "2026-08-11T10:10:00.010Z",
"itemId": "item_semantic_denied",
"summary": "mcp_app tool_result"
},
{
"position": 3,
"sourceEventId": "semantic_denied_03",
"sourceSeq": 3,
"eventType": "run.result.proposed",
"emittedAt": "2026-08-11T10:10:00.020Z",
"summary": "The governed action was denied without fallback."
},
{
"position": 4,
"sourceEventId": "semantic_denied_04",
"sourceSeq": 4,
"eventType": "run.terminal",
"emittedAt": "2026-08-11T10:10:00.030Z",
"summary": "Run succeeded"
}
],
"sourceCursors": {
"runner:runner_semantic_denied": 4
},
"processedEventIds": [
"semantic_denied_01",
"semantic_denied_02",
"semantic_denied_03",
"semantic_denied_04"
],
"duplicateEventIds": [],
"outOfOrderEventIds": [],
"gaps": [],
"integrity": "complete"
}
@@ -0,0 +1,9 @@
{
"runId": "run_semantic_denied",
"integrity": "complete",
"timelineCount": 4,
"duplicateEventIds": [],
"gaps": [],
"turnTerminalState": "completed",
"runTerminalState": "succeeded"
}
@@ -0,0 +1,139 @@
{
"schema": "paperclip.prp.session-snapshot.v1",
"fixtureName": "Semantic governance target and continuation chain",
"identity": {
"schema": "paperclip.prp.identity.v1",
"companyId": "company_semantic_governance",
"issueId": "issue_semantic_governance",
"runId": "run_semantic_governance",
"environmentLeaseId": "lease_semantic_governance",
"runnerInstanceId": "runner_semantic_governance",
"normalizedSessionId": "session_semantic_governance",
"driverSessionId": "driver_semantic_governance"
},
"capabilities": {
"schema": "paperclip.prp.capabilities.v1",
"sessionReusePolicy": "reuse_per_issue",
"driver": {
"kind": "provider-neutral",
"version": "1.0.0"
},
"steer": true,
"interrupt": true,
"resume": true,
"runtimeRequests": true,
"structuredResult": true,
"typedEvents": true,
"semanticTools": {
"schema": "paperclip.prp.semantic_tools.v1",
"schemaVersion": 1,
"operations": [
{
"operationId": "request_human_input",
"version": 1,
"availability": "available",
"redactionDisposition": "digest_only",
"requiredClaims": []
}
]
},
"unsupported": []
},
"runPhase": "terminal",
"sessionState": "not_started",
"turnState": "not_started",
"activeTurnId": null,
"items": [],
"requests": [],
"proposedResult": {
"schema": "paperclip.run_result.v1",
"reportedWorkDisposition": "yielded",
"summary": "The immutable decision target is waiting on a bounded monitor continuation.",
"completionClaim": {
"contractRevision": "semantic-receipts-v1",
"objectiveSatisfied": false,
"criteria": [],
"remainingWork": [
{
"description": "Wait for the interaction response.",
"blocksCompletion": true
}
]
},
"evidence": [
{
"receiptId": "receipt_semantic_governance"
}
],
"verification": [
{
"commandOrCheck": "immutable target and causal chain",
"status": "passed"
}
],
"attentionRequests": [],
"artifacts": [],
"continuation": {
"kind": "monitor",
"summary": "Recheck after the typed response wake.",
"idempotencyKey": "monitor_semantic_governance"
}
},
"terminal": {
"schema": "paperclip.prp.terminal.v1",
"turnTerminalState": "completed",
"runTerminalState": "succeeded",
"reportedWorkDisposition": "yielded",
"workAssessmentId": "assessment_semantic_governance",
"statusDecisionId": "decision_semantic_governance"
},
"timeline": [
{
"position": 1,
"sourceEventId": "semantic_governance_01",
"sourceSeq": 1,
"eventType": "mcp_app.tool_input",
"emittedAt": "2026-08-11T10:30:00.000Z",
"itemId": "item_semantic_governance",
"summary": "mcp_app tool_input"
},
{
"position": 2,
"sourceEventId": "semantic_governance_02",
"sourceSeq": 2,
"eventType": "mcp_app.tool_result",
"emittedAt": "2026-08-11T10:30:00.010Z",
"itemId": "item_semantic_governance",
"summary": "mcp_app tool_result"
},
{
"position": 3,
"sourceEventId": "semantic_governance_03",
"sourceSeq": 3,
"eventType": "run.result.proposed",
"emittedAt": "2026-08-11T10:30:00.020Z",
"summary": "The immutable decision target is waiting on a bounded monitor continuation."
},
{
"position": 4,
"sourceEventId": "semantic_governance_04",
"sourceSeq": 4,
"eventType": "run.terminal",
"emittedAt": "2026-08-11T10:30:00.030Z",
"summary": "Run succeeded"
}
],
"sourceCursors": {
"runner:runner_semantic_governance": 4
},
"processedEventIds": [
"semantic_governance_01",
"semantic_governance_02",
"semantic_governance_03",
"semantic_governance_04"
],
"duplicateEventIds": [],
"outOfOrderEventIds": [],
"gaps": [],
"integrity": "complete"
}
@@ -0,0 +1,9 @@
{
"runId": "run_semantic_governance",
"integrity": "complete",
"timelineCount": 4,
"duplicateEventIds": [],
"gaps": [],
"turnTerminalState": "completed",
"runTerminalState": "succeeded"
}
@@ -0,0 +1,133 @@
{
"schema": "paperclip.prp.session-snapshot.v1",
"fixtureName": "Semantic tool unknown optional envelope fields",
"identity": {
"schema": "paperclip.prp.identity.v1",
"companyId": "company_semantic_future",
"issueId": "issue_semantic_future",
"runId": "run_semantic_future",
"environmentLeaseId": "lease_semantic_future",
"runnerInstanceId": "runner_semantic_future",
"normalizedSessionId": "session_semantic_future",
"driverSessionId": "driver_semantic_future"
},
"capabilities": {
"schema": "paperclip.prp.capabilities.v1",
"sessionReusePolicy": "new_per_run",
"driver": {
"kind": "provider-neutral",
"version": "1.0.0"
},
"steer": false,
"interrupt": true,
"resume": true,
"runtimeRequests": false,
"structuredResult": true,
"typedEvents": true,
"semanticTools": {
"schema": "paperclip.prp.semantic_tools.v1",
"schemaVersion": 1,
"operations": [
{
"operationId": "get_task_context",
"version": 1,
"availability": "available",
"redactionDisposition": "digest_only",
"requiredClaims": [],
"futureAvailabilityHint": "optional"
}
],
"futureNegotiationHint": {
"safe": true
}
},
"unsupported": []
},
"runPhase": "terminal",
"sessionState": "not_started",
"turnState": "not_started",
"activeTurnId": null,
"items": [],
"requests": [],
"proposedResult": {
"schema": "paperclip.run_result.v1",
"reportedWorkDisposition": "done",
"summary": "Unknown optional receipt fields were ignored by the v1 projection.",
"completionClaim": {
"contractRevision": "semantic-receipts-v1",
"objectiveSatisfied": true,
"criteria": [],
"remainingWork": []
},
"evidence": [
{
"receiptId": "receipt_semantic_future"
}
],
"verification": [
{
"commandOrCheck": "optional field projection stability",
"status": "passed"
}
],
"attentionRequests": [],
"artifacts": []
},
"terminal": {
"schema": "paperclip.prp.terminal.v1",
"turnTerminalState": "completed",
"runTerminalState": "succeeded",
"reportedWorkDisposition": "done",
"workAssessmentId": "assessment_semantic_future",
"statusDecisionId": "decision_semantic_future"
},
"timeline": [
{
"position": 1,
"sourceEventId": "semantic_future_01",
"sourceSeq": 1,
"eventType": "mcp_app.tool_input",
"emittedAt": "2026-08-11T10:50:00.000Z",
"itemId": "item_semantic_future",
"summary": "mcp_app tool_input"
},
{
"position": 2,
"sourceEventId": "semantic_future_02",
"sourceSeq": 2,
"eventType": "mcp_app.tool_result",
"emittedAt": "2026-08-11T10:50:00.010Z",
"itemId": "item_semantic_future",
"summary": "mcp_app tool_result"
},
{
"position": 3,
"sourceEventId": "semantic_future_03",
"sourceSeq": 3,
"eventType": "run.result.proposed",
"emittedAt": "2026-08-11T10:50:00.020Z",
"summary": "Unknown optional receipt fields were ignored by the v1 projection."
},
{
"position": 4,
"sourceEventId": "semantic_future_04",
"sourceSeq": 4,
"eventType": "run.terminal",
"emittedAt": "2026-08-11T10:50:00.030Z",
"summary": "Run succeeded"
}
],
"sourceCursors": {
"runner:runner_semantic_future": 4
},
"processedEventIds": [
"semantic_future_01",
"semantic_future_02",
"semantic_future_03",
"semantic_future_04"
],
"duplicateEventIds": [],
"outOfOrderEventIds": [],
"gaps": [],
"integrity": "complete"
}
@@ -0,0 +1,9 @@
{
"runId": "run_semantic_future",
"integrity": "complete",
"timelineCount": 4,
"duplicateEventIds": [],
"gaps": [],
"turnTerminalState": "completed",
"runTerminalState": "succeeded"
}
@@ -0,0 +1,186 @@
{
"schema": "paperclip.prp.fixture.v1",
"fixtureVersion": 1,
"protocolVersion": 1,
"name": "Semantic tool artifact happy path",
"description": "A provider-neutral register_deliverable call emits safe input and result receipts linked to the terminal artifact result.",
"identity": {
"schema": "paperclip.prp.identity.v1",
"companyId": "company_semantic_happy",
"issueId": "issue_semantic_happy",
"runId": "run_semantic_happy",
"environmentLeaseId": "lease_semantic_happy",
"runnerInstanceId": "runner_semantic_happy",
"normalizedSessionId": "session_semantic_happy",
"driverSessionId": "driver_semantic_happy"
},
"capabilities": {
"schema": "paperclip.prp.capabilities.v1",
"sessionReusePolicy": "new_per_run",
"driver": { "kind": "provider-neutral", "version": "1.0.0" },
"steer": true,
"interrupt": true,
"resume": true,
"runtimeRequests": true,
"structuredResult": true,
"typedEvents": true,
"semanticTools": {
"schema": "paperclip.prp.semantic_tools.v1",
"schemaVersion": 1,
"operations": [{
"operationId": "register_deliverable",
"version": 1,
"availability": "available",
"redactionDisposition": "digest_only",
"requiredClaims": []
}]
},
"unsupported": []
},
"commands": [],
"events": [
{
"schema": "paperclip.prp.event.v1",
"sourceEventId": "semantic_happy_01",
"sourceSeq": 1,
"sourceInstanceId": "runner_semantic_happy",
"sourceKind": "runner",
"runId": "run_semantic_happy",
"normalizedSessionId": "session_semantic_happy",
"turnId": "turn_semantic_happy",
"itemId": "item_semantic_happy",
"eventType": "mcp_app.tool_input",
"schemaVersion": 1,
"priority": 1,
"emittedAt": "2026-08-11T10:00:00.000Z",
"payload": {
"semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1",
"schemaVersion": 1,
"phase": "input",
"operationId": "register_deliverable",
"callId": "call_semantic_happy",
"correlation": {
"runId": "run_semantic_happy",
"normalizedSessionId": "session_semantic_happy",
"turnId": "turn_semantic_happy",
"itemId": "item_semantic_happy"
},
"idempotencyKey": "artifact_once",
"content": {
"digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"redactionDisposition": "digest_only",
"references": [{ "kind": "task", "id": "issue_semantic_happy" }]
}
}
}
},
{
"schema": "paperclip.prp.event.v1",
"sourceEventId": "semantic_happy_02",
"sourceSeq": 2,
"sourceInstanceId": "runner_semantic_happy",
"sourceKind": "runner",
"runId": "run_semantic_happy",
"normalizedSessionId": "session_semantic_happy",
"turnId": "turn_semantic_happy",
"itemId": "item_semantic_happy",
"eventType": "mcp_app.tool_result",
"schemaVersion": 1,
"priority": 1,
"emittedAt": "2026-08-11T10:00:00.010Z",
"payload": {
"semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1",
"schemaVersion": 1,
"phase": "result",
"operationId": "register_deliverable",
"callId": "call_semantic_happy",
"correlation": {
"runId": "run_semantic_happy",
"normalizedSessionId": "session_semantic_happy",
"turnId": "turn_semantic_happy",
"itemId": "item_semantic_happy"
},
"idempotencyKey": "artifact_once",
"content": {
"digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"redactionDisposition": "allowlisted_references",
"references": [
{ "kind": "artifact", "id": "artifact_semantic_happy" },
{ "kind": "work_product", "id": "work_product_semantic_happy" }
]
},
"outcome": "succeeded",
"code": "ok",
"retryable": false,
"authorizationBoundary": "active_task",
"operationReceiptId": "receipt_semantic_happy",
"auditReceiptId": "audit_semantic_happy",
"currentRevision": 2,
"artifactRefs": [
{ "kind": "artifact", "id": "artifact_semantic_happy" },
{ "kind": "work_product", "id": "work_product_semantic_happy" }
]
}
}
},
{
"schema": "paperclip.prp.event.v1",
"sourceEventId": "semantic_happy_03",
"sourceSeq": 3,
"sourceInstanceId": "runner_semantic_happy",
"sourceKind": "runner",
"runId": "run_semantic_happy",
"normalizedSessionId": "session_semantic_happy",
"turnId": "turn_semantic_happy",
"eventType": "run.result.proposed",
"schemaVersion": 1,
"priority": 0,
"emittedAt": "2026-08-11T10:00:00.020Z",
"payload": {
"schema": "paperclip.run_result.v1",
"reportedWorkDisposition": "done",
"summary": "The artifact was registered through the semantic tool receipt.",
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] },
"evidence": [{ "receiptId": "receipt_semantic_happy" }],
"verification": [{ "commandOrCheck": "semantic artifact receipt", "status": "passed", "artifactRef": "artifact_semantic_happy" }],
"attentionRequests": [],
"artifacts": [{ "kind": "artifact", "ref": "artifact_semantic_happy", "title": "Semantic receipt report" }]
}
},
{
"schema": "paperclip.prp.event.v1",
"sourceEventId": "semantic_happy_04",
"sourceSeq": 4,
"sourceInstanceId": "runner_semantic_happy",
"sourceKind": "runner",
"runId": "run_semantic_happy",
"normalizedSessionId": "session_semantic_happy",
"turnId": "turn_semantic_happy",
"eventType": "run.terminal",
"schemaVersion": 1,
"priority": 0,
"emittedAt": "2026-08-11T10:00:00.030Z",
"payload": {
"schema": "paperclip.prp.terminal.v1",
"turnTerminalState": "completed",
"runTerminalState": "succeeded",
"reportedWorkDisposition": "done",
"workAssessmentId": "assessment_semantic_happy",
"statusDecisionId": "decision_semantic_happy"
}
}
],
"requests": [],
"result": {
"schema": "paperclip.run_result.v1",
"reportedWorkDisposition": "done",
"summary": "The artifact was registered through the semantic tool receipt.",
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] },
"evidence": [{ "receiptId": "receipt_semantic_happy" }],
"verification": [{ "commandOrCheck": "semantic artifact receipt", "status": "passed", "artifactRef": "artifact_semantic_happy" }],
"attentionRequests": [],
"artifacts": [{ "kind": "artifact", "ref": "artifact_semantic_happy", "title": "Semantic receipt report" }]
}
}
@@ -0,0 +1,120 @@
{
"schema": "paperclip.prp.fixture.v1",
"fixtureVersion": 1,
"protocolVersion": 1,
"name": "Semantic tool conflict and duplicate retry",
"description": "A stale document write conflicts, a fresh retry succeeds, and an exact idempotent retry returns the original operation receipt.",
"identity": {
"schema": "paperclip.prp.identity.v1", "companyId": "company_semantic_retry", "issueId": "issue_semantic_retry",
"runId": "run_semantic_retry", "environmentLeaseId": "lease_semantic_retry", "runnerInstanceId": "runner_semantic_retry",
"normalizedSessionId": "session_semantic_retry", "driverSessionId": "driver_semantic_retry"
},
"capabilities": {
"schema": "paperclip.prp.capabilities.v1", "sessionReusePolicy": "new_per_run",
"driver": { "kind": "provider-neutral", "version": "1.0.0" },
"steer": false, "interrupt": true, "resume": true, "runtimeRequests": true, "structuredResult": true, "typedEvents": true,
"semanticTools": { "schema": "paperclip.prp.semantic_tools.v1", "schemaVersion": 1, "operations": [{
"operationId": "write_document", "version": 1, "availability": "available", "redactionDisposition": "digest_only", "requiredClaims": []
}] },
"unsupported": []
},
"commands": [],
"events": [
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_01", "sourceSeq": 1, "sourceInstanceId": "runner_semantic_retry",
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
"itemId": "item_semantic_conflict", "eventType": "mcp_app.tool_input", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:20:00.000Z",
"payload": { "semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "input", "operationId": "write_document", "callId": "call_semantic_conflict",
"correlation": { "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry", "itemId": "item_semantic_conflict" },
"idempotencyKey": "write_plan_stale",
"content": { "digest": "sha256:1111111111111111111111111111111111111111111111111111111111111111", "redactionDisposition": "digest_only", "references": [{ "kind": "document_revision", "id": "revision_plan_stale" }] }
} }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_02", "sourceSeq": 2, "sourceInstanceId": "runner_semantic_retry",
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
"itemId": "item_semantic_conflict", "eventType": "mcp_app.tool_result", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:20:00.010Z",
"payload": { "semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "result", "operationId": "write_document", "callId": "call_semantic_conflict",
"correlation": { "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry", "itemId": "item_semantic_conflict" },
"idempotencyKey": "write_plan_stale",
"content": { "digest": "sha256:2222222222222222222222222222222222222222222222222222222222222222", "redactionDisposition": "allowlisted_references", "references": [{ "kind": "document_revision", "id": "revision_plan_2" }] },
"outcome": "conflict", "code": "document_revision_conflict", "retryable": true, "authorizationBoundary": "revision",
"operationReceiptId": "receipt_semantic_conflict", "auditReceiptId": "audit_semantic_conflict", "currentRevision": "revision_plan_2"
} }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_03", "sourceSeq": 3, "sourceInstanceId": "runner_semantic_retry",
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
"itemId": "item_semantic_fresh", "eventType": "mcp_app.tool_input", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:20:00.020Z",
"payload": { "semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "input", "operationId": "write_document", "callId": "call_semantic_fresh",
"correlation": { "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry", "itemId": "item_semantic_fresh" },
"idempotencyKey": "write_plan_retry",
"content": { "digest": "sha256:3333333333333333333333333333333333333333333333333333333333333333", "redactionDisposition": "digest_only", "references": [{ "kind": "document_revision", "id": "revision_plan_2" }] }
} }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_04", "sourceSeq": 4, "sourceInstanceId": "runner_semantic_retry",
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
"itemId": "item_semantic_fresh", "eventType": "mcp_app.tool_result", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:20:00.030Z",
"payload": { "semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "result", "operationId": "write_document", "callId": "call_semantic_fresh",
"correlation": { "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry", "itemId": "item_semantic_fresh" },
"idempotencyKey": "write_plan_retry",
"content": { "digest": "sha256:4444444444444444444444444444444444444444444444444444444444444444", "redactionDisposition": "allowlisted_references", "references": [{ "kind": "document_revision", "id": "revision_plan_3" }] },
"outcome": "succeeded", "code": "ok", "retryable": false, "authorizationBoundary": "revision",
"operationReceiptId": "receipt_semantic_document_commit", "auditReceiptId": "audit_semantic_document_commit", "currentRevision": "revision_plan_3"
} }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_05", "sourceSeq": 5, "sourceInstanceId": "runner_semantic_retry",
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
"itemId": "item_semantic_duplicate", "eventType": "mcp_app.tool_input", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:20:00.040Z",
"payload": { "semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "input", "operationId": "write_document", "callId": "call_semantic_duplicate",
"correlation": { "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry", "itemId": "item_semantic_duplicate" },
"idempotencyKey": "write_plan_retry",
"content": { "digest": "sha256:3333333333333333333333333333333333333333333333333333333333333333", "redactionDisposition": "digest_only", "references": [{ "kind": "document_revision", "id": "revision_plan_2" }] }
} }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_06", "sourceSeq": 6, "sourceInstanceId": "runner_semantic_retry",
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
"itemId": "item_semantic_duplicate", "eventType": "mcp_app.tool_result", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:20:00.050Z",
"payload": { "semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "result", "operationId": "write_document", "callId": "call_semantic_duplicate",
"correlation": { "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry", "itemId": "item_semantic_duplicate" },
"idempotencyKey": "write_plan_retry",
"content": { "digest": "sha256:4444444444444444444444444444444444444444444444444444444444444444", "redactionDisposition": "allowlisted_references", "references": [{ "kind": "document_revision", "id": "revision_plan_3" }] },
"outcome": "duplicate", "code": "duplicate", "retryable": false, "authorizationBoundary": "revision",
"operationReceiptId": "receipt_semantic_document_commit", "duplicateOfReceiptId": "receipt_semantic_document_commit", "currentRevision": "revision_plan_3"
} }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_07", "sourceSeq": 7, "sourceInstanceId": "runner_semantic_retry",
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
"eventType": "run.result.proposed", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:20:00.060Z",
"payload": {
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "done", "summary": "The revision-safe retry committed once.",
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] },
"evidence": [{ "receiptId": "receipt_semantic_conflict" }, { "receiptId": "receipt_semantic_document_commit" }],
"verification": [{ "commandOrCheck": "conflict then exact duplicate retry", "status": "passed" }], "attentionRequests": [], "artifacts": []
}
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_08", "sourceSeq": 8, "sourceInstanceId": "runner_semantic_retry",
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
"eventType": "run.terminal", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:20:00.070Z",
"payload": { "schema": "paperclip.prp.terminal.v1", "turnTerminalState": "completed", "runTerminalState": "succeeded", "reportedWorkDisposition": "done", "workAssessmentId": "assessment_semantic_retry", "statusDecisionId": "decision_semantic_retry" }
}
],
"requests": [],
"result": {
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "done", "summary": "The revision-safe retry committed once.",
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] },
"evidence": [{ "receiptId": "receipt_semantic_conflict" }, { "receiptId": "receipt_semantic_document_commit" }],
"verification": [{ "commandOrCheck": "conflict then exact duplicate retry", "status": "passed" }], "attentionRequests": [], "artifacts": []
}
}
@@ -0,0 +1,102 @@
{
"schema": "paperclip.prp.fixture.v1",
"fixtureVersion": 1,
"protocolVersion": 1,
"name": "Semantic tool denial and redaction",
"description": "A governed action is denied with digest-only content, a typed authorization boundary, and no fallback semantic call.",
"identity": {
"schema": "paperclip.prp.identity.v1",
"companyId": "company_semantic_denied",
"issueId": "issue_semantic_denied",
"runId": "run_semantic_denied",
"environmentLeaseId": "lease_semantic_denied",
"runnerInstanceId": "runner_semantic_denied",
"normalizedSessionId": "session_semantic_denied",
"driverSessionId": "driver_semantic_denied"
},
"capabilities": {
"schema": "paperclip.prp.capabilities.v1",
"sessionReusePolicy": "new_per_run",
"driver": { "kind": "provider-neutral", "version": "1.0.0" },
"steer": false,
"interrupt": true,
"resume": true,
"runtimeRequests": true,
"structuredResult": true,
"typedEvents": true,
"semanticTools": {
"schema": "paperclip.prp.semantic_tools.v1",
"schemaVersion": 1,
"operations": [{
"operationId": "decide_approval",
"version": 1,
"availability": "denied",
"redactionDisposition": "digest_only",
"requiredClaims": ["governance:approvals:decide"],
"reasonCode": "required_claim_missing"
}]
},
"unsupported": []
},
"commands": [],
"events": [
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_denied_01", "sourceSeq": 1,
"sourceInstanceId": "runner_semantic_denied", "sourceKind": "runner", "runId": "run_semantic_denied",
"normalizedSessionId": "session_semantic_denied", "turnId": "turn_semantic_denied", "itemId": "item_semantic_denied",
"eventType": "mcp_app.tool_input", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:10:00.000Z",
"payload": { "semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "input",
"operationId": "decide_approval", "callId": "call_semantic_denied",
"correlation": { "runId": "run_semantic_denied", "normalizedSessionId": "session_semantic_denied", "turnId": "turn_semantic_denied", "itemId": "item_semantic_denied" },
"idempotencyKey": "denied_once",
"content": { "digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "redactionDisposition": "redacted", "references": [] }
} }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_denied_02", "sourceSeq": 2,
"sourceInstanceId": "runner_semantic_denied", "sourceKind": "runner", "runId": "run_semantic_denied",
"normalizedSessionId": "session_semantic_denied", "turnId": "turn_semantic_denied", "itemId": "item_semantic_denied",
"eventType": "mcp_app.tool_result", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:10:00.010Z",
"payload": { "semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "result",
"operationId": "decide_approval", "callId": "call_semantic_denied",
"correlation": { "runId": "run_semantic_denied", "normalizedSessionId": "session_semantic_denied", "turnId": "turn_semantic_denied", "itemId": "item_semantic_denied" },
"idempotencyKey": "denied_once",
"content": { "digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", "redactionDisposition": "redacted", "references": [] },
"outcome": "denied", "code": "required_claim_missing", "retryable": false,
"authorizationBoundary": "grant", "operationReceiptId": "receipt_semantic_denied", "auditReceiptId": "audit_semantic_denied", "currentRevision": 4
} }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_denied_03", "sourceSeq": 3,
"sourceInstanceId": "runner_semantic_denied", "sourceKind": "runner", "runId": "run_semantic_denied",
"normalizedSessionId": "session_semantic_denied", "turnId": "turn_semantic_denied",
"eventType": "run.result.proposed", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:10:00.020Z",
"payload": {
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "needs_review",
"summary": "The governed action was denied without fallback.",
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": false, "criteria": [], "remainingWork": [{ "description": "An authorized approver must decide.", "blocksCompletion": true }] },
"evidence": [{ "receiptId": "receipt_semantic_denied" }],
"verification": [{ "commandOrCheck": "no fallback semantic call", "status": "passed" }],
"attentionRequests": [], "artifacts": []
}
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_denied_04", "sourceSeq": 4,
"sourceInstanceId": "runner_semantic_denied", "sourceKind": "runner", "runId": "run_semantic_denied",
"normalizedSessionId": "session_semantic_denied", "turnId": "turn_semantic_denied",
"eventType": "run.terminal", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:10:00.030Z",
"payload": { "schema": "paperclip.prp.terminal.v1", "turnTerminalState": "completed", "runTerminalState": "succeeded", "reportedWorkDisposition": "needs_review", "workAssessmentId": "assessment_semantic_denied", "statusDecisionId": "decision_semantic_denied" }
}
],
"requests": [],
"result": {
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "needs_review",
"summary": "The governed action was denied without fallback.",
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": false, "criteria": [], "remainingWork": [{ "description": "An authorized approver must decide.", "blocksCompletion": true }] },
"evidence": [{ "receiptId": "receipt_semantic_denied" }],
"verification": [{ "commandOrCheck": "no fallback semantic call", "status": "passed" }],
"attentionRequests": [], "artifacts": []
}
}
@@ -0,0 +1,90 @@
{
"schema": "paperclip.prp.fixture.v1",
"fixtureVersion": 1,
"protocolVersion": 1,
"name": "Semantic governance target and continuation chain",
"description": "A semantic result binds immutable interaction and approval targets while carrying only safe wake and monitor causal references.",
"identity": {
"schema": "paperclip.prp.identity.v1", "companyId": "company_semantic_governance", "issueId": "issue_semantic_governance",
"runId": "run_semantic_governance", "environmentLeaseId": "lease_semantic_governance", "runnerInstanceId": "runner_semantic_governance",
"normalizedSessionId": "session_semantic_governance", "driverSessionId": "driver_semantic_governance"
},
"capabilities": {
"schema": "paperclip.prp.capabilities.v1", "sessionReusePolicy": "reuse_per_issue",
"driver": { "kind": "provider-neutral", "version": "1.0.0" },
"steer": true, "interrupt": true, "resume": true, "runtimeRequests": true, "structuredResult": true, "typedEvents": true,
"semanticTools": { "schema": "paperclip.prp.semantic_tools.v1", "schemaVersion": 1, "operations": [{
"operationId": "request_human_input", "version": 1, "availability": "available", "redactionDisposition": "digest_only", "requiredClaims": []
}] },
"unsupported": []
},
"commands": [],
"events": [
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_governance_01", "sourceSeq": 1, "sourceInstanceId": "runner_semantic_governance",
"sourceKind": "runner", "runId": "run_semantic_governance", "normalizedSessionId": "session_semantic_governance",
"turnId": "turn_semantic_governance", "itemId": "item_semantic_governance", "eventType": "mcp_app.tool_input",
"schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:30:00.000Z",
"payload": { "semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "input", "operationId": "request_human_input", "callId": "call_semantic_governance",
"correlation": { "runId": "run_semantic_governance", "normalizedSessionId": "session_semantic_governance", "turnId": "turn_semantic_governance", "itemId": "item_semantic_governance", "requestId": "request_semantic_governance" },
"idempotencyKey": "interaction_once",
"content": { "digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555", "redactionDisposition": "digest_only", "references": [{ "kind": "document_revision", "id": "revision_plan_7" }] }
} }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_governance_02", "sourceSeq": 2, "sourceInstanceId": "runner_semantic_governance",
"sourceKind": "runner", "runId": "run_semantic_governance", "normalizedSessionId": "session_semantic_governance",
"turnId": "turn_semantic_governance", "itemId": "item_semantic_governance", "eventType": "mcp_app.tool_result",
"schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:30:00.010Z",
"payload": { "semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "result", "operationId": "request_human_input", "callId": "call_semantic_governance",
"correlation": { "runId": "run_semantic_governance", "normalizedSessionId": "session_semantic_governance", "turnId": "turn_semantic_governance", "itemId": "item_semantic_governance", "requestId": "request_semantic_governance" },
"idempotencyKey": "interaction_once",
"content": { "digest": "sha256:6666666666666666666666666666666666666666666666666666666666666666", "redactionDisposition": "allowlisted_references", "references": [{ "kind": "interaction", "id": "interaction_semantic_governance" }, { "kind": "wake", "id": "wake_semantic_governance" }, { "kind": "monitor", "id": "monitor_semantic_governance" }] },
"outcome": "succeeded", "code": "ok", "retryable": false, "authorizationBoundary": "governed_action",
"operationReceiptId": "receipt_semantic_governance", "auditReceiptId": "audit_semantic_governance", "currentRevision": 8,
"targets": [
{ "kind": "interaction", "id": "interaction_semantic_governance", "immutable": true, "revisionId": "revision_plan_7" },
{ "kind": "approval", "id": "approval_semantic_governance", "immutable": true, "decisionId": "decision_approval_semantic_governance" }
],
"causalRefs": [
{ "kind": "document_revision", "id": "revision_plan_7" },
{ "kind": "interaction", "id": "interaction_semantic_governance" },
{ "kind": "approval", "id": "approval_semantic_governance" },
{ "kind": "decision", "id": "decision_approval_semantic_governance" },
{ "kind": "wake", "id": "wake_semantic_governance" },
{ "kind": "monitor", "id": "monitor_semantic_governance" }
]
} }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_governance_03", "sourceSeq": 3, "sourceInstanceId": "runner_semantic_governance",
"sourceKind": "runner", "runId": "run_semantic_governance", "normalizedSessionId": "session_semantic_governance", "turnId": "turn_semantic_governance",
"eventType": "run.result.proposed", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:30:00.020Z",
"payload": {
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "yielded", "summary": "The immutable decision target is waiting on a bounded monitor continuation.",
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": false, "criteria": [], "remainingWork": [{ "description": "Wait for the interaction response.", "blocksCompletion": true }] },
"evidence": [{ "receiptId": "receipt_semantic_governance" }],
"verification": [{ "commandOrCheck": "immutable target and causal chain", "status": "passed" }],
"attentionRequests": [], "artifacts": [],
"continuation": { "kind": "monitor", "summary": "Recheck after the typed response wake.", "idempotencyKey": "monitor_semantic_governance" }
}
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_governance_04", "sourceSeq": 4, "sourceInstanceId": "runner_semantic_governance",
"sourceKind": "runner", "runId": "run_semantic_governance", "normalizedSessionId": "session_semantic_governance", "turnId": "turn_semantic_governance",
"eventType": "run.terminal", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:30:00.030Z",
"payload": { "schema": "paperclip.prp.terminal.v1", "turnTerminalState": "completed", "runTerminalState": "succeeded", "reportedWorkDisposition": "yielded", "workAssessmentId": "assessment_semantic_governance", "statusDecisionId": "decision_semantic_governance" }
}
],
"requests": [],
"result": {
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "yielded", "summary": "The immutable decision target is waiting on a bounded monitor continuation.",
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": false, "criteria": [], "remainingWork": [{ "description": "Wait for the interaction response.", "blocksCompletion": true }] },
"evidence": [{ "receiptId": "receipt_semantic_governance" }],
"verification": [{ "commandOrCheck": "immutable target and causal chain", "status": "passed" }],
"attentionRequests": [], "artifacts": [],
"continuation": { "kind": "monitor", "summary": "Recheck after the typed response wake.", "idempotencyKey": "monitor_semantic_governance" }
}
}
@@ -0,0 +1,72 @@
{
"schema": "paperclip.prp.fixture.v1",
"fixtureVersion": 1,
"protocolVersion": 1,
"name": "Semantic tool unknown optional envelope fields",
"description": "Unknown additive fields inside typed semantic envelopes validate without changing the existing v1 reducer projection.",
"identity": {
"schema": "paperclip.prp.identity.v1", "companyId": "company_semantic_future", "issueId": "issue_semantic_future", "runId": "run_semantic_future",
"environmentLeaseId": "lease_semantic_future", "runnerInstanceId": "runner_semantic_future", "normalizedSessionId": "session_semantic_future", "driverSessionId": "driver_semantic_future"
},
"capabilities": {
"schema": "paperclip.prp.capabilities.v1", "sessionReusePolicy": "new_per_run", "driver": { "kind": "provider-neutral", "version": "1.0.0" },
"steer": false, "interrupt": true, "resume": true, "runtimeRequests": false, "structuredResult": true, "typedEvents": true,
"semanticTools": { "schema": "paperclip.prp.semantic_tools.v1", "schemaVersion": 1, "operations": [{
"operationId": "get_task_context", "version": 1, "availability": "available", "redactionDisposition": "digest_only", "requiredClaims": [],
"futureAvailabilityHint": "optional"
}], "futureNegotiationHint": { "safe": true } },
"unsupported": []
},
"commands": [],
"events": [
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_future_01", "sourceSeq": 1, "sourceInstanceId": "runner_semantic_future",
"sourceKind": "runner", "runId": "run_semantic_future", "normalizedSessionId": "session_semantic_future", "turnId": "turn_semantic_future", "itemId": "item_semantic_future",
"eventType": "mcp_app.tool_input", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:50:00.000Z",
"payload": { "semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "input", "operationId": "get_task_context", "callId": "call_semantic_future",
"correlation": { "runId": "run_semantic_future", "normalizedSessionId": "session_semantic_future", "turnId": "turn_semantic_future", "itemId": "item_semantic_future", "futureCorrelationHint": 1 },
"idempotencyKey": null,
"content": { "digest": "sha256:7777777777777777777777777777777777777777777777777777777777777777", "redactionDisposition": "digest_only", "references": [], "futureContentHint": "optional" },
"futureEnvelopeHint": { "version": "1.1-preview" }
} }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_future_02", "sourceSeq": 2, "sourceInstanceId": "runner_semantic_future",
"sourceKind": "runner", "runId": "run_semantic_future", "normalizedSessionId": "session_semantic_future", "turnId": "turn_semantic_future", "itemId": "item_semantic_future",
"eventType": "mcp_app.tool_result", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:50:00.010Z",
"payload": { "semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "result", "operationId": "get_task_context", "callId": "call_semantic_future",
"correlation": { "runId": "run_semantic_future", "normalizedSessionId": "session_semantic_future", "turnId": "turn_semantic_future", "itemId": "item_semantic_future", "futureCorrelationHint": 1 },
"idempotencyKey": null,
"content": { "digest": "sha256:8888888888888888888888888888888888888888888888888888888888888888", "redactionDisposition": "digest_only", "references": [{ "kind": "task", "id": "issue_semantic_future" }], "futureContentHint": "optional" },
"outcome": "succeeded", "code": "ok", "retryable": false, "authorizationBoundary": "active_task", "operationReceiptId": "receipt_semantic_future", "currentRevision": 1,
"futureEnvelopeHint": { "version": "1.1-preview" }
} }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_future_03", "sourceSeq": 3, "sourceInstanceId": "runner_semantic_future",
"sourceKind": "runner", "runId": "run_semantic_future", "normalizedSessionId": "session_semantic_future", "turnId": "turn_semantic_future",
"eventType": "run.result.proposed", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:50:00.020Z",
"payload": {
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "done", "summary": "Unknown optional receipt fields were ignored by the v1 projection.",
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] },
"evidence": [{ "receiptId": "receipt_semantic_future" }], "verification": [{ "commandOrCheck": "optional field projection stability", "status": "passed" }],
"attentionRequests": [], "artifacts": []
}
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_future_04", "sourceSeq": 4, "sourceInstanceId": "runner_semantic_future",
"sourceKind": "runner", "runId": "run_semantic_future", "normalizedSessionId": "session_semantic_future", "turnId": "turn_semantic_future",
"eventType": "run.terminal", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:50:00.030Z",
"payload": { "schema": "paperclip.prp.terminal.v1", "turnTerminalState": "completed", "runTerminalState": "succeeded", "reportedWorkDisposition": "done", "workAssessmentId": "assessment_semantic_future", "statusDecisionId": "decision_semantic_future" }
}
],
"requests": [],
"result": {
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "done", "summary": "Unknown optional receipt fields were ignored by the v1 projection.",
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] },
"evidence": [{ "receiptId": "receipt_semantic_future" }], "verification": [{ "commandOrCheck": "optional field projection stability", "status": "passed" }],
"attentionRequests": [], "artifacts": []
}
}
@@ -0,0 +1,55 @@
{
"schema": "paperclip.prp.fixture.v1",
"fixtureVersion": 1,
"protocolVersion": 1,
"name": "Unsupported semantic tool envelope version",
"description": "A required semantic_tool v2 envelope must fail closed in a PRP v1 consumer.",
"identity": {
"schema": "paperclip.prp.identity.v1", "companyId": "company_semantic_v2", "issueId": "issue_semantic_v2", "runId": "run_semantic_v2",
"environmentLeaseId": "lease_semantic_v2", "runnerInstanceId": "runner_semantic_v2", "normalizedSessionId": "session_semantic_v2", "driverSessionId": "driver_semantic_v2"
},
"capabilities": {
"schema": "paperclip.prp.capabilities.v1", "sessionReusePolicy": "new_per_run", "driver": { "kind": "provider-neutral", "version": "1.0.0" },
"steer": false, "interrupt": true, "resume": true, "runtimeRequests": false, "structuredResult": true, "typedEvents": true, "unsupported": []
},
"commands": [],
"events": [
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_v2_01", "sourceSeq": 1, "sourceInstanceId": "runner_semantic_v2",
"sourceKind": "runner", "runId": "run_semantic_v2", "normalizedSessionId": "session_semantic_v2", "turnId": "turn_semantic_v2", "itemId": "item_semantic_v2",
"eventType": "mcp_app.tool_input", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T11:00:00.000Z",
"payload": { "semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v2", "schemaVersion": 2, "phase": "input", "operationId": "get_task_context", "callId": "call_semantic_v2",
"correlation": { "runId": "run_semantic_v2", "normalizedSessionId": "session_semantic_v2", "turnId": "turn_semantic_v2", "itemId": "item_semantic_v2" },
"idempotencyKey": null,
"content": { "digest": "sha256:9999999999999999999999999999999999999999999999999999999999999999", "redactionDisposition": "digest_only", "references": [] }
} }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_v2_02", "sourceSeq": 2, "sourceInstanceId": "runner_semantic_v2",
"sourceKind": "runner", "runId": "run_semantic_v2", "normalizedSessionId": "session_semantic_v2", "turnId": "turn_semantic_v2", "itemId": "item_semantic_v2",
"eventType": "mcp_app.tool_result", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T11:00:00.010Z",
"payload": { "semantic_tool": {
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "result", "operationId": "get_task_context", "callId": "call_semantic_v2",
"correlation": { "runId": "run_semantic_v2", "normalizedSessionId": "session_semantic_v2", "turnId": "turn_semantic_v2", "itemId": "item_semantic_v2" },
"idempotencyKey": null,
"content": { "digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000", "redactionDisposition": "digest_only", "references": [] },
"outcome": "succeeded", "code": "ok", "retryable": false, "authorizationBoundary": "active_task", "operationReceiptId": "receipt_semantic_v2"
} }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_v2_03", "sourceSeq": 3, "sourceInstanceId": "runner_semantic_v2",
"sourceKind": "runner", "runId": "run_semantic_v2", "normalizedSessionId": "session_semantic_v2", "turnId": "turn_semantic_v2",
"eventType": "run.result.proposed", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T11:00:00.020Z",
"payload": { "schema": "paperclip.run_result.v1", "reportedWorkDisposition": "done", "summary": "This result must not replay.", "completionClaim": { "contractRevision": "semantic-receipts-v2", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] }, "evidence": [], "verification": [], "attentionRequests": [], "artifacts": [] }
},
{
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_v2_04", "sourceSeq": 4, "sourceInstanceId": "runner_semantic_v2",
"sourceKind": "runner", "runId": "run_semantic_v2", "normalizedSessionId": "session_semantic_v2", "turnId": "turn_semantic_v2",
"eventType": "run.terminal", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T11:00:00.030Z",
"payload": { "schema": "paperclip.prp.terminal.v1", "turnTerminalState": "completed", "runTerminalState": "succeeded", "reportedWorkDisposition": "done" }
}
],
"requests": [],
"result": { "schema": "paperclip.run_result.v1", "reportedWorkDisposition": "done", "summary": "This result must not replay.", "completionClaim": { "contractRevision": "semantic-receipts-v2", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] }, "evidence": [], "verification": [], "attentionRequests": [], "artifacts": [] }
}
@@ -34,6 +34,45 @@
"runtimeRequests": { "type": "boolean" },
"structuredResult": { "type": "boolean" },
"typedEvents": { "type": "boolean" },
"semanticTools": {
"type": "object",
"required": ["schema", "schemaVersion", "operations"],
"properties": {
"schema": { "const": "paperclip.prp.semantic_tools.v1" },
"schemaVersion": { "const": 1 },
"operations": {
"type": "array",
"items": {
"type": "object",
"required": ["operationId", "version", "availability", "redactionDisposition"],
"properties": {
"operationId": {
"type": "string",
"minLength": 1,
"maxLength": 160,
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
},
"version": { "const": 1 },
"availability": { "enum": ["available", "denied", "unsupported"] },
"redactionDisposition": { "const": "digest_only" },
"requiredClaims": {
"type": "array",
"items": { "type": "string", "minLength": 1, "maxLength": 160 },
"uniqueItems": true
},
"reasonCode": {
"type": "string",
"minLength": 1,
"maxLength": 160,
"pattern": "^[a-z][a-z0-9_.:-]*$"
}
},
"additionalProperties": true
}
}
},
"additionalProperties": true
},
"unsupported": {
"type": "array",
"items": { "type": "string", "minLength": 1 },
@@ -51,6 +51,44 @@
"debug": { "type": "object", "additionalProperties": true }
},
"allOf": [
{
"if": { "properties": { "eventType": { "const": "mcp_app.tool_input" } } },
"then": {
"properties": {
"payload": {
"type": "object",
"properties": {
"semantic_tool": {
"allOf": [
{ "$ref": "https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json" },
{ "type": "object", "properties": { "phase": { "const": "input" } } }
]
}
},
"additionalProperties": true
}
}
}
},
{
"if": { "properties": { "eventType": { "const": "mcp_app.tool_result" } } },
"then": {
"properties": {
"payload": {
"type": "object",
"properties": {
"semantic_tool": {
"allOf": [
{ "$ref": "https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json" },
{ "type": "object", "properties": { "phase": { "const": "result" } } }
]
}
},
"additionalProperties": true
}
}
}
},
{
"if": { "properties": { "eventType": { "const": "run.terminal" } } },
"then": {
@@ -0,0 +1,164 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json",
"title": "PRP provider-neutral semantic tool envelope",
"type": "object",
"required": [
"schema",
"schemaVersion",
"phase",
"operationId",
"callId",
"correlation",
"idempotencyKey",
"content"
],
"properties": {
"schema": { "const": "paperclip.prp.semantic_tool.v1" },
"schemaVersion": { "const": 1 },
"phase": { "enum": ["input", "result"] },
"operationId": { "$ref": "#/$defs/stableId" },
"callId": { "$ref": "#/$defs/stableId" },
"correlation": {
"type": "object",
"required": ["runId", "normalizedSessionId", "turnId", "itemId"],
"properties": {
"runId": { "$ref": "#/$defs/stableId" },
"normalizedSessionId": { "$ref": "#/$defs/stableId" },
"turnId": { "$ref": "#/$defs/stableId" },
"itemId": { "$ref": "#/$defs/stableId" },
"requestId": { "$ref": "#/$defs/stableId" }
},
"additionalProperties": true
},
"idempotencyKey": {
"type": ["string", "null"],
"minLength": 1,
"maxLength": 240
},
"content": { "$ref": "#/$defs/safeContent" },
"outcome": {
"enum": ["succeeded", "denied", "conflict", "duplicate", "unavailable", "failed"]
},
"code": { "$ref": "#/$defs/stableCode" },
"retryable": { "type": "boolean" },
"authorizationBoundary": {
"enum": ["company", "actor", "active_task", "grant", "governed_action", "lock", "revision"]
},
"operationReceiptId": { "$ref": "#/$defs/stableId" },
"auditReceiptId": { "$ref": "#/$defs/stableId" },
"currentRevision": {
"oneOf": [
{ "type": "integer", "minimum": 0 },
{ "$ref": "#/$defs/stableId" }
]
},
"duplicateOfReceiptId": { "$ref": "#/$defs/stableId" },
"artifactRefs": {
"type": "array",
"items": {
"allOf": [
{ "$ref": "#/$defs/safeReference" },
{
"type": "object",
"properties": { "kind": { "enum": ["artifact", "work_product"] } }
}
]
},
"uniqueItems": true
},
"targets": {
"type": "array",
"items": { "$ref": "#/$defs/immutableTarget" },
"uniqueItems": true
},
"causalRefs": {
"type": "array",
"items": { "$ref": "#/$defs/safeReference" },
"uniqueItems": true
}
},
"allOf": [
{
"if": { "properties": { "phase": { "const": "result" } } },
"then": {
"required": [
"outcome",
"code",
"retryable",
"authorizationBoundary",
"operationReceiptId"
]
}
}
],
"additionalProperties": true,
"$defs": {
"stableId": {
"type": "string",
"minLength": 1,
"maxLength": 240,
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
},
"stableCode": {
"type": "string",
"minLength": 1,
"maxLength": 160,
"pattern": "^[a-z][a-z0-9_.:-]*$"
},
"safeContent": {
"type": "object",
"required": ["digest", "redactionDisposition", "references"],
"properties": {
"digest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"redactionDisposition": {
"enum": ["digest_only", "allowlisted_references", "redacted"]
},
"references": {
"type": "array",
"items": { "$ref": "#/$defs/safeReference" },
"uniqueItems": true
}
},
"additionalProperties": true
},
"safeReference": {
"type": "object",
"required": ["kind", "id"],
"properties": {
"kind": {
"enum": [
"task",
"document_revision",
"interaction",
"approval",
"decision",
"artifact",
"work_product",
"wake",
"monitor",
"audit",
"operation"
]
},
"id": { "$ref": "#/$defs/stableId" }
},
"additionalProperties": true
},
"immutableTarget": {
"type": "object",
"required": ["kind", "id", "immutable"],
"properties": {
"kind": { "enum": ["document_revision", "interaction", "approval", "decision"] },
"id": { "$ref": "#/$defs/stableId" },
"immutable": { "const": true },
"revisionId": { "$ref": "#/$defs/stableId" },
"decisionId": { "$ref": "#/$defs/stableId" }
},
"additionalProperties": true
}
}
}
@@ -0,0 +1,65 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://paperclip.dev/schemas/prp/v1/stop-reason.schema.json",
"title": "PRP terminal stop reason receipt",
"type": "object",
"required": [
"schema",
"schemaVersion",
"receiptId",
"kind",
"code",
"retryable",
"decisionId",
"limitClass",
"aggregate"
],
"properties": {
"schema": { "const": "paperclip.prp.stop_reason.v1" },
"schemaVersion": { "const": 1 },
"receiptId": { "$ref": "#/$defs/stableId" },
"kind": { "enum": ["budget", "cost"] },
"code": {
"type": "string",
"minLength": 1,
"maxLength": 160,
"pattern": "^[a-z][a-z0-9_.:-]*$"
},
"retryable": { "type": "boolean" },
"decisionId": { "$ref": "#/$defs/stableId" },
"limitClass": {
"enum": [
"company_monthly",
"actor_monthly",
"project_monthly",
"run_cost",
"provider_quota"
]
},
"aggregate": {
"type": "object",
"required": ["unit", "observed", "limit", "window"],
"properties": {
"unit": { "enum": ["cents", "usd_micros", "tokens"] },
"observed": { "type": "integer", "minimum": 0 },
"limit": { "type": "integer", "minimum": 0 },
"window": { "enum": ["run", "monthly_utc", "provider_window"] }
},
"additionalProperties": true
},
"relatedReceiptIds": {
"type": "array",
"items": { "$ref": "#/$defs/stableId" },
"uniqueItems": true
}
},
"additionalProperties": true,
"$defs": {
"stableId": {
"type": "string",
"minLength": 1,
"maxLength": 240,
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
}
}
}
@@ -19,7 +19,8 @@
"enum": ["done", "blocked", "needs_review", "yielded"]
},
"workAssessmentId": { "type": "string", "minLength": 1 },
"statusDecisionId": { "type": "string", "minLength": 1 }
"statusDecisionId": { "type": "string", "minLength": 1 },
"stopReason": { "$ref": "https://paperclip.dev/schemas/prp/v1/stop-reason.schema.json" }
},
"additionalProperties": true
}
@@ -9,6 +9,9 @@ pub const PRP_PROTOCOL_VERSION: u64 = 1;
const PRP_FIXTURE_VERSION: u64 = 1;
const PRP_FIXTURE_SCHEMA: &str = "paperclip.prp.fixture.v1";
const PRP_EVENT_SCHEMA: &str = "paperclip.prp.event.v1";
const PRP_SEMANTIC_TOOL_SCHEMA: &str = "paperclip.prp.semantic_tool.v1";
const PRP_STOP_REASON_SCHEMA: &str = "paperclip.prp.stop_reason.v1";
const PRP_SEMANTIC_TOOLS_CAPABILITY_SCHEMA: &str = "paperclip.prp.semantic_tools.v1";
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -17,6 +20,7 @@ struct ReplayFixture {
fixture_version: u64,
protocol_version: u64,
identity: ReplayIdentity,
capabilities: Value,
events: Vec<ReplayEvent>,
result: Value,
}
@@ -100,6 +104,11 @@ pub fn reduce_replay_fixture(input: &str) -> Result<ReplayParitySummary, ReplayE
fixture.schema
)));
}
validate_optional_versioned_envelope(
fixture.capabilities.get("semanticTools"),
PRP_SEMANTIC_TOOLS_CAPABILITY_SCHEMA,
"semanticTools",
)?;
let mut delivery_counts = HashMap::<&str, usize>::new();
for event in &fixture.events {
@@ -135,6 +144,16 @@ pub fn reduce_replay_fixture(input: &str) -> Result<ReplayParitySummary, ReplayE
event.schema_version
)));
}
validate_optional_versioned_envelope(
event.payload.get("semantic_tool"),
PRP_SEMANTIC_TOOL_SCHEMA,
"semantic_tool",
)?;
validate_optional_versioned_envelope(
event.payload.get("stopReason"),
PRP_STOP_REASON_SCHEMA,
"stopReason",
)?;
if event.run_id != fixture.identity.run_id {
return Err(ReplayError::invalid(
"event runId must match identity.runId",
@@ -219,6 +238,35 @@ pub fn reduce_replay_fixture(input: &str) -> Result<ReplayParitySummary, ReplayE
})
}
fn validate_optional_versioned_envelope(
envelope: Option<&Value>,
expected_schema: &str,
field: &str,
) -> Result<(), ReplayError> {
let Some(envelope) = envelope else {
return Ok(());
};
let schema = envelope
.get("schema")
.and_then(Value::as_str)
.unwrap_or("missing");
if schema != expected_schema {
return Err(ReplayError::invalid(format!(
"unsupported required {field} schema {schema}; expected {expected_schema}"
)));
}
let version = envelope
.get("schemaVersion")
.and_then(Value::as_u64)
.unwrap_or(0);
if version != 1 {
return Err(ReplayError::invalid(format!(
"unsupported required {field} schemaVersion {version}; expected 1"
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::{reduce_replay_fixture, ReplayParitySummary};
@@ -269,6 +317,36 @@ mod tests {
"unknown-optional-fields",
"unknown-optional-fields"
);
parity_case!(
replay_fixture_parity_semantic_tool_artifact_happy_path,
"semantic-tool-artifact-happy-path",
"semantic-tool-artifact-happy-path"
);
parity_case!(
replay_fixture_parity_semantic_tool_denial_redaction,
"semantic-tool-denial-redaction",
"semantic-tool-denial-redaction"
);
parity_case!(
replay_fixture_parity_semantic_tool_conflict_duplicate_retry,
"semantic-tool-conflict-duplicate-retry",
"semantic-tool-conflict-duplicate-retry"
);
parity_case!(
replay_fixture_parity_semantic_tool_governance_wake_monitor,
"semantic-tool-governance-wake-monitor",
"semantic-tool-governance-wake-monitor"
);
parity_case!(
replay_fixture_parity_budget_cost_stop_reason,
"budget-cost-stop-reason",
"budget-cost-stop-reason"
);
parity_case!(
replay_fixture_parity_semantic_tool_unknown_optional_envelope,
"semantic-tool-unknown-optional-envelope",
"semantic-tool-unknown-optional-envelope"
);
#[test]
fn replay_fixture_parity_rejects_unsupported_required_version() {
@@ -281,6 +359,17 @@ mod tests {
.contains("unsupported required protocolVersion 2"));
}
#[test]
fn replay_fixture_parity_rejects_unsupported_semantic_tool_version() {
let error = reduce_replay_fixture(include_str!(
"../../../../protocol/fixtures/replay/semantic-tool-unsupported-required-version.json"
))
.expect_err("semantic_tool v2 fixture must fail closed");
assert!(error
.to_string()
.contains("unsupported required semantic_tool schema paperclip.prp.semantic_tool.v2"));
}
#[test]
fn replay_fixture_parity_rejects_unsupported_fixture_version() {
let fixture = include_str!("../../../../protocol/fixtures/replay/happy-path.json")
@@ -16,6 +16,8 @@ const schemaNames = [
"identity",
"capabilities",
"command",
"semantic-tool",
"stop-reason",
"terminal",
"request",
"result",
@@ -30,10 +32,11 @@ const schemas = await Promise.all(
})),
);
const identifier = (name) => name.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
const declarations = schemas
.map(({ name, value }) => `export const ${name}Schema = ${JSON.stringify(value, null, 2)} as const;`)
.map(({ name, value }) => `export const ${identifier(name)}Schema = ${JSON.stringify(value, null, 2)} as const;`)
.join("\n\n");
const bundle = schemaNames.map((name) => ` ${name}: ${name}Schema,`).join("\n");
const bundle = schemaNames.map((name) => ` ${JSON.stringify(name)}: ${identifier(name)}Schema,`).join("\n");
const generated = `// Generated by scripts/generate-protocol-schema-module.mjs. Do not edit.\n\n${declarations}\n\nexport const prpSchemaBundle = {\n${bundle}\n} as const;\n`;
const schemaByName = Object.fromEntries(schemas.map(({ name, value }) => [name, value]));
@@ -56,11 +59,18 @@ const ucs2RuntimePattern = /const (func\d+) = require\("ajv\/dist\/runtime\/ucs2
if (!ucs2RuntimePattern.test(standaloneValidators)) {
throw new Error("Ajv standalone output no longer has the expected ucs2length runtime binding");
}
const equalRuntimePattern = /const (func\d+) = require\("ajv\/dist\/runtime\/equal"\)\.default;/;
if (!equalRuntimePattern.test(standaloneValidators)) {
throw new Error("Ajv standalone output no longer has the expected equal runtime binding");
}
const validators = standaloneValidators.replace(
ucs2RuntimePattern,
"const $1 = typeof ucs2LengthModule === \"function\" ? ucs2LengthModule : ucs2LengthModule.default;",
).replace(
equalRuntimePattern,
"const $1 = typeof equalModule === \"function\" ? equalModule : equalModule.default;",
);
const generatedValidators = `// Generated by scripts/generate-protocol-schema-module.mjs. Do not edit.\n// @ts-nocheck -- Ajv standalone output is JavaScript compiled from the checked-in schemas.\n\nimport ucs2LengthModule from "ajv/dist/runtime/ucs2length.js";\n\n${validators}\n`;
const generatedValidators = `// Generated by scripts/generate-protocol-schema-module.mjs. Do not edit.\n// @ts-nocheck -- Ajv standalone output is JavaScript compiled from the checked-in schemas.\n\nimport equalModule from "ajv/dist/runtime/equal.js";\nimport ucs2LengthModule from "ajv/dist/runtime/ucs2length.js";\n\n${validators}\n`;
if (process.argv.includes("--check")) {
const current = await readFile(outputPath, "utf8").catch(() => "");
@@ -18,6 +18,12 @@ const fixtureNames = [
"duplicate-event",
"source-gap",
"unknown-optional-fields",
"semantic-tool-artifact-happy-path",
"semantic-tool-denial-redaction",
"semantic-tool-conflict-duplicate-retry",
"semantic-tool-governance-wake-monitor",
"budget-cost-stop-reason",
"semantic-tool-unknown-optional-envelope",
];
const check = process.argv.includes("--check");
const stale = [];
@@ -4,10 +4,12 @@
PRP v1 is sufficient for runner lifecycle, replay integrity, negotiated runner
features, runtime/issue-thread request routing, structured work disposition,
and terminal causality. It is **not** yet sufficient to claim a provider-neutral
semantic-control-plane protocol: tool invocation, authorization denial,
redaction, optimistic concurrency, mutation receipts, and budget stop reasons
are presently represented only by event or request `payload` extension space.
terminal causality, and provider-neutral semantic-control-plane receipts. The
optional `semantic_tool` and `terminal.stopReason` envelopes now type tool
invocation, authorization denial, redaction, optimistic concurrency, mutation
receipts, artifacts, governed targets, causal continuations, and budget stops.
They remain receipts for control-plane-owned operations, not a provider tool
catalog or permission to move control-plane policy into the runner.
`prp-v1-expressiveness-crosswalk.json` is the machine-checked source for this
verdict. Its Vitest gate rejects an unclassified required fact, a fact justified
@@ -39,12 +41,18 @@ coverage row.
assessment, and issue-status decision references. Existing golden replay,
duplicate-event, unknown-optional-fields, and unsupported-required-version
fixtures demonstrate the compatibility boundary.
- `capabilities.semanticTools` advertises versioned, provider-neutral operation
availability and redaction rules. Paired `mcp_app.tool_input` and
`mcp_app.tool_result` events carry one safe `semantic_tool` envelope per call.
- `terminal.stopReason` carries a safe budget/cost aggregate and the decision
receipt that stopped the run. Eval `trace_completeness` reads these PRP wire
receipts when available while retaining the existing live-evidence fallback.
## Required additive v1 envelope
## Landed additive v1 envelope
The smallest correction is one optional, versioned `semantic_tool` envelope
for `mcp_app.tool_input` / `mcp_app.tool_result` (or equivalent new event
types), plus an optional `terminal.stopReason` envelope. It must define:
The correction is one optional, versioned `semantic_tool` envelope for
`mcp_app.tool_input` / `mcp_app.tool_result`, plus an optional
`terminal.stopReason` envelope. It defines:
- operation id, call id, correlation ids, idempotency key, outcome, stable
code, retryability, and audit/operation receipt id;
@@ -58,10 +66,10 @@ types), plus an optional `terminal.stopReason` envelope. It must define:
result; immutable interaction/approval document or decision targets; and
budget stop reason/aggregate/decision id.
This is additive because existing v1 consumers can ignore the optional typed
envelope. It is not permission to add speculative semantic events: the
crosswalk names the exact facts and the conformance vectors that must land
with any schema change.
This remains additive because existing v1 consumers can ignore the optional
typed envelopes. The reducer intentionally does not project them into session
state; they supply inspectable trace evidence only. Unknown optional fields are
accepted, while an unknown required envelope version fails closed.
## Classification and fixture plan
@@ -70,19 +78,20 @@ compositional v1 with a listed invariant, control-plane-local, additive v1,
missing fixture/docs, or breaking v2 work. Direct and compositional support is
never inferred merely from `additionalProperties: true`.
Before an additive change graduates to direct support, add these fixtures and
golden projections:
The additive change graduated to direct support with these fixtures and golden
projections:
1. semantic tool happy path and artifact registration;
2. denied operation with redaction assertions and no fallback call;
3. stale optimistic write conflict and exact duplicate/retry receipt;
4. immutable interaction/approval target plus wake/monitor causal chain;
5. budget/cost terminal stop reason;
6. unknown optional envelope fields accepted without changing v1 projection;
7. unknown required envelope version rejected fail-closed.
1. `semantic-tool-artifact-happy-path.json` — artifact and work-product receipt;
2. `semantic-tool-denial-redaction.json` — denied/redacted with no fallback;
3. `semantic-tool-conflict-duplicate-retry.json` — stale conflict and exact retry;
4. `semantic-tool-governance-wake-monitor.json` — immutable governed targets and continuation chain;
5. `budget-cost-stop-reason.json` — typed terminal budget/cost stop;
6. `semantic-tool-unknown-optional-envelope.json` — optional fields accepted with unchanged projection;
7. `semantic-tool-unsupported-required-version.json` — required v2 envelope rejected fail-closed.
Each mutation fixture must replay to a golden summary/snapshot and be executed
through mock and real adapters with normalized receipt/state-diff comparison.
The six accepted fixtures replay to TypeScript/Rust parity summaries and golden
snapshots. Shared high-risk semantic vectors compare normalized receipt and
state-diff observations across deterministic mock and production bindings.
## Explicit exclusions
@@ -99,4 +108,4 @@ or denied outcomes rather than tunnel those operations through generic payloads.
accepted protocol schema/event addition.
- A semantic receipt is typed and safe before any surface is marked supported.
- Control-plane-local decisions stay out of the semantic tool wire.
- Fixture/document debt is recorded separately from a protocol semantic gap.
- Eval trace scoring consumes wire receipts without changing projection semantics.
@@ -2,19 +2,103 @@
"$schema": "./prp-v1-expressiveness-crosswalk.schema.json",
"version": 1,
"facts": [
{"id":"semantic-tool-exposure","classification":"additive_v1_change","wire":"capabilities.schema.json","evidence":["capabilities.schema.json#/properties/unsupported","spec/capability/capabilities.yaml"],"invariant":"Advertised operations are a typed operation-id set with grant, task-mode, and redaction disposition; generic_api_request never counts as coverage.","correction":{"kind":"additive_schema","target":"capabilities.semanticTools","fixtures":["semantic-tool-denied.json","semantic-tool-unsupported.json"]}},
{"id":"semantic-tool-call-result","classification":"additive_v1_change","wire":"event.schema.json","evidence":["event.schema.json#/properties/eventType","docs/capability-semantic-tools.md"],"invariant":"Each semantic invocation has one call id, operation id, input digest/redaction disposition, and exactly one typed result receipt.","correction":{"kind":"additive_event","target":"semantic_tool.call|result","fixtures":["semantic-tool-happy-path.json","semantic-tool-duplicate-retry.json"]}},
{"id":"semantic-tool-denial-and-redaction","classification":"additive_v1_change","wire":"event.schema.json","evidence":["event.schema.json#/properties/eventType","docs/capability-authorization-and-exposure.md"],"invariant":"A denied operation yields a typed safe receipt naming the failed boundary; secret values and hidden identifiers are absent.","correction":{"kind":"additive_schema","target":"semantic_tool.receipt","fixtures":["semantic-tool-denied.json","semantic-tool-redaction.json"]}},
{"id":"optimistic-write-conflict","classification":"additive_v1_change","wire":"event.schema.json","evidence":["event.schema.json#/properties/eventType","docs/capability-mock-control-plane-port.md"],"invariant":"A mutation receipt carries operation id, idempotency key, outcome, stable code, retryability, and safe current revision when applicable.","correction":{"kind":"additive_schema","target":"semantic_tool.receipt","fixtures":["document-conflict.json","document-duplicate-retry.json"]}},
{"id":"artifacts-and-deliverables","classification":"compositional_v1_with_invariant","wire":"result.schema.json","evidence":["result.schema.json#/properties/artifacts","protocol/fixtures/replay/happy-path.json"],"invariant":"Every artifact ref is produced by a typed semantic receipt or registered control-plane work product, and is linked to the terminal result by run id."},
{"id":"governance-links-and-interactions","classification":"compositional_v1_with_invariant","wire":"request.schema.json","evidence":["request.schema.json#/oneOf/1","event.schema.json#/properties/eventType"],"invariant":"Interaction and approval targets are immutable revision/decision references carried in a typed target envelope, not an unconstrained request payload."},
{"id":"blockers-monitors-and-wakes","classification":"compositional_v1_with_invariant","wire":"result.schema.json","evidence":["result.schema.json#/properties/blocker","result.schema.json#/properties/continuation","event.schema.json#/properties/eventType"],"invariant":"A blocked or yielded result names a bounded owner/action or continuation kind; wake routing remains a control-plane decision and emits only safe causal identifiers."},
{"id":"budget-cost-stop-reason","classification":"additive_v1_change","wire":"terminal.schema.json","evidence":["terminal.schema.json#/properties/runTerminalState","docs/capability-disposition.md"],"invariant":"A budget/cost stop includes a typed stop reason, observed limit class, safe usage aggregate, and control-plane decision id.","correction":{"kind":"additive_schema","target":"terminal.stopReason","fixtures":["budget-stop.json"]}},
{"id":"audit-and-idempotency-receipts","classification":"additive_v1_change","wire":"event.schema.json","evidence":["event.schema.json#/required","protocol/fixtures/replay/duplicate-event.json"],"invariant":"Source-event idempotency is already replay-safe; semantic mutation idempotency additionally requires a stable operation receipt/audit ref.","correction":{"kind":"additive_schema","target":"semantic_tool.receipt","fixtures":["semantic-tool-duplicate-retry.json"]}},
{"id":"capability-negotiation-and-unsupported","classification":"direct_v1","wire":"capabilities.schema.json","evidence":["capabilities.schema.json#/required","capabilities.schema.json#/properties/unsupported","protocol/fixtures/replay/unsupported-required-version.json"],"invariant":"The controller binds behavior to the negotiated typed booleans/version and treats unsupported required versions as fail-closed."},
{"id":"end-to-end-causality","classification":"compositional_v1_with_invariant","wire":"event.schema.json","evidence":["event.schema.json#/required","terminal.schema.json#/properties/workAssessmentId","protocol/fixtures/replay/golden/happy-path.snapshot.json"],"invariant":"Run, session, turn, item, source-event, work-assessment, status-decision, request, and receipt identifiers form an acyclic trace from command to terminal decision."},
{"id":"control-plane-local-lifecycle","classification":"control_plane_local","wire":"none","evidence":["docs/capability-disposition.md","spec/capability/capabilities.yaml"],"invariant":"Checkout/release, wake routing, budget enforcement, audit persistence, run persistence/replay, assignment, and monitor management are not runner tool calls and must not be serialized as semantic operations."},
{"id":"future-cross-company-and-destructive-admin","classification":"breaking_v2_work","wire":"none","evidence":["docs/capability-disposition.md","docs/capability-future-binding-boundary.md"],"invariant":"Cross-company discovery, broad audit access, destructive document lifecycle, and company administration require separately governed product contracts; v1 must reject rather than tunnel them through generic payloads."},
{"id":"existing-fixture-and-doc-coverage","classification":"missing_fixture_docs","wire":"protocol/fixtures/replay","evidence":["protocol/fixtures/replay/duplicate-event.json","protocol/fixtures/replay/unknown-optional-fields.json","src/protocol/replay-contract.test.ts"],"invariant":"Every accepted additive correction ships a golden replay, duplicate/retry, redaction/denial, and unknown-version fixture before it is called direct support."}
{
"id": "semantic-tool-exposure",
"classification": "direct_v1",
"wire": "capabilities.schema.json",
"evidence": ["capabilities.schema.json#/properties/semanticTools", "protocol/fixtures/replay/semantic-tool-unknown-optional-envelope.json"],
"invariant": "Advertised operations are a typed operation-id set with availability, required claims, and redaction disposition; generic_api_request never counts as coverage."
},
{
"id": "semantic-tool-call-result",
"classification": "direct_v1",
"wire": "event.schema.json",
"evidence": ["event.schema.json#/allOf", "semantic-tool.schema.json", "protocol/fixtures/replay/semantic-tool-artifact-happy-path.json"],
"invariant": "Each semantic invocation has one call id, operation id, safe input digest, and exactly one typed result receipt."
},
{
"id": "semantic-tool-denial-and-redaction",
"classification": "direct_v1",
"wire": "semantic-tool.schema.json",
"evidence": ["semantic-tool.schema.json#/properties/authorizationBoundary", "protocol/fixtures/replay/semantic-tool-denial-redaction.json"],
"invariant": "A denied operation yields a typed safe receipt naming the failed boundary; secret values and hidden identifiers are absent."
},
{
"id": "optimistic-write-conflict",
"classification": "direct_v1",
"wire": "semantic-tool.schema.json",
"evidence": ["semantic-tool.schema.json#/properties/currentRevision", "protocol/fixtures/replay/semantic-tool-conflict-duplicate-retry.json"],
"invariant": "A mutation receipt carries operation id, idempotency key, outcome, stable code, retryability, and safe current revision when applicable."
},
{
"id": "artifacts-and-deliverables",
"classification": "compositional_v1_with_invariant",
"wire": "semantic-tool.schema.json + result.schema.json",
"evidence": ["semantic-tool.schema.json#/properties/artifactRefs", "result.schema.json#/properties/artifacts", "protocol/fixtures/replay/semantic-tool-artifact-happy-path.json"],
"invariant": "Every artifact ref is produced by a typed semantic receipt or registered work product and remains linked to the terminal result by run id."
},
{
"id": "governance-links-and-interactions",
"classification": "compositional_v1_with_invariant",
"wire": "semantic-tool.schema.json + request.schema.json",
"evidence": ["semantic-tool.schema.json#/properties/targets", "protocol/fixtures/replay/semantic-tool-governance-wake-monitor.json"],
"invariant": "Interaction and approval targets are immutable revision or decision references carried in a typed target envelope."
},
{
"id": "blockers-monitors-and-wakes",
"classification": "compositional_v1_with_invariant",
"wire": "semantic-tool.schema.json + result.schema.json",
"evidence": ["semantic-tool.schema.json#/properties/causalRefs", "result.schema.json#/properties/continuation", "protocol/fixtures/replay/semantic-tool-governance-wake-monitor.json"],
"invariant": "A blocked or yielded result names a bounded continuation; wake routing remains a control-plane decision and emits only safe causal identifiers."
},
{
"id": "budget-cost-stop-reason",
"classification": "direct_v1",
"wire": "terminal.schema.json",
"evidence": ["terminal.schema.json#/properties/stopReason", "stop-reason.schema.json", "protocol/fixtures/replay/budget-cost-stop-reason.json"],
"invariant": "A budget or cost stop includes a typed reason, observed limit class, safe usage aggregate, and control-plane decision id."
},
{
"id": "audit-and-idempotency-receipts",
"classification": "direct_v1",
"wire": "semantic-tool.schema.json",
"evidence": ["semantic-tool.schema.json#/properties/operationReceiptId", "protocol/fixtures/replay/semantic-tool-conflict-duplicate-retry.json"],
"invariant": "Source-event and semantic mutation idempotency are distinct; an exact semantic retry reuses its stable operation receipt and records the duplicate link."
},
{
"id": "capability-negotiation-and-unsupported",
"classification": "direct_v1",
"wire": "capabilities.schema.json",
"evidence": ["capabilities.schema.json#/properties/semanticTools", "protocol/fixtures/replay/semantic-tool-unknown-optional-envelope.json", "protocol/fixtures/replay/semantic-tool-unsupported-required-version.json"],
"invariant": "The controller binds behavior to negotiated typed capability versions, accepts optional extensions, and rejects unknown required versions fail-closed."
},
{
"id": "end-to-end-causality",
"classification": "compositional_v1_with_invariant",
"wire": "event.schema.json + semantic-tool.schema.json + terminal.schema.json",
"evidence": ["event.schema.json#/required", "semantic-tool.schema.json#/properties/correlation", "protocol/fixtures/replay/semantic-tool-governance-wake-monitor.json"],
"invariant": "Run, session, turn, item, source-event, governed-target, wake, monitor, decision, and receipt identifiers form an inspectable trace to terminal disposition."
},
{
"id": "control-plane-local-lifecycle",
"classification": "control_plane_local",
"wire": "none",
"evidence": ["docs/capability-disposition.md", "spec/capability/capabilities.yaml"],
"invariant": "Checkout and release, wake routing, budget enforcement, audit persistence, run persistence and replay, assignment, and monitor management are not runner tool calls."
},
{
"id": "future-cross-company-and-destructive-admin",
"classification": "breaking_v2_work",
"wire": "none",
"evidence": ["docs/capability-disposition.md", "docs/capability-future-binding-boundary.md"],
"invariant": "Cross-company discovery, broad audit access, destructive document lifecycle, and company administration require separately governed contracts; v1 rejects rather than tunnels them."
},
{
"id": "existing-fixture-and-doc-coverage",
"classification": "direct_v1",
"wire": "protocol/fixtures/replay",
"evidence": ["src/protocol/replay-contract.test.ts", "runner/crates/runner-core/src/replay.rs", "docs/protocol-compatibility.md"],
"invariant": "Every accepted additive correction ships golden replay, duplicate and retry, redaction and denial, optional-extension, and unknown-required-version conformance coverage."
}
]
}
@@ -50,5 +50,9 @@ describe("capability semantic conformance vectors", () => {
.toMatchObject({ task: { status: "done" } });
expect(report.rows.find((row) => row.vectorId === "terminal-with-dependency")?.observation.state)
.toMatchObject({ task: { status: "done" }, dependencies: [expect.any(String)] });
expect(report.rows.every((row) => row.observation.receipt?.operationReceiptPresent === true))
.toBe(true);
expect(report.rows.find((row) => row.vectorId === "document-stale-revision")?.observation.receipt)
.toMatchObject({ outcome: "conflict", authorizationBoundary: "revision", retryable: false });
});
});
@@ -11,6 +11,12 @@ import {
type CapabilitySemanticDispatcherOptions,
} from "../semantic-tools/dispatcher.js";
import type { CapabilitySemanticToolResult } from "../semantic-tools/types.js";
import {
createPrpSemanticToolInputEnvelope,
createPrpSemanticToolResultEnvelope,
semanticAuthorizationBoundaryForCode,
semanticOutcomeForCode,
} from "../protocol/semantic-tool-receipts.js";
import type {
SemanticConformanceAdapter,
SemanticConformanceObservation,
@@ -192,7 +198,13 @@ export class CapabilityMockSemanticConformanceAdapter implements SemanticConform
input: vector.input,
});
const after = world.port.snapshot();
return normalizeCapabilitySemanticObservation({ result, before, after, taskId: world.taskId });
return normalizeCapabilitySemanticObservation({
result,
before,
after,
taskId: world.taskId,
semanticInput: vector.input,
});
}
async stop(): Promise<void> {
@@ -229,18 +241,77 @@ export function normalizeCapabilitySemanticObservation(input: {
readonly before: Readonly<CapabilityFixtureState>;
readonly after: Readonly<CapabilityFixtureState>;
readonly taskId: string;
readonly semanticInput: unknown;
}): SemanticConformanceObservation {
const { result, before, after, taskId } = input;
const run = after.runs.find((candidate) => candidate.taskId === taskId);
if (run === undefined) throw new Error(`semantic_conformance_run_missing:${taskId}`);
const idempotencyKey = idempotencyKeyFrom(input.semanticInput);
const disposition = result.ok && isRecord(result.result) ? result.result.disposition : undefined;
const code = result.ok && disposition === "duplicate"
? "duplicate"
: result.ok
? "ok"
: result.denial.controlPlaneCode ?? result.denial.code;
const effects = result.ok ? normalizeEffects(result.result) : [];
const authorization = result.ok
? { outcome: "allowed" as const }
: {
outcome: "denied" as const,
code,
};
const envelopeBase = {
operationId: result.operationId,
callId: result.callId,
correlation: {
runId: run.id,
normalizedSessionId: run.sessionId,
turnId: `turn:${result.callId}`,
itemId: `item:${result.callId}`,
},
idempotencyKey,
};
const inputReceipt = createPrpSemanticToolInputEnvelope({
...envelopeBase,
content: input.semanticInput,
});
const receipt = createPrpSemanticToolResultEnvelope({
...envelopeBase,
content: { authorization, effects },
outcome: semanticOutcomeForCode({
ok: result.ok,
code,
disposition,
}),
code,
retryable: result.ok ? false : result.denial.retryable,
authorizationBoundary: semanticAuthorizationBoundaryForCode(code),
...(after.audit.length <= before.audit.length
? {}
: { auditReceiptId: after.audit.at(-1)?.id }),
currentRevision: result.stateRevision,
});
return Object.freeze({
authorization: result.ok
? { outcome: "allowed" as const }
: {
outcome: "denied" as const,
code: result.denial.controlPlaneCode ?? result.denial.code,
},
authorization,
state: projectSemanticState(after, taskId),
effects: result.ok ? normalizeEffects(result.result) : [],
effects,
audit: after.audit.length > before.audit.length ? [{ outcome: "recorded" }] : [],
receipt: {
schema: receipt.schema,
schemaVersion: receipt.schemaVersion,
phase: "result",
operationId: receipt.operationId,
callId: receipt.callId,
idempotencyKeyPresent: receipt.idempotencyKey !== null,
outcome: String(receipt.outcome),
code: String(receipt.code),
retryable: receipt.retryable === true,
authorizationBoundary: String(receipt.authorizationBoundary),
operationReceiptPresent: typeof receipt.operationReceiptId === "string",
inputDigestPresent: typeof inputReceipt.content.digest === "string",
outputDigestPresent: typeof receipt.content.digest === "string",
currentRevisionPresent: receipt.currentRevision !== undefined,
},
});
}
@@ -293,6 +364,17 @@ function normalizeEffects(result: unknown): readonly unknown[] {
}];
}
function idempotencyKeyFrom(value: unknown): string | null {
if (!isRecord(value)) return null;
return typeof value.idempotencyKey === "string" && value.idempotencyKey.length > 0
? value.idempotencyKey
: null;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function mockWorld(
id: string,
taskId: string,
@@ -17,6 +17,23 @@ export interface SemanticConformanceObservation {
readonly state: unknown;
readonly effects: readonly unknown[];
readonly audit: readonly unknown[];
/** Provider-neutral normalized view of the PRP semantic result receipt. */
readonly receipt?: {
readonly schema: "paperclip.prp.semantic_tool.v1";
readonly schemaVersion: 1;
readonly phase: "result";
readonly operationId: string;
readonly callId: string;
readonly idempotencyKeyPresent: boolean;
readonly outcome: string;
readonly code: string;
readonly retryable: boolean;
readonly authorizationBoundary: string;
readonly operationReceiptPresent: boolean;
readonly inputDigestPresent: boolean;
readonly outputDigestPresent: boolean;
readonly currentRevisionPresent: boolean;
};
}
export type SemanticConformanceAdapterKind = "mock" | "production_binding";
@@ -23,6 +23,13 @@ import {
type EvalFaultClass,
} from "./eval-bundle.js";
import { scoreEval, type AuthorizationState, type EvalObservation } from "./eval-scoring.js";
import type { PrpEvent } from "../protocol/replay-contract.js";
import {
createPrpSemanticToolInputEnvelope,
createPrpSemanticToolResultEnvelope,
semanticAuthorizationBoundaryForCode,
semanticOutcomeForCode,
} from "../protocol/semantic-tool-receipts.js";
export const EVAL_BEHAVIOR_IDS = [
"checkout_context",
@@ -579,8 +586,14 @@ async function runBehaviorCase(bundle: EvalBundle, spec: BehaviorCase): Promise<
sessionId: SESSION_ID,
turnId: `turn-${caseId}`,
itemId: `item-${caseId}`,
receiptIds: results.map((result) => result.callId),
receiptIds: [],
terminalPresent: true,
wireEvents: evalWireEvents({
caseId,
invocations: spec.invocations,
results,
authorizationRecords,
}),
},
efficiency: { attempts: observedCalls.length },
budget: { maxAttempts: spec.maxAttempts ?? Math.max(1, spec.expectedCalls.length) },
@@ -589,6 +602,142 @@ async function runBehaviorCase(bundle: EvalBundle, spec: BehaviorCase): Promise<
return { behavior: spec.behavior, counterpart: spec.counterpart, observation };
}
function evalWireEvents(input: {
caseId: string;
invocations: readonly Invocation[];
results: readonly CapabilitySemanticToolResult[];
authorizationRecords: ReturnType<CapabilitySemanticDispatcher["authorizationRecords"]>;
}): PrpEvent[] {
const events: PrpEvent[] = [];
const turnId = `turn:${input.caseId}`;
const emittedAt = "2026-08-11T00:00:00.000Z";
const append = (
eventType: "mcp_app.tool_input" | "mcp_app.tool_result" | "run.terminal",
payload: Record<string, unknown>,
itemId?: string,
): void => {
const sourceSeq = events.length + 1;
events.push({
schema: "paperclip.prp.event.v1",
sourceEventId: `eval-wire:${input.caseId}:${sourceSeq}`,
sourceSeq,
sourceInstanceId: "eval-wire",
sourceKind: "runner",
runId: RUN_ID,
normalizedSessionId: SESSION_ID,
turnId,
...(itemId === undefined ? {} : { itemId }),
eventType,
schemaVersion: 1,
priority: eventType === "run.terminal" ? 0 : 1,
emittedAt,
payload,
});
};
input.results.forEach((result, index) => {
const invocation = input.invocations[index]!;
const itemId = `item:${result.callId}`;
const correlation = { runId: RUN_ID, normalizedSessionId: SESSION_ID, turnId, itemId };
const idempotencyKey = semanticIdempotencyKey(invocation.input);
append("mcp_app.tool_input", {
semantic_tool: createPrpSemanticToolInputEnvelope({
operationId: result.operationId,
callId: result.callId,
correlation,
idempotencyKey,
content: invocation.input,
}),
}, itemId);
const code = result.ok ? semanticSuccessCode(result.result) : result.denial.controlPlaneCode ?? result.denial.code;
const authorizationRecord = input.authorizationRecords.find(
(record) => record.phase === "invocation" && record.callId === result.callId,
);
append("mcp_app.tool_result", {
semantic_tool: createPrpSemanticToolResultEnvelope({
operationId: result.operationId,
callId: result.callId,
correlation,
idempotencyKey,
content: result,
outcome: semanticOutcomeForCode({
ok: result.ok,
code,
disposition: result.ok && isRecord(result.result) ? result.result.disposition : undefined,
}),
code,
retryable: result.ok ? false : result.denial.retryable,
authorizationBoundary: semanticAuthorizationBoundaryForCode(code),
...(authorizationRecord === undefined ? {} : { auditReceiptId: authorizationRecord.id }),
currentRevision: result.stateRevision,
artifactRefs: result.ok ? semanticArtifactRefs(result.result) : [],
causalRefs: result.ok ? semanticCausalRefs(result.result) : [],
}),
}, itemId);
});
append("run.terminal", {
schema: "paperclip.prp.terminal.v1",
turnTerminalState: "completed",
runTerminalState: "succeeded",
reportedWorkDisposition: "done",
}, `item:${input.caseId}`);
return events;
}
function semanticIdempotencyKey(value: unknown): string | null {
return isRecord(value) && typeof value.idempotencyKey === "string"
? value.idempotencyKey
: null;
}
function semanticSuccessCode(value: unknown): string {
return isRecord(value) && value.disposition === "duplicate" ? "duplicate" : "ok";
}
function semanticArtifactRefs(value: unknown) {
return semanticRefs(value).filter((reference) =>
reference.kind === "artifact" || reference.kind === "work_product",
);
}
function semanticCausalRefs(value: unknown) {
return semanticRefs(value).filter((reference) =>
["document_revision", "interaction", "approval", "decision", "wake", "monitor"].includes(reference.kind),
);
}
function semanticRefs(value: unknown): Array<{
kind: "task" | "document_revision" | "interaction" | "approval" | "decision" | "artifact" | "work_product" | "wake" | "monitor" | "audit" | "operation";
id: string;
}> {
if (!isRecord(value) || !Array.isArray(value.entityRefs)) return [];
const kindMap = {
task: "task",
revision: "document_revision",
interaction: "interaction",
approval: "approval",
decision: "decision",
artifact: "artifact",
"work-product": "work_product",
wake: "wake",
monitor: "monitor",
audit: "audit",
command: "operation",
} as const;
return value.entityRefs.flatMap((entry) => {
if (typeof entry !== "string") return [];
const separator = entry.indexOf(":");
if (separator < 1) return [];
const kind = kindMap[entry.slice(0, separator) as keyof typeof kindMap];
const id = entry.slice(separator + 1);
return kind === undefined || id.length === 0 ? [] : [{ kind, id }];
});
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
/**
* Executes the eight requested behaviors and their red counterparts against the
* real deterministic mock authority. The four declared fault classes are not
@@ -7,6 +7,11 @@ import {
scoreEval,
type EvalObservation,
} from "./eval-scoring.js";
import {
createPrpBudgetStopReason,
createPrpSemanticToolResultEnvelope,
} from "../protocol/semantic-tool-receipts.js";
import type { PrpEvent } from "../protocol/replay-contract.js";
function greenObservation(overrides: Partial<EvalObservation> = {}): EvalObservation {
return {
@@ -207,6 +212,59 @@ describe("scoreEval — dimensions are scored separately (red counterparts)", ()
expect(card.dimensions.trace_completeness.score).toBeCloseTo(5 / 6, 5);
});
it("derives semantic receipt and terminal completeness from PRP wire events", () => {
const card = scoreEval(
greenObservation({
trace: {
receiptIds: [],
terminalPresent: false,
wireEvents: wireTrace(),
},
}),
OPTIONS,
);
expect(card.dimensions.trace_completeness).toMatchObject({ score: 1, passed: true });
});
it("rejects a PRP receipt whose operation does not match the observed call", () => {
const events = wireTrace();
const payload = events[0]!.payload as Record<string, unknown>;
const receipt = payload.semantic_tool as Record<string, unknown>;
receipt.operationId = "report_progress";
const card = scoreEval(
greenObservation({ trace: { receiptIds: ["legacy-must-not-mask-wire"], terminalPresent: true, wireEvents: events } }),
OPTIONS,
);
expect(card.dimensions.trace_completeness.reasons).toContain("trace missing receipt-per-call");
});
it("requires a receipt and decision id when a terminal advertises a stop reason", () => {
const events = wireTrace();
const terminal = events[1]!.payload as Record<string, unknown>;
terminal.stopReason = createPrpBudgetStopReason({
receiptId: "stop-receipt-1",
kind: "budget",
code: "budget_hard_stop",
retryable: true,
decisionId: "budget-decision-1",
limitClass: "actor_monthly",
aggregate: { unit: "cents", observed: 5000, limit: 5000, window: "monthly_utc" },
});
const card = scoreEval(
greenObservation({ trace: { receiptIds: [], terminalPresent: false, wireEvents: events } }),
OPTIONS,
);
expect(card.dimensions.trace_completeness).toMatchObject({ score: 1, passed: true });
delete (terminal.stopReason as Record<string, unknown>).decisionId;
const invalid = scoreEval(
greenObservation({ trace: { receiptIds: [], terminalPresent: false, wireEvents: events } }),
OPTIONS,
);
expect(invalid.dimensions.trace_completeness.reasons)
.toContain("trace missing stop-reason-receipt");
});
it("drops quality_efficiency when a budget is exceeded", () => {
const card = scoreEval(
greenObservation({ efficiency: { latencyMs: 9000, totalTokens: 400, costUsd: 0.01, attempts: 1 } }),
@@ -226,6 +284,65 @@ describe("scoreEval — dimensions are scored separately (red counterparts)", ()
});
});
function wireTrace(): PrpEvent[] {
const correlation = {
runId: "run-1",
normalizedSessionId: "session-1",
turnId: "turn-1",
itemId: "item-1",
};
return [
{
schema: "paperclip.prp.event.v1",
sourceEventId: "wire-event-1",
sourceSeq: 1,
sourceInstanceId: "wire-test",
sourceKind: "runner",
runId: "run-1",
normalizedSessionId: "session-1",
turnId: "turn-1",
itemId: "item-1",
eventType: "mcp_app.tool_result",
schemaVersion: 1,
priority: 1,
emittedAt: "2026-08-11T00:00:00.000Z",
payload: {
semantic_tool: createPrpSemanticToolResultEnvelope({
operationId: "finish_task",
callId: "call-1",
correlation,
idempotencyKey: "finish-once",
content: { disposition: "applied" },
outcome: "succeeded",
code: "ok",
retryable: false,
authorizationBoundary: "active_task",
}),
},
},
{
schema: "paperclip.prp.event.v1",
sourceEventId: "wire-event-2",
sourceSeq: 2,
sourceInstanceId: "wire-test",
sourceKind: "runner",
runId: "run-1",
normalizedSessionId: "session-1",
turnId: "turn-1",
eventType: "run.terminal",
schemaVersion: 1,
priority: 0,
emittedAt: "2026-08-11T00:00:01.000Z",
payload: {
schema: "paperclip.prp.terminal.v1",
turnTerminalState: "completed",
runTerminalState: "succeeded",
reportedWorkDisposition: "done",
},
},
];
}
describe("scoreEval — determinism and weighting", () => {
it("is deterministic for identical observations", () => {
const obs = greenObservation({ finalState: { expected: "mutated", observed: "unchanged" } });
@@ -2,6 +2,8 @@ import type {
CapabilityEvalCaseResult,
CapabilityLiveCodexMatrixResult,
} from "../conformance/capability-eval-suite.js";
import type { PrpEvent } from "../protocol/replay-contract.js";
import { prpSemanticToolResultReceipts } from "../protocol/semantic-tool-receipts.js";
import type { EvalFaultClass } from "./eval-bundle.js";
/**
@@ -64,6 +66,11 @@ export interface EvalTraceEvidence {
/** One safe receipt/operation id per observed semantic call. */
receiptIds: string[];
terminalPresent: boolean;
/**
* When present, PRP is authoritative for causal ids, semantic receipts, and
* terminal presence. Legacy live snapshots continue to use the scalar fields.
*/
wireEvents?: PrpEvent[];
}
export interface EvalEfficiencyEvidence {
@@ -200,17 +207,25 @@ function scoreTrajectoryRestraint(obs: EvalObservation): { score: number; reason
function scoreTraceCompleteness(obs: EvalObservation): { score: number; reasons: string[] } {
const reasons: string[] = [];
const wire = obs.trace.wireEvents === undefined
? null
: traceEvidenceFromPrpEvents(obs.trace.wireEvents, obs.observedCalls);
const trace = wire ?? obs.trace;
const receiptsComplete =
obs.trace.receiptIds.length >= obs.observedCalls.length &&
obs.trace.receiptIds.every((id) => id.length > 0);
trace.receiptIds.length >= obs.observedCalls.length &&
trace.receiptIds.every((id) => id.length > 0) &&
(wire?.semanticReceiptsMatchCalls ?? true);
const checks: Array<[string, boolean]> = [
["runId", Boolean(obs.trace.runId)],
["sessionId", Boolean(obs.trace.sessionId)],
["turnId", Boolean(obs.trace.turnId)],
["itemId", Boolean(obs.trace.itemId)],
["terminal", obs.trace.terminalPresent],
["runId", Boolean(trace.runId)],
["sessionId", Boolean(trace.sessionId)],
["turnId", Boolean(trace.turnId)],
["itemId", Boolean(trace.itemId)],
["terminal", trace.terminalPresent],
["receipt-per-call", receiptsComplete],
];
if (wire?.stopReasonPresent) {
checks.push(["stop-reason-receipt", wire.stopReasonReceiptValid]);
}
for (const [label, present] of checks) {
if (!present) reasons.push(`trace missing ${label}`);
}
@@ -218,6 +233,58 @@ function scoreTraceCompleteness(obs: EvalObservation): { score: number; reasons:
return { score: clamp01(passed / checks.length), reasons };
}
export function traceEvidenceFromPrpEvents(
events: readonly PrpEvent[],
observedCalls: readonly string[] = [],
): EvalTraceEvidence & {
semanticReceiptsMatchCalls: boolean;
stopReasonPresent: boolean;
stopReasonReceiptValid: boolean;
} {
const receipts = prpSemanticToolResultReceipts(events);
const receiptCalls = receipts.map((receipt) => String(receipt.operationId));
const semanticReceiptsMatchCalls = receiptCalls.length === observedCalls.length
&& observedCalls.every((operationId, index) =>
receiptCalls[index] === operationId
&& typeof receipts[index]?.operationReceiptId === "string"
&& String(receipts[index]?.operationReceiptId).length > 0,
);
const terminal = events.find((event) => event.eventType === "run.terminal");
const terminalPayload = asRecord(terminal?.payload);
const stopReason = asRecord(terminalPayload?.stopReason);
const stopReasonPresent = stopReason !== null;
const stopReasonReceiptValid = !stopReasonPresent || (
stopReason?.schema === "paperclip.prp.stop_reason.v1"
&& stopReason.schemaVersion === 1
&& typeof stopReason.receiptId === "string"
&& stopReason.receiptId.length > 0
&& typeof stopReason.decisionId === "string"
&& stopReason.decisionId.length > 0
);
const correlated = events.find((event) => event.eventType === "mcp_app.tool_result")
?? events.find((event) => event.turnId !== undefined);
return {
runId: events[0]?.runId,
sessionId: correlated?.normalizedSessionId,
turnId: correlated?.turnId,
itemId: correlated?.itemId,
receiptIds: receipts.flatMap((receipt) =>
typeof receipt.operationReceiptId === "string" ? [receipt.operationReceiptId] : [],
),
terminalPresent: terminal !== undefined,
wireEvents: [...events],
semanticReceiptsMatchCalls,
stopReasonPresent,
stopReasonReceiptValid,
};
}
function asRecord(value: unknown): Record<string, unknown> | null {
return typeof value === "object" && value !== null && !Array.isArray(value)
? value as Record<string, unknown>
: null;
}
function scoreQualityEfficiency(obs: EvalObservation): { score: number; reasons: string[] } {
const budget = obs.budget;
if (!budget || Object.keys(budget).length === 0) {
+1
View File
@@ -13,6 +13,7 @@ export * from "./drivers/codex/app-server-transport.js";
export * from "./drivers/codex/codex-app-server-driver.js";
export * from "./protocol/replay-contract.js";
export * from "./protocol/replay-loader.js";
export * from "./protocol/semantic-tool-receipts.js";
export * from "./reducer/session-reducer.js";
export * from "./generated/capability-contract.js";
export * from "./semantic-tools/index.js";
@@ -120,6 +120,72 @@ export const capabilitiesSchema = {
"typedEvents": {
"type": "boolean"
},
"semanticTools": {
"type": "object",
"required": [
"schema",
"schemaVersion",
"operations"
],
"properties": {
"schema": {
"const": "paperclip.prp.semantic_tools.v1"
},
"schemaVersion": {
"const": 1
},
"operations": {
"type": "array",
"items": {
"type": "object",
"required": [
"operationId",
"version",
"availability",
"redactionDisposition"
],
"properties": {
"operationId": {
"type": "string",
"minLength": 1,
"maxLength": 160,
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
},
"version": {
"const": 1
},
"availability": {
"enum": [
"available",
"denied",
"unsupported"
]
},
"redactionDisposition": {
"const": "digest_only"
},
"requiredClaims": {
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 160
},
"uniqueItems": true
},
"reasonCode": {
"type": "string",
"minLength": 1,
"maxLength": 160,
"pattern": "^[a-z][a-z0-9_.:-]*$"
}
},
"additionalProperties": true
}
}
},
"additionalProperties": true
},
"unsupported": {
"type": "array",
"items": {
@@ -221,6 +287,393 @@ export const commandSchema = {
"additionalProperties": true
} as const;
export const semanticToolSchema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json",
"title": "PRP provider-neutral semantic tool envelope",
"type": "object",
"required": [
"schema",
"schemaVersion",
"phase",
"operationId",
"callId",
"correlation",
"idempotencyKey",
"content"
],
"properties": {
"schema": {
"const": "paperclip.prp.semantic_tool.v1"
},
"schemaVersion": {
"const": 1
},
"phase": {
"enum": [
"input",
"result"
]
},
"operationId": {
"$ref": "#/$defs/stableId"
},
"callId": {
"$ref": "#/$defs/stableId"
},
"correlation": {
"type": "object",
"required": [
"runId",
"normalizedSessionId",
"turnId",
"itemId"
],
"properties": {
"runId": {
"$ref": "#/$defs/stableId"
},
"normalizedSessionId": {
"$ref": "#/$defs/stableId"
},
"turnId": {
"$ref": "#/$defs/stableId"
},
"itemId": {
"$ref": "#/$defs/stableId"
},
"requestId": {
"$ref": "#/$defs/stableId"
}
},
"additionalProperties": true
},
"idempotencyKey": {
"type": [
"string",
"null"
],
"minLength": 1,
"maxLength": 240
},
"content": {
"$ref": "#/$defs/safeContent"
},
"outcome": {
"enum": [
"succeeded",
"denied",
"conflict",
"duplicate",
"unavailable",
"failed"
]
},
"code": {
"$ref": "#/$defs/stableCode"
},
"retryable": {
"type": "boolean"
},
"authorizationBoundary": {
"enum": [
"company",
"actor",
"active_task",
"grant",
"governed_action",
"lock",
"revision"
]
},
"operationReceiptId": {
"$ref": "#/$defs/stableId"
},
"auditReceiptId": {
"$ref": "#/$defs/stableId"
},
"currentRevision": {
"oneOf": [
{
"type": "integer",
"minimum": 0
},
{
"$ref": "#/$defs/stableId"
}
]
},
"duplicateOfReceiptId": {
"$ref": "#/$defs/stableId"
},
"artifactRefs": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/$defs/safeReference"
},
{
"type": "object",
"properties": {
"kind": {
"enum": [
"artifact",
"work_product"
]
}
}
}
]
},
"uniqueItems": true
},
"targets": {
"type": "array",
"items": {
"$ref": "#/$defs/immutableTarget"
},
"uniqueItems": true
},
"causalRefs": {
"type": "array",
"items": {
"$ref": "#/$defs/safeReference"
},
"uniqueItems": true
}
},
"allOf": [
{
"if": {
"properties": {
"phase": {
"const": "result"
}
}
},
"then": {
"required": [
"outcome",
"code",
"retryable",
"authorizationBoundary",
"operationReceiptId"
]
}
}
],
"additionalProperties": true,
"$defs": {
"stableId": {
"type": "string",
"minLength": 1,
"maxLength": 240,
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
},
"stableCode": {
"type": "string",
"minLength": 1,
"maxLength": 160,
"pattern": "^[a-z][a-z0-9_.:-]*$"
},
"safeContent": {
"type": "object",
"required": [
"digest",
"redactionDisposition",
"references"
],
"properties": {
"digest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"redactionDisposition": {
"enum": [
"digest_only",
"allowlisted_references",
"redacted"
]
},
"references": {
"type": "array",
"items": {
"$ref": "#/$defs/safeReference"
},
"uniqueItems": true
}
},
"additionalProperties": true
},
"safeReference": {
"type": "object",
"required": [
"kind",
"id"
],
"properties": {
"kind": {
"enum": [
"task",
"document_revision",
"interaction",
"approval",
"decision",
"artifact",
"work_product",
"wake",
"monitor",
"audit",
"operation"
]
},
"id": {
"$ref": "#/$defs/stableId"
}
},
"additionalProperties": true
},
"immutableTarget": {
"type": "object",
"required": [
"kind",
"id",
"immutable"
],
"properties": {
"kind": {
"enum": [
"document_revision",
"interaction",
"approval",
"decision"
]
},
"id": {
"$ref": "#/$defs/stableId"
},
"immutable": {
"const": true
},
"revisionId": {
"$ref": "#/$defs/stableId"
},
"decisionId": {
"$ref": "#/$defs/stableId"
}
},
"additionalProperties": true
}
}
} as const;
export const stopReasonSchema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://paperclip.dev/schemas/prp/v1/stop-reason.schema.json",
"title": "PRP terminal stop reason receipt",
"type": "object",
"required": [
"schema",
"schemaVersion",
"receiptId",
"kind",
"code",
"retryable",
"decisionId",
"limitClass",
"aggregate"
],
"properties": {
"schema": {
"const": "paperclip.prp.stop_reason.v1"
},
"schemaVersion": {
"const": 1
},
"receiptId": {
"$ref": "#/$defs/stableId"
},
"kind": {
"enum": [
"budget",
"cost"
]
},
"code": {
"type": "string",
"minLength": 1,
"maxLength": 160,
"pattern": "^[a-z][a-z0-9_.:-]*$"
},
"retryable": {
"type": "boolean"
},
"decisionId": {
"$ref": "#/$defs/stableId"
},
"limitClass": {
"enum": [
"company_monthly",
"actor_monthly",
"project_monthly",
"run_cost",
"provider_quota"
]
},
"aggregate": {
"type": "object",
"required": [
"unit",
"observed",
"limit",
"window"
],
"properties": {
"unit": {
"enum": [
"cents",
"usd_micros",
"tokens"
]
},
"observed": {
"type": "integer",
"minimum": 0
},
"limit": {
"type": "integer",
"minimum": 0
},
"window": {
"enum": [
"run",
"monthly_utc",
"provider_window"
]
}
},
"additionalProperties": true
},
"relatedReceiptIds": {
"type": "array",
"items": {
"$ref": "#/$defs/stableId"
},
"uniqueItems": true
}
},
"additionalProperties": true,
"$defs": {
"stableId": {
"type": "string",
"minLength": 1,
"maxLength": 240,
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$"
}
}
} as const;
export const terminalSchema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://paperclip.dev/schemas/prp/v1/terminal.schema.json",
@@ -266,6 +719,9 @@ export const terminalSchema = {
"statusDecisionId": {
"type": "string",
"minLength": 1
},
"stopReason": {
"$ref": "https://paperclip.dev/schemas/prp/v1/stop-reason.schema.json"
}
},
"additionalProperties": true
@@ -807,6 +1263,74 @@ export const eventSchema = {
}
},
"allOf": [
{
"if": {
"properties": {
"eventType": {
"const": "mcp_app.tool_input"
}
}
},
"then": {
"properties": {
"payload": {
"type": "object",
"properties": {
"semantic_tool": {
"allOf": [
{
"$ref": "https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json"
},
{
"type": "object",
"properties": {
"phase": {
"const": "input"
}
}
}
]
}
},
"additionalProperties": true
}
}
}
},
{
"if": {
"properties": {
"eventType": {
"const": "mcp_app.tool_result"
}
}
},
"then": {
"properties": {
"payload": {
"type": "object",
"properties": {
"semantic_tool": {
"allOf": [
{
"$ref": "https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json"
},
{
"type": "object",
"properties": {
"phase": {
"const": "result"
}
}
}
]
}
},
"additionalProperties": true
}
}
}
},
{
"if": {
"properties": {
@@ -913,12 +1437,14 @@ export const fixtureSchema = {
} as const;
export const prpSchemaBundle = {
identity: identitySchema,
capabilities: capabilitiesSchema,
command: commandSchema,
terminal: terminalSchema,
request: requestSchema,
result: resultSchema,
event: eventSchema,
fixture: fixtureSchema,
"identity": identitySchema,
"capabilities": capabilitiesSchema,
"command": commandSchema,
"semantic-tool": semanticToolSchema,
"stop-reason": stopReasonSchema,
"terminal": terminalSchema,
"request": requestSchema,
"result": resultSchema,
"event": eventSchema,
"fixture": fixtureSchema,
} as const;
@@ -1,6 +1,7 @@
// Generated by scripts/generate-protocol-schema-module.mjs. Do not edit.
// @ts-nocheck -- Ajv standalone output is JavaScript compiled from the checked-in schemas.
import equalModule from "ajv/dist/runtime/equal.js";
import ucs2LengthModule from "ajv/dist/runtime/ucs2length.js";
"use strict";export const fixtureValidator = validate20;const schema31 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/fixture.schema.json","title":"PRP scripted replay fixture","type":"object","required":["schema","fixtureVersion","protocolVersion","name","description","identity","capabilities","commands","events","result"],"properties":{"schema":{"const":"paperclip.prp.fixture.v1"},"fixtureVersion":{"const":1},"protocolVersion":{"const":1},"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","minLength":1,"maxLength":1000},"identity":{"$ref":"https://paperclip.dev/schemas/prp/v1/identity.schema.json"},"capabilities":{"$ref":"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json"},"commands":{"type":"array","items":{"$ref":"https://paperclip.dev/schemas/prp/v1/command.schema.json"}},"events":{"type":"array","minItems":1,"items":{"$ref":"https://paperclip.dev/schemas/prp/v1/event.schema.json"}},"requests":{"type":"array","items":{"$ref":"https://paperclip.dev/schemas/prp/v1/request.schema.json"}},"result":{"$ref":"https://paperclip.dev/schemas/prp/v1/result.schema.json"}},"additionalProperties":true};const schema41 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json","title":"PRP negotiated capabilities","type":"object","required":["schema","sessionReusePolicy","driver","steer","interrupt","resume","runtimeRequests","structuredResult","typedEvents"],"properties":{"schema":{"const":"paperclip.prp.capabilities.v1"},"sessionReusePolicy":{"enum":["new_per_run","reuse_per_issue","reuse_per_workspace"]},"driver":{"type":"object","required":["kind","version"],"properties":{"kind":{"type":"string","minLength":1,"maxLength":80},"version":{"type":"string","minLength":1,"maxLength":80}},"additionalProperties":true},"steer":{"type":"boolean"},"interrupt":{"type":"boolean"},"resume":{"type":"boolean"},"runtimeRequests":{"type":"boolean"},"structuredResult":{"type":"boolean"},"typedEvents":{"type":"boolean"},"unsupported":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true}},"additionalProperties":true};const schema42 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/command.schema.json","title":"PRP runner command","type":"object","required":["schema","commandId","controllerSeq","type","issuedAt","payload"],"properties":{"schema":{"const":"paperclip.prp.command.v1"},"commandId":{"type":"string","minLength":1,"maxLength":160},"controllerSeq":{"type":"integer","minimum":1},"type":{"enum":["run.prepare","session.open","turn.start","turn.steer","turn.interrupt","turn.stop","request.resolve","interaction.receipt","session.snapshot","session.close","run.cancel","runner.drain","runner.shutdown"]},"issuedAt":{"type":"string","format":"date-time"},"deadlineAt":{"type":"string","format":"date-time"},"precondition":{"type":"object","properties":{"runnerState":{"type":"array","items":{"type":"string"}},"runState":{"type":"array","items":{"type":"string"}},"sessionState":{"type":"array","items":{"type":"string"}},"activeTurnId":{"type":["string","null"]}},"additionalProperties":true},"payload":{"type":"object","additionalProperties":true}},"additionalProperties":true};const schema46 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/request.schema.json","title":"PRP request","oneOf":[{"type":"object","required":["schema","requestKind","requestId","type","status","prompt"],"properties":{"schema":{"const":"paperclip.runtime_request.v1"},"requestKind":{"const":"runtime"},"requestId":{"type":"string","minLength":1,"maxLength":160},"type":{"enum":["permission","input"]},"status":{"enum":["pending","resolved","expired","cancelled"]},"prompt":{"type":"string","minLength":1,"maxLength":4000},"choices":{"type":"array","items":{"type":"object","required":["key","label"],"properties":{"key":{"type":"string","minLength":1},"label":{"type":"string","minLength":1}},"additionalProperties":true}}},"additionalProperties":true},{"type":"object","required":["schema","requestKind","requestId","kind","blocking","payload"],"properties":{"schema":{"const":"paperclip.interaction_request.v1"},"requestKind":{"const":"issue_thread"},"requestId":{"type":"string","minLength":1,"maxLength":160},"kind":{"enum":["request_confirmation","request_checkbox_confirmation","request_item_verdicts","ask_user_questions","suggest_tasks"]},"blocking":{"type":"boolean"},"payload":{"type":"object","additionalProperties":true}},"additionalProperties":true}]};const schema45 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/result.schema.json","title":"PRP structured run result","type":"object","required":["schema","reportedWorkDisposition","summary","completionClaim","evidence","verification","attentionRequests","artifacts"],"pLine truncated
"use strict";export const fixtureValidator = validate20;const schema31 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/fixture.schema.json","title":"PRP scripted replay fixture","type":"object","required":["schema","fixtureVersion","protocolVersion","name","description","identity","capabilities","commands","events","result"],"properties":{"schema":{"const":"paperclip.prp.fixture.v1"},"fixtureVersion":{"const":1},"protocolVersion":{"const":1},"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","minLength":1,"maxLength":1000},"identity":{"$ref":"https://paperclip.dev/schemas/prp/v1/identity.schema.json"},"capabilities":{"$ref":"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json"},"commands":{"type":"array","items":{"$ref":"https://paperclip.dev/schemas/prp/v1/command.schema.json"}},"events":{"type":"array","minItems":1,"items":{"$ref":"https://paperclip.dev/schemas/prp/v1/event.schema.json"}},"requests":{"type":"array","items":{"$ref":"https://paperclip.dev/schemas/prp/v1/request.schema.json"}},"result":{"$ref":"https://paperclip.dev/schemas/prp/v1/result.schema.json"}},"additionalProperties":true};const schema41 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/capabilities.schema.json","title":"PRP negotiated capabilities","type":"object","required":["schema","sessionReusePolicy","driver","steer","interrupt","resume","runtimeRequests","structuredResult","typedEvents"],"properties":{"schema":{"const":"paperclip.prp.capabilities.v1"},"sessionReusePolicy":{"enum":["new_per_run","reuse_per_issue","reuse_per_workspace"]},"driver":{"type":"object","required":["kind","version"],"properties":{"kind":{"type":"string","minLength":1,"maxLength":80},"version":{"type":"string","minLength":1,"maxLength":80}},"additionalProperties":true},"steer":{"type":"boolean"},"interrupt":{"type":"boolean"},"resume":{"type":"boolean"},"runtimeRequests":{"type":"boolean"},"structuredResult":{"type":"boolean"},"typedEvents":{"type":"boolean"},"semanticTools":{"type":"object","required":["schema","schemaVersion","operations"],"properties":{"schema":{"const":"paperclip.prp.semantic_tools.v1"},"schemaVersion":{"const":1},"operations":{"type":"array","items":{"type":"object","required":["operationId","version","availability","redactionDisposition"],"properties":{"operationId":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]*$"},"version":{"const":1},"availability":{"enum":["available","denied","unsupported"]},"redactionDisposition":{"const":"digest_only"},"requiredClaims":{"type":"array","items":{"type":"string","minLength":1,"maxLength":160},"uniqueItems":true},"reasonCode":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[a-z][a-z0-9_.:-]*$"}},"additionalProperties":true}}},"additionalProperties":true},"unsupported":{"type":"array","items":{"type":"string","minLength":1},"uniqueItems":true}},"additionalProperties":true};const schema42 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/command.schema.json","title":"PRP runner command","type":"object","required":["schema","commandId","controllerSeq","type","issuedAt","payload"],"properties":{"schema":{"const":"paperclip.prp.command.v1"},"commandId":{"type":"string","minLength":1,"maxLength":160},"controllerSeq":{"type":"integer","minimum":1},"type":{"enum":["run.prepare","session.open","turn.start","turn.steer","turn.interrupt","turn.stop","request.resolve","interaction.receipt","session.snapshot","session.close","run.cancel","runner.drain","runner.shutdown"]},"issuedAt":{"type":"string","format":"date-time"},"deadlineAt":{"type":"string","format":"date-time"},"precondition":{"type":"object","properties":{"runnerState":{"type":"array","items":{"type":"string"}},"runState":{"type":"array","items":{"type":"string"}},"sessionState":{"type":"array","items":{"type":"string"}},"activeTurnId":{"type":["string","null"]}},"additionalProperties":true},"payload":{"type":"object","additionalProperties":true}},"additionalProperties":true};const schema70 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://paperclip.dev/schemas/prp/v1/request.schema.json","title":"PRP request","oneOf":[{"type":"object","required":["schema","requestKind","requestId","type","status","prompt"],"properties":{"schema":{"const":"paperclip.runtime_request.v1"},"requestKind":{"const":"runtime"},"requestId":{"type":"string","minLength":1,"maxLength":160},"type":{"enum":["permission","input"]},"status":{"enum":["pending","resolved","expired","cancelled"]},"prompt":{"type":"string","minLength":1,"maxLength":4000},"choices":{"type":"array","items":{"type":"object","required":["key","label"],"properties":{"key":{"type":"string","minLength":1},"label":{"type":"string","minLength":1}},"additionalProperties":true}}},"additionalProperties":true},{"type":"object","rLine truncated
@@ -64,4 +64,13 @@ describe("PRP v1 expressiveness crosswalk", () => {
}
}
});
it("has no unlanded additive receipt or fixture/document gap", () => {
expect(
crosswalk.facts.filter((fact) =>
fact.classification === "additive_v1_change"
|| fact.classification === "missing_fixture_docs",
),
).toEqual([]);
});
});
@@ -7,6 +7,7 @@ import {
parsePrpFixtureText,
PRP_PROTOCOL_VERSION,
} from "./replay-contract.js";
import { reducePrpFixture } from "../reducer/session-reducer.js";
const fixtureDirectory = new URL("../../protocol/fixtures/replay/", import.meta.url);
const validFixtures = [
@@ -16,6 +17,12 @@ const validFixtures = [
"duplicate-event.json",
"source-gap.json",
"unknown-optional-fields.json",
"semantic-tool-artifact-happy-path.json",
"semantic-tool-denial-redaction.json",
"semantic-tool-conflict-duplicate-retry.json",
"semantic-tool-governance-wake-monitor.json",
"budget-cost-stop-reason.json",
"semantic-tool-unknown-optional-envelope.json",
];
async function readFixture(name = "happy-path.json"): Promise<Record<string, unknown>> {
@@ -62,6 +69,174 @@ describe("PRP v1 JSON Schema contract", () => {
});
});
it("fails closed on an unsupported required semantic-tool version", async () => {
const result = parsePrpFixtureText(
await readFile(
new URL("semantic-tool-unsupported-required-version.json", fixtureDirectory),
"utf8",
),
);
expect(result).toMatchObject({
ok: false,
issues: [
{
code: "unsupported_required_version",
path: "/events/0/payload/semantic_tool/schemaVersion",
},
],
});
});
it("records an artifact receipt without projecting semantic-tool payload", async () => {
const result = parsePrpFixtureText(
await readFile(
new URL("semantic-tool-artifact-happy-path.json", fixtureDirectory),
"utf8",
),
);
expect(result.ok).toBe(true);
if (!result.ok) return;
const semantic = result.fixture.events[1]?.payload.semantic_tool;
expect(semantic).toMatchObject({
phase: "result",
operationId: "register_deliverable",
outcome: "succeeded",
operationReceiptId: "receipt_semantic_happy",
artifactRefs: [
{ kind: "artifact", id: "artifact_semantic_happy" },
{ kind: "work_product", id: "work_product_semantic_happy" },
],
});
});
it("records a redacted denial with no fallback semantic call", async () => {
const result = parsePrpFixtureText(
await readFile(
new URL("semantic-tool-denial-redaction.json", fixtureDirectory),
"utf8",
),
);
expect(result.ok).toBe(true);
if (!result.ok) return;
const semanticEvents = result.fixture.events.filter(
(event) => event.payload.semantic_tool !== undefined,
);
expect(semanticEvents).toHaveLength(2);
expect(semanticEvents.map((event) => event.payload.semantic_tool?.operationId)).toEqual([
"decide_approval",
"decide_approval",
]);
expect(semanticEvents[1]?.payload.semantic_tool).toMatchObject({
outcome: "denied",
code: "required_claim_missing",
authorizationBoundary: "grant",
content: { redactionDisposition: "redacted" },
});
expect(JSON.stringify(semanticEvents)).not.toContain("Bearer ");
});
it("links a stale conflict to an exact duplicate retry receipt", async () => {
const result = parsePrpFixtureText(
await readFile(
new URL("semantic-tool-conflict-duplicate-retry.json", fixtureDirectory),
"utf8",
),
);
expect(result.ok).toBe(true);
if (!result.ok) return;
const receipts = result.fixture.events
.filter((event) => event.eventType === "mcp_app.tool_result")
.map((event) => event.payload.semantic_tool);
expect(receipts[0]).toMatchObject({
outcome: "conflict",
currentRevision: "revision_plan_2",
retryable: true,
});
expect(receipts[2]).toMatchObject({
outcome: "duplicate",
operationReceiptId: receipts[1]?.operationReceiptId,
duplicateOfReceiptId: receipts[1]?.operationReceiptId,
});
});
it("deduplicates an at-least-once semantic source delivery before pair binding", async () => {
const fixture = await readFixture("semantic-tool-artifact-happy-path.json");
const events = fixture.events as Array<Record<string, unknown>>;
events.splice(1, 0, structuredClone(events[0]!));
expect(parsePrpFixtureText(JSON.stringify(fixture))).toMatchObject({ ok: true });
});
it("binds immutable interaction and approval targets to a wake/monitor chain", async () => {
const result = parsePrpFixtureText(
await readFile(
new URL("semantic-tool-governance-wake-monitor.json", fixtureDirectory),
"utf8",
),
);
expect(result.ok).toBe(true);
if (!result.ok) return;
const receipt = result.fixture.events[1]?.payload.semantic_tool;
expect(receipt?.targets).toEqual([
{
kind: "interaction",
id: "interaction_semantic_governance",
immutable: true,
revisionId: "revision_plan_7",
},
{
kind: "approval",
id: "approval_semantic_governance",
immutable: true,
decisionId: "decision_approval_semantic_governance",
},
]);
expect(receipt?.causalRefs?.map((reference) => reference.kind)).toEqual([
"document_revision",
"interaction",
"approval",
"decision",
"wake",
"monitor",
]);
});
it("carries a budget/cost stop receipt on the terminal event", async () => {
const result = parsePrpFixtureText(
await readFile(new URL("budget-cost-stop-reason.json", fixtureDirectory), "utf8"),
);
expect(result.ok).toBe(true);
if (!result.ok) return;
const terminal = result.fixture.events.find(
(event) => event.eventType === "run.terminal",
);
expect(terminal?.payload.stopReason).toMatchObject({
kind: "budget",
code: "budget_hard_stop",
decisionId: "budget_decision_stop",
aggregate: { unit: "cents", observed: 10000, limit: 10000 },
});
});
it("accepts unknown optional semantic fields without changing projection", async () => {
const result = parsePrpFixtureText(
await readFile(
new URL("semantic-tool-unknown-optional-envelope.json", fixtureDirectory),
"utf8",
),
);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.fixture.events[0]?.payload.semantic_tool?.futureEnvelopeHint).toEqual({
version: "1.1-preview",
});
const withoutEnvelope = structuredClone(result.fixture);
for (const event of withoutEnvelope.events) {
delete event.payload.semantic_tool;
}
expect(reducePrpFixture(result.fixture)).toEqual(reducePrpFixture(withoutEnvelope));
});
it("fails closed on unsupported nested required schema versions", async () => {
const fixture = await readFixture();
const events = fixture.events as Array<Record<string, unknown>>;
@@ -8,6 +8,8 @@ import {
identitySchema,
requestSchema,
resultSchema,
semanticToolSchema,
stopReasonSchema,
terminalSchema,
} from "./generated/schema-bundle.js";
import {
@@ -20,11 +22,22 @@ export const PRP_PROTOCOL_NAME = "paperclip.runner";
export const PRP_PROTOCOL_VERSION = 1;
export const PRP_FIXTURE_SCHEMA = "paperclip.prp.fixture.v1";
type EventReferences = [typeof terminalSchema, typeof resultSchema];
type TerminalReferences = [typeof stopReasonSchema];
type EventReferences = [
typeof semanticToolSchema,
typeof stopReasonSchema,
typeof terminalSchema,
typeof resultSchema,
];
export type PrpIdentity = FromSchema<typeof identitySchema>;
export type PrpCapabilities = FromSchema<typeof capabilitiesSchema>;
export type PrpCommand = FromSchema<typeof commandSchema>;
export type PrpTerminalState = FromSchema<typeof terminalSchema>;
export type PrpSemanticToolEnvelope = FromSchema<typeof semanticToolSchema>;
export type PrpStopReason = FromSchema<typeof stopReasonSchema>;
export type PrpTerminalState = FromSchema<
typeof terminalSchema,
{ references: TerminalReferences }
>;
export type PrpRequest = FromSchema<typeof requestSchema>;
export type PrpStructuredRunResult = FromSchema<typeof resultSchema>;
export type PrpEvent = FromSchema<
@@ -127,6 +140,35 @@ function versionIssues(value: unknown): ProtocolValidationIssue[] {
message: `event schemaVersion ${actual} is unsupported; this implementation requires 1`,
});
}
const payload = asRecord(event?.payload);
const semanticTool = asRecord(payload?.semantic_tool);
const semanticToolVersion = semanticTool?.schemaVersion;
if (typeof semanticToolVersion === "number" && semanticToolVersion !== 1) {
issues.push({
code: "unsupported_required_version",
path: `/events/${index}/payload/semantic_tool/schemaVersion`,
message: `semantic_tool schemaVersion ${semanticToolVersion} is unsupported; this implementation requires 1`,
});
}
const stopReason = asRecord(payload?.stopReason);
const stopReasonVersion = stopReason?.schemaVersion;
if (typeof stopReasonVersion === "number" && stopReasonVersion !== 1) {
issues.push({
code: "unsupported_required_version",
path: `/events/${index}/payload/stopReason/schemaVersion`,
message: `stopReason schemaVersion ${stopReasonVersion} is unsupported; this implementation requires 1`,
});
}
});
}
const capabilities = asRecord(fixture.capabilities);
const semanticTools = asRecord(capabilities?.semanticTools);
const semanticToolsVersion = semanticTools?.schemaVersion;
if (typeof semanticToolsVersion === "number" && semanticToolsVersion !== 1) {
issues.push({
code: "unsupported_required_version",
path: "/capabilities/semanticTools/schemaVersion",
message: `semanticTools schemaVersion ${semanticToolsVersion} is unsupported; this implementation requires 1`,
});
}
return issues;
@@ -143,6 +185,10 @@ function ajvIssue(error: ErrorObject): ProtocolValidationIssue {
function bindingIssues(fixture: PrpFixture): ProtocolValidationIssue[] {
const issues: ProtocolValidationIssue[] = [];
const uniqueEvents = new Map<string, PrpEvent>();
const semanticCalls = new Map<
string,
{ input?: { envelope: PrpSemanticToolEnvelope; index: number }; result?: { envelope: PrpSemanticToolEnvelope; index: number } }
>();
fixture.events.forEach((event, index) => {
if (event.runId !== fixture.identity.runId) {
issues.push({
@@ -164,15 +210,71 @@ function bindingIssues(fixture: PrpFixture): ProtocolValidationIssue[] {
const existing = uniqueEvents.get(event.sourceEventId);
if (existing === undefined) {
uniqueEvents.set(event.sourceEventId, event);
} else if (canonicalJson(existing) !== canonicalJson(event)) {
issues.push({
code: "binding_mismatch",
path: `/events/${index}/sourceEventId`,
message: "duplicate sourceEventId deliveries must be byte-equivalent",
});
} else {
if (canonicalJson(existing) !== canonicalJson(event)) {
issues.push({
code: "binding_mismatch",
path: `/events/${index}/sourceEventId`,
message: "duplicate sourceEventId deliveries must be byte-equivalent",
});
}
// At-least-once source delivery is not a second semantic invocation.
return;
}
const payload = asRecord(event.payload);
const semanticTool = asRecord(payload?.semantic_tool) as PrpSemanticToolEnvelope | null;
if (semanticTool !== null) {
const correlation = asRecord(semanticTool.correlation);
for (const [field, actual, expected] of [
["runId", correlation?.runId, event.runId],
["normalizedSessionId", correlation?.normalizedSessionId, event.normalizedSessionId],
["turnId", correlation?.turnId, event.turnId],
["itemId", correlation?.itemId, event.itemId],
] as const) {
if (actual !== expected) {
issues.push({
code: "binding_mismatch",
path: `/events/${index}/payload/semantic_tool/correlation/${field}`,
message: `semantic_tool correlation ${field} must match the containing event`,
});
}
}
const call = semanticCalls.get(semanticTool.callId) ?? {};
const phase = semanticTool.phase;
if (call[phase] !== undefined) {
issues.push({
code: "binding_mismatch",
path: `/events/${index}/payload/semantic_tool/callId`,
message: `semantic_tool call ${semanticTool.callId} must contain exactly one ${phase} envelope`,
});
} else {
call[phase] = { envelope: semanticTool, index };
semanticCalls.set(semanticTool.callId, call);
}
}
});
for (const [callId, call] of semanticCalls) {
if (call.input === undefined || call.result === undefined) {
const present = call.input ?? call.result;
issues.push({
code: "binding_mismatch",
path: `/events/${present?.index ?? 0}/payload/semantic_tool/callId`,
message: `semantic_tool call ${callId} must contain one input and one result envelope`,
});
continue;
}
for (const field of ["operationId", "idempotencyKey"] as const) {
if (canonicalJson(call.input.envelope[field]) !== canonicalJson(call.result.envelope[field])) {
issues.push({
code: "binding_mismatch",
path: `/events/${call.result.index}/payload/semantic_tool/${field}`,
message: `semantic_tool result ${field} must match its input envelope`,
});
}
}
}
fixture.commands.forEach((command, index) => {
if (command.controllerSeq !== index + 1) {
issues.push({
@@ -0,0 +1,121 @@
import { describe, expect, it } from "vitest";
import { validatePrpEvent, type PrpEvent } from "./replay-contract.js";
import {
createPrpBudgetStopReason,
createPrpSemanticToolInputEnvelope,
createPrpSemanticToolResultEnvelope,
semanticAuthorizationBoundaryForCode,
} from "./semantic-tool-receipts.js";
const correlation = {
runId: "run_receipt_test",
normalizedSessionId: "session_receipt_test",
turnId: "turn_receipt_test",
itemId: "item_receipt_test",
};
describe("PRP provider-neutral semantic receipts", () => {
it("digests raw content and retains only allowlisted references", () => {
const envelope = createPrpSemanticToolInputEnvelope({
operationId: "get_task_context",
callId: "call_receipt_test",
correlation,
content: { authorization: "Bearer secret-token", apiKey: "do-not-emit" },
references: [{ kind: "task", id: "issue_receipt_test" }],
});
expect(envelope.content).toMatchObject({
digest: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
redactionDisposition: "digest_only",
references: [{ kind: "task", id: "issue_receipt_test" }],
});
expect(JSON.stringify(envelope)).not.toContain("secret-token");
expect(JSON.stringify(envelope)).not.toContain("do-not-emit");
});
it("gives exact retries the same operation receipt without provider identifiers", () => {
const base = {
operationId: "write_document",
correlation,
idempotencyKey: "write_once",
content: { revisionId: "revision_1" },
outcome: "succeeded" as const,
code: "ok",
retryable: false,
authorizationBoundary: "revision" as const,
};
const first = createPrpSemanticToolResultEnvelope({ ...base, callId: "call_first" });
const retry = createPrpSemanticToolResultEnvelope({ ...base, callId: "call_retry" });
expect(retry.operationReceiptId).toBe(first.operationReceiptId);
expect(retry.operationReceiptId).toMatch(/^semantic_receipt:[a-f0-9]{32}$/);
expect(retry).not.toHaveProperty("provider");
});
it("validates semantic input/result events and a terminal stop receipt", () => {
const input = createPrpSemanticToolInputEnvelope({
operationId: "get_task_context",
callId: "call_receipt_test",
correlation,
content: {},
});
const result = createPrpSemanticToolResultEnvelope({
operationId: "get_task_context",
callId: "call_receipt_test",
correlation,
content: {},
outcome: "succeeded",
code: "ok",
retryable: false,
authorizationBoundary: "active_task",
});
expect(validatePrpEvent(event("mcp_app.tool_input", input)).ok).toBe(true);
expect(validatePrpEvent(event("mcp_app.tool_result", result)).ok).toBe(true);
expect(
createPrpBudgetStopReason({
receiptId: "stop_receipt_test",
kind: "cost",
code: "run_cost_limit",
retryable: true,
decisionId: "decision_receipt_test",
limitClass: "run_cost",
aggregate: { unit: "usd_micros", observed: 100, limit: 100, window: "run" },
}),
).toMatchObject({
schema: "paperclip.prp.stop_reason.v1",
kind: "cost",
decisionId: "decision_receipt_test",
});
});
it("maps denial and conflict codes onto protocol authorization boundaries", () => {
expect(semanticAuthorizationBoundaryForCode("company_scope_denied")).toBe("company");
expect(semanticAuthorizationBoundaryForCode("actor_role_denied")).toBe("actor");
expect(semanticAuthorizationBoundaryForCode("document_revision_conflict")).toBe("revision");
expect(semanticAuthorizationBoundaryForCode("approval_required")).toBe("governed_action");
});
});
function event(
eventType: "mcp_app.tool_input" | "mcp_app.tool_result",
semanticTool: ReturnType<typeof createPrpSemanticToolInputEnvelope>,
): PrpEvent {
return {
schema: "paperclip.prp.event.v1",
sourceEventId: `${eventType}:receipt_test`,
sourceSeq: eventType === "mcp_app.tool_input" ? 1 : 2,
sourceInstanceId: "runner_receipt_test",
sourceKind: "runner",
runId: correlation.runId,
normalizedSessionId: correlation.normalizedSessionId,
turnId: correlation.turnId,
itemId: correlation.itemId,
eventType,
schemaVersion: 1,
priority: 1,
emittedAt: "2026-08-11T12:00:00.000Z",
payload: { semantic_tool: semanticTool },
} as PrpEvent;
}
@@ -0,0 +1,239 @@
import { createHash } from "node:crypto";
import type {
PrpEvent,
PrpSemanticToolEnvelope,
PrpStopReason,
} from "./replay-contract.js";
export type PrpSemanticAuthorizationBoundary =
| "company"
| "actor"
| "active_task"
| "grant"
| "governed_action"
| "lock"
| "revision";
export type PrpSemanticToolOutcome =
| "succeeded"
| "denied"
| "conflict"
| "duplicate"
| "unavailable"
| "failed";
export interface PrpSemanticCorrelation {
runId: string;
normalizedSessionId: string;
turnId: string;
itemId: string;
requestId?: string;
}
export interface PrpSemanticSafeReference {
kind:
| "task"
| "document_revision"
| "interaction"
| "approval"
| "decision"
| "artifact"
| "work_product"
| "wake"
| "monitor"
| "audit"
| "operation";
id: string;
}
export interface PrpSemanticImmutableTarget {
kind: "document_revision" | "interaction" | "approval" | "decision";
id: string;
immutable: true;
revisionId?: string;
decisionId?: string;
}
interface SemanticEnvelopeBaseInput {
operationId: string;
callId: string;
correlation: PrpSemanticCorrelation;
idempotencyKey?: string | null;
content: unknown;
references?: readonly PrpSemanticSafeReference[];
redactionDisposition?: "digest_only" | "allowlisted_references" | "redacted";
}
export interface CreatePrpSemanticToolResultInput extends SemanticEnvelopeBaseInput {
outcome: PrpSemanticToolOutcome;
code: string;
retryable: boolean;
authorizationBoundary: PrpSemanticAuthorizationBoundary;
operationReceiptId?: string;
auditReceiptId?: string;
currentRevision?: string | number;
duplicateOfReceiptId?: string;
artifactRefs?: readonly PrpSemanticSafeReference[];
targets?: readonly PrpSemanticImmutableTarget[];
causalRefs?: readonly PrpSemanticSafeReference[];
}
/**
* Creates a safe input envelope: raw content is reduced to a canonical digest,
* while callers may add only typed, non-secret references.
*/
export function createPrpSemanticToolInputEnvelope(
input: SemanticEnvelopeBaseInput,
): PrpSemanticToolEnvelope {
return {
schema: "paperclip.prp.semantic_tool.v1",
schemaVersion: 1,
phase: "input",
operationId: input.operationId,
callId: input.callId,
correlation: { ...input.correlation },
idempotencyKey: input.idempotencyKey ?? null,
content: safeContent(input),
} as PrpSemanticToolEnvelope;
}
/** Creates the provider-neutral semantic result receipt carried on PRP. */
export function createPrpSemanticToolResultEnvelope(
input: CreatePrpSemanticToolResultInput,
): PrpSemanticToolEnvelope {
const operationReceiptId = input.operationReceiptId ?? derivedReceiptId(input);
return {
schema: "paperclip.prp.semantic_tool.v1",
schemaVersion: 1,
phase: "result",
operationId: input.operationId,
callId: input.callId,
correlation: { ...input.correlation },
idempotencyKey: input.idempotencyKey ?? null,
content: safeContent(input),
outcome: input.outcome,
code: input.code,
retryable: input.retryable,
authorizationBoundary: input.authorizationBoundary,
operationReceiptId,
...(input.auditReceiptId === undefined ? {} : { auditReceiptId: input.auditReceiptId }),
...(input.currentRevision === undefined ? {} : { currentRevision: input.currentRevision }),
...(input.duplicateOfReceiptId === undefined
? {}
: { duplicateOfReceiptId: input.duplicateOfReceiptId }),
...(input.artifactRefs === undefined ? {} : { artifactRefs: [...input.artifactRefs] }),
...(input.targets === undefined ? {} : { targets: [...input.targets] }),
...(input.causalRefs === undefined ? {} : { causalRefs: [...input.causalRefs] }),
} as PrpSemanticToolEnvelope;
}
export function createPrpBudgetStopReason(input: {
receiptId: string;
kind: "budget" | "cost";
code: string;
retryable: boolean;
decisionId: string;
limitClass: "company_monthly" | "actor_monthly" | "project_monthly" | "run_cost" | "provider_quota";
aggregate: {
unit: "cents" | "usd_micros" | "tokens";
observed: number;
limit: number;
window: "run" | "monthly_utc" | "provider_window";
};
relatedReceiptIds?: readonly string[];
}): PrpStopReason {
return {
schema: "paperclip.prp.stop_reason.v1",
schemaVersion: 1,
receiptId: input.receiptId,
kind: input.kind,
code: input.code,
retryable: input.retryable,
decisionId: input.decisionId,
limitClass: input.limitClass,
aggregate: { ...input.aggregate },
...(input.relatedReceiptIds === undefined
? {}
: { relatedReceiptIds: [...input.relatedReceiptIds] }),
} as PrpStopReason;
}
/** Returns only typed result receipts; optional provider payload stays ignored. */
export function prpSemanticToolResultReceipts(
events: readonly PrpEvent[],
): PrpSemanticToolEnvelope[] {
return events.flatMap((event) => {
if (event.eventType !== "mcp_app.tool_result") return [];
const payload = asRecord(event.payload);
const envelope = asRecord(payload?.semantic_tool);
if (
envelope?.schema !== "paperclip.prp.semantic_tool.v1"
|| envelope.schemaVersion !== 1
|| envelope.phase !== "result"
) return [];
return [structuredClone(envelope) as PrpSemanticToolEnvelope];
});
}
export function semanticAuthorizationBoundaryForCode(
code: string,
): PrpSemanticAuthorizationBoundary {
if (code.includes("company")) return "company";
if (code.includes("actor") || code.includes("role")) return "actor";
if (code.includes("claim") || code.includes("exposed")) return "grant";
if (code.includes("revision") || code.includes("idempotency_conflict")) return "revision";
if (code.includes("lock") || code.includes("ownership")) return "lock";
if (code.includes("approval") || code.includes("interaction") || code.includes("governed")) {
return "governed_action";
}
return "active_task";
}
export function semanticOutcomeForCode(input: {
ok: boolean;
code: string;
disposition?: unknown;
}): PrpSemanticToolOutcome {
if (input.ok) return input.disposition === "duplicate" ? "duplicate" : "succeeded";
if (input.code.includes("conflict") || input.code.includes("revision")) return "conflict";
if (input.code.includes("unavailable") || input.code.includes("not_exposed")) return "unavailable";
return "denied";
}
export function digestPrpSemanticContent(value: unknown): string {
return `sha256:${createHash("sha256").update(canonicalJson(value)).digest("hex")}`;
}
function safeContent(input: SemanticEnvelopeBaseInput) {
return {
digest: digestPrpSemanticContent(input.content),
redactionDisposition: input.redactionDisposition ?? "digest_only",
references: [...(input.references ?? [])],
};
}
function derivedReceiptId(input: CreatePrpSemanticToolResultInput): string {
const identity = input.idempotencyKey === undefined || input.idempotencyKey === null
? `${input.correlation.runId}:${input.operationId}:${input.callId}`
: `${input.correlation.runId}:${input.operationId}:${input.idempotencyKey}`;
return `semantic_receipt:${createHash("sha256").update(identity).digest("hex").slice(0, 32)}`;
}
function asRecord(value: unknown): Record<string, unknown> | null {
return typeof value === "object" && value !== null && !Array.isArray(value)
? value as Record<string, unknown>
: null;
}
function canonicalJson(value: unknown): string {
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
const record = asRecord(value);
if (record !== null) {
return `{${Object.keys(record)
.sort()
.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
.join(",")}}`;
}
return JSON.stringify(value) ?? "null";
}
@@ -22,6 +22,12 @@ const fixtureNames = [
"duplicate-event",
"source-gap",
"unknown-optional-fields",
"semantic-tool-artifact-happy-path",
"semantic-tool-denial-redaction",
"semantic-tool-conflict-duplicate-retry",
"semantic-tool-governance-wake-monitor",
"budget-cost-stop-reason",
"semantic-tool-unknown-optional-envelope",
];
async function loadFixture(name: string): Promise<PrpFixture> {
@@ -95,7 +95,13 @@ export class PaperclipProductionSemanticConformanceAdapter implements SemanticCo
input: vector.input,
});
const after = world.port.snapshot();
return normalizeCapabilitySemanticObservation({ result, before, after, taskId: world.taskId });
return normalizeCapabilitySemanticObservation({
result,
before,
after,
taskId: world.taskId,
semanticInput: vector.input,
});
}
}
@@ -94,5 +94,7 @@ describeEmbedded("Paperclip semantic mock/production conformance", () => {
.toMatchObject({ interactions: [{ continuationPolicy: "wake_assignee" }] });
expect(report.rows.find((row) => row.vectorId === "terminal-finish")?.observation.state)
.toMatchObject({ task: { status: "done" } });
expect(report.rows.every((row) => row.observation.receipt?.operationReceiptPresent === true))
.toBe(true);
}, 30_000);
});