@openwop/openwop-conformance 2.43.0 → 2.43.2

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 (32) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +3 -3
  3. package/dist/cli.js +15 -12
  4. package/dist/lib/certification-bundle-v3.js +19 -0
  5. package/dist/lib/scenario-disposition.js +30 -0
  6. package/dist/spec-artifacts.lock.json +2 -2
  7. package/fixtures/conformance-approval-reject-loopback.json +33 -0
  8. package/fixtures/conformance-approval-reject-routed.json +32 -0
  9. package/fixtures/conformance-approval-timeout-approve.json +46 -0
  10. package/fixtures/conformance-approval-timeout.json +28 -0
  11. package/fixtures/conformance-replay-ordinal-loop.json +31 -0
  12. package/fixtures.md +69 -1
  13. package/package.json +2 -2
  14. package/requirements.json +243 -30
  15. package/scenario-majors.json +11 -2
  16. package/schemas/CORPUS-STAMP.json +47 -47
  17. package/src/cli.ts +15 -12
  18. package/src/lib/certification-bundle-v3.ts +25 -0
  19. package/src/lib/scenario-disposition.ts +40 -0
  20. package/src/lib/standard-webhooks.ts +53 -0
  21. package/src/scenarios/agent-org-chart-scoping.test.ts +63 -0
  22. package/src/scenarios/v2-agent-org-chart-served-shape.test.ts +63 -0
  23. package/src/scenarios/v2-approval-reject-disposition.test.ts +106 -6
  24. package/src/scenarios/v2-error-registry.test.ts +59 -0
  25. package/src/scenarios/v2-manifest-ceiling-refused.test.ts +21 -9
  26. package/src/scenarios/v2-manifest-hatch-carried.test.ts +23 -7
  27. package/src/scenarios/v2-pack-isolation.test.ts +10 -6
  28. package/src/scenarios/v2-peer-dependency-declared.test.ts +18 -6
  29. package/src/scenarios/v2-replay-suppression-ordinal.test.ts +108 -0
  30. package/src/scenarios/v2-run-fork-ancestry.test.ts +83 -0
  31. package/src/scenarios/v2-webhook-secret-rotation.test.ts +28 -11
  32. package/src/setup.ts +25 -2
@@ -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.43.0",
5
- "corpusTag": "v2.43.0",
4
+ "version": "2.43.2",
5
+ "corpusTag": "v2.43.2",
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": "c6d9d70e74a4c883a016a69da438fb6c87f56822334a1d45bd682c6d68bdac32",
13
- "api/v2/asyncapi.yaml": "0b72bbd3f0f25172e3080a027a801d68d4a4130188addf2879fe92759d352de6",
14
- "api/v2/openapi.yaml": "fdbc6bcf064de81e2c8bf3d7a896ccbc39d8b12f71f9b8c4727f825f1ae31af0",
12
+ "api/seams-v2.yaml": "987123ecfbfbc906c5655538b9380021ac0bcbfd93a467a67818e855d624ed0e",
13
+ "api/v2/asyncapi.yaml": "76474704b7a8e829588abec30d31f09e11557ee5daacad301c3be7e77affe7ce",
14
+ "api/v2/openapi.yaml": "f4af9e7fddad438d3a70753af29e5738ea48d15d1abd214a3fbeec97260cea9d",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
16
  "schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
17
17
  "schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
@@ -171,7 +171,7 @@
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": "b7b497c0a0fa41f279750138227d3378fc83ca894fb33e4b0a4fe7c685b84bc4",
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": "29c9cfa6978342f0175e250607e6eddb5e4058ea0c4adacc1965e1f3bb4dc9ee",
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,49 +204,49 @@
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": "4c43eec4ac5a43b026949f9d9c65d6b92f7b79706a185c4a147e232b1be0c945",
207
+ "spec/v1/core-standard-manifest.json": "0be2c8da837ddb170866303ab4640c8e8a8b2554d8c2bb9ac3d1bd9780bede5a",
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": "2d4e4772cd647b471f989bdb6416ca902b11e10d17dc7f84825bb9f2eef1314c",
213
+ "spec/v1/gaps.json": "7eefa3aa02e978a41e1e5948818961718d23cb0cbe29a4a491524360feb9fd6f",
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": "3d1a1975cb688244ca18cd194f56f3a995cc81a9155277dbaeeaa51eabb3a0c7",
221
- "spec/v2/core/conformance.md": "e7bd7d54062f28801071165a1b92edbeee3774bb3710f31eed75817139b66d95",
222
- "spec/v2/core/connection-packs.md": "3168d2c8f1a59417a7256a678f4203570be434f64dead6b11fdec86b352686df",
223
- "spec/v2/core/conversation.md": "6db43d8160bc45d3944aa1151816a6f0fd1669d59d7f822267a2d6cc9db22990",
224
- "spec/v2/core/errors.md": "7931c9411e50b5ebfbc20d15aaf0e7b1a5b3e0ddc558b931e432da49ce09152c",
225
- "spec/v2/core/events.md": "234338deb97be648731e8ab2126a90b7b8ad0a7070d98ffc85ee4689ff059791",
226
- "spec/v2/core/form-content-packs.md": "41d40afe0a3760bcd792a4c406e16f28b0ab0aa7b0925323110d3fa2730d5400",
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": "21830b7d23dd03e9e8ba4b0de253dd001fc960f166310d6b6b60b1c33cf60b30",
231
- "spec/v2/core/identity.md": "acb7f739729c69a1f40dcaa5b1d873ba9f32993c17bfd297f5fa56351cf47c69",
232
- "spec/v2/core/interop.md": "3cfa2af70ae6ba1f4b4268447d46d1d2f5fb65f7917f15bad159d9779d25b1a6",
233
- "spec/v2/core/interrupt.md": "5e6fe9767228638095d5a2210307e1febfcefda808f4a400f2c7c528b929c445",
234
- "spec/v2/core/node-pack-runtimes.md": "d52966319c53aaa03575025915b73b470a42832c039c41d73aff6931758630cb",
235
- "spec/v2/core/oauth.md": "57f167d899993b6959c243e4102819f4014b365299b9b2b253decd2781e6b8b7",
236
- "spec/v2/core/overview.md": "3dbc8c0cf7474b31816b1a12412c1edf5597066c38edcdbca4dd2e465880f65b",
237
- "spec/v2/core/packs.md": "e46bf8cd34ac9566fe1e5601f67e7a17fce9639b8676e0f5278a10c6ca70ab29",
238
- "spec/v2/core/persistence.md": "bba672ed3bf6d83af6e4e65c0f4f480fc21fe12207811e321306af2108d9010d",
239
- "spec/v2/core/portability.md": "a35fbe66b8b5ccf63cf0216157d4b61d4c47f6dc8e757c830569ddf244e21e8a",
240
- "spec/v2/core/replay.md": "a5be4e05242f25f8b5a8a4fb31bd359d5abb4bf34fa8e536403a8ee5779ab876",
241
- "spec/v2/core/runs.md": "72a94c27d884036d84290e9b108702e98b0c6b2fb598213bbda44fba9c9f860d",
242
- "spec/v2/core/security-defaults.md": "e77cf97290ffb0c201706ea10cc0883d717d034353ae0632067838ef227d3749",
243
- "spec/v2/core/tool-catalog.md": "6b54b8ce221107de19c718f374a720123c6a5f55db4054e6625f73d06e0a442f",
244
- "spec/v2/core/versioning.md": "2bfe16a72a909c46b4585fab39dc9e94c97506ed95df6b1b527f97b4cd3684ef",
245
- "spec/v2/core/webhooks.md": "78fe97ab5bdee20218183efe32abf5ba1996705d757cb37c8d6a4d808c44199f",
246
- "spec/v2/core/workflow-chain-packs.md": "35a957f1dd22016febd76bf2c4dfb79cda58b95d8efd8893b5e5babda9f79cf3",
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": "2c8a4026930ec40ba47d7415dd02b6eda997a03d7a084e5084fec6f1261aba1c",
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": "4d379494057746c6883f6047972e93e621863b998ed291b9a691d3aa57c1ff43",
241
+ "spec/v2/core/runs.md": "55e21292ae4b41d0d104199a6c2634ecb3f787c4a671f9ebc37084be38ceef03",
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
247
  "spec/v2/corrections.json": "59f647f4d22477f6c2b06dcf913b682dde5d51dd5e607a5d0cf648ab60864d6e",
248
248
  "spec/v2/corrections.schema.json": "46bbffb954d13bd9995aebc912ceeb2544f7064331abf25fd6ab85c81a5cec94",
249
- "spec/v2/declaration.json": "48cf54bc5ab04c00df3079b3fee701802de35a5a211dbfaf62c6a16bd4d3cd06",
249
+ "spec/v2/declaration.json": "184b83919e56d2f12bda73c7772905810a441cbab53fc42ad0b3135937fac3e5",
250
250
  "spec/v2/declaration.schema.json": "ef951633e4070899f64828f5cf1b5f4870270a0360f81dbb49b5b4d6cd407550",
251
251
  "spec/v2/errors.json": "dc1a846abe3e888acb0ce4a33cf9d1fb98e41fcc8b49f42b3682fd65deca2eb2",
252
252
  "spec/v2/errors.schema.json": "47404ad53e6147cf9017b813483712466573e419fb33e6c4b6c12d3e7deba1bc",
@@ -254,23 +254,23 @@
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": "4f9db1715219070fc57e06cc9b066f13aa130dd50f4869722d908e6e15720787",
258
- "spec/v2/ext/canvas/README.md": "9a08469f1cbde2b0d6594ce76f8cce94b9fb5415979ef5e2ecb732f311c86bae",
259
- "spec/v2/ext/chat/README.md": "07e9ca34b3e5fc222ace1c2af60375266e001d1695da87f38f56b58f17aeaead",
257
+ "spec/v2/ext/brand/README.md": "82989c018d7965bab029bcce7a67b1d76ffc433d08971a4cac91befcccc09438",
258
+ "spec/v2/ext/canvas/README.md": "0e91b68566d2be90319feedfaebe2cd2db0bd8f72fb59c958fa723c05e38023f",
259
+ "spec/v2/ext/chat/README.md": "f59b6706162abbfff96f8bccd4c0e1aeed721047cf62e9af58c875727a38d716",
260
260
  "spec/v2/ext/coordination/README.md": "c7fe0884f52405fdd02c5f9ec309ac6b585685ee06ec5f338fff7acf03c07148",
261
- "spec/v2/ext/dataIntegration/README.md": "a18e179cf88eb5d3e2fe2e017018141747a64a984d25a4f4ed32d776ea62e26d",
261
+ "spec/v2/ext/dataIntegration/README.md": "286dec8aa29e4d3225cbd8b3a361f5a438c3288e759966b9415ecbb99c576e70",
262
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": "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",
264
+ "spec/v2/ext/kanban/README.md": "bfc386dc0f874a4afa1e3ec6a2f99084b547dcce9bd363da3b8ebd8786b9c184",
265
+ "spec/v2/ext/knowledge/README.md": "cbe1f945a757791dedac0fd542a68c86c5b457778b831d58d9ae44a2baa270f2",
266
+ "spec/v2/ext/launchStudio/README.md": "a941ef1d2401438a412eaea48d2d6ca807a032b44ea08719fea33120daf088a0",
267
+ "spec/v2/ext/messaging/README.md": "949f350e35294f52ad490e42699f8c560cd154549516fbaa1805f06312b7f342",
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": "acda51f1a973308841861a119def691db972f310c7b7250ab14f248e9c968585",
271
+ "spec/v2/ext/restTransport/README.md": "a8115faefeab01b7c85d516de60ebfc99b8e89552d16ba654456224518447b28",
272
272
  "spec/v2/ext/sandbox-runtime-notes/README.md": "0d1290fb1e20b2e35c0b15051541ab6546e4e2a6f693b4f912cb3f7d6a65e92c",
273
- "spec/v2/ext/webResearch/README.md": "3ec0fa2c798bcfe16a545c6b47a624c44b776561ff46fa754711e5f6677826f8",
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
276
  "spec/v2/facets/auditLogIntegrity.schema.json": "9a7f81320c8f940c6f4129bc7dd895a4063231c5458a717a08c2cbf9bb886c46",
@@ -298,9 +298,9 @@
298
298
  "spec/v2/path-manifest.json": "17298a245c5da9a70aa7ab0b7f2db4277e06209d6fcdd7187a8b4cea179c08f3",
299
299
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
300
300
  "spec/v2/profiles.json": "180beb9ef2d161766fa23b2987e7e829851e3d316273147cf6a4a3f324befd6b",
301
- "spec/v2/release.json": "8bd257db47f63fdc4759ed13f34df309d80cd69ee83b96a24aed8bb3a9810b1d",
301
+ "spec/v2/release.json": "ec99d76b53fba89d3d8788206c649a782774b55acf5284b75cab38b79ecf1d61",
302
302
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
303
- "spec/v2/surface-baseline.json": "d47f26c3194de0e0d42a731ecaeeae1c2adfdea8c09314792d15c02b50d62efb"
303
+ "spec/v2/surface-baseline.json": "3d3d0fc7c0b48e7d7339e5d8ada0640487908f37220b2de65ccc9bff093f876a"
304
304
  },
305
- "corpusCommit": "49be1aa2be9d835a80f934ce98dc4cebf0af46a4"
305
+ "corpusCommit": "6612868966f2ecb9672d0a64382cb7067491c004"
306
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, signBundleV3, verifierSign, verifyBundleV3, witnessDigest, type BundleV3, type BundleV3Requirement } from './lib/certification-bundle-v3.js';
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 unsigned: Omit<BundleV3, 'signature'> = {
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
- const signature = signBundleV3(unsigned, signingKeyPem, keyId);
742
- const v3: BundleV3 = { ...unsigned, signature };
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
- v3.verifierSignature = verifierSign(unsigned, vk, vkId);
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
@@ -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
  }
@@ -153,6 +153,46 @@ export interface TestFailure {
153
153
  readonly message?: string;
154
154
  }
155
155
 
156
+ /** A finished `it` as vitest's own task tree reports it. */
157
+ export interface FinishedTest {
158
+ readonly id: string;
159
+ readonly name: string;
160
+ readonly state: string | undefined;
161
+ readonly message?: string;
162
+ }
163
+
164
+ /**
165
+ * The tests the per-test recorder never saw, as file states and failures.
166
+ *
167
+ * vitest runs a file's `afterEach` hooks in stack order, so a scenario's own
168
+ * `afterEach` (unregistering webhooks, closing a receiver) runs BEFORE the
169
+ * recorder `setup.ts` registers. If that hook throws or times out, vitest
170
+ * fails the test and skips the remaining hooks, so the recorder never runs.
171
+ * The file then has no recorded state, and its row read "no test executed and
172
+ * no disposition recorded" (`blocked`). That row gives no reason and hides a
173
+ * real failure. One tier-2 cut of `v2-webhook-message-id-stable` produced it
174
+ * on 2026-09-28.
175
+ *
176
+ * Only tests that finished `pass` or `fail` are recovered. A skipped test
177
+ * never reaches the recorder either way, and the existing rules already cover it.
178
+ */
179
+ export function unrecordedTests(
180
+ tests: readonly FinishedTest[],
181
+ recordedIds: ReadonlySet<string>,
182
+ ): { states: FileTestState[]; failures: TestFailure[] } {
183
+ const states: FileTestState[] = [];
184
+ const failures: TestFailure[] = [];
185
+ for (const t of tests) {
186
+ if (recordedIds.has(t.id)) continue;
187
+ if (t.state === 'pass') states.push('pass');
188
+ else if (t.state === 'fail') {
189
+ states.push('fail');
190
+ failures.push({ name: t.name, message: `the test or its afterEach hook failed before the runner recorded it: ${t.message ?? 'no message'}` });
191
+ }
192
+ }
193
+ return { states, failures };
194
+ }
195
+
156
196
  /**
157
197
  * The `executed-fail` detail for a file row: WHICH cases failed, and what the
158
198
  * first one said.
@@ -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
+ });
@@ -0,0 +1,63 @@
1
+ /**
2
+ * RFC 0087 §A/§D — the SERVED bodies of the org-chart read pair validate against
3
+ * `schemas/v2/agent-org-chart.schema.json` and
4
+ * `schemas/v2/org-chart-responsibility-view.schema.json`. Target major 2; the
5
+ * major-1 twin is the served-shape leg of `agent-org-chart-scoping.test.ts`.
6
+ *
7
+ * Why this exists: until 2026-09-28 only a server-free probe compiled the
8
+ * schema. openwop-app served its stored record (`{ tenantId, departments,
9
+ * members, updatedAt }` — `owner` missing, two extra keys on an
10
+ * `additionalProperties:false` object) on `GET /agents/org-chart` at both
11
+ * majors and nothing in the suite noticed (fixed in openwop-app #4189).
12
+ *
13
+ * Gate: the v2 `agents` record carries `orgChart` (presence is the claim,
14
+ * capabilities.md; there is no `supported` seat), else `inapplicable` — never a
15
+ * strict-mode failure. The department leg runs when the record carries
16
+ * `responsibilityView: true` and the chart has a department to read.
17
+ *
18
+ * @see api/v2/openapi.yaml `/agents/org-chart`, `/agents/org-chart/{departmentId}`
19
+ * @see RFCS/0087-agent-org-chart.md
20
+ */
21
+ import { describe, it, expect } from 'vitest';
22
+ import { driver } from '../lib/driver.js';
23
+ import { familyAdvertised, v2Validator } from '../lib/v2.js';
24
+ import { softSkip } from '../lib/soft-skip.js';
25
+ import { req } from '../lib/requirement-ids.js';
26
+
27
+ const ID = 'openwop.it.v2-agent-org-chart-served-shape.serves-the-org-chart-read-pair-in-the-exact-v2-schema-shape';
28
+
29
+ describe('RFC 0087 §A/§D — org-chart served shape (major 2, gated on agents.orgChart)', () => {
30
+ it('serves the org-chart read pair in the exact v2 schema shape', async () => {
31
+ const agents = await familyAdvertised('agents');
32
+ const orgChart = agents?.['orgChart'];
33
+ if (!orgChart || typeof orgChart !== 'object') return softSkip('inapplicable', 'agents.orgChart not advertised in the v2 discovery document');
34
+
35
+ const chartRes = await driver.get('/agents/org-chart');
36
+ expect(
37
+ chartRes.status,
38
+ req(ID, 'api/v2/openapi.yaml /agents/org-chart (RFC 0087 §D)', 'a host advertising agents.orgChart MUST serve GET /agents/org-chart with 200'),
39
+ ).toBe(200);
40
+ const chartCheck = v2Validator('agent-org-chart')(chartRes.json);
41
+ expect(
42
+ chartCheck.ok ? 'valid' : chartCheck.errors,
43
+ req(ID, 'schemas/v2/agent-org-chart.schema.json (RFC 0087 §A)', 'the served GET /agents/org-chart body MUST validate against agent-org-chart.schema.json (owner required; no extra keys)'),
44
+ ).toBe('valid');
45
+
46
+ const departments = (chartRes.json as { departments?: Array<{ departmentId?: unknown }> } | undefined)?.departments;
47
+ const probeDeptId = Array.isArray(departments) ? departments[0]?.departmentId : undefined;
48
+ // The view leg needs a department to read; an empty chart is still
49
+ // validated above (an empty chart is a conforming chart).
50
+ if ((orgChart as Record<string, unknown>)['responsibilityView'] === true && typeof probeDeptId === 'string') {
51
+ const view = await driver.get(`/agents/org-chart/${encodeURIComponent(probeDeptId)}`);
52
+ expect(
53
+ view.status,
54
+ req(ID, 'api/v2/openapi.yaml /agents/org-chart/{departmentId} (RFC 0087 §D)', "a host advertising agents.orgChart.responsibilityView MUST serve GET /agents/org-chart/{departmentId} with 200 for a department in the caller's chart"),
55
+ ).toBe(200);
56
+ const viewCheck = v2Validator('org-chart-responsibility-view')(view.json);
57
+ expect(
58
+ viewCheck.ok ? 'valid' : viewCheck.errors,
59
+ req(ID, 'schemas/v2/org-chart-responsibility-view.schema.json (RFC 0087 §D)', 'the served GET /agents/org-chart/{departmentId} body MUST validate against org-chart-responsibility-view.schema.json'),
60
+ ).toBe('valid');
61
+ }
62
+ });
63
+ });