mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-30 13:20:54 +01:00
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:
1 parent
3cf6db82c1
commit
90caa73b95
49 files changed
+3816
-76
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
+126
@@ -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"
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"runId": "run_budget_stop",
|
||||
"integrity": "complete",
|
||||
"timelineCount": 2,
|
||||
"duplicateEventIds": [],
|
||||
"gaps": [],
|
||||
"turnTerminalState": "cancelled",
|
||||
"runTerminalState": "cancelled"
|
||||
}
|
||||
+136
@@ -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"
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"runId": "run_semantic_happy",
|
||||
"integrity": "complete",
|
||||
"timelineCount": 4,
|
||||
"duplicateEventIds": [],
|
||||
"gaps": [],
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded"
|
||||
}
|
||||
+172
@@ -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"
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"runId": "run_semantic_retry",
|
||||
"integrity": "complete",
|
||||
"timelineCount": 8,
|
||||
"duplicateEventIds": [],
|
||||
"gaps": [],
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded"
|
||||
}
|
||||
+137
@@ -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"
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"runId": "run_semantic_denied",
|
||||
"integrity": "complete",
|
||||
"timelineCount": 4,
|
||||
"duplicateEventIds": [],
|
||||
"gaps": [],
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded"
|
||||
}
|
||||
+139
@@ -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"
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"runId": "run_semantic_governance",
|
||||
"integrity": "complete",
|
||||
"timelineCount": 4,
|
||||
"duplicateEventIds": [],
|
||||
"gaps": [],
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded"
|
||||
}
|
||||
+133
@@ -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"
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"runId": "run_semantic_future",
|
||||
"integrity": "complete",
|
||||
"timelineCount": 4,
|
||||
"duplicateEventIds": [],
|
||||
"gaps": [],
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded"
|
||||
}
|
||||
+186
@@ -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" }]
|
||||
}
|
||||
}
|
||||
+120
@@ -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": []
|
||||
}
|
||||
}
|
||||
+102
@@ -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": []
|
||||
}
|
||||
}
|
||||
+90
@@ -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" }
|
||||
}
|
||||
}
|
||||
+72
@@ -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": []
|
||||
}
|
||||
}
|
||||
+55
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
Reference in new issue
Block a user