@openwop/openwop-conformance 2.42.9 → 2.43.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/CHANGELOG.md +13 -0
- package/README.md +3 -3
- package/coverage.md +1 -1
- package/dist/spec-artifacts.lock.json +2 -2
- package/fixtures.md +2 -1
- package/package.json +2 -2
- package/requirements.json +350 -16
- package/scenario-majors.json +17 -5
- package/schemas/CORPUS-STAMP.json +37 -36
- package/src/lib/auditIntegrity.ts +45 -0
- package/src/scenarios/audit-anomaly-shape.test.ts +100 -0
- package/src/scenarios/audit-checkpoint-signature.test.ts +14 -15
- package/src/scenarios/audit-log-integrity.test.ts +102 -46
- package/src/scenarios/byok-roundtrip.test.ts +21 -7
- package/src/scenarios/v2-approval-reject-disposition.test.ts +162 -0
- package/src/scenarios/v2-registry-lifecycle.test.ts +232 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "Provenance of @openwop/spec-artifacts (RFC 0168 §D.2). files: SHA-256 per file; the conformance suite compares the installed peer against dist/spec-artifacts.lock.json at start.",
|
|
3
3
|
"package": "@openwop/spec-artifacts",
|
|
4
|
-
"version": "2.
|
|
5
|
-
"corpusTag": "v2.
|
|
4
|
+
"version": "2.43.0",
|
|
5
|
+
"corpusTag": "v2.43.0",
|
|
6
6
|
"files": {
|
|
7
7
|
"api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
|
|
8
8
|
"api/asyncapi.yaml": "d5ecb9ee6114582be3b1f662c84bfac9ae96dae7bacb853e461168f70a8e1c7d",
|
|
9
9
|
"api/grpc/openwop.proto": "c3e72bb17cba514ee98feb6434e6c9b6ea6795bfd086489ec69fd882dd1ad977",
|
|
10
10
|
"api/openapi.yaml": "69464bc0e1e71ef3b543a73d4fa4f67202343d2603c80af8033f6c3619ef1b30",
|
|
11
11
|
"api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
|
|
12
|
-
"api/seams-v2.yaml": "
|
|
13
|
-
"api/v2/asyncapi.yaml": "
|
|
14
|
-
"api/v2/openapi.yaml": "
|
|
12
|
+
"api/seams-v2.yaml": "c6d9d70e74a4c883a016a69da438fb6c87f56822334a1d45bd682c6d68bdac32",
|
|
13
|
+
"api/v2/asyncapi.yaml": "0b72bbd3f0f25172e3080a027a801d68d4a4130188addf2879fe92759d352de6",
|
|
14
|
+
"api/v2/openapi.yaml": "fdbc6bcf064de81e2c8bf3d7a896ccbc39d8b12f71f9b8c4727f825f1ae31af0",
|
|
15
15
|
"api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
|
|
16
16
|
"schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
|
|
17
17
|
"schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"schemas/annotation-create.schema.json": "567e8327777968e0ddab8e1547b4acd7beda734cf9d1fc6cde17037d8d9540d4",
|
|
30
30
|
"schemas/annotation.schema.json": "b26d22f9a97e1ae7c195e8799964aca804e78452a079ca3a0d61ea013907ce91",
|
|
31
31
|
"schemas/artifact-type-pack-manifest.schema.json": "60342fcb15eb7f3ab46271f0641409a30d3a4a8f318b1020c53070886b191252",
|
|
32
|
-
"schemas/audit-verify-result.schema.json": "
|
|
32
|
+
"schemas/audit-verify-result.schema.json": "0d3a3088ac1c32f9df66d346c33d7adbd8037940dba040e14ea847565a1a14bd",
|
|
33
33
|
"schemas/budget-policy.schema.json": "3d4fe30ae129fe136ab9ece395f2a2a42a76360786b1b9a1d5f1cbc753eb5b39",
|
|
34
34
|
"schemas/capabilities.schema.json": "56b367074bce0f02ff87e0b84ccdf91856793d3d285e28f261656edba6352cc0",
|
|
35
35
|
"schemas/certification-bundle-v2.schema.json": "2f85530dd04824a15810c94e96869362dee1e78396eca21fdd18e5501416bb7d",
|
|
@@ -114,9 +114,9 @@
|
|
|
114
114
|
"schemas/v2/annotation.schema.json": "eb4813cdaa8e325412cfcaeeee5c8a28f3751c946dec64e546c7ba0ad3aad8d4",
|
|
115
115
|
"schemas/v2/artifact-type-pack-manifest.schema.json": "c8820351a2ab93855bd654ad3d8a9788f97975d09b3a7a0418e374ed6114595d",
|
|
116
116
|
"schemas/v2/artifact.schema.json": "85b9dc284d39b99d2a66fa8cbf47f2dd3d8526d3bd6cc411ad315b372de38975",
|
|
117
|
-
"schemas/v2/audit-verify-result.schema.json": "
|
|
117
|
+
"schemas/v2/audit-verify-result.schema.json": "14af8e9283510f0a40e48a2059c929ee44e0fc9fa3cc66a95bfbe90b17c48297",
|
|
118
118
|
"schemas/v2/budget-policy.schema.json": "c7449daeb6e1d95e7a047b8c2814d3d54748697c27046972834874867d9b5f4c",
|
|
119
|
-
"schemas/v2/capabilities.schema.json": "
|
|
119
|
+
"schemas/v2/capabilities.schema.json": "ae6f60641d6ed680351b2710df945257c401f1ed83534f7e44fa2e651dc401ab",
|
|
120
120
|
"schemas/v2/certification-bundle.schema.json": "82ce31a1ed6c7e21b0ba88ff62a7b8b768fc3b176b1d06fce023203830b94725",
|
|
121
121
|
"schemas/v2/channel-presence-payload.schema.json": "4eeb224910e6f7eee3ab5e7c3899559c25c83a26d0022e0ad195c4672611977a",
|
|
122
122
|
"schemas/v2/channel-written-payload.schema.json": "ccecff3c71a3275ad8db035ac0e04db25e6f8d23cf091ddbcd5a60d1be1cbb6b",
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
"schemas/v2/envelopes/schema.request.schema.json": "be2c5f7aa881cdd252ccff47ac02193659e1393633a1541f4297383205d384a2",
|
|
143
143
|
"schemas/v2/envelopes/schema.response.schema.json": "d049a081267c06c28879c331b147f3ed821b14ccfd93005c67aff64db0a8c68e",
|
|
144
144
|
"schemas/v2/envelopes/ui.a2ui-surface.schema.json": "e92a2548ba65454c9e0a34f72ea6768d18f59e61fa5da7668d0b2d5baddaddc8",
|
|
145
|
-
"schemas/v2/error-envelope.schema.json": "
|
|
145
|
+
"schemas/v2/error-envelope.schema.json": "d1a6862930e7c3fbc8ec334ef17953ac7b716525c6c82769edf89d0809d0c94b",
|
|
146
146
|
"schemas/v2/eval-summary.schema.json": "2f384daf05b8f3b4f604688cae504d6d9971ba41b8c771e55fe9a17e09e86b7a",
|
|
147
147
|
"schemas/v2/export-bundle.schema.json": "bd28905a7729437ab7f2ad8bb33e13a371b2bf5605a7eb2962a0d246de312d69",
|
|
148
148
|
"schemas/v2/form-content-pack-manifest.schema.json": "a1942a124e250a6e9fbdb6b811c5a125e78fbcbcaa46dfc71d0636f24710a339",
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
"schemas/v2/prompt-ref.schema.json": "11538fa36a7a652c93100cac4f1857eb4130137b546e3d94890618636d44b729",
|
|
168
168
|
"schemas/v2/prompt-template.schema.json": "494fc83028cb3af5dbb8a943d0e83b2348f7775e9fa02369162bcb58a6f81559",
|
|
169
169
|
"schemas/v2/proposal.schema.json": "a5c582ce1e854e5fe10412ebed00e537961ee827bbaca506ca374b200a685992",
|
|
170
|
-
"schemas/v2/registry-version-manifest.schema.json": "
|
|
170
|
+
"schemas/v2/registry-version-manifest.schema.json": "940cfd82511690cd6f2539e5676b185e7812628d0707c283fd7ffb33b6e54afd",
|
|
171
171
|
"schemas/v2/residency.schema.json": "3fd23c9732beb140504e81560fc426eb6764d547e56da5fb92c5a5c7bf04db3a",
|
|
172
172
|
"schemas/v2/run-ancestry-response.schema.json": "334c17f2062dea62726ca1d07b8be3f169cdc422af843c1def5fb317039814c9",
|
|
173
173
|
"schemas/v2/run-diff-response.schema.json": "8bdb2045c274580038bd74781f7d062f5049839797c164a89de4ed89edc53a74",
|
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
"schemas/v2/self-hosted-runner-result-frame.schema.json": "8e739a721f350226f3ffb4af752b4fe0a72016042a7b6f42e16c0487c3c58983",
|
|
184
184
|
"schemas/v2/subject-link.schema.json": "1fc941d6c14af886534009f2ffa6611e2035eb07e30b1df918c50d6bafff026c",
|
|
185
185
|
"schemas/v2/subject.schema.json": "094e981fb20052a911393e219ea5f2065d8b8417b65080a70c5ea99fd960a8fe",
|
|
186
|
-
"schemas/v2/suspend-request.schema.json": "
|
|
186
|
+
"schemas/v2/suspend-request.schema.json": "29c9cfa6978342f0175e250607e6eddb5e4058ea0c4adacc1965e1f3bb4dc9ee",
|
|
187
187
|
"schemas/v2/tool-descriptor.schema.json": "8c98fb5e93bc649dff859d3d1dde8075db929efd6c49c496252d0a88cccc73e1",
|
|
188
188
|
"schemas/v2/trigger-event.schema.json": "710ecca8b3ecdad3a532a96768177f062192b24a589a4f798c7eb24bab64b30a",
|
|
189
189
|
"schemas/v2/trigger-subscription-registration.schema.json": "04bc314a07c7e8b565577cb8df5821d968ce2af915900dfdccd4a24e928d20db",
|
|
@@ -204,24 +204,24 @@
|
|
|
204
204
|
"schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
|
|
205
205
|
"spec/v1/alias-detectors.json": "29f21e0fb2c7a232a92a5c19b4cba3a86bc3b5e16f06780452f0421c66c6ec38",
|
|
206
206
|
"spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
|
|
207
|
-
"spec/v1/core-standard-manifest.json": "
|
|
207
|
+
"spec/v1/core-standard-manifest.json": "4c43eec4ac5a43b026949f9d9c65d6b92f7b79706a185c4a147e232b1be0c945",
|
|
208
208
|
"spec/v1/deprecations.json": "46df807fb4adf0aae24671c20f69975cf3496e3bc3a521cb7e6ea556bdce563c",
|
|
209
209
|
"spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
|
|
210
210
|
"spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
|
|
211
211
|
"spec/v1/event-codemap.schema.json": "d05933b2e88103aff51a2f774df97b9bda065e0fc88dbd9b114cc47a32189174",
|
|
212
212
|
"spec/v1/extensions.json": "79a60754aa16cbdbdb604f8e5c00038af535a1690a4d07bd5ff7c38f0256a7bc",
|
|
213
|
-
"spec/v1/gaps.json": "
|
|
213
|
+
"spec/v1/gaps.json": "2d4e4772cd647b471f989bdb6416ca902b11e10d17dc7f84825bb9f2eef1314c",
|
|
214
214
|
"spec/v1/gaps.schema.json": "8fd83259f556553c9df0f53e7a82ca8c2a4771d471197f69b4e2ae0ceeacfd66",
|
|
215
215
|
"spec/v1/migrations.json": "2efcd9064b2bc65a84a3b3d281aa86a91efd002690c1f4f14e8335c4a18194d1",
|
|
216
216
|
"spec/v1/migrations.schema.json": "886779aa6c22e646db097f5df210adb018a4dd14a7b815465a18c8a7056c8f72",
|
|
217
217
|
"spec/v1/operation-path-manifest.json": "fada0342815fb129c1ae61f1829bd6e34417717408ce86fcc54280f373c19e74",
|
|
218
218
|
"spec/v1/spec-gaps.json": "ea9612af55bea9dae3e82223d4f714243f8f590b0193f1188257b13491a38afc",
|
|
219
219
|
"spec/v2/README.md": "4fa067d377580495e60baf08a4eafc3214b8ca48d8fb270bf91fc5066fad7186",
|
|
220
|
-
"spec/v2/core/capabilities.md": "
|
|
220
|
+
"spec/v2/core/capabilities.md": "3d1a1975cb688244ca18cd194f56f3a995cc81a9155277dbaeeaa51eabb3a0c7",
|
|
221
221
|
"spec/v2/core/conformance.md": "e7bd7d54062f28801071165a1b92edbeee3774bb3710f31eed75817139b66d95",
|
|
222
222
|
"spec/v2/core/connection-packs.md": "3168d2c8f1a59417a7256a678f4203570be434f64dead6b11fdec86b352686df",
|
|
223
223
|
"spec/v2/core/conversation.md": "6db43d8160bc45d3944aa1151816a6f0fd1669d59d7f822267a2d6cc9db22990",
|
|
224
|
-
"spec/v2/core/errors.md": "
|
|
224
|
+
"spec/v2/core/errors.md": "7931c9411e50b5ebfbc20d15aaf0e7b1a5b3e0ddc558b931e432da49ce09152c",
|
|
225
225
|
"spec/v2/core/events.md": "234338deb97be648731e8ab2126a90b7b8ad0a7070d98ffc85ee4689ff059791",
|
|
226
226
|
"spec/v2/core/form-content-packs.md": "41d40afe0a3760bcd792a4c406e16f28b0ab0aa7b0925323110d3fa2730d5400",
|
|
227
227
|
"spec/v2/core/headers.md": "5cb1624fa63acc81992b6a6d842ec392cf7271aaf2f65d1f83c8c4fa286e6730",
|
|
@@ -230,49 +230,50 @@
|
|
|
230
230
|
"spec/v2/core/idempotency.md": "21830b7d23dd03e9e8ba4b0de253dd001fc960f166310d6b6b60b1c33cf60b30",
|
|
231
231
|
"spec/v2/core/identity.md": "acb7f739729c69a1f40dcaa5b1d873ba9f32993c17bfd297f5fa56351cf47c69",
|
|
232
232
|
"spec/v2/core/interop.md": "3cfa2af70ae6ba1f4b4268447d46d1d2f5fb65f7917f15bad159d9779d25b1a6",
|
|
233
|
-
"spec/v2/core/interrupt.md": "
|
|
233
|
+
"spec/v2/core/interrupt.md": "5e6fe9767228638095d5a2210307e1febfcefda808f4a400f2c7c528b929c445",
|
|
234
234
|
"spec/v2/core/node-pack-runtimes.md": "d52966319c53aaa03575025915b73b470a42832c039c41d73aff6931758630cb",
|
|
235
235
|
"spec/v2/core/oauth.md": "57f167d899993b6959c243e4102819f4014b365299b9b2b253decd2781e6b8b7",
|
|
236
236
|
"spec/v2/core/overview.md": "3dbc8c0cf7474b31816b1a12412c1edf5597066c38edcdbca4dd2e465880f65b",
|
|
237
|
-
"spec/v2/core/packs.md": "
|
|
237
|
+
"spec/v2/core/packs.md": "e46bf8cd34ac9566fe1e5601f67e7a17fce9639b8676e0f5278a10c6ca70ab29",
|
|
238
238
|
"spec/v2/core/persistence.md": "bba672ed3bf6d83af6e4e65c0f4f480fc21fe12207811e321306af2108d9010d",
|
|
239
239
|
"spec/v2/core/portability.md": "a35fbe66b8b5ccf63cf0216157d4b61d4c47f6dc8e757c830569ddf244e21e8a",
|
|
240
240
|
"spec/v2/core/replay.md": "a5be4e05242f25f8b5a8a4fb31bd359d5abb4bf34fa8e536403a8ee5779ab876",
|
|
241
241
|
"spec/v2/core/runs.md": "72a94c27d884036d84290e9b108702e98b0c6b2fb598213bbda44fba9c9f860d",
|
|
242
|
-
"spec/v2/core/security-defaults.md": "
|
|
242
|
+
"spec/v2/core/security-defaults.md": "e77cf97290ffb0c201706ea10cc0883d717d034353ae0632067838ef227d3749",
|
|
243
243
|
"spec/v2/core/tool-catalog.md": "6b54b8ce221107de19c718f374a720123c6a5f55db4054e6625f73d06e0a442f",
|
|
244
244
|
"spec/v2/core/versioning.md": "2bfe16a72a909c46b4585fab39dc9e94c97506ed95df6b1b527f97b4cd3684ef",
|
|
245
245
|
"spec/v2/core/webhooks.md": "78fe97ab5bdee20218183efe32abf5ba1996705d757cb37c8d6a4d808c44199f",
|
|
246
246
|
"spec/v2/core/workflow-chain-packs.md": "35a957f1dd22016febd76bf2c4dfb79cda58b95d8efd8893b5e5babda9f79cf3",
|
|
247
|
-
"spec/v2/corrections.json": "
|
|
247
|
+
"spec/v2/corrections.json": "59f647f4d22477f6c2b06dcf913b682dde5d51dd5e607a5d0cf648ab60864d6e",
|
|
248
248
|
"spec/v2/corrections.schema.json": "46bbffb954d13bd9995aebc912ceeb2544f7064331abf25fd6ab85c81a5cec94",
|
|
249
|
-
"spec/v2/declaration.json": "
|
|
249
|
+
"spec/v2/declaration.json": "48cf54bc5ab04c00df3079b3fee701802de35a5a211dbfaf62c6a16bd4d3cd06",
|
|
250
250
|
"spec/v2/declaration.schema.json": "ef951633e4070899f64828f5cf1b5f4870270a0360f81dbb49b5b4d6cd407550",
|
|
251
|
-
"spec/v2/errors.json": "
|
|
251
|
+
"spec/v2/errors.json": "dc1a846abe3e888acb0ce4a33cf9d1fb98e41fcc8b49f42b3682fd65deca2eb2",
|
|
252
252
|
"spec/v2/errors.schema.json": "47404ad53e6147cf9017b813483712466573e419fb33e6c4b6c12d3e7deba1bc",
|
|
253
253
|
"spec/v2/event-codemap.json": "37869a03cdfd8758b57e6f350641a6ebbd383231cebda51f5473ad85f94ab74c",
|
|
254
254
|
"spec/v2/event-codemap.schema.json": "b173f2a9bcc0bf9b62a474ce3b91e1431d597e64fd1560fc58662cd8606eaf9c",
|
|
255
255
|
"spec/v2/ext/README.md": "e2731ebb5156ece1840ed3b96e7a347f6345e73071ff4e54df0f21c1585c5027",
|
|
256
256
|
"spec/v2/ext/a2uiSurface/README.md": "5518c7f355262eb7cc584422f4ff8e548f066b211878f969274a3a578127622d",
|
|
257
|
-
"spec/v2/ext/brand/README.md": "
|
|
258
|
-
"spec/v2/ext/canvas/README.md": "
|
|
259
|
-
"spec/v2/ext/chat/README.md": "
|
|
260
|
-
"spec/v2/ext/coordination/README.md": "
|
|
261
|
-
"spec/v2/ext/dataIntegration/README.md": "
|
|
262
|
-
"spec/v2/ext/entities/README.md": "
|
|
257
|
+
"spec/v2/ext/brand/README.md": "4f9db1715219070fc57e06cc9b066f13aa130dd50f4869722d908e6e15720787",
|
|
258
|
+
"spec/v2/ext/canvas/README.md": "9a08469f1cbde2b0d6594ce76f8cce94b9fb5415979ef5e2ecb732f311c86bae",
|
|
259
|
+
"spec/v2/ext/chat/README.md": "07e9ca34b3e5fc222ace1c2af60375266e001d1695da87f38f56b58f17aeaead",
|
|
260
|
+
"spec/v2/ext/coordination/README.md": "c7fe0884f52405fdd02c5f9ec309ac6b585685ee06ec5f338fff7acf03c07148",
|
|
261
|
+
"spec/v2/ext/dataIntegration/README.md": "a18e179cf88eb5d3e2fe2e017018141747a64a984d25a4f4ed32d776ea62e26d",
|
|
262
|
+
"spec/v2/ext/entities/README.md": "956d4a9a2aedb8cb9c79ce8145580480920474cc6943f0e33ede32d7b20414dc",
|
|
263
263
|
"spec/v2/ext/grpc-transport/README.md": "6ba378e3c6d1f2e3269b392cfdf0c40565ee9bfa259d1baaa9ee1d350e38e792",
|
|
264
|
-
"spec/v2/ext/kanban/README.md": "
|
|
265
|
-
"spec/v2/ext/knowledge/README.md": "
|
|
266
|
-
"spec/v2/ext/launchStudio/README.md": "
|
|
267
|
-
"spec/v2/ext/messaging/README.md": "
|
|
264
|
+
"spec/v2/ext/kanban/README.md": "df179bea93a35c7a9d2556f2abefe2d53e05e33730b0b8cb2a9facab0e394ed2",
|
|
265
|
+
"spec/v2/ext/knowledge/README.md": "ab50dff74a1f285f890fab93f2d3a85f41032fff9191e4623ce9cbaed6d12124",
|
|
266
|
+
"spec/v2/ext/launchStudio/README.md": "ed33abe5bb000288e1be52f2b7a88175c7666a4390a599a6a77844eab564cabe",
|
|
267
|
+
"spec/v2/ext/messaging/README.md": "484cfa3091f89261dbdd48372afba441d3e6c16c3169a861b0ed7fc8c2902780",
|
|
268
268
|
"spec/v2/ext/portability/README.md": "c59423ee446638b748a911b9e97e803c4786157be6120f656f0a060586bc7b82",
|
|
269
269
|
"spec/v2/ext/provider-idempotency/README.md": "9d1bccef0ec16a19a3370ae79c2f5a1726ce0d1d25a51352907f288a2ccb15d7",
|
|
270
270
|
"spec/v2/ext/provider-idempotency/registry.json": "5b5fecad604fb4ab38da39f5abe697a4e57b05406c398f8afeb377331b2bd965",
|
|
271
|
-
"spec/v2/ext/restTransport/README.md": "
|
|
271
|
+
"spec/v2/ext/restTransport/README.md": "acda51f1a973308841861a119def691db972f310c7b7250ab14f248e9c968585",
|
|
272
272
|
"spec/v2/ext/sandbox-runtime-notes/README.md": "0d1290fb1e20b2e35c0b15051541ab6546e4e2a6f693b4f912cb3f7d6a65e92c",
|
|
273
|
-
"spec/v2/ext/webResearch/README.md": "
|
|
273
|
+
"spec/v2/ext/webResearch/README.md": "3ec0fa2c798bcfe16a545c6b47a624c44b776561ff46fa754711e5f6677826f8",
|
|
274
274
|
"spec/v2/facets/a2a.schema.json": "b68bf5e29e455e46c2ad7cfcccf5963730ec2310ff9db81884550d8df30fd6ef",
|
|
275
275
|
"spec/v2/facets/aiProviders.schema.json": "81fb39b6698564d9d07a0d10525ecb6360c152fe3f3a5ea0439112509ca130e2",
|
|
276
|
+
"spec/v2/facets/auditLogIntegrity.schema.json": "9a7f81320c8f940c6f4129bc7dd895a4063231c5458a717a08c2cbf9bb886c46",
|
|
276
277
|
"spec/v2/facets/auth.schema.json": "7d98bc81a0f7d307460d039eebfebd21388091bfe1d538762583940f94c0bd2a",
|
|
277
278
|
"spec/v2/facets/envelopeStrictness.schema.json": "28be0a27f880bd45ec1222a16155f5e9167bbca57bf0322970fe1eb2654562aa",
|
|
278
279
|
"spec/v2/facets/heartbeat.schema.json": "81d7fa1895a8b0241bd07cf00a7e7905bef0fd14eb3427325261c62eee5746a6",
|
|
@@ -297,9 +298,9 @@
|
|
|
297
298
|
"spec/v2/path-manifest.json": "17298a245c5da9a70aa7ab0b7f2db4277e06209d6fcdd7187a8b4cea179c08f3",
|
|
298
299
|
"spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
|
|
299
300
|
"spec/v2/profiles.json": "180beb9ef2d161766fa23b2987e7e829851e3d316273147cf6a4a3f324befd6b",
|
|
300
|
-
"spec/v2/release.json": "
|
|
301
|
+
"spec/v2/release.json": "8bd257db47f63fdc4759ed13f34df309d80cd69ee83b96a24aed8bb3a9810b1d",
|
|
301
302
|
"spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
|
|
302
|
-
"spec/v2/surface-baseline.json": "
|
|
303
|
+
"spec/v2/surface-baseline.json": "d47f26c3194de0e0d42a731ecaeeae1c2adfdea8c09314792d15c02b50d62efb"
|
|
303
304
|
},
|
|
304
|
-
"corpusCommit": "
|
|
305
|
+
"corpusCommit": "49be1aa2be9d835a80f934ce98dc4cebf0af46a4"
|
|
305
306
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The audit-log-integrity advertisement, resolved for the target major.
|
|
3
|
+
*
|
|
4
|
+
* major 1 `capabilities.auth.profiles[]` names `openwop-audit-log-integrity`
|
|
5
|
+
* and `capabilities.auth.auditLogIntegrity` carries the facets
|
|
6
|
+
* (spec/v1/auth-profiles.md §"Audit-log integrity").
|
|
7
|
+
* major 2 the `auditLogIntegrity` family record is present at the v2 root
|
|
8
|
+
* (RFC 0224; spec/v2/core/security-defaults.md §Audit-log integrity).
|
|
9
|
+
* Presence is the claim, so there is no `hashChain` seat.
|
|
10
|
+
*
|
|
11
|
+
* `auditVerifyPath()` is `/v1/audit/verify` at major 1 and `/audit/verify` at
|
|
12
|
+
* major 2, so a scenario never reports a suite 404 as a host defect.
|
|
13
|
+
*/
|
|
14
|
+
import { driver } from './driver.js';
|
|
15
|
+
import { capabilityFamily } from './discovery-capabilities.js';
|
|
16
|
+
import { targetMajor } from './seams.js';
|
|
17
|
+
import { familyAdvertised } from './v2.js';
|
|
18
|
+
|
|
19
|
+
export const AUDIT_PROFILE = 'openwop-audit-log-integrity';
|
|
20
|
+
|
|
21
|
+
export interface AuditIntegrityCaps {
|
|
22
|
+
hashChain?: boolean;
|
|
23
|
+
checkpointSignatureAlgorithm?: string;
|
|
24
|
+
checkpointPublicKey?: string;
|
|
25
|
+
checkpointIntervalEntries?: number;
|
|
26
|
+
checkpointIntervalSeconds?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The advertised facets, or null when the host makes no audit-log-integrity claim at the target major. */
|
|
30
|
+
export async function auditIntegrityAdvert(): Promise<AuditIntegrityCaps | null> {
|
|
31
|
+
if (targetMajor() === 2) return (await familyAdvertised('auditLogIntegrity')) as AuditIntegrityCaps | null;
|
|
32
|
+
const disco = await driver.get('/.well-known/openwop');
|
|
33
|
+
const auth = capabilityFamily<{ profiles?: string[]; auditLogIntegrity?: AuditIntegrityCaps }>(disco.json, 'auth') ?? {};
|
|
34
|
+
if (!(Array.isArray(auth.profiles) && auth.profiles.includes(AUDIT_PROFILE))) return null;
|
|
35
|
+
return auth.auditLogIntegrity ?? {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Where the claim lives at the target major, for skip reasons and failure messages. */
|
|
39
|
+
export function auditClaimName(): string {
|
|
40
|
+
return targetMajor() === 2 ? 'the auditLogIntegrity family' : AUDIT_PROFILE;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function auditVerifyPath(fromSeq: number, toSeq: number): string {
|
|
44
|
+
return `${targetMajor() === 2 ? '' : '/v1'}/audit/verify?fromSeq=${fromSeq}&toSeq=${toSeq}`;
|
|
45
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 0218 §C — an audit anomaly's shape, server-free
|
|
3
|
+
* (`spec/v1/auth-profiles.md` §"Audit-log integrity" 4).
|
|
4
|
+
*
|
|
5
|
+
* A black-box caller cannot tamper with a host's audit log, so an untampered log
|
|
6
|
+
* serves no anomaly and `audit-log-integrity.test.ts` sees an empty array. This
|
|
7
|
+
* file holds the shape instead: one sample anomaly of every `kind` validates
|
|
8
|
+
* against `audit-verify-result.schema.json` (v1 and the derived v2 copy), and the
|
|
9
|
+
* shapes that must not are refused, the reference hosts' pre-§C
|
|
10
|
+
* `{ atSequence, kind, detail }` first among them. The host side is the tamper
|
|
11
|
+
* tests in openwop-examples, which assert every kind's member set.
|
|
12
|
+
*
|
|
13
|
+
* Sabotages, each of which turns a leg red: dropping the `checkpoint` conditional
|
|
14
|
+
* (a `merkle-mismatch` without it validates); dropping the `chain-break`
|
|
15
|
+
* conditional (a `chain-break` without its hashes validates); dropping the
|
|
16
|
+
* `else` arms (a mixed entry validates); dropping the root `chainValid`
|
|
17
|
+
* conditional (a forged-signature report with `chainValid: true` validates);
|
|
18
|
+
* admitting `atSequence` and dropping `atSeq` from `required` (the hosts' old
|
|
19
|
+
* hash-mismatch validates).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { describe, it, expect } from 'vitest';
|
|
23
|
+
import { readFileSync } from 'node:fs';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
26
|
+
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
27
|
+
import { req } from '../lib/requirement-ids.js';
|
|
28
|
+
|
|
29
|
+
const SPEC = 'RFC 0218 §C · spec/v1/auth-profiles.md §"Audit-log integrity" 4';
|
|
30
|
+
const SHAPE = 'openwop.requirement.0218.anomaly-shape';
|
|
31
|
+
|
|
32
|
+
const HASH_A = 'a'.repeat(64);
|
|
33
|
+
const HASH_B = 'b'.repeat(64);
|
|
34
|
+
const CHECKPOINT = { checkpoint: 'cp_5', atSequence: 5, merkleRoot: HASH_A, signature: 'c2ln' };
|
|
35
|
+
|
|
36
|
+
const result = (anomalies: object[], chainValid = anomalies.length === 0): object => ({
|
|
37
|
+
fromSeq: 1,
|
|
38
|
+
toSeq: 5,
|
|
39
|
+
chainValid,
|
|
40
|
+
checkpoints: [CHECKPOINT],
|
|
41
|
+
anomalies,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const VALID: Array<[string, object]> = [
|
|
45
|
+
['a chain-break with both hashes', { atSeq: 4, kind: 'chain-break', expectedPrevHash: HASH_A, actualPrevHash: HASH_B, detail: 'prev_hash differs' }],
|
|
46
|
+
['a chain-break at the genesis entry (null hashes)', { atSeq: 1, kind: 'chain-break', expectedPrevHash: null, actualPrevHash: HASH_B }],
|
|
47
|
+
['a legacy entry without kind (a chain-break)', { atSeq: 4, expectedPrevHash: HASH_A, actualPrevHash: '' }],
|
|
48
|
+
['a hash-mismatch', { atSeq: 3, kind: 'hash-mismatch', detail: 'recomputed != stored' }],
|
|
49
|
+
['a missing-entry', { atSeq: 2, kind: 'missing-entry' }],
|
|
50
|
+
['a merkle-mismatch with its checkpoint', { atSeq: 5, kind: 'merkle-mismatch', checkpoint: 'cp_5' }],
|
|
51
|
+
['a signature-invalid with its checkpoint', { atSeq: 5, kind: 'signature-invalid', checkpoint: 'cp_5', detail: 'does not verify' }],
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const INVALID: Array<[string, object]> = [
|
|
55
|
+
// The reference hosts' pre-§C hash-mismatch, verbatim: wrong only in the
|
|
56
|
+
// sequence member's name, so nothing but `atSeq` can refuse it.
|
|
57
|
+
['`atSequence` for `atSeq` (the reference hosts\' pre-§C shape)', { atSequence: 3, kind: 'hash-mismatch', detail: 'recomputed != stored' }],
|
|
58
|
+
['a merkle-mismatch without `checkpoint`', { atSeq: 5, kind: 'merkle-mismatch' }],
|
|
59
|
+
['a signature-invalid without `checkpoint`', { atSeq: 5, kind: 'signature-invalid', detail: 'x' }],
|
|
60
|
+
['a chain-break without its hashes', { atSeq: 4, kind: 'chain-break', detail: 'x' }],
|
|
61
|
+
['a legacy entry without kind or hashes', { atSeq: 4 }],
|
|
62
|
+
['a mixed entry: a merkle-mismatch carrying the chain hashes', { atSeq: 5, kind: 'merkle-mismatch', checkpoint: 'cp_5', expectedPrevHash: HASH_A, actualPrevHash: HASH_B }],
|
|
63
|
+
['a mixed entry: a chain-break carrying a checkpoint', { atSeq: 4, kind: 'chain-break', expectedPrevHash: HASH_A, actualPrevHash: HASH_B, checkpoint: 'cp_5' }],
|
|
64
|
+
['a hash-mismatch carrying a checkpoint', { atSeq: 3, kind: 'hash-mismatch', checkpoint: 'cp_5' }],
|
|
65
|
+
['an unknown kind', { atSeq: 3, kind: 'row-rewritten' }],
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const SCHEMAS = [['v1', 'audit-verify-result.schema.json'], ['v2', join('v2', 'audit-verify-result.schema.json')]].map(([label, file]) => {
|
|
69
|
+
const schema = JSON.parse(readFileSync(join(SCHEMAS_DIR, file!), 'utf8')) as object;
|
|
70
|
+
return { label: label!, validate: new Ajv2020({ strict: false, allErrors: true }).compile(schema) };
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('RFC 0218 §C — audit anomaly shape (server-free, v1 and v2 schemas)', () => {
|
|
74
|
+
it('every anomaly kind validates in its own shape', () => {
|
|
75
|
+
for (const { label, validate } of SCHEMAS) {
|
|
76
|
+
for (const [name, anomaly] of VALID) {
|
|
77
|
+
const ok = validate(result([anomaly]));
|
|
78
|
+
expect(ok, req(SHAPE, SPEC, `${label}: ${name} validates: ${JSON.stringify(validate.errors)}`)).toBe(true);
|
|
79
|
+
}
|
|
80
|
+
expect(validate(result([])), req(SHAPE, SPEC, `${label}: an untampered result with no anomaly validates`)).toBe(true);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('an anomaly outside its kind\'s shape is refused', () => {
|
|
85
|
+
for (const { label, validate } of SCHEMAS) {
|
|
86
|
+
for (const [name, anomaly] of INVALID) {
|
|
87
|
+
expect(validate(result([anomaly])), req(SHAPE, SPEC, `${label}: ${name} is refused`)).toBe(false);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('chainValid is false exactly when anomalies is non-empty', () => {
|
|
93
|
+
const forged = { atSeq: 5, kind: 'signature-invalid', checkpoint: 'cp_5' };
|
|
94
|
+
for (const { label, validate } of SCHEMAS) {
|
|
95
|
+
expect(validate(result([forged], true)), req(SHAPE, SPEC, `${label}: chainValid: true beside a signature-invalid anomaly is refused`)).toBe(false);
|
|
96
|
+
expect(validate(result([], false)), req(SHAPE, SPEC, `${label}: chainValid: false with no anomaly is refused`)).toBe(false);
|
|
97
|
+
expect(validate(result([forged], false)), req(SHAPE, SPEC, `${label}: chainValid: false beside the anomaly validates`)).toBe(true);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* RFC 0218 — a host's audit checkpoints are signed over their Merkle root
|
|
3
3
|
* (`spec/v1/auth-profiles.md` §"Audit-log integrity" 3).
|
|
4
4
|
*
|
|
5
|
-
* Gated on the `openwop-audit-log-integrity` profile
|
|
6
|
-
* `
|
|
5
|
+
* Gated on the `openwop-audit-log-integrity` profile at major 1 and on the
|
|
6
|
+
* `auditLogIntegrity` family at major 2 (RFC 0224; spec/v2/core/
|
|
7
|
+
* security-defaults.md §Audit-log integrity). Every checkpoint
|
|
8
|
+
* `GET /v1/audit/verify` (major 2: `GET /audit/verify`) returns MUST verify under the advertised
|
|
7
9
|
* `checkpointPublicKey` as Ed25519 over its hex-decoded `merkleRoot`. The root
|
|
8
10
|
* itself cannot be recomputed from outside (the entries are not on the wire),
|
|
9
11
|
* so the black box witnesses the signature half only. The root half belongs to
|
|
@@ -15,38 +17,35 @@
|
|
|
15
17
|
|
|
16
18
|
import { describe, it, expect } from 'vitest';
|
|
17
19
|
import { driver } from '../lib/driver.js';
|
|
18
|
-
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
19
20
|
import { req } from '../lib/requirement-ids.js';
|
|
20
21
|
import { softSkip } from '../lib/soft-skip.js';
|
|
21
22
|
import { checkpointPublicKey, verifyCheckpointSignature } from '../lib/audit-checkpoint.js';
|
|
23
|
+
import { auditClaimName, auditIntegrityAdvert, auditVerifyPath } from '../lib/auditIntegrity.js';
|
|
22
24
|
|
|
23
|
-
const SPEC = 'RFC 0218 · spec/v1/auth-profiles.md §"Audit-log integrity" 3';
|
|
25
|
+
const SPEC = 'RFC 0218 · spec/v1/auth-profiles.md §"Audit-log integrity" 3; spec/v2/core/security-defaults.md §"Audit-log integrity" (RFC 0224)';
|
|
24
26
|
const SIGNED = 'openwop.requirement.0218.checkpoint-signature-over-root';
|
|
25
27
|
|
|
26
|
-
interface AuditIntegrityCaps { checkpointPublicKey?: string }
|
|
27
|
-
interface AuthCaps { profiles?: string[]; auditLogIntegrity?: AuditIntegrityCaps }
|
|
28
|
-
|
|
29
28
|
describe('RFC 0218 — a host\'s checkpoints are signed over their root', () => {
|
|
30
29
|
it('every checkpoint GET /v1/audit/verify returns verifies under the advertised key', async () => {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return softSkip('inapplicable', 'the host does not claim openwop-audit-log-integrity');
|
|
30
|
+
const integrity = await auditIntegrityAdvert();
|
|
31
|
+
if (integrity === null) {
|
|
32
|
+
return softSkip('inapplicable', `the host does not claim ${auditClaimName()}`);
|
|
35
33
|
}
|
|
36
|
-
const advertised =
|
|
34
|
+
const advertised = integrity.checkpointPublicKey;
|
|
37
35
|
if (typeof advertised !== 'string' || advertised.length === 0) {
|
|
38
36
|
// A claimed profile without its key is the profile-shape leg's failure
|
|
39
37
|
// (audit-log-integrity.test.ts); this row cannot be observed without it.
|
|
40
|
-
return softSkip('blocked',
|
|
38
|
+
return softSkip('blocked', `the host claims ${auditClaimName()} but advertises no auditLogIntegrity.checkpointPublicKey`);
|
|
41
39
|
}
|
|
42
40
|
let key;
|
|
43
41
|
try { key = checkpointPublicKey(advertised); } catch (e) {
|
|
44
42
|
return softSkip('blocked', `auditLogIntegrity.checkpointPublicKey is not an Ed25519 SPKI key: ${(e as Error).message}`);
|
|
45
43
|
}
|
|
46
44
|
|
|
47
|
-
const
|
|
45
|
+
const path = auditVerifyPath(0, 1_000_000);
|
|
46
|
+
const verify = await driver.get(path);
|
|
48
47
|
if (verify.status !== 200) {
|
|
49
|
-
return softSkip('blocked', `GET
|
|
48
|
+
return softSkip('blocked', `GET ${path.split('?')[0]} answered ${verify.status}; the verify leg of audit-log-integrity owns that contract`);
|
|
50
49
|
}
|
|
51
50
|
const checkpoints = ((verify.json as { checkpoints?: Array<{ atSequence?: number; merkleRoot?: string; signature?: string }> }).checkpoints) ?? [];
|
|
52
51
|
if (checkpoints.length === 0) {
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Track 13: audit-log integrity profile (auth-profiles.md v1.1)
|
|
2
|
+
* Track 13: audit-log integrity profile (auth-profiles.md v1.1), and at major 2
|
|
3
|
+
* the `auditLogIntegrity` family (RFC 0224, spec/v2/core/security-defaults.md
|
|
4
|
+
* §Audit-log integrity).
|
|
3
5
|
*
|
|
4
|
-
* Verifies that hosts claiming
|
|
5
|
-
* 1.
|
|
6
|
-
*
|
|
6
|
+
* Verifies that hosts claiming audit-log integrity:
|
|
7
|
+
* 1. Advertise the facets a verifier needs — at major 1
|
|
8
|
+
* `capabilities.auth.auditLogIntegrity.hashChain: true` plus the key; at
|
|
9
|
+
* major 2 a record that validates against the generated family schema,
|
|
10
|
+
* whose `checkpointPublicKey` is an Ed25519 SPKI key.
|
|
11
|
+
* 2. Expose `GET /v1/audit/verify` (major 2: `GET /audit/verify`) which
|
|
12
|
+
* returns `{chainValid, checkpoints, anomalies}`.
|
|
7
13
|
* 3. Report `chainValid: true` for an unmodified range.
|
|
8
14
|
* 4. Surface at least one signed checkpoint with a non-empty `signature`.
|
|
15
|
+
* 5. List checkpoints ascending, no more than `checkpointIntervalEntries`
|
|
16
|
+
* apart (RFC 0224 §A.3). A host that omits a checkpoint from the verify
|
|
17
|
+
* body leaves a gap the advertised cadence forbids.
|
|
18
|
+
*
|
|
19
|
+
* Major 1 gates through behaviorGate (strict mode fails an unadvertised
|
|
20
|
+
* profile). Major 2 records `inapplicable` before any assertion when the
|
|
21
|
+
* family is absent — presence of the record is the claim.
|
|
9
22
|
*
|
|
10
23
|
* Tamper detection (mutating an entry then asserting `chainValid: false`)
|
|
11
24
|
* requires admin access to the host's audit store and is NOT exercised
|
|
@@ -13,14 +26,18 @@
|
|
|
13
26
|
* test for tamper detection — see auth-profiles.md.
|
|
14
27
|
*
|
|
15
28
|
* @see spec/v1/auth-profiles.md §"Audit-log integrity"
|
|
29
|
+
* @see spec/v2/core/security-defaults.md §"Audit-log integrity"
|
|
16
30
|
*/
|
|
17
31
|
|
|
18
32
|
import { describe, it, expect } from 'vitest';
|
|
19
33
|
import { driver } from '../lib/driver.js';
|
|
20
34
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
21
|
-
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
22
35
|
import { req } from '../lib/requirement-ids.js';
|
|
23
36
|
import { softSkip } from '../lib/soft-skip.js';
|
|
37
|
+
import { targetMajor } from '../lib/seams.js';
|
|
38
|
+
import { v2RefValidator, v2Validator } from '../lib/v2.js';
|
|
39
|
+
import { checkpointPublicKey } from '../lib/audit-checkpoint.js';
|
|
40
|
+
import { AUDIT_PROFILE, auditClaimName, auditIntegrityAdvert, auditVerifyPath, type AuditIntegrityCaps } from '../lib/auditIntegrity.js';
|
|
24
41
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
25
42
|
import { readFileSync } from 'node:fs';
|
|
26
43
|
import { join } from 'node:path';
|
|
@@ -31,67 +48,77 @@ import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
|
31
48
|
* is enforced. (Until 2026-09-27 the suite stripped them because the reference
|
|
32
49
|
* postgres/sqlite hosts emitted a per-checkpoint `verified` bit the schema does
|
|
33
50
|
* not name; the hosts now keep that bit host-internal. The aggregate verdict is
|
|
34
|
-
* the schema's OPTIONAL `checkpointsValid`.)
|
|
51
|
+
* the schema's OPTIONAL `checkpointsValid`.) Major 2 validates against the v2 copy.
|
|
35
52
|
*/
|
|
36
53
|
const VERIFY_RESULT_SCHEMA = JSON.parse(
|
|
37
54
|
readFileSync(join(SCHEMAS_DIR, 'audit-verify-result.schema.json'), 'utf8'),
|
|
38
55
|
) as Record<string, unknown>;
|
|
39
56
|
|
|
40
|
-
|
|
41
|
-
hashChain?: boolean;
|
|
42
|
-
checkpointSignatureAlgorithm?: string;
|
|
43
|
-
checkpointPublicKey?: string;
|
|
44
|
-
checkpointIntervalEntries?: number;
|
|
45
|
-
checkpointIntervalSeconds?: number;
|
|
46
|
-
}
|
|
57
|
+
const CADENCE = 'openwop.requirement.0224.checkpoint-cadence';
|
|
47
58
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
/**
|
|
60
|
+
* The gate, per major. Major 1: behaviorGate on the profile (unchanged). Major 2:
|
|
61
|
+
* the family record or null. A null is recorded `inapplicable` by the caller
|
|
62
|
+
* before any assertion (`unclaimed()`).
|
|
63
|
+
*/
|
|
64
|
+
async function gate(): Promise<AuditIntegrityCaps | null> {
|
|
65
|
+
const caps = await auditIntegrityAdvert();
|
|
66
|
+
if (targetMajor() === 2) return caps;
|
|
67
|
+
return behaviorGate(AUDIT_PROFILE, caps !== null) ? caps : null;
|
|
51
68
|
}
|
|
52
69
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
70
|
+
const unclaimed = (): undefined => softSkip('inapplicable', `the host does not claim ${auditClaimName()} at major ${targetMajor()}`);
|
|
71
|
+
|
|
72
|
+
function validateVerifyBody(body: unknown): { ok: boolean; errors: string } {
|
|
73
|
+
if (targetMajor() === 2) return v2Validator('audit-verify-result')(body);
|
|
74
|
+
const validate = new Ajv2020({ strict: false, allErrors: true }).compile(VERIFY_RESULT_SCHEMA);
|
|
75
|
+
const ok = validate(body) as boolean;
|
|
76
|
+
return { ok, errors: JSON.stringify(validate.errors ?? []) };
|
|
57
77
|
}
|
|
58
78
|
|
|
59
79
|
describe('audit-log-integrity: profile shape', () => {
|
|
60
80
|
it('host that claims the profile advertises required capability fields', async () => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const disco = await driver.get('/.well-known/openwop');
|
|
66
|
-
const integrity =
|
|
67
|
-
capabilityFamily<AuthCaps>(disco.json, 'auth')
|
|
68
|
-
?.auditLogIntegrity ?? {};
|
|
81
|
+
const integrity = await gate();
|
|
82
|
+
if (integrity === null) return unclaimed();
|
|
69
83
|
|
|
70
|
-
|
|
71
|
-
'
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
if (targetMajor() === 2) {
|
|
85
|
+
const shape = v2RefValidator('capabilities.schema.json#/properties/auditLogIntegrity')(integrity);
|
|
86
|
+
expect(shape.ok, req('openwop.it.audit-log-integrity.host-that-claims-the-profile-advertises-required-capability-fields',
|
|
87
|
+
'security-defaults.md §"Audit-log integrity"',
|
|
88
|
+
`the auditLogIntegrity record MUST validate against the generated family schema: ${shape.errors}`,
|
|
89
|
+
)).toBe(true);
|
|
90
|
+
let keyOk = true;
|
|
91
|
+
try { checkpointPublicKey(String(integrity.checkpointPublicKey ?? '')); } catch { keyOk = false; }
|
|
92
|
+
expect(keyOk, req('openwop.it.audit-log-integrity.host-that-claims-the-profile-advertises-required-capability-fields',
|
|
93
|
+
'security-defaults.md §"Audit-log integrity"',
|
|
94
|
+
'auditLogIntegrity.checkpointPublicKey MUST be an Ed25519 SubjectPublicKeyInfo, base64',
|
|
95
|
+
)).toBe(true);
|
|
96
|
+
} else {
|
|
97
|
+
expect(integrity.hashChain, req('openwop.it.audit-log-integrity.host-that-claims-the-profile-advertises-required-capability-fields',
|
|
98
|
+
'auth-profiles.md §"Audit-log integrity"',
|
|
99
|
+
"openwop-audit-log-integrity profile MUST advertise auditLogIntegrity.hashChain: true",
|
|
100
|
+
)).toBe(true);
|
|
101
|
+
expect(integrity.checkpointSignatureAlgorithm, req('openwop.it.audit-log-integrity.host-that-claims-the-profile-advertises-required-capability-fields',
|
|
102
|
+
'auth-profiles.md §"Audit-log integrity" §"Key management"',
|
|
103
|
+
'checkpointSignatureAlgorithm MUST be present (canonical: ed25519)',
|
|
104
|
+
)).toBeDefined();
|
|
105
|
+
expect(typeof integrity.checkpointPublicKey).toBe('string');
|
|
106
|
+
}
|
|
79
107
|
});
|
|
80
108
|
});
|
|
81
109
|
|
|
82
110
|
describe('audit-log-integrity: verify endpoint returns chainValid', () => {
|
|
83
111
|
it('GET /v1/audit/verify on a recent range reports chainValid: true', async () => {
|
|
84
|
-
if (
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
112
|
+
if ((await gate()) === null) return unclaimed();
|
|
87
113
|
|
|
88
|
-
const
|
|
114
|
+
const path = auditVerifyPath(0, 100);
|
|
115
|
+
const verify = await driver.get(path);
|
|
89
116
|
if (verify.status === 404) {
|
|
90
117
|
// Host claims the profile but doesn't expose the endpoint — that's
|
|
91
118
|
// a profile-claim violation. Fail explicitly.
|
|
92
119
|
expect(verify.status, req('openwop.it.audit-log-integrity.get-v1-audit-verify-on-a-recent-range-reports-chainvalid-true',
|
|
93
120
|
'auth-profiles.md §"Audit-log integrity" §"Verification endpoint"',
|
|
94
|
-
|
|
121
|
+
`claiming ${auditClaimName()} REQUIRES exposing GET ${path.split('?')[0]}`,
|
|
95
122
|
)).not.toBe(404);
|
|
96
123
|
return softSkip('blocked', 'precondition not met — `verify.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
97
124
|
}
|
|
@@ -111,11 +138,10 @@ describe('audit-log-integrity: verify endpoint returns chainValid', () => {
|
|
|
111
138
|
// length > 0)`. The body is now validated against
|
|
112
139
|
// audit-verify-result.schema.json (checkpoints[] + anomalies[] required;
|
|
113
140
|
// each checkpoint needs checkpoint/atSequence/merkleRoot/signature).
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
expect(shapeOk, req('openwop.it.audit-log-integrity.get-v1-audit-verify-on-a-recent-range-reports-chainvalid-true',
|
|
141
|
+
const shape = validateVerifyBody(body);
|
|
142
|
+
expect(shape.ok, req('openwop.it.audit-log-integrity.get-v1-audit-verify-on-a-recent-range-reports-chainvalid-true',
|
|
117
143
|
'audit-verify-result.schema.json',
|
|
118
|
-
`GET
|
|
144
|
+
`GET ${path.split('?')[0]} MUST return the AuditVerifyResult shape: ${shape.errors}`,
|
|
119
145
|
)).toBe(true);
|
|
120
146
|
|
|
121
147
|
expect(body.chainValid, req('openwop.it.audit-log-integrity.get-v1-audit-verify-on-a-recent-range-reports-chainvalid-true',
|
|
@@ -132,3 +158,33 @@ describe('audit-log-integrity: verify endpoint returns chainValid', () => {
|
|
|
132
158
|
}
|
|
133
159
|
});
|
|
134
160
|
});
|
|
161
|
+
|
|
162
|
+
describe('audit-log-integrity: checkpoints at the advertised cadence', () => {
|
|
163
|
+
it('verify lists every checkpoint ascending, no more than checkpointIntervalEntries apart', async () => {
|
|
164
|
+
const integrity = await gate();
|
|
165
|
+
if (integrity === null) return unclaimed();
|
|
166
|
+
const every = integrity.checkpointIntervalEntries;
|
|
167
|
+
if (typeof every !== 'number' || !Number.isInteger(every) || every < 1) {
|
|
168
|
+
// Required at major 2 (the shape leg owns that failure); optional at major 1.
|
|
169
|
+
return softSkip(targetMajor() === 2 ? 'blocked' : 'inapplicable', 'checkpointIntervalEntries is not advertised as a positive integer, so no cadence is claimed');
|
|
170
|
+
}
|
|
171
|
+
const verify = await driver.get(auditVerifyPath(0, 1_000_000));
|
|
172
|
+
if (verify.status !== 200) {
|
|
173
|
+
return softSkip('blocked', `the verify endpoint answered ${verify.status}; the verify leg owns that contract`);
|
|
174
|
+
}
|
|
175
|
+
const seqs = (((verify.json as { checkpoints?: Array<{ atSequence?: unknown }> }).checkpoints) ?? []).map((c) => c.atSequence);
|
|
176
|
+
if (seqs.length === 0) {
|
|
177
|
+
return softSkip('inapplicable', 'the host has minted no checkpoint yet, so there is no cadence to observe');
|
|
178
|
+
}
|
|
179
|
+
let prev = 0;
|
|
180
|
+
for (const at of seqs) {
|
|
181
|
+
expect(typeof at === 'number' && Number.isInteger(at), req(CADENCE, 'security-defaults.md §"Audit-log integrity"', 'every checkpoint MUST carry an integer atSequence')).toBe(true);
|
|
182
|
+
const n = at as number;
|
|
183
|
+
expect(n >= prev, req(CADENCE, 'security-defaults.md §"Audit-log integrity"', `checkpoints MUST be listed ascending by atSequence: ${n} follows ${prev}`)).toBe(true);
|
|
184
|
+
expect(n - prev, req(CADENCE, 'security-defaults.md §"Audit-log integrity"',
|
|
185
|
+
`a checkpoint MUST anchor at most checkpointIntervalEntries (${every}) entries: checkpoint at ${n} follows ${prev === 0 ? 'the start of the log' : `the checkpoint at ${prev}`} — a checkpoint is missing from the verify body, or was never minted`,
|
|
186
|
+
)).toBeLessThanOrEqual(every);
|
|
187
|
+
prev = n;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
});
|