@openwop/openwop-conformance 1.153.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 +8 -0
- package/README.md +9 -0
- package/dist/cli.js +7 -2
- package/package.json +31 -2
- 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/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-ids.test.ts +0 -83
- 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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# `@openwop/openwop-conformance` Changelog
|
|
2
2
|
|
|
3
|
+
## [1.154.0] — 2026-09-02 — the tarball carries the suite and nothing else; the vendored contract is digest-checked (v2 charter Phase 1)
|
|
4
|
+
|
|
5
|
+
No new scenario file. Three packed changes:
|
|
6
|
+
|
|
7
|
+
- **Corpus-coherence scenarios and suite self-tests leave the package.** The 28 `SPEC_COHERENCE_SCENARIOS` (they read `spec/v1`, assert nothing about a host, and reported `blocked`/`inapplicable` in every host bundle) and every `src/lib/*.test.ts` are excluded via `package.json` `files` negations; `scripts/check-npm-pack-contents.sh` fails if either returns. The list is published as `conformance/spec-coherence-scenarios.json`. The published layout now collects 1,430 tests across 324 files; the layout gate's floors are re-measured (1,350 / 300).
|
|
8
|
+
- **`schemas/CORPUS-STAMP.json` carries per-file SHA-256 digests** of every vendored `api/` and `schemas/` file, written at `prepack`. `src/lib/corpus-stamp.ts` verifies them at global setup in the published layout and **refuses to run on a mismatch** (missing, altered, or extra vendored files are each named); repo layout and pre-1.154.0 stamps report `not checked`, never a silent pass. This removes the mechanism behind the 1.138.1 defect one layer down: a package can no longer identify its contract by a commit hash it cannot check.
|
|
9
|
+
- **`--offline` is a declared property:** exactly `fixtures-valid.test.ts` (the only server-free scenario that ships and runs in the published layout). `spec-corpus-validity.test.ts` left the set with the coherence scenarios. Documented in README §"--offline".
|
|
10
|
+
|
|
3
11
|
## [1.153.0] — 2026-09-02 — per-`it` requirement rows and the requirement registry (RFC 0148 §A / G3; v2 charter Phase 1)
|
|
4
12
|
|
|
5
13
|
No new scenario file. Three packed changes, all additive to bundle v2:
|
package/README.md
CHANGED
|
@@ -92,6 +92,15 @@ Exit code is non-zero on any failed assertion. `--certify` distinguishes: `0`
|
|
|
92
92
|
|
|
93
93
|
---
|
|
94
94
|
|
|
95
|
+
|
|
96
|
+
### `--offline` — a declared set
|
|
97
|
+
|
|
98
|
+
`openwop-conformance --offline` runs exactly **`src/scenarios/fixtures-valid.test.ts`**: the server-free scenario that ships in the tarball and runs in the published layout. That is the whole set, by declaration (suite 1.154.0), not by whatever happens to skip cleanly. `spec-corpus-validity.test.ts` and the other 27 corpus-coherence scenarios (`spec-coherence-scenarios.json`) read `spec/v1`, assert nothing about a host, and are not packed; run them from a spec checkout with `npm run openwop:check`.
|
|
99
|
+
|
|
100
|
+
### The vendored contract is digest-checked
|
|
101
|
+
|
|
102
|
+
`schemas/CORPUS-STAMP.json` carries `suiteVersion`, `corpusCommit`, and — from 1.154.0 — `files`: a SHA-256 for every vendored `api/` and `schemas/` file. At global setup in the published layout the suite verifies the map and **refuses to run on a mismatch**, naming each missing, altered, or extra file. A hand-patched vendored schema therefore cannot produce evidence. In a spec checkout nothing is vendored and the log line says `corpus stamp not checked`; an older tarball without `files` says so too. Three outcomes, never folded.
|
|
103
|
+
|
|
95
104
|
## What's Covered
|
|
96
105
|
|
|
97
106
|
The current suite has 468 scenario files under `src/scenarios/`.
|
package/dist/cli.js
CHANGED
|
@@ -161,7 +161,7 @@ Required (unless --offline):
|
|
|
161
161
|
--api-key <key> Bearer-style API key (or set OPENWOP_API_KEY env var)
|
|
162
162
|
|
|
163
163
|
Filtering:
|
|
164
|
-
--offline Run only the server-free subset (fixtures
|
|
164
|
+
--offline Run only the declared server-free subset (fixtures-valid; see README §"--offline")
|
|
165
165
|
--filter <pattern> Pass through to vitest --testNamePattern
|
|
166
166
|
|
|
167
167
|
Implementation labels (cosmetic — surface in failure messages):
|
|
@@ -572,7 +572,12 @@ async function main() {
|
|
|
572
572
|
// the conformance package is used as a workspace member.
|
|
573
573
|
const vitestArgs = ['run', '--config', resolvePath(conformanceRoot, 'vitest.config.ts')];
|
|
574
574
|
if (args.offline) {
|
|
575
|
-
|
|
575
|
+
// Suite 1.154.0: the offline set is a DECLARED property of the package —
|
|
576
|
+
// exactly the server-free scenarios that ship in the tarball and run in
|
|
577
|
+
// the published layout. `spec-corpus-validity.test.ts` left the set: it is
|
|
578
|
+
// a corpus-coherence scenario (reads spec/v1, asserts nothing about a host)
|
|
579
|
+
// and is no longer packed. See conformance/README.md §"--offline".
|
|
580
|
+
vitestArgs.push('src/scenarios/fixtures-valid.test.ts');
|
|
576
581
|
}
|
|
577
582
|
if (args.filter) {
|
|
578
583
|
vitestArgs.push('--testNamePattern', args.filter);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openwop/openwop-conformance",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.154.0",
|
|
4
4
|
"description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,36 @@
|
|
|
26
26
|
"vitest.config.ts",
|
|
27
27
|
"README.md",
|
|
28
28
|
"CHANGELOG.md",
|
|
29
|
-
"LICENSE"
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"!src/lib/*.test.ts",
|
|
31
|
+
"!src/scenarios/artifact-schema-compile-bounded.test.ts",
|
|
32
|
+
"!src/scenarios/artifact-type-legacy-ids.test.ts",
|
|
33
|
+
"!src/scenarios/capability-example-root-layout.test.ts",
|
|
34
|
+
"!src/scenarios/certification-floor-enforcement.test.ts",
|
|
35
|
+
"!src/scenarios/chain-subchain-unsupported-refused.test.ts",
|
|
36
|
+
"!src/scenarios/compensation-profile.test.ts",
|
|
37
|
+
"!src/scenarios/core-manifest-and-extension-registry.test.ts",
|
|
38
|
+
"!src/scenarios/discovery-canonical-family-no-shadow.test.ts",
|
|
39
|
+
"!src/scenarios/edge-condition-truthy-falsy.test.ts",
|
|
40
|
+
"!src/scenarios/effect-identity-composition.test.ts",
|
|
41
|
+
"!src/scenarios/effect-identity-cross-scope.test.ts",
|
|
42
|
+
"!src/scenarios/error-envelope-canonical-shape.test.ts",
|
|
43
|
+
"!src/scenarios/form-content-packs.test.ts",
|
|
44
|
+
"!src/scenarios/multi-region-effect-vocabulary.test.ts",
|
|
45
|
+
"!src/scenarios/normative-example-extraction.test.ts",
|
|
46
|
+
"!src/scenarios/openapi-asyncapi-sdk-parity.test.ts",
|
|
47
|
+
"!src/scenarios/pack-manifest-extensions.test.ts",
|
|
48
|
+
"!src/scenarios/protocol-version-grammar.test.ts",
|
|
49
|
+
"!src/scenarios/registry-declarative-kinds.test.ts",
|
|
50
|
+
"!src/scenarios/rfc-0147-self-audit.test.ts",
|
|
51
|
+
"!src/scenarios/rfc-lifecycle-coherence.test.ts",
|
|
52
|
+
"!src/scenarios/semantic-digest-v2.test.ts",
|
|
53
|
+
"!src/scenarios/spec-corpus-validity.test.ts",
|
|
54
|
+
"!src/scenarios/spec-section-citations.test.ts",
|
|
55
|
+
"!src/scenarios/tool-result-trust-monotone.test.ts",
|
|
56
|
+
"!src/scenarios/versioned-composition-profiles.test.ts",
|
|
57
|
+
"!src/scenarios/workflow-chain-internal-flag.test.ts",
|
|
58
|
+
"!src/scenarios/workload-identity-profile.test.ts"
|
|
30
59
|
],
|
|
31
60
|
"scripts": {
|
|
32
61
|
"test": "vitest run",
|
|
@@ -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
|
+
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Server-free pin of the RFC 0163 §C schema conditional in
|
|
3
|
-
* `schemas/capabilities.schema.json`:
|
|
4
|
-
*
|
|
5
|
-
* auth.subjectLinking: true ⇒ auth.subjectLinkKey required (RFC 0163)
|
|
6
|
-
* auth.subjectLinkKey ∈ { opaque-idp, configured-immutable } (RFC 0163)
|
|
7
|
-
* profiles ⊇ {saml, scim} ⇒ subjectLinking const true + key required (RFC 0164)
|
|
8
|
-
*
|
|
9
|
-
* This checks the SCHEMA ARTIFACT, not any host, which is why it lives under
|
|
10
|
-
* `src/lib/` (no scenario ledger row — see `saml-idp.test.ts` for the G8
|
|
11
|
-
* reasoning). The host-facing assertion is the advertisement leg of
|
|
12
|
-
* `scenarios/auth-subject-link-key-class.test.ts`, gated on the flag.
|
|
13
|
-
*
|
|
14
|
-
* @see RFCS/0163-subject-linking-hardening.md §C
|
|
15
|
-
* @see spec/v1/auth-profiles.md §"Subject linking (SAML ⟷ SCIM)"
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { describe, it, expect } from 'vitest';
|
|
19
|
-
import { readFileSync, readdirSync } from 'node:fs';
|
|
20
|
-
import { join } from 'node:path';
|
|
21
|
-
import Ajv2020 from 'ajv/dist/2020.js';
|
|
22
|
-
import addFormats from 'ajv-formats';
|
|
23
|
-
import { SCHEMAS_DIR } from './paths.js';
|
|
24
|
-
|
|
25
|
-
function compileCapabilities() {
|
|
26
|
-
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
27
|
-
addFormats(ajv);
|
|
28
|
-
for (const file of readdirSync(SCHEMAS_DIR).filter((f) => f.endsWith('.schema.json'))) {
|
|
29
|
-
const s = JSON.parse(readFileSync(join(SCHEMAS_DIR, file), 'utf8')) as { $id?: string };
|
|
30
|
-
ajv.addSchema(s, file);
|
|
31
|
-
ajv.addSchema(s, `./${file}`);
|
|
32
|
-
}
|
|
33
|
-
// Validate the `auth` block in isolation: the conditional lives on that
|
|
34
|
-
// object, and a full discovery document would drag in every root-level
|
|
35
|
-
// required family (supportedEnvelopes, schemaVersions, limits, …) that has
|
|
36
|
-
// nothing to do with what this file pins.
|
|
37
|
-
return ajv.compile({ $ref: 'capabilities.schema.json#/properties/auth' });
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const PROFILES = ['openwop-auth-saml', 'openwop-auth-scim'];
|
|
41
|
-
function doc(auth: Record<string, unknown>) {
|
|
42
|
-
return auth;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
describe('capabilities.schema.json: auth.subjectLinkKey conditional (RFC 0163 §C)', () => {
|
|
46
|
-
const validate = compileCapabilities();
|
|
47
|
-
|
|
48
|
-
it('subjectLinking:true WITHOUT subjectLinkKey fails validation (§A.1 co-requirement is in the schema, not only prose)', () => {
|
|
49
|
-
const ok = validate(doc({ profiles: PROFILES, subjectLinking: true }));
|
|
50
|
-
expect(ok, JSON.stringify(validate.errors)).toBe(false);
|
|
51
|
-
expect(JSON.stringify(validate.errors)).toContain('subjectLinkKey');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('subjectLinking:true with a class member validates', () => {
|
|
55
|
-
for (const k of ['opaque-idp', 'configured-immutable']) {
|
|
56
|
-
const ok = validate(doc({ profiles: PROFILES, subjectLinking: true, subjectLinkKey: k }));
|
|
57
|
-
expect(ok, `${k}: ${JSON.stringify(validate.errors)}`).toBe(true);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('a mutable/PII key is INEXPRESSIBLE: "email" fails the enum', () => {
|
|
62
|
-
const ok = validate(doc({ profiles: PROFILES, subjectLinking: true, subjectLinkKey: 'email' }));
|
|
63
|
-
expect(ok).toBe(false);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('the retired Draft-era attribute names are not members (the enum names classes)', () => {
|
|
67
|
-
for (const k of ['oid', 'immutable-id']) {
|
|
68
|
-
expect(validate(doc({ profiles: PROFILES, subjectLinking: true, subjectLinkKey: k })), k).toBe(false);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('one profile alone does not require the flag or a key (SAML-only / SCIM-only hosts are unaffected)', () => {
|
|
73
|
-
expect(validate(doc({ profiles: ['openwop-auth-saml'] }))).toBe(true);
|
|
74
|
-
expect(validate(doc({ profiles: ['openwop-auth-scim'], subjectLinking: false }))).toBe(true);
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
describe('capabilities.schema.json: the profile pair implies the contract (RFC 0164 §B)', () => {
|
|
79
|
-
const validate = compileCapabilities();
|
|
80
|
-
|
|
81
|
-
it('BOTH profiles with subjectLinking absent fails validation (the vulnerable pre-0164 shape)', () => {
|
|
82
|
-
const ok = validate(doc({ profiles: PROFILES }));
|
|
83
|
-
expect(ok, JSON.stringify(validate.errors)).toBe(false);
|
|
84
|
-
expect(JSON.stringify(validate.errors)).toContain('subjectLinking');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('BOTH profiles with subjectLinking:false fails validation (the flag is derived, not a choice)', () => {
|
|
88
|
-
expect(validate(doc({ profiles: PROFILES, subjectLinking: false, subjectLinkKey: 'opaque-idp' }))).toBe(false);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('BOTH profiles with subjectLinking:true but no subjectLinkKey fails validation', () => {
|
|
92
|
-
expect(validate(doc({ profiles: PROFILES, subjectLinking: true }))).toBe(false);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('BOTH profiles with subjectLinking:true + a class member validates (the derived advertisement)', () => {
|
|
96
|
-
expect(validate(doc({ profiles: PROFILES, subjectLinking: true, subjectLinkKey: 'opaque-idp' }))).toBe(true);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('profile order does not matter', () => {
|
|
100
|
-
expect(validate(doc({ profiles: ['openwop-auth-scim', 'openwop-auth-api-key-rotation', 'openwop-auth-saml'], subjectLinking: true, subjectLinkKey: 'configured-immutable' }))).toBe(true);
|
|
101
|
-
expect(validate(doc({ profiles: ['openwop-auth-scim', 'openwop-auth-api-key-rotation', 'openwop-auth-saml'] }))).toBe(false);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pins `forkDeclined` (RFC 0148 §A).
|
|
3
|
-
*
|
|
4
|
-
* The risk in a helper like this is not that it misses a status — that shows up
|
|
5
|
-
* as a red test somebody investigates. It is that it swallows one: every status
|
|
6
|
-
* it accepts turns an assertion into a skip, silently, and a suite that skips
|
|
7
|
-
* is indistinguishable from a suite that passes unless something records why.
|
|
8
|
-
* So the load-bearing cases here are the NEGATIVE ones.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { describe, expect, it, beforeEach } from 'vitest';
|
|
12
|
-
import { forkDeclined } from './fork-availability.js';
|
|
13
|
-
import { resetSoftSkips, softSkipDisposition } from './soft-skip.js';
|
|
14
|
-
|
|
15
|
-
describe('forkDeclined', () => {
|
|
16
|
-
beforeEach(() => resetSoftSkips());
|
|
17
|
-
|
|
18
|
-
it('treats 404 and 403 the same as 501 — route absent is not weaker than route-declines', () => {
|
|
19
|
-
// The asymmetry this fixes: every replay scenario handled 501 and none
|
|
20
|
-
// handled 404. `501` means "I know this route and decline"; `404` means
|
|
21
|
-
// "there is no such route" — strictly less implemented. The suite treated
|
|
22
|
-
// the weaker signal as a skip and the stronger one as a defect, so a host
|
|
23
|
-
// had to implement the route in order to say it had not implemented it.
|
|
24
|
-
for (const status of [404, 403, 501]) {
|
|
25
|
-
expect(forkDeclined(status, 'leg'), `status ${status} means the fork did not happen`).toBe(true);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('does NOT swallow any other status — a 500 is still a defect, not a skip', () => {
|
|
30
|
-
// If this ever goes green for one of these, a real failure has become a
|
|
31
|
-
// silent skip and no assertion downstream will ever run again.
|
|
32
|
-
for (const status of [200, 201, 202, 400, 409, 422, 500, 502, 503]) {
|
|
33
|
-
expect(forkDeclined(status, 'leg'), `status ${status} must reach the assertion`).toBe(false);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('records WHY, so the ledger row is not an unclassified return', () => {
|
|
38
|
-
// Two of the eleven original call sites were a bare `return`. A bare return
|
|
39
|
-
// in a file whose OTHER tests assert is invisible: the file records
|
|
40
|
-
// `executed-pass` and the leg that never ran leaves no trace.
|
|
41
|
-
forkDeclined(404, 'fanout-suppression replay fork');
|
|
42
|
-
const noted = softSkipDisposition('fork-availability.test.ts');
|
|
43
|
-
expect(noted?.kind).toBe('blocked');
|
|
44
|
-
expect(noted?.reason).toContain('404');
|
|
45
|
-
expect(noted?.reason).toContain('fanout-suppression replay fork');
|
|
46
|
-
expect(noted?.reason).toContain('not mounted');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('records nothing when the status is not a decline', () => {
|
|
50
|
-
forkDeclined(201, 'leg');
|
|
51
|
-
expect(softSkipDisposition('fork-availability.test.ts')).toBeNull();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('names the leg, so one file with several forks stays attributable', () => {
|
|
55
|
-
forkDeclined(501, 'branch fork');
|
|
56
|
-
expect(softSkipDisposition('fork-availability.test.ts')?.reason).toContain('branch fork');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('distinguishes the three declines in its reason text', () => {
|
|
60
|
-
forkDeclined(404, 'a');
|
|
61
|
-
const a = softSkipDisposition('fork-availability.test.ts')?.reason ?? '';
|
|
62
|
-
resetSoftSkips();
|
|
63
|
-
forkDeclined(501, 'a');
|
|
64
|
-
const b = softSkipDisposition('fork-availability.test.ts')?.reason ?? '';
|
|
65
|
-
// `blocked` alone does not tell a bundle reader whether the host lacks the
|
|
66
|
-
// route or declines the range; the reason must.
|
|
67
|
-
expect(a).not.toBe(b);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pins the run-end disposition summary (`src/global-setup.ts`, RFC 0148 §A).
|
|
3
|
-
*
|
|
4
|
-
* The defect this guards is not "the summary is ugly" — it is that the suite
|
|
5
|
-
* computed an honest disposition on every run and PUBLISHED it on one. Two
|
|
6
|
-
* host implementers read vitest's `1 passed` as coverage for a requirement the
|
|
7
|
-
* suite had internally classified `blocked`, because the classification had no
|
|
8
|
-
* reader outside `--certify`.
|
|
9
|
-
*
|
|
10
|
-
* Server-free and pure: `summarise` takes the raw JSONL and returns the text.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { describe, expect, it } from 'vitest';
|
|
14
|
-
import { summarise } from '../global-setup.js';
|
|
15
|
-
|
|
16
|
-
const line = (o: Record<string, unknown>): string => JSON.stringify(o);
|
|
17
|
-
|
|
18
|
-
describe('global-setup: RFC 0148 §A disposition summary', () => {
|
|
19
|
-
it('reads the ledger key the ledger actually writes (`requirementId`, not `id`)', () => {
|
|
20
|
-
// Regression: the first draft of this reader keyed on `entry.id`, a field
|
|
21
|
-
// `recordRequirement` has never written, and fell back to `(unnamed)`.
|
|
22
|
-
// The fallback made a WRONG READER look like MISSING DATA — the same
|
|
23
|
-
// failure shape the summary exists to expose, reproduced in the exposer.
|
|
24
|
-
const out = summarise(
|
|
25
|
-
line({ requirementId: 'openwop.scenario.webhook-signed-delivery', disposition: 'blocked', detail: 'guard rejected loopback' }),
|
|
26
|
-
);
|
|
27
|
-
expect(out).toContain('openwop.scenario.webhook-signed-delivery');
|
|
28
|
-
expect(out).not.toContain('(unnamed)');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('lists blocked / skipped / inapplicable as NOT witnessed, with the reason', () => {
|
|
32
|
-
const out = summarise(
|
|
33
|
-
[
|
|
34
|
-
line({ requirementId: 'a', disposition: 'blocked', detail: 'seam absent' }),
|
|
35
|
-
line({ requirementId: 'b', disposition: 'skipped', detail: 'operator opted out' }),
|
|
36
|
-
line({ requirementId: 'c', disposition: 'inapplicable', detail: 'profile not advertised' }),
|
|
37
|
-
].join('\n'),
|
|
38
|
-
);
|
|
39
|
-
expect(out).toContain('did NOT witness');
|
|
40
|
-
for (const s of ['seam absent', 'operator opted out', 'profile not advertised']) {
|
|
41
|
-
expect(out).toContain(s);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('flags an executed-pass that asserted nothing — the other RFC 0148 §A vacuity shape', () => {
|
|
46
|
-
const out = summarise(line({ requirementId: 'vacuous', disposition: 'executed-pass', assertionCount: 0 }));
|
|
47
|
-
expect(out).toContain('executed-pass (0 assertions)');
|
|
48
|
-
expect(out).toContain('vacuous');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('does NOT flag a real executed-pass', () => {
|
|
52
|
-
const out = summarise(line({ requirementId: 'real', disposition: 'executed-pass', assertionCount: 12 }));
|
|
53
|
-
expect(out).toContain('executed-pass 1');
|
|
54
|
-
expect(out).not.toContain('did NOT witness');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('announces truncation rather than capping silently', () => {
|
|
58
|
-
// A capped list that does not say it was capped reads as a complete one.
|
|
59
|
-
const many = Array.from({ length: 45 }, (_, i) =>
|
|
60
|
-
line({ requirementId: `r${i}`, disposition: 'blocked', detail: 'why' }),
|
|
61
|
-
).join('\n');
|
|
62
|
-
const out = summarise(many);
|
|
63
|
-
expect(out).toContain('5 more not listed');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('returns null on an empty ledger so teardown prints nothing', () => {
|
|
67
|
-
expect(summarise('')).toBeNull();
|
|
68
|
-
expect(summarise('\n\n')).toBeNull();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('skips unparseable lines without discarding the rest of the run', () => {
|
|
72
|
-
const out = summarise(['{ not json', line({ requirementId: 'ok', disposition: 'blocked', detail: 'r' })].join('\n'));
|
|
73
|
-
expect(out).toContain('ok');
|
|
74
|
-
expect(out).toContain('blocked 1');
|
|
75
|
-
});
|
|
76
|
-
});
|