@openwop/openwop-conformance 1.152.0 → 1.154.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 +17 -0
- package/README.md +9 -0
- package/dist/cli.js +7 -2
- package/dist/lib/requirement-ids.js +100 -0
- package/dist/lib/scenario-disposition.js +22 -0
- package/package.json +33 -2
- package/requirement-aliases.json +4 -0
- package/requirements.json +23554 -0
- package/schemas/CORPUS-STAMP.json +99 -3
- package/src/cli.ts +7 -5
- package/src/global-setup.ts +13 -0
- package/src/lib/corpus-stamp.ts +125 -0
- package/src/lib/requirement-ids.ts +111 -0
- package/src/lib/scenario-disposition.ts +22 -0
- package/src/setup.ts +84 -5
- package/src/lib/capabilities-auth-subject-link.test.ts +0 -103
- package/src/lib/fork-availability.test.ts +0 -69
- package/src/lib/global-setup.test.ts +0 -76
- package/src/lib/grpc-framing.test.ts +0 -96
- package/src/lib/oidc-issuer.test.ts +0 -328
- package/src/lib/otel-collector-grpc.test.ts +0 -191
- package/src/lib/otel-collector.test.ts +0 -303
- package/src/lib/otlp-protobuf.test.ts +0 -461
- package/src/lib/polling.test.ts +0 -80
- package/src/lib/requirement-ledger.test.ts +0 -75
- package/src/lib/risk-disposition.test.ts +0 -91
- package/src/lib/saml-idp.test.ts +0 -127
- package/src/lib/spec-coherence-registry.test.ts +0 -155
- package/src/lib/webhook-receiver.test.ts +0 -144
- package/src/scenarios/artifact-schema-compile-bounded.test.ts +0 -126
- package/src/scenarios/artifact-type-legacy-ids.test.ts +0 -124
- package/src/scenarios/capability-example-root-layout.test.ts +0 -272
- package/src/scenarios/certification-floor-enforcement.test.ts +0 -204
- package/src/scenarios/chain-subchain-unsupported-refused.test.ts +0 -70
- package/src/scenarios/compensation-profile.test.ts +0 -340
- package/src/scenarios/core-manifest-and-extension-registry.test.ts +0 -250
- package/src/scenarios/discovery-canonical-family-no-shadow.test.ts +0 -219
- package/src/scenarios/edge-condition-truthy-falsy.test.ts +0 -108
- package/src/scenarios/effect-identity-composition.test.ts +0 -129
- package/src/scenarios/effect-identity-cross-scope.test.ts +0 -82
- package/src/scenarios/error-envelope-canonical-shape.test.ts +0 -64
- package/src/scenarios/form-content-packs.test.ts +0 -415
- package/src/scenarios/multi-region-effect-vocabulary.test.ts +0 -175
- package/src/scenarios/normative-example-extraction.test.ts +0 -242
- package/src/scenarios/openapi-asyncapi-sdk-parity.test.ts +0 -309
- package/src/scenarios/pack-manifest-extensions.test.ts +0 -203
- package/src/scenarios/protocol-version-grammar.test.ts +0 -119
- package/src/scenarios/registry-declarative-kinds.test.ts +0 -121
- package/src/scenarios/rfc-0147-self-audit.test.ts +0 -104
- package/src/scenarios/rfc-lifecycle-coherence.test.ts +0 -215
- package/src/scenarios/semantic-digest-v2.test.ts +0 -128
- package/src/scenarios/spec-corpus-validity.test.ts +0 -1727
- package/src/scenarios/spec-section-citations.test.ts +0 -132
- package/src/scenarios/tool-result-trust-monotone.test.ts +0 -168
- package/src/scenarios/versioned-composition-profiles.test.ts +0 -201
- package/src/scenarios/workflow-chain-internal-flag.test.ts +0 -84
- package/src/scenarios/workload-identity-profile.test.ts +0 -184
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "Provenance of this vendored schemas/ copy. See conformance/README.md
|
|
3
|
-
"suiteVersion": "1.
|
|
4
|
-
"corpusCommit": "
|
|
2
|
+
"_comment": "Provenance of this vendored schemas/ copy. See conformance/README.md §\"Resolving the contract\". Compare against the stamp in your installed @openwop/openwop-conformance to detect a stale hand-copied contract.",
|
|
3
|
+
"suiteVersion": "1.154.0",
|
|
4
|
+
"corpusCommit": "07f4573cca4a742445d1078773f422d28f986ee5",
|
|
5
|
+
"files": {
|
|
6
|
+
"api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
|
|
7
|
+
"api/asyncapi.yaml": "d5ecb9ee6114582be3b1f662c84bfac9ae96dae7bacb853e461168f70a8e1c7d",
|
|
8
|
+
"api/grpc/openwop.proto": "c3e72bb17cba514ee98feb6434e6c9b6ea6795bfd086489ec69fd882dd1ad977",
|
|
9
|
+
"api/openapi.yaml": "105e7bc27d216eb29fbb526b3548b5545711ec2a18c004bce20b08f5af38b750",
|
|
10
|
+
"api/redocly.yaml": "9f2374405b9ca58994ae076ada9fbe4dfb8265b77e4bf65331d1493192c0bd3a",
|
|
11
|
+
"schemas/README.md": "d9624e98ba3534b27fe4823227e8c2ac8df3ffd4bc4f57b4a023c91f5addefd6",
|
|
12
|
+
"schemas/a2a-task-state.schema.json": "c9365918f993f943b4b619d42551eb066a1ed33a08d895d51b395432a5b1f1bc",
|
|
13
|
+
"schemas/a2ui-surface-delta-frame.schema.json": "970d434e4fdcb925de64aaace91eb83bfbcfd8f76757a9ce9f339eda5fb3f4e8",
|
|
14
|
+
"schemas/agent-deployment-transition.schema.json": "1ac987fed0f380fa5b4e9a40e8e13316e5ae42d03a0ff42db4be6273e1bfe9f4",
|
|
15
|
+
"schemas/agent-deployment.schema.json": "0a71f0c7ab8cdf4fcac15e8652514f926cd84dfc7db32c47c9f3f3c9bd04f512",
|
|
16
|
+
"schemas/agent-eval-suite.schema.json": "d1aea50a48ddfe5ba8e25e9ddcc2c6e28eb1d953ef81b103518999a50e0967f7",
|
|
17
|
+
"schemas/agent-inventory-response.schema.json": "3a64dde3b95ece16ad73b95051ca99a1ca285f60255b5b506bed655676d82687",
|
|
18
|
+
"schemas/agent-manifest.schema.json": "b82738459792e24b14c4fe43622e8feb0df2250bda02054caa960c3556438b2c",
|
|
19
|
+
"schemas/agent-org-chart.schema.json": "2ca433503e76b29a70be35db22cce24bb698e6aa2f8b79c6fe6025f988f94fb0",
|
|
20
|
+
"schemas/agent-ref.schema.json": "b411bd51439e26d862393d546215d6b26d9349e6c9bdd92b353ca0fde2a51fb2",
|
|
21
|
+
"schemas/agent-roster-entry.schema.json": "3e0d8dfc0c66afb887bd05974a1ffbf79eece391255c09d2d0f7c5536aa219fd",
|
|
22
|
+
"schemas/agent-roster-response.schema.json": "1f6057bddd95d1cd31fdcef2fc87ae4560003c6e0c6d9e3fd1a148bac537c0b8",
|
|
23
|
+
"schemas/ai-envelope.schema.json": "78167a5767809286071bfcfcdfeceed0ed04c8b9f8c13079019d70eff4f351c1",
|
|
24
|
+
"schemas/annotation-create.schema.json": "567e8327777968e0ddab8e1547b4acd7beda734cf9d1fc6cde17037d8d9540d4",
|
|
25
|
+
"schemas/annotation.schema.json": "b26d22f9a97e1ae7c195e8799964aca804e78452a079ca3a0d61ea013907ce91",
|
|
26
|
+
"schemas/artifact-type-pack-manifest.schema.json": "13932ea58e819e01b3a0cc7a65590b70683dd5fe9208fc06c64c339943d3c4ce",
|
|
27
|
+
"schemas/audit-verify-result.schema.json": "e8fd013fb610ba5db27ab0113ec2eb18e7da5f9310dd3de2dd3dc87a259c21fe",
|
|
28
|
+
"schemas/budget-policy.schema.json": "3d4fe30ae129fe136ab9ece395f2a2a42a76360786b1b9a1d5f1cbc753eb5b39",
|
|
29
|
+
"schemas/capabilities.schema.json": "594d92806866886f3aefa7855e1ef5f33fec6459b06ef1ce6c07220c396b5661",
|
|
30
|
+
"schemas/certification-bundle-v2.schema.json": "3540232676086869ae78d24f3b9c2154f9c4a8a5eeb976512d6a7409f8cf19bb",
|
|
31
|
+
"schemas/channel-presence-payload.schema.json": "f9d0ef4219814c2b765baf3e68cbe5eea2cb42379f516a93a0459dcd5f7db47e",
|
|
32
|
+
"schemas/channel-written-payload.schema.json": "54723a56bc5a35232f64544ca686ac1ac959a5fb05b32b2401facca6b3ef51a3",
|
|
33
|
+
"schemas/chat-card-pack-manifest.schema.json": "402f7e9081cc39857a52e772061f4e034a957dcbc17501fe6e8e0bcb3d337e42",
|
|
34
|
+
"schemas/compact-tool-descriptor.schema.json": "4856d52aed0438d366b4328e53e4d248126e46f57137ad25fb04c3cc22bba1be",
|
|
35
|
+
"schemas/compensation-policy.schema.json": "6038bf939a982be1105d92b8a0bb70fae936b49d5e8187e1215660d265e2c382",
|
|
36
|
+
"schemas/conformance-certification-bundle.schema.json": "30a1f7fc35ed06098b7abb6fcf3b6f46b5716f29fbae35e0de1247f1f8cbe54c",
|
|
37
|
+
"schemas/connection-pack-manifest.schema.json": "35aca9fc3b3c8322babebe77ef77a8033673254f948025fb2f37191f4cfd05b5",
|
|
38
|
+
"schemas/conversation-event.schema.json": "bfcb88ce8dfe4dd7a3a40e56519a2833c7d25487fd2ab4104f6a23195120c0d9",
|
|
39
|
+
"schemas/conversation-turn.schema.json": "7c025df336aef9456d07f3a1a6959fc6d1c6fa62859e884354d71efee705ca3c",
|
|
40
|
+
"schemas/core-conformance-mock-agent-config.schema.json": "2253e1fb0cd8823ef2bfbae9b957ac374a7b617f62c1eb15d819e3536296feb3",
|
|
41
|
+
"schemas/credential-provenance.schema.json": "7675889469a67c68f31b8d0b28fd0b0a3c837f21ef51ce3b3c676064d3f58597",
|
|
42
|
+
"schemas/credential-reference.schema.json": "14927f095c235367a0645cc4811f4cfcfa3bed152bf05b970881831c62178b56",
|
|
43
|
+
"schemas/debug-bundle.schema.json": "8bd626e4cfbdaa774231e7182f61f2b5a4a42c3afeb8672c8388ea41e3fb7f2e",
|
|
44
|
+
"schemas/dispatch-config.schema.json": "131767797315e980f182b2480a1e2ed562d3326a2db08628704273f5b38c9e26",
|
|
45
|
+
"schemas/envelopes/clarification.request.schema.json": "3b3520a9ca2e40b4fb54b3a8741b2d3f2d414a0dfc004ba7c71dcf3786c32603",
|
|
46
|
+
"schemas/envelopes/error.schema.json": "845261720e8398975042ddfe78012491e42f8a39ed195b524c389cc9bc25f5c0",
|
|
47
|
+
"schemas/envelopes/media.audio.schema.json": "6c23f7398558ed781a36295c980a7ca9c585cc51faac8dd0ea56973be9dc96df",
|
|
48
|
+
"schemas/envelopes/media.file.schema.json": "3eb9ba4d3727a86640d82d566dca4fbcc06f6f0f3f80cfd100be869e9739d0b4",
|
|
49
|
+
"schemas/envelopes/media.image.schema.json": "1517b2036450226a20a55c223583d3cb2b606d157698a0806e00ca9ea1ea413e",
|
|
50
|
+
"schemas/envelopes/schema.request.schema.json": "16f6523778bdae67c715c10b0eb61fb8c7abaed4673d888957d6cc46f604be8c",
|
|
51
|
+
"schemas/envelopes/schema.response.schema.json": "35323b628db30a2b51efb66192f8f1219e184afa130126395524165a98539c6b",
|
|
52
|
+
"schemas/envelopes/ui.a2ui-surface.schema.json": "68f977c11b9bb40435a9c5727b66d76275f8dcfa0b380086622a837c84b8c5e4",
|
|
53
|
+
"schemas/error-envelope.schema.json": "1f2addeabbaa3cd7957c77816a1609b0c5005e0730cff78162bbc2804ff7c4aa",
|
|
54
|
+
"schemas/eval-summary.schema.json": "cda7ea443069f5cfd088cd000dccb4ea8b6e8268cfcc3af740295241bf77fa10",
|
|
55
|
+
"schemas/export-bundle.schema.json": "1ed674786293fc33f3474fb93cd3e5a1d8f85d0cb09636a09856bd8671e62c90",
|
|
56
|
+
"schemas/form-content-pack-manifest.schema.json": "bd373116c8912e2cd8a0e4df1f7a56086c0fdae472320f695552dd46b1b48a45",
|
|
57
|
+
"schemas/frontend-plugin-manifest.schema.json": "33d4e4eea5753900669c7ed6416e314376395a0256a9d656bf03a18845c16e84",
|
|
58
|
+
"schemas/goal.schema.json": "66aab339cda217318dab40e40ea0b46ea949164e7f2482e865284d6b2e3383db",
|
|
59
|
+
"schemas/heartbeat-evaluated.schema.json": "d3caa3fae3d935cc782c2359afa2d6bf11feb1ce9d71b9345b8e5716a9348c61",
|
|
60
|
+
"schemas/heartbeat-state-changed.schema.json": "6b66086582436ac72bd929d2830d0539d932a41b8ddf90263e2b8ca903d60584",
|
|
61
|
+
"schemas/localized-content-language-settings.schema.json": "6ba0e2918e08756493648a64d6169829eed0ead91fb6c3f2a76dec5c451b2d77",
|
|
62
|
+
"schemas/localized-content-page-response.schema.json": "3fd47db2484c40417ab7a555ec42bf443d48deca0c38ecdbe008a3bdcd1a6809",
|
|
63
|
+
"schemas/localized-content-page.schema.json": "2ca2d591d197562c4bf668b338e47803256cfa0c9105abdf64bd79e8469f15a8",
|
|
64
|
+
"schemas/localized-content-section.schema.json": "3e183eb2e80b985dc9b3770e97865247059d32ffbab2957f4a5c0addfdfedc2e",
|
|
65
|
+
"schemas/memory-entry.schema.json": "f5d806c934e900a979e14ccd961224d55eede954ffd10c5369f4cb97654e142b",
|
|
66
|
+
"schemas/memory-list-options.schema.json": "fd3e61c0caf984c6cf14f8acf55c0e84b6b579d62a702be45e20dbedfc9fffce",
|
|
67
|
+
"schemas/node-pack-manifest.schema.json": "3c7812fc8aae840edd8afe6d51fcb58571a50008e0b7ade4b7c5d55e8da9fcb8",
|
|
68
|
+
"schemas/orchestrator-decision.schema.json": "9dbcc9e7595df1781db91b1e8764fec67feca7d232b07d151094400aa53750e7",
|
|
69
|
+
"schemas/org-chart-responsibility-view.schema.json": "9bf2cc7b2ac8b6d7c28d360e8263da5706f691c364c30d20f16fd46b21fdd278",
|
|
70
|
+
"schemas/pack-lockfile.schema.json": "e6c6fd6c042485a3a16c5aedf184a3304b3068c6dd0cf8a22cba1e2e58781556",
|
|
71
|
+
"schemas/prompt-kind.schema.json": "fcf4305b430dc9d2d34b6f2971f4185fd2ed7f617013720d975bfc3ab665e000",
|
|
72
|
+
"schemas/prompt-pack-manifest.schema.json": "d473d827797ce6c9fe4fe03e439840a9dbc78d899bfb9ff05ff3e6a747380977",
|
|
73
|
+
"schemas/prompt-ref.schema.json": "2fffd62cb27c36ddfa32363922b2cb5339091716112c54ded32c21a8c26398bb",
|
|
74
|
+
"schemas/prompt-template.schema.json": "54fc1f0d22b938d171dff214f3d446ae911ef0c3cdb2056929b93d01248e0969",
|
|
75
|
+
"schemas/proposal.schema.json": "472ac8c6bcbb4e34ae8fe7a0757567de4875e69aa5058dac4dc9226ddba6c731",
|
|
76
|
+
"schemas/registry-version-manifest.schema.json": "437890063087beab3cadbdb0dae354e9f92a3ebbecdd90a1d0487df71d34ed93",
|
|
77
|
+
"schemas/residency.schema.json": "b45fdc91ae88091251914dbd6787a1e2ce687580b91d31a759217b7a1f0010c0",
|
|
78
|
+
"schemas/run-ancestry-response.schema.json": "963a5763b03216aa72fc2854560c778e53994f8cb06ea9a4053e541273c87f5c",
|
|
79
|
+
"schemas/run-diff-response.schema.json": "d9d196730b5d5d8a04a591d21a8fc6b44be7fa134f11827ff7e4f1a86d47e83d",
|
|
80
|
+
"schemas/run-event-payloads.schema.json": "6907c2a388d27169756781e1a5771e411238b465d9f0917698cde25dde6bccae",
|
|
81
|
+
"schemas/run-event.schema.json": "c70c06932b9831295042836213e617c8e946f20ddd49cf4e889b74d89d2175a8",
|
|
82
|
+
"schemas/run-options.schema.json": "f8241ece6ddc6527de9160d811d42c231b4f77b935f2e107b3c05608545c59ab",
|
|
83
|
+
"schemas/run-orchestrator-decided-event.schema.json": "17a43a3c44680a24d411141357a867b3caed6107d56de88fe2e062eaf93fa2c1",
|
|
84
|
+
"schemas/run-snapshot.schema.json": "05c406120e1958984ac7e06deff369f2df9f4d7b72ced9b624c327c1c86999e8",
|
|
85
|
+
"schemas/security-advisory.schema.json": "9c550f0bf10a3193dcca4a127081fb66fe5dc1b6f729c4c17bfaea3ba3ce05fc",
|
|
86
|
+
"schemas/self-hosted-runner-dispatch-frame.schema.json": "3f1d7edd572c6747657299525d8e85329b93b6fadf670e3a19c74d53e725adec",
|
|
87
|
+
"schemas/self-hosted-runner-registration.schema.json": "6c781adf1bb915a9e47796494f4a527353cbbf72b480e5a05be4aac65a6d0c6e",
|
|
88
|
+
"schemas/self-hosted-runner-result-frame.schema.json": "16f2f711b6071c797d8d4065f6f5ff39102ff03890fefce7a88a669831566296",
|
|
89
|
+
"schemas/suspend-request.schema.json": "1aa39f2a947934bedc9f9696bf45fcb6119395c580d5ca282efb9443bd94a7bb",
|
|
90
|
+
"schemas/tool-descriptor.schema.json": "7f6adbae63003c1f1311f6a436f7f286542be62aa19a27b46730f2c2810857f7",
|
|
91
|
+
"schemas/trigger-event.schema.json": "59d63b79dca3593f45d281776cbfe43ae4d85ee25519b82ad59f162a9efb592c",
|
|
92
|
+
"schemas/trigger-subscription-registration.schema.json": "929d628bad08e1def696e47e9a148aba00fa801e28d49bbd0dc2360b08673435",
|
|
93
|
+
"schemas/trigger-subscription.schema.json": "4f7b067f20032073b140c4bb15dc9a9914c4f6421a252ff1ff261cbdd8edf270",
|
|
94
|
+
"schemas/ui-plugin-message.schema.json": "346638babf1f8ea7b161cde75c0038b518cd2cc5bf0631772e20a9b74c96b5dd",
|
|
95
|
+
"schemas/workflow-chain-pack-manifest.schema.json": "c027571913c44d8d9678750c72701a710a02fa0df55e86849da03e13e95c333a",
|
|
96
|
+
"schemas/workflow-definition.schema.json": "28b050ffe22121796c9d4a5a8c7e57c901bb283bd08d3fd3ff2ea25a898108c1",
|
|
97
|
+
"schemas/workload-identity.schema.json": "64ed93e12cc9f0657e56ff9b8f045e5d68d397df5276d103c07f42c7f34f0579",
|
|
98
|
+
"schemas/workspace-file-create.schema.json": "70a23b92c2958c0165d7d29aea9020a045dc7816af82c80c5f50d0aac19c4856",
|
|
99
|
+
"schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4"
|
|
100
|
+
}
|
|
5
101
|
}
|
package/src/cli.ts
CHANGED
|
@@ -195,7 +195,7 @@ Required (unless --offline):
|
|
|
195
195
|
--api-key <key> Bearer-style API key (or set OPENWOP_API_KEY env var)
|
|
196
196
|
|
|
197
197
|
Filtering:
|
|
198
|
-
--offline Run only the server-free subset (fixtures
|
|
198
|
+
--offline Run only the declared server-free subset (fixtures-valid; see README §"--offline")
|
|
199
199
|
--filter <pattern> Pass through to vitest --testNamePattern
|
|
200
200
|
|
|
201
201
|
Implementation labels (cosmetic — surface in failure messages):
|
|
@@ -656,10 +656,12 @@ async function main(): Promise<never> {
|
|
|
656
656
|
// the conformance package is used as a workspace member.
|
|
657
657
|
const vitestArgs: string[] = ['run', '--config', resolvePath(conformanceRoot, 'vitest.config.ts')];
|
|
658
658
|
if (args.offline) {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
)
|
|
659
|
+
// Suite 1.154.0: the offline set is a DECLARED property of the package —
|
|
660
|
+
// exactly the server-free scenarios that ship in the tarball and run in
|
|
661
|
+
// the published layout. `spec-corpus-validity.test.ts` left the set: it is
|
|
662
|
+
// a corpus-coherence scenario (reads spec/v1, asserts nothing about a host)
|
|
663
|
+
// and is no longer packed. See conformance/README.md §"--offline".
|
|
664
|
+
vitestArgs.push('src/scenarios/fixtures-valid.test.ts');
|
|
663
665
|
}
|
|
664
666
|
if (args.filter) {
|
|
665
667
|
vitestArgs.push('--testNamePattern', args.filter);
|
package/src/global-setup.ts
CHANGED
|
@@ -56,6 +56,8 @@ const MAX_ROWS = 40;
|
|
|
56
56
|
// at runtime, so this costs nothing and makes the compiler the oracle: rename
|
|
57
57
|
// a ledger field and this file stops building instead of silently degrading.
|
|
58
58
|
import type { LedgerEntry } from './lib/requirement-ledger.js';
|
|
59
|
+
import { LAYOUT, PKG_ROOT_PATH } from './lib/paths.js';
|
|
60
|
+
import { describeVerdict, verifyCorpusStamp } from './lib/corpus-stamp.js';
|
|
59
61
|
|
|
60
62
|
/** What a JSONL line parses to before validation — every field may be absent. */
|
|
61
63
|
type LedgerLine = Partial<Record<keyof LedgerEntry, unknown>>;
|
|
@@ -64,6 +66,17 @@ let ownedDir: string | null = null;
|
|
|
64
66
|
let ledgerPath: string | null = null;
|
|
65
67
|
|
|
66
68
|
export function setup(): void {
|
|
69
|
+
// Suite 1.154.0 — the vendored contract must be the one this suite shipped.
|
|
70
|
+
// In the published layout every api/ + schemas/ file is checked against the
|
|
71
|
+
// SHA-256 map pack-vendor.sh wrote into schemas/CORPUS-STAMP.json; a mismatch
|
|
72
|
+
// aborts the run (a suite validating a host against a schema it did not ship
|
|
73
|
+
// is evidence about nothing). Repo layout: nothing vendored, nothing to check,
|
|
74
|
+
// and the log line says so rather than implying a pass.
|
|
75
|
+
const stamp = verifyCorpusStamp(PKG_ROOT_PATH, LAYOUT);
|
|
76
|
+
process.stderr.write(`${describeVerdict(stamp)}\n`);
|
|
77
|
+
if (stamp.kind === 'mismatch') {
|
|
78
|
+
throw new Error('openwop-conformance: refusing to run — schemas/CORPUS-STAMP.json digests do not match the vendored api/ + schemas/ files. Reinstall the package; do not hand-patch vendored contract files.');
|
|
79
|
+
}
|
|
67
80
|
if (process.env['OPENWOP_DISPOSITION_SUMMARY'] === 'false') return;
|
|
68
81
|
// `--certify` already routes the ledger to its own report dir and reads it
|
|
69
82
|
// there. Never take it over — this exists only for the path that had no
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus-stamp digest verification (v2 charter Phase 1, suite 1.154.0).
|
|
3
|
+
*
|
|
4
|
+
* The published suite vendors `api/` and `schemas/` at `prepack` and writes
|
|
5
|
+
* `schemas/CORPUS-STAMP.json` (RFC 0145 G2) — until 1.154.0 that stamp carried
|
|
6
|
+
* only `suiteVersion` + `corpusCommit`, so a tarball whose vendored files had
|
|
7
|
+
* been altered, partially copied, or hand-patched after install still read as
|
|
8
|
+
* "the 1.x contract". That is the generated-surface-drift class the charter's
|
|
9
|
+
* §A names (`@openwop/openwop-conformance@1.138.1` on npm was not the 1.138.1 in
|
|
10
|
+
* the tree), one layer down: the package identified its corpus by a commit
|
|
11
|
+
* hash it could not check.
|
|
12
|
+
*
|
|
13
|
+
* From 1.154.0 the stamp also carries `files`: a map of every vendored path
|
|
14
|
+
* (relative to the package root) to its SHA-256. In the PUBLISHED layout the
|
|
15
|
+
* suite verifies the map at global setup and refuses to run on a mismatch —
|
|
16
|
+
* a suite that validated a host against a schema it did not ship would be
|
|
17
|
+
* evidence about nothing. In the REPO layout (a spec checkout) there is no
|
|
18
|
+
* vendored copy and nothing to verify; the check is skipped and says so.
|
|
19
|
+
*
|
|
20
|
+
* Three outcomes, never folded (OK / FAIL / UNKNOWN): `verified` when every
|
|
21
|
+
* digest matches; `mismatch` (throws) when any file is missing or altered;
|
|
22
|
+
* `not-applicable` when the layout is not published or the stamp predates
|
|
23
|
+
* 1.154.0 (older tarballs cannot be verified and the run says so rather than
|
|
24
|
+
* pretending).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { createHash } from 'node:crypto';
|
|
28
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
29
|
+
import { join, relative, sep } from 'node:path';
|
|
30
|
+
|
|
31
|
+
export interface CorpusStamp {
|
|
32
|
+
readonly suiteVersion?: string;
|
|
33
|
+
readonly corpusCommit?: string;
|
|
34
|
+
/** `<relative path>` → lowercase hex SHA-256. Present from suite 1.154.0. */
|
|
35
|
+
readonly files?: Readonly<Record<string, string>>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type StampVerdict =
|
|
39
|
+
| { readonly kind: 'verified'; readonly files: number }
|
|
40
|
+
| { readonly kind: 'not-applicable'; readonly reason: string }
|
|
41
|
+
| { readonly kind: 'mismatch'; readonly missing: readonly string[]; readonly altered: readonly string[]; readonly extra: readonly string[] };
|
|
42
|
+
|
|
43
|
+
export const STAMP_RELATIVE_PATH = join('schemas', 'CORPUS-STAMP.json');
|
|
44
|
+
|
|
45
|
+
/** SHA-256 of a file's bytes, lowercase hex. */
|
|
46
|
+
export function sha256File(path: string): string {
|
|
47
|
+
return createHash('sha256').update(readFileSync(path)).digest('hex');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Every regular file under `dir`, as package-relative POSIX paths, sorted. */
|
|
51
|
+
export function listVendoredFiles(root: string, dirs: readonly string[] = ['api', 'schemas']): string[] {
|
|
52
|
+
const out: string[] = [];
|
|
53
|
+
const walk = (d: string): void => {
|
|
54
|
+
if (!existsSync(d)) return;
|
|
55
|
+
for (const name of readdirSync(d).sort()) {
|
|
56
|
+
const p = join(d, name);
|
|
57
|
+
if (statSync(p).isDirectory()) walk(p);
|
|
58
|
+
else out.push(relative(root, p).split(sep).join('/'));
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
for (const d of dirs) walk(join(root, d));
|
|
62
|
+
return out.filter((p) => p !== STAMP_RELATIVE_PATH.split(sep).join('/')).sort();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Build the `files` map for a package root (used by pack-vendor at prepack). */
|
|
66
|
+
export function digestVendoredFiles(root: string): Record<string, string> {
|
|
67
|
+
const map: Record<string, string> = {};
|
|
68
|
+
for (const rel of listVendoredFiles(root)) map[rel] = sha256File(join(root, ...rel.split('/')));
|
|
69
|
+
return map;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Compare the stamp's `files` map against the bytes on disk. Pure: reads,
|
|
74
|
+
* never writes, never throws on a mismatch — the caller decides how loud.
|
|
75
|
+
*/
|
|
76
|
+
export function verifyCorpusStamp(root: string, layout: string): StampVerdict {
|
|
77
|
+
if (layout !== 'published') {
|
|
78
|
+
return { kind: 'not-applicable', reason: `layout is '${layout}' — the vendored copy exists only in the published package` };
|
|
79
|
+
}
|
|
80
|
+
const stampPath = join(root, STAMP_RELATIVE_PATH);
|
|
81
|
+
if (!existsSync(stampPath)) {
|
|
82
|
+
return { kind: 'not-applicable', reason: 'schemas/CORPUS-STAMP.json is absent — not a packed suite' };
|
|
83
|
+
}
|
|
84
|
+
let stamp: CorpusStamp;
|
|
85
|
+
try {
|
|
86
|
+
stamp = JSON.parse(readFileSync(stampPath, 'utf8')) as CorpusStamp;
|
|
87
|
+
} catch (e) {
|
|
88
|
+
return { kind: 'mismatch', missing: [STAMP_RELATIVE_PATH], altered: [], extra: [] };
|
|
89
|
+
}
|
|
90
|
+
if (stamp.files === undefined) {
|
|
91
|
+
return { kind: 'not-applicable', reason: `stamp ${stamp.suiteVersion ?? '?'} predates per-file digests (suite < 1.154.0); the vendored contract cannot be verified` };
|
|
92
|
+
}
|
|
93
|
+
const expected = stamp.files;
|
|
94
|
+
const onDisk = new Set(listVendoredFiles(root));
|
|
95
|
+
const missing: string[] = [];
|
|
96
|
+
const altered: string[] = [];
|
|
97
|
+
for (const [rel, digest] of Object.entries(expected).sort()) {
|
|
98
|
+
const p = join(root, ...rel.split('/'));
|
|
99
|
+
if (!existsSync(p)) {
|
|
100
|
+
missing.push(rel);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (sha256File(p) !== digest) altered.push(rel);
|
|
104
|
+
onDisk.delete(rel);
|
|
105
|
+
}
|
|
106
|
+
const extra = [...onDisk].sort();
|
|
107
|
+
if (missing.length > 0 || altered.length > 0 || extra.length > 0) return { kind: 'mismatch', missing, altered, extra };
|
|
108
|
+
return { kind: 'verified', files: Object.keys(expected).length };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** One-line, greppable rendering for the run log. */
|
|
112
|
+
export function describeVerdict(v: StampVerdict): string {
|
|
113
|
+
switch (v.kind) {
|
|
114
|
+
case 'verified':
|
|
115
|
+
return `[openwop-conformance] corpus stamp VERIFIED — ${v.files} vendored api/ + schemas/ files match their SHA-256 digests`;
|
|
116
|
+
case 'not-applicable':
|
|
117
|
+
return `[openwop-conformance] corpus stamp not checked — ${v.reason}`;
|
|
118
|
+
case 'mismatch':
|
|
119
|
+
return (
|
|
120
|
+
`[openwop-conformance] corpus stamp MISMATCH — the vendored contract is not the one this suite shipped ` +
|
|
121
|
+
`(missing ${v.missing.length}, altered ${v.altered.length}, extra ${v.extra.length}): ` +
|
|
122
|
+
[...v.missing.map((f) => `missing ${f}`), ...v.altered.map((f) => `altered ${f}`), ...v.extra.map((f) => `extra ${f}`)].slice(0, 12).join('; ')
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-`it` requirement ids (RFC 0148 §A, gap G3; v2 charter Phase 1).
|
|
3
|
+
*
|
|
4
|
+
* The ledger has recorded ONE row per scenario FILE (`openwop.floor.<file>` /
|
|
5
|
+
* `openwop.scenario.<file>`). That granularity is what produces certification
|
|
6
|
+
* gap G8: a file that asserted a positive control and then soft-skipped the
|
|
7
|
+
* requirement still resolves to `executed-pass` for the whole file. The durable
|
|
8
|
+
* fix named in `scenario-disposition.ts` is per-`it` recording — this module
|
|
9
|
+
* supplies the ids for it.
|
|
10
|
+
*
|
|
11
|
+
* Why the id derives from the TEST TITLE and not from the `driver.describe`
|
|
12
|
+
* text: 190 of 1,756 citations interpolate the requirement text at run time
|
|
13
|
+
* (`docs/REQUIREMENT-REGISTRY-FEASIBILITY.md`), so no text-derived id can be
|
|
14
|
+
* stable for them. Test titles are literals in every scenario file, stable
|
|
15
|
+
* across hosts, and already the unit vitest reports on. The registry
|
|
16
|
+
* (`conformance/requirements.json`, generated) maps each id back to the
|
|
17
|
+
* citations found inside that test's body, so a bundle reader can still ask
|
|
18
|
+
* "which spec section did this row witness".
|
|
19
|
+
*
|
|
20
|
+
* Grammar: `openwop.it.<file-stem>.<title-slug>` where the stem is the scenario
|
|
21
|
+
* file name without `.test.ts` and the slug is the title lower-cased, every run
|
|
22
|
+
* of non-alphanumerics folded to one `-`, trimmed, and capped at 80 characters.
|
|
23
|
+
* A second test in the same file whose title slugs identically gets `~2`,
|
|
24
|
+
* `~3`, … (vitest allows duplicate titles; the ledger does not allow duplicate
|
|
25
|
+
* ids). A scenario MAY override the derived id for the current test with
|
|
26
|
+
* `req()` when it wants a hand-authored, registry-listed id.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export const IT_ID_PREFIX = 'openwop.it.';
|
|
30
|
+
export const IT_ID_GRAMMAR = /^openwop\.it\.[a-z0-9]+(?:-[a-z0-9]+)*\.[a-z0-9]+(?:-[a-z0-9]+)*(?:~[2-9][0-9]*)?$/;
|
|
31
|
+
|
|
32
|
+
const MAX_SLUG = 80;
|
|
33
|
+
|
|
34
|
+
/** Title → slug: lower-case, non-alphanumeric runs → `-`, trimmed, capped. */
|
|
35
|
+
export function slugTitle(title: string): string {
|
|
36
|
+
const s = title
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
39
|
+
.replace(/^-+|-+$/g, '');
|
|
40
|
+
const capped = s.length > MAX_SLUG ? s.slice(0, MAX_SLUG).replace(/-+$/g, '') : s;
|
|
41
|
+
return capped.length > 0 ? capped : 'untitled';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** `auth-subject-link.test.ts` → `auth-subject-link`. */
|
|
45
|
+
export function fileStem(scenarioFile: string): string {
|
|
46
|
+
return scenarioFile.replace(/\.test\.ts$/, '');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Derived id for one test, before collision suffixing. */
|
|
50
|
+
export function itRequirementId(scenarioFile: string, title: string): string {
|
|
51
|
+
return `${IT_ID_PREFIX}${fileStem(scenarioFile)}.${slugTitle(title)}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The scenario file a per-`it` id belongs to, or null when the id is not one. */
|
|
55
|
+
export function scenarioFileOfItId(requirementId: string): string | null {
|
|
56
|
+
if (!requirementId.startsWith(IT_ID_PREFIX)) return null;
|
|
57
|
+
const rest = requirementId.slice(IT_ID_PREFIX.length);
|
|
58
|
+
const dot = rest.indexOf('.');
|
|
59
|
+
if (dot <= 0) return null;
|
|
60
|
+
return `${rest.slice(0, dot)}.test.ts`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Collision suffixing: the first occurrence keeps the bare id, later ones get
|
|
65
|
+
* `~2`, `~3`, … Keyed per file so a worker running many files never crosses
|
|
66
|
+
* streams. Reset per file by `setup.ts` when the file finishes.
|
|
67
|
+
*/
|
|
68
|
+
export class ItIdAllocator {
|
|
69
|
+
private readonly seen = new Map<string, number>();
|
|
70
|
+
|
|
71
|
+
allocate(scenarioFile: string, title: string): string {
|
|
72
|
+
const base = itRequirementId(scenarioFile, title);
|
|
73
|
+
const n = (this.seen.get(base) ?? 0) + 1;
|
|
74
|
+
this.seen.set(base, n);
|
|
75
|
+
return n === 1 ? base : `${base}~${n}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
reset(): void {
|
|
79
|
+
this.seen.clear();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Explicit override for the current test.
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
let explicitId: string | null = null;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Attach a hand-authored requirement id to the CURRENT test. The per-`it`
|
|
91
|
+
* ledger row for this test is recorded under `id` instead of the derived
|
|
92
|
+
* title id. Returns the usual `driver.describe`-style message so the call
|
|
93
|
+
* doubles as the assertion message:
|
|
94
|
+
*
|
|
95
|
+
* expect(x, req('openwop.auth.subject-link.leaver-deny', 'auth-profiles.md §Subject linking', 'deactivation MUST deny')).toBe(true)
|
|
96
|
+
*
|
|
97
|
+
* The id MUST be listed in `conformance/requirements.json` (the generator
|
|
98
|
+
* collects `req(` first-argument literals); an unlisted id fails the registry
|
|
99
|
+
* check, so a hand id cannot drift from the registry silently.
|
|
100
|
+
*/
|
|
101
|
+
export function req(id: string, specSection: string, requirement: string): string {
|
|
102
|
+
explicitId = id;
|
|
103
|
+
return `${specSection}: ${requirement}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** setup.ts reads and clears the override after each test. */
|
|
107
|
+
export function takeExplicitRequirementId(): string | null {
|
|
108
|
+
const id = explicitId;
|
|
109
|
+
explicitId = null;
|
|
110
|
+
return id;
|
|
111
|
+
}
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* host or a vitest subprocess.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
+
import { scenarioFileOfItId } from './requirement-ids.js';
|
|
29
30
|
import { PROFILE_FLOOR_SCENARIOS } from './profiles.js';
|
|
30
31
|
import { requirementIdForScenario, requirementIdForPrefix, requirementsFor } from './requirement-registry.js';
|
|
31
32
|
import { UNCLASSIFIED_RETURN_DETAIL } from './soft-skip.js';
|
|
@@ -285,6 +286,27 @@ export function deriveRequirementDispositions(
|
|
|
285
286
|
rows.push(row);
|
|
286
287
|
}
|
|
287
288
|
|
|
289
|
+
// Per-`it` rows (suite 1.153.0): every ledger entry keyed `openwop.it.<file>.<slug>`
|
|
290
|
+
// becomes its own bundle row, attributed to its scenario file. Additive — the
|
|
291
|
+
// file-level and prefix rows above are unchanged, and the floors still key on
|
|
292
|
+
// them. This is the granularity RFC 0148 §A describes and the G8 fix.
|
|
293
|
+
const emitted = new Set(rows.map((r) => r.requirementId));
|
|
294
|
+
for (const e of [...ledger].sort((a, b) => a.requirementId.localeCompare(b.requirementId))) {
|
|
295
|
+
const file = scenarioFileOfItId(e.requirementId);
|
|
296
|
+
// Attribute only to files this run reported on: a worker's ledger can carry
|
|
297
|
+
// rows from files outside the certified set (the suite's own lib tests, or
|
|
298
|
+
// a filtered run), and those are not evidence about the host.
|
|
299
|
+
if (file === null || emitted.has(e.requirementId) || !reportStates.has(file)) continue;
|
|
300
|
+
emitted.add(e.requirementId);
|
|
301
|
+
rows.push({
|
|
302
|
+
requirementId: e.requirementId,
|
|
303
|
+
scenarioId: file,
|
|
304
|
+
disposition: e.disposition,
|
|
305
|
+
...(e.detail === undefined ? {} : { detail: e.detail }),
|
|
306
|
+
...(e.assertionCount === undefined ? {} : { assertionCount: e.assertionCount }),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
288
310
|
const totals = { executedPass: 0, executedFail: 0, skipped: 0, inapplicable: 0, blocked: 0 };
|
|
289
311
|
for (const r of rows) {
|
|
290
312
|
if (r.disposition === 'executed-pass') totals.executedPass++;
|
package/src/setup.ts
CHANGED
|
@@ -30,11 +30,13 @@ import { setMultiAgentCapabilities } from './lib/multi-agent-capabilities.js';
|
|
|
30
30
|
import { OtelCollector, setCollector } from './lib/otel-collector.js';
|
|
31
31
|
import { McpFakeServer, setMcpFakeServer } from './lib/mcp-fake-server.js';
|
|
32
32
|
import { A2AFakePeer, setA2AFakePeer } from './lib/a2a-fake-peer.js';
|
|
33
|
-
import { afterAll, afterEach, beforeAll, expect } from 'vitest';
|
|
33
|
+
import { afterAll, afterEach, beforeAll, beforeEach, expect } from 'vitest';
|
|
34
34
|
import { basename } from 'node:path';
|
|
35
35
|
import { recordRequirement, hasRequirement, journalLength, journalSince } from './lib/requirement-ledger.js';
|
|
36
36
|
import { requirementIdForFile, resolveFileRecord, type FileTestState } from './lib/scenario-disposition.js';
|
|
37
37
|
import { softSkipDisposition } from './lib/soft-skip.js';
|
|
38
|
+
import { ItIdAllocator, takeExplicitRequirementId } from './lib/requirement-ids.js';
|
|
39
|
+
import { SPEC_COHERENCE_SCENARIOS, SPEC_COHERENCE_DETAIL } from './lib/spec-coherence.js';
|
|
38
40
|
import type { DiscoveryPayload } from './lib/profiles.js';
|
|
39
41
|
|
|
40
42
|
const SUITE_INIT_TIMEOUT_MS = 5_000;
|
|
@@ -225,6 +227,22 @@ await maybeStartA2AFakePeer();
|
|
|
225
227
|
const _fileStates = new Map<string, FileTestState[]>();
|
|
226
228
|
const _fileAssertions = new Map<string, number>();
|
|
227
229
|
const _ledgerMarks = new Map<string, number>();
|
|
230
|
+
// Per-`it` recording (v2 charter Phase 1, suite 1.153.0 — the durable G8 fix
|
|
231
|
+
// named in scenario-disposition.ts). Each test gets its own ledger row under
|
|
232
|
+
// `openwop.it.<file>.<title-slug>` so a file that asserted a positive control
|
|
233
|
+
// and soft-skipped the requirement no longer certifies the requirement. The
|
|
234
|
+
// file-level row is RETAINED (floors key on it); the per-`it` rows are
|
|
235
|
+
// additive bundle rows (RFC 0148 §C `requirements[]` accepts any id).
|
|
236
|
+
const _itAllocators = new Map<string, ItIdAllocator>();
|
|
237
|
+
const _itMarks = new Map<string, number>();
|
|
238
|
+
const _itAssertionsBefore = new Map<string, number>();
|
|
239
|
+
function _assertionCalls(): number {
|
|
240
|
+
try {
|
|
241
|
+
return (expect.getState() as { assertionCalls?: number }).assertionCalls ?? 0;
|
|
242
|
+
} catch {
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
228
246
|
function _fileOf(task: { file?: { filepath?: string; name?: string } } | undefined): string | null {
|
|
229
247
|
const f = task?.file?.filepath ?? task?.file?.name;
|
|
230
248
|
return typeof f === 'string' && f.length > 0 ? basename(f) : null;
|
|
@@ -236,6 +254,14 @@ beforeAll(({}, suite) => {
|
|
|
236
254
|
const file = _fileOf({ file: s.file ?? s });
|
|
237
255
|
if (file !== null && !_ledgerMarks.has(file)) _ledgerMarks.set(file, journalLength());
|
|
238
256
|
});
|
|
257
|
+
beforeEach(({ task }) => {
|
|
258
|
+
const file = _fileOf(task as { file?: { filepath?: string; name?: string } });
|
|
259
|
+
if (file === null) return;
|
|
260
|
+
// Window for this test's own gate decisions and its own assertion count.
|
|
261
|
+
_itMarks.set(file, journalLength());
|
|
262
|
+
_itAssertionsBefore.set(file, _assertionCalls());
|
|
263
|
+
takeExplicitRequirementId(); // clear any override left by a test that threw before afterEach
|
|
264
|
+
});
|
|
239
265
|
afterEach(({ task }) => {
|
|
240
266
|
const file = _fileOf(task as { file?: { filepath?: string; name?: string } });
|
|
241
267
|
if (file === null) return;
|
|
@@ -248,13 +274,63 @@ afterEach(({ task }) => {
|
|
|
248
274
|
// A leg that early-returns from a gate makes zero, and a file of such legs is
|
|
249
275
|
// an `executed-pass` with assertionCount 0 — visible, and unclassified for a
|
|
250
276
|
// claimed floor.
|
|
251
|
-
|
|
277
|
+
//
|
|
278
|
+
// `expect.getState().assertionCalls` is per-test in vitest (reset at each
|
|
279
|
+
// test start), so it is this test's count; the file total is the sum.
|
|
280
|
+
const calls = _assertionCalls();
|
|
281
|
+
_fileAssertions.set(file, (_fileAssertions.get(file) ?? 0) + calls);
|
|
282
|
+
|
|
283
|
+
// Per-`it` row. Disposition follows RFC 0148 §A at test granularity:
|
|
284
|
+
// fail → executed-fail (detail = the first error message)
|
|
285
|
+
// skip (ctx.skip / it.skip) → the gate's recorded reason since this test began, else `skipped`
|
|
286
|
+
// pass with ≥1 assertion → executed-pass
|
|
287
|
+
// pass with 0 assertions → the gate's recorded reason (softSkip / seamAbsent / behaviorGate)
|
|
288
|
+
// since this test began, else `blocked` (unclassified return)
|
|
289
|
+
if (!file.endsWith('.test.ts')) return;
|
|
290
|
+
// Only SCENARIO files get per-`it` rows. The setup hooks run for every file in
|
|
291
|
+
// the worker, including `src/lib/*.test.ts` — the suite's own self-tests,
|
|
292
|
+
// which prove fixtures and helpers, not a host, and must never become bundle
|
|
293
|
+
// evidence (that is why RFC 0163 G5 / RFC 0050 G1 moved them out of scenarios).
|
|
294
|
+
const filepath = (task as { file?: { filepath?: string } }).file?.filepath ?? '';
|
|
295
|
+
if (!/[\\/]src[\\/]scenarios[\\/]/.test(filepath)) {
|
|
296
|
+
takeExplicitRequirementId();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const explicit = takeExplicitRequirementId();
|
|
300
|
+
const alloc = _itAllocators.get(file) ?? new ItIdAllocator();
|
|
301
|
+
_itAllocators.set(file, alloc);
|
|
302
|
+
const itId = explicit ?? alloc.allocate(file, task.name);
|
|
303
|
+
const since = journalSince(_itMarks.get(file) ?? 0);
|
|
304
|
+
const gate = since.find((e) => e.disposition === 'inapplicable') ?? since.find((e) => e.disposition === 'skipped');
|
|
305
|
+
let disposition: 'executed-pass' | 'executed-fail' | 'skipped' | 'inapplicable' | 'blocked';
|
|
306
|
+
let detail: string | undefined;
|
|
307
|
+
if (SPEC_COHERENCE_SCENARIOS.has(file) || SPEC_COHERENCE_SCENARIOS.has(file.replace(/\.test\.ts$/, ''))) {
|
|
308
|
+
// Corpus-coherence scenario: it reads spec/v1 and asserts nothing about a
|
|
309
|
+
// host, in any layout. Its rows are `inapplicable` to every host — the same
|
|
310
|
+
// rule `resolveFileRecord` applies to the file in the published layout.
|
|
311
|
+
disposition = 'inapplicable';
|
|
312
|
+
detail = SPEC_COHERENCE_DETAIL;
|
|
313
|
+
} else if (state === 'fail') {
|
|
314
|
+
disposition = 'executed-fail';
|
|
315
|
+
const err = (task.result?.errors ?? [])[0] as { message?: string } | undefined;
|
|
316
|
+
detail = `the test executed and failed: ${(err?.message ?? 'no message').slice(0, 300)}`;
|
|
317
|
+
} else if (state === 'pass' && calls > 0) {
|
|
318
|
+
disposition = 'executed-pass';
|
|
319
|
+
} else if (gate !== undefined) {
|
|
320
|
+
disposition = gate.disposition as 'skipped' | 'inapplicable';
|
|
321
|
+
detail = gate.detail ?? `${gate.disposition} (gate recorded no reason)`;
|
|
322
|
+
} else if (state === 'pass') {
|
|
323
|
+
disposition = 'blocked';
|
|
324
|
+
detail = 'unclassified return: the test passed with zero assertions and recorded no reason — RFC 0148 §A resolves it to blocked, never to a pass';
|
|
325
|
+
} else {
|
|
326
|
+
disposition = 'skipped';
|
|
327
|
+
detail = 'vitest skipped the test (ctx.skip / it.skip) without a recorded gate reason';
|
|
328
|
+
}
|
|
252
329
|
try {
|
|
253
|
-
|
|
330
|
+
recordRequirement(itId, disposition, detail, { assertionCount: calls });
|
|
254
331
|
} catch {
|
|
255
|
-
/*
|
|
332
|
+
/* never fail a test for bookkeeping */
|
|
256
333
|
}
|
|
257
|
-
_fileAssertions.set(file, (_fileAssertions.get(file) ?? 0) + calls);
|
|
258
334
|
});
|
|
259
335
|
afterAll(({}, suite) => {
|
|
260
336
|
// vitest 4: the suite/file task is the SECOND argument. For a file-level
|
|
@@ -306,5 +382,8 @@ afterAll(({}, suite) => {
|
|
|
306
382
|
_fileStates.delete(file);
|
|
307
383
|
_fileAssertions.delete(file);
|
|
308
384
|
_ledgerMarks.delete(file);
|
|
385
|
+
_itAllocators.delete(file);
|
|
386
|
+
_itMarks.delete(file);
|
|
387
|
+
_itAssertionsBefore.delete(file);
|
|
309
388
|
});
|
|
310
389
|
|