@openwop/openwop-conformance 2.42.9 → 2.43.1
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 +29 -0
- package/README.md +3 -3
- package/coverage.md +1 -1
- package/dist/cli.js +15 -12
- package/dist/lib/certification-bundle-v3.js +19 -0
- package/dist/spec-artifacts.lock.json +2 -2
- package/fixtures/conformance-approval-reject-loopback.json +33 -0
- package/fixtures/conformance-approval-reject-routed.json +32 -0
- package/fixtures/conformance-approval-timeout-approve.json +46 -0
- package/fixtures/conformance-approval-timeout.json +28 -0
- package/fixtures.md +59 -2
- package/package.json +2 -2
- package/requirements.json +535 -38
- package/scenario-majors.json +20 -5
- package/schemas/CORPUS-STAMP.json +55 -54
- package/src/cli.ts +15 -12
- package/src/lib/auditIntegrity.ts +45 -0
- package/src/lib/certification-bundle-v3.ts +25 -0
- package/src/lib/standard-webhooks.ts +53 -0
- package/src/scenarios/agent-org-chart-scoping.test.ts +63 -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-agent-org-chart-served-shape.test.ts +63 -0
- package/src/scenarios/v2-approval-reject-disposition.test.ts +262 -0
- package/src/scenarios/v2-error-registry.test.ts +59 -0
- package/src/scenarios/v2-manifest-ceiling-refused.test.ts +21 -9
- package/src/scenarios/v2-manifest-hatch-carried.test.ts +23 -7
- package/src/scenarios/v2-pack-isolation.test.ts +10 -6
- package/src/scenarios/v2-peer-dependency-declared.test.ts +18 -6
- package/src/scenarios/v2-registry-lifecycle.test.ts +232 -0
- package/src/scenarios/v2-webhook-secret-rotation.test.ts +28 -11
package/scenario-majors.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "GENERATED by conformance/scripts/generate-scenario-majors.mjs (RFC 0168 §D.3). Do not edit; add a file to BOTH_MAJORS in the generator to target both majors.",
|
|
3
3
|
"counts": {
|
|
4
|
-
"files":
|
|
5
|
-
"v1":
|
|
6
|
-
"v2":
|
|
4
|
+
"files": 571,
|
|
5
|
+
"v1": 452,
|
|
6
|
+
"v2": 134
|
|
7
7
|
},
|
|
8
8
|
"majors": {
|
|
9
9
|
"a2a-1-0-agent-card.test.ts": [
|
|
@@ -249,15 +249,21 @@
|
|
|
249
249
|
"artifact-type-store-without-render.test.ts": [
|
|
250
250
|
1
|
|
251
251
|
],
|
|
252
|
+
"audit-anomaly-shape.test.ts": [
|
|
253
|
+
1,
|
|
254
|
+
2
|
|
255
|
+
],
|
|
252
256
|
"audit-checkpoint-signature.test.ts": [
|
|
253
|
-
1
|
|
257
|
+
1,
|
|
258
|
+
2
|
|
254
259
|
],
|
|
255
260
|
"audit-checkpoint-vectors.test.ts": [
|
|
256
261
|
1,
|
|
257
262
|
2
|
|
258
263
|
],
|
|
259
264
|
"audit-log-integrity.test.ts": [
|
|
260
|
-
1
|
|
265
|
+
1,
|
|
266
|
+
2
|
|
261
267
|
],
|
|
262
268
|
"auth-api-key-rotation.test.ts": [
|
|
263
269
|
1
|
|
@@ -1281,6 +1287,12 @@
|
|
|
1281
1287
|
"v2-advertised-path-space-served.test.ts": [
|
|
1282
1288
|
2
|
|
1283
1289
|
],
|
|
1290
|
+
"v2-agent-org-chart-served-shape.test.ts": [
|
|
1291
|
+
2
|
|
1292
|
+
],
|
|
1293
|
+
"v2-approval-reject-disposition.test.ts": [
|
|
1294
|
+
2
|
|
1295
|
+
],
|
|
1284
1296
|
"v2-approver-enforced.test.ts": [
|
|
1285
1297
|
2
|
|
1286
1298
|
],
|
|
@@ -1503,6 +1515,9 @@
|
|
|
1503
1515
|
"v2-refresh-sla.test.ts": [
|
|
1504
1516
|
2
|
|
1505
1517
|
],
|
|
1518
|
+
"v2-registry-lifecycle.test.ts": [
|
|
1519
|
+
2
|
|
1520
|
+
],
|
|
1506
1521
|
"v2-relaxation-recorded.test.ts": [
|
|
1507
1522
|
2
|
|
1508
1523
|
],
|
|
@@ -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.1",
|
|
5
|
+
"corpusTag": "v2.43.1",
|
|
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": "a0a51b581511aaec7f86c67e8157988e580564f569343556dfce3c5329926ce2",
|
|
13
|
+
"api/v2/asyncapi.yaml": "0ad04a46c6985d782a7cc3645ded70fd973fcda86ef244086419274062855c09",
|
|
14
|
+
"api/v2/openapi.yaml": "95bf518a2290e614e2ba750dfe5233f8df242fafdd31dd84beafbdd512c0e810",
|
|
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,11 +167,11 @@
|
|
|
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",
|
|
174
|
-
"schemas/v2/run-event-payloads.schema.json": "
|
|
174
|
+
"schemas/v2/run-event-payloads.schema.json": "f8518f366ad4379356eaf94063817ea0e75975873b883f75e42d98bef6eb4565",
|
|
175
175
|
"schemas/v2/run-event.schema.json": "2f0d7416bd376c56affe97b6b2b49981532e8e9ff45c0a162cf806c78b3e7fb7",
|
|
176
176
|
"schemas/v2/run-list-response.schema.json": "c2dbc34768dcf64528914a213aa3cb55952e8e3e90a64b04a9423b2a19749d38",
|
|
177
177
|
"schemas/v2/run-options.schema.json": "f0e9f494d80086c2ed2cf9fe87df54e5b4e66601699c4a1089f4edbc7ba58867",
|
|
@@ -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": "368d9932efb08f85b4520cac44b3c553876209f55bada93b5fcac93cc890b411",
|
|
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,75 +204,76 @@
|
|
|
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": "0c23e7ab67fa69cda05de86da9dee127e62d82045137fcecc1684d3201feaa97",
|
|
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": "dfdb7a8344316ec6b72e2e249736306cb19155f61180c5f13a44f57d51e803e8",
|
|
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": "
|
|
221
|
-
"spec/v2/core/conformance.md": "
|
|
222
|
-
"spec/v2/core/connection-packs.md": "
|
|
223
|
-
"spec/v2/core/conversation.md": "
|
|
224
|
-
"spec/v2/core/errors.md": "
|
|
225
|
-
"spec/v2/core/events.md": "
|
|
226
|
-
"spec/v2/core/form-content-packs.md": "
|
|
220
|
+
"spec/v2/core/capabilities.md": "ad728e8a761153b55d036ece55043ff62d0982121e2e629e5e7cdf560fca3c03",
|
|
221
|
+
"spec/v2/core/conformance.md": "0cfceb926eed87055550a5c23e640210c795fc8cf9dd1b2544db0e2b3842c564",
|
|
222
|
+
"spec/v2/core/connection-packs.md": "1ee129d792c537bfe82393ccb92555d8e6ddc342b1aa781a13f101b27ec38171",
|
|
223
|
+
"spec/v2/core/conversation.md": "354fa57c73d11062e523a44aa75a6353c5c81aefaf306d30c42416a228c82c86",
|
|
224
|
+
"spec/v2/core/errors.md": "bcb62944d8a482b1b6d5ac08a692ad7e4aaadf6165746c7ba7fa39157be2a300",
|
|
225
|
+
"spec/v2/core/events.md": "604abb6f9e2259a04103e1cb76d728712c3701c0d84dd915d4dc2949c912ee5e",
|
|
226
|
+
"spec/v2/core/form-content-packs.md": "02931227899676fd4581a27017be2a6c4953904717be9b81c29d12f87ab3917d",
|
|
227
227
|
"spec/v2/core/headers.md": "5cb1624fa63acc81992b6a6d842ec392cf7271aaf2f65d1f83c8c4fa286e6730",
|
|
228
228
|
"spec/v2/core/host-services.md": "15923cceb95cba148a32e0944f6a2d1d25e9cc36b0c169b67ee42d483bcac800",
|
|
229
229
|
"spec/v2/core/i18n.md": "846c6d2878fc8454c2ae0a1576f6b6e6c8958d47187a6c96cc5826ca3c3e2e91",
|
|
230
|
-
"spec/v2/core/idempotency.md": "
|
|
231
|
-
"spec/v2/core/identity.md": "
|
|
232
|
-
"spec/v2/core/interop.md": "
|
|
233
|
-
"spec/v2/core/interrupt.md": "
|
|
234
|
-
"spec/v2/core/node-pack-runtimes.md": "
|
|
235
|
-
"spec/v2/core/oauth.md": "
|
|
236
|
-
"spec/v2/core/overview.md": "
|
|
237
|
-
"spec/v2/core/packs.md": "
|
|
238
|
-
"spec/v2/core/persistence.md": "
|
|
239
|
-
"spec/v2/core/portability.md": "
|
|
240
|
-
"spec/v2/core/replay.md": "
|
|
241
|
-
"spec/v2/core/runs.md": "
|
|
242
|
-
"spec/v2/core/security-defaults.md": "
|
|
243
|
-
"spec/v2/core/tool-catalog.md": "
|
|
244
|
-
"spec/v2/core/versioning.md": "
|
|
245
|
-
"spec/v2/core/webhooks.md": "
|
|
246
|
-
"spec/v2/core/workflow-chain-packs.md": "
|
|
247
|
-
"spec/v2/corrections.json": "
|
|
230
|
+
"spec/v2/core/idempotency.md": "2084d756789c1faf5121593fb39e67ca36a4c227178e175004742e403178ea49",
|
|
231
|
+
"spec/v2/core/identity.md": "2dceb1c2697015e4a4fa8a9b28f4fbd25fa44a76ca5a919c752e2a8163c819db",
|
|
232
|
+
"spec/v2/core/interop.md": "e72e25dff864c83b2980d7366b34054cffa072af0225821199f260f6228d260e",
|
|
233
|
+
"spec/v2/core/interrupt.md": "b4376fbbaaa0c7b0ca5df04d40049b487753f6736f8e54ed7d75b7f8563b2f72",
|
|
234
|
+
"spec/v2/core/node-pack-runtimes.md": "e1088cb69b8c3c67256c494063328916c2980ab1aa97a7063de25bab82fdb1e5",
|
|
235
|
+
"spec/v2/core/oauth.md": "c96483a44b62978c596945fbb7d7c863cf6056cd980fdb4cf4e97555e3e10b72",
|
|
236
|
+
"spec/v2/core/overview.md": "4087f171b00a3907551f508fec32fb9b0b3ccec8c0ac1b0c7c6d32cd2c0e3ff8",
|
|
237
|
+
"spec/v2/core/packs.md": "b472a583211d466d6ab8e00c414c225b246e678043fe6283a710c899fadde913",
|
|
238
|
+
"spec/v2/core/persistence.md": "711874f74cb08b03cd118d683056db10a063517762fcea73ec70feb5d983e6a4",
|
|
239
|
+
"spec/v2/core/portability.md": "87c4327f674f714fda6217fd79b99d2ba219ad47dc02f9751bb56c200fb2c17a",
|
|
240
|
+
"spec/v2/core/replay.md": "22cc8dc94cd145201f1269028fb0cb2abae2d988ee748205582edd59afd1da4d",
|
|
241
|
+
"spec/v2/core/runs.md": "157600d7dd2554856bba29e231e46489b5097cc0c0a0e74073549d410897a78b",
|
|
242
|
+
"spec/v2/core/security-defaults.md": "153884da7a2d54c30c81b5e649cfdef31e151b3a2a19dee1bc5a16c35d60a3aa",
|
|
243
|
+
"spec/v2/core/tool-catalog.md": "d16a9004418719c48301804aefa4d7ab6f41112a4ed83727f552aa1b92b418b0",
|
|
244
|
+
"spec/v2/core/versioning.md": "e3b3ab6d76e51035039a32173e9b412628a52c8fe7b16687ff58a2d0d983faa0",
|
|
245
|
+
"spec/v2/core/webhooks.md": "6fc2fb7d9a993a682765ebc68e5c5bb215046ee599234ca7a660465279444eff",
|
|
246
|
+
"spec/v2/core/workflow-chain-packs.md": "fdce7a8477e269950a2bac05ee6eddbd66eb0b670b464d00054f7196ec05d29c",
|
|
247
|
+
"spec/v2/corrections.json": "59f647f4d22477f6c2b06dcf913b682dde5d51dd5e607a5d0cf648ab60864d6e",
|
|
248
248
|
"spec/v2/corrections.schema.json": "46bbffb954d13bd9995aebc912ceeb2544f7064331abf25fd6ab85c81a5cec94",
|
|
249
|
-
"spec/v2/declaration.json": "
|
|
249
|
+
"spec/v2/declaration.json": "7765c86f7a3fbdd5a80c6778fb22335f099dcc4caddf9747fb144c1f318a17f1",
|
|
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": "
|
|
257
|
+
"spec/v2/ext/brand/README.md": "4f9db1715219070fc57e06cc9b066f13aa130dd50f4869722d908e6e15720787",
|
|
258
258
|
"spec/v2/ext/canvas/README.md": "0e91b68566d2be90319feedfaebe2cd2db0bd8f72fb59c958fa723c05e38023f",
|
|
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": "
|
|
259
|
+
"spec/v2/ext/chat/README.md": "f59b6706162abbfff96f8bccd4c0e1aeed721047cf62e9af58c875727a38d716",
|
|
260
|
+
"spec/v2/ext/coordination/README.md": "c7fe0884f52405fdd02c5f9ec309ac6b585685ee06ec5f338fff7acf03c07148",
|
|
261
|
+
"spec/v2/ext/dataIntegration/README.md": "286dec8aa29e4d3225cbd8b3a361f5a438c3288e759966b9415ecbb99c576e70",
|
|
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": "
|
|
264
|
+
"spec/v2/ext/kanban/README.md": "bfc386dc0f874a4afa1e3ec6a2f99084b547dcce9bd363da3b8ebd8786b9c184",
|
|
265
|
+
"spec/v2/ext/knowledge/README.md": "cbe1f945a757791dedac0fd542a68c86c5b457778b831d58d9ae44a2baa270f2",
|
|
266
266
|
"spec/v2/ext/launchStudio/README.md": "c732bb855252c31ee1e971cbc2936f2ca29b234c111d548c648095634507babb",
|
|
267
|
-
"spec/v2/ext/messaging/README.md": "
|
|
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": "b675e6f6646128d781b661f754c2393eed86e5935d16c3a22df582a5ce025b9b",
|
|
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": "e52aaf9f10896088b2fcca2586ab05a477043275e7a3a48b769ffb4865e37c4a",
|
|
301
302
|
"spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
|
|
302
|
-
"spec/v2/surface-baseline.json": "
|
|
303
|
+
"spec/v2/surface-baseline.json": "d5b4e6c55a1d4bbe54241f9d0d020e7f9857f29912bb9f2ef910087b276be7e6"
|
|
303
304
|
},
|
|
304
|
-
"corpusCommit": "
|
|
305
|
+
"corpusCommit": "a99d0e2a2fd3000cff23cade553a6a45ad8f17a9"
|
|
305
306
|
}
|
package/src/cli.ts
CHANGED
|
@@ -46,7 +46,7 @@ import { readLedgerFile } from './lib/requirement-ledger.js';
|
|
|
46
46
|
import { deriveRung, emittedByNewerSuite, type RowEvidence } from './lib/durability-evidence.js';
|
|
47
47
|
import { deriveRequirementDispositions } from './lib/scenario-disposition.js';
|
|
48
48
|
import { scrubEvidence, evidenceSecretsFromEnv, verifyBundleV2 } from './lib/certification-bundle-verify.js';
|
|
49
|
-
import { publicKeyFromPrivate,
|
|
49
|
+
import { publicKeyFromPrivate, sealBundleV3, verifierSign, verifyBundleV3, type BundleV3, type BundleV3Requirement } from './lib/certification-bundle-v3.js';
|
|
50
50
|
import { canonicalJSON, parseIJson } from './lib/jcs.js';
|
|
51
51
|
import {
|
|
52
52
|
deriveProfiles,
|
|
@@ -718,7 +718,7 @@ async function runCertify(args: ParsedArgs, baseUrl: string, apiKey: string): Pr
|
|
|
718
718
|
if (rows3.some((r) => r.id.startsWith('openwop.requirement.0158.') && r.result === 'executed-pass' && r.id !== 'openwop.requirement.0158.poison-exhaustion')) {
|
|
719
719
|
process.stderr.write(`openwop-conformance --certify: RFC 0158 rung — ${rung3.rung ?? 'NONE'} (${rung3.why})\n`);
|
|
720
720
|
}
|
|
721
|
-
const
|
|
721
|
+
const draft: Omit<BundleV3, 'signature' | 'witnessSha256'> = {
|
|
722
722
|
bundleVersion: '3',
|
|
723
723
|
generatedAt: new Date().toISOString(),
|
|
724
724
|
suite: { name: '@openwop/openwop-conformance', version, targetMajor: target.major, specArtifactsVersion: lock?.version ?? 'repo-layout', ...(lock ? { stampSha256: lock.stampSha256 } : {}) },
|
|
@@ -730,7 +730,6 @@ async function runCertify(args: ParsedArgs, baseUrl: string, apiKey: string): Pr
|
|
|
730
730
|
discovery: { url: discoveryUrl, sha256, protocolVersions, preferredVersion, document },
|
|
731
731
|
claimedProfiles: claimed3,
|
|
732
732
|
results: { totals: totals3, requirements: rows3 },
|
|
733
|
-
witnessSha256: witnessDigest(rows3, relaxations, args.colocatedCompanion ? 'colocated-companion' : undefined),
|
|
734
733
|
assertionCount: rows3.reduce((n, r) => n + (r.assertions ?? 0), 0),
|
|
735
734
|
...(nonPass.length ? { detail: { nonPass: nonPass.map((r) => ({ id: r.id, result: r.result, reason: r.detail ?? '' })) } } : {}),
|
|
736
735
|
// RFC 0158 §D: claimed ONLY when these rows support it. The verifier
|
|
@@ -738,20 +737,24 @@ async function runCertify(args: ParsedArgs, baseUrl: string, apiKey: string): Pr
|
|
|
738
737
|
// claimed more would be writing a bundle its own `--verify` rejects.
|
|
739
738
|
...(rung3.rung === null ? {} : { durability: { rung: rung3.rung } }),
|
|
740
739
|
};
|
|
741
|
-
|
|
742
|
-
|
|
740
|
+
// The published keyId is never a secret, whichever variable carried it
|
|
741
|
+
// (evidenceSecretsFromEnv: the `_ID` exclusion is the rule, `except` is
|
|
742
|
+
// the belt for a host that names its env var differently). Scrubbed BEFORE
|
|
743
|
+
// the digest and the signature (sealBundleV3), so the signed rows are the
|
|
744
|
+
// written rows.
|
|
745
|
+
const secrets3 = evidenceSecretsFromEnv(process.env, [apiKey, signingKeyPem], [keyId]);
|
|
746
|
+
const sealed = sealBundleV3(draft, secrets3, signingKeyPem, keyId);
|
|
747
|
+
const { unsigned } = sealed;
|
|
748
|
+
const v3Out: BundleV3 = sealed.bundle;
|
|
749
|
+
if (sealed.redactedAt.length > 0) {
|
|
750
|
+
process.stderr.write(`openwop-conformance --certify: REDACTED ${sealed.redactedAt.length} evidence field(s) that carried a configured secret or the conformance canary: ${sealed.redactedAt.slice(0, 8).join(', ')}${sealed.redactedAt.length > 8 ? ', …' : ''}\n`);
|
|
751
|
+
}
|
|
743
752
|
if (args.evidenceTier === 'independent') {
|
|
744
753
|
const vk = args.verifierKeyPath ? readFileSync(args.verifierKeyPath, 'utf8') : process.env['OPENWOP_BUNDLE_VERIFIER_KEY'];
|
|
745
754
|
const vkId = args.verifierKeyId ?? process.env['OPENWOP_BUNDLE_VERIFIER_KEY_ID'];
|
|
746
755
|
if (!vk || !vkId) { process.stderr.write('openwop-conformance --certify: --evidence-tier independent needs --verifier-key and --verifier-key-id (RFC 0168 §E.2)\n'); process.exit(2); }
|
|
747
|
-
|
|
756
|
+
v3Out.verifierSignature = verifierSign(unsigned, vk, vkId);
|
|
748
757
|
}
|
|
749
|
-
// The published keyId is never a secret, whichever variable carried it
|
|
750
|
-
// (evidenceSecretsFromEnv: the `_ID` exclusion is the rule, `except` is
|
|
751
|
-
// the belt for a host that names its env var differently).
|
|
752
|
-
const secrets3 = evidenceSecretsFromEnv(process.env, [apiKey, signingKeyPem], [keyId]);
|
|
753
|
-
const scrubbed3 = scrubEvidence(v3, secrets3);
|
|
754
|
-
const v3Out = scrubbed3.value as BundleV3;
|
|
755
758
|
const audit3 = verifyBundleV3(v3Out, { hostPublicKeyPem: publicKeyFromPrivate(signingKeyPem) });
|
|
756
759
|
// `relaxed-profile-certified` is no longer excused: the emitter now computes
|
|
757
760
|
// the relaxation into `certified`, so if the verifier still finds that
|
|
@@ -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
|
+
}
|
|
@@ -22,6 +22,7 @@ import { profileDerivable, type DiscoveryPayload } from './profiles.js';
|
|
|
22
22
|
import { checkRungClaim, type DurabilityRung, type RowEvidence } from './durability-evidence.js';
|
|
23
23
|
import { profilesDeniedByObservedRelaxation, profilesRelaxedBy, v2RegistryAvailable } from './v2-profiles.js';
|
|
24
24
|
import { canonicalJSON, codeUnitCompare, JcsRefusal } from './jcs.js';
|
|
25
|
+
import { scrubEvidence } from './certification-bundle-verify.js';
|
|
25
26
|
|
|
26
27
|
export type BundleV3Result = 'executed-pass' | 'executed-fail' | 'skipped' | 'inapplicable' | 'blocked';
|
|
27
28
|
|
|
@@ -145,6 +146,30 @@ export function verifierSign(bundle: Pick<BundleV3, 'witnessSha256' | 'host' | '
|
|
|
145
146
|
return { alg: 'ed25519', keyId, sig: toBase64url(edSign(null, attestationPayload(bundle), key)) };
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Scrub, THEN digest, THEN sign (RFC 0148 §C + RFC 0168 §E.2).
|
|
151
|
+
*
|
|
152
|
+
* Until 2.43.1 the emitter computed `witnessSha256` over the raw rows, signed,
|
|
153
|
+
* and only then ran `scrubEvidence` over the finished bundle. A secret found in
|
|
154
|
+
* a row's `detail` or `evidence` was redacted AFTER the digest was taken, so
|
|
155
|
+
* the written rows no longer hashed to `witnessSha256` and the emitter's own
|
|
156
|
+
* self-verification refused the bundle (`[witness-digest]`, exit 2, no bundle)
|
|
157
|
+
* — openwop-app at major 1 on suite 2.43.0, 2026-09-28. The redaction was
|
|
158
|
+
* right and the check was right; the order was wrong. Everything the digest and
|
|
159
|
+
* the attestation cover is now scrubbed before either is computed, so the
|
|
160
|
+
* signed bytes are the shipped bytes.
|
|
161
|
+
*/
|
|
162
|
+
export function sealBundleV3(
|
|
163
|
+
draft: Omit<BundleV3, 'signature' | 'witnessSha256'>,
|
|
164
|
+
secrets: readonly string[],
|
|
165
|
+
privateKeyPem: string,
|
|
166
|
+
keyId: string,
|
|
167
|
+
): { unsigned: Omit<BundleV3, 'signature'>; bundle: BundleV3; redactedAt: readonly string[] } {
|
|
168
|
+
const { value: scrubbed, redactedAt } = scrubEvidence(draft, secrets);
|
|
169
|
+
const unsigned: Omit<BundleV3, 'signature'> = { ...scrubbed, witnessSha256: witnessDigest(scrubbed.results.requirements, scrubbed.host.relaxations, scrubbed.host.deployment) };
|
|
170
|
+
return { unsigned, bundle: { ...unsigned, signature: signBundleV3(unsigned, privateKeyPem, keyId) }, redactedAt };
|
|
171
|
+
}
|
|
172
|
+
|
|
148
173
|
export function publicKeyFromPrivate(privateKeyPem: string): string {
|
|
149
174
|
return createPublicKey(createPrivateKey(privateKeyPem)).export({ type: 'spki', format: 'pem' }) as string;
|
|
150
175
|
}
|
|
@@ -20,6 +20,7 @@ import { readErrorCode } from './error-envelope.js';
|
|
|
20
20
|
import type { SoftSkipKind } from './soft-skip.js';
|
|
21
21
|
import { projectBoundId } from './bound-id.js';
|
|
22
22
|
import { hitHeader, type ModalHit } from './webhook-receiver.js';
|
|
23
|
+
import { SEAMS_PREFIX } from './seams.js';
|
|
23
24
|
|
|
24
25
|
export const STANDARD_WEBHOOKS_ALG = 'standard-webhooks-1';
|
|
25
26
|
export const SW_FIXTURE = 'conformance-noop';
|
|
@@ -118,3 +119,55 @@ export function deliveriesFor(hits: readonly ModalHit[], webhookId: string, runI
|
|
|
118
119
|
export function standardWebhooksHeaderNames(hit: ModalHit): string[] {
|
|
119
120
|
return Object.keys(hit.headers).filter((k) => k.toLowerCase().startsWith('webhook-'));
|
|
120
121
|
}
|
|
122
|
+
|
|
123
|
+
export const ROTATION_OVERLAP_SEAM = `${SEAMS_PREFIX}/sample/webhooks/rotation-overlap`;
|
|
124
|
+
|
|
125
|
+
export type ShortenedOverlap =
|
|
126
|
+
| { readonly ok: true; readonly expiresAt: number }
|
|
127
|
+
| { readonly ok: false; readonly unserved: true; readonly reason: string }
|
|
128
|
+
| { readonly ok: false; readonly unserved: false; readonly reason: string };
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Shorten the rotation overlap in progress on ONE subscription
|
|
132
|
+
* (host-sample-test-seams.md §29, `shortenRotationOverlap`), so the RFC 0201
|
|
133
|
+
* §E.20 post-overlap leg can be observed on a host whose advertised
|
|
134
|
+
* `overlapSeconds` outlasts the wait cap. The seam only moves the stored expiry;
|
|
135
|
+
* which secret signs after it is the production signer's decision, and that is
|
|
136
|
+
* what the caller asserts on. UNSERVED (404/405/501) is reported apart from a
|
|
137
|
+
* served seam that breaks its contract: the seam is OPTIONAL and newer than the
|
|
138
|
+
* leg, so its absence falls back to the pre-seam partial witness.
|
|
139
|
+
*
|
|
140
|
+
* The contract checked here: `200 { webhookId, previousSecretExpiresAt }` with
|
|
141
|
+
* the expiry `overlapSeconds` after the request (±1 s), and a foreign-tenant id
|
|
142
|
+
* refused `403 id_tenant_mismatch` before the lookup, as `rotateWebhookSecret`
|
|
143
|
+
* refuses it. The real call goes first so that a 404 can only mean "unserved".
|
|
144
|
+
*/
|
|
145
|
+
export async function shortenRotationOverlap(webhookId: string, overlapSeconds: number, foreignWebhookId: string): Promise<ShortenedOverlap> {
|
|
146
|
+
const before = Date.now();
|
|
147
|
+
let res: OpenWOPResponse;
|
|
148
|
+
try { res = await driver.post(ROTATION_OVERLAP_SEAM, { webhookId, overlapSeconds }); } catch {
|
|
149
|
+
return { ok: false, unserved: false, reason: `${ROTATION_OVERLAP_SEAM} unreachable (fetch failed)` };
|
|
150
|
+
}
|
|
151
|
+
const after = Date.now();
|
|
152
|
+
if (res.status === 404 || res.status === 405 || res.status === 501) {
|
|
153
|
+
return { ok: false, unserved: true, reason: `${ROTATION_OVERLAP_SEAM} answered ${res.status} — the host does not serve the optional shortenRotationOverlap seam (host-sample-test-seams.md §29)` };
|
|
154
|
+
}
|
|
155
|
+
const body = (res.json ?? {}) as { webhookId?: unknown; previousSecretExpiresAt?: unknown };
|
|
156
|
+
const expiresAt = typeof body.previousSecretExpiresAt === 'string' ? Date.parse(body.previousSecretExpiresAt) : Number.NaN;
|
|
157
|
+
if (res.status !== 200 || body.webhookId !== webhookId || !Number.isFinite(expiresAt)) {
|
|
158
|
+
return { ok: false, unserved: false, reason: `${ROTATION_OVERLAP_SEAM} answered ${res.status} ${readErrorCode(res.json) ?? ''} without 200 { webhookId, previousSecretExpiresAt } for ${webhookId} — the seam contract (api/seams-v2.yaml shortenRotationOverlap) is not honoured`.replace(/ +/g, ' ') };
|
|
159
|
+
}
|
|
160
|
+
const lo = before + overlapSeconds * 1000 - 1000;
|
|
161
|
+
const hi = after + overlapSeconds * 1000 + 1000;
|
|
162
|
+
if (expiresAt < lo || expiresAt > hi) {
|
|
163
|
+
return { ok: false, unserved: false, reason: `${ROTATION_OVERLAP_SEAM} reported previousSecretExpiresAt ${body.previousSecretExpiresAt as string}, not ${overlapSeconds}s after the request (window ${new Date(lo).toISOString()}..${new Date(hi).toISOString()}) — the overlap it reports is not the one it was asked to set` };
|
|
164
|
+
}
|
|
165
|
+
let cross: OpenWOPResponse;
|
|
166
|
+
try { cross = await driver.post(ROTATION_OVERLAP_SEAM, { webhookId: foreignWebhookId, overlapSeconds }); } catch {
|
|
167
|
+
return { ok: false, unserved: false, reason: `${ROTATION_OVERLAP_SEAM} unreachable on the foreign-tenant probe (fetch failed)` };
|
|
168
|
+
}
|
|
169
|
+
if (cross.status !== 403 || readErrorCode(cross.json) !== 'id_tenant_mismatch') {
|
|
170
|
+
return { ok: false, unserved: false, reason: `${ROTATION_OVERLAP_SEAM} answered ${cross.status} ${readErrorCode(cross.json) ?? ''} for a foreign-tenant webhookId — the seam MUST be tenant-checked before the lookup (403 id_tenant_mismatch), exactly as rotateWebhookSecret`.replace(/ +/g, ' ') };
|
|
171
|
+
}
|
|
172
|
+
return { ok: true, expiresAt };
|
|
173
|
+
}
|
|
@@ -29,10 +29,31 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import { describe, it, expect } from 'vitest';
|
|
32
|
+
import { readFileSync } from 'node:fs';
|
|
33
|
+
import { join } from 'node:path';
|
|
34
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
35
|
+
import addFormats from 'ajv-formats';
|
|
32
36
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
33
37
|
import { readOrgChartCap, getOrgChartResponse, getDepartmentView, type OrgChart } from '../lib/agentOrgChart.js';
|
|
38
|
+
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
34
39
|
import { req } from '../lib/requirement-ids.js';
|
|
35
40
|
|
|
41
|
+
function loadSchema(name: string): Record<string, unknown> {
|
|
42
|
+
return JSON.parse(readFileSync(join(SCHEMAS_DIR, name), 'utf8')) as Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A validator for `schemas/<name>.schema.json` (this file targets major 1;
|
|
46
|
+
* the major-2 twin is `v2-agent-org-chart-served-shape.test.ts`). The
|
|
47
|
+
* responsibility view `$ref`s the chart's `$defs`, so the chart schema is
|
|
48
|
+
* registered first. */
|
|
49
|
+
function servedShapeValidator(name: 'agent-org-chart' | 'org-chart-responsibility-view'): (d: unknown) => { ok: boolean; errors: string } {
|
|
50
|
+
const ajv = new Ajv2020({ strict: false, allErrors: true });
|
|
51
|
+
addFormats(ajv);
|
|
52
|
+
if (name !== 'agent-org-chart') ajv.addSchema(loadSchema('agent-org-chart.schema.json'));
|
|
53
|
+
const validate = ajv.compile(loadSchema(`${name}.schema.json`));
|
|
54
|
+
return (d: unknown) => ({ ok: validate(d) as boolean, errors: ajv.errorsText(validate.errors, { separator: '; ' }) });
|
|
55
|
+
}
|
|
56
|
+
|
|
36
57
|
const ROSTER_ID_RE = /^host:[a-z0-9][a-z0-9._-]*$/;
|
|
37
58
|
|
|
38
59
|
describe('agent-org-chart-scoping (RFC 0087 §A/§C/§D)', () => {
|
|
@@ -159,3 +180,45 @@ describe('agent-org-chart-scoping (RFC 0087 §A/§C/§D)', () => {
|
|
|
159
180
|
}
|
|
160
181
|
});
|
|
161
182
|
});
|
|
183
|
+
|
|
184
|
+
// Org-chart served-shape leg (2026-09-28). Leg 1 above walks the chart's tree
|
|
185
|
+
// and member references but never validated the body against the schema, so a
|
|
186
|
+
// host serving its stored record (`{ tenantId, departments, members, updatedAt }`
|
|
187
|
+
// — `owner` missing, two extra keys on an `additionalProperties:false` object)
|
|
188
|
+
// passed at both majors; openwop-app did exactly that (fixed in openwop-app
|
|
189
|
+
// #4189). This leg validates the SERVED bodies of the normative read pair
|
|
190
|
+
// against `agent-org-chart.schema.json` / `org-chart-responsibility-view.schema.json`;
|
|
191
|
+
// `v2-agent-org-chart-served-shape.test.ts` is the same leg at major 2.
|
|
192
|
+
describe('agent-org-chart-scoping: served shape (RFC 0087 §A/§D)', () => {
|
|
193
|
+
it('serves the org-chart read pair in the exact schema shape', async () => {
|
|
194
|
+
const cap = await readOrgChartCap();
|
|
195
|
+
if (!behaviorGate('openwop-org-chart-scoping', cap?.supported === true)) return;
|
|
196
|
+
|
|
197
|
+
const chartRes = await getOrgChartResponse();
|
|
198
|
+
expect(
|
|
199
|
+
chartRes.status,
|
|
200
|
+
req('openwop.it.agent-org-chart-scoping.serves-the-org-chart-read-pair-in-the-exact-schema-shape', 'agent-org-chart.md §A / §E', 'a host advertising agents.orgChart.supported MUST serve GET /v1/agents/org-chart with 200'),
|
|
201
|
+
).toBe(200);
|
|
202
|
+
const chartCheck = servedShapeValidator('agent-org-chart')(chartRes.chart);
|
|
203
|
+
expect(
|
|
204
|
+
chartCheck.ok ? 'valid' : chartCheck.errors,
|
|
205
|
+
req('openwop.it.agent-org-chart-scoping.serves-the-org-chart-read-pair-in-the-exact-schema-shape', 'agent-org-chart.schema.json (RFC 0087 §A)', 'the served GET /v1/agents/org-chart body MUST validate against agent-org-chart.schema.json (owner required; no extra keys)'),
|
|
206
|
+
).toBe('valid');
|
|
207
|
+
|
|
208
|
+
const probeDeptId = chartRes.chart?.departments?.[0]?.departmentId;
|
|
209
|
+
// The view leg needs a department to read; an empty chart is still
|
|
210
|
+
// validated above (an empty chart is a conforming chart).
|
|
211
|
+
if (cap?.responsibilityView === true && typeof probeDeptId === 'string') {
|
|
212
|
+
const { status, view } = await getDepartmentView(probeDeptId);
|
|
213
|
+
expect(
|
|
214
|
+
status,
|
|
215
|
+
req('openwop.it.agent-org-chart-scoping.serves-the-org-chart-read-pair-in-the-exact-schema-shape', 'agent-org-chart.md §D / §E', "a host advertising agents.orgChart.responsibilityView MUST serve GET /v1/agents/org-chart/{departmentId} with 200 for a department in the caller's chart"),
|
|
216
|
+
).toBe(200);
|
|
217
|
+
const viewCheck = servedShapeValidator('org-chart-responsibility-view')(view);
|
|
218
|
+
expect(
|
|
219
|
+
viewCheck.ok ? 'valid' : viewCheck.errors,
|
|
220
|
+
req('openwop.it.agent-org-chart-scoping.serves-the-org-chart-read-pair-in-the-exact-schema-shape', 'org-chart-responsibility-view.schema.json (RFC 0087 §D)', 'the served GET /v1/agents/org-chart/{departmentId} body MUST validate against org-chart-responsibility-view.schema.json'),
|
|
221
|
+
).toBe('valid');
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
});
|