@openwop/openwop-conformance 2.46.43 → 2.47.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 +11 -0
- package/README.md +2 -2
- package/dist/spec-artifacts.lock.json +2 -2
- package/fixtures/conformance-llm-cache-key.json +23 -0
- package/fixtures/conformance-loopback-refine-fail.json +92 -0
- package/fixtures/conformance-loopback-refine.json +91 -0
- package/fixtures.md +28 -0
- package/package.json +2 -2
- package/requirements.json +646 -23
- package/scenario-majors.json +14 -2
- package/schemas/CORPUS-STAMP.json +29 -25
- package/src/lib/bounded-loopback-witness.ts +125 -0
- package/src/lib/llm-cache-key-2.ts +114 -0
- package/src/scenarios/fixtures-valid.test.ts +14 -2
- package/src/scenarios/v2-bounded-loopback.test.ts +231 -0
- package/src/scenarios/v2-interrupt-resolved-via.test.ts +257 -0
- package/src/scenarios/v2-replay-llm-cache-key.test.ts +160 -0
- package/src/scenarios/v2-telemetry-export.test.ts +181 -0
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":
|
|
4
|
+
"files": 670,
|
|
5
5
|
"v1": 455,
|
|
6
|
-
"v2":
|
|
6
|
+
"v2": 230
|
|
7
7
|
},
|
|
8
8
|
"majors": {
|
|
9
9
|
"a2a-1-0-agent-card.test.ts": [
|
|
@@ -1344,6 +1344,9 @@
|
|
|
1344
1344
|
"v2-bound-id-path-projection.test.ts": [
|
|
1345
1345
|
2
|
|
1346
1346
|
],
|
|
1347
|
+
"v2-bounded-loopback.test.ts": [
|
|
1348
|
+
2
|
|
1349
|
+
],
|
|
1347
1350
|
"v2-budget-enforcement.test.ts": [
|
|
1348
1351
|
2
|
|
1349
1352
|
],
|
|
@@ -1539,6 +1542,9 @@
|
|
|
1539
1542
|
"v2-interrupt-resolve-terminal.test.ts": [
|
|
1540
1543
|
2
|
|
1541
1544
|
],
|
|
1545
|
+
"v2-interrupt-resolved-via.test.ts": [
|
|
1546
|
+
2
|
|
1547
|
+
],
|
|
1542
1548
|
"v2-interrupt-token-scheme.test.ts": [
|
|
1543
1549
|
2
|
|
1544
1550
|
],
|
|
@@ -1734,6 +1740,9 @@
|
|
|
1734
1740
|
"v2-relaxation-recorded.test.ts": [
|
|
1735
1741
|
2
|
|
1736
1742
|
],
|
|
1743
|
+
"v2-replay-llm-cache-key.test.ts": [
|
|
1744
|
+
2
|
|
1745
|
+
],
|
|
1737
1746
|
"v2-replay-suppression-ordinal.test.ts": [
|
|
1738
1747
|
2
|
|
1739
1748
|
],
|
|
@@ -1827,6 +1836,9 @@
|
|
|
1827
1836
|
"v2-table-schema-enforcement.test.ts": [
|
|
1828
1837
|
2
|
|
1829
1838
|
],
|
|
1839
|
+
"v2-telemetry-export.test.ts": [
|
|
1840
|
+
2
|
|
1841
|
+
],
|
|
1830
1842
|
"v2-terminal-event-once.test.ts": [
|
|
1831
1843
|
2
|
|
1832
1844
|
],
|
|
@@ -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.47.0",
|
|
5
|
+
"corpusTag": "v2.47.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": "ac6de5303872664b78ff419c035915593e01599e02835e777a0123de1baeb398",
|
|
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": "dffb86d9287e72ee6261bd830b6920068ac78c1a0947f8d5087b47ecc19f11e5",
|
|
13
|
+
"api/v2/asyncapi.yaml": "7a8c4937c9b4f8387cc8d7cd6a5b890449b751ac2254b991f3434774798452d9",
|
|
14
|
+
"api/v2/openapi.yaml": "0c3ba86687c60e7b65add574b011c306c1ad132e1cc371c954ac1190c1f3781e",
|
|
15
15
|
"api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
|
|
16
16
|
"schemas/README.md": "276fc1d51eb08e14e9127b9fd27761d7046ee33c12000f3a64a801322db8bf58",
|
|
17
17
|
"schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
|
|
@@ -115,8 +115,8 @@
|
|
|
115
115
|
"schemas/v2/artifact.schema.json": "85b9dc284d39b99d2a66fa8cbf47f2dd3d8526d3bd6cc411ad315b372de38975",
|
|
116
116
|
"schemas/v2/audit-verify-result.schema.json": "14af8e9283510f0a40e48a2059c929ee44e0fc9fa3cc66a95bfbe90b17c48297",
|
|
117
117
|
"schemas/v2/budget-policy.schema.json": "1de68095db0beb5c7a4be629de26e79f62ebbe748a88d46f36e768edfd1f0d68",
|
|
118
|
-
"schemas/v2/capabilities.schema.json": "
|
|
119
|
-
"schemas/v2/certification-bundle.schema.json": "
|
|
118
|
+
"schemas/v2/capabilities.schema.json": "609aa5998cafb549badbae7a6d94100ee32d130ade03dc6dfa40005f40154ac1",
|
|
119
|
+
"schemas/v2/certification-bundle.schema.json": "29d0be495dfaa2265f30394de92a96ca95058f1a89f663ece97c67ea6fae94c0",
|
|
120
120
|
"schemas/v2/channel-presence-payload.schema.json": "fc03df0c20cc12a25f7ee6a47503a4c5931d79fe3d5ff24815e8fd7c1bcb9a87",
|
|
121
121
|
"schemas/v2/channel-written-payload.schema.json": "ccecff3c71a3275ad8db035ac0e04db25e6f8d23cf091ddbcd5a60d1be1cbb6b",
|
|
122
122
|
"schemas/v2/chat-card-pack-manifest.schema.json": "9cf235f5d0fe662f34f7e72bfbf2a82e0743046059a9d5af7aec948e228fdb0e",
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
"schemas/v2/credential-reference.schema.json": "ea699ce30f58fb2c32d4279158b3f730f70be7b2dc8c3037693a76e66eb3e001",
|
|
133
133
|
"schemas/v2/debug-bundle.schema.json": "0ec1e0bca9c0a359cc6cf3448e5200a1c3d3d5b1e5efaa6605231477daaf3fee",
|
|
134
134
|
"schemas/v2/dispatch-config.schema.json": "27cb203d2a5cec90d8eb398569acbd8314ad9b8e231d1acfb8db4aa76f8d8f8b",
|
|
135
|
-
"schemas/v2/effect-ledger-projection.schema.json": "
|
|
135
|
+
"schemas/v2/effect-ledger-projection.schema.json": "ac1f577ec420ece9a5fbe74a33a54aa5bbe4d8259f3c5e9c2bd9b8901ed1c289",
|
|
136
136
|
"schemas/v2/effect-seam-manifest.schema.json": "197ef9944ab027a50d1b9352f317cbbc12c8ad955fdb14650fb90477052ffef0",
|
|
137
137
|
"schemas/v2/error-envelope.schema.json": "5d566a910b715452e99b32876426cd490ef803f2177e3ecbbd1051170d4d4462",
|
|
138
138
|
"schemas/v2/eval-summary.schema.json": "d07f4d116c5410997a81d0bc5387545fb61933a6333c360a26ab8a71c749336b",
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
"schemas/v2/residency.schema.json": "3fd23c9732beb140504e81560fc426eb6764d547e56da5fb92c5a5c7bf04db3a",
|
|
170
170
|
"schemas/v2/run-ancestry-response.schema.json": "334c17f2062dea62726ca1d07b8be3f169cdc422af843c1def5fb317039814c9",
|
|
171
171
|
"schemas/v2/run-diff-response.schema.json": "8bdb2045c274580038bd74781f7d062f5049839797c164a89de4ed89edc53a74",
|
|
172
|
-
"schemas/v2/run-event-payloads.schema.json": "
|
|
172
|
+
"schemas/v2/run-event-payloads.schema.json": "0bfd4b52e8b666f533614e6700de5ff8342f1c021f210b988cc7e374b056ea1d",
|
|
173
173
|
"schemas/v2/run-event.schema.json": "2f0d7416bd376c56affe97b6b2b49981532e8e9ff45c0a162cf806c78b3e7fb7",
|
|
174
174
|
"schemas/v2/run-list-response.schema.json": "c2dbc34768dcf64528914a213aa3cb55952e8e3e90a64b04a9423b2a19749d38",
|
|
175
175
|
"schemas/v2/run-options.schema.json": "f0e9f494d80086c2ed2cf9fe87df54e5b4e66601699c4a1089f4edbc7ba58867",
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
"schemas/v2/self-hosted-runner-result-frame.schema.json": "8e739a721f350226f3ffb4af752b4fe0a72016042a7b6f42e16c0487c3c58983",
|
|
182
182
|
"schemas/v2/subject-link.schema.json": "1fc941d6c14af886534009f2ffa6611e2035eb07e30b1df918c50d6bafff026c",
|
|
183
183
|
"schemas/v2/subject.schema.json": "094e981fb20052a911393e219ea5f2065d8b8417b65080a70c5ea99fd960a8fe",
|
|
184
|
-
"schemas/v2/suspend-request.schema.json": "
|
|
184
|
+
"schemas/v2/suspend-request.schema.json": "5d261691251a1528c153788450633b1947ab6aba8f71e23b55c53b69b10c7aa8",
|
|
185
185
|
"schemas/v2/tool-descriptor.schema.json": "9bfe8a1ee5ea55d1c23e383d97234d9d6545d833996d7bc30caf075e0a6294bb",
|
|
186
186
|
"schemas/v2/trigger-dead-letter-page.schema.json": "571f8c39b64571bff20d391a6e6e829d4ce470ab790b2b7f810cc4aa40edcbc1",
|
|
187
187
|
"schemas/v2/trigger-event.schema.json": "710ecca8b3ecdad3a532a96768177f062192b24a589a4f798c7eb24bab64b30a",
|
|
@@ -191,8 +191,8 @@
|
|
|
191
191
|
"schemas/v2/webhook-dead-letter-page.schema.json": "78f3284722c2b42d52ff8a1e6d9b9092ba1ac35c2dbe036f3ff2470b34086d1e",
|
|
192
192
|
"schemas/v2/webhook-delivery.schema.json": "5f5361ede12098b5c2d685f0aa70fcb07219fd6b68c6ad6af393ca8dd266d56a",
|
|
193
193
|
"schemas/v2/webhook-verification.schema.json": "76e4e0d81143ffd2649bc7adba2aa34f2525e2c4e9f0300a425d908b3163196b",
|
|
194
|
-
"schemas/v2/workflow-chain-pack-manifest.schema.json": "
|
|
195
|
-
"schemas/v2/workflow-definition.schema.json": "
|
|
194
|
+
"schemas/v2/workflow-chain-pack-manifest.schema.json": "e63a674a56a61bb604c25fdc01af3bf9a47aed6ba464230146c5e09a69a23a3e",
|
|
195
|
+
"schemas/v2/workflow-definition.schema.json": "02d1dcbd3d1b6962583dde0892b3469617e1ac0c72304829df60846857891d4b",
|
|
196
196
|
"schemas/v2/workload-identity.schema.json": "1f589debfac6d70e0908da511550ec69a27b4bcc8314551cd843b6d923605efc",
|
|
197
197
|
"schemas/v2/workspace-file-create.schema.json": "b6b5260857318c3b67de0231ed15e3a746f525880a763e7b36a7854178dd10d0",
|
|
198
198
|
"schemas/v2/workspace-file.schema.json": "d2ed39e8c229dde33b56905c8df6f8b2ff056c97e1c5f615e3124baa54a6254f",
|
|
@@ -203,14 +203,14 @@
|
|
|
203
203
|
"schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
|
|
204
204
|
"spec/v1/alias-detectors.json": "202a443678e592a515e33aad7f6ab679a07d8408809f2d4b0f59d156c84ffc65",
|
|
205
205
|
"spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
|
|
206
|
-
"spec/v1/core-standard-manifest.json": "
|
|
206
|
+
"spec/v1/core-standard-manifest.json": "9c3d53b164244ae9f238d5047681e01701c0df244172489a50509e62265514bc",
|
|
207
207
|
"spec/v1/deprecations.json": "901fdab0a7212627f1838cbb06ae7b7204f0dff528299e56d3d938deecea27c1",
|
|
208
208
|
"spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
|
|
209
209
|
"spec/v1/end-of-support.json": "35f0bf3b70fe78b16e2a2fd81a153fc27e703f6c16376c6fe7cdf1f4f2c4e6d4",
|
|
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": "08579ba52f384764f8b5849ee7d0bc97a0cb49318f6c6ccd0561707ab5f3be80",
|
|
214
214
|
"spec/v1/gaps.schema.json": "8fd83259f556553c9df0f53e7a82ca8c2a4771d471197f69b4e2ae0ceeacfd66",
|
|
215
215
|
"spec/v1/migrations.json": "4c50760be70cb644d02427bcaf5749606700edf4513f423903cf3b1d12f5c300",
|
|
216
216
|
"spec/v1/migrations.schema.json": "886779aa6c22e646db097f5df210adb018a4dd14a7b815465a18c8a7056c8f72",
|
|
@@ -218,13 +218,13 @@
|
|
|
218
218
|
"spec/v1/spec-gaps.json": "ea9612af55bea9dae3e82223d4f714243f8f590b0193f1188257b13491a38afc",
|
|
219
219
|
"spec/v2/README.md": "c58acd84bb67c69043d9f777cbaa16d03e4f0f0a8158d2984526b69981067a14",
|
|
220
220
|
"spec/v2/core/artifact-type-packs.md": "ba5a258ac93b1aa4d74a4464c63a8f72dbbc5d24fd56f34a2c2ce9f464892799",
|
|
221
|
-
"spec/v2/core/capabilities.md": "
|
|
221
|
+
"spec/v2/core/capabilities.md": "7ab4c1ab5b0b0a95a5e35ff2db9b45c5fb6bf23844c97ffeed6d6ebda3d17c7e",
|
|
222
222
|
"spec/v2/core/conformance.md": "2e12b5fceb9cbd1bbf4a536310fe92a4cd44947e48d0466013327c5de202a2d9",
|
|
223
223
|
"spec/v2/core/connection-packs.md": "c80f95bd6a03698f35c72cdb2b46bd973f49203bbd786c26760e35fa86e6f23d",
|
|
224
224
|
"spec/v2/core/conversation.md": "8d6abedf6af408a74577753bfca24719d318e2632e10b45ff5d63bdcf31a8aaa",
|
|
225
225
|
"spec/v2/core/errors.md": "0ba4fa59f0f3dc025d43147135dabede6c19d583677b761eafe693fc2169352c",
|
|
226
226
|
"spec/v2/core/events.md": "5834308616f2e722e217db776edea37338c283196b6d2a7c241256575ccca825",
|
|
227
|
-
"spec/v2/core/execution.md": "
|
|
227
|
+
"spec/v2/core/execution.md": "062c178d5b5f8fda0123028bead235b5960d9a8755ea0322e794053917501ef0",
|
|
228
228
|
"spec/v2/core/form-content-packs.md": "d56022c207c8406fed4b2e9501bd14e70a4300e371c0cfc371787899ae0bd997",
|
|
229
229
|
"spec/v2/core/headers.md": "71f5024adfa754789cf751001308baa87decff6a3afd514c75d573674cbf50ee",
|
|
230
230
|
"spec/v2/core/host-services.md": "68fbe359ec9efc453105a225998b8d0dada07ae61bfd05e324dd8649af31261d",
|
|
@@ -232,24 +232,25 @@
|
|
|
232
232
|
"spec/v2/core/idempotency.md": "adbd317a6ebb57c2759c076297b31f67def9e391576bccd0bbea787b13e85872",
|
|
233
233
|
"spec/v2/core/identity.md": "3447f4bec0e4780a3b32b0039d9a0ae1d85de5a71166a4d7e2d3d68b09d55129",
|
|
234
234
|
"spec/v2/core/interop.md": "78d7944c8955412a570f14b84bf74ed4e0ef5dec6680e1cb538c3329a4aa778b",
|
|
235
|
-
"spec/v2/core/interrupt.md": "
|
|
235
|
+
"spec/v2/core/interrupt.md": "00e745e6aa8af32c14e5dd7bf73b47f10e853f49e5bcb47eca344dcd4e0f6811",
|
|
236
236
|
"spec/v2/core/node-pack-runtimes.md": "0a269d2b36477870d45bdd2e7e34fea9dfd428e2e59e6cc7cbc0c67f3147a9ee",
|
|
237
237
|
"spec/v2/core/oauth.md": "9bdbdc85ad2ff4c1f578730edba3a79a6ce111f0335f90feb06863c8e823a23f",
|
|
238
238
|
"spec/v2/core/overview.md": "593c2ac434952f08c6f552e1b77780541906fe5ffdeb911133f6402075e65e72",
|
|
239
239
|
"spec/v2/core/packs.md": "c46a3bbd8a3a8cfbb6476b88f90d234a45f724aa1f640c9ce6430ba05bacb069",
|
|
240
240
|
"spec/v2/core/persistence.md": "d1767bde0db8038bf6c4f8dc31c4900c67dbd77b4ccc382e99b9f29501b472a3",
|
|
241
241
|
"spec/v2/core/portability.md": "93b9cb8bb85448669df716effb7627236b2bb069f8c5ca7306e8971b9355abfd",
|
|
242
|
-
"spec/v2/core/replay.md": "
|
|
242
|
+
"spec/v2/core/replay.md": "de705ad72e4878def9a0e1d215df9020c129eabae5f224b8c9b3330bd3b48c47",
|
|
243
243
|
"spec/v2/core/runs.md": "f9c26490e52fa69a10c8e98c8a489e3bc8dfbe3effe334bb1793bc9b575f5efe",
|
|
244
244
|
"spec/v2/core/security-defaults.md": "18c9956cae51c3da1def80ce94741ad1fd664a8150701160c6cd0ef367c79749",
|
|
245
245
|
"spec/v2/core/storage.md": "bfcc74526d0f24539188b1c5d1567d8510e80d0b6c39fdc4d19204fcd6fd1f85",
|
|
246
|
+
"spec/v2/core/telemetry.md": "5e2f30919c51195b57566a2a277663e72dffd1a56a68c19fb8b0d3260629ff7f",
|
|
246
247
|
"spec/v2/core/tool-catalog.md": "d16a9004418719c48301804aefa4d7ab6f41112a4ed83727f552aa1b92b418b0",
|
|
247
248
|
"spec/v2/core/versioning.md": "f25c64849e9fa7e3ddc976f33c502bbf0e3fdd158c5c2d5730d524b1f8a19bfe",
|
|
248
249
|
"spec/v2/core/webhooks.md": "861284396eb4f445f4b7bec210f07dd2c96d18084f8511371235b98617bf2b90",
|
|
249
250
|
"spec/v2/core/workflow-chain-packs.md": "fdce7a8477e269950a2bac05ee6eddbd66eb0b670b464d00054f7196ec05d29c",
|
|
250
251
|
"spec/v2/corrections.json": "5ee76d1ee87677a86d5d9ee2f477b88fc29f9f9d37611040b9ccb08f8f30fb2d",
|
|
251
252
|
"spec/v2/corrections.schema.json": "02f6a0a9531e657693be311af0f59d6432f3efcf645f56ccbf39e5b141e6b641",
|
|
252
|
-
"spec/v2/declaration.json": "
|
|
253
|
+
"spec/v2/declaration.json": "7c8da768d45866b70544fad1e26c513f1dce8657c276ffa7148127d11b744811",
|
|
253
254
|
"spec/v2/declaration.schema.json": "ef951633e4070899f64828f5cf1b5f4870270a0360f81dbb49b5b4d6cd407550",
|
|
254
255
|
"spec/v2/eos-override.json": "4c72d2f6a658943b01c1de1643cf294642091933af7832f38ee5495df684cb2d",
|
|
255
256
|
"spec/v2/errors.json": "8b4d2ebab569b26f608323ca4c9adaf805ab515286d26532db23fcb97541787f",
|
|
@@ -282,22 +283,25 @@
|
|
|
282
283
|
"spec/v2/facets/connections.schema.json": "f998bb206a5814183bb4720cc34fa43b7ab64d61e39a79437b009c589ce8b78e",
|
|
283
284
|
"spec/v2/facets/heartbeat.schema.json": "81d7fa1895a8b0241bd07cf00a7e7905bef0fd14eb3427325261c62eee5746a6",
|
|
284
285
|
"spec/v2/facets/hostEvents.schema.json": "8834c2fde7bc2ba9eb433f22c9d6ec3f9739f44049eecdbf0a05baf2bfa0bb7d",
|
|
285
|
-
"spec/v2/facets/
|
|
286
|
+
"spec/v2/facets/idempotency.schema.json": "f20c1e0b08749385b443a7e65293197cfe68cb51486b29659c6af6b96179fa98",
|
|
287
|
+
"spec/v2/facets/interrupt.schema.json": "e89e008a06b0a53cebfeac500c1622ca871c920fc8856b31c019689c5df41b66",
|
|
286
288
|
"spec/v2/facets/limits.schema.json": "e68327665def83007f2573b413757caa602e82ad347d1b85e9853fea153ffb48",
|
|
289
|
+
"spec/v2/facets/loopback.schema.json": "577a34333d1633f8ab00f11e1aeaee2708022faa69a26cf316fc5bea00ee7d50",
|
|
287
290
|
"spec/v2/facets/mcp.schema.json": "7060657dfcff170f079beed160e3db7c1d3cc4410cd3cf64863baf6459f781cd",
|
|
288
291
|
"spec/v2/facets/nondeterminismPolicy.schema.json": "e9dfaa32e0778a1a837fdad3b8a0673fccda9579ab08c8bfe9777362dd15e989",
|
|
289
292
|
"spec/v2/facets/oauth.schema.json": "3a0cbe249d3bd5a5982349ae7ba2534776cc958e4a57e0fbeabf2bdab6e99e69",
|
|
290
293
|
"spec/v2/facets/packs.schema.json": "d544a280419c0052ce0f3c3b378f7aa04085cadc31df52a3c8f43da600b131b1",
|
|
291
|
-
"spec/v2/facets/replay.schema.json": "
|
|
294
|
+
"spec/v2/facets/replay.schema.json": "ebb1ce04ed1e4fca237f448e565fe75c833e630320930accecba2ce4e660c8d9",
|
|
292
295
|
"spec/v2/facets/runList.schema.json": "6223e1baaebbb9f4faa71bfe968f8d22b4aa88046d0e43e8d5f56409e7f86e90",
|
|
293
296
|
"spec/v2/facets/sandbox.schema.json": "e75f54486d387b2d089791aa93b5093cd1450f1920753a0f97fc23acdfe978ed",
|
|
297
|
+
"spec/v2/facets/telemetry.schema.json": "56e96a26910ff1d891eabfc520c2d152fd9881449fdcea7e03df34793763849e",
|
|
294
298
|
"spec/v2/facets/uiPlugins.schema.json": "1fd3f8440cfb6e4422553a7c4b5f091eb3e10a40da0d43e8b977a18929116758",
|
|
295
299
|
"spec/v2/facets/webhooks.schema.json": "54b576f1d150e28e1642693d846ce471c118e9c521bdf3924185305fbd1d0de4",
|
|
296
300
|
"spec/v2/facets/workflowChainPacks.schema.json": "ea625ce818fbeb746478f00f31f96ee9613d538e4409c258c1bfb517acdeaba9",
|
|
297
301
|
"spec/v2/generated/error-codes.md": "430113c79553b05a999d443587a1bc4d5e9db2e14afe1334e293279baf628479",
|
|
298
302
|
"spec/v2/harness-trust-anchors.json": "6007b92eae16a070fbc3777424efb0abfe865fda89d096963109b96282b91205",
|
|
299
303
|
"spec/v2/harness-trust-anchors.schema.json": "372b5d92337c893d3449eff3f6bfd009ac09fdac285296d8bdbe0a08fff9e34c",
|
|
300
|
-
"spec/v2/id-field-bindings.json": "
|
|
304
|
+
"spec/v2/id-field-bindings.json": "b037453dd3b0a6cc5b0675462d96cc856674792c43d87d57604b57de4697e3ac",
|
|
301
305
|
"spec/v2/interop-map.json": "02255dafe7eb55f61ca7f9ae7c21f079d1848a6756991343088975ac9d184b8d",
|
|
302
306
|
"spec/v2/interop-map.schema.json": "0017c882df34ee215e2e0729de051490e09d43aac3d2c949285698ed51832f12",
|
|
303
307
|
"spec/v2/migrations.json": "651954938a1e154c0b6f1e3dd6e105889d30de3bb65789f21de7f77a12869e9e",
|
|
@@ -305,9 +309,9 @@
|
|
|
305
309
|
"spec/v2/path-manifest.json": "3720e502614f301efc7556e0bfcbfa7b7ad8a37c579256ba6bca82417a2728f1",
|
|
306
310
|
"spec/v2/peer-dependency-aliases.json": "cbf62f551f9295b0ac7ba6cc0466abb153563354a57b0622f3ec6e6689cba47e",
|
|
307
311
|
"spec/v2/profiles.json": "b382b261c7844798575f7c1b37c934f0da19846016a2337d883d4f5ef6e05479",
|
|
308
|
-
"spec/v2/release.json": "
|
|
312
|
+
"spec/v2/release.json": "6082692ec2549fcb02ca33f24336e7f9f417ec4ff08a14d23fc02533d8a00527",
|
|
309
313
|
"spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
|
|
310
|
-
"spec/v2/surface-baseline.json": "
|
|
314
|
+
"spec/v2/surface-baseline.json": "c67e5764105a35d5dc575942582b7b0784c0ad2e3739fe7b5b23de28ea58408f"
|
|
311
315
|
},
|
|
312
|
-
"corpusCommit": "
|
|
316
|
+
"corpusCommit": "aeb6ca4f976b56668c3770acd128c08ec59cfb18"
|
|
313
317
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 0258 — judges for `v2-bounded-loopback.test.ts`. Pure functions over a
|
|
3
|
+
* run's event log, so each can be shown to fail on a sabotaged log
|
|
4
|
+
* (`bounded-loopback-witness.test.ts`). Rules: `$defs/EdgeLoopback` in
|
|
5
|
+
* `schemas/v2/workflow-definition.schema.json`, `$defs/nodeCompleted.loopback`
|
|
6
|
+
* in `run-event-payloads.schema.json`, `$defs/ApprovalData.loopback` in
|
|
7
|
+
* `suspend-request.schema.json`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface Ev { type?: string; sequence?: number; nodeId?: string; payload?: Record<string, unknown> }
|
|
11
|
+
export interface LoopFinding { ok: boolean; message: string }
|
|
12
|
+
|
|
13
|
+
const ok = (cond: boolean, message: string): LoopFinding => ({ ok: cond, message });
|
|
14
|
+
const nodeOf = (e: Ev): string | undefined => (typeof e.nodeId === 'string' ? e.nodeId : (e.payload?.['nodeId'] as string | undefined));
|
|
15
|
+
const of = (log: readonly Ev[], type: string, nodeId?: string): Ev[] => log.filter((e) => e.type === type && (nodeId === undefined || nodeOf(e) === nodeId));
|
|
16
|
+
|
|
17
|
+
/** Every `interrupt.requested` raised by `gate`, in log order. */
|
|
18
|
+
export function requestsOf(log: readonly Ev[], gate: string): Ev[] { return of(log, 'interrupt.requested', gate); }
|
|
19
|
+
|
|
20
|
+
/** The `ApprovalData.loopback` of one request, or undefined. */
|
|
21
|
+
export function approvalLoopback(e: Ev | undefined): Record<string, unknown> | undefined {
|
|
22
|
+
const data = e?.payload?.['data'] as Record<string, unknown> | undefined;
|
|
23
|
+
const lb = data?.['loopback'];
|
|
24
|
+
return lb && typeof lb === 'object' ? (lb as Record<string, unknown>) : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The gate's `node.completed` events that carry the §C.1 marker. */
|
|
28
|
+
export function markersOf(log: readonly Ev[], gate: string): Ev[] {
|
|
29
|
+
return of(log, 'node.completed', gate).filter((e) => e.payload?.['loopback'] !== undefined);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `0258.refine-rearms` — after one refine: the gate's completion carries the
|
|
34
|
+
* marker for round 1 naming every region node, the target starts again, the
|
|
35
|
+
* gate is asked again under a different key, and the gate's forward target
|
|
36
|
+
* never started.
|
|
37
|
+
*/
|
|
38
|
+
export function judgeRearm(log: readonly Ev[], o: { gate: string; target: string; edgeId: string; region: readonly string[]; forward: string }): LoopFinding[] {
|
|
39
|
+
const marks = markersOf(log, o.gate);
|
|
40
|
+
const m = marks[0]?.payload?.['loopback'] as Record<string, unknown> | undefined;
|
|
41
|
+
const reqs = requestsOf(log, o.gate);
|
|
42
|
+
const keys = reqs.map((e) => e.payload?.['key']);
|
|
43
|
+
return [
|
|
44
|
+
ok(marks.length === 1, `the gate's node.completed MUST carry loopback after one refine (found ${marks.length} marked completion(s))`),
|
|
45
|
+
ok(m?.['edgeId'] === o.edgeId && m?.['targetNodeId'] === o.target && m?.['round'] === 1, `the marker MUST read {edgeId: ${o.edgeId}, targetNodeId: ${o.target}, round: 1} (got ${JSON.stringify(m)})`),
|
|
46
|
+
ok(JSON.stringify(m?.['rearmed']) === JSON.stringify(o.region), `rearmed MUST list the region in nodes[] order, ${JSON.stringify(o.region)} (got ${JSON.stringify(m?.['rearmed'])})`),
|
|
47
|
+
ok(of(log, 'node.started', o.target).length === 2, `the target MUST start again as a new visit (node.started ×${of(log, 'node.started', o.target).length}, want 2)`),
|
|
48
|
+
ok(reqs.length === 2 && typeof keys[0] === 'string' && typeof keys[1] === 'string' && keys[0] !== keys[1], `the gate MUST be asked again under a different key (interrupt.requested ×${reqs.length}, keys ${JSON.stringify(keys)})`),
|
|
49
|
+
ok(of(log, 'node.started', o.forward).length === 0, `nothing leaves the region on a refine: ${o.forward} MUST NOT start before accept`),
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `0258.history-from-log` — the target (an input-echoing node) shows the round
|
|
55
|
+
* history it received: `[]` on its first visit, then one entry per round
|
|
56
|
+
* holding exactly `round`, the target's own outputs from that round, and the
|
|
57
|
+
* refine feedback. No other key, so no timestamp, principal, run id or key.
|
|
58
|
+
*/
|
|
59
|
+
export function judgeHistory(log: readonly Ev[], o: { target: string; port: string; feedback: readonly unknown[] }): LoopFinding[] {
|
|
60
|
+
const done = of(log, 'node.completed', o.target).map((e) => (e.payload?.['outputs'] ?? {}) as Record<string, unknown>);
|
|
61
|
+
const out: LoopFinding[] = [ok(done.length === o.feedback.length + 1, `the target MUST complete once per visit (${done.length} completion(s), want ${o.feedback.length + 1})`)];
|
|
62
|
+
done.forEach((outputs, visit) => {
|
|
63
|
+
const h = outputs[o.port];
|
|
64
|
+
if (!Array.isArray(h)) { out.push(ok(false, `visit ${visit}: the ${o.port} input MUST be an array (got ${JSON.stringify(h)})`)); return; }
|
|
65
|
+
out.push(ok(h.length === visit, `visit ${visit}: history MUST hold ${visit} entr${visit === 1 ? 'y' : 'ies'} (got ${h.length})`));
|
|
66
|
+
h.forEach((entry: unknown, i: number) => {
|
|
67
|
+
const en = (entry ?? {}) as Record<string, unknown>;
|
|
68
|
+
const extra = Object.keys(en).filter((k) => !['round', 'outputs', 'refineFeedback'].includes(k));
|
|
69
|
+
out.push(ok(en['round'] === i + 1, `visit ${visit} entry ${i}: round MUST be ${i + 1}, oldest first (got ${JSON.stringify(en['round'])})`));
|
|
70
|
+
out.push(ok(JSON.stringify(en['outputs']) === JSON.stringify(done[i]), `visit ${visit} entry ${i}: outputs MUST be the target's own round-${i + 1} outputs`));
|
|
71
|
+
out.push(ok(JSON.stringify(en['refineFeedback']) === JSON.stringify(o.feedback[i]), `visit ${visit} entry ${i}: refineFeedback MUST be round ${i + 1}'s (got ${JSON.stringify(en['refineFeedback'])})`));
|
|
72
|
+
out.push(ok(extra.length === 0, `visit ${visit} entry ${i}: an entry MUST carry no other key (found ${JSON.stringify(extra)})`));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** `0258.approval-data-loopback` — each visit's `ApprovalData.loopback` reads the expected triple. */
|
|
79
|
+
export function judgeApprovalData(log: readonly Ev[], gate: string, want: ReadonlyArray<{ round: number; maxIterations: number; exhausted: boolean }>): LoopFinding[] {
|
|
80
|
+
const got = requestsOf(log, gate).map(approvalLoopback);
|
|
81
|
+
const out: LoopFinding[] = [ok(got.length === want.length, `the gate MUST be asked ${want.length} time(s) (got ${got.length})`)];
|
|
82
|
+
want.forEach((w, i) => out.push(ok(JSON.stringify(got[i]) === JSON.stringify(w), `visit ${i}: ApprovalData.loopback MUST be ${JSON.stringify(w)} (got ${JSON.stringify(got[i])})`)));
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** `0258.withdraw-refine` — the exhausted visit omits `refine` from `actions`. */
|
|
87
|
+
export function judgeWithdrawn(log: readonly Ev[], gate: string): LoopFinding[] {
|
|
88
|
+
const last = requestsOf(log, gate).at(-1);
|
|
89
|
+
const actions = (last?.payload?.['data'] as Record<string, unknown> | undefined)?.['actions'];
|
|
90
|
+
return [
|
|
91
|
+
ok(approvalLoopback(last)?.['exhausted'] === true, 'the last visit MUST read exhausted: true'),
|
|
92
|
+
ok(Array.isArray(actions) && !actions.includes('refine') && actions.length > 0, `the exhausted visit MUST omit refine from actions and keep the others (got ${JSON.stringify(actions)})`),
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** `0258.fail-on-exhausted` — a refine on the exhausted visit, under `onExhausted: fail`. */
|
|
97
|
+
export function judgeFailExhausted(log: readonly Ev[], gate: string, maxIterations: number): LoopFinding[] {
|
|
98
|
+
const limit = of(log, 'workflow.loopback-limit')[0];
|
|
99
|
+
const cap = of(log, 'cap.breached').find((e) => e.payload?.['kind'] === 'loopback-iterations');
|
|
100
|
+
const failed = of(log, 'run.failed')[0];
|
|
101
|
+
const err = (failed?.payload?.['error'] ?? {}) as Record<string, unknown>;
|
|
102
|
+
const seq = (e: Ev | undefined): number => (typeof e?.sequence === 'number' ? e.sequence : Number.NaN);
|
|
103
|
+
return [
|
|
104
|
+
ok(limit?.payload?.['nodeId'] === gate && limit?.payload?.['iterations'] === maxIterations && limit?.payload?.['limit'] === maxIterations, `workflow.loopback-limit MUST read {nodeId: ${gate}, iterations: ${maxIterations}, limit: ${maxIterations}} (got ${JSON.stringify(limit?.payload)})`),
|
|
105
|
+
ok(cap !== undefined && cap.payload?.['limit'] === maxIterations && cap.payload?.['observed'] === maxIterations + 1, `cap.breached {kind: loopback-iterations, limit: ${maxIterations}, observed: ${maxIterations + 1}} MUST be emitted (got ${JSON.stringify(cap?.payload)})`),
|
|
106
|
+
ok(seq(limit) < seq(cap) && seq(cap) < seq(failed), 'the order MUST be workflow.loopback-limit, then cap.breached, then run.failed'),
|
|
107
|
+
ok(err['code'] === 'loop_limit_exceeded', `run.failed MUST carry loop_limit_exceeded (got ${JSON.stringify(err['code'])})`),
|
|
108
|
+
ok(failed?.payload?.['failedNodeId'] === gate, `run.failed.failedNodeId MUST name the gate (got ${JSON.stringify(failed?.payload?.['failedNodeId'])})`),
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* `0258.replay-rearms` — a replay fork whose prefix ends at the marked
|
|
114
|
+
* completion reaches the gate's second visit: a new request for the gate after
|
|
115
|
+
* the fork point, and no resolution of the gate after it (round 1 is not
|
|
116
|
+
* decided again).
|
|
117
|
+
*/
|
|
118
|
+
export function judgeReplayRearm(fork: readonly Ev[], gate: string, fromSeq: number): LoopFinding[] {
|
|
119
|
+
const after = fork.filter((e) => typeof e.sequence === 'number' && e.sequence >= fromSeq);
|
|
120
|
+
return [
|
|
121
|
+
ok(of(after, 'interrupt.requested', gate).length === 1, `the fork MUST re-arm and ask the gate again after the marker (requests after fromSeq: ${of(after, 'interrupt.requested', gate).length})`),
|
|
122
|
+
ok(of(after, 'interrupt.resolved', gate).length === 0, 'the fork MUST NOT decide round 1 again (no interrupt.resolved for the gate after fromSeq)'),
|
|
123
|
+
ok(!after.some((e) => e.type === 'run.completed' || e.type === 'run.failed'), 'the fork MUST wait at the gate, not settle the run'),
|
|
124
|
+
];
|
|
125
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 0260 — LLM cache key recipe 2 (`openwop-llm-key-2`). A pure function: the
|
|
3
|
+
* suite recomputes the key a host recorded and compares (§B, `0260.key-portable`).
|
|
4
|
+
*
|
|
5
|
+
* key = hex(SHA-256(JCS({ recipe, model, provider, messages, tools, toolChoice,
|
|
6
|
+
* responseSchema, params })))
|
|
7
|
+
*
|
|
8
|
+
* `params` names every sampling or output-shaping parameter; one the host did not
|
|
9
|
+
* send is `null`, never omitted. `stop` is sorted (UTF-16 code-unit order, as JCS
|
|
10
|
+
* orders keys) and de-duplicated. Any other parameter sits under `params.extra`.
|
|
11
|
+
* A string message content is the one-part array `[{type: "text", text}]`. No
|
|
12
|
+
* Unicode normalisation: the bytes are as sent.
|
|
13
|
+
*/
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
import { canonicalJSON, codeUnitCompare } from './jcs.js';
|
|
16
|
+
|
|
17
|
+
export const RECIPE_2 = 'openwop-llm-key-2';
|
|
18
|
+
|
|
19
|
+
/** The parameters recipe 2 names. A request that omits one puts `null` in the key. */
|
|
20
|
+
export const NAMED_PARAMS = ['temperature', 'maxTokens', 'stop', 'seed', 'topP', 'topK', 'frequencyPenalty', 'presencePenalty'] as const;
|
|
21
|
+
export type NamedParam = (typeof NAMED_PARAMS)[number];
|
|
22
|
+
|
|
23
|
+
export interface KeyInputs {
|
|
24
|
+
readonly model: string;
|
|
25
|
+
readonly provider: string;
|
|
26
|
+
readonly messages: readonly { readonly role: string; readonly content: string | readonly Record<string, unknown>[] }[];
|
|
27
|
+
readonly tools?: readonly unknown[];
|
|
28
|
+
readonly toolChoice?: unknown;
|
|
29
|
+
readonly responseSchema?: unknown;
|
|
30
|
+
readonly params?: Partial<Record<NamedParam, unknown>> & { readonly extra?: Record<string, unknown> };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const nullIfAbsent = (v: unknown): unknown => (v === undefined ? null : v);
|
|
34
|
+
|
|
35
|
+
/** The object recipe 2 hashes, before canonicalization. Exported so a failure can show what was hashed. */
|
|
36
|
+
export function keyPreimage(i: KeyInputs): Record<string, unknown> {
|
|
37
|
+
const p = i.params ?? {};
|
|
38
|
+
const params: Record<string, unknown> = {};
|
|
39
|
+
for (const name of NAMED_PARAMS) params[name] = nullIfAbsent(p[name]);
|
|
40
|
+
if (Array.isArray(params['stop'])) params['stop'] = [...new Set((params['stop'] as unknown[]).map(String))].sort(codeUnitCompare);
|
|
41
|
+
if (p.extra !== undefined && Object.keys(p.extra).length > 0) params['extra'] = p.extra;
|
|
42
|
+
return {
|
|
43
|
+
recipe: RECIPE_2,
|
|
44
|
+
model: i.model,
|
|
45
|
+
provider: i.provider,
|
|
46
|
+
messages: i.messages.map((m) => ({ role: m.role, content: typeof m.content === 'string' ? [{ type: 'text', text: m.content }] : m.content })),
|
|
47
|
+
tools: i.tools ?? [],
|
|
48
|
+
toolChoice: nullIfAbsent(i.toolChoice),
|
|
49
|
+
responseSchema: nullIfAbsent(i.responseSchema),
|
|
50
|
+
params,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function cacheKey2(i: KeyInputs): string {
|
|
55
|
+
return createHash('sha256').update(canonicalJSON(keyPreimage(i)), 'utf8').digest('hex');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The inputs the `conformance-llm-cache-key` fixture pins (conformance/fixtures.md
|
|
60
|
+
* §The LLM cache-key fixture): the host's conformance mock is provider
|
|
61
|
+
* `conformance-mock`, model `conformance-mock-1`; the node sends one user message,
|
|
62
|
+
* no tools, no toolChoice, no responseSchema, and exactly the parameters in the
|
|
63
|
+
* run's `inputs.request.params`.
|
|
64
|
+
*/
|
|
65
|
+
export const FIXTURE = 'conformance-llm-cache-key';
|
|
66
|
+
export const NODE_ID = 'llm-call';
|
|
67
|
+
export const FIXTURE_PROVIDER = 'conformance-mock';
|
|
68
|
+
export const FIXTURE_MODEL = 'conformance-mock-1';
|
|
69
|
+
export function fixtureInputs(prompt: string, params: NonNullable<KeyInputs['params']>): KeyInputs {
|
|
70
|
+
return { model: FIXTURE_MODEL, provider: FIXTURE_PROVIDER, messages: [{ role: 'user', content: prompt }], params };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Judges. Each returns findings and asserts nothing; the scenario frames them with req(). ──
|
|
74
|
+
|
|
75
|
+
export interface Finding { readonly ok: boolean; readonly message: string }
|
|
76
|
+
const f = (ok: boolean, message: string): Finding => ({ ok, message });
|
|
77
|
+
|
|
78
|
+
/** The `cacheKey` member of the fixture node's provider-call record on GET /runs/{runId}/effects, or undefined. */
|
|
79
|
+
export interface LedgerKey { readonly recipe?: unknown; readonly digest?: unknown }
|
|
80
|
+
|
|
81
|
+
export function readLedgerKey(projection: unknown, nodeId: string): LedgerKey | undefined | 'no-record' {
|
|
82
|
+
const effects = (projection as { effects?: unknown } | null)?.effects;
|
|
83
|
+
if (!Array.isArray(effects)) return 'no-record';
|
|
84
|
+
const mine = effects.filter((e) => (e as { nodeId?: unknown } | null)?.nodeId === nodeId);
|
|
85
|
+
if (mine.length === 0) return 'no-record';
|
|
86
|
+
const withKey = mine.find((e) => (e as { cacheKey?: unknown }).cacheKey !== undefined) as { cacheKey?: LedgerKey } | undefined;
|
|
87
|
+
return withKey?.cacheKey;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const HEX64 = /^[0-9a-f]{64}$/;
|
|
91
|
+
|
|
92
|
+
export function judgeRecorded(key: LedgerKey | undefined): Finding[] {
|
|
93
|
+
return [
|
|
94
|
+
f(key !== undefined, 'the provider-call record MUST carry cacheKey when replay.llmCacheKeyRecipe is advertised'),
|
|
95
|
+
f(key?.recipe === RECIPE_2, `cacheKey.recipe MUST be "${RECIPE_2}" (got ${JSON.stringify(key?.recipe)})`),
|
|
96
|
+
f(typeof key?.digest === 'string' && HEX64.test(key.digest), `cacheKey.digest MUST be 64 lowercase hex characters (got ${JSON.stringify(key?.digest)})`),
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Two invocations that differ only in one named parameter MUST NOT share a key. */
|
|
101
|
+
export function judgeParamsInKey(a: LedgerKey | undefined, b: LedgerKey | undefined): Finding[] {
|
|
102
|
+
return [f(typeof a?.digest === 'string' && typeof b?.digest === 'string' && a.digest !== b.digest, `invocations differing only in maxTokens MUST have different keys (got ${String(a?.digest)} twice)`)];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The same inputs MUST give the same key. */
|
|
106
|
+
export function judgeStable(a: LedgerKey | undefined, b: LedgerKey | undefined): Finding[] {
|
|
107
|
+
return [f(typeof a?.digest === 'string' && a.digest === b?.digest, `identical invocations MUST have the same key (got ${String(a?.digest)} and ${String(b?.digest)})`)];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The host's key MUST equal the recipe computed from the inputs the fixture pins. */
|
|
111
|
+
export function judgePortable(key: LedgerKey | undefined, expected: KeyInputs): Finding[] {
|
|
112
|
+
const want = cacheKey2(expected);
|
|
113
|
+
return [f(key?.digest === want, `cacheKey.digest MUST equal recipe 2 over the fixture's inputs: expected ${want}, got ${String(key?.digest)} (preimage ${canonicalJSON(keyPreimage(expected))})`)];
|
|
114
|
+
}
|
|
@@ -41,6 +41,13 @@ describe('fixtures: workflow-definition schema validity', () => {
|
|
|
41
41
|
}
|
|
42
42
|
// The root schema is already registered under its $id above; compile via getSchema.
|
|
43
43
|
const validate = ajv.getSchema('workflow-definition.schema.json') ?? ajv.compile(JSON.parse(readFileSync(SCHEMA_PATH, 'utf8')));
|
|
44
|
+
// The v2 tree, for fixtures tagged `v2-only` (below). Loaded here rather than
|
|
45
|
+
// through lib/v2.ts, which imports the driver and would make this file host-bound.
|
|
46
|
+
const ajv2 = new Ajv2020({ allErrors: true, strict: false });
|
|
47
|
+
addFormats(ajv2);
|
|
48
|
+
const V2_DIR = join(SCHEMAS_DIR, 'v2');
|
|
49
|
+
for (const file of readdirSync(V2_DIR).filter((f) => f.endsWith('.schema.json'))) ajv2.addSchema(JSON.parse(readFileSync(join(V2_DIR, file), 'utf8')) as Record<string, unknown>);
|
|
50
|
+
const validateV2 = ajv2.getSchema('https://openwop.dev/spec/v2/workflow-definition.schema.json');
|
|
44
51
|
|
|
45
52
|
const files = readdirSync(FIXTURES_DIR)
|
|
46
53
|
.filter((f) => f.endsWith('.json')) // top-level only — sub-dirs hold non-workflow fixtures
|
|
@@ -53,8 +60,13 @@ describe('fixtures: workflow-definition schema validity', () => {
|
|
|
53
60
|
for (const file of files) {
|
|
54
61
|
it(`${file} validates against workflow-definition.schema.json`, () => {
|
|
55
62
|
const data = JSON.parse(readFileSync(join(FIXTURES_DIR, file), 'utf8'));
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
// A fixture tagged `v2-only` uses a construct the frozen v1 wire cannot
|
|
64
|
+
// carry (RFC 0258's `WorkflowEdge.loopback`), so it is judged by the v2
|
|
65
|
+
// schema instead. v1 is end-of-support (RFC 0234) and is never edited.
|
|
66
|
+
const v2Only = Array.isArray(data?.metadata?.tags) && data.metadata.tags.includes('v2-only');
|
|
67
|
+
const judge = v2Only && validateV2 ? validateV2 : validate;
|
|
68
|
+
const ok = v2Only && !validateV2 ? false : judge(data);
|
|
69
|
+
const errors = (judge.errors ?? [])
|
|
58
70
|
.map((e: ErrorObject) => `${e.instancePath || '/'}: ${e.message}`)
|
|
59
71
|
.join('\n');
|
|
60
72
|
expect(ok, req('openwop.it.fixtures-valid.validates-against-workflow-definition-schema-json', 'fixtures-valid.test.ts (no spec citation in file)', `Fixture ${file} fails workflow-definition schema:\n${errors}`)).toBe(true);
|