@openwop/openwop-conformance 2.0.4 → 2.0.6

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 CHANGED
@@ -1,5 +1,98 @@
1
1
  # `@openwop/openwop-conformance` Changelog
2
2
 
3
+ ## [2.0.6] — 2026-09-06 — the release that made a rule uncheckable
4
+
5
+ **If you pinned 2.0.5, `v2-unmapped-type-refused` did not run against your
6
+ host.** It soft-skipped `inapplicable` and the lane went green. Pin 2.0.6 and
7
+ re-measure before trusting any result that scenario gave you.
8
+
9
+ 2.0.5 shipped four fixes for rules whose instruments could not answer, and one
10
+ of those fixes did the same thing to a fifth. Reported by a host operator who
11
+ kept a local witness for a defect they knew was unfixed and re-measured before
12
+ accepting a green they wanted. Two defects, one symptom:
13
+
14
+ - **The corpus resolver was anchored on a repo-only directory.**
15
+ `registeredOrgs()` and `codemapV1toV2()` located `spec/v2/` via
16
+ `V1_DIR/../v2` — a **v1**-anchored path to a **v2** file. No published package
17
+ ships `spec/v1/`, so `V1_DIR` is `null` in every install and both lookups
18
+ returned nothing. The data was never missing; it is in the exact-pinned
19
+ `@openwop/spec-artifacts` peer. Both now resolve through `SPEC_V2_DIR`
20
+ (`lib/paths`), anchored on the contract root, which holds in a repo checkout
21
+ and an install alike. The codemap failed more quietly — 7 fallback rows
22
+ instead of 118, so era-2 readers asserted against names it never had.
23
+
24
+ - **A precondition written for one leg gated the other.**
25
+ `v2-unmapped-type-refused` drives two opposite halves of the reader rule.
26
+ Both were gated on a single check that demanded a resolvable registry, which
27
+ only the *control* leg needs. With the registry unreachable, the *refusal*
28
+ leg skipped on exactly the hosts it exists to catch: one answering `200` and
29
+ one answering `500` were both green. Gates are now separate and pure
30
+ (`unmappedRefusalGate` / `vendorControlGate`), with the fail-closed reading
31
+ explicit — an unreadable registry registers nothing, so the refusal is still
32
+ required and the leg stays drivable.
33
+
34
+ Added `src/lib/era2-unmapped-gates.test.ts` (the gates' truth table, including
35
+ the rows a live-host scenario cannot check about itself; sabotage-verified) and
36
+ a post-install corpus-resolution assertion in `verify-installable` — a clean
37
+ `npm install` was never evidence the suite works.
38
+
39
+ **A ratchet's STALE signal means the scenario stopped failing. It is not
40
+ evidence the defect is fixed.**
41
+
42
+ ## [2.0.5] — 2026-09-06 — four rules with no way to be checked
43
+
44
+ Four instruments that could not, even in principle, return the answer they
45
+ appeared to give. Three of them were green.
46
+
47
+ **The vendor rule's registry did not exist.** `events.md` §Rules and
48
+ `persistence.md` §The codemap is data both require a vendor event type's first
49
+ segment to be an org registered under `extensions` in
50
+ `spec/v2/declaration.json`. There was no `extensions` key. Three near misses
51
+ kept it plausible — `extensionsKeyPattern` is a key SHAPE, `reservedOrgs` lists
52
+ FORBIDDEN orgs, and `metadata[]` carries a row keyed `extensions` that is the
53
+ host's own extension map in the discovery payload. The registry now exists and
54
+ holds `example`, reserved by the protocol and never assignable to a vendor.
55
+
56
+ **`v2-unmapped-type-refused` drove only the refusal half**, so a host that
57
+ refuses EVERY unmapped type — including a registered vendor type it MUST pass
58
+ through — went green while violating the rule. New leg
59
+ `openwop.requirement.0176.vendor-type-passthrough`: same log shape, one segment
60
+ changed. Its preconditions were a note inside a seeded payload, prose in a place
61
+ no runner reads; they are checked now, and the file blocks with the reason.
62
+
63
+ **"Byte-equivalent" was witnessed by comparing two of ten fields.**
64
+ `v2-run-fork-prefix` compared `${sequence}:${type}`, so a fork could inherit a
65
+ prefix whose payloads differed in every field and pass. It now compares `type`,
66
+ `nodeId` and `payload`, excluding `eventId`/`runId`/`timestamp`/`causationId` —
67
+ using **`runs.md` §Diff and ancestry's** exclusion set rather than one invented
68
+ for the leg. `schemaVersion`, `engineVersion` and the §37 snapshot claim are
69
+ recorded as named residue, unasserted, with the reason. Floor membership
70
+ unchanged: still RULED OUT.
71
+
72
+ **A v3 bundle's `certified: true` was uncheckable by its recipient.** v2 bundles
73
+ carried `discovery.document` and the verifier re-derived every claimed profile
74
+ from it (RFC 0148 §B(1)); v3 dropped the field and the check with it. Optional
75
+ `discovery.document` returns: the digest is re-derived first (the signature
76
+ covers `sha256`, not the document, so a swap would otherwise verify), then every
77
+ certified profile is re-derived, and absence is surfaced as
78
+ `derivabilityChecked: false` rather than swallowed. Sabotage-proved.
79
+
80
+ **A requirement's id depended on whether it passed.** The registry generator
81
+ read `req()`'s first argument only as a string literal, so `const ID = …`
82
+ recorded `explicitId: null` and the row fell back to a title-derived slug —
83
+ `v2-run-fork-prefix` is in the evidence tree under two ids, the explicit one
84
+ where the assertion ran and the slug where the leg was `inapplicable`. A
85
+ verifier asking whether a bundle carries a requirement got a well-formed NO from
86
+ a host that had merely not held the profile. 2058 explicit ids became 2106.
87
+
88
+ **`memory-attribution-replay-stable` is `majors: [1, 2]`** (was `[1]`). Its rule
89
+ is `replay.md` §Determinism caveat 5 in both majors, word for word. What held it
90
+ back was a gate returning early **unrecorded** and three hard-coded `/v1/` paths
91
+ that are not seams. Both are properties of the instrument. First row of the
92
+ 445-file backfill.
93
+
94
+ **A rule you cannot check is not a weaker rule; it is a rule that is not there.**
95
+
3
96
  ## [2.0.4] — 2026-09-06 — a fixture that punished a host for obeying the rule the scenario checks
4
97
 
5
98
  `v2-provider-conflict` drove `connection-pack-github`. On a host shipping a built-in `github`, RFC 0177 §D.1 requires refusing the later registration — so the fixture never installed and the qualified-form leg recorded **`blocked`** permanently, on a host whose only fault was obeying the rule under test. A production host carried that row across a dozen cuts; any blocked row denies certification (RFC 0168 §E.1).
package/dist/cli.js CHANGED
@@ -653,7 +653,11 @@ async function runCertify(args, baseUrl, apiKey) {
653
653
  generatedAt: new Date().toISOString(),
654
654
  suite: { name: '@openwop/openwop-conformance', version, targetMajor: target.major, specArtifactsVersion: lock?.version ?? 'repo-layout', ...(lock ? { stampSha256: lock.stampSha256 } : {}) },
655
655
  host: { name: host.name, version: host.version, ...(host.vendor ? { vendor: host.vendor } : {}), build, signingKeyId: keyId, ...(relaxations && relaxations.length ? { relaxations } : {}) },
656
- discovery: { url: discoveryUrl, sha256, protocolVersions, preferredVersion },
656
+ // `document` is what makes `claimedProfiles[].certified` checkable by
657
+ // someone other than this process (RFC 0148 §B(1)); v2 carried it and v3
658
+ // dropped it. `sha256` is a digest of `canonicalJSON(document)`, so the
659
+ // two are consistent by construction and the verifier re-derives it.
660
+ discovery: { url: discoveryUrl, sha256, protocolVersions, preferredVersion, document },
657
661
  claimedProfiles: claimed3,
658
662
  results: { totals: totals3, requirements: rows3 },
659
663
  witnessSha256: witnessDigest(rows3),
@@ -18,6 +18,7 @@
18
18
  * in cli.ts) and pure: it never reads the environment.
19
19
  */
20
20
  import { createHash, createPrivateKey, createPublicKey, sign as edSign, verify as edVerify } from 'node:crypto';
21
+ import { profileDerivable } from './profiles.js';
21
22
  export const SIGNATURE_OVER = ['witnessSha256', 'host.build', 'suite.version', 'discovery.sha256'];
22
23
  /** Deterministic JSON: keys sorted at every level, no whitespace. */
23
24
  export function canonicalJSON(value) {
@@ -125,6 +126,33 @@ export function verifyBundleV3(bundle, opts = {}) {
125
126
  }
126
127
  if (expected.blocked > 0 && certifiedProfiles.length > 0)
127
128
  rejections.push({ kind: 'blocked-certified', detail: `${expected.blocked} blocked row(s): a bundle with blocked > 0 does not certify (RFC 0168 §E.1)` });
129
+ // Derivability, restored. RFC 0148 §B(1): a profile certifies only if it is
130
+ // DERIVABLE from what the host advertised. `verifyBundleV2` checked exactly
131
+ // that against `discovery.document`; v3 stopped carrying the document and the
132
+ // check went with it, leaving `certified: true` a claim only its emitter
133
+ // could evaluate. When the document is present the check runs again — and it
134
+ // runs on the SAME bytes the signature covers, because `discovery.sha256` is
135
+ // a digest of `canonicalJSON(document)` and is re-derived here first. A
136
+ // document that does not hash to the signed digest is a substituted
137
+ // document, and rejecting it is the whole point of checking.
138
+ const document = bundle.discovery?.document;
139
+ let derivabilityChecked = false;
140
+ if (document !== undefined) {
141
+ const digest = createHash('sha256').update(canonicalJSON(document)).digest('hex');
142
+ if (digest !== bundle.discovery?.sha256) {
143
+ rejections.push({ kind: 'discovery-digest', detail: `discovery.document hashes to ${digest.slice(0, 12)} but discovery.sha256 is ${String(bundle.discovery?.sha256).slice(0, 12)} — the captured document is not the one the signature attests to` });
144
+ }
145
+ else {
146
+ derivabilityChecked = true;
147
+ for (const p of bundle.claimedProfiles ?? []) {
148
+ if (!p.certified)
149
+ continue;
150
+ if (!profileDerivable(document, p.id)) {
151
+ rejections.push({ kind: 'profile-not-derivable', profile: p.id, detail: `${p.id} is marked certified, but the captured discovery document does not derive it (RFC 0148 §B(1)) — evidence cannot certify a profile the host does not advertise` });
152
+ }
153
+ }
154
+ }
155
+ }
128
156
  // RFC 0168 §E.2: the verifier REFUSES, it does not warn — but a refusal is
129
157
  // scoped to what it names. A rejection carrying a `profile` removes that
130
158
  // profile only (a relaxation on one obligation does not poison an unrelated
@@ -138,5 +166,6 @@ export function verifyBundleV3(bundle, opts = {}) {
138
166
  signatureVerified,
139
167
  verifierSignatureVerified,
140
168
  certifiedProfiles: bundleWide.length > 0 ? [] : certifiedProfiles.filter((p) => !scoped.has(p)),
169
+ derivabilityChecked,
141
170
  };
142
171
  }
package/dist/lib/paths.js CHANGED
@@ -103,6 +103,8 @@ function resolveFromRoot(root, layout, contractRoot = root) {
103
103
  : null;
104
104
  const v1Probe = join(root, 'spec', 'v1');
105
105
  const v1Dir = existsSync(v1Probe) ? v1Probe : null;
106
+ const specV2Probe = join(contractRoot, 'spec', 'v2');
107
+ const specV2Dir = existsSync(specV2Probe) ? specV2Probe : null;
106
108
  const readmeProbe = join(root, 'README.md');
107
109
  const readmePath = existsSync(readmeProbe) ? readmeProbe : null;
108
110
  const typescriptRunHelpersProbe = join(root, 'sdk', 'typescript', 'src', 'run-helpers.ts');
@@ -121,6 +123,7 @@ function resolveFromRoot(root, layout, contractRoot = root) {
121
123
  fixturesDocPath,
122
124
  coverageDocPath,
123
125
  v1Dir,
126
+ specV2Dir,
124
127
  readmePath,
125
128
  typescriptRunHelpersPath,
126
129
  pythonTypesPath,
@@ -171,6 +174,7 @@ export const CONFORMANCE_README_PATH = _layout.conformanceReadmePath;
171
174
  export const FIXTURES_DOC_PATH = _layout.fixturesDocPath;
172
175
  export const COVERAGE_DOC_PATH = _layout.coverageDocPath;
173
176
  export const V1_DIR = _layout.v1Dir;
177
+ export const SPEC_V2_DIR = _layout.specV2Dir;
174
178
  export const README_PATH = _layout.readmePath;
175
179
  export const TYPESCRIPT_RUN_HELPERS_PATH = _layout.typescriptRunHelpersPath;
176
180
  export const PYTHON_TYPES_PATH = _layout.pythonTypesPath;
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "package": "@openwop/spec-artifacts",
3
- "version": "2.0.4",
4
- "stampSha256": "8abb52df9340e47ac5d16d15fb0abe88cc3bd6857e2cb8be269079d6d7ca1530"
3
+ "version": "2.0.6",
4
+ "stampSha256": "4f7707b8acb297687e2cc9017f2fa34191033dd0b5db6c1cc64c2c04477ecee6"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
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.4"
59
+ "@openwop/spec-artifacts": "2.0.6"
60
60
  }
61
61
  }
@@ -1,7 +1,172 @@
1
1
  {
2
- "$comment": "old id → new id. Add a row when a test title is reworded so bundles citing the old id still resolve.",
2
+ "$comment": "old id → new id. Add a row when a test title is reworded so bundles citing the old id still resolve. Suite 2.0.5 added 165 rows for a SECOND cause: the registry generator now resolves module-level const bindings, so a leg that soft-skips is labelled by its explicit requirement id instead of the title-derived slug it used to fall back to. The label moved without any title changing, and the generator --check cannot see it (it compares the record id, which did not move) — 11 rows across the three committed host bundles cited slugs the 2.0.5 suite no longer mints.",
3
3
  "aliases": {
4
4
  "openwop.it.v2-run-cancel.cancelling-a-terminal-run-answers-409-run-terminal-or-200-idempotently-the-prose": "openwop.it.v2-run-cancel.cancelling-a-terminal-run-is-refused-409-run-terminal",
5
- "openwop.it.pause-resume.pause-on-an-already-paused-run-is-a-no-op-200-202-idempotent": "openwop.it.pause-resume.pause-on-an-already-paused-run-returns-409-with-details-runstatus-paused-unless"
5
+ "openwop.it.pause-resume.pause-on-an-already-paused-run-is-a-no-op-200-202-idempotent": "openwop.it.pause-resume.pause-on-an-already-paused-run-returns-409-with-details-runstatus-paused-unless",
6
+ "openwop.it.era-key-stamped-v1.a-run-the-host-just-created-carries-eventlogschemaversion": "openwop.requirement.version-negotiation.era-key-stamped",
7
+ "openwop.it.era-key-stamped-v1.a-freshly-created-run-is-not-classified-legacy-by-the-host-s-own-rule": "openwop.requirement.version-negotiation.era-key-not-legacy",
8
+ "openwop.it.era-key-stamped-v1.a-run-the-host-just-created-carries-engineversion-the-legacy-escape-cannot-reach": "openwop.requirement.version-negotiation.engine-version-stamped",
9
+ "openwop.it.v2-advertised-fixtures-exist.a-sampled-advertised-fixture-is-actually-creatable-not-just-listed": "openwop.requirement.0168.advertised-fixtures-exist",
10
+ "openwop.it.v2-advertised-path-space-served.a-host-advertising-major-2-reaches-the-surfaces-it-already-serves-under-v1": "openwop.requirement.0172.advertised-path-space-served",
11
+ "openwop.it.v2-approver-enforced.a-resolver-outside-approverslist-is-refused-at-resolve-time": "openwop.requirement.0173.approver-enforced",
12
+ "openwop.it.v2-assurance-downgrade-audited.an-identity-below-the-lane-floor-is-refused-with-sender-constraint-missing": "openwop.requirement.0170.assurance-downgrade-audited.refused",
13
+ "openwop.it.v2-assurance-downgrade-audited.a-resolved-identity-records-the-assurance-actually-used-never-a-downgraded-beare": "openwop.requirement.0170.assurance-downgrade-audited.audited",
14
+ "openwop.it.v2-bundle-v3-signed.a-signed-v3-bundle-validates-against-the-closed-root-schema-an-extra-root-key-is": "openwop.requirement.0168.bundle-v3-signed.schema-closed-root",
15
+ "openwop.it.v2-bundle-v3-signed.the-attestation-verifies-under-the-host-key-and-a-flipped-witnesssha256-is-refus": "openwop.requirement.0168.bundle-v3-signed.witness-digest",
16
+ "openwop.it.v2-bundle-v3-signed.an-independent-claim-self-signed-under-the-host-key-is-refused-a-distinct-verifi": "openwop.requirement.0168.bundle-v3-signed.independent-self-signed",
17
+ "openwop.it.v2-capabilities-root-closed.the-host-document-validates-against-capabilities-schema-json": "openwop.requirement.0169.capabilities-root-closed.valid",
18
+ "openwop.it.v2-capabilities-root-closed.an-unknown-root-key-fails-validation": "openwop.requirement.0169.capabilities-root-closed.unknown-root-key",
19
+ "openwop.it.v2-capabilities-root-closed.a-dotted-mirror-key-fails-validation": "openwop.requirement.0169.capabilities-root-closed.dotted-key",
20
+ "openwop.it.v2-capabilities-root-closed.the-v1-capabilities-wrapper-fails-validation": "openwop.requirement.0169.capabilities-root-closed.wrapper",
21
+ "openwop.it.v2-capabilities-root-closed.a-root-profiles-fails-validation": "openwop.requirement.0169.capabilities-root-closed.profiles",
22
+ "openwop.it.v2-capability-record-shape.every-family-record-carries-status-since-and-witness-from-the-wire-legal-sets": "openwop.requirement.0169.capability-record-shape.required-fields",
23
+ "openwop.it.v2-capability-record-shape.until-is-present-iff-the-record-is-experimental-or-deprecated": "openwop.requirement.0169.capability-record-shape.until-iff-not-stable",
24
+ "openwop.it.v2-capability-record-shape.no-advertised-record-carries-an-until-already-in-the-past": "openwop.requirement.0169.capability-record-shape.until-not-past",
25
+ "openwop.it.v2-chain-pin-exact.a-chain-reference-carrying-a-range-core-ai-callprompt-1-is-refused-at-register": "openwop.requirement.0177.chain-pin-exact.range-refused",
26
+ "openwop.it.v2-chain-pin-exact.a-chain-reference-pinning-an-exact-version-core-ai-callprompt-1-0-0-registers": "openwop.requirement.0177.chain-pin-exact.exact-accepted",
27
+ "openwop.it.v2-coherence-not-in-bundle.every-corpus-ledger-id-has-a-scenario-in-src-coherence-and-none-in-src-scenarios": "openwop.requirement.0168.coherence-not-in-bundle.disjoint-by-construction",
28
+ "openwop.it.v2-coherence-not-in-bundle.the-bundle-schema-rejects-a-v3-bundle-carrying-a-corpus-coherence-id-and-accepts": "openwop.requirement.0168.coherence-not-in-bundle.schema-forbids-coherence-ids",
29
+ "openwop.it.v2-compensation-read-projection.get-runs-runid-compensation-on-a-fresh-run-validates-with-status-none": "openwop.requirement.0173.compensation-read-projection",
30
+ "openwop.it.v2-compensation-read-projection.an-unknown-run-projects-404-not-found-through-the-canonical-envelope": "openwop.requirement.0173.compensation-read-projection.not-found",
31
+ "openwop.it.v2-configurable-closed.the-standalone-schema-rejects-a-dotted-key-and-an-unknown-root-key-and-accepts-t": "openwop.requirement.0171.configurable-closed.schema",
32
+ "openwop.it.v2-configurable-closed.post-runs-rejects-a-dotted-key-with-400-validation-error": "openwop.requirement.0171.configurable-closed.dotted-key",
33
+ "openwop.it.v2-configurable-closed.post-runs-rejects-an-unknown-root-key-with-400-validation-error": "openwop.requirement.0171.configurable-closed.unknown-key",
34
+ "openwop.it.v2-created-run-readable.a-run-created-at-this-base-is-readable-and-pollable-at-this-base-by-the-id-it-re": "openwop.requirement.0172.created-run-readable",
35
+ "openwop.it.v2-dual-stack-negotiation.a-run-created-through-v1-runs-with-no-header-is-readable-through-runs-under-open": "openwop.requirement.0172.dual-stack-negotiation.cross-major-read",
36
+ "openwop.it.v2-dual-stack-negotiation.an-unlisted-major-is-406-protocol-version-unsupported-with-details-protocolversi": "openwop.requirement.0172.dual-stack-negotiation.unlisted-major",
37
+ "openwop.it.v2-dual-stack-negotiation.openwop-version-2-0-on-a-v1-path-is-400-protocol-version-mismatch": "openwop.requirement.0172.dual-stack-negotiation.v1-path-mismatch",
38
+ "openwop.it.v2-dual-stack-negotiation.every-response-reports-the-contract-that-produced-it": "openwop.requirement.0172.dual-stack-negotiation.response-header",
39
+ "openwop.it.v2-effect-identity-business-key.get-runs-runid-effects-validates-and-every-row-is-keyed-on-business-identity-or": "openwop.requirement.0173.effect-identity-business-key",
40
+ "openwop.it.v2-effect-identity-business-key.the-same-provider-key-is-presented-across-two-transport-retries": "openwop.requirement.0173.effect-identity-business-key.retry",
41
+ "openwop.it.v2-effect-seam-manifest.get-host-effect-seams-validates-and-every-row-is-guarded": "openwop.requirement.0173.effect-seam-manifest",
42
+ "openwop.it.v2-effect-seam-manifest.the-replay-facet-names-the-manifest-address-as-the-constant-host-effect-seams": "openwop.requirement.0173.effect-seam-manifest.facet",
43
+ "openwop.it.v2-effect-seam-no-refire.driving-one-seam-of-each-kind-observes-no-re-fire-on-replay": "openwop.requirement.0173.effect-seam-no-refire",
44
+ "openwop.it.v2-enum-growth-rule.the-envelope-accepts-a-registered-but-unknown-to-this-suite-vendor-member": "openwop.requirement.0171.enum-growth-rule.unknown-member-accepted",
45
+ "openwop.it.v2-enum-growth-rule.the-reserved-prefix-and-a-malformed-member-are-rejected": "openwop.requirement.0171.enum-growth-rule.reserved-rejected",
46
+ "openwop.it.v2-era-2-append-vocabulary.a-host-append-to-an-open-era-2-run-keeps-the-log-in-v1-vocabulary-so-the-whole-l": "openwop.requirement.0176.era-2-append-vocabulary",
47
+ "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": "openwop.requirement.0176.era-2-append-vocabulary",
48
+ "openwop.it.v2-era-key.a-run-created-now-carries-eventlogschemaversion-3-on-its-snapshot": "openwop.requirement.0176.era-key",
49
+ "openwop.it.v2-era-key.discovery-advertises-the-value-the-host-writes-for-new-runs-and-nothing-else": "openwop.requirement.0176.era-key.discovery",
50
+ "openwop.it.v2-era-stamp-universal.discovery-advertises-an-era-the-host-actually-writes-and-a-new-run-carries-it": "openwop.requirement.0176.era-stamp-universal",
51
+ "openwop.it.v2-era-stamp-universal.a-v2-era-host-stamps-3-so-its-new-runs-are-never-read-as-the-v1-era": "openwop.requirement.0176.era-stamp-universal",
52
+ "openwop.it.v2-error-registry.an-error-response-is-the-closed-envelope-with-a-registered-code-at-its-registere": "openwop.requirement.0171.error-registry.envelope",
53
+ "openwop.it.v2-error-registry.retry-timing-is-never-spelled-inside-details": "openwop.requirement.0171.error-registry.no-retry-details",
54
+ "openwop.it.v2-error-registry.a-429-rate-limited-response-carries-retry-after": "openwop.requirement.0171.error-registry.retry-after",
55
+ "openwop.it.v2-event-type-closed.every-emitted-type-is-a-registered-protocol-type-or-a-vendor-type-under-an-adver": "openwop.requirement.0171.event-type-closed.type",
56
+ "openwop.it.v2-event-type-closed.every-emitted-event-validates-as-a-runeventdoc": "openwop.requirement.0171.event-type-closed.envelope",
57
+ "openwop.it.v2-fork-a-v1-run.the-fork-of-an-era-2-run-is-accepted-and-its-prefix-is-byte-equivalent-to-the-tr": "openwop.requirement.0176.fork-a-v1-run.prefix",
58
+ "openwop.it.v2-fork-a-v1-run.run-started-on-the-fork-carries-the-legacy-subject-where-the-parent-had-none": "openwop.requirement.0176.fork-a-v1-run.legacy-subject",
59
+ "openwop.it.v2-form-when-reuses-edge-conditions.a-field-when-is-the-edge-condition-grammar-type-left-right-and-no-other-expressi": "openwop.requirement.0177.form-when-reuses-edge-conditions",
60
+ "openwop.it.v2-header-scheme.no-removed-header-family-appears-on-any-response": "openwop.requirement.0171.header-scheme.no-legacy-headers",
61
+ "openwop.it.v2-header-scheme.openwop-version-names-the-contract-on-every-response": "openwop.requirement.0171.header-scheme.version-on-every-response",
62
+ "openwop.it.v2-id-grammar.every-id-on-the-run-snapshot-matches-its-ids-schema-json-kind": "openwop.requirement.0170.id-grammar.snapshot",
63
+ "openwop.it.v2-id-grammar.every-id-on-the-run-events-matches-its-ids-schema-json-kind": "openwop.requirement.0170.id-grammar.events",
64
+ "openwop.it.v2-id-grammar.a-run-id-whose-tenant-segment-is-not-the-caller-s-is-refused": "openwop.requirement.0170.id-grammar.tenant-binding",
65
+ "openwop.it.v2-idempotency-key-grammar.a-22-character-key-inside-the-grammar-is-accepted": "openwop.requirement.0170.idempotency-key-grammar.valid-accepted",
66
+ "openwop.it.v2-idempotency-key-grammar.a-key-outside-the-grammar-is-refused-with-400-idempotency-key-invalid": "openwop.requirement.0170.idempotency-key-grammar.invalid-refused",
67
+ "openwop.it.v2-interrupt-token-scheme.a-token-without-the-ow2-prefix-is-refused": "openwop.requirement.0170.interrupt-token-scheme.unprefixed-refused",
68
+ "openwop.it.v2-interrupt-token-scheme.a-well-formed-token-under-a-kid-the-host-does-not-hold-is-401-interrupt-token-in": "openwop.requirement.0170.interrupt-token-scheme.unheld-kid-refused",
69
+ "openwop.it.v2-lane-issuer-advertised.every-auth-lanes-member-names-lane-issuers-revocation-and-minimumassurance": "openwop.requirement.0170.lane-issuer-advertised.members",
70
+ "openwop.it.v2-lane-issuer-advertised.a-windowed-revocation-rule-advertises-revocationwindowseconds": "openwop.requirement.0170.lane-issuer-advertised.window",
71
+ "openwop.it.v2-legacy-profiles-absent.no-legacy-id-appears-in-a2a-profiles-or-mcp-profiles-and-each-id-matches-its-fac": "openwop.requirement.0175.legacy-profiles-absent",
72
+ "openwop.it.v2-legacy-profiles-absent.no-root-profiles-exists-in-the-v2-representation": "openwop.requirement.0175.legacy-profiles-absent.no-root-profiles",
73
+ "openwop.it.v2-manifest-ceiling-refused.an-engines-openwop-range-with-no-upper-bound-1-0-0-reads-2-0-0-and-is-refused-at": "openwop.requirement.0177.manifest-ceiling-refused.unbounded",
74
+ "openwop.it.v2-manifest-ceiling-refused.a-v1-ceiling-1-0-0-2-0-0-is-refused-at-install-with-pack-engine-unsupported": "openwop.requirement.0177.manifest-ceiling-refused.v1-ceiling",
75
+ "openwop.it.v2-manifest-ceiling-refused.a-range-admitting-the-host-major-2-0-0-3-0-0-installs": "openwop.requirement.0177.manifest-ceiling-refused.v2-installs",
76
+ "openwop.it.v2-manifest-hatch-carried.an-x-field-inside-agents-0-of-a-node-pack-is-ignored-not-rejected-the-pack-insta": "openwop.requirement.0177.manifest-hatch-carried.agents-x-field",
77
+ "openwop.it.v2-manifest-hatch-carried.an-x-field-inside-a-prompt-template-of-a-prompt-pack-is-ignored-not-rejected-the": "openwop.requirement.0177.manifest-hatch-carried.prompt-template-x-field",
78
+ "openwop.it.v2-min-client-version.a-client-below-minclientversion-is-refused-with-426-client-version-unsupported": "openwop.requirement.0172.min-client-version",
79
+ "openwop.it.v2-minimum-version-refused.an-a2a-peer-offering-only-a-version-below-minimumversion-is-refused": "openwop.requirement.0175.minimum-version-refused",
80
+ "openwop.it.v2-minimum-version-refused.an-mcp-server-offering-only-a-revision-below-minimumrevision-is-refused": "openwop.requirement.0175.minimum-version-refused.mcp",
81
+ "openwop.it.v2-mrtr-rounds-ceiling.mcp-mrtr-maxrounds-is-an-integer-ceiling-in-1-16": "openwop.requirement.0175.mrtr-rounds-ceiling",
82
+ "openwop.it.v2-mrtr-rounds-ceiling.round-maxrounds-1-is-refused-with-mcp-mrtr-rounds-exceeded": "openwop.requirement.0175.mrtr-rounds-ceiling.refused",
83
+ "openwop.it.v2-no-transport-advertisement.the-v2-root-carries-no-supportedtransports-and-no-grpc-block": "openwop.requirement.0175.no-transport-advertisement",
84
+ "openwop.it.v2-owner-subject-required.owner-subject-is-present-and-validates-against-subject-schema-json": "openwop.requirement.0170.owner-subject-required.subject",
85
+ "openwop.it.v2-owner-subject-required.owner-carries-no-principal-principalkind": "openwop.requirement.0170.owner-subject-required.no-principal",
86
+ "openwop.it.v2-pack-isolation.sandbox-isolationmodel-names-a-real-mechanism-never-node-vm": "openwop.requirement.0173.pack-isolation",
87
+ "openwop.it.v2-pack-isolation.node-pack-sandbox-timeout-exceeding-wallclocklimitms-fails-with-sandbox-timeout": "openwop.requirement.0173.pack-isolation.timeout",
88
+ "openwop.it.v2-pack-isolation.node-pack-sandbox-memory-cap-exceeding-memorylimitbytes-fails-with-sandbox-memor": "openwop.requirement.0173.pack-isolation.memory-cap",
89
+ "openwop.it.v2-pack-isolation.node-pack-sandbox-isolated-context-a-pack-cannot-mutate-state-another-invocation": "openwop.requirement.0173.pack-isolation.isolated-context",
90
+ "openwop.it.v2-payload-registry-closed.every-registered-event-carries-a-payload-that-validates-against-its-typeindex-en": "openwop.requirement.0171.payload-registry-closed",
91
+ "openwop.it.v2-peer-dependency-declared.a-peerdependencies-key-the-declaration-file-does-not-name-is-refused-with-pack-p": "openwop.requirement.0177.peer-dependency-declared.undefined-key",
92
+ "openwop.it.v2-peer-dependency-declared.an-alias-table-row-host-fs-fs-resolves-through-the-overlap-and-the-pack-installs": "openwop.requirement.0177.peer-dependency-declared.alias-overlap",
93
+ "openwop.it.v2-pinned-run-disposition.a-pin-naming-a-change-id-the-host-does-not-implement-is-cancelled-with-v1-pin-un": "openwop.requirement.0176.pinned-run-disposition.cancelled",
94
+ "openwop.it.v2-pinned-run-disposition.a-pin-naming-a-change-id-the-host-still-implements-continues-under-the-reader-ru": "openwop.requirement.0176.pinned-run-disposition.continued",
95
+ "openwop.it.v2-poll-cursor-v2.the-response-is-the-closed-runid-events-lastsequence-status-isterminal": "openwop.requirement.0171.poll-cursor-v2.shape",
96
+ "openwop.it.v2-poll-cursor-v2.omitting-the-cursor-returns-from-the-first-event-sequence-0": "openwop.requirement.0171.poll-cursor-v2.from-first",
97
+ "openwop.it.v2-poll-cursor-v2.aftersequence-n-returns-only-events-with-sequence-n": "openwop.requirement.0171.poll-cursor-v2.after-sequence",
98
+ "openwop.it.v2-poll-cursor-v2.a-cursor-past-the-end-of-the-log-returns-200-with-an-empty-events-array": "openwop.requirement.0171.poll-cursor-v2.past-end",
99
+ "openwop.it.v2-preferred-version-default.preferredversion-is-a-member-of-protocolversions": "openwop.requirement.0172.preferred-version-default.member",
100
+ "openwop.it.v2-preferred-version-default.a-header-less-get-well-known-openwop-is-the-preferredversion-representation": "openwop.requirement.0172.preferred-version-default.headerless",
101
+ "openwop.it.v2-preferred-version-default.through-the-overlap-preferredversion-names-a-1-x-member": "openwop.requirement.0172.preferred-version-default.overlap",
102
+ "openwop.it.v2-profiles-derived-only.the-v2-document-carries-no-root-profiles": "openwop.requirement.0169.profiles-derived-only.no-root-profiles",
103
+ "openwop.it.v2-profiles-derived-only.every-registry-profile-is-derivable-from-the-document-the-discovery-core-predica": "openwop.requirement.0169.profiles-derived-only.derivable",
104
+ "openwop.it.v2-provider-conflict.two-definitions-of-one-bare-provider-id-fail-closed-the-later-registration-is-re": "openwop.requirement.0177.provider-conflict.fail-closed",
105
+ "openwop.it.v2-provider-conflict.the-qualified-form-packname-acme-widgets-resolves-to-the-named-pack-s-definition": "openwop.requirement.0177.provider-conflict.qualified-form",
106
+ "openwop.it.v2-refresh-sla.refreshedat-on-each-embedded-protocol-facet-is-within-90-days-of-now": "openwop.requirement.0175.refresh-sla",
107
+ "openwop.it.v2-relaxation-recorded.a-bundle-recording-a-relaxation-cannot-certify-the-relaxed-profile": "openwop.requirement.0173.relaxation-recorded",
108
+ "openwop.it.v2-relaxation-recorded.durability-is-a-closed-set-session-deployment-persisted": "openwop.requirement.0173.relaxation-recorded.durability-closed",
109
+ "openwop.it.v2-revocation-honored.a-revoked-next-request-credential-is-refused-on-the-next-request-with-credential": "openwop.requirement.0170.revocation-honored",
110
+ "openwop.it.v2-run-annotation-not-event.an-annotation-is-created-and-listed-and-never-enters-the-run-s-event-log": "openwop.requirement.0170.run-annotation-not-event",
111
+ "openwop.it.v2-run-bulk-cancel.101-ids-are-refused-400-validation-error-with-details-maxrunids": "openwop.requirement.0170.run-bulk-cancel",
112
+ "openwop.it.v2-run-bulk-cancel.results-come-back-in-request-order-each-id-processed-independently-a-foreign-id": "openwop.requirement.0170.run-bulk-cancel",
113
+ "openwop.it.v2-run-cancel.cancelling-a-terminal-run-is-refused-409-run-terminal": "openwop.requirement.0170.run-cancel",
114
+ "openwop.it.v2-run-cancel.cancelling-a-running-run-answers-200-with-cancelling-cancelled-emits-run-cancell": "openwop.requirement.0170.run-cancel",
115
+ "openwop.it.v2-run-completed-outputs.the-run-completed-event-of-a-run-that-completed-carries-an-outputs-object": "openwop.requirement.0171.run-completed-outputs",
116
+ "openwop.it.v2-run-diff-identical.two-identical-logs-diff-to-divergedatseq-null-and-empty-eventdiffs-in-a-response": "openwop.requirement.0170.run-diff-identical",
117
+ "openwop.it.v2-run-fork-prefix.a-replay-fork-inherits-exactly-0-fromseq-the-event-at-fromseq-is-re-executed-nev": "openwop.requirement.0170.fork-prefix-boundary",
118
+ "openwop.it.v2-run-fork-refusals.replay-overlay-400-a-fromseq-outside-the-log-422-fork-point-invalid-a-valid-repl": "openwop.requirement.0170.run-fork-refusals",
119
+ "openwop.it.v2-run-options-limits.odd-format-tags-and-free-form-metadata-are-accepted-and-surface-unchanged-on-the": "openwop.requirement.0171.run-options-limits",
120
+ "openwop.it.v2-run-pause-resume.pause-and-resume-on-a-terminal-run-answer-409-with-an-error-envelope": "openwop.requirement.0170.run-pause-resume",
121
+ "openwop.it.v2-run-pause-resume.a-running-run-pauses-202-paused-refuses-a-second-pause-409-resumes-202-running-a": "openwop.requirement.0170.run-pause-resume",
122
+ "openwop.it.v2-run-snapshot-etag.a-matching-if-none-match-receives-304-with-no-body-and-the-version-header-a-non": "openwop.requirement.0170.run-snapshot-etag",
123
+ "openwop.it.v2-sse-last-event-id.last-event-id-resumes-at-the-next-sequence-never-re-emits-the-resumption-point-l": "openwop.requirement.0171.sse-last-event-id",
124
+ "openwop.it.v2-stream-mode-refusal.a-value-outside-the-pattern-a-forbidden-combination-and-an-unimplemented-mode-ar": "openwop.requirement.0171.stream-mode-refusal",
125
+ "openwop.it.v2-stream-mode-refusal.updates-streams-200-text-event-stream-with-frames-bufferms-yields-at-least-one-b": "openwop.requirement.0171.stream-mode-refusal",
126
+ "openwop.it.v2-stream-sse-projection.every-data-frame-on-the-major-2-stream-carries-the-tenant-bound-runid-of-the-run": "openwop.requirement.0171.stream-sse-projection",
127
+ "openwop.it.v2-subject-link-record.the-link-formed-across-scim-and-saml-is-a-closed-subjectlink-record": "openwop.requirement.0170.subject-link-record.shape",
128
+ "openwop.it.v2-subject-link-record.deactivation-sets-deniedat-and-the-saml-decision-path-consults-it": "openwop.requirement.0170.subject-link-record.denied-at",
129
+ "openwop.it.v2-unmapped-type-refused.poll-over-a-log-with-an-unmapped-unprefixed-type-fails-with-500-event-type-unmap": "openwop.requirement.0176.unmapped-type-refused",
130
+ "openwop.it.v2-unmapped-type-refused.a-registered-vendor-type-the-codemap-does-not-name-is-read-under-its-own-name-un": "openwop.requirement.0176.vendor-type-passthrough",
131
+ "openwop.it.v2-unmapped-type-refused.a-fork-of-the-same-log-is-refused-for-the-same-reason-the-rule-binds-every-reade": "openwop.requirement.0176.unmapped-type-refused.fork",
132
+ "openwop.it.v2-v1-events-translated.poll-reads-an-era-2-log-with-v2-type-names-and-sequence-0-present": "openwop.requirement.0176.v1-events-translated.poll",
133
+ "openwop.it.v2-v1-events-translated.sse-reads-the-same-era-2-log-with-v2-type-names": "openwop.requirement.0176.v1-events-translated.sse",
134
+ "openwop.it.v2-v1-events-translated.a-fork-of-the-era-2-run-reads-its-inherited-prefix-with-v2-type-names": "openwop.requirement.0176.v1-events-translated.fork",
135
+ "openwop.it.v2-v1-signed-webhook-accepted.the-webhooks-facet-lists-scheme-v1-the-scheme-a-v1-signed-delivery-is-verified-u": "openwop.requirement.0176.v1-signed-webhook-accepted.facet",
136
+ "openwop.it.v2-v1-signed-webhook-accepted.the-host-s-v2-receiver-path-accepts-an-x-openwop-only-scheme-v1-delivery-and-ref": "openwop.requirement.0176.v1-signed-webhook-accepted",
137
+ "openwop.it.v2-version-header-honored.openwop-version-is-honored-or-refused-never-ignored": "openwop.requirement.0172.version-header-honored",
138
+ "openwop.it.v2-version-header-honored.a-host-advertising-two-majors-serves-two-different-representations": "openwop.requirement.0172.version-header-honored",
139
+ "openwop.it.v2-webhook-durable-delivery.a-failed-attempt-is-retried-and-the-event-is-delivered-at-least-once": "openwop.requirement.0173.webhook-durable-delivery",
140
+ "openwop.it.v2-webhook-durable-delivery.an-exhausted-delivery-is-dead-lettered-never-dropped": "openwop.requirement.0173.webhook-durable-delivery.dead-letter",
141
+ "openwop.it.v2-well-known-one-resource.a-header-less-get-and-an-openwop-version-2-0-get-are-two-representations-of-one": "openwop.requirement.0176.well-known-one-resource",
142
+ "openwop.it.v2-well-known-one-resource.the-v2-representation-carries-no-capabilities-wrapper-no-dotted-host-key-no-prof": "openwop.requirement.0176.well-known-one-resource.v2-representation",
143
+ "openwop.it.v2-alias-covers-reword.the-corpus-gate-script-exits-0": "openwop.requirement.0168.alias-covers-reword",
144
+ "openwop.it.v2-bundle-signature-attributable.the-v2-discovery-root-publishes-signingkeys-the-surface-the-rfc-named": "openwop.requirement.0168.bundle-signature-attributable.surface",
145
+ "openwop.it.v2-bundle-signature-attributable.the-front-door-gate-verifies-the-attestation-instead-of-inspecting-the-string": "openwop.requirement.0168.bundle-signature-attributable.verifies",
146
+ "openwop.it.v2-bundle-signature-attributable.the-four-signature-outcomes-stay-distinct-collapsing-any-two-restores-the-defect": "openwop.requirement.0168.bundle-signature-attributable.outcomes",
147
+ "openwop.it.v2-bundle-signature-attributable.signingkeys-is-reachable-from-a-v1-root-too-a-bundle-is-v3-regardless-of-major": "openwop.requirement.0168.bundle-signature-attributable.v1-root",
148
+ "openwop.it.v2-corpus-tag-pinned.every-vendoring-consumer-in-the-cross-repo-inventory-records-a-published-corpus": "openwop.requirement.0176.corpus-tag-pinned",
149
+ "openwop.it.v2-declaration-parity.the-corpus-gate-script-exits-0": "openwop.requirement.0169.declaration-parity",
150
+ "openwop.it.v2-eos-clock.the-clock-file-is-current-with-the-matrix-and-the-public-history": "openwop.requirement.0174.v1-eos-clock",
151
+ "openwop.it.v2-eos-clock.check-removal-dates-fails-the-v1-tree-sources-of-v1-end-of-support-rows-once-the": "openwop.requirement.0174.v1-eos-clock",
152
+ "openwop.it.v2-eos-clock.check-removal-dates-passes-under-the-same-clock-when-today-is-before-the-date-an": "openwop.requirement.0174.v1-eos-clock",
153
+ "openwop.it.v2-error-registry-prose-parity.errors-md-states-the-registry-count-it-was-generated-from-and-renders-every-code": "openwop.requirement.0171.error-registry-prose-parity",
154
+ "openwop.it.v2-event-naming-rule.every-closed-enum-v2-event-type-is-two-kebab-segments-and-every-recorded-artifac": "openwop.requirement.0171.event-naming-rule",
155
+ "openwop.it.v2-event-naming-rule.the-closed-enum-and-the-vendor-branch-are-disjoint-every-registered-type-validat": "openwop.requirement.0171.event-naming-rule",
156
+ "openwop.it.v2-events-channel-parity.the-corpus-gate-script-exits-0": "openwop.requirement.0171.events-channel-parity",
157
+ "openwop.it.v2-externally-gated-never-stable.the-corpus-gate-script-exits-0": "openwop.requirement.0169.externally-gated-never-stable",
158
+ "openwop.it.v2-id-kinds-bound.every-v2-id-field-refs-its-kind-and-every-id-property-is-triaged": "openwop.requirement.0170.id-kinds-bound",
159
+ "openwop.it.v2-one-signing-scheme.registry-version-manifest-signing-has-exactly-one-scheme-ed25519-canonical-json": "openwop.requirement.0177.one-signing-scheme",
160
+ "openwop.it.v2-path-parity.the-corpus-gate-script-exits-0": "openwop.requirement.0172.path-parity",
161
+ "openwop.it.v2-release-identity.the-corpus-gate-script-exits-0": "openwop.requirement.0172.release-identity",
162
+ "openwop.it.v2-req-only-assertions.the-corpus-gate-script-exits-0": "openwop.requirement.0168.req-only-assertions",
163
+ "openwop.it.v2-retention-floors.the-registry-validates-and-the-script-prints-the-floor-state-offline": "openwop.requirement.0174.retention-floors",
164
+ "openwop.it.v2-retention-floors.a-pinned-old-major-version-missing-from-its-registry-while-the-floor-is-open-fai": "openwop.requirement.0174.retention-floors",
165
+ "openwop.it.v2-retention-floors.the-same-probe-with-every-pinned-version-present-passes-while-the-floor-is-open": "openwop.requirement.0174.retention-floors",
166
+ "openwop.it.v2-seams-not-in-canonical.the-corpus-gate-script-exits-0": "openwop.requirement.0168.seams-not-in-canonical",
167
+ "openwop.it.v2-security-defaults-table.the-obligation-table-has-a-witness-column-and-no-row-is-unwitnessable": "openwop.requirement.0173.security-defaults-table",
168
+ "openwop.it.v2-spec-artifacts-digest.the-corpus-gate-script-exits-0": "openwop.requirement.0168.spec-artifacts-digest",
169
+ "openwop.it.v2-threat-model-template.the-corpus-gate-script-exits-0": "openwop.requirement.0173.threat-model-template",
170
+ "openwop.it.v2-witness-required.the-corpus-gate-script-exits-0": "openwop.requirement.0168.witness-required"
6
171
  }
7
172
  }