@mcp-audit-gateway/core 0.1.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.
Files changed (82) hide show
  1. package/.github/workflows/ci.yml +37 -0
  2. package/.well-known/agent-governance.json +47 -0
  3. package/.well-known/security-insights-snippet.yml +9 -0
  4. package/CHANGELOG.md +32 -0
  5. package/README.md +31 -3
  6. package/dist/attestation/audit-log.d.ts +35 -3
  7. package/dist/attestation/audit-log.d.ts.map +1 -1
  8. package/dist/attestation/audit-log.js +303 -8
  9. package/dist/attestation/audit-log.js.map +1 -1
  10. package/dist/attestation/checkpoint.test.d.ts +2 -0
  11. package/dist/attestation/checkpoint.test.d.ts.map +1 -0
  12. package/dist/attestation/checkpoint.test.js +870 -0
  13. package/dist/attestation/checkpoint.test.js.map +1 -0
  14. package/dist/attestation/signer.d.ts +24 -9
  15. package/dist/attestation/signer.d.ts.map +1 -1
  16. package/dist/attestation/signer.js +151 -10
  17. package/dist/attestation/signer.js.map +1 -1
  18. package/dist/attestation/signer.test.js +83 -0
  19. package/dist/attestation/signer.test.js.map +1 -1
  20. package/dist/attestation/verify.d.ts +23 -2
  21. package/dist/attestation/verify.d.ts.map +1 -1
  22. package/dist/attestation/verify.js +219 -2
  23. package/dist/attestation/verify.js.map +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/integration.test.js +1 -0
  29. package/dist/integration.test.js.map +1 -1
  30. package/dist/policy/engine.d.ts +12 -1
  31. package/dist/policy/engine.d.ts.map +1 -1
  32. package/dist/policy/engine.js +26 -4
  33. package/dist/policy/engine.js.map +1 -1
  34. package/dist/policy/engine.test.js +42 -1
  35. package/dist/policy/engine.test.js.map +1 -1
  36. package/dist/proxy/gateway.d.ts +4 -0
  37. package/dist/proxy/gateway.d.ts.map +1 -1
  38. package/dist/proxy/gateway.js +9 -2
  39. package/dist/proxy/gateway.js.map +1 -1
  40. package/dist/proxy/gateway.test.js +19 -0
  41. package/dist/proxy/gateway.test.js.map +1 -1
  42. package/dist/proxy/mcp-server-adapter.d.ts +1 -0
  43. package/dist/proxy/mcp-server-adapter.d.ts.map +1 -1
  44. package/dist/proxy/mcp-server-adapter.js +28 -1
  45. package/dist/proxy/mcp-server-adapter.js.map +1 -1
  46. package/dist/proxy/mcp-server-adapter.test.js +1 -0
  47. package/dist/proxy/mcp-server-adapter.test.js.map +1 -1
  48. package/dist/types.d.ts +81 -0
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js +13 -0
  51. package/dist/types.js.map +1 -1
  52. package/dist/wrap/proxy.test.js +2 -2
  53. package/dist/wrap/proxy.test.js.map +1 -1
  54. package/docs/BACKLOG.md +33 -0
  55. package/docs/SECURITY-DESIGN.md +126 -0
  56. package/docs/v0.4.0-patch-audit.md +115 -0
  57. package/package.json +1 -1
  58. package/src/attestation/audit-log.ts +357 -13
  59. package/src/attestation/checkpoint.test.ts +956 -0
  60. package/src/attestation/signer.test.ts +98 -0
  61. package/src/attestation/signer.ts +159 -19
  62. package/src/attestation/verify.ts +270 -4
  63. package/src/index.ts +1 -1
  64. package/src/integration.test.ts +1 -0
  65. package/src/policy/engine.test.ts +47 -1
  66. package/src/policy/engine.ts +38 -5
  67. package/src/proxy/gateway.test.ts +18 -0
  68. package/src/proxy/gateway.ts +10 -1
  69. package/src/proxy/mcp-server-adapter.test.ts +1 -0
  70. package/src/proxy/mcp-server-adapter.ts +26 -0
  71. package/src/types.ts +56 -0
  72. package/src/wrap/proxy.test.ts +2 -2
  73. package/test/vectors/README.md +44 -0
  74. package/test/vectors/aps-action-ref-v1-vectors.json +351 -0
  75. package/test/vectors/aps-action-ref-v1.mjs +145 -0
  76. package/test/vectors/canonicalization.json +764 -0
  77. package/test/vectors/checkpoint.json +450 -0
  78. package/test/vectors/generate.mjs +354 -0
  79. package/test/vectors/verify-checkpoint.mjs +344 -0
  80. package/test/vectors/verify-checkpoint.py +358 -0
  81. package/test/vectors/verify.mjs +346 -0
  82. package/test/vectors/verify.py +354 -0
@@ -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
+ }
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * APS action-ref-v1 conformance runner.
4
+ * Adapted from: https://github.com/Agent-Authority-Conformance/aps-conformance-suite/tree/main/fixtures/cross-stack/action-ref-v1-negatives
5
+ *
6
+ * Tests JCS (RFC 8785) canonical recomputation and fail-closed digest comparison.
7
+ * No APS SDK dependency — uses the same SHA-256 + canonical-sort primitives
8
+ * that back our attestation layer.
9
+ *
10
+ * Property under test: a verifier recomputes action_ref from the invocation
11
+ * payload exactly once via JCS canonicalization, then rejects on mismatch.
12
+ * No retries, no coercion, no normalization.
13
+ *
14
+ * Scope relative to the full APS conformance suite: we extract the fail-closed
15
+ * digest recomputation property, which our attestation layer shares. The
16
+ * aae-envelope interop vectors test delegation chain semantics (scope narrowing,
17
+ * expiry cascade, revocation cascade) that this system does not implement — we
18
+ * provide audit trail attestation, not delegation verification. The one
19
+ * aae-envelope concept that applies is signature substitution (valid signature
20
+ * from key A rejected when verified against key B), tested directly in
21
+ * src/attestation/signer.test.ts.
22
+ */
23
+
24
+ import { createHash } from "node:crypto";
25
+ import { readFileSync } from "node:fs";
26
+ import { fileURLToPath } from "node:url";
27
+ import { dirname, join } from "node:path";
28
+
29
+ const __dirname = dirname(fileURLToPath(import.meta.url));
30
+ const fixture = JSON.parse(
31
+ readFileSync(join(__dirname, "aps-action-ref-v1-vectors.json"), "utf8")
32
+ );
33
+
34
+ const sha256hex = (s) => createHash("sha256").update(s, "utf8").digest("hex");
35
+
36
+ // RFC 8785 (JCS) for flat objects: keys sorted lexicographically,
37
+ // values serialized per ECMA-262. For these fixtures all values are
38
+ // strings or safe integers.
39
+ function jcsFlat(obj) {
40
+ const parts = Object.keys(obj)
41
+ .sort()
42
+ .map((k) => `${JSON.stringify(k)}:${jcsValue(obj[k])}`);
43
+ return `{${parts.join(",")}}`;
44
+ }
45
+
46
+ function jcsValue(v) {
47
+ if (typeof v === "string") return JSON.stringify(v);
48
+ if (typeof v === "number" && Number.isSafeInteger(v)) return String(v);
49
+ throw new Error(`jcsFlat: unsupported value type for ${JSON.stringify(v)}`);
50
+ }
51
+
52
+ // Canonical timestamp grammar: RFC 3339 UTC, exactly three fractional digits, Z suffix.
53
+ const CANONICAL_TS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
54
+
55
+ // Single-path verifier: grammar gate, one recomputation, one comparison.
56
+ function verifyClaim(payload, claimedActionRef) {
57
+ for (const field of ["action_type", "agent_id", "scope", "timestamp"]) {
58
+ if (!(field in payload)) {
59
+ return { ok: false, stage: "grammar", reason: `missing field: ${field}` };
60
+ }
61
+ }
62
+ const ts = payload.timestamp;
63
+ if (typeof ts !== "string" || !CANONICAL_TS.test(ts)) {
64
+ return { ok: false, stage: "grammar", reason: "timestamp grammar rejected" };
65
+ }
66
+ const recomputed = sha256hex(jcsFlat(payload));
67
+ if (recomputed !== claimedActionRef) {
68
+ return { ok: false, stage: "recompute", reason: "digest mismatch", recomputed };
69
+ }
70
+ return { ok: true, stage: "recompute", reason: "match", recomputed };
71
+ }
72
+
73
+ let passed = 0;
74
+ let failed = 0;
75
+
76
+ function check(condition, name, detail) {
77
+ if (condition) {
78
+ passed++;
79
+ console.log(` PASS: ${name}`);
80
+ } else {
81
+ failed++;
82
+ console.log(` FAIL: ${name}`);
83
+ if (detail) console.log(` ${detail}`);
84
+ }
85
+ }
86
+
87
+ // === Positive vectors: recomputation must match ===
88
+ console.log("\n=== action-ref-v1 Positive Recomputation ===");
89
+ for (const v of fixture.positive_fixture.vectors) {
90
+ const jcs = jcsFlat(v.preimage);
91
+ check(
92
+ jcs === v.jcs_payload,
93
+ `${v.id} JCS canonical form`,
94
+ `expected: ${v.jcs_payload}\n got: ${jcs}`
95
+ );
96
+
97
+ const digest = sha256hex(jcs);
98
+ check(
99
+ digest === v.action_ref,
100
+ `${v.id} SHA-256 recomputation`,
101
+ `expected: ${v.action_ref}\n got: ${digest}`
102
+ );
103
+
104
+ const verdict = verifyClaim(v.preimage, v.action_ref);
105
+ check(verdict.ok, `${v.id} verifier accepts`);
106
+ }
107
+
108
+ // === Negative vectors: must fail-closed ===
109
+ console.log("\n=== action-ref-v1 Negative (Fail-Closed) ===");
110
+ const stageMap = { grammar_reject: "grammar", recompute_mismatch: "recompute" };
111
+
112
+ for (const v of fixture.negative_fixture.vectors) {
113
+ const verdict = verifyClaim(v.invocation_payload, v.claimed_action_ref);
114
+ const expectedStage = stageMap[v.expected_failure_stage];
115
+
116
+ check(
117
+ !verdict.ok,
118
+ `${v.id} rejected`,
119
+ verdict.ok ? "verifier accepted (BUG)" : undefined
120
+ );
121
+ check(
122
+ verdict.stage === expectedStage,
123
+ `${v.id} failure stage = ${expectedStage}`,
124
+ `got: ${verdict.stage}`
125
+ );
126
+
127
+ // Fixture integrity: drifted digest is byte-derived (not invented)
128
+ const drifted = v.drifted_serialization ?? v.drifted_jcs_payload;
129
+ if (drifted) {
130
+ check(
131
+ sha256hex(drifted) === v.claimed_action_ref,
132
+ `${v.id} fixture integrity (drifted digest is real)`
133
+ );
134
+ }
135
+
136
+ // Invariant: claimed != correct (they're genuinely different digests)
137
+ check(
138
+ v.claimed_action_ref !== v.correct_action_ref,
139
+ `${v.id} invariant (claimed != correct)`
140
+ );
141
+ }
142
+
143
+ // === Summary ===
144
+ console.log(`\n=== Results: ${passed} passed, ${failed} failed (${passed + failed} total) ===`);
145
+ process.exit(failed > 0 ? 1 : 0);