@mcp-audit-gateway/core 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +37 -0
- package/README.md +31 -3
- package/dist/attestation/audit-log.d.ts +34 -3
- package/dist/attestation/audit-log.d.ts.map +1 -1
- package/dist/attestation/audit-log.js +286 -10
- package/dist/attestation/audit-log.js.map +1 -1
- package/dist/attestation/checkpoint.test.d.ts +2 -0
- package/dist/attestation/checkpoint.test.d.ts.map +1 -0
- package/dist/attestation/checkpoint.test.js +870 -0
- package/dist/attestation/checkpoint.test.js.map +1 -0
- package/dist/attestation/signer.d.ts +24 -9
- package/dist/attestation/signer.d.ts.map +1 -1
- package/dist/attestation/signer.js +145 -11
- package/dist/attestation/signer.js.map +1 -1
- package/dist/attestation/signer.test.js +11 -0
- package/dist/attestation/signer.test.js.map +1 -1
- package/dist/attestation/verify.d.ts +23 -2
- package/dist/attestation/verify.d.ts.map +1 -1
- package/dist/attestation/verify.js +219 -2
- package/dist/attestation/verify.js.map +1 -1
- package/dist/integration.test.js +1 -0
- package/dist/integration.test.js.map +1 -1
- package/dist/proxy/gateway.d.ts +4 -0
- package/dist/proxy/gateway.d.ts.map +1 -1
- package/dist/proxy/gateway.js +4 -1
- package/dist/proxy/gateway.js.map +1 -1
- package/dist/proxy/gateway.test.js +19 -0
- package/dist/proxy/gateway.test.js.map +1 -1
- package/dist/proxy/mcp-server-adapter.d.ts +1 -0
- package/dist/proxy/mcp-server-adapter.d.ts.map +1 -1
- package/dist/proxy/mcp-server-adapter.js +28 -1
- package/dist/proxy/mcp-server-adapter.js.map +1 -1
- package/dist/proxy/mcp-server-adapter.test.js +1 -0
- package/dist/proxy/mcp-server-adapter.test.js.map +1 -1
- package/dist/types.d.ts +74 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -1
- package/dist/wrap/proxy.test.js +2 -2
- package/dist/wrap/proxy.test.js.map +1 -1
- package/docs/BACKLOG.md +33 -0
- package/docs/SECURITY-DESIGN.md +126 -0
- package/docs/v0.4.0-patch-audit.md +115 -0
- package/package.json +1 -1
- package/src/attestation/audit-log.ts +336 -15
- package/src/attestation/checkpoint.test.ts +956 -0
- package/src/attestation/signer.test.ts +14 -0
- package/src/attestation/signer.ts +152 -19
- package/src/attestation/verify.ts +270 -4
- package/src/integration.test.ts +1 -0
- package/src/proxy/gateway.test.ts +18 -0
- package/src/proxy/gateway.ts +4 -0
- package/src/proxy/mcp-server-adapter.test.ts +1 -0
- package/src/proxy/mcp-server-adapter.ts +26 -0
- package/src/types.ts +48 -0
- package/src/wrap/proxy.test.ts +2 -2
- package/test/vectors/aps-action-ref-v1-vectors.json +351 -0
- package/test/vectors/aps-action-ref-v1.mjs +145 -0
- package/test/vectors/canonicalization.json +182 -0
- package/test/vectors/checkpoint.json +450 -0
- package/test/vectors/verify-checkpoint.mjs +344 -0
- package/test/vectors/verify-checkpoint.py +358 -0
- package/test/vectors/verify.mjs +74 -1
- package/test/vectors/verify.py +78 -1
|
@@ -76,6 +76,7 @@ export class McpServerAdapter {
|
|
|
76
76
|
async (request) => {
|
|
77
77
|
const principal = this.extractPrincipal(request.params?._meta);
|
|
78
78
|
const traceContext = this.extractTraceContext(request.params?._meta);
|
|
79
|
+
const aiInvocation = this.extractAiInvocation(request.params?._meta);
|
|
79
80
|
const toolName = request.params.name;
|
|
80
81
|
const args = (request.params.arguments ?? {}) as Record<string, unknown>;
|
|
81
82
|
|
|
@@ -85,6 +86,7 @@ export class McpServerAdapter {
|
|
|
85
86
|
args,
|
|
86
87
|
principal,
|
|
87
88
|
traceContext,
|
|
89
|
+
aiInvocation,
|
|
88
90
|
);
|
|
89
91
|
|
|
90
92
|
const upstreamResult = result as {
|
|
@@ -96,6 +98,7 @@ export class McpServerAdapter {
|
|
|
96
98
|
content: upstreamResult?.content ?? [{ type: "text" as const, text: JSON.stringify(result) }],
|
|
97
99
|
isError: upstreamResult?.isError,
|
|
98
100
|
_meta: {
|
|
101
|
+
...(aiInvocation?.turnId ? { "io.modelcontextprotocol/aiInvocation": { turnId: aiInvocation.turnId } } : {}),
|
|
99
102
|
"x-gateway-attestation/v1": {
|
|
100
103
|
auditId: auditRecord.id,
|
|
101
104
|
attestation: auditRecord.attestation,
|
|
@@ -109,6 +112,7 @@ export class McpServerAdapter {
|
|
|
109
112
|
content: [{ type: "text" as const, text: err.message }],
|
|
110
113
|
isError: true,
|
|
111
114
|
_meta: {
|
|
115
|
+
...(aiInvocation?.turnId ? { "io.modelcontextprotocol/aiInvocation": { turnId: aiInvocation.turnId } } : {}),
|
|
112
116
|
"x-gateway-attestation/v1": {
|
|
113
117
|
auditId: err.auditRecord.id,
|
|
114
118
|
attestation: err.auditRecord.attestation,
|
|
@@ -137,4 +141,26 @@ export class McpServerAdapter {
|
|
|
137
141
|
const tc = meta.traceContext as { traceparent?: string; tracestate?: string } | undefined;
|
|
138
142
|
return tc;
|
|
139
143
|
}
|
|
144
|
+
|
|
145
|
+
private extractAiInvocation(
|
|
146
|
+
meta?: Record<string, unknown>,
|
|
147
|
+
): { turnId?: string; invocationReason?: string; model?: string } | undefined {
|
|
148
|
+
if (!meta) return undefined;
|
|
149
|
+
const key = "io.modelcontextprotocol/aiInvocation";
|
|
150
|
+
const inv = meta[key] as Record<string, unknown> | undefined;
|
|
151
|
+
if (!inv) return undefined;
|
|
152
|
+
const result: { turnId?: string; invocationReason?: string; model?: string } = {};
|
|
153
|
+
if (typeof inv.turnId === "string") result.turnId = inv.turnId;
|
|
154
|
+
if (typeof inv.invocationReason === "string") {
|
|
155
|
+
result.invocationReason = inv.invocationReason;
|
|
156
|
+
} else if (inv.invocationReason && typeof (inv.invocationReason as Record<string, unknown>).text === "string") {
|
|
157
|
+
result.invocationReason = (inv.invocationReason as Record<string, unknown>).text as string;
|
|
158
|
+
}
|
|
159
|
+
if (typeof inv.model === "string") {
|
|
160
|
+
result.model = inv.model;
|
|
161
|
+
} else if (inv.model && typeof (inv.model as Record<string, unknown>).name === "string") {
|
|
162
|
+
result.model = (inv.model as Record<string, unknown>).name as string;
|
|
163
|
+
}
|
|
164
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
165
|
+
}
|
|
140
166
|
}
|
package/src/types.ts
CHANGED
|
@@ -48,6 +48,13 @@ export const TelemetryConfigSchema = z.object({
|
|
|
48
48
|
sampleRate: z.number().min(0).max(1).default(1.0),
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
export const CheckpointConfigSchema = z.object({
|
|
52
|
+
enabled: z.boolean().default(false),
|
|
53
|
+
intervalRecords: z.number().positive().default(100),
|
|
54
|
+
intervalSeconds: z.number().positive().default(60),
|
|
55
|
+
trigger: z.enum(["records", "time", "whichever_first"]).default("whichever_first"),
|
|
56
|
+
});
|
|
57
|
+
|
|
51
58
|
export const GatewayConfigSchema = z.object({
|
|
52
59
|
name: z.string().default("mcp-audit-gateway"),
|
|
53
60
|
version: z.string().default("0.1.0"),
|
|
@@ -69,6 +76,7 @@ export const GatewayConfigSchema = z.object({
|
|
|
69
76
|
path: z.string().default("./audit.jsonl"),
|
|
70
77
|
rotateAfterMb: z.number().positive().default(100),
|
|
71
78
|
}).default({}),
|
|
79
|
+
checkpoint: CheckpointConfigSchema.default({}),
|
|
72
80
|
});
|
|
73
81
|
|
|
74
82
|
export type UpstreamConfig = z.infer<typeof UpstreamConfigSchema>;
|
|
@@ -93,6 +101,12 @@ export interface PartyAttribution {
|
|
|
93
101
|
scope: string[];
|
|
94
102
|
}
|
|
95
103
|
|
|
104
|
+
export interface AiInvocationContext {
|
|
105
|
+
turnId?: string;
|
|
106
|
+
invocationReason?: string;
|
|
107
|
+
model?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
96
110
|
export interface AuditRecord {
|
|
97
111
|
id: string;
|
|
98
112
|
timestamp: string;
|
|
@@ -105,11 +119,45 @@ export interface AuditRecord {
|
|
|
105
119
|
success: boolean;
|
|
106
120
|
errorCode?: number;
|
|
107
121
|
decisionContextDigest?: string;
|
|
122
|
+
extensionsDigest?: string;
|
|
123
|
+
aiInvocation?: AiInvocationContext;
|
|
108
124
|
parties?: PartyAttribution[];
|
|
109
125
|
previousHash?: string;
|
|
110
126
|
attestation?: string;
|
|
111
127
|
}
|
|
112
128
|
|
|
129
|
+
export interface CheckpointRecord {
|
|
130
|
+
id: string;
|
|
131
|
+
type: "checkpoint";
|
|
132
|
+
timestamp: string;
|
|
133
|
+
sequence: number;
|
|
134
|
+
recordCount: number;
|
|
135
|
+
previousHash: string;
|
|
136
|
+
parties?: PartyAttribution[];
|
|
137
|
+
attestation?: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface ChainBreakRecord {
|
|
141
|
+
id: string;
|
|
142
|
+
type: "chain_break";
|
|
143
|
+
timestamp: string;
|
|
144
|
+
reason: string;
|
|
145
|
+
priorHead?: string;
|
|
146
|
+
priorSequence?: number;
|
|
147
|
+
priorRecordCount?: number;
|
|
148
|
+
attestation?: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export type ChainRecord = AuditRecord | CheckpointRecord | ChainBreakRecord;
|
|
152
|
+
|
|
153
|
+
export function isCheckpoint(record: ChainRecord): record is CheckpointRecord {
|
|
154
|
+
return "type" in record && (record as CheckpointRecord).type === "checkpoint";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function isChainBreak(record: ChainRecord): record is ChainBreakRecord {
|
|
158
|
+
return "type" in record && (record as ChainBreakRecord).type === "chain_break";
|
|
159
|
+
}
|
|
160
|
+
|
|
113
161
|
export interface ToolEntry {
|
|
114
162
|
name: string;
|
|
115
163
|
originalName: string;
|
package/src/wrap/proxy.test.ts
CHANGED
|
@@ -44,7 +44,7 @@ describe("wrap proxy", () => {
|
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
it("forwards non-tool messages transparently", async () => {
|
|
47
|
+
it.skipIf(!process.env.RUN_E2E)("forwards non-tool messages transparently", async () => {
|
|
48
48
|
const proc = spawn("node", [CLI_PATH, "wrap", "--", "node", "-e", `
|
|
49
49
|
process.stdin.setEncoding('utf-8');
|
|
50
50
|
let buf = '';
|
|
@@ -82,7 +82,7 @@ describe("wrap proxy", () => {
|
|
|
82
82
|
expect(parsed[1].result.resources).toBeDefined();
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
it("logs tool calls with attestation", async () => {
|
|
85
|
+
it.skipIf(!process.env.RUN_E2E)("logs tool calls with attestation", async () => {
|
|
86
86
|
const proc = spawn("node", [CLI_PATH, "wrap", "--", "node", "-e", `
|
|
87
87
|
process.stdin.setEncoding('utf-8');
|
|
88
88
|
let buf = '';
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "aps-action-ref-v1-recompute-negatives-v1",
|
|
3
|
+
"canon_version": "jcs-rfc8785-v1",
|
|
4
|
+
"authored_for": "argentum-core examples/conformance shape conventions (see SOURCE.md)",
|
|
5
|
+
"mirrors_local_branch": "fixtures/action-ref-v1-recompute @ 43223a658b6805c68a679f258a1dfb1a16964338 (local clone only, not pushed)",
|
|
6
|
+
"positive_fixture": {
|
|
7
|
+
"fixture_id": "recompute-drift-v1-positive",
|
|
8
|
+
"version": "v1",
|
|
9
|
+
"spec": "docs/spec/action-ref.md",
|
|
10
|
+
"spec_stable_ref": "https://github.com/giskard09/argentum-core/blob/action-ref-v1.0/docs/spec/action-ref.md",
|
|
11
|
+
"hash_algo": "sha256",
|
|
12
|
+
"preimage_format": "jcs-rfc8785-v1",
|
|
13
|
+
"generated_at": "2026-06-11",
|
|
14
|
+
"purpose": "Positive recomputation vectors for the action-ref-v1 recompute property. Each action_ref was computed by two independent paths (a shipping TypeScript implementation and a stdlib-only SHA-256 over the JCS bytes) and byte-checked equal before inclusion. An external implementer who reproduces these digests byte-identical from the stated preimage fields has a conformant recomputation path.",
|
|
15
|
+
"reproduce_in_python": "import hashlib, json\ndef jcs(obj): return json.dumps(obj, separators=(',',':'), sort_keys=True, ensure_ascii=False)\naction_ref = hashlib.sha256(jcs(preimage).encode()).hexdigest()",
|
|
16
|
+
"vectors": [
|
|
17
|
+
{
|
|
18
|
+
"id": "0001-basic",
|
|
19
|
+
"description": "Minimal baseline, 4 preimage fields, namespace-prefixed scope, mid-range millisecond value. Any conformant implementation must reproduce action_ref byte-identical from the stated preimage.",
|
|
20
|
+
"preimage": {
|
|
21
|
+
"action_type": "trail.anchor",
|
|
22
|
+
"agent_id": "recompute-agent-001",
|
|
23
|
+
"scope": "argentum:recompute_baseline",
|
|
24
|
+
"timestamp": "2026-06-11T10:00:00.123Z"
|
|
25
|
+
},
|
|
26
|
+
"jcs_payload": "{\"action_type\":\"trail.anchor\",\"agent_id\":\"recompute-agent-001\",\"scope\":\"argentum:recompute_baseline\",\"timestamp\":\"2026-06-11T10:00:00.123Z\"}",
|
|
27
|
+
"preimage_canonical_bytes_hex": "7b22616374696f6e5f74797065223a22747261696c2e616e63686f72222c226167656e745f6964223a227265636f6d707574652d6167656e742d303031222c2273636f7065223a22617267656e74756d3a7265636f6d707574655f626173656c696e65222c2274696d657374616d70223a22323032362d30362d31315431303a30303a30302e3132335a227d",
|
|
28
|
+
"action_ref": "de7d7f40147b9b7f41134b14752aa36e3435bda89c51938ba358a69be04dccf6"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "0002-unicode-fields",
|
|
32
|
+
"description": "Non-ASCII BMP characters in agent_id and scope (no surrogate pairs). JCS RFC 8785 emits the raw UTF-8 code points without escaping; an implementation that ASCII-escapes produces a different digest and is non-conformant.",
|
|
33
|
+
"preimage": {
|
|
34
|
+
"action_type": "negotiation.accept",
|
|
35
|
+
"agent_id": "agentur-zürich-07",
|
|
36
|
+
"scope": "mycelium:übergabe:été",
|
|
37
|
+
"timestamp": "2026-06-11T10:15:00.500Z"
|
|
38
|
+
},
|
|
39
|
+
"jcs_payload": "{\"action_type\":\"negotiation.accept\",\"agent_id\":\"agentur-zürich-07\",\"scope\":\"mycelium:übergabe:été\",\"timestamp\":\"2026-06-11T10:15:00.500Z\"}",
|
|
40
|
+
"preimage_canonical_bytes_hex": "7b22616374696f6e5f74797065223a226e65676f74696174696f6e2e616363657074222c226167656e745f6964223a226167656e7475722d7ac3bc726963682d3037222c2273636f7065223a226d7963656c69756d3ac3bc626572676162653ac3a974c3a9222c2274696d657374616d70223a22323032362d30362d31315431303a31353a30302e3530305a227d",
|
|
41
|
+
"action_ref": "a5c0ec37b01673b6ca9d593d4dba6e6f40dafdfc25fde3ad3673dceb79b72cc4"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "0003-empty-scope",
|
|
45
|
+
"description": "Empty-string scope, valid per the spec field table (pass \"\" if not applicable). The empty string still occupies its JCS position; omitting the key instead produces a different digest.",
|
|
46
|
+
"preimage": {
|
|
47
|
+
"action_type": "oracle.signal",
|
|
48
|
+
"agent_id": "recompute-agent-001",
|
|
49
|
+
"scope": "",
|
|
50
|
+
"timestamp": "2026-06-11T10:30:00.250Z"
|
|
51
|
+
},
|
|
52
|
+
"jcs_payload": "{\"action_type\":\"oracle.signal\",\"agent_id\":\"recompute-agent-001\",\"scope\":\"\",\"timestamp\":\"2026-06-11T10:30:00.250Z\"}",
|
|
53
|
+
"preimage_canonical_bytes_hex": "7b22616374696f6e5f74797065223a226f7261636c652e7369676e616c222c226167656e745f6964223a227265636f6d707574652d6167656e742d303031222c2273636f7065223a22222c2274696d657374616d70223a22323032362d30362d31315431303a33303a30302e3235305a227d",
|
|
54
|
+
"action_ref": "8a5e53417ae5a4e1cdfc1659980ece6518611d7610c173ccf7d14121924b241e"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "0004-ms-floor",
|
|
58
|
+
"description": "Millisecond floor edge: fractional part .000. Trailing-zero suppression (emitting .0, or no fractional part) changes the bytes and the digest; exactly three digits are required.",
|
|
59
|
+
"preimage": {
|
|
60
|
+
"action_type": "payment.send",
|
|
61
|
+
"agent_id": "pioneer-agent-001",
|
|
62
|
+
"scope": "nobulex:bilateral",
|
|
63
|
+
"timestamp": "2026-06-11T23:59:59.000Z"
|
|
64
|
+
},
|
|
65
|
+
"jcs_payload": "{\"action_type\":\"payment.send\",\"agent_id\":\"pioneer-agent-001\",\"scope\":\"nobulex:bilateral\",\"timestamp\":\"2026-06-11T23:59:59.000Z\"}",
|
|
66
|
+
"preimage_canonical_bytes_hex": "7b22616374696f6e5f74797065223a227061796d656e742e73656e64222c226167656e745f6964223a2270696f6e6565722d6167656e742d303031222c2273636f7065223a226e6f62756c65783a62696c61746572616c222c2274696d657374616d70223a22323032362d30362d31315432333a35393a35392e3030305a227d",
|
|
67
|
+
"action_ref": "01eb9688d2a1ababeb993c900bd7b7836f9b334f179030fafec2ecc8b33c4f6c"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "0005-ms-ceiling",
|
|
71
|
+
"description": "Millisecond ceiling edge: fractional part .999. Checks that no rounding or carry into the seconds field happens anywhere in the pipeline.",
|
|
72
|
+
"preimage": {
|
|
73
|
+
"action_type": "payment.send",
|
|
74
|
+
"agent_id": "pioneer-agent-001",
|
|
75
|
+
"scope": "nobulex:bilateral",
|
|
76
|
+
"timestamp": "2026-06-11T23:59:59.999Z"
|
|
77
|
+
},
|
|
78
|
+
"jcs_payload": "{\"action_type\":\"payment.send\",\"agent_id\":\"pioneer-agent-001\",\"scope\":\"nobulex:bilateral\",\"timestamp\":\"2026-06-11T23:59:59.999Z\"}",
|
|
79
|
+
"preimage_canonical_bytes_hex": "7b22616374696f6e5f74797065223a227061796d656e742e73656e64222c226167656e745f6964223a2270696f6e6565722d6167656e742d303031222c2273636f7065223a226e6f62756c65783a62696c61746572616c222c2274696d657374616d70223a22323032362d30362d31315432333a35393a35392e3939395a227d",
|
|
80
|
+
"action_ref": "0cc0a719600948603fbefabb5167145df36215f63b7f677a0e84466540b445e5"
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
"negative_fixture": {
|
|
85
|
+
"fixture_id": "recompute-drift-v1-negative",
|
|
86
|
+
"version": "v1",
|
|
87
|
+
"spec": "docs/spec/action-ref.md",
|
|
88
|
+
"spec_stable_ref": "https://github.com/giskard09/argentum-core/blob/action-ref-v1.0/docs/spec/action-ref.md",
|
|
89
|
+
"hash_algo": "sha256",
|
|
90
|
+
"preimage_format": "jcs-rfc8785-v1",
|
|
91
|
+
"generated_at": "2026-06-11",
|
|
92
|
+
"purpose": "Negative recomputation vectors for the action-ref-v1 recompute property. A verifier recomputes action_ref from the invocation payload tuple and MUST fail closed, before invocation, when the claimed action_ref does not match. It must never retry alternative preimages, coerce, or normalize to force a match. Every claimed_action_ref here is a real SHA-256 digest of a stated drifted byte form, not an invented value, so the forbidden behavior each vector targets is explicit.",
|
|
93
|
+
"validation_rule": "A conformant verifier computes SHA-256(JCS(invocation_payload)) exactly once, after checking the timestamp grammar, and compares it to the claimed action_ref. Any grammar violation or digest mismatch is a terminal failure before invocation. A verifier that accepts any vector in this file has a conformance bug.",
|
|
94
|
+
"drift_families": {
|
|
95
|
+
"field_order_drift": "Claim computed over a serialization with fields in received order, no JCS key sort.",
|
|
96
|
+
"timestamp_form_drift": "Claim computed over an epoch-integer, second-precision, or six-digit-microsecond rendering of the same instant.",
|
|
97
|
+
"casing_drift": "agent_id letter casing differs between the invocation payload and the tuple behind the claim.",
|
|
98
|
+
"payload_drift": "A tuple field (scope or action_type) in the invocation payload differs from the preimage behind the claim."
|
|
99
|
+
},
|
|
100
|
+
"vectors": [
|
|
101
|
+
{
|
|
102
|
+
"id": "neg-a01-received-order",
|
|
103
|
+
"failure_mode": "field_order_drift",
|
|
104
|
+
"description": "The claimed action_ref is a real SHA-256, but it was computed over a non-canonical serialization with fields in received order: agent_id, action_type, scope, timestamp and no JCS key sort. The canonical recomputation over the same four values mismatches. Matching the claim would require retrying the hash over the drifted byte order, which is a forbidden preimage retry.",
|
|
105
|
+
"invocation_payload": {
|
|
106
|
+
"action_type": "payment.send",
|
|
107
|
+
"agent_id": "pioneer-agent-001",
|
|
108
|
+
"scope": "nobulex:bilateral",
|
|
109
|
+
"timestamp": "2026-06-11T12:00:00.000Z"
|
|
110
|
+
},
|
|
111
|
+
"canonical_jcs_payload": "{\"action_type\":\"payment.send\",\"agent_id\":\"pioneer-agent-001\",\"scope\":\"nobulex:bilateral\",\"timestamp\":\"2026-06-11T12:00:00.000Z\"}",
|
|
112
|
+
"canonical_bytes_hex": "7b22616374696f6e5f74797065223a227061796d656e742e73656e64222c226167656e745f6964223a2270696f6e6565722d6167656e742d303031222c2273636f7065223a226e6f62756c65783a62696c61746572616c222c2274696d657374616d70223a22323032362d30362d31315431323a30303a30302e3030305a227d",
|
|
113
|
+
"correct_action_ref": "fefa13abb3765bc5f9279b49ea5507a406799b7076611e1238e482cbd5dfb32d",
|
|
114
|
+
"claimed_action_ref": "5fcfb5213cbb304570a3cf4e2f472feba3cf9fd8c13410f14a45431fc9f72dd6",
|
|
115
|
+
"drift_note": "drifted_serialization is the exact byte string behind claimed_action_ref. It contains the same four field values as invocation_payload; only the key order differs.",
|
|
116
|
+
"invariant": "claimed_action_ref != correct_action_ref",
|
|
117
|
+
"expected_result": "fail-closed before invocation",
|
|
118
|
+
"expected_failure_stage": "recompute_mismatch",
|
|
119
|
+
"expected_reason": "recomputed canonical action_ref does not match the claimed action_ref",
|
|
120
|
+
"verifier_must": "Recompute SHA-256(JCS(invocation_payload)) exactly once over the payload tuple as received. Reject on mismatch or grammar violation. Never retry alternative preimages, reorder, coerce, or normalize to force a match.",
|
|
121
|
+
"drifted_serialization": "{\"agent_id\":\"pioneer-agent-001\",\"action_type\":\"payment.send\",\"scope\":\"nobulex:bilateral\",\"timestamp\":\"2026-06-11T12:00:00.000Z\"}",
|
|
122
|
+
"drifted_bytes_hex": "7b226167656e745f6964223a2270696f6e6565722d6167656e742d303031222c22616374696f6e5f74797065223a227061796d656e742e73656e64222c2273636f7065223a226e6f62756c65783a62696c61746572616c222c2274696d657374616d70223a22323032362d30362d31315431323a30303a30302e3030305a227d"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"id": "neg-a02-reversed-order",
|
|
126
|
+
"failure_mode": "field_order_drift",
|
|
127
|
+
"description": "The claimed action_ref is a real SHA-256, but it was computed over a non-canonical serialization with fields in received order: timestamp, scope, agent_id, action_type and no JCS key sort. The canonical recomputation over the same four values mismatches. Matching the claim would require retrying the hash over the drifted byte order, which is a forbidden preimage retry.",
|
|
128
|
+
"invocation_payload": {
|
|
129
|
+
"action_type": "trail.anchor",
|
|
130
|
+
"agent_id": "giskard-self",
|
|
131
|
+
"scope": "mycelium:baseline",
|
|
132
|
+
"timestamp": "2026-06-11T12:05:00.000Z"
|
|
133
|
+
},
|
|
134
|
+
"canonical_jcs_payload": "{\"action_type\":\"trail.anchor\",\"agent_id\":\"giskard-self\",\"scope\":\"mycelium:baseline\",\"timestamp\":\"2026-06-11T12:05:00.000Z\"}",
|
|
135
|
+
"canonical_bytes_hex": "7b22616374696f6e5f74797065223a22747261696c2e616e63686f72222c226167656e745f6964223a226769736b6172642d73656c66222c2273636f7065223a226d7963656c69756d3a626173656c696e65222c2274696d657374616d70223a22323032362d30362d31315431323a30353a30302e3030305a227d",
|
|
136
|
+
"correct_action_ref": "6f028fc93f87cca54a0251da90a8de03c50c4cb19a8a915fa28b382959ce8c92",
|
|
137
|
+
"claimed_action_ref": "9c34dad62de307e4c65e34a56b83fa91be9efe221682580d8ed6813ecb9d1288",
|
|
138
|
+
"drift_note": "drifted_serialization is the exact byte string behind claimed_action_ref. It contains the same four field values as invocation_payload; only the key order differs.",
|
|
139
|
+
"invariant": "claimed_action_ref != correct_action_ref",
|
|
140
|
+
"expected_result": "fail-closed before invocation",
|
|
141
|
+
"expected_failure_stage": "recompute_mismatch",
|
|
142
|
+
"expected_reason": "recomputed canonical action_ref does not match the claimed action_ref",
|
|
143
|
+
"verifier_must": "Recompute SHA-256(JCS(invocation_payload)) exactly once over the payload tuple as received. Reject on mismatch or grammar violation. Never retry alternative preimages, reorder, coerce, or normalize to force a match.",
|
|
144
|
+
"drifted_serialization": "{\"timestamp\":\"2026-06-11T12:05:00.000Z\",\"scope\":\"mycelium:baseline\",\"agent_id\":\"giskard-self\",\"action_type\":\"trail.anchor\"}",
|
|
145
|
+
"drifted_bytes_hex": "7b2274696d657374616d70223a22323032362d30362d31315431323a30353a30302e3030305a222c2273636f7065223a226d7963656c69756d3a626173656c696e65222c226167656e745f6964223a226769736b6172642d73656c66222c22616374696f6e5f74797065223a22747261696c2e616e63686f72227d"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"id": "neg-b01-epoch-integer",
|
|
149
|
+
"failure_mode": "timestamp_form_drift",
|
|
150
|
+
"description": "The invocation payload carries the timestamp as an epoch-millisecond integer instead of the canonical RFC 3339 string. The claimed action_ref is the real digest of that integer-form tuple, so a verifier that skips the grammar gate and hashes the payload verbatim would see a match. A conformant verifier rejects the timestamp grammar before hashing and never converts the integer to the string form to retry.",
|
|
151
|
+
"invocation_payload": {
|
|
152
|
+
"action_type": "oracle.signal",
|
|
153
|
+
"agent_id": "nexus-agent-xa12.onrender.com",
|
|
154
|
+
"scope": "BTC",
|
|
155
|
+
"timestamp": 1781166600123
|
|
156
|
+
},
|
|
157
|
+
"drifted_jcs_payload": "{\"action_type\":\"oracle.signal\",\"agent_id\":\"nexus-agent-xa12.onrender.com\",\"scope\":\"BTC\",\"timestamp\":1781166600123}",
|
|
158
|
+
"drifted_bytes_hex": "7b22616374696f6e5f74797065223a226f7261636c652e7369676e616c222c226167656e745f6964223a226e657875732d6167656e742d786131322e6f6e72656e6465722e636f6d222c2273636f7065223a22425443222c2274696d657374616d70223a313738313136363630303132337d",
|
|
159
|
+
"claimed_action_ref": "d807275082611d69aa0cf3fbd265ac52df68c380eb32e7e6614bc9df61415763",
|
|
160
|
+
"canonical_form_payload": {
|
|
161
|
+
"action_type": "oracle.signal",
|
|
162
|
+
"agent_id": "nexus-agent-xa12.onrender.com",
|
|
163
|
+
"scope": "BTC",
|
|
164
|
+
"timestamp": "2026-06-11T08:30:00.123Z"
|
|
165
|
+
},
|
|
166
|
+
"canonical_jcs_payload": "{\"action_type\":\"oracle.signal\",\"agent_id\":\"nexus-agent-xa12.onrender.com\",\"scope\":\"BTC\",\"timestamp\":\"2026-06-11T08:30:00.123Z\"}",
|
|
167
|
+
"correct_action_ref": "d461626a9ef4b3ab7fe59f2c5392d24143efc98306720bfa1f41373ce2326e10",
|
|
168
|
+
"drift_note": "Same instant: epoch-ms integer 1781166600123 versus canonical string \"2026-06-11T08:30:00.123Z\". canonical_form_payload and correct_action_ref show what a conformant emitter would have produced.",
|
|
169
|
+
"invariant": "claimed_action_ref != correct_action_ref",
|
|
170
|
+
"expected_result": "fail-closed before invocation",
|
|
171
|
+
"expected_failure_stage": "grammar_reject",
|
|
172
|
+
"expected_reason": "timestamp is not an RFC 3339 UTC string with exactly three fractional digits and a Z suffix",
|
|
173
|
+
"verifier_must": "Reject the payload at the timestamp grammar gate. Never coerce the epoch integer into the canonical string form to obtain a matching digest; that is a forbidden normalization retry."
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"id": "neg-b02-second-precision",
|
|
177
|
+
"failure_mode": "timestamp_form_drift",
|
|
178
|
+
"description": "The invocation payload carries the canonical three-digit-millisecond timestamp, but the claimed action_ref was computed over the same instant rendered as second-precision RFC 3339 (no fractional digits). The bytes differ, so the canonical recomputation mismatches. Matching the claim would require re-rendering the timestamp into the drifted form and rehashing, which is a forbidden normalization retry.",
|
|
179
|
+
"invocation_payload": {
|
|
180
|
+
"action_type": "oracle.signal",
|
|
181
|
+
"agent_id": "nexus-agent-xa12.onrender.com",
|
|
182
|
+
"scope": "BTC",
|
|
183
|
+
"timestamp": "2026-06-11T08:45:00.000Z"
|
|
184
|
+
},
|
|
185
|
+
"canonical_jcs_payload": "{\"action_type\":\"oracle.signal\",\"agent_id\":\"nexus-agent-xa12.onrender.com\",\"scope\":\"BTC\",\"timestamp\":\"2026-06-11T08:45:00.000Z\"}",
|
|
186
|
+
"canonical_bytes_hex": "7b22616374696f6e5f74797065223a226f7261636c652e7369676e616c222c226167656e745f6964223a226e657875732d6167656e742d786131322e6f6e72656e6465722e636f6d222c2273636f7065223a22425443222c2274696d657374616d70223a22323032362d30362d31315430383a34353a30302e3030305a227d",
|
|
187
|
+
"correct_action_ref": "d73b70c4c8fc900c54de705cb5d53bb735f774e1b248e9c975cc39bd06e9485b",
|
|
188
|
+
"claimed_action_ref": "48862e6601dcdf9b951fbe1b41a9113231e02e775567813531ad8eb3ad8ee451",
|
|
189
|
+
"drift_note": "drifted_preimage is identical to invocation_payload except timestamp = \"2026-06-11T08:45:00Z\". claimed_action_ref is the real digest of that drifted tuple.",
|
|
190
|
+
"invariant": "claimed_action_ref != correct_action_ref",
|
|
191
|
+
"expected_result": "fail-closed before invocation",
|
|
192
|
+
"expected_failure_stage": "recompute_mismatch",
|
|
193
|
+
"expected_reason": "recomputed canonical action_ref does not match the claimed action_ref",
|
|
194
|
+
"verifier_must": "Recompute SHA-256(JCS(invocation_payload)) exactly once over the payload tuple as received. Reject on mismatch or grammar violation. Never retry alternative preimages, reorder, coerce, or normalize to force a match.",
|
|
195
|
+
"drifted_preimage": {
|
|
196
|
+
"action_type": "oracle.signal",
|
|
197
|
+
"agent_id": "nexus-agent-xa12.onrender.com",
|
|
198
|
+
"scope": "BTC",
|
|
199
|
+
"timestamp": "2026-06-11T08:45:00Z"
|
|
200
|
+
},
|
|
201
|
+
"drifted_jcs_payload": "{\"action_type\":\"oracle.signal\",\"agent_id\":\"nexus-agent-xa12.onrender.com\",\"scope\":\"BTC\",\"timestamp\":\"2026-06-11T08:45:00Z\"}",
|
|
202
|
+
"drifted_bytes_hex": "7b22616374696f6e5f74797065223a226f7261636c652e7369676e616c222c226167656e745f6964223a226e657875732d6167656e742d786131322e6f6e72656e6465722e636f6d222c2273636f7065223a22425443222c2274696d657374616d70223a22323032362d30362d31315430383a34353a30305a227d"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"id": "neg-b03-microsecond-precision",
|
|
206
|
+
"failure_mode": "timestamp_form_drift",
|
|
207
|
+
"description": "The invocation payload carries the canonical three-digit-millisecond timestamp, but the claimed action_ref was computed over the same instant rendered as six-digit-microsecond RFC 3339. The bytes differ, so the canonical recomputation mismatches. Matching the claim would require re-rendering the timestamp into the drifted form and rehashing, which is a forbidden normalization retry.",
|
|
208
|
+
"invocation_payload": {
|
|
209
|
+
"action_type": "oracle.signal",
|
|
210
|
+
"agent_id": "nexus-agent-xa12.onrender.com",
|
|
211
|
+
"scope": "BTC",
|
|
212
|
+
"timestamp": "2026-06-11T08:30:00.123Z"
|
|
213
|
+
},
|
|
214
|
+
"canonical_jcs_payload": "{\"action_type\":\"oracle.signal\",\"agent_id\":\"nexus-agent-xa12.onrender.com\",\"scope\":\"BTC\",\"timestamp\":\"2026-06-11T08:30:00.123Z\"}",
|
|
215
|
+
"canonical_bytes_hex": "7b22616374696f6e5f74797065223a226f7261636c652e7369676e616c222c226167656e745f6964223a226e657875732d6167656e742d786131322e6f6e72656e6465722e636f6d222c2273636f7065223a22425443222c2274696d657374616d70223a22323032362d30362d31315430383a33303a30302e3132335a227d",
|
|
216
|
+
"correct_action_ref": "d461626a9ef4b3ab7fe59f2c5392d24143efc98306720bfa1f41373ce2326e10",
|
|
217
|
+
"claimed_action_ref": "fa3f63e9b37fe877bb8ffdc855577f031d022bee993989c01d98916c7eea2d16",
|
|
218
|
+
"drift_note": "drifted_preimage is identical to invocation_payload except timestamp = \"2026-06-11T08:30:00.123000Z\". claimed_action_ref is the real digest of that drifted tuple.",
|
|
219
|
+
"invariant": "claimed_action_ref != correct_action_ref",
|
|
220
|
+
"expected_result": "fail-closed before invocation",
|
|
221
|
+
"expected_failure_stage": "recompute_mismatch",
|
|
222
|
+
"expected_reason": "recomputed canonical action_ref does not match the claimed action_ref",
|
|
223
|
+
"verifier_must": "Recompute SHA-256(JCS(invocation_payload)) exactly once over the payload tuple as received. Reject on mismatch or grammar violation. Never retry alternative preimages, reorder, coerce, or normalize to force a match.",
|
|
224
|
+
"drifted_preimage": {
|
|
225
|
+
"action_type": "oracle.signal",
|
|
226
|
+
"agent_id": "nexus-agent-xa12.onrender.com",
|
|
227
|
+
"scope": "BTC",
|
|
228
|
+
"timestamp": "2026-06-11T08:30:00.123000Z"
|
|
229
|
+
},
|
|
230
|
+
"drifted_jcs_payload": "{\"action_type\":\"oracle.signal\",\"agent_id\":\"nexus-agent-xa12.onrender.com\",\"scope\":\"BTC\",\"timestamp\":\"2026-06-11T08:30:00.123000Z\"}",
|
|
231
|
+
"drifted_bytes_hex": "7b22616374696f6e5f74797065223a226f7261636c652e7369676e616c222c226167656e745f6964223a226e657875732d6167656e742d786131322e6f6e72656e6465722e636f6d222c2273636f7065223a22425443222c2274696d657374616d70223a22323032362d30362d31315430383a33303a30302e3132333030305a227d"
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"id": "neg-c01-agent-id-lowercased",
|
|
235
|
+
"failure_mode": "casing_drift",
|
|
236
|
+
"description": "agent_id casing changed between the invocation payload and the tuple behind the claimed action_ref: the claim was computed with agent_id lowercased (\"did:aps:zagentexample\") relative to the payload value \"did:aps:zAgentExample\". agent_id is opaque bytes in the preimage; the digests differ. No case folding is allowed at verification time.",
|
|
237
|
+
"invocation_payload": {
|
|
238
|
+
"action_type": "delegation.execute",
|
|
239
|
+
"agent_id": "did:aps:zAgentExample",
|
|
240
|
+
"scope": "aps:delegation",
|
|
241
|
+
"timestamp": "2026-06-11T14:00:00.000Z"
|
|
242
|
+
},
|
|
243
|
+
"canonical_jcs_payload": "{\"action_type\":\"delegation.execute\",\"agent_id\":\"did:aps:zAgentExample\",\"scope\":\"aps:delegation\",\"timestamp\":\"2026-06-11T14:00:00.000Z\"}",
|
|
244
|
+
"canonical_bytes_hex": "7b22616374696f6e5f74797065223a2264656c65676174696f6e2e65786563757465222c226167656e745f6964223a226469643a6170733a7a4167656e744578616d706c65222c2273636f7065223a226170733a64656c65676174696f6e222c2274696d657374616d70223a22323032362d30362d31315431343a30303a30302e3030305a227d",
|
|
245
|
+
"correct_action_ref": "b5499f92b80552fa63518f9bf54095895cfd3f5b0c928914ae0b5789f8a54e28",
|
|
246
|
+
"claimed_action_ref": "b63e4aba6b21f718cbd425972fe536b77645578d2cf4320074a9f9a01b887c65",
|
|
247
|
+
"drift_note": "drifted_preimage is identical to invocation_payload except for agent_id letter casing. claimed_action_ref is the real digest of that drifted tuple.",
|
|
248
|
+
"invariant": "claimed_action_ref != correct_action_ref",
|
|
249
|
+
"expected_result": "fail-closed before invocation",
|
|
250
|
+
"expected_failure_stage": "recompute_mismatch",
|
|
251
|
+
"expected_reason": "recomputed canonical action_ref does not match the claimed action_ref",
|
|
252
|
+
"verifier_must": "Recompute SHA-256(JCS(invocation_payload)) exactly once over the payload tuple as received. Reject on mismatch or grammar violation. Never retry alternative preimages, reorder, coerce, or normalize to force a match.",
|
|
253
|
+
"drifted_preimage": {
|
|
254
|
+
"action_type": "delegation.execute",
|
|
255
|
+
"agent_id": "did:aps:zagentexample",
|
|
256
|
+
"scope": "aps:delegation",
|
|
257
|
+
"timestamp": "2026-06-11T14:00:00.000Z"
|
|
258
|
+
},
|
|
259
|
+
"drifted_jcs_payload": "{\"action_type\":\"delegation.execute\",\"agent_id\":\"did:aps:zagentexample\",\"scope\":\"aps:delegation\",\"timestamp\":\"2026-06-11T14:00:00.000Z\"}",
|
|
260
|
+
"drifted_bytes_hex": "7b22616374696f6e5f74797065223a2264656c65676174696f6e2e65786563757465222c226167656e745f6964223a226469643a6170733a7a6167656e746578616d706c65222c2273636f7065223a226170733a64656c65676174696f6e222c2274696d657374616d70223a22323032362d30362d31315431343a30303a30302e3030305a227d"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"id": "neg-c02-agent-id-uppercased",
|
|
264
|
+
"failure_mode": "casing_drift",
|
|
265
|
+
"description": "agent_id casing changed between the invocation payload and the tuple behind the claimed action_ref: the claim was computed with agent_id recased (\"did:aps:zAgent\") relative to the payload value \"did:aps:zagent\". agent_id is opaque bytes in the preimage; the digests differ. No case folding is allowed at verification time.",
|
|
266
|
+
"invocation_payload": {
|
|
267
|
+
"action_type": "payment.send",
|
|
268
|
+
"agent_id": "did:aps:zagent",
|
|
269
|
+
"scope": "aps:payments",
|
|
270
|
+
"timestamp": "2026-06-11T14:05:00.000Z"
|
|
271
|
+
},
|
|
272
|
+
"canonical_jcs_payload": "{\"action_type\":\"payment.send\",\"agent_id\":\"did:aps:zagent\",\"scope\":\"aps:payments\",\"timestamp\":\"2026-06-11T14:05:00.000Z\"}",
|
|
273
|
+
"canonical_bytes_hex": "7b22616374696f6e5f74797065223a227061796d656e742e73656e64222c226167656e745f6964223a226469643a6170733a7a6167656e74222c2273636f7065223a226170733a7061796d656e7473222c2274696d657374616d70223a22323032362d30362d31315431343a30353a30302e3030305a227d",
|
|
274
|
+
"correct_action_ref": "a7351a1d995d00e650a0c6051e9301e42880538068fdaa262d69cbed6c96e4c7",
|
|
275
|
+
"claimed_action_ref": "2d555e3e5191b7ebd935f3e2d9aaddaf7c3fb8fd478e8b6c0b407e22266b1303",
|
|
276
|
+
"drift_note": "drifted_preimage is identical to invocation_payload except for agent_id letter casing. claimed_action_ref is the real digest of that drifted tuple.",
|
|
277
|
+
"invariant": "claimed_action_ref != correct_action_ref",
|
|
278
|
+
"expected_result": "fail-closed before invocation",
|
|
279
|
+
"expected_failure_stage": "recompute_mismatch",
|
|
280
|
+
"expected_reason": "recomputed canonical action_ref does not match the claimed action_ref",
|
|
281
|
+
"verifier_must": "Recompute SHA-256(JCS(invocation_payload)) exactly once over the payload tuple as received. Reject on mismatch or grammar violation. Never retry alternative preimages, reorder, coerce, or normalize to force a match.",
|
|
282
|
+
"drifted_preimage": {
|
|
283
|
+
"action_type": "payment.send",
|
|
284
|
+
"agent_id": "did:aps:zAgent",
|
|
285
|
+
"scope": "aps:payments",
|
|
286
|
+
"timestamp": "2026-06-11T14:05:00.000Z"
|
|
287
|
+
},
|
|
288
|
+
"drifted_jcs_payload": "{\"action_type\":\"payment.send\",\"agent_id\":\"did:aps:zAgent\",\"scope\":\"aps:payments\",\"timestamp\":\"2026-06-11T14:05:00.000Z\"}",
|
|
289
|
+
"drifted_bytes_hex": "7b22616374696f6e5f74797065223a227061796d656e742e73656e64222c226167656e745f6964223a226469643a6170733a7a4167656e74222c2273636f7065223a226170733a7061796d656e7473222c2274696d657374616d70223a22323032362d30362d31315431343a30353a30302e3030305a227d"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"id": "neg-d01-scope-changed",
|
|
293
|
+
"failure_mode": "payload_drift",
|
|
294
|
+
"description": "The invocation payload's scope (\"argentum:payments:send\") differs from the scope behind the claimed action_ref (\"argentum:payments:refund\"). The claim is a real digest of the original tuple, but the payload tuple the verifier recomputes over has changed, so the recomputation mismatches. The action the claim was bound to is not the action being invoked.",
|
|
295
|
+
"invocation_payload": {
|
|
296
|
+
"action_type": "payment.send",
|
|
297
|
+
"agent_id": "pioneer-agent-001",
|
|
298
|
+
"scope": "argentum:payments:send",
|
|
299
|
+
"timestamp": "2026-06-11T16:00:00.000Z"
|
|
300
|
+
},
|
|
301
|
+
"canonical_jcs_payload": "{\"action_type\":\"payment.send\",\"agent_id\":\"pioneer-agent-001\",\"scope\":\"argentum:payments:send\",\"timestamp\":\"2026-06-11T16:00:00.000Z\"}",
|
|
302
|
+
"canonical_bytes_hex": "7b22616374696f6e5f74797065223a227061796d656e742e73656e64222c226167656e745f6964223a2270696f6e6565722d6167656e742d303031222c2273636f7065223a22617267656e74756d3a7061796d656e74733a73656e64222c2274696d657374616d70223a22323032362d30362d31315431363a30303a30302e3030305a227d",
|
|
303
|
+
"correct_action_ref": "4aea72656c085069fca26571158db1319c33456f704d78587904f1c2a28b16fe",
|
|
304
|
+
"claimed_action_ref": "1bf527c864278ecbb6a1da5158264efd966fc29db453d88b6e7b658654493c7a",
|
|
305
|
+
"drift_note": "drifted_preimage is the original tuple behind the claim, with scope = \"argentum:payments:refund\". claimed_action_ref is its real digest.",
|
|
306
|
+
"invariant": "claimed_action_ref != correct_action_ref",
|
|
307
|
+
"expected_result": "fail-closed before invocation",
|
|
308
|
+
"expected_failure_stage": "recompute_mismatch",
|
|
309
|
+
"expected_reason": "recomputed canonical action_ref does not match the claimed action_ref",
|
|
310
|
+
"verifier_must": "Recompute SHA-256(JCS(invocation_payload)) exactly once over the payload tuple as received. Reject on mismatch or grammar violation. Never retry alternative preimages, reorder, coerce, or normalize to force a match.",
|
|
311
|
+
"drifted_preimage": {
|
|
312
|
+
"action_type": "payment.send",
|
|
313
|
+
"agent_id": "pioneer-agent-001",
|
|
314
|
+
"scope": "argentum:payments:refund",
|
|
315
|
+
"timestamp": "2026-06-11T16:00:00.000Z"
|
|
316
|
+
},
|
|
317
|
+
"drifted_jcs_payload": "{\"action_type\":\"payment.send\",\"agent_id\":\"pioneer-agent-001\",\"scope\":\"argentum:payments:refund\",\"timestamp\":\"2026-06-11T16:00:00.000Z\"}",
|
|
318
|
+
"drifted_bytes_hex": "7b22616374696f6e5f74797065223a227061796d656e742e73656e64222c226167656e745f6964223a2270696f6e6565722d6167656e742d303031222c2273636f7065223a22617267656e74756d3a7061796d656e74733a726566756e64222c2274696d657374616d70223a22323032362d30362d31315431363a30303a30302e3030305a227d"
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
"id": "neg-d02-action-type-changed",
|
|
322
|
+
"failure_mode": "payload_drift",
|
|
323
|
+
"description": "The invocation payload's action_type (\"payment.send\") differs from the action_type behind the claimed action_ref (\"payment.preauth\"). The claim is a real digest of the original tuple, but the payload tuple the verifier recomputes over has changed, so the recomputation mismatches. The action the claim was bound to is not the action being invoked.",
|
|
324
|
+
"invocation_payload": {
|
|
325
|
+
"action_type": "payment.send",
|
|
326
|
+
"agent_id": "pioneer-agent-001",
|
|
327
|
+
"scope": "nobulex:bilateral",
|
|
328
|
+
"timestamp": "2026-06-11T16:05:00.000Z"
|
|
329
|
+
},
|
|
330
|
+
"canonical_jcs_payload": "{\"action_type\":\"payment.send\",\"agent_id\":\"pioneer-agent-001\",\"scope\":\"nobulex:bilateral\",\"timestamp\":\"2026-06-11T16:05:00.000Z\"}",
|
|
331
|
+
"canonical_bytes_hex": "7b22616374696f6e5f74797065223a227061796d656e742e73656e64222c226167656e745f6964223a2270696f6e6565722d6167656e742d303031222c2273636f7065223a226e6f62756c65783a62696c61746572616c222c2274696d657374616d70223a22323032362d30362d31315431363a30353a30302e3030305a227d",
|
|
332
|
+
"correct_action_ref": "e578a0a48ecc30530189109a54ea47e2476213ca5819640302a9c850e485605a",
|
|
333
|
+
"claimed_action_ref": "202f0cc855fc50602cfb3d96298381fb959f90447c916eadba86e147de340c1d",
|
|
334
|
+
"drift_note": "drifted_preimage is the original tuple behind the claim, with action_type = \"payment.preauth\". claimed_action_ref is its real digest.",
|
|
335
|
+
"invariant": "claimed_action_ref != correct_action_ref",
|
|
336
|
+
"expected_result": "fail-closed before invocation",
|
|
337
|
+
"expected_failure_stage": "recompute_mismatch",
|
|
338
|
+
"expected_reason": "recomputed canonical action_ref does not match the claimed action_ref",
|
|
339
|
+
"verifier_must": "Recompute SHA-256(JCS(invocation_payload)) exactly once over the payload tuple as received. Reject on mismatch or grammar violation. Never retry alternative preimages, reorder, coerce, or normalize to force a match.",
|
|
340
|
+
"drifted_preimage": {
|
|
341
|
+
"action_type": "payment.preauth",
|
|
342
|
+
"agent_id": "pioneer-agent-001",
|
|
343
|
+
"scope": "nobulex:bilateral",
|
|
344
|
+
"timestamp": "2026-06-11T16:05:00.000Z"
|
|
345
|
+
},
|
|
346
|
+
"drifted_jcs_payload": "{\"action_type\":\"payment.preauth\",\"agent_id\":\"pioneer-agent-001\",\"scope\":\"nobulex:bilateral\",\"timestamp\":\"2026-06-11T16:05:00.000Z\"}",
|
|
347
|
+
"drifted_bytes_hex": "7b22616374696f6e5f74797065223a227061796d656e742e70726561757468222c226167656e745f6964223a2270696f6e6565722d6167656e742d303031222c2273636f7065223a226e6f62756c65783a62696c61746572616c222c2274696d657374616d70223a22323032362d30362d31315431363a30353a30302e3030305a227d"
|
|
348
|
+
}
|
|
349
|
+
]
|
|
350
|
+
}
|
|
351
|
+
}
|