@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.
Files changed (57) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +9 -0
  3. package/dist/cli.js +7 -2
  4. package/dist/lib/requirement-ids.js +100 -0
  5. package/dist/lib/scenario-disposition.js +22 -0
  6. package/package.json +33 -2
  7. package/requirement-aliases.json +4 -0
  8. package/requirements.json +23554 -0
  9. package/schemas/CORPUS-STAMP.json +99 -3
  10. package/src/cli.ts +7 -5
  11. package/src/global-setup.ts +13 -0
  12. package/src/lib/corpus-stamp.ts +125 -0
  13. package/src/lib/requirement-ids.ts +111 -0
  14. package/src/lib/scenario-disposition.ts +22 -0
  15. package/src/setup.ts +84 -5
  16. package/src/lib/capabilities-auth-subject-link.test.ts +0 -103
  17. package/src/lib/fork-availability.test.ts +0 -69
  18. package/src/lib/global-setup.test.ts +0 -76
  19. package/src/lib/grpc-framing.test.ts +0 -96
  20. package/src/lib/oidc-issuer.test.ts +0 -328
  21. package/src/lib/otel-collector-grpc.test.ts +0 -191
  22. package/src/lib/otel-collector.test.ts +0 -303
  23. package/src/lib/otlp-protobuf.test.ts +0 -461
  24. package/src/lib/polling.test.ts +0 -80
  25. package/src/lib/requirement-ledger.test.ts +0 -75
  26. package/src/lib/risk-disposition.test.ts +0 -91
  27. package/src/lib/saml-idp.test.ts +0 -127
  28. package/src/lib/spec-coherence-registry.test.ts +0 -155
  29. package/src/lib/webhook-receiver.test.ts +0 -144
  30. package/src/scenarios/artifact-schema-compile-bounded.test.ts +0 -126
  31. package/src/scenarios/artifact-type-legacy-ids.test.ts +0 -124
  32. package/src/scenarios/capability-example-root-layout.test.ts +0 -272
  33. package/src/scenarios/certification-floor-enforcement.test.ts +0 -204
  34. package/src/scenarios/chain-subchain-unsupported-refused.test.ts +0 -70
  35. package/src/scenarios/compensation-profile.test.ts +0 -340
  36. package/src/scenarios/core-manifest-and-extension-registry.test.ts +0 -250
  37. package/src/scenarios/discovery-canonical-family-no-shadow.test.ts +0 -219
  38. package/src/scenarios/edge-condition-truthy-falsy.test.ts +0 -108
  39. package/src/scenarios/effect-identity-composition.test.ts +0 -129
  40. package/src/scenarios/effect-identity-cross-scope.test.ts +0 -82
  41. package/src/scenarios/error-envelope-canonical-shape.test.ts +0 -64
  42. package/src/scenarios/form-content-packs.test.ts +0 -415
  43. package/src/scenarios/multi-region-effect-vocabulary.test.ts +0 -175
  44. package/src/scenarios/normative-example-extraction.test.ts +0 -242
  45. package/src/scenarios/openapi-asyncapi-sdk-parity.test.ts +0 -309
  46. package/src/scenarios/pack-manifest-extensions.test.ts +0 -203
  47. package/src/scenarios/protocol-version-grammar.test.ts +0 -119
  48. package/src/scenarios/registry-declarative-kinds.test.ts +0 -121
  49. package/src/scenarios/rfc-0147-self-audit.test.ts +0 -104
  50. package/src/scenarios/rfc-lifecycle-coherence.test.ts +0 -215
  51. package/src/scenarios/semantic-digest-v2.test.ts +0 -128
  52. package/src/scenarios/spec-corpus-validity.test.ts +0 -1727
  53. package/src/scenarios/spec-section-citations.test.ts +0 -132
  54. package/src/scenarios/tool-result-trust-monotone.test.ts +0 -168
  55. package/src/scenarios/versioned-composition-profiles.test.ts +0 -201
  56. package/src/scenarios/workflow-chain-internal-flag.test.ts +0 -84
  57. package/src/scenarios/workload-identity-profile.test.ts +0 -184
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
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
+
11
+ ## [1.153.0] — 2026-09-02 — per-`it` requirement rows and the requirement registry (RFC 0148 §A / G3; v2 charter Phase 1)
12
+
13
+ No new scenario file. Three packed changes, all additive to bundle v2:
14
+
15
+ - **Every test records its own ledger row.** `setup.ts` now records one RFC 0148 §A disposition per `it()` under `openwop.it.<file-stem>.<title-slug>` (`src/lib/requirement-ids.ts`), next to the retained file-level row the floors key on. A test that passes with ≥1 assertion is `executed-pass`; a failure is `executed-fail` with the first error message; a pass with zero assertions takes the gate reason recorded during that test (`softSkip` / `seamAbsent` / `behaviorGate`) or resolves to `blocked`. This is the durable fix for certification gap G8: a file that asserted a positive control and then soft-skipped the requirement no longer certifies the requirement, because the requirement's own row says `skipped`/`inapplicable`/`blocked`.
16
+ - **`--certify` emits the per-`it` rows** in `results.requirements[]`, attributed to their scenario file. Existing verifiers accept them (the row shape is unchanged; ids are new). Expect bundles to grow from ~470 rows to ~2,400.
17
+ - **`conformance/requirements.json`** (packed) is the generated registry: one record per test with its id, file, line, title, and the `driver.describe` / `req()` citations found in its body. `scripts/generate-requirement-registry.mjs --check` runs in `openwop:check`; a reworded title needs a row in `requirement-aliases.json` or the check fails, so a bundle that cited the old id still resolves. Measured at generation: 1,950 tests in 468 files, 1,927 stable ids, 23 interpolated titles (those rows exist at run time keyed by the rendered title and map to the registry by file+line).
18
+ - New `req(id, section, requirement)` helper: a scenario may attach a hand-authored registry id to the current test; it doubles as the assertion message.
19
+
3
20
  ## [1.152.0] — 2026-09-02 — v2 charter Phase 0: `--certify` defaults to bundle v2; the RFC 0050 reference suite leaves the scenario file
4
21
 
5
22
  No new scenario file. Two packed changes:
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 + spec corpus)
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
- vitestArgs.push('src/scenarios/fixtures-valid.test.ts', 'src/scenarios/spec-corpus-validity.test.ts');
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);
@@ -0,0 +1,100 @@
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
+ export const IT_ID_PREFIX = 'openwop.it.';
29
+ export const IT_ID_GRAMMAR = /^openwop\.it\.[a-z0-9]+(?:-[a-z0-9]+)*\.[a-z0-9]+(?:-[a-z0-9]+)*(?:~[2-9][0-9]*)?$/;
30
+ const MAX_SLUG = 80;
31
+ /** Title → slug: lower-case, non-alphanumeric runs → `-`, trimmed, capped. */
32
+ export function slugTitle(title) {
33
+ const s = title
34
+ .toLowerCase()
35
+ .replace(/[^a-z0-9]+/g, '-')
36
+ .replace(/^-+|-+$/g, '');
37
+ const capped = s.length > MAX_SLUG ? s.slice(0, MAX_SLUG).replace(/-+$/g, '') : s;
38
+ return capped.length > 0 ? capped : 'untitled';
39
+ }
40
+ /** `auth-subject-link.test.ts` → `auth-subject-link`. */
41
+ export function fileStem(scenarioFile) {
42
+ return scenarioFile.replace(/\.test\.ts$/, '');
43
+ }
44
+ /** Derived id for one test, before collision suffixing. */
45
+ export function itRequirementId(scenarioFile, title) {
46
+ return `${IT_ID_PREFIX}${fileStem(scenarioFile)}.${slugTitle(title)}`;
47
+ }
48
+ /** The scenario file a per-`it` id belongs to, or null when the id is not one. */
49
+ export function scenarioFileOfItId(requirementId) {
50
+ if (!requirementId.startsWith(IT_ID_PREFIX))
51
+ return null;
52
+ const rest = requirementId.slice(IT_ID_PREFIX.length);
53
+ const dot = rest.indexOf('.');
54
+ if (dot <= 0)
55
+ return null;
56
+ return `${rest.slice(0, dot)}.test.ts`;
57
+ }
58
+ /**
59
+ * Collision suffixing: the first occurrence keeps the bare id, later ones get
60
+ * `~2`, `~3`, … Keyed per file so a worker running many files never crosses
61
+ * streams. Reset per file by `setup.ts` when the file finishes.
62
+ */
63
+ export class ItIdAllocator {
64
+ seen = new Map();
65
+ allocate(scenarioFile, title) {
66
+ const base = itRequirementId(scenarioFile, title);
67
+ const n = (this.seen.get(base) ?? 0) + 1;
68
+ this.seen.set(base, n);
69
+ return n === 1 ? base : `${base}~${n}`;
70
+ }
71
+ reset() {
72
+ this.seen.clear();
73
+ }
74
+ }
75
+ // ---------------------------------------------------------------------------
76
+ // Explicit override for the current test.
77
+ // ---------------------------------------------------------------------------
78
+ let explicitId = null;
79
+ /**
80
+ * Attach a hand-authored requirement id to the CURRENT test. The per-`it`
81
+ * ledger row for this test is recorded under `id` instead of the derived
82
+ * title id. Returns the usual `driver.describe`-style message so the call
83
+ * doubles as the assertion message:
84
+ *
85
+ * expect(x, req('openwop.auth.subject-link.leaver-deny', 'auth-profiles.md §Subject linking', 'deactivation MUST deny')).toBe(true)
86
+ *
87
+ * The id MUST be listed in `conformance/requirements.json` (the generator
88
+ * collects `req(` first-argument literals); an unlisted id fails the registry
89
+ * check, so a hand id cannot drift from the registry silently.
90
+ */
91
+ export function req(id, specSection, requirement) {
92
+ explicitId = id;
93
+ return `${specSection}: ${requirement}`;
94
+ }
95
+ /** setup.ts reads and clears the override after each test. */
96
+ export function takeExplicitRequirementId() {
97
+ const id = explicitId;
98
+ explicitId = null;
99
+ return id;
100
+ }
@@ -25,6 +25,7 @@
25
25
  * Pure functions, no I/O, so `runner-ledger.test.ts` can pin them without a
26
26
  * host or a vitest subprocess.
27
27
  */
28
+ import { scenarioFileOfItId } from './requirement-ids.js';
28
29
  import { PROFILE_FLOOR_SCENARIOS } from './profiles.js';
29
30
  import { requirementIdForScenario, requirementIdForPrefix, requirementsFor } from './requirement-registry.js';
30
31
  import { UNCLASSIFIED_RETURN_DETAIL } from './soft-skip.js';
@@ -233,6 +234,27 @@ document) {
233
234
  }
234
235
  rows.push(row);
235
236
  }
237
+ // Per-`it` rows (suite 1.153.0): every ledger entry keyed `openwop.it.<file>.<slug>`
238
+ // becomes its own bundle row, attributed to its scenario file. Additive — the
239
+ // file-level and prefix rows above are unchanged, and the floors still key on
240
+ // them. This is the granularity RFC 0148 §A describes and the G8 fix.
241
+ const emitted = new Set(rows.map((r) => r.requirementId));
242
+ for (const e of [...ledger].sort((a, b) => a.requirementId.localeCompare(b.requirementId))) {
243
+ const file = scenarioFileOfItId(e.requirementId);
244
+ // Attribute only to files this run reported on: a worker's ledger can carry
245
+ // rows from files outside the certified set (the suite's own lib tests, or
246
+ // a filtered run), and those are not evidence about the host.
247
+ if (file === null || emitted.has(e.requirementId) || !reportStates.has(file))
248
+ continue;
249
+ emitted.add(e.requirementId);
250
+ rows.push({
251
+ requirementId: e.requirementId,
252
+ scenarioId: file,
253
+ disposition: e.disposition,
254
+ ...(e.detail === undefined ? {} : { detail: e.detail }),
255
+ ...(e.assertionCount === undefined ? {} : { assertionCount: e.assertionCount }),
256
+ });
257
+ }
236
258
  const totals = { executedPass: 0, executedFail: 0, skipped: 0, inapplicable: 0, blocked: 0 };
237
259
  for (const r of rows) {
238
260
  if (r.disposition === 'executed-pass')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "1.152.0",
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",
@@ -18,13 +18,44 @@
18
18
  "fixtures",
19
19
  "fixtures.md",
20
20
  "coverage.md",
21
+ "requirements.json",
22
+ "requirement-aliases.json",
21
23
  "api",
22
24
  "schemas",
23
25
  "vectors",
24
26
  "vitest.config.ts",
25
27
  "README.md",
26
28
  "CHANGELOG.md",
27
- "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"
28
59
  ],
29
60
  "scripts": {
30
61
  "test": "vitest run",
@@ -0,0 +1,4 @@
1
+ {
2
+ "$comment": "old id → new id. Add a row when a test title is reworded so bundles citing the old id still resolve.",
3
+ "aliases": {}
4
+ }