Add durable semantic tool receipts (#12353)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Semantic tools cross a trust boundary between a provider and the control plane > - Durable runs need exact input, result, denial, duplicate, and reconciliation receipts > - Replay must reject unsupported required versions and mismatched receipt pairs > - This pull request adds the receipt builders and deterministic replay fixtures > - It keeps newer sequence and gap safety limits from the current stack > - The benefit is auditable semantic activity before more providers use it ## Linked Issues or Issue Description **Subsystem affected** packages/paperclip-runner **Problem or motivation** Semantic tool calls have basic authorization records, but durable replay does not yet cover reconciled calls, denial redaction, duplicate receipts, governance targets, or artifact references. **Proposed solution** Add bounded semantic receipt builders, a reconciled phase, strict pair binding, fail-closed version checks, and generated replay oracles for the important lifecycle cases. **Alternatives considered** The runner could store provider-native tool payloads. That would weaken protocol portability and make redaction and retry behavior provider-specific. **Roadmap alignment** This supports the existing experimental Paperclip Runner rollout. It does not enable a production adapter. ## What Changed - Add semantic input and result receipt builders. - Add optional reconciliation receipts for pending calls. - Reject unsupported semantic receipt versions. - Validate receipt correlation, operation, idempotency, and digest bindings. - Add deterministic replay fixtures and generated golden outputs. ## Verification - `pnpm --filter @paperclipai/paperclip-runner test:typescript` - `pnpm --filter @paperclipai/paperclip-runner typecheck:typescript` - `pnpm -r typecheck` - `pnpm build` - Replay golden and protocol manifest checks pass. - The branch changes 27 files relative to its declared base. ## Risks The main risk is accepting a receipt that belongs to another call or replaying a duplicate as a new mutation. Binding checks compare correlation, operation, idempotency, and content digest fields. Fixtures cover denials, duplicates, governance chains, optional fields, artifacts, and unsupported versions. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI Codex, `gpt-5`, with agentic reasoning, tool use, and code execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either linked an existing public issue or described the issue in-PR - [x] I have not referenced internal or instance-local Paperclip issues or links - [x] My branch name describes the change and contains no internal Paperclip ticket id - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
This commit is contained in:
parent
d18e281c33
commit
e18632ebcb
|
|
@ -0,0 +1,136 @@
|
|||
{
|
||||
"schema": "paperclip.prp.session-snapshot.v1",
|
||||
"fixtureName": "Semantic tool artifact happy path",
|
||||
"identity": {
|
||||
"schema": "paperclip.prp.identity.v1",
|
||||
"companyId": "company_semantic_happy",
|
||||
"issueId": "issue_semantic_happy",
|
||||
"runId": "run_semantic_happy",
|
||||
"environmentLeaseId": "lease_semantic_happy",
|
||||
"runnerInstanceId": "runner_semantic_happy",
|
||||
"normalizedSessionId": "session_semantic_happy",
|
||||
"driverSessionId": "driver_semantic_happy"
|
||||
},
|
||||
"capabilities": {
|
||||
"schema": "paperclip.prp.capabilities.v1",
|
||||
"sessionReusePolicy": "new_per_run",
|
||||
"driver": {
|
||||
"kind": "provider-neutral",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"steer": true,
|
||||
"interrupt": true,
|
||||
"resume": true,
|
||||
"runtimeRequests": true,
|
||||
"structuredResult": true,
|
||||
"typedEvents": true,
|
||||
"semanticTools": {
|
||||
"schema": "paperclip.prp.semantic_tools.v1",
|
||||
"schemaVersion": 1,
|
||||
"operations": [
|
||||
{
|
||||
"operationId": "register_deliverable",
|
||||
"version": 1,
|
||||
"availability": "available",
|
||||
"redactionDisposition": "digest_only",
|
||||
"requiredClaims": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"unsupported": []
|
||||
},
|
||||
"runPhase": "terminal",
|
||||
"sessionState": "not_started",
|
||||
"turnState": "not_started",
|
||||
"activeTurnId": null,
|
||||
"items": [],
|
||||
"requests": [],
|
||||
"proposedResult": {
|
||||
"schema": "paperclip.run_result.v1",
|
||||
"reportedWorkDisposition": "done",
|
||||
"summary": "The artifact was registered through the semantic tool receipt.",
|
||||
"completionClaim": {
|
||||
"contractRevision": "semantic-receipts-v1",
|
||||
"objectiveSatisfied": true,
|
||||
"criteria": [],
|
||||
"remainingWork": []
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"receiptId": "receipt_semantic_happy"
|
||||
}
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"commandOrCheck": "semantic artifact receipt",
|
||||
"status": "passed",
|
||||
"artifactRef": "artifact_semantic_happy"
|
||||
}
|
||||
],
|
||||
"attentionRequests": [],
|
||||
"artifacts": [
|
||||
{
|
||||
"kind": "artifact",
|
||||
"ref": "artifact_semantic_happy",
|
||||
"title": "Semantic receipt report"
|
||||
}
|
||||
]
|
||||
},
|
||||
"terminal": {
|
||||
"schema": "paperclip.prp.terminal.v1",
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded",
|
||||
"reportedWorkDisposition": "done",
|
||||
"workAssessmentId": "assessment_semantic_happy",
|
||||
"statusDecisionId": "decision_semantic_happy"
|
||||
},
|
||||
"timeline": [
|
||||
{
|
||||
"position": 1,
|
||||
"sourceEventId": "semantic_happy_01",
|
||||
"sourceSeq": 1,
|
||||
"eventType": "mcp_app.tool_input",
|
||||
"emittedAt": "2026-08-11T10:00:00.000Z",
|
||||
"itemId": "item_semantic_happy",
|
||||
"summary": "mcp_app tool_input"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"sourceEventId": "semantic_happy_02",
|
||||
"sourceSeq": 2,
|
||||
"eventType": "mcp_app.tool_result",
|
||||
"emittedAt": "2026-08-11T10:00:00.010Z",
|
||||
"itemId": "item_semantic_happy",
|
||||
"summary": "mcp_app tool_result"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"sourceEventId": "semantic_happy_03",
|
||||
"sourceSeq": 3,
|
||||
"eventType": "run.result.proposed",
|
||||
"emittedAt": "2026-08-11T10:00:00.020Z",
|
||||
"summary": "The artifact was registered through the semantic tool receipt."
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"sourceEventId": "semantic_happy_04",
|
||||
"sourceSeq": 4,
|
||||
"eventType": "run.terminal",
|
||||
"emittedAt": "2026-08-11T10:00:00.030Z",
|
||||
"summary": "Run succeeded"
|
||||
}
|
||||
],
|
||||
"sourceCursors": {
|
||||
"runner:runner_semantic_happy": 4
|
||||
},
|
||||
"processedEventIds": [
|
||||
"semantic_happy_01",
|
||||
"semantic_happy_02",
|
||||
"semantic_happy_03",
|
||||
"semantic_happy_04"
|
||||
],
|
||||
"duplicateEventIds": [],
|
||||
"outOfOrderEventIds": [],
|
||||
"gaps": [],
|
||||
"integrity": "complete"
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"runId": "run_semantic_happy",
|
||||
"integrity": "complete",
|
||||
"timelineCount": 4,
|
||||
"duplicateEventIds": [],
|
||||
"gaps": [],
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded"
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
{
|
||||
"schema": "paperclip.prp.session-snapshot.v1",
|
||||
"fixtureName": "Semantic tool conflict and duplicate retry",
|
||||
"identity": {
|
||||
"schema": "paperclip.prp.identity.v1",
|
||||
"companyId": "company_semantic_retry",
|
||||
"issueId": "issue_semantic_retry",
|
||||
"runId": "run_semantic_retry",
|
||||
"environmentLeaseId": "lease_semantic_retry",
|
||||
"runnerInstanceId": "runner_semantic_retry",
|
||||
"normalizedSessionId": "session_semantic_retry",
|
||||
"driverSessionId": "driver_semantic_retry"
|
||||
},
|
||||
"capabilities": {
|
||||
"schema": "paperclip.prp.capabilities.v1",
|
||||
"sessionReusePolicy": "new_per_run",
|
||||
"driver": {
|
||||
"kind": "provider-neutral",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"steer": false,
|
||||
"interrupt": true,
|
||||
"resume": true,
|
||||
"runtimeRequests": true,
|
||||
"structuredResult": true,
|
||||
"typedEvents": true,
|
||||
"semanticTools": {
|
||||
"schema": "paperclip.prp.semantic_tools.v1",
|
||||
"schemaVersion": 1,
|
||||
"operations": [
|
||||
{
|
||||
"operationId": "write_document",
|
||||
"version": 1,
|
||||
"availability": "available",
|
||||
"redactionDisposition": "digest_only",
|
||||
"requiredClaims": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"unsupported": []
|
||||
},
|
||||
"runPhase": "terminal",
|
||||
"sessionState": "not_started",
|
||||
"turnState": "not_started",
|
||||
"activeTurnId": null,
|
||||
"items": [],
|
||||
"requests": [],
|
||||
"proposedResult": {
|
||||
"schema": "paperclip.run_result.v1",
|
||||
"reportedWorkDisposition": "done",
|
||||
"summary": "The revision-safe retry committed once.",
|
||||
"completionClaim": {
|
||||
"contractRevision": "semantic-receipts-v1",
|
||||
"objectiveSatisfied": true,
|
||||
"criteria": [],
|
||||
"remainingWork": []
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"receiptId": "receipt_semantic_conflict"
|
||||
},
|
||||
{
|
||||
"receiptId": "receipt_semantic_document_commit"
|
||||
}
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"commandOrCheck": "conflict then exact duplicate retry",
|
||||
"status": "passed"
|
||||
}
|
||||
],
|
||||
"attentionRequests": [],
|
||||
"artifacts": []
|
||||
},
|
||||
"terminal": {
|
||||
"schema": "paperclip.prp.terminal.v1",
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded",
|
||||
"reportedWorkDisposition": "done",
|
||||
"workAssessmentId": "assessment_semantic_retry",
|
||||
"statusDecisionId": "decision_semantic_retry"
|
||||
},
|
||||
"timeline": [
|
||||
{
|
||||
"position": 1,
|
||||
"sourceEventId": "semantic_retry_01",
|
||||
"sourceSeq": 1,
|
||||
"eventType": "mcp_app.tool_input",
|
||||
"emittedAt": "2026-08-11T10:20:00.000Z",
|
||||
"itemId": "item_semantic_conflict",
|
||||
"summary": "mcp_app tool_input"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"sourceEventId": "semantic_retry_02",
|
||||
"sourceSeq": 2,
|
||||
"eventType": "mcp_app.tool_result",
|
||||
"emittedAt": "2026-08-11T10:20:00.010Z",
|
||||
"itemId": "item_semantic_conflict",
|
||||
"summary": "mcp_app tool_result"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"sourceEventId": "semantic_retry_03",
|
||||
"sourceSeq": 3,
|
||||
"eventType": "mcp_app.tool_input",
|
||||
"emittedAt": "2026-08-11T10:20:00.020Z",
|
||||
"itemId": "item_semantic_fresh",
|
||||
"summary": "mcp_app tool_input"
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"sourceEventId": "semantic_retry_04",
|
||||
"sourceSeq": 4,
|
||||
"eventType": "mcp_app.tool_result",
|
||||
"emittedAt": "2026-08-11T10:20:00.030Z",
|
||||
"itemId": "item_semantic_fresh",
|
||||
"summary": "mcp_app tool_result"
|
||||
},
|
||||
{
|
||||
"position": 5,
|
||||
"sourceEventId": "semantic_retry_05",
|
||||
"sourceSeq": 5,
|
||||
"eventType": "mcp_app.tool_input",
|
||||
"emittedAt": "2026-08-11T10:20:00.040Z",
|
||||
"itemId": "item_semantic_duplicate",
|
||||
"summary": "mcp_app tool_input"
|
||||
},
|
||||
{
|
||||
"position": 6,
|
||||
"sourceEventId": "semantic_retry_06",
|
||||
"sourceSeq": 6,
|
||||
"eventType": "mcp_app.tool_result",
|
||||
"emittedAt": "2026-08-11T10:20:00.050Z",
|
||||
"itemId": "item_semantic_duplicate",
|
||||
"summary": "mcp_app tool_result"
|
||||
},
|
||||
{
|
||||
"position": 7,
|
||||
"sourceEventId": "semantic_retry_07",
|
||||
"sourceSeq": 7,
|
||||
"eventType": "run.result.proposed",
|
||||
"emittedAt": "2026-08-11T10:20:00.060Z",
|
||||
"summary": "The revision-safe retry committed once."
|
||||
},
|
||||
{
|
||||
"position": 8,
|
||||
"sourceEventId": "semantic_retry_08",
|
||||
"sourceSeq": 8,
|
||||
"eventType": "run.terminal",
|
||||
"emittedAt": "2026-08-11T10:20:00.070Z",
|
||||
"summary": "Run succeeded"
|
||||
}
|
||||
],
|
||||
"sourceCursors": {
|
||||
"runner:runner_semantic_retry": 8
|
||||
},
|
||||
"processedEventIds": [
|
||||
"semantic_retry_01",
|
||||
"semantic_retry_02",
|
||||
"semantic_retry_03",
|
||||
"semantic_retry_04",
|
||||
"semantic_retry_05",
|
||||
"semantic_retry_06",
|
||||
"semantic_retry_07",
|
||||
"semantic_retry_08"
|
||||
],
|
||||
"duplicateEventIds": [],
|
||||
"outOfOrderEventIds": [],
|
||||
"gaps": [],
|
||||
"integrity": "complete"
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"runId": "run_semantic_retry",
|
||||
"integrity": "complete",
|
||||
"timelineCount": 8,
|
||||
"duplicateEventIds": [],
|
||||
"gaps": [],
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded"
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
{
|
||||
"schema": "paperclip.prp.session-snapshot.v1",
|
||||
"fixtureName": "Semantic tool denial and redaction",
|
||||
"identity": {
|
||||
"schema": "paperclip.prp.identity.v1",
|
||||
"companyId": "company_semantic_denied",
|
||||
"issueId": "issue_semantic_denied",
|
||||
"runId": "run_semantic_denied",
|
||||
"environmentLeaseId": "lease_semantic_denied",
|
||||
"runnerInstanceId": "runner_semantic_denied",
|
||||
"normalizedSessionId": "session_semantic_denied",
|
||||
"driverSessionId": "driver_semantic_denied"
|
||||
},
|
||||
"capabilities": {
|
||||
"schema": "paperclip.prp.capabilities.v1",
|
||||
"sessionReusePolicy": "new_per_run",
|
||||
"driver": {
|
||||
"kind": "provider-neutral",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"steer": false,
|
||||
"interrupt": true,
|
||||
"resume": true,
|
||||
"runtimeRequests": true,
|
||||
"structuredResult": true,
|
||||
"typedEvents": true,
|
||||
"semanticTools": {
|
||||
"schema": "paperclip.prp.semantic_tools.v1",
|
||||
"schemaVersion": 1,
|
||||
"operations": [
|
||||
{
|
||||
"operationId": "decide_approval",
|
||||
"version": 1,
|
||||
"availability": "denied",
|
||||
"redactionDisposition": "digest_only",
|
||||
"requiredClaims": [
|
||||
"governance:approvals:decide"
|
||||
],
|
||||
"reasonCode": "required_claim_missing"
|
||||
}
|
||||
]
|
||||
},
|
||||
"unsupported": []
|
||||
},
|
||||
"runPhase": "terminal",
|
||||
"sessionState": "not_started",
|
||||
"turnState": "not_started",
|
||||
"activeTurnId": null,
|
||||
"items": [],
|
||||
"requests": [],
|
||||
"proposedResult": {
|
||||
"schema": "paperclip.run_result.v1",
|
||||
"reportedWorkDisposition": "needs_review",
|
||||
"summary": "The governed action was denied without fallback.",
|
||||
"completionClaim": {
|
||||
"contractRevision": "semantic-receipts-v1",
|
||||
"objectiveSatisfied": false,
|
||||
"criteria": [],
|
||||
"remainingWork": [
|
||||
{
|
||||
"description": "An authorized approver must decide.",
|
||||
"blocksCompletion": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"receiptId": "receipt_semantic_denied"
|
||||
}
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"commandOrCheck": "no fallback semantic call",
|
||||
"status": "passed"
|
||||
}
|
||||
],
|
||||
"attentionRequests": [],
|
||||
"artifacts": []
|
||||
},
|
||||
"terminal": {
|
||||
"schema": "paperclip.prp.terminal.v1",
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded",
|
||||
"reportedWorkDisposition": "needs_review",
|
||||
"workAssessmentId": "assessment_semantic_denied",
|
||||
"statusDecisionId": "decision_semantic_denied"
|
||||
},
|
||||
"timeline": [
|
||||
{
|
||||
"position": 1,
|
||||
"sourceEventId": "semantic_denied_01",
|
||||
"sourceSeq": 1,
|
||||
"eventType": "mcp_app.tool_input",
|
||||
"emittedAt": "2026-08-11T10:10:00.000Z",
|
||||
"itemId": "item_semantic_denied",
|
||||
"summary": "mcp_app tool_input"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"sourceEventId": "semantic_denied_02",
|
||||
"sourceSeq": 2,
|
||||
"eventType": "mcp_app.tool_result",
|
||||
"emittedAt": "2026-08-11T10:10:00.010Z",
|
||||
"itemId": "item_semantic_denied",
|
||||
"summary": "mcp_app tool_result"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"sourceEventId": "semantic_denied_03",
|
||||
"sourceSeq": 3,
|
||||
"eventType": "run.result.proposed",
|
||||
"emittedAt": "2026-08-11T10:10:00.020Z",
|
||||
"summary": "The governed action was denied without fallback."
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"sourceEventId": "semantic_denied_04",
|
||||
"sourceSeq": 4,
|
||||
"eventType": "run.terminal",
|
||||
"emittedAt": "2026-08-11T10:10:00.030Z",
|
||||
"summary": "Run succeeded"
|
||||
}
|
||||
],
|
||||
"sourceCursors": {
|
||||
"runner:runner_semantic_denied": 4
|
||||
},
|
||||
"processedEventIds": [
|
||||
"semantic_denied_01",
|
||||
"semantic_denied_02",
|
||||
"semantic_denied_03",
|
||||
"semantic_denied_04"
|
||||
],
|
||||
"duplicateEventIds": [],
|
||||
"outOfOrderEventIds": [],
|
||||
"gaps": [],
|
||||
"integrity": "complete"
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"runId": "run_semantic_denied",
|
||||
"integrity": "complete",
|
||||
"timelineCount": 4,
|
||||
"duplicateEventIds": [],
|
||||
"gaps": [],
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded"
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
{
|
||||
"schema": "paperclip.prp.session-snapshot.v1",
|
||||
"fixtureName": "Semantic governance target and continuation chain",
|
||||
"identity": {
|
||||
"schema": "paperclip.prp.identity.v1",
|
||||
"companyId": "company_semantic_governance",
|
||||
"issueId": "issue_semantic_governance",
|
||||
"runId": "run_semantic_governance",
|
||||
"environmentLeaseId": "lease_semantic_governance",
|
||||
"runnerInstanceId": "runner_semantic_governance",
|
||||
"normalizedSessionId": "session_semantic_governance",
|
||||
"driverSessionId": "driver_semantic_governance"
|
||||
},
|
||||
"capabilities": {
|
||||
"schema": "paperclip.prp.capabilities.v1",
|
||||
"sessionReusePolicy": "reuse_per_issue",
|
||||
"driver": {
|
||||
"kind": "provider-neutral",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"steer": true,
|
||||
"interrupt": true,
|
||||
"resume": true,
|
||||
"runtimeRequests": true,
|
||||
"structuredResult": true,
|
||||
"typedEvents": true,
|
||||
"semanticTools": {
|
||||
"schema": "paperclip.prp.semantic_tools.v1",
|
||||
"schemaVersion": 1,
|
||||
"operations": [
|
||||
{
|
||||
"operationId": "request_human_input",
|
||||
"version": 1,
|
||||
"availability": "available",
|
||||
"redactionDisposition": "digest_only",
|
||||
"requiredClaims": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"unsupported": []
|
||||
},
|
||||
"runPhase": "terminal",
|
||||
"sessionState": "not_started",
|
||||
"turnState": "not_started",
|
||||
"activeTurnId": null,
|
||||
"items": [],
|
||||
"requests": [],
|
||||
"proposedResult": {
|
||||
"schema": "paperclip.run_result.v1",
|
||||
"reportedWorkDisposition": "yielded",
|
||||
"summary": "The immutable decision target is waiting on a bounded monitor continuation.",
|
||||
"completionClaim": {
|
||||
"contractRevision": "semantic-receipts-v1",
|
||||
"objectiveSatisfied": false,
|
||||
"criteria": [],
|
||||
"remainingWork": [
|
||||
{
|
||||
"description": "Wait for the interaction response.",
|
||||
"blocksCompletion": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"receiptId": "receipt_semantic_governance"
|
||||
}
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"commandOrCheck": "immutable target and causal chain",
|
||||
"status": "passed"
|
||||
}
|
||||
],
|
||||
"attentionRequests": [],
|
||||
"artifacts": [],
|
||||
"continuation": {
|
||||
"kind": "monitor",
|
||||
"summary": "Recheck after the typed response wake.",
|
||||
"idempotencyKey": "monitor_semantic_governance"
|
||||
}
|
||||
},
|
||||
"terminal": {
|
||||
"schema": "paperclip.prp.terminal.v1",
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded",
|
||||
"reportedWorkDisposition": "yielded",
|
||||
"workAssessmentId": "assessment_semantic_governance",
|
||||
"statusDecisionId": "decision_semantic_governance"
|
||||
},
|
||||
"timeline": [
|
||||
{
|
||||
"position": 1,
|
||||
"sourceEventId": "semantic_governance_01",
|
||||
"sourceSeq": 1,
|
||||
"eventType": "mcp_app.tool_input",
|
||||
"emittedAt": "2026-08-11T10:30:00.000Z",
|
||||
"itemId": "item_semantic_governance",
|
||||
"summary": "mcp_app tool_input"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"sourceEventId": "semantic_governance_02",
|
||||
"sourceSeq": 2,
|
||||
"eventType": "mcp_app.tool_result",
|
||||
"emittedAt": "2026-08-11T10:30:00.010Z",
|
||||
"itemId": "item_semantic_governance",
|
||||
"summary": "mcp_app tool_result"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"sourceEventId": "semantic_governance_03",
|
||||
"sourceSeq": 3,
|
||||
"eventType": "run.result.proposed",
|
||||
"emittedAt": "2026-08-11T10:30:00.020Z",
|
||||
"summary": "The immutable decision target is waiting on a bounded monitor continuation."
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"sourceEventId": "semantic_governance_04",
|
||||
"sourceSeq": 4,
|
||||
"eventType": "run.terminal",
|
||||
"emittedAt": "2026-08-11T10:30:00.030Z",
|
||||
"summary": "Run succeeded"
|
||||
}
|
||||
],
|
||||
"sourceCursors": {
|
||||
"runner:runner_semantic_governance": 4
|
||||
},
|
||||
"processedEventIds": [
|
||||
"semantic_governance_01",
|
||||
"semantic_governance_02",
|
||||
"semantic_governance_03",
|
||||
"semantic_governance_04"
|
||||
],
|
||||
"duplicateEventIds": [],
|
||||
"outOfOrderEventIds": [],
|
||||
"gaps": [],
|
||||
"integrity": "complete"
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"runId": "run_semantic_governance",
|
||||
"integrity": "complete",
|
||||
"timelineCount": 4,
|
||||
"duplicateEventIds": [],
|
||||
"gaps": [],
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded"
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
{
|
||||
"schema": "paperclip.prp.session-snapshot.v1",
|
||||
"fixtureName": "Semantic tool unknown optional envelope fields",
|
||||
"identity": {
|
||||
"schema": "paperclip.prp.identity.v1",
|
||||
"companyId": "company_semantic_future",
|
||||
"issueId": "issue_semantic_future",
|
||||
"runId": "run_semantic_future",
|
||||
"environmentLeaseId": "lease_semantic_future",
|
||||
"runnerInstanceId": "runner_semantic_future",
|
||||
"normalizedSessionId": "session_semantic_future",
|
||||
"driverSessionId": "driver_semantic_future"
|
||||
},
|
||||
"capabilities": {
|
||||
"schema": "paperclip.prp.capabilities.v1",
|
||||
"sessionReusePolicy": "new_per_run",
|
||||
"driver": {
|
||||
"kind": "provider-neutral",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"steer": false,
|
||||
"interrupt": true,
|
||||
"resume": true,
|
||||
"runtimeRequests": false,
|
||||
"structuredResult": true,
|
||||
"typedEvents": true,
|
||||
"semanticTools": {
|
||||
"schema": "paperclip.prp.semantic_tools.v1",
|
||||
"schemaVersion": 1,
|
||||
"operations": [
|
||||
{
|
||||
"operationId": "get_task_context",
|
||||
"version": 1,
|
||||
"availability": "available",
|
||||
"redactionDisposition": "digest_only",
|
||||
"requiredClaims": [],
|
||||
"futureAvailabilityHint": "optional"
|
||||
}
|
||||
],
|
||||
"futureNegotiationHint": {
|
||||
"safe": true
|
||||
}
|
||||
},
|
||||
"unsupported": []
|
||||
},
|
||||
"runPhase": "terminal",
|
||||
"sessionState": "not_started",
|
||||
"turnState": "not_started",
|
||||
"activeTurnId": null,
|
||||
"items": [],
|
||||
"requests": [],
|
||||
"proposedResult": {
|
||||
"schema": "paperclip.run_result.v1",
|
||||
"reportedWorkDisposition": "done",
|
||||
"summary": "Unknown optional receipt fields were ignored by the v1 projection.",
|
||||
"completionClaim": {
|
||||
"contractRevision": "semantic-receipts-v1",
|
||||
"objectiveSatisfied": true,
|
||||
"criteria": [],
|
||||
"remainingWork": []
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"receiptId": "receipt_semantic_future"
|
||||
}
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"commandOrCheck": "optional field projection stability",
|
||||
"status": "passed"
|
||||
}
|
||||
],
|
||||
"attentionRequests": [],
|
||||
"artifacts": []
|
||||
},
|
||||
"terminal": {
|
||||
"schema": "paperclip.prp.terminal.v1",
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded",
|
||||
"reportedWorkDisposition": "done",
|
||||
"workAssessmentId": "assessment_semantic_future",
|
||||
"statusDecisionId": "decision_semantic_future"
|
||||
},
|
||||
"timeline": [
|
||||
{
|
||||
"position": 1,
|
||||
"sourceEventId": "semantic_future_01",
|
||||
"sourceSeq": 1,
|
||||
"eventType": "mcp_app.tool_input",
|
||||
"emittedAt": "2026-08-11T10:50:00.000Z",
|
||||
"itemId": "item_semantic_future",
|
||||
"summary": "mcp_app tool_input"
|
||||
},
|
||||
{
|
||||
"position": 2,
|
||||
"sourceEventId": "semantic_future_02",
|
||||
"sourceSeq": 2,
|
||||
"eventType": "mcp_app.tool_result",
|
||||
"emittedAt": "2026-08-11T10:50:00.010Z",
|
||||
"itemId": "item_semantic_future",
|
||||
"summary": "mcp_app tool_result"
|
||||
},
|
||||
{
|
||||
"position": 3,
|
||||
"sourceEventId": "semantic_future_03",
|
||||
"sourceSeq": 3,
|
||||
"eventType": "run.result.proposed",
|
||||
"emittedAt": "2026-08-11T10:50:00.020Z",
|
||||
"summary": "Unknown optional receipt fields were ignored by the v1 projection."
|
||||
},
|
||||
{
|
||||
"position": 4,
|
||||
"sourceEventId": "semantic_future_04",
|
||||
"sourceSeq": 4,
|
||||
"eventType": "run.terminal",
|
||||
"emittedAt": "2026-08-11T10:50:00.030Z",
|
||||
"summary": "Run succeeded"
|
||||
}
|
||||
],
|
||||
"sourceCursors": {
|
||||
"runner:runner_semantic_future": 4
|
||||
},
|
||||
"processedEventIds": [
|
||||
"semantic_future_01",
|
||||
"semantic_future_02",
|
||||
"semantic_future_03",
|
||||
"semantic_future_04"
|
||||
],
|
||||
"duplicateEventIds": [],
|
||||
"outOfOrderEventIds": [],
|
||||
"gaps": [],
|
||||
"integrity": "complete"
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"runId": "run_semantic_future",
|
||||
"integrity": "complete",
|
||||
"timelineCount": 4,
|
||||
"duplicateEventIds": [],
|
||||
"gaps": [],
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded"
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
{
|
||||
"schema": "paperclip.prp.fixture.v1",
|
||||
"fixtureVersion": 1,
|
||||
"protocolVersion": 1,
|
||||
"name": "Semantic tool artifact happy path",
|
||||
"description": "A provider-neutral register_deliverable call emits safe input and result receipts linked to the terminal artifact result.",
|
||||
"identity": {
|
||||
"schema": "paperclip.prp.identity.v1",
|
||||
"companyId": "company_semantic_happy",
|
||||
"issueId": "issue_semantic_happy",
|
||||
"runId": "run_semantic_happy",
|
||||
"environmentLeaseId": "lease_semantic_happy",
|
||||
"runnerInstanceId": "runner_semantic_happy",
|
||||
"normalizedSessionId": "session_semantic_happy",
|
||||
"driverSessionId": "driver_semantic_happy"
|
||||
},
|
||||
"capabilities": {
|
||||
"schema": "paperclip.prp.capabilities.v1",
|
||||
"sessionReusePolicy": "new_per_run",
|
||||
"driver": { "kind": "provider-neutral", "version": "1.0.0" },
|
||||
"steer": true,
|
||||
"interrupt": true,
|
||||
"resume": true,
|
||||
"runtimeRequests": true,
|
||||
"structuredResult": true,
|
||||
"typedEvents": true,
|
||||
"semanticTools": {
|
||||
"schema": "paperclip.prp.semantic_tools.v1",
|
||||
"schemaVersion": 1,
|
||||
"operations": [{
|
||||
"operationId": "register_deliverable",
|
||||
"version": 1,
|
||||
"availability": "available",
|
||||
"redactionDisposition": "digest_only",
|
||||
"requiredClaims": []
|
||||
}]
|
||||
},
|
||||
"unsupported": []
|
||||
},
|
||||
"commands": [],
|
||||
"events": [
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1",
|
||||
"sourceEventId": "semantic_happy_01",
|
||||
"sourceSeq": 1,
|
||||
"sourceInstanceId": "runner_semantic_happy",
|
||||
"sourceKind": "runner",
|
||||
"runId": "run_semantic_happy",
|
||||
"normalizedSessionId": "session_semantic_happy",
|
||||
"turnId": "turn_semantic_happy",
|
||||
"itemId": "item_semantic_happy",
|
||||
"eventType": "mcp_app.tool_input",
|
||||
"schemaVersion": 1,
|
||||
"priority": 1,
|
||||
"emittedAt": "2026-08-11T10:00:00.000Z",
|
||||
"payload": {
|
||||
"semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1",
|
||||
"schemaVersion": 1,
|
||||
"phase": "input",
|
||||
"operationId": "register_deliverable",
|
||||
"callId": "call_semantic_happy",
|
||||
"correlation": {
|
||||
"runId": "run_semantic_happy",
|
||||
"normalizedSessionId": "session_semantic_happy",
|
||||
"turnId": "turn_semantic_happy",
|
||||
"itemId": "item_semantic_happy"
|
||||
},
|
||||
"idempotencyKey": "artifact_once",
|
||||
"content": {
|
||||
"digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"redactionDisposition": "digest_only",
|
||||
"references": [{ "kind": "task", "id": "issue_semantic_happy" }]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1",
|
||||
"sourceEventId": "semantic_happy_02",
|
||||
"sourceSeq": 2,
|
||||
"sourceInstanceId": "runner_semantic_happy",
|
||||
"sourceKind": "runner",
|
||||
"runId": "run_semantic_happy",
|
||||
"normalizedSessionId": "session_semantic_happy",
|
||||
"turnId": "turn_semantic_happy",
|
||||
"itemId": "item_semantic_happy",
|
||||
"eventType": "mcp_app.tool_result",
|
||||
"schemaVersion": 1,
|
||||
"priority": 1,
|
||||
"emittedAt": "2026-08-11T10:00:00.010Z",
|
||||
"payload": {
|
||||
"semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1",
|
||||
"schemaVersion": 1,
|
||||
"phase": "result",
|
||||
"operationId": "register_deliverable",
|
||||
"callId": "call_semantic_happy",
|
||||
"correlation": {
|
||||
"runId": "run_semantic_happy",
|
||||
"normalizedSessionId": "session_semantic_happy",
|
||||
"turnId": "turn_semantic_happy",
|
||||
"itemId": "item_semantic_happy"
|
||||
},
|
||||
"idempotencyKey": "artifact_once",
|
||||
"content": {
|
||||
"digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"redactionDisposition": "allowlisted_references",
|
||||
"references": [
|
||||
{ "kind": "artifact", "id": "artifact_semantic_happy" },
|
||||
{ "kind": "work_product", "id": "work_product_semantic_happy" }
|
||||
]
|
||||
},
|
||||
"outcome": "succeeded",
|
||||
"code": "ok",
|
||||
"retryable": false,
|
||||
"authorizationBoundary": "active_task",
|
||||
"operationReceiptId": "receipt_semantic_happy",
|
||||
"auditReceiptId": "audit_semantic_happy",
|
||||
"currentRevision": 2,
|
||||
"artifactRefs": [
|
||||
{ "kind": "artifact", "id": "artifact_semantic_happy" },
|
||||
{ "kind": "work_product", "id": "work_product_semantic_happy" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1",
|
||||
"sourceEventId": "semantic_happy_03",
|
||||
"sourceSeq": 3,
|
||||
"sourceInstanceId": "runner_semantic_happy",
|
||||
"sourceKind": "runner",
|
||||
"runId": "run_semantic_happy",
|
||||
"normalizedSessionId": "session_semantic_happy",
|
||||
"turnId": "turn_semantic_happy",
|
||||
"eventType": "run.result.proposed",
|
||||
"schemaVersion": 1,
|
||||
"priority": 0,
|
||||
"emittedAt": "2026-08-11T10:00:00.020Z",
|
||||
"payload": {
|
||||
"schema": "paperclip.run_result.v1",
|
||||
"reportedWorkDisposition": "done",
|
||||
"summary": "The artifact was registered through the semantic tool receipt.",
|
||||
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] },
|
||||
"evidence": [{ "receiptId": "receipt_semantic_happy" }],
|
||||
"verification": [{ "commandOrCheck": "semantic artifact receipt", "status": "passed", "artifactRef": "artifact_semantic_happy" }],
|
||||
"attentionRequests": [],
|
||||
"artifacts": [{ "kind": "artifact", "ref": "artifact_semantic_happy", "title": "Semantic receipt report" }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1",
|
||||
"sourceEventId": "semantic_happy_04",
|
||||
"sourceSeq": 4,
|
||||
"sourceInstanceId": "runner_semantic_happy",
|
||||
"sourceKind": "runner",
|
||||
"runId": "run_semantic_happy",
|
||||
"normalizedSessionId": "session_semantic_happy",
|
||||
"turnId": "turn_semantic_happy",
|
||||
"eventType": "run.terminal",
|
||||
"schemaVersion": 1,
|
||||
"priority": 0,
|
||||
"emittedAt": "2026-08-11T10:00:00.030Z",
|
||||
"payload": {
|
||||
"schema": "paperclip.prp.terminal.v1",
|
||||
"turnTerminalState": "completed",
|
||||
"runTerminalState": "succeeded",
|
||||
"reportedWorkDisposition": "done",
|
||||
"workAssessmentId": "assessment_semantic_happy",
|
||||
"statusDecisionId": "decision_semantic_happy"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requests": [],
|
||||
"result": {
|
||||
"schema": "paperclip.run_result.v1",
|
||||
"reportedWorkDisposition": "done",
|
||||
"summary": "The artifact was registered through the semantic tool receipt.",
|
||||
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] },
|
||||
"evidence": [{ "receiptId": "receipt_semantic_happy" }],
|
||||
"verification": [{ "commandOrCheck": "semantic artifact receipt", "status": "passed", "artifactRef": "artifact_semantic_happy" }],
|
||||
"attentionRequests": [],
|
||||
"artifacts": [{ "kind": "artifact", "ref": "artifact_semantic_happy", "title": "Semantic receipt report" }]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
"schema": "paperclip.prp.fixture.v1",
|
||||
"fixtureVersion": 1,
|
||||
"protocolVersion": 1,
|
||||
"name": "Semantic tool conflict and duplicate retry",
|
||||
"description": "A stale document write conflicts, a fresh retry succeeds, and an exact idempotent retry returns the original operation receipt.",
|
||||
"identity": {
|
||||
"schema": "paperclip.prp.identity.v1", "companyId": "company_semantic_retry", "issueId": "issue_semantic_retry",
|
||||
"runId": "run_semantic_retry", "environmentLeaseId": "lease_semantic_retry", "runnerInstanceId": "runner_semantic_retry",
|
||||
"normalizedSessionId": "session_semantic_retry", "driverSessionId": "driver_semantic_retry"
|
||||
},
|
||||
"capabilities": {
|
||||
"schema": "paperclip.prp.capabilities.v1", "sessionReusePolicy": "new_per_run",
|
||||
"driver": { "kind": "provider-neutral", "version": "1.0.0" },
|
||||
"steer": false, "interrupt": true, "resume": true, "runtimeRequests": true, "structuredResult": true, "typedEvents": true,
|
||||
"semanticTools": { "schema": "paperclip.prp.semantic_tools.v1", "schemaVersion": 1, "operations": [{
|
||||
"operationId": "write_document", "version": 1, "availability": "available", "redactionDisposition": "digest_only", "requiredClaims": []
|
||||
}] },
|
||||
"unsupported": []
|
||||
},
|
||||
"commands": [],
|
||||
"events": [
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_01", "sourceSeq": 1, "sourceInstanceId": "runner_semantic_retry",
|
||||
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
|
||||
"itemId": "item_semantic_conflict", "eventType": "mcp_app.tool_input", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:20:00.000Z",
|
||||
"payload": { "semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "input", "operationId": "write_document", "callId": "call_semantic_conflict",
|
||||
"correlation": { "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry", "itemId": "item_semantic_conflict" },
|
||||
"idempotencyKey": "write_plan_stale",
|
||||
"content": { "digest": "sha256:1111111111111111111111111111111111111111111111111111111111111111", "redactionDisposition": "digest_only", "references": [{ "kind": "document_revision", "id": "revision_plan_stale" }] }
|
||||
} }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_02", "sourceSeq": 2, "sourceInstanceId": "runner_semantic_retry",
|
||||
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
|
||||
"itemId": "item_semantic_conflict", "eventType": "mcp_app.tool_result", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:20:00.010Z",
|
||||
"payload": { "semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "result", "operationId": "write_document", "callId": "call_semantic_conflict",
|
||||
"correlation": { "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry", "itemId": "item_semantic_conflict" },
|
||||
"idempotencyKey": "write_plan_stale",
|
||||
"content": { "digest": "sha256:2222222222222222222222222222222222222222222222222222222222222222", "redactionDisposition": "allowlisted_references", "references": [{ "kind": "document_revision", "id": "revision_plan_2" }] },
|
||||
"outcome": "conflict", "code": "document_revision_conflict", "retryable": true, "authorizationBoundary": "revision",
|
||||
"operationReceiptId": "receipt_semantic_conflict", "auditReceiptId": "audit_semantic_conflict", "currentRevision": "revision_plan_2"
|
||||
} }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_03", "sourceSeq": 3, "sourceInstanceId": "runner_semantic_retry",
|
||||
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
|
||||
"itemId": "item_semantic_fresh", "eventType": "mcp_app.tool_input", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:20:00.020Z",
|
||||
"payload": { "semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "input", "operationId": "write_document", "callId": "call_semantic_fresh",
|
||||
"correlation": { "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry", "itemId": "item_semantic_fresh" },
|
||||
"idempotencyKey": "write_plan_retry",
|
||||
"content": { "digest": "sha256:3333333333333333333333333333333333333333333333333333333333333333", "redactionDisposition": "digest_only", "references": [{ "kind": "document_revision", "id": "revision_plan_2" }] }
|
||||
} }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_04", "sourceSeq": 4, "sourceInstanceId": "runner_semantic_retry",
|
||||
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
|
||||
"itemId": "item_semantic_fresh", "eventType": "mcp_app.tool_result", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:20:00.030Z",
|
||||
"payload": { "semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "result", "operationId": "write_document", "callId": "call_semantic_fresh",
|
||||
"correlation": { "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry", "itemId": "item_semantic_fresh" },
|
||||
"idempotencyKey": "write_plan_retry",
|
||||
"content": { "digest": "sha256:4444444444444444444444444444444444444444444444444444444444444444", "redactionDisposition": "allowlisted_references", "references": [{ "kind": "document_revision", "id": "revision_plan_3" }] },
|
||||
"outcome": "succeeded", "code": "ok", "retryable": false, "authorizationBoundary": "revision",
|
||||
"operationReceiptId": "receipt_semantic_document_commit", "auditReceiptId": "audit_semantic_document_commit", "currentRevision": "revision_plan_3"
|
||||
} }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_05", "sourceSeq": 5, "sourceInstanceId": "runner_semantic_retry",
|
||||
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
|
||||
"itemId": "item_semantic_duplicate", "eventType": "mcp_app.tool_input", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:20:00.040Z",
|
||||
"payload": { "semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "input", "operationId": "write_document", "callId": "call_semantic_duplicate",
|
||||
"correlation": { "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry", "itemId": "item_semantic_duplicate" },
|
||||
"idempotencyKey": "write_plan_retry",
|
||||
"content": { "digest": "sha256:3333333333333333333333333333333333333333333333333333333333333333", "redactionDisposition": "digest_only", "references": [{ "kind": "document_revision", "id": "revision_plan_2" }] }
|
||||
} }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_06", "sourceSeq": 6, "sourceInstanceId": "runner_semantic_retry",
|
||||
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
|
||||
"itemId": "item_semantic_duplicate", "eventType": "mcp_app.tool_result", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:20:00.050Z",
|
||||
"payload": { "semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "result", "operationId": "write_document", "callId": "call_semantic_duplicate",
|
||||
"correlation": { "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry", "itemId": "item_semantic_duplicate" },
|
||||
"idempotencyKey": "write_plan_retry",
|
||||
"content": { "digest": "sha256:4444444444444444444444444444444444444444444444444444444444444444", "redactionDisposition": "allowlisted_references", "references": [{ "kind": "document_revision", "id": "revision_plan_3" }] },
|
||||
"outcome": "duplicate", "code": "duplicate", "retryable": false, "authorizationBoundary": "revision",
|
||||
"operationReceiptId": "receipt_semantic_document_commit", "duplicateOfReceiptId": "receipt_semantic_document_commit", "currentRevision": "revision_plan_3"
|
||||
} }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_07", "sourceSeq": 7, "sourceInstanceId": "runner_semantic_retry",
|
||||
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
|
||||
"eventType": "run.result.proposed", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:20:00.060Z",
|
||||
"payload": {
|
||||
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "done", "summary": "The revision-safe retry committed once.",
|
||||
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] },
|
||||
"evidence": [{ "receiptId": "receipt_semantic_conflict" }, { "receiptId": "receipt_semantic_document_commit" }],
|
||||
"verification": [{ "commandOrCheck": "conflict then exact duplicate retry", "status": "passed" }], "attentionRequests": [], "artifacts": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_retry_08", "sourceSeq": 8, "sourceInstanceId": "runner_semantic_retry",
|
||||
"sourceKind": "runner", "runId": "run_semantic_retry", "normalizedSessionId": "session_semantic_retry", "turnId": "turn_semantic_retry",
|
||||
"eventType": "run.terminal", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:20:00.070Z",
|
||||
"payload": { "schema": "paperclip.prp.terminal.v1", "turnTerminalState": "completed", "runTerminalState": "succeeded", "reportedWorkDisposition": "done", "workAssessmentId": "assessment_semantic_retry", "statusDecisionId": "decision_semantic_retry" }
|
||||
}
|
||||
],
|
||||
"requests": [],
|
||||
"result": {
|
||||
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "done", "summary": "The revision-safe retry committed once.",
|
||||
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] },
|
||||
"evidence": [{ "receiptId": "receipt_semantic_conflict" }, { "receiptId": "receipt_semantic_document_commit" }],
|
||||
"verification": [{ "commandOrCheck": "conflict then exact duplicate retry", "status": "passed" }], "attentionRequests": [], "artifacts": []
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
{
|
||||
"schema": "paperclip.prp.fixture.v1",
|
||||
"fixtureVersion": 1,
|
||||
"protocolVersion": 1,
|
||||
"name": "Semantic tool denial and redaction",
|
||||
"description": "A governed action is denied with digest-only content, a typed authorization boundary, and no fallback semantic call.",
|
||||
"identity": {
|
||||
"schema": "paperclip.prp.identity.v1",
|
||||
"companyId": "company_semantic_denied",
|
||||
"issueId": "issue_semantic_denied",
|
||||
"runId": "run_semantic_denied",
|
||||
"environmentLeaseId": "lease_semantic_denied",
|
||||
"runnerInstanceId": "runner_semantic_denied",
|
||||
"normalizedSessionId": "session_semantic_denied",
|
||||
"driverSessionId": "driver_semantic_denied"
|
||||
},
|
||||
"capabilities": {
|
||||
"schema": "paperclip.prp.capabilities.v1",
|
||||
"sessionReusePolicy": "new_per_run",
|
||||
"driver": { "kind": "provider-neutral", "version": "1.0.0" },
|
||||
"steer": false,
|
||||
"interrupt": true,
|
||||
"resume": true,
|
||||
"runtimeRequests": true,
|
||||
"structuredResult": true,
|
||||
"typedEvents": true,
|
||||
"semanticTools": {
|
||||
"schema": "paperclip.prp.semantic_tools.v1",
|
||||
"schemaVersion": 1,
|
||||
"operations": [{
|
||||
"operationId": "decide_approval",
|
||||
"version": 1,
|
||||
"availability": "denied",
|
||||
"redactionDisposition": "digest_only",
|
||||
"requiredClaims": ["governance:approvals:decide"],
|
||||
"reasonCode": "required_claim_missing"
|
||||
}]
|
||||
},
|
||||
"unsupported": []
|
||||
},
|
||||
"commands": [],
|
||||
"events": [
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_denied_01", "sourceSeq": 1,
|
||||
"sourceInstanceId": "runner_semantic_denied", "sourceKind": "runner", "runId": "run_semantic_denied",
|
||||
"normalizedSessionId": "session_semantic_denied", "turnId": "turn_semantic_denied", "itemId": "item_semantic_denied",
|
||||
"eventType": "mcp_app.tool_input", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:10:00.000Z",
|
||||
"payload": { "semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "input",
|
||||
"operationId": "decide_approval", "callId": "call_semantic_denied",
|
||||
"correlation": { "runId": "run_semantic_denied", "normalizedSessionId": "session_semantic_denied", "turnId": "turn_semantic_denied", "itemId": "item_semantic_denied" },
|
||||
"idempotencyKey": "denied_once",
|
||||
"content": { "digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "redactionDisposition": "redacted", "references": [] }
|
||||
} }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_denied_02", "sourceSeq": 2,
|
||||
"sourceInstanceId": "runner_semantic_denied", "sourceKind": "runner", "runId": "run_semantic_denied",
|
||||
"normalizedSessionId": "session_semantic_denied", "turnId": "turn_semantic_denied", "itemId": "item_semantic_denied",
|
||||
"eventType": "mcp_app.tool_result", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:10:00.010Z",
|
||||
"payload": { "semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "result",
|
||||
"operationId": "decide_approval", "callId": "call_semantic_denied",
|
||||
"correlation": { "runId": "run_semantic_denied", "normalizedSessionId": "session_semantic_denied", "turnId": "turn_semantic_denied", "itemId": "item_semantic_denied" },
|
||||
"idempotencyKey": "denied_once",
|
||||
"content": { "digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", "redactionDisposition": "redacted", "references": [] },
|
||||
"outcome": "denied", "code": "required_claim_missing", "retryable": false,
|
||||
"authorizationBoundary": "grant", "operationReceiptId": "receipt_semantic_denied", "auditReceiptId": "audit_semantic_denied", "currentRevision": 4
|
||||
} }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_denied_03", "sourceSeq": 3,
|
||||
"sourceInstanceId": "runner_semantic_denied", "sourceKind": "runner", "runId": "run_semantic_denied",
|
||||
"normalizedSessionId": "session_semantic_denied", "turnId": "turn_semantic_denied",
|
||||
"eventType": "run.result.proposed", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:10:00.020Z",
|
||||
"payload": {
|
||||
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "needs_review",
|
||||
"summary": "The governed action was denied without fallback.",
|
||||
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": false, "criteria": [], "remainingWork": [{ "description": "An authorized approver must decide.", "blocksCompletion": true }] },
|
||||
"evidence": [{ "receiptId": "receipt_semantic_denied" }],
|
||||
"verification": [{ "commandOrCheck": "no fallback semantic call", "status": "passed" }],
|
||||
"attentionRequests": [], "artifacts": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_denied_04", "sourceSeq": 4,
|
||||
"sourceInstanceId": "runner_semantic_denied", "sourceKind": "runner", "runId": "run_semantic_denied",
|
||||
"normalizedSessionId": "session_semantic_denied", "turnId": "turn_semantic_denied",
|
||||
"eventType": "run.terminal", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:10:00.030Z",
|
||||
"payload": { "schema": "paperclip.prp.terminal.v1", "turnTerminalState": "completed", "runTerminalState": "succeeded", "reportedWorkDisposition": "needs_review", "workAssessmentId": "assessment_semantic_denied", "statusDecisionId": "decision_semantic_denied" }
|
||||
}
|
||||
],
|
||||
"requests": [],
|
||||
"result": {
|
||||
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "needs_review",
|
||||
"summary": "The governed action was denied without fallback.",
|
||||
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": false, "criteria": [], "remainingWork": [{ "description": "An authorized approver must decide.", "blocksCompletion": true }] },
|
||||
"evidence": [{ "receiptId": "receipt_semantic_denied" }],
|
||||
"verification": [{ "commandOrCheck": "no fallback semantic call", "status": "passed" }],
|
||||
"attentionRequests": [], "artifacts": []
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
{
|
||||
"schema": "paperclip.prp.fixture.v1",
|
||||
"fixtureVersion": 1,
|
||||
"protocolVersion": 1,
|
||||
"name": "Semantic governance target and continuation chain",
|
||||
"description": "A semantic result binds immutable interaction and approval targets while carrying only safe wake and monitor causal references.",
|
||||
"identity": {
|
||||
"schema": "paperclip.prp.identity.v1", "companyId": "company_semantic_governance", "issueId": "issue_semantic_governance",
|
||||
"runId": "run_semantic_governance", "environmentLeaseId": "lease_semantic_governance", "runnerInstanceId": "runner_semantic_governance",
|
||||
"normalizedSessionId": "session_semantic_governance", "driverSessionId": "driver_semantic_governance"
|
||||
},
|
||||
"capabilities": {
|
||||
"schema": "paperclip.prp.capabilities.v1", "sessionReusePolicy": "reuse_per_issue",
|
||||
"driver": { "kind": "provider-neutral", "version": "1.0.0" },
|
||||
"steer": true, "interrupt": true, "resume": true, "runtimeRequests": true, "structuredResult": true, "typedEvents": true,
|
||||
"semanticTools": { "schema": "paperclip.prp.semantic_tools.v1", "schemaVersion": 1, "operations": [{
|
||||
"operationId": "request_human_input", "version": 1, "availability": "available", "redactionDisposition": "digest_only", "requiredClaims": []
|
||||
}] },
|
||||
"unsupported": []
|
||||
},
|
||||
"commands": [],
|
||||
"events": [
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_governance_01", "sourceSeq": 1, "sourceInstanceId": "runner_semantic_governance",
|
||||
"sourceKind": "runner", "runId": "run_semantic_governance", "normalizedSessionId": "session_semantic_governance",
|
||||
"turnId": "turn_semantic_governance", "itemId": "item_semantic_governance", "eventType": "mcp_app.tool_input",
|
||||
"schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:30:00.000Z",
|
||||
"payload": { "semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "input", "operationId": "request_human_input", "callId": "call_semantic_governance",
|
||||
"correlation": { "runId": "run_semantic_governance", "normalizedSessionId": "session_semantic_governance", "turnId": "turn_semantic_governance", "itemId": "item_semantic_governance", "requestId": "request_semantic_governance" },
|
||||
"idempotencyKey": "interaction_once",
|
||||
"content": { "digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555", "redactionDisposition": "digest_only", "references": [{ "kind": "document_revision", "id": "revision_plan_7" }] }
|
||||
} }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_governance_02", "sourceSeq": 2, "sourceInstanceId": "runner_semantic_governance",
|
||||
"sourceKind": "runner", "runId": "run_semantic_governance", "normalizedSessionId": "session_semantic_governance",
|
||||
"turnId": "turn_semantic_governance", "itemId": "item_semantic_governance", "eventType": "mcp_app.tool_result",
|
||||
"schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:30:00.010Z",
|
||||
"payload": { "semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "result", "operationId": "request_human_input", "callId": "call_semantic_governance",
|
||||
"correlation": { "runId": "run_semantic_governance", "normalizedSessionId": "session_semantic_governance", "turnId": "turn_semantic_governance", "itemId": "item_semantic_governance", "requestId": "request_semantic_governance" },
|
||||
"idempotencyKey": "interaction_once",
|
||||
"content": { "digest": "sha256:6666666666666666666666666666666666666666666666666666666666666666", "redactionDisposition": "allowlisted_references", "references": [{ "kind": "interaction", "id": "interaction_semantic_governance" }, { "kind": "wake", "id": "wake_semantic_governance" }, { "kind": "monitor", "id": "monitor_semantic_governance" }] },
|
||||
"outcome": "succeeded", "code": "ok", "retryable": false, "authorizationBoundary": "governed_action",
|
||||
"operationReceiptId": "receipt_semantic_governance", "auditReceiptId": "audit_semantic_governance", "currentRevision": 8,
|
||||
"targets": [
|
||||
{ "kind": "interaction", "id": "interaction_semantic_governance", "immutable": true, "revisionId": "revision_plan_7" },
|
||||
{ "kind": "approval", "id": "approval_semantic_governance", "immutable": true, "decisionId": "decision_approval_semantic_governance" }
|
||||
],
|
||||
"causalRefs": [
|
||||
{ "kind": "document_revision", "id": "revision_plan_7" },
|
||||
{ "kind": "interaction", "id": "interaction_semantic_governance" },
|
||||
{ "kind": "approval", "id": "approval_semantic_governance" },
|
||||
{ "kind": "decision", "id": "decision_approval_semantic_governance" },
|
||||
{ "kind": "wake", "id": "wake_semantic_governance" },
|
||||
{ "kind": "monitor", "id": "monitor_semantic_governance" }
|
||||
]
|
||||
} }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_governance_03", "sourceSeq": 3, "sourceInstanceId": "runner_semantic_governance",
|
||||
"sourceKind": "runner", "runId": "run_semantic_governance", "normalizedSessionId": "session_semantic_governance", "turnId": "turn_semantic_governance",
|
||||
"eventType": "run.result.proposed", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:30:00.020Z",
|
||||
"payload": {
|
||||
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "yielded", "summary": "The immutable decision target is waiting on a bounded monitor continuation.",
|
||||
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": false, "criteria": [], "remainingWork": [{ "description": "Wait for the interaction response.", "blocksCompletion": true }] },
|
||||
"evidence": [{ "receiptId": "receipt_semantic_governance" }],
|
||||
"verification": [{ "commandOrCheck": "immutable target and causal chain", "status": "passed" }],
|
||||
"attentionRequests": [], "artifacts": [],
|
||||
"continuation": { "kind": "monitor", "summary": "Recheck after the typed response wake.", "idempotencyKey": "monitor_semantic_governance" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_governance_04", "sourceSeq": 4, "sourceInstanceId": "runner_semantic_governance",
|
||||
"sourceKind": "runner", "runId": "run_semantic_governance", "normalizedSessionId": "session_semantic_governance", "turnId": "turn_semantic_governance",
|
||||
"eventType": "run.terminal", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:30:00.030Z",
|
||||
"payload": { "schema": "paperclip.prp.terminal.v1", "turnTerminalState": "completed", "runTerminalState": "succeeded", "reportedWorkDisposition": "yielded", "workAssessmentId": "assessment_semantic_governance", "statusDecisionId": "decision_semantic_governance" }
|
||||
}
|
||||
],
|
||||
"requests": [],
|
||||
"result": {
|
||||
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "yielded", "summary": "The immutable decision target is waiting on a bounded monitor continuation.",
|
||||
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": false, "criteria": [], "remainingWork": [{ "description": "Wait for the interaction response.", "blocksCompletion": true }] },
|
||||
"evidence": [{ "receiptId": "receipt_semantic_governance" }],
|
||||
"verification": [{ "commandOrCheck": "immutable target and causal chain", "status": "passed" }],
|
||||
"attentionRequests": [], "artifacts": [],
|
||||
"continuation": { "kind": "monitor", "summary": "Recheck after the typed response wake.", "idempotencyKey": "monitor_semantic_governance" }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"schema": "paperclip.prp.fixture.v1",
|
||||
"fixtureVersion": 1,
|
||||
"protocolVersion": 1,
|
||||
"name": "Semantic tool unknown optional envelope fields",
|
||||
"description": "Unknown additive fields inside typed semantic envelopes validate without changing the existing v1 reducer projection.",
|
||||
"identity": {
|
||||
"schema": "paperclip.prp.identity.v1", "companyId": "company_semantic_future", "issueId": "issue_semantic_future", "runId": "run_semantic_future",
|
||||
"environmentLeaseId": "lease_semantic_future", "runnerInstanceId": "runner_semantic_future", "normalizedSessionId": "session_semantic_future", "driverSessionId": "driver_semantic_future"
|
||||
},
|
||||
"capabilities": {
|
||||
"schema": "paperclip.prp.capabilities.v1", "sessionReusePolicy": "new_per_run", "driver": { "kind": "provider-neutral", "version": "1.0.0" },
|
||||
"steer": false, "interrupt": true, "resume": true, "runtimeRequests": false, "structuredResult": true, "typedEvents": true,
|
||||
"semanticTools": { "schema": "paperclip.prp.semantic_tools.v1", "schemaVersion": 1, "operations": [{
|
||||
"operationId": "get_task_context", "version": 1, "availability": "available", "redactionDisposition": "digest_only", "requiredClaims": [],
|
||||
"futureAvailabilityHint": "optional"
|
||||
}], "futureNegotiationHint": { "safe": true } },
|
||||
"unsupported": []
|
||||
},
|
||||
"commands": [],
|
||||
"events": [
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_future_01", "sourceSeq": 1, "sourceInstanceId": "runner_semantic_future",
|
||||
"sourceKind": "runner", "runId": "run_semantic_future", "normalizedSessionId": "session_semantic_future", "turnId": "turn_semantic_future", "itemId": "item_semantic_future",
|
||||
"eventType": "mcp_app.tool_input", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:50:00.000Z",
|
||||
"payload": { "semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "input", "operationId": "get_task_context", "callId": "call_semantic_future",
|
||||
"correlation": { "runId": "run_semantic_future", "normalizedSessionId": "session_semantic_future", "turnId": "turn_semantic_future", "itemId": "item_semantic_future", "futureCorrelationHint": 1 },
|
||||
"idempotencyKey": null,
|
||||
"content": { "digest": "sha256:7777777777777777777777777777777777777777777777777777777777777777", "redactionDisposition": "digest_only", "references": [], "futureContentHint": "optional" },
|
||||
"futureEnvelopeHint": { "version": "1.1-preview" }
|
||||
} }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_future_02", "sourceSeq": 2, "sourceInstanceId": "runner_semantic_future",
|
||||
"sourceKind": "runner", "runId": "run_semantic_future", "normalizedSessionId": "session_semantic_future", "turnId": "turn_semantic_future", "itemId": "item_semantic_future",
|
||||
"eventType": "mcp_app.tool_result", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T10:50:00.010Z",
|
||||
"payload": { "semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "result", "operationId": "get_task_context", "callId": "call_semantic_future",
|
||||
"correlation": { "runId": "run_semantic_future", "normalizedSessionId": "session_semantic_future", "turnId": "turn_semantic_future", "itemId": "item_semantic_future", "futureCorrelationHint": 1 },
|
||||
"idempotencyKey": null,
|
||||
"content": { "digest": "sha256:8888888888888888888888888888888888888888888888888888888888888888", "redactionDisposition": "digest_only", "references": [{ "kind": "task", "id": "issue_semantic_future" }], "futureContentHint": "optional" },
|
||||
"outcome": "succeeded", "code": "ok", "retryable": false, "authorizationBoundary": "active_task", "operationReceiptId": "receipt_semantic_future", "currentRevision": 1,
|
||||
"futureEnvelopeHint": { "version": "1.1-preview" }
|
||||
} }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_future_03", "sourceSeq": 3, "sourceInstanceId": "runner_semantic_future",
|
||||
"sourceKind": "runner", "runId": "run_semantic_future", "normalizedSessionId": "session_semantic_future", "turnId": "turn_semantic_future",
|
||||
"eventType": "run.result.proposed", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:50:00.020Z",
|
||||
"payload": {
|
||||
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "done", "summary": "Unknown optional receipt fields were ignored by the v1 projection.",
|
||||
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] },
|
||||
"evidence": [{ "receiptId": "receipt_semantic_future" }], "verification": [{ "commandOrCheck": "optional field projection stability", "status": "passed" }],
|
||||
"attentionRequests": [], "artifacts": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_future_04", "sourceSeq": 4, "sourceInstanceId": "runner_semantic_future",
|
||||
"sourceKind": "runner", "runId": "run_semantic_future", "normalizedSessionId": "session_semantic_future", "turnId": "turn_semantic_future",
|
||||
"eventType": "run.terminal", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T10:50:00.030Z",
|
||||
"payload": { "schema": "paperclip.prp.terminal.v1", "turnTerminalState": "completed", "runTerminalState": "succeeded", "reportedWorkDisposition": "done", "workAssessmentId": "assessment_semantic_future", "statusDecisionId": "decision_semantic_future" }
|
||||
}
|
||||
],
|
||||
"requests": [],
|
||||
"result": {
|
||||
"schema": "paperclip.run_result.v1", "reportedWorkDisposition": "done", "summary": "Unknown optional receipt fields were ignored by the v1 projection.",
|
||||
"completionClaim": { "contractRevision": "semantic-receipts-v1", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] },
|
||||
"evidence": [{ "receiptId": "receipt_semantic_future" }], "verification": [{ "commandOrCheck": "optional field projection stability", "status": "passed" }],
|
||||
"attentionRequests": [], "artifacts": []
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"schema": "paperclip.prp.fixture.v1",
|
||||
"fixtureVersion": 1,
|
||||
"protocolVersion": 1,
|
||||
"name": "Unsupported semantic tool envelope version",
|
||||
"description": "A required semantic_tool v2 envelope must fail closed in a PRP v1 consumer.",
|
||||
"identity": {
|
||||
"schema": "paperclip.prp.identity.v1", "companyId": "company_semantic_v2", "issueId": "issue_semantic_v2", "runId": "run_semantic_v2",
|
||||
"environmentLeaseId": "lease_semantic_v2", "runnerInstanceId": "runner_semantic_v2", "normalizedSessionId": "session_semantic_v2", "driverSessionId": "driver_semantic_v2"
|
||||
},
|
||||
"capabilities": {
|
||||
"schema": "paperclip.prp.capabilities.v1", "sessionReusePolicy": "new_per_run", "driver": { "kind": "provider-neutral", "version": "1.0.0" },
|
||||
"steer": false, "interrupt": true, "resume": true, "runtimeRequests": false, "structuredResult": true, "typedEvents": true, "unsupported": []
|
||||
},
|
||||
"commands": [],
|
||||
"events": [
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_v2_01", "sourceSeq": 1, "sourceInstanceId": "runner_semantic_v2",
|
||||
"sourceKind": "runner", "runId": "run_semantic_v2", "normalizedSessionId": "session_semantic_v2", "turnId": "turn_semantic_v2", "itemId": "item_semantic_v2",
|
||||
"eventType": "mcp_app.tool_input", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T11:00:00.000Z",
|
||||
"payload": { "semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v2", "schemaVersion": 2, "phase": "input", "operationId": "get_task_context", "callId": "call_semantic_v2",
|
||||
"correlation": { "runId": "run_semantic_v2", "normalizedSessionId": "session_semantic_v2", "turnId": "turn_semantic_v2", "itemId": "item_semantic_v2" },
|
||||
"idempotencyKey": null,
|
||||
"content": { "digest": "sha256:9999999999999999999999999999999999999999999999999999999999999999", "redactionDisposition": "digest_only", "references": [] }
|
||||
} }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_v2_02", "sourceSeq": 2, "sourceInstanceId": "runner_semantic_v2",
|
||||
"sourceKind": "runner", "runId": "run_semantic_v2", "normalizedSessionId": "session_semantic_v2", "turnId": "turn_semantic_v2", "itemId": "item_semantic_v2",
|
||||
"eventType": "mcp_app.tool_result", "schemaVersion": 1, "priority": 1, "emittedAt": "2026-08-11T11:00:00.010Z",
|
||||
"payload": { "semantic_tool": {
|
||||
"schema": "paperclip.prp.semantic_tool.v1", "schemaVersion": 1, "phase": "result", "operationId": "get_task_context", "callId": "call_semantic_v2",
|
||||
"correlation": { "runId": "run_semantic_v2", "normalizedSessionId": "session_semantic_v2", "turnId": "turn_semantic_v2", "itemId": "item_semantic_v2" },
|
||||
"idempotencyKey": null,
|
||||
"content": { "digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000", "redactionDisposition": "digest_only", "references": [] },
|
||||
"outcome": "succeeded", "code": "ok", "retryable": false, "authorizationBoundary": "active_task", "operationReceiptId": "receipt_semantic_v2"
|
||||
} }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_v2_03", "sourceSeq": 3, "sourceInstanceId": "runner_semantic_v2",
|
||||
"sourceKind": "runner", "runId": "run_semantic_v2", "normalizedSessionId": "session_semantic_v2", "turnId": "turn_semantic_v2",
|
||||
"eventType": "run.result.proposed", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T11:00:00.020Z",
|
||||
"payload": { "schema": "paperclip.run_result.v1", "reportedWorkDisposition": "done", "summary": "This result must not replay.", "completionClaim": { "contractRevision": "semantic-receipts-v2", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] }, "evidence": [], "verification": [], "attentionRequests": [], "artifacts": [] }
|
||||
},
|
||||
{
|
||||
"schema": "paperclip.prp.event.v1", "sourceEventId": "semantic_v2_04", "sourceSeq": 4, "sourceInstanceId": "runner_semantic_v2",
|
||||
"sourceKind": "runner", "runId": "run_semantic_v2", "normalizedSessionId": "session_semantic_v2", "turnId": "turn_semantic_v2",
|
||||
"eventType": "run.terminal", "schemaVersion": 1, "priority": 0, "emittedAt": "2026-08-11T11:00:00.030Z",
|
||||
"payload": { "schema": "paperclip.prp.terminal.v1", "turnTerminalState": "completed", "runTerminalState": "succeeded", "reportedWorkDisposition": "done" }
|
||||
}
|
||||
],
|
||||
"requests": [],
|
||||
"result": { "schema": "paperclip.run_result.v1", "reportedWorkDisposition": "done", "summary": "This result must not replay.", "completionClaim": { "contractRevision": "semantic-receipts-v2", "objectiveSatisfied": true, "criteria": [], "remainingWork": [] }, "evidence": [], "verification": [], "attentionRequests": [], "artifacts": [] }
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@
|
|||
{
|
||||
"path": "schemas/semantic-tool.schema.json",
|
||||
"id": "https://paperclip.dev/schemas/prp/v1/semantic-tool.schema.json",
|
||||
"sha256": "f53f6832c0d65caaccf540662fdb08cf62d9bab45a18def7b559c4068ef54efc"
|
||||
"sha256": "72dba6b17be1358160633e2ac430002b8be1ab311a236289a942cb05b21385da"
|
||||
},
|
||||
{
|
||||
"path": "schemas/stop-reason.schema.json",
|
||||
|
|
@ -235,6 +235,66 @@
|
|||
"expectation": "accept",
|
||||
"compatibilityCase": "deterministic-replay-oracle"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/golden/semantic-tool-artifact-happy-path.snapshot.json",
|
||||
"sha256": "78d61ea44793714dc652b86e0db8b2fdfd8e7f8617f03de6bd88904f51b20491",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "deterministic-replay-oracle"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/golden/semantic-tool-artifact-happy-path.summary.json",
|
||||
"sha256": "8b3e72e071f7458e67180fdd9933bb89cb6458f2507f2706000c1f172d1e529f",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "deterministic-replay-oracle"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/golden/semantic-tool-conflict-duplicate-retry.snapshot.json",
|
||||
"sha256": "c236827d1cadd5841a058c3f5ec0d6a542a4676b2ce65dee65e8435216266545",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "deterministic-replay-oracle"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/golden/semantic-tool-conflict-duplicate-retry.summary.json",
|
||||
"sha256": "f0b277d37c422b90d2012ad61ea5fbe214ebf1607f1a6c2419c518c4f9a52b7e",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "deterministic-replay-oracle"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/golden/semantic-tool-denial-redaction.snapshot.json",
|
||||
"sha256": "368678297d7763ea5d5d453afa9369e22484ecfc70de7f5769f4cbb05c372565",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "deterministic-replay-oracle"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/golden/semantic-tool-denial-redaction.summary.json",
|
||||
"sha256": "b0379c2ea8d30e9c2866d3737d9fdc73711de24a955364bd78b75769d925764f",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "deterministic-replay-oracle"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/golden/semantic-tool-governance-wake-monitor.snapshot.json",
|
||||
"sha256": "4c5f747c497d1a4a1ba1af4e7efaf3951dafc8e095f8f9816fa868dac03803f6",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "deterministic-replay-oracle"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/golden/semantic-tool-governance-wake-monitor.summary.json",
|
||||
"sha256": "a20a038c04e3f023f7eb478b67144fca48dd288cf484e74d66810d92debc62e5",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "deterministic-replay-oracle"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/golden/semantic-tool-unknown-optional-envelope.snapshot.json",
|
||||
"sha256": "27fbc947268d2b2a5a60127cd6b575a820b235bc7bce5ec7ade6a6196d4652b6",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "deterministic-replay-oracle"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/golden/semantic-tool-unknown-optional-envelope.summary.json",
|
||||
"sha256": "51fb686791aa158f9a23a0c2b90ca24d43bcc953add9e3277ab3f01b958fc26a",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "deterministic-replay-oracle"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/golden/source-gap.snapshot.json",
|
||||
"sha256": "7db7f25e423a721e60adfcdfee02db6d5e6fcd4a644ee180f5e29810ebbc0d78",
|
||||
|
|
@ -271,6 +331,42 @@
|
|||
"expectation": "accept",
|
||||
"compatibilityCase": "canonical"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/semantic-tool-artifact-happy-path.json",
|
||||
"sha256": "17e4e7ef03563e26111d028b056985c80a760bf6d45726a4e30bc09c1a43ae38",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "canonical"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/semantic-tool-conflict-duplicate-retry.json",
|
||||
"sha256": "8298d73d1be7e54522e1947a6b029f6e58a5a41d4cc190a335ac50d2199d1a2e",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "canonical"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/semantic-tool-denial-redaction.json",
|
||||
"sha256": "4409606c6ae1becd2708f4ff7c5ca82921901cbd0d5dfada171238b032e5343c",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "canonical"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/semantic-tool-governance-wake-monitor.json",
|
||||
"sha256": "56f14ca3284e01894ee8ee70b26d4cdc1181d4e09362e3aa89170fb81f4537ff",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "canonical"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/semantic-tool-unknown-optional-envelope.json",
|
||||
"sha256": "53b90800c7d49877892b4e7b0a3bde21bdda6faa55ac89a008a2ff5420e79d85",
|
||||
"expectation": "accept",
|
||||
"compatibilityCase": "canonical"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/semantic-tool-unsupported-required-version.json",
|
||||
"sha256": "ae1a1a873133f11e18f94b0005318000963e1c747dc4e85ae2dfdc08570aadfa",
|
||||
"expectation": "reject",
|
||||
"compatibilityCase": "unknown-required-version"
|
||||
},
|
||||
{
|
||||
"path": "fixtures/replay/source-gap.json",
|
||||
"sha256": "d8a3eb2861e8198226360e666e5296ff5d7215f81d255a273b21d2bfe7dcc2ef",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
"properties": {
|
||||
"schema": { "const": "paperclip.prp.semantic_tool.v1" },
|
||||
"schemaVersion": { "const": 1 },
|
||||
"phase": { "enum": ["input", "result"] },
|
||||
"phase": { "enum": ["input", "result", "reconciled"] },
|
||||
"operationId": { "$ref": "#/$defs/stableId" },
|
||||
"callId": { "$ref": "#/$defs/stableId" },
|
||||
"correlation": {
|
||||
|
|
@ -80,7 +80,10 @@
|
|||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": { "properties": { "phase": { "const": "result" } } },
|
||||
"if": {
|
||||
"properties": { "phase": { "enum": ["result", "reconciled"] } },
|
||||
"required": ["phase"]
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"outcome",
|
||||
|
|
|
|||
|
|
@ -22,8 +22,10 @@ const protocolRoot = resolve(packageRoot, "protocol");
|
|||
const schemaDirectory = resolve(protocolRoot, "schemas");
|
||||
const fixtureDirectory = resolve(protocolRoot, "fixtures");
|
||||
const outputPath = resolve(protocolRoot, "manifest.json");
|
||||
const expectedRejectedFixture =
|
||||
"fixtures/replay/unsupported-required-version.json";
|
||||
const expectedRejectedFixtures = new Set([
|
||||
"fixtures/replay/unsupported-required-version.json",
|
||||
"fixtures/replay/semantic-tool-unsupported-required-version.json",
|
||||
]);
|
||||
|
||||
export async function buildProtocolManifest() {
|
||||
const schemas = await loadSchemaCatalog(schemaDirectory);
|
||||
|
|
@ -42,7 +44,7 @@ export async function buildProtocolManifest() {
|
|||
if (relativePath.startsWith("fixtures/replay/golden/")) {
|
||||
compatibilityCase = "deterministic-replay-oracle";
|
||||
} else if (relativePath.startsWith("fixtures/replay/")) {
|
||||
if (relativePath === expectedRejectedFixture) {
|
||||
if (expectedRejectedFixtures.has(relativePath)) {
|
||||
expectation = "reject";
|
||||
compatibilityCase = "unknown-required-version";
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ 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",
|
||||
"semantic-tool-unknown-optional-envelope",
|
||||
];
|
||||
const check = process.argv.includes("--check");
|
||||
const stale = [];
|
||||
|
|
|
|||
|
|
@ -170,6 +170,10 @@ export function assertReplayFixtureCompatibility(fixture) {
|
|||
for (const [index, event] of fixture.events.entries()) {
|
||||
requireSchema(event, "paperclip.prp.event.v1", `events[${index}]`);
|
||||
requireVersion(event.schemaVersion, SUPPORTED_EVENT_SCHEMA_VERSION, `events[${index}].schemaVersion`);
|
||||
const semanticToolVersion = event.payload?.semantic_tool?.schemaVersion;
|
||||
if (semanticToolVersion !== undefined) {
|
||||
requireVersion(semanticToolVersion, 1, `events[${index}].payload.semantic_tool.schemaVersion`);
|
||||
}
|
||||
}
|
||||
|
||||
requireSchema(fixture.result, "paperclip.run_result.v1", "result");
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export {
|
|||
export type { DurableRecoveryIdentity } from "./control-plane/prp-transport-types.js";
|
||||
export * from "./protocol/replay-contract.js";
|
||||
export * from "./protocol/result-normalization.js";
|
||||
export * from "./protocol/semantic-tool-receipts.js";
|
||||
export * from "./provider-events.js";
|
||||
export * from "./reducer/session-reducer.js";
|
||||
export * from "./semantic-tools/index.js";
|
||||
|
|
|
|||
|
|
@ -1866,7 +1866,8 @@ export const semanticToolSchema = {
|
|||
"phase": {
|
||||
"enum": [
|
||||
"input",
|
||||
"result"
|
||||
"result",
|
||||
"reconciled"
|
||||
]
|
||||
},
|
||||
"operationId": {
|
||||
|
|
@ -2002,9 +2003,15 @@ export const semanticToolSchema = {
|
|||
"if": {
|
||||
"properties": {
|
||||
"phase": {
|
||||
"const": "result"
|
||||
"enum": [
|
||||
"result",
|
||||
"reconciled"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"phase"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ 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",
|
||||
"semantic-tool-unknown-optional-envelope.json",
|
||||
];
|
||||
|
||||
async function readFixture(
|
||||
|
|
@ -29,6 +34,31 @@ async function readFixture(
|
|||
) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function reconciledEvent(
|
||||
events: Array<Record<string, unknown>>,
|
||||
): Record<string, unknown> {
|
||||
const reconciled = structuredClone(events[0]!);
|
||||
reconciled.sourceEventId = "semantic_happy_reconciled";
|
||||
reconciled.sourceSeq = 2;
|
||||
reconciled.eventType = "semantic_tool.reconciled";
|
||||
const payload = reconciled.payload as Record<string, unknown>;
|
||||
const semanticTool = payload.semantic_tool as Record<string, unknown>;
|
||||
const resultPayload = events[1]!.payload as Record<string, unknown>;
|
||||
const result = resultPayload.semantic_tool as Record<string, unknown>;
|
||||
semanticTool.phase = "reconciled";
|
||||
for (const field of [
|
||||
"content",
|
||||
"outcome",
|
||||
"code",
|
||||
"retryable",
|
||||
"authorizationBoundary",
|
||||
"operationReceiptId",
|
||||
]) {
|
||||
semanticTool[field] = structuredClone(result[field]);
|
||||
}
|
||||
return reconciled;
|
||||
}
|
||||
|
||||
describe("PRP v1 JSON Schema contract", () => {
|
||||
for (const fixtureName of validFixtures) {
|
||||
it(`validates ${fixtureName}`, async () => {
|
||||
|
|
@ -73,6 +103,169 @@ 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("accepts a pending semantic call closed by reconciliation alone", async () => {
|
||||
const fixture = await readFixture("semantic-tool-artifact-happy-path.json");
|
||||
const events = fixture.events as Array<Record<string, unknown>>;
|
||||
const reconciled = reconciledEvent(events);
|
||||
events[1] = reconciled;
|
||||
|
||||
expect(parsePrpFixtureText(JSON.stringify(fixture))).toMatchObject({
|
||||
ok: true,
|
||||
});
|
||||
|
||||
const semanticTool = (
|
||||
reconciled.payload as Record<string, unknown>
|
||||
).semantic_tool as Record<string, unknown>;
|
||||
delete semanticTool.outcome;
|
||||
expect(parsePrpFixtureText(JSON.stringify(fixture))).toMatchObject({
|
||||
ok: false,
|
||||
issues: expect.arrayContaining([
|
||||
expect.objectContaining({ code: "schema_validation" }),
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it("binds reconciliation identity while preserving recovered result content", async () => {
|
||||
const fixture = await readFixture("semantic-tool-artifact-happy-path.json");
|
||||
const events = fixture.events as Array<Record<string, unknown>>;
|
||||
const reconciled = reconciledEvent(events);
|
||||
const payload = reconciled.payload as Record<string, unknown>;
|
||||
const semanticTool = payload.semantic_tool as Record<string, unknown>;
|
||||
events[1] = reconciled;
|
||||
|
||||
expect(parsePrpFixtureText(JSON.stringify(fixture))).toMatchObject({
|
||||
ok: true,
|
||||
});
|
||||
expect(
|
||||
(semanticTool.content as Record<string, unknown>).digest,
|
||||
).toBe("sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
|
||||
|
||||
semanticTool.operationId = "different_operation";
|
||||
expect(parsePrpFixtureText(JSON.stringify(fixture))).toMatchObject({
|
||||
ok: false,
|
||||
issues: [
|
||||
expect.objectContaining({
|
||||
path: "/events/1/payload/semantic_tool/operationId",
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
semanticTool.operationId = (
|
||||
(events[0]!.payload as Record<string, unknown>).semantic_tool as Record<
|
||||
string,
|
||||
unknown
|
||||
>
|
||||
).operationId;
|
||||
reconciled.turnId = "different-turn";
|
||||
(semanticTool.correlation as Record<string, unknown>).turnId =
|
||||
"different-turn";
|
||||
expect(parsePrpFixtureText(JSON.stringify(fixture))).toMatchObject({
|
||||
ok: false,
|
||||
issues: [
|
||||
expect.objectContaining({
|
||||
path: "/events/1/payload/semantic_tool/correlation/turnId",
|
||||
}),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it(
|
||||
"allows reconciliation to omit optional correlation metadata",
|
||||
async () => {
|
||||
const fixture = await readFixture(
|
||||
"semantic-tool-artifact-happy-path.json",
|
||||
);
|
||||
const events = fixture.events as Array<Record<string, unknown>>;
|
||||
const input = (events[0]!.payload as Record<string, unknown>)
|
||||
.semantic_tool as Record<string, unknown>;
|
||||
const inputCorrelation = input.correlation as Record<string, unknown>;
|
||||
inputCorrelation.requestId = "request_semantic_happy";
|
||||
inputCorrelation.futureTraceId = "trace_semantic_happy";
|
||||
|
||||
const reconciled = reconciledEvent(events);
|
||||
const reconciledEnvelope = (
|
||||
reconciled.payload as Record<string, unknown>
|
||||
).semantic_tool as Record<string, unknown>;
|
||||
const reconciledCorrelation = reconciledEnvelope.correlation as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
delete reconciledCorrelation.requestId;
|
||||
delete reconciledCorrelation.futureTraceId;
|
||||
events[1] = reconciled;
|
||||
|
||||
expect(parsePrpFixtureText(JSON.stringify(fixture))).toMatchObject({
|
||||
ok: true,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it(
|
||||
"preserves replacement-runner provenance during reconciliation",
|
||||
async () => {
|
||||
const fixture = await readFixture(
|
||||
"semantic-tool-artifact-happy-path.json",
|
||||
);
|
||||
const events = fixture.events as Array<Record<string, unknown>>;
|
||||
const reconciled = reconciledEvent(events);
|
||||
const inputSourceInstanceId = events[0]!.sourceInstanceId;
|
||||
reconciled.sourceInstanceId = "runner_semantic_other";
|
||||
events[1] = reconciled;
|
||||
|
||||
const result = parsePrpFixtureText(JSON.stringify(fixture));
|
||||
expect(result).toMatchObject({ ok: true });
|
||||
if (result.ok) {
|
||||
expect(result.fixture.events[0]?.sourceInstanceId).toBe(
|
||||
inputSourceInstanceId,
|
||||
);
|
||||
expect(result.fixture.events[1]?.sourceInstanceId).toBe(
|
||||
"runner_semantic_other",
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects result and reconciliation as two terminal phases for one call", async () => {
|
||||
const fixture = await readFixture("semantic-tool-artifact-happy-path.json");
|
||||
const events = fixture.events as Array<Record<string, unknown>>;
|
||||
const reconciled = reconciledEvent(events);
|
||||
for (const event of events.slice(1)) {
|
||||
event.sourceSeq = Number(event.sourceSeq) + 1;
|
||||
}
|
||||
events.splice(1, 0, reconciled);
|
||||
|
||||
expect(parsePrpFixtureText(JSON.stringify(fixture))).toMatchObject({
|
||||
ok: false,
|
||||
issues: [
|
||||
expect.objectContaining({
|
||||
path: "/events/0/payload/semantic_tool/callId",
|
||||
message: expect.stringContaining("exactly one result or reconciled"),
|
||||
}),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("fails closed on unsupported nested required schema versions", async () => {
|
||||
const fixture = await readFixture();
|
||||
const events = fixture.events as Array<Record<string, unknown>>;
|
||||
|
|
|
|||
|
|
@ -207,14 +207,20 @@ function ajvIssue(error: ErrorObject): ProtocolValidationIssue {
|
|||
};
|
||||
}
|
||||
|
||||
interface SemanticCallBinding {
|
||||
envelope: PrpSemanticToolEnvelope;
|
||||
index: number;
|
||||
}
|
||||
|
||||
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 };
|
||||
input?: SemanticCallBinding;
|
||||
result?: SemanticCallBinding;
|
||||
reconciled?: SemanticCallBinding;
|
||||
}
|
||||
>();
|
||||
fixture.events.forEach((event, index) => {
|
||||
|
|
@ -283,32 +289,77 @@ function bindingIssues(fixture: PrpFixture): ProtocolValidationIssue[] {
|
|||
message: `semantic_tool call ${semanticTool.callId} must contain exactly one ${phase} envelope`,
|
||||
});
|
||||
} else {
|
||||
call[phase] = { envelope: semanticTool, index };
|
||||
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;
|
||||
const terminalPhaseCount =
|
||||
Number(call.result !== undefined) + Number(call.reconciled !== undefined);
|
||||
if (call.input === undefined || terminalPhaseCount !== 1) {
|
||||
const present = call.input ?? call.result ?? call.reconciled;
|
||||
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`,
|
||||
message: `semantic_tool call ${callId} must contain one input and exactly one result or reconciled 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`,
|
||||
});
|
||||
if (call.result !== undefined) {
|
||||
for (const field of [
|
||||
"operationId",
|
||||
"idempotencyKey",
|
||||
"correlation",
|
||||
] 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`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (call.reconciled !== undefined) {
|
||||
// A replacement runner may reconcile a call after recovering the run.
|
||||
// The authenticated ingestion boundary owns runner authorization, while
|
||||
// replay keeps each event's sourceInstanceId as immutable provenance.
|
||||
for (const field of ["operationId", "idempotencyKey"] as const) {
|
||||
if (
|
||||
canonicalJson(call.input.envelope[field]) !==
|
||||
canonicalJson(call.reconciled.envelope[field])
|
||||
) {
|
||||
issues.push({
|
||||
code: "binding_mismatch",
|
||||
path: `/events/${call.reconciled.index}/payload/semantic_tool/${field}`,
|
||||
message: `semantic_tool reconciled ${field} must match its input envelope`,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const field of [
|
||||
"runId",
|
||||
"normalizedSessionId",
|
||||
"turnId",
|
||||
"itemId",
|
||||
] as const) {
|
||||
if (
|
||||
call.input.envelope.correlation[field] !==
|
||||
call.reconciled.envelope.correlation[field]
|
||||
) {
|
||||
issues.push({
|
||||
code: "binding_mismatch",
|
||||
path: `/events/${call.reconciled.index}/payload/semantic_tool/correlation/${field}`,
|
||||
message: `semantic_tool reconciled correlation ${field} must match its input envelope`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,149 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { validatePrpEvent, type PrpEvent } from "./replay-contract.js";
|
||||
import {
|
||||
createPrpBudgetStopReason,
|
||||
createPrpSemanticToolInputEnvelope,
|
||||
createPrpSemanticToolReconciledEnvelope,
|
||||
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("requires a complete terminal receipt when a pending tool call is reconciled", () => {
|
||||
const semanticTool = createPrpSemanticToolReconciledEnvelope({
|
||||
operationId: "get_task_context",
|
||||
callId: "call_reconciled_test",
|
||||
correlation,
|
||||
content: {},
|
||||
outcome: "succeeded",
|
||||
code: "recovered_receipt",
|
||||
retryable: false,
|
||||
authorizationBoundary: "active_task",
|
||||
});
|
||||
const reconciled = event("semantic_tool.reconciled", semanticTool);
|
||||
|
||||
expect(validatePrpEvent(reconciled).ok).toBe(true);
|
||||
expect(
|
||||
validatePrpEvent({
|
||||
...reconciled,
|
||||
payload: {
|
||||
semantic_tool: {
|
||||
...semanticTool,
|
||||
outcome: undefined,
|
||||
},
|
||||
},
|
||||
}).ok,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
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" | "semantic_tool.reconciled",
|
||||
semanticTool: Record<string, unknown>,
|
||||
): PrpEvent {
|
||||
return {
|
||||
schema: "paperclip.prp.event.v1",
|
||||
sourceEventId: `${eventType}:receipt_test`,
|
||||
sourceSeq: eventType === "mcp_app.tool_input" ? 1 : eventType === "mcp_app.tool_result" ? 2 : 3,
|
||||
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,253 @@
|
|||
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 {
|
||||
return createPrpSemanticToolTerminalEnvelope(input, "result");
|
||||
}
|
||||
|
||||
/** Creates a complete terminal receipt for an outcome recovered after restart. */
|
||||
export function createPrpSemanticToolReconciledEnvelope(
|
||||
input: CreatePrpSemanticToolResultInput,
|
||||
): PrpSemanticToolEnvelope {
|
||||
return createPrpSemanticToolTerminalEnvelope(input, "reconciled");
|
||||
}
|
||||
|
||||
function createPrpSemanticToolTerminalEnvelope(
|
||||
input: CreatePrpSemanticToolResultInput,
|
||||
phase: "result" | "reconciled",
|
||||
): PrpSemanticToolEnvelope {
|
||||
const operationReceiptId = input.operationReceiptId ?? derivedReceiptId(input);
|
||||
return {
|
||||
schema: "paperclip.prp.semantic_tool.v1",
|
||||
schemaVersion: 1,
|
||||
phase,
|
||||
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";
|
||||
}
|
||||
Loading…
Reference in New Issue