@openwop/openwop-conformance 2.0.0-rc.2 → 2.0.0-rc.28

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/README.md CHANGED
@@ -112,7 +112,7 @@ Exit code is non-zero on any failed assertion. `--certify` distinguishes: `0`
112
112
 
113
113
  ## What's Covered
114
114
 
115
- The current suite has 495 scenario files under `src/scenarios/`.
115
+ The current suite has 500 scenario files under `src/scenarios/`.
116
116
  - 2026-09-03 (suite `1.157.0 -> 1.158.0`, gap G17): NEW `idempotency-concurrent-claim.test.ts` — drives the new `host-sample-test-seams.md` §25 concurrent duplicate-delivery seam for the RFC 0150 §B / `idempotency.md` §"Concurrent duplicates (Layer 2)" atomic-claim MUST, which is unconditional and had no witness of any kind. Asserts every executor mints the SAME `logicalInvocationId` **before** asserting `delivered === 1` — without the identity check a host passes by minting different ids and never colliding, one effect because nothing raced. Not profile-gated and so not opt-out-able (the obligation is unconditional); an unmounted seam records `blocked`, which is not certifiable. Graduates `layer2-invocation-claim-atomic` reference-impl -> protocol.
117
117
  - 2026-08-19 (suite `1.137.0 → 1.138.0`): NEW `durability-poison-exhaustion.test.ts` — RFC 0158 §C.8, the FIRST row of that RFC's conformance table to land. Asserts what `failure-path.test.ts` cannot: not just that deterministically failing work reaches terminal, but that attempts STOP — counted on the log, re-counted after a scaled quiet window, asserted unchanged. A host still redelivering records more. Seam-gated on the existing event-log seam (`blocked` = unobservable, not unmet) and outside every profile floor.
118
118
  - 2026-08-19 (suite `1.136.15 → 1.137.0`): NEW `replay-fanout-suppression.test.ts` — capability-gated on `webhooks.supported`, **outside every profile floor**; witnesses `replay.md` §"Host-initiated fan-out is an external effect", which was the largest normative MUST NOT on the replay surface with no scenario and no SECURITY invariant. Three legs in ONE `it` against ONE receiver and ONE subscription — a positive control, the MUST NOT, and a `branch` boundary leg — because "no delivery arrived" passes identically when delivery never worked, so absence is asserted only after presence is proven on that exact wiring. A host with an SSRF guard correctly refuses the loopback receiver and records `blocked`: **unobservable, not unmet.**
@@ -457,7 +457,7 @@ Server-required (added in 1.7.0):
457
457
  | ------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
458
458
  | **Redaction** | [`capabilities.md`](../spec/v1/capabilities.md) §"Secrets" + NFR-7 + §"aiProviders" | Vendor-neutral assertions that the server doesn't leak secret material. Three scenario groups: (a) discovery shape contract — `secrets` + `aiProviders` advertisements are well-formed regardless of `secrets.supported`; when `supported === true`, scopes MUST be non-empty + `resolution === 'host-managed'`; `byok ⊆ supported`. (b) bearer-token redaction — invalid Bearer canary in `Authorization` header is not echoed in the 401 response body. (c) credentialRef echo control — gated on `secrets.supported === true`; canary planted in `configurable.ai.credentialRef` MUST NOT appear in any RunEvent payload (poll-based capture; transport-agnostic). Uses runtime-built canary fixtures (`lib/canaries.ts`) that defeat static secret scanners. 6 scenarios. |
459
459
 
460
- Current source tree: 495 scenario files. Use [`coverage.md`](./coverage.md) for current grade/gap tracking.
460
+ Current source tree: 500 scenario files. Use [`coverage.md`](./coverage.md) for current grade/gap tracking.
461
461
 
462
462
  ## Remaining Gaps
463
463
 
package/dist/cli.js CHANGED
@@ -46,6 +46,7 @@ import { deriveRequirementDispositions } from './lib/scenario-disposition.js';
46
46
  import { scrubEvidence, evidenceSecretsFromEnv, verifyBundleV2 } from './lib/certification-bundle-verify.js';
47
47
  import { publicKeyFromPrivate, signBundleV3, verifierSign, verifyBundleV3, witnessDigest } from './lib/certification-bundle-v3.js';
48
48
  import { deriveProfiles, isCoreStandard, agentPlatformStatus, DEPRECATED_PROFILE_ALIASES, PROFILE_FLOOR_SCENARIOS, } from './lib/profiles.js';
49
+ import { setV2ProfileFloors } from './lib/requirement-registry.js';
49
50
  function parseArgs(argv) {
50
51
  let baseUrl;
51
52
  let apiKey;
@@ -299,13 +300,7 @@ function claimedProfilesFor(doc) {
299
300
  * no v2 host could ever certify. Falls back to the empty set only when the
300
301
  * registry is genuinely absent from the layout, and says so.
301
302
  */
302
- function claimedProfilesForV2(doc, conformanceRoot) {
303
- // Resolve the peer the way `lib/paths.ts` does — through Node's resolver from
304
- // this package — instead of guessing directory shapes. Hand-rolled candidates
305
- // found the registry in a repo checkout and missed it in every published
306
- // install, where npm hoists the peer to a SIBLING package dir: the probe that
307
- // walked one level up landed on the `@openwop/` scope directory, not a
308
- // package, so a real host run silently claimed nothing.
303
+ function v2RegistryPath(conformanceRoot) {
309
304
  const candidates = [];
310
305
  try {
311
306
  const req = createRequire(resolvePath(conformanceRoot, 'package.json'));
@@ -313,8 +308,17 @@ function claimedProfilesForV2(doc, conformanceRoot) {
313
308
  }
314
309
  catch { /* not installed as a package; the repo-layout candidates below */ }
315
310
  candidates.push(resolvePath(conformanceRoot, 'spec', 'v2', 'profiles.json'), resolvePath(conformanceRoot, '..', 'spec', 'v2', 'profiles.json'), resolvePath(conformanceRoot, '..', 'spec-artifacts', 'spec', 'v2', 'profiles.json'));
316
- const found = candidates.find((c) => existsSync(c));
317
- if (found === undefined) {
311
+ return candidates.find((c) => existsSync(c)) ?? null;
312
+ }
313
+ function claimedProfilesForV2(doc, conformanceRoot) {
314
+ // Resolve the peer the way `lib/paths.ts` does — through Node's resolver from
315
+ // this package — instead of guessing directory shapes. Hand-rolled candidates
316
+ // found the registry in a repo checkout and missed it in every published
317
+ // install, where npm hoists the peer to a SIBLING package dir: the probe that
318
+ // walked one level up landed on the `@openwop/` scope directory, not a
319
+ // package, so a real host run silently claimed nothing.
320
+ const found = v2RegistryPath(conformanceRoot);
321
+ if (found === null) {
318
322
  process.stderr.write('openwop-conformance --certify: spec/v2/profiles.json not found in this layout; claimedProfiles is empty (RFC 0169 §C.1).\n');
319
323
  return [];
320
324
  }
@@ -342,6 +346,46 @@ function claimedProfilesForV2(doc, conformanceRoot) {
342
346
  }
343
347
  return out;
344
348
  }
349
+ /**
350
+ * `spec/v2/profiles.json` `floorScenarios` → scenario file names. A
351
+ * `planned:<name>` entry names a scenario the registry expects but that may not
352
+ * exist yet; it resolves to `v2-<name>.test.ts` when that file is in the
353
+ * manifest and is dropped otherwise, so a planned-but-unwritten floor cannot
354
+ * fail a host for the corpus's own backlog.
355
+ */
356
+ function v2ProfileFloors(conformanceRoot) {
357
+ const registryPath = v2RegistryPath(conformanceRoot);
358
+ if (registryPath === null)
359
+ return {};
360
+ let known;
361
+ try {
362
+ known = new Set(Object.keys(JSON.parse(readFileSync(resolvePath(conformanceRoot, 'scenario-majors.json'), 'utf8')).majors));
363
+ }
364
+ catch {
365
+ known = new Set();
366
+ }
367
+ let registry;
368
+ try {
369
+ registry = JSON.parse(readFileSync(registryPath, 'utf8'));
370
+ }
371
+ catch {
372
+ return {};
373
+ }
374
+ const out = {};
375
+ for (const p of registry.profiles ?? []) {
376
+ if (typeof p.id !== 'string')
377
+ continue;
378
+ const raw = Array.isArray(p.floorScenarios) ? p.floorScenarios.map(String) : [];
379
+ const files = [];
380
+ for (const entry of raw) {
381
+ const name = entry.startsWith('planned:') ? `v2-${entry.slice('planned:'.length)}.test.ts` : entry.endsWith('.test.ts') ? entry : `${entry}.test.ts`;
382
+ if (known.size === 0 || known.has(name))
383
+ files.push(name);
384
+ }
385
+ out[p.id] = files;
386
+ }
387
+ return out;
388
+ }
345
389
  /**
346
390
  * Reduce a vitest JSON report into a per-scenario-file terminal state, keyed by
347
391
  * the test-file basename (e.g. `discovery.test.ts`) to align with the basenames
@@ -450,6 +494,10 @@ async function runCertify(args, baseUrl, apiKey) {
450
494
  const sha256 = createHash('sha256').update(canonicalJSON(document)).digest('hex');
451
495
  // (b) Derive claimedProfiles from the captured document.
452
496
  const claimedProfiles = target.major === 2 ? claimedProfilesForV2(document, conformanceRoot) : claimedProfilesFor(document);
497
+ // Major-2 floors come from spec/v2/profiles.json. Without this the derivation
498
+ // measures a v2 host against v1 scenario files a major-2 run never executes,
499
+ // and refuses certification for not running them.
500
+ setV2ProfileFloors(target.major === 2 ? v2ProfileFloors(conformanceRoot) : null);
453
501
  // (c) Run the suite, capturing per-scenario terminal state via the vitest
454
502
  // JSON reporter. server-targeted scenarios live under src/scenarios/.
455
503
  const reportDir = mkdtempSync(join(tmpdir(), 'owp-certify-'));
@@ -76,7 +76,27 @@ export function floorFilesFor(profile, document) {
76
76
  * discovery-conditional floor (RFC 0148 §C G7 — `openwop-replay-fork`): without
77
77
  * it such a floor is UNEVALUABLE and this returns `null`, never `[]`.
78
78
  */
79
+ /**
80
+ * Major-2 floors come from `spec/v2/profiles.json`, not from the v1 table.
81
+ * `PROFILE_FLOOR_SCENARIOS` names v1 scenario FILES (`runs-lifecycle.test.ts`,
82
+ * `discovery.test.ts`, …) that `scenario-majors.json` assigns to major 1 and a
83
+ * major-2 run therefore never executes — so every one of them came back
84
+ * unclassified and a v2 host was refused certification for not running v1
85
+ * scenarios. Set by the runner before deriving; empty means the registry
86
+ * declares no floor for that profile, which is a real "witnesses nothing yet",
87
+ * not an unclassified return.
88
+ */
89
+ let v2Floors = null;
90
+ export function setV2ProfileFloors(floors) {
91
+ v2Floors = floors;
92
+ }
79
93
  export function requirementsFor(profile, document) {
94
+ if (v2Floors !== null) {
95
+ const files = v2Floors[profile];
96
+ if (files === undefined)
97
+ return null;
98
+ return files.map(requirementIdForScenario);
99
+ }
80
100
  const floor = PROFILE_FLOOR_SCENARIOS[profile];
81
101
  if (floor === undefined)
82
102
  return null;
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "package": "@openwop/spec-artifacts",
3
- "version": "2.0.0-rc.2",
4
- "stampSha256": "b238d58665762e832fcd76df7149671705ffa588969cc80737358255d29488c9"
3
+ "version": "2.0.0-rc.28",
4
+ "stampSha256": "c7d6661057eca87dffb69c7d262e9b0dd6025087be2950df2007214fda6b0e57"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "2.0.0-rc.2",
3
+ "version": "2.0.0-rc.28",
4
4
  "description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -56,6 +56,6 @@
56
56
  "@openwop/spec-artifacts": "file:../spec-artifacts"
57
57
  },
58
58
  "peerDependencies": {
59
- "@openwop/spec-artifacts": "2.0.0-rc.2"
59
+ "@openwop/spec-artifacts": "2.0.0-rc.28"
60
60
  }
61
61
  }
package/requirements.json CHANGED
@@ -2,9 +2,9 @@
2
2
  "$comment": "GENERATED by conformance/scripts/generate-requirement-registry.mjs — do not edit. One record per it()/test() in src/scenarios. Ids: openwop.it.<file-stem>.<title-slug>[~n] (src/lib/requirement-ids.ts). A record with id null has an interpolated title; its run-time row is keyed by the rendered title and maps here by file+line only. Renamed ids need a row in requirement-aliases.json.",
3
3
  "generatedFrom": "src/scenarios/*.test.ts",
4
4
  "counts": {
5
- "files": 539,
6
- "tests": 2108,
7
- "withStableId": 2108,
5
+ "files": 546,
6
+ "tests": 2122,
7
+ "withStableId": 2122,
8
8
  "interpolatedTitles": 0,
9
9
  "explicitIds": 2057
10
10
  },
@@ -12262,6 +12262,58 @@
12262
12262
  }
12263
12263
  ]
12264
12264
  },
12265
+ {
12266
+ "id": "openwop.it.era-key-stamped-v1.a-run-the-host-just-created-carries-eventlogschemaversion",
12267
+ "file": "era-key-stamped-v1.test.ts",
12268
+ "line": 75,
12269
+ "title": "a run the host just created carries eventLogSchemaVersion",
12270
+ "explicitId": null,
12271
+ "citations": [
12272
+ {
12273
+ "section": null,
12274
+ "requirement": "every persisted run document MUST carry an eventLogSchemaVersion — the field is prose-only (run-snapshot.schema.json requires just runId, workflowId and status), so a snapshot without it validates cleanly and only this check can see its absence",
12275
+ "interpolated": true
12276
+ },
12277
+ {
12278
+ "section": null,
12279
+ "requirement": null,
12280
+ "interpolated": true
12281
+ }
12282
+ ]
12283
+ },
12284
+ {
12285
+ "id": "openwop.it.era-key-stamped-v1.a-freshly-created-run-is-not-classified-legacy-by-the-host-s-own-rule",
12286
+ "file": "era-key-stamped-v1.test.ts",
12287
+ "line": 98,
12288
+ "title": "a freshly created run is not classified legacy by the host's own rule",
12289
+ "explicitId": null,
12290
+ "citations": [
12291
+ {
12292
+ "section": "spec/v1/version-negotiation.md §Legacy detection",
12293
+ "requirement": null,
12294
+ "interpolated": true
12295
+ }
12296
+ ]
12297
+ },
12298
+ {
12299
+ "id": "openwop.it.era-key-stamped-v1.a-run-the-host-just-created-carries-engineversion-the-legacy-escape-cannot-reach",
12300
+ "file": "era-key-stamped-v1.test.ts",
12301
+ "line": 124,
12302
+ "title": "a run the host just created carries engineVersion — the legacy escape cannot reach it",
12303
+ "explicitId": null,
12304
+ "citations": [
12305
+ {
12306
+ "section": null,
12307
+ "requirement": "every persisted run document MUST carry engineVersion; the \"MAY omit\" escape applies only to legacy runs that predate the contract, and this run was created moments ago",
12308
+ "interpolated": true
12309
+ },
12310
+ {
12311
+ "section": null,
12312
+ "requirement": null,
12313
+ "interpolated": true
12314
+ }
12315
+ ]
12316
+ },
12265
12317
  {
12266
12318
  "id": "openwop.it.errors.get-v1-runs-nonexistentid-returns-canonical-envelope",
12267
12319
  "file": "errors.test.ts",
@@ -25741,6 +25793,39 @@
25741
25793
  }
25742
25794
  ]
25743
25795
  },
25796
+ {
25797
+ "id": "openwop.it.v2-advertised-fixtures-exist.a-sampled-advertised-fixture-is-actually-creatable-not-just-listed",
25798
+ "file": "v2-advertised-fixtures-exist.test.ts",
25799
+ "line": 62,
25800
+ "title": "a sampled advertised fixture is actually creatable, not just listed",
25801
+ "explicitId": null,
25802
+ "citations": [
25803
+ {
25804
+ "section": null,
25805
+ "requirement": null,
25806
+ "interpolated": true
25807
+ }
25808
+ ]
25809
+ },
25810
+ {
25811
+ "id": "openwop.it.v2-advertised-path-space-served.a-host-advertising-major-2-reaches-the-surfaces-it-already-serves-under-v1",
25812
+ "file": "v2-advertised-path-space-served.test.ts",
25813
+ "line": 87,
25814
+ "title": "a host advertising major 2 reaches the surfaces it already serves under /v1",
25815
+ "explicitId": null,
25816
+ "citations": [
25817
+ {
25818
+ "section": null,
25819
+ "requirement": null,
25820
+ "interpolated": true
25821
+ },
25822
+ {
25823
+ "section": null,
25824
+ "requirement": "at least one non-well-known surface MUST be reachable under major 2, or the advertisement rests entirely on the one resource the header selects",
25825
+ "interpolated": true
25826
+ }
25827
+ ]
25828
+ },
25744
25829
  {
25745
25830
  "id": "openwop.it.v2-approver-enforced.a-resolver-outside-approverslist-is-refused-at-resolve-time",
25746
25831
  "file": "v2-approver-enforced.test.ts",
@@ -26243,7 +26328,8 @@
26243
26328
  },
26244
26329
  {
26245
26330
  "section": "spec/v2/core/versioning.md §5",
26246
- "requirement": "the v2 read MUST name the same run"
26331
+ "requirement": null,
26332
+ "interpolated": true
26247
26333
  },
26248
26334
  {
26249
26335
  "section": "spec/v2/core/versioning.md §1.4",
@@ -26254,7 +26340,7 @@
26254
26340
  {
26255
26341
  "id": "openwop.it.v2-dual-stack-negotiation.an-unlisted-major-is-406-protocol-version-unsupported-with-details-protocolversi",
26256
26342
  "file": "v2-dual-stack-negotiation.test.ts",
26257
- "line": 83,
26343
+ "line": 108,
26258
26344
  "title": "an unlisted major is 406 protocol_version_unsupported with details.protocolVersions[]",
26259
26345
  "explicitId": "openwop.requirement.0172.dual-stack-negotiation.unlisted-major",
26260
26346
  "citations": [
@@ -26278,7 +26364,7 @@
26278
26364
  {
26279
26365
  "id": "openwop.it.v2-dual-stack-negotiation.openwop-version-2-0-on-a-v1-path-is-400-protocol-version-mismatch",
26280
26366
  "file": "v2-dual-stack-negotiation.test.ts",
26281
- "line": 94,
26367
+ "line": 119,
26282
26368
  "title": "OpenWOP-Version: 2.0 on a /v1/ path is 400 protocol_version_mismatch",
26283
26369
  "explicitId": "openwop.requirement.0172.dual-stack-negotiation.v1-path-mismatch",
26284
26370
  "citations": [
@@ -26297,7 +26383,7 @@
26297
26383
  {
26298
26384
  "id": "openwop.it.v2-dual-stack-negotiation.every-response-reports-the-contract-that-produced-it",
26299
26385
  "file": "v2-dual-stack-negotiation.test.ts",
26300
- "line": 103,
26386
+ "line": 128,
26301
26387
  "title": "every response reports the contract that produced it",
26302
26388
  "explicitId": "openwop.requirement.0172.dual-stack-negotiation.response-header",
26303
26389
  "citations": [
@@ -26533,7 +26619,7 @@
26533
26619
  {
26534
26620
  "id": "openwop.it.v2-era-2-append-vocabulary.the-run-keeps-the-era-it-was-created-with-an-append-does-not-promote-it-to-era-3",
26535
26621
  "file": "v2-era-2-append-vocabulary.test.ts",
26536
- "line": 119,
26622
+ "line": 128,
26537
26623
  "title": "the run keeps the era it was created with; an append does not promote it to era 3",
26538
26624
  "explicitId": null,
26539
26625
  "citations": [
@@ -26587,6 +26673,44 @@
26587
26673
  }
26588
26674
  ]
26589
26675
  },
26676
+ {
26677
+ "id": "openwop.it.v2-era-stamp-universal.discovery-advertises-an-era-the-host-actually-writes-and-a-new-run-carries-it",
26678
+ "file": "v2-era-stamp-universal.test.ts",
26679
+ "line": 40,
26680
+ "title": "discovery advertises an era the host actually writes, and a new run carries it",
26681
+ "explicitId": null,
26682
+ "citations": [
26683
+ {
26684
+ "section": null,
26685
+ "requirement": null,
26686
+ "interpolated": true
26687
+ },
26688
+ {
26689
+ "section": null,
26690
+ "requirement": null,
26691
+ "interpolated": true
26692
+ },
26693
+ {
26694
+ "section": null,
26695
+ "requirement": null,
26696
+ "interpolated": true
26697
+ }
26698
+ ]
26699
+ },
26700
+ {
26701
+ "id": "openwop.it.v2-era-stamp-universal.a-v2-era-host-stamps-3-so-its-new-runs-are-never-read-as-the-v1-era",
26702
+ "file": "v2-era-stamp-universal.test.ts",
26703
+ "line": 80,
26704
+ "title": "a v2-era host stamps 3, so its new runs are never read as the v1 era",
26705
+ "explicitId": null,
26706
+ "citations": [
26707
+ {
26708
+ "section": null,
26709
+ "requirement": "a host advertising era 3 MUST stamp 3 on every run it creates — an unstamped run made after the cut is indistinguishable from a pre-cut run and every reader translates it as era 2, which is a silent wrong read rather than an error",
26710
+ "interpolated": true
26711
+ }
26712
+ ]
26713
+ },
26590
26714
  {
26591
26715
  "id": "openwop.it.v2-error-registry.an-error-response-is-the-closed-envelope-with-a-registered-code-at-its-registere",
26592
26716
  "file": "v2-error-registry.test.ts",
@@ -28058,6 +28182,49 @@
28058
28182
  }
28059
28183
  ]
28060
28184
  },
28185
+ {
28186
+ "id": "openwop.it.v2-version-header-honored.openwop-version-is-honored-or-refused-never-ignored",
28187
+ "file": "v2-version-header-honored.test.ts",
28188
+ "line": 61,
28189
+ "title": "OpenWOP-Version is honored or refused — never ignored",
28190
+ "explicitId": null,
28191
+ "citations": [
28192
+ {
28193
+ "section": null,
28194
+ "requirement": "a host that does not serve major 2 MUST refuse with 406 protocol_version_unsupported, which it did",
28195
+ "interpolated": true
28196
+ },
28197
+ {
28198
+ "section": null,
28199
+ "requirement": "answering 200 to OpenWOP-Version: 2.0 with a body byte-identical to the header-less fetch means the header was IGNORED, not honored — the host served v1 and called it v2. Refusing with 406 is the correct answer for a host that does not implement major 2; silently returning v1 is the one answer that no presence-gated scenario can detect, because an absent v2 shape records `inapplicable` whether the host refused or ignored",
28200
+ "interpolated": true
28201
+ }
28202
+ ]
28203
+ },
28204
+ {
28205
+ "id": "openwop.it.v2-version-header-honored.a-host-advertising-two-majors-serves-two-different-representations",
28206
+ "file": "v2-version-header-honored.test.ts",
28207
+ "line": 92,
28208
+ "title": "a host advertising two majors serves two different representations",
28209
+ "explicitId": null,
28210
+ "citations": [
28211
+ {
28212
+ "section": null,
28213
+ "requirement": null,
28214
+ "interpolated": true
28215
+ },
28216
+ {
28217
+ "section": null,
28218
+ "requirement": "the v1 document and the closed v2 root differ by construction (capabilities.md §1), so a host advertising both majors returning identical bytes for both has advertised a major it does not actually serve",
28219
+ "interpolated": true
28220
+ },
28221
+ {
28222
+ "section": null,
28223
+ "requirement": null,
28224
+ "interpolated": true
28225
+ }
28226
+ ]
28227
+ },
28061
28228
  {
28062
28229
  "id": "openwop.it.v2-webhook-durable-delivery.a-failed-attempt-is-retried-and-the-event-is-delivered-at-least-once",
28063
28230
  "file": "v2-webhook-durable-delivery.test.ts",
@@ -28311,7 +28478,7 @@
28311
28478
  {
28312
28479
  "id": "openwop.it.version-negotiation.get-well-known-openwop-returns-capabilities-with-protocolversion-string",
28313
28480
  "file": "version-negotiation.test.ts",
28314
- "line": 46,
28481
+ "line": 68,
28315
28482
  "title": "GET /.well-known/openwop returns Capabilities with protocolVersion (string)",
28316
28483
  "explicitId": "openwop.it.version-negotiation.get-well-known-openwop-returns-capabilities-with-protocolversion-string",
28317
28484
  "citations": [
@@ -28324,7 +28491,7 @@
28324
28491
  {
28325
28492
  "id": "openwop.it.version-negotiation.every-event-has-the-6-required-runeventdoc-fields-per-run-event-schema-json",
28326
28493
  "file": "version-negotiation.test.ts",
28327
- "line": 60,
28494
+ "line": 82,
28328
28495
  "title": "every event has the 6 required RunEventDoc fields per run-event.schema.json",
28329
28496
  "explicitId": "openwop.it.version-negotiation.every-event-has-the-6-required-runeventdoc-fields-per-run-event-schema-json",
28330
28497
  "citations": [
@@ -28365,7 +28532,7 @@
28365
28532
  {
28366
28533
  "id": "openwop.it.version-negotiation.event-sequences-within-a-run-are-strictly-monotonic",
28367
28534
  "file": "version-negotiation.test.ts",
28368
- "line": 104,
28535
+ "line": 126,
28369
28536
  "title": "event sequences within a run are strictly monotonic",
28370
28537
  "explicitId": "openwop.it.version-negotiation.event-sequences-within-a-run-are-strictly-monotonic",
28371
28538
  "citations": [
@@ -28379,7 +28546,7 @@
28379
28546
  {
28380
28547
  "id": "openwop.it.version-negotiation.events-poll-with-lastsequence-past-current-end-returns-empty-events-iscomplete",
28381
28548
  "file": "version-negotiation.test.ts",
28382
- "line": 132,
28549
+ "line": 154,
28383
28550
  "title": "events/poll with lastSequence past current end returns empty events + isComplete",
28384
28551
  "explicitId": "openwop.it.version-negotiation.events-poll-with-lastsequence-past-current-end-returns-empty-events-iscomplete",
28385
28552
  "citations": [
@@ -34426,6 +34593,82 @@
34426
34593
  }
34427
34594
  ]
34428
34595
  },
34596
+ {
34597
+ "id": "openwop.it.v2-bundle-signature-attributable.the-v2-discovery-root-publishes-signingkeys-the-surface-the-rfc-named",
34598
+ "file": "v2-bundle-signature-attributable.test.ts",
34599
+ "line": 103,
34600
+ "title": "the v2 discovery root publishes signingKeys[] — the surface the RFC named",
34601
+ "explicitId": null,
34602
+ "citations": [
34603
+ {
34604
+ "section": null,
34605
+ "requirement": "the v2 discovery root MUST carry signingKeys[] — RFC 0168 disposed of \"an Ed25519 attestation without a key registry is a signature nobody can check\" by naming this surface, so if it is absent the disposition names a place that does not exist and every bundle signature is unattributable",
34606
+ "interpolated": true
34607
+ },
34608
+ {
34609
+ "section": null,
34610
+ "requirement": null,
34611
+ "interpolated": true
34612
+ },
34613
+ {
34614
+ "section": null,
34615
+ "requirement": "a signingKeys[] entry MUST be closed; an open key record lets a host attach meaning the verifier does not read",
34616
+ "interpolated": true
34617
+ },
34618
+ {
34619
+ "section": null,
34620
+ "requirement": "a signingKeys[] entry MUST be able to record retirement, because a retired key has to stay listed — dropping it silently invalidates every bundle it already signed, which is the opposite of what an evidence trail is for",
34621
+ "interpolated": true
34622
+ }
34623
+ ]
34624
+ },
34625
+ {
34626
+ "id": "openwop.it.v2-bundle-signature-attributable.the-front-door-gate-verifies-the-attestation-instead-of-inspecting-the-string",
34627
+ "file": "v2-bundle-signature-attributable.test.ts",
34628
+ "line": 134,
34629
+ "title": "the Front-door gate verifies the attestation instead of inspecting the string",
34630
+ "explicitId": null,
34631
+ "citations": [
34632
+ {
34633
+ "section": null,
34634
+ "requirement": "the gate MUST NOT accept a bundle signature by presence: `typeof signature.sig === 'string'` is satisfied by any string, so it cannot distinguish a host key from a keypair minted seconds earlier by whoever wrote the bundle",
34635
+ "interpolated": true
34636
+ },
34637
+ {
34638
+ "section": null,
34639
+ "requirement": "the gate MUST resolve signature.keyId against the host's published signingKeys[] and verify the Ed25519 attestation under it — resolving without verifying, or verifying against a key the bundle supplied itself, both leave the signature unaccountable",
34640
+ "interpolated": true
34641
+ }
34642
+ ]
34643
+ },
34644
+ {
34645
+ "id": "openwop.it.v2-bundle-signature-attributable.the-four-signature-outcomes-stay-distinct-collapsing-any-two-restores-the-defect",
34646
+ "file": "v2-bundle-signature-attributable.test.ts",
34647
+ "line": 149,
34648
+ "title": "the four signature outcomes stay distinct — collapsing any two restores the defect",
34649
+ "explicitId": null,
34650
+ "citations": [
34651
+ {
34652
+ "section": null,
34653
+ "requirement": null,
34654
+ "interpolated": true
34655
+ }
34656
+ ]
34657
+ },
34658
+ {
34659
+ "id": "openwop.it.v2-bundle-signature-attributable.signingkeys-is-reachable-from-a-v1-root-too-a-bundle-is-v3-regardless-of-major",
34660
+ "file": "v2-bundle-signature-attributable.test.ts",
34661
+ "line": 170,
34662
+ "title": "signingKeys[] is reachable from a v1 root too — a bundle is v3 regardless of major",
34663
+ "explicitId": null,
34664
+ "citations": [
34665
+ {
34666
+ "section": null,
34667
+ "requirement": "the v1 discovery root MUST also carry signingKeys[]: a certification bundle is v3 regardless of major, and a v1-only host has only this document to resolve signature.keyId against — putting the key on the v2 root alone leaves every v1 host bundle unattributable",
34668
+ "interpolated": true
34669
+ }
34670
+ ]
34671
+ },
34429
34672
  {
34430
34673
  "id": "openwop.it.v2-corpus-tag-pinned.every-vendoring-consumer-in-the-cross-repo-inventory-records-a-published-corpus",
34431
34674
  "file": "v2-corpus-tag-pinned.test.ts",
@@ -34454,6 +34697,30 @@
34454
34697
  }
34455
34698
  ]
34456
34699
  },
34700
+ {
34701
+ "id": "openwop.it.v2-error-registry-prose-parity.errors-md-states-the-registry-count-it-was-generated-from-and-renders-every-code",
34702
+ "file": "v2-error-registry-prose-parity.test.ts",
34703
+ "line": 26,
34704
+ "title": "errors.md states the registry count it was generated from and renders every code",
34705
+ "explicitId": null,
34706
+ "citations": [
34707
+ {
34708
+ "section": null,
34709
+ "requirement": null,
34710
+ "interpolated": true
34711
+ },
34712
+ {
34713
+ "section": null,
34714
+ "requirement": "errors.md states the registry size at least once, so the claim is checkable",
34715
+ "interpolated": true
34716
+ },
34717
+ {
34718
+ "section": null,
34719
+ "requirement": null,
34720
+ "interpolated": true
34721
+ }
34722
+ ]
34723
+ },
34457
34724
  {
34458
34725
  "id": "openwop.it.v2-event-naming-rule.every-closed-enum-v2-event-type-is-two-kebab-segments-and-every-recorded-artifac",
34459
34726
  "file": "v2-event-naming-rule.test.ts",
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "$comment": "GENERATED by conformance/scripts/generate-scenario-majors.mjs (RFC 0168 §D.3). Do not edit; add a file to BOTH_MAJORS in the generator to target both majors.",
3
3
  "counts": {
4
- "files": 495,
5
- "v1": 444,
6
- "v2": 52
4
+ "files": 500,
5
+ "v1": 445,
6
+ "v2": 56
7
7
  },
8
8
  "majors": {
9
9
  "a2a-1-0-agent-card.test.ts": [
@@ -564,6 +564,9 @@
564
564
  "envelope-variant-discriminator-static.test.ts": [
565
565
  1
566
566
  ],
567
+ "era-key-stamped-v1.test.ts": [
568
+ 1
569
+ ],
567
570
  "errors.test.ts": [
568
571
  1
569
572
  ],
@@ -1228,6 +1231,12 @@
1228
1231
  "trigger-stream-cdc-sources.test.ts": [
1229
1232
  1
1230
1233
  ],
1234
+ "v2-advertised-fixtures-exist.test.ts": [
1235
+ 2
1236
+ ],
1237
+ "v2-advertised-path-space-served.test.ts": [
1238
+ 2
1239
+ ],
1231
1240
  "v2-approver-enforced.test.ts": [
1232
1241
  2
1233
1242
  ],
@@ -1273,6 +1282,9 @@
1273
1282
  "v2-era-key.test.ts": [
1274
1283
  2
1275
1284
  ],
1285
+ "v2-era-stamp-universal.test.ts": [
1286
+ 2
1287
+ ],
1276
1288
  "v2-error-registry.test.ts": [
1277
1289
  2
1278
1290
  ],
@@ -1375,6 +1387,9 @@
1375
1387
  "v2-v1-signed-webhook-accepted.test.ts": [
1376
1388
  2
1377
1389
  ],
1390
+ "v2-version-header-honored.test.ts": [
1391
+ 2
1392
+ ],
1378
1393
  "v2-webhook-durable-delivery.test.ts": [
1379
1394
  2
1380
1395
  ],