@openwop/openwop-conformance 2.0.9 → 2.0.11

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,50 @@
1
1
  # `@openwop/openwop-conformance` Changelog
2
2
 
3
+ ## [2.0.11] — 2026-09-10 — the emitter scrubbed the key id it was required to publish
4
+
5
+ `--certify` fix. `evidenceSecretsFromEnv` selected every `OPENWOP_*` variable
6
+ whose name matched `/(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)/` and replaced its
7
+ value throughout the bundle after `discovery.sha256` was taken.
8
+ `OPENWOP_BUNDLE_SIGNING_KEY_ID` — the variable `--help` offers — matches on
9
+ `KEY`, and its value is the identifier RFC 0168 §E.2 requires
10
+ `discovery.document.signingKeys[].keyId` to publish. The embedded document lost
11
+ its keyId, the digest kept it, and the emitter rejected its own output
12
+ (`discovery.document ≠ discovery.sha256`). Three MyndHyve cuts on 2.0.8 and
13
+ 2.0.9 failed on this alone; the same host passed the day before with the id
14
+ on `--signing-key-id`.
15
+
16
+ - `*_ID` variable names are excluded from the classifier — an identifier of a
17
+ key is not the key.
18
+ - The emitter passes the keyId it is about to publish as `except`, so it
19
+ survives whichever variable carried it.
20
+ - On a self-verification failure the rejected bundle is written to
21
+ `<out>.rejected.json` and named on stderr, instead of being discarded — the
22
+ diagnostic a host had to patch a scratch copy of this CLI to obtain.
23
+ - New redaction leg `openwop.it.certification-bundle-redaction.key-identifier-never-scrubbed`.
24
+
25
+ No scenario logic changes.
26
+
27
+ ## [2.0.10] — 2026-09-10 — the bare-id leg
28
+
29
+ One new leg in `v2-id-grammar` (`openwop.requirement.0170.id-grammar.bare-id`):
30
+ the run this caller just created is requested again with its tenant segment
31
+ stripped — the v1 spelling of the same resource. While the host advertises a
32
+ `1.x` member the leg requires 200 with the **bound** id in the body (the
33
+ overlap affordance `v2-dual-stack-negotiation` has required since rc.44, now
34
+ stated in `identity.md` §5); once no `1.x` member is advertised it requires
35
+ `400 validation_error`. The branch is read from live discovery.
36
+
37
+ For the record: the first draft of this leg required 400 unconditionally, on a
38
+ steward ruling given on the bus the same day. Running it against the reference
39
+ host turned `v2-dual-stack-negotiation` and `v2-compensation-read-projection`
40
+ red — the suite already encoded the admission in six places and the prose had
41
+ never said so. The instrument was right; the ruling was withdrawn and the
42
+ prose now matches the instrument.
43
+
44
+ No other scenario changes. The pinned `@openwop/spec-artifacts` peer moves to
45
+ `2.0.10` with the corpus (`renderEndpoint` default, off-origin facets, the §5
46
+ paragraph).
47
+
3
48
  ## [2.0.9] — 2026-09-09 — the rule this suite measures now says what it measures
4
49
 
5
50
  No scenario logic changes, no assertion changes, and no host disposition
package/dist/cli.js CHANGED
@@ -643,13 +643,20 @@ async function runCertify(args, baseUrl, apiKey) {
643
643
  }
644
644
  v3.verifierSignature = verifierSign(unsigned, vk, vkId);
645
645
  }
646
- const secrets3 = evidenceSecretsFromEnv(process.env, [apiKey, signingKeyPem]);
646
+ // The published keyId is never a secret, whichever variable carried it
647
+ // (evidenceSecretsFromEnv: the `_ID` exclusion is the rule, `except` is
648
+ // the belt for a host that names its env var differently).
649
+ const secrets3 = evidenceSecretsFromEnv(process.env, [apiKey, signingKeyPem], [keyId]);
647
650
  const scrubbed3 = scrubEvidence(v3, secrets3);
648
651
  const v3Out = scrubbed3.value;
649
652
  const audit3 = verifyBundleV3(v3Out, { hostPublicKeyPem: publicKeyFromPrivate(signingKeyPem) });
650
653
  const emitterDefects = audit3.rejections.filter((r) => !['blocked-certified', 'relaxed-profile-certified', 'independent-unverifiable'].includes(r.kind));
651
654
  if (emitterDefects.length > 0) {
652
- process.stderr.write('openwop-conformance --certify: assembled v3 bundle FAILED self-verification (emitter defect):\n' + emitterDefects.map((r) => ` - [${r.kind}] ${r.detail}`).join('\n') + '\n');
655
+ // Keep the rejected bundle: a self-verification failure with nothing to
656
+ // inspect sent a host patching a scratch copy of this CLI to see it.
657
+ const rejectedPath = `${outPath}.rejected.json`;
658
+ writeFileSync(rejectedPath, `${JSON.stringify(v3Out, null, 2)}\n`);
659
+ process.stderr.write('openwop-conformance --certify: assembled v3 bundle FAILED self-verification (emitter defect):\n' + emitterDefects.map((r) => ` - [${r.kind}] ${r.detail}`).join('\n') + `\n the rejected bundle is at ${rejectedPath} (NOT a certification artifact — do not check it in)\n`);
653
660
  process.exit(2);
654
661
  }
655
662
  const v3Schema = JSON.parse(readFileSync(join(SCHEMAS_DIR, 'v2', 'certification-bundle.schema.json'), 'utf8'));
@@ -255,19 +255,33 @@ export function scrubEvidence(value, secrets) {
255
255
  * The secrets a `--certify` run must keep out of its own evidence: the
256
256
  * credential it was handed, plus every `OPENWOP_*` environment value that
257
257
  * names a key/token/secret/password, plus the conformance canary.
258
+ *
259
+ * An *identifier* of a key is not the key. `OPENWOP_BUNDLE_SIGNING_KEY_ID`
260
+ * (and `_VERIFIER_KEY_ID`) name the public `keyId` RFC 0168 §E.2 requires the
261
+ * host to publish in `discovery.document.signingKeys[]`; matching them on
262
+ * `KEY` scrubbed that id out of the embedded document AFTER `discovery.sha256`
263
+ * was taken, so every host that passed the id by env failed its own bundle's
264
+ * self-verification (`discovery.document` ≠ `discovery.sha256`) — suites 2.0.8
265
+ * and 2.0.9, found by MyndHyve 2026-09-10 across three cuts; the same host had
266
+ * passed the day before by passing the id as a flag. Names ending in `_ID`
267
+ * are excluded, and a caller may name values that MUST stay visible (`except`)
268
+ * regardless of which variable carried them.
258
269
  */
259
- export function evidenceSecretsFromEnv(env, extra = []) {
270
+ export function evidenceSecretsFromEnv(env, extra = [], except = []) {
271
+ const keep = new Set(except.filter((s) => s !== undefined && s.trim() !== ''));
260
272
  const out = new Set();
261
273
  for (const [k, v] of Object.entries(env)) {
262
274
  if (!k.startsWith('OPENWOP_'))
263
275
  continue;
264
276
  if (!/(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)/.test(k))
265
277
  continue;
266
- if (v !== undefined && v.trim() !== '')
278
+ if (/_ID$/.test(k))
279
+ continue; // a key's identifier is published, not secret
280
+ if (v !== undefined && v.trim() !== '' && !keep.has(v))
267
281
  out.add(v);
268
282
  }
269
283
  for (const s of extra)
270
- if (s !== undefined && s.trim() !== '')
284
+ if (s !== undefined && s.trim() !== '' && !keep.has(s))
271
285
  out.add(s);
272
286
  out.add(CONFORMANCE_SECRET_CANARY);
273
287
  return [...out];
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "package": "@openwop/spec-artifacts",
3
- "version": "2.0.9",
4
- "stampSha256": "2a7043538bf6c7ce6a3d5ad9836279f5754d9daa969e9dc0307d6c656fdfaf44"
3
+ "version": "2.0.11",
4
+ "stampSha256": "73ec5b7c6f2dceeefad0afb5d47446c3566a33ed6123db4bafcb26af38767fa6"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "2.0.9",
3
+ "version": "2.0.11",
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.9"
59
+ "@openwop/spec-artifacts": "2.0.11"
60
60
  }
61
61
  }
package/requirements.json CHANGED
@@ -3,10 +3,10 @@
3
3
  "generatedFrom": "src/scenarios/*.test.ts",
4
4
  "counts": {
5
5
  "files": 565,
6
- "tests": 2151,
7
- "withStableId": 2151,
6
+ "tests": 2153,
7
+ "withStableId": 2153,
8
8
  "interpolatedTitles": 0,
9
- "explicitIds": 2106
9
+ "explicitIds": 2108
10
10
  },
11
11
  "records": [
12
12
  {
@@ -7082,9 +7082,26 @@
7082
7082
  ]
7083
7083
  },
7084
7084
  {
7085
- "id": "openwop.it.certification-bundle-redaction.verifybundlev2-rejects-a-bundle-carrying-the-conformance-canary-anywhere-and-acc",
7085
+ "id": "openwop.it.certification-bundle-redaction.evidencesecretsfromenv-never-scrubs-a-key-identifier-id-variables-are-excluded-a",
7086
7086
  "file": "certification-bundle-redaction.test.ts",
7087
7087
  "line": 157,
7088
+ "title": "evidenceSecretsFromEnv never scrubs a key IDENTIFIER: *_ID variables are excluded and `except` values stay visible whichever variable carried them",
7089
+ "explicitId": "openwop.it.certification-bundle-redaction.key-identifier-never-scrubbed",
7090
+ "citations": [
7091
+ {
7092
+ "section": "RFC 0168 §E.2",
7093
+ "requirement": "the keyId a host publishes in discovery.document.signingKeys[] MUST survive evidence scrubbing — a *_ID variable is an identifier, not a secret"
7094
+ },
7095
+ {
7096
+ "section": "RFC 0168 §E.2",
7097
+ "requirement": "a value the emitter names as `except` (the keyId it is about to publish) is never scrubbed, whichever variable carried it"
7098
+ }
7099
+ ]
7100
+ },
7101
+ {
7102
+ "id": "openwop.it.certification-bundle-redaction.verifybundlev2-rejects-a-bundle-carrying-the-conformance-canary-anywhere-and-acc",
7103
+ "file": "certification-bundle-redaction.test.ts",
7104
+ "line": 177,
7088
7105
  "title": "verifyBundleV2 REJECTS a bundle carrying the conformance canary anywhere, and accepts the scrubbed twin",
7089
7106
  "explicitId": "openwop.it.certification-bundle-redaction.verifybundlev2-rejects-a-bundle-carrying-the-conformance-canary-anywhere-and-acc",
7090
7107
  "citations": [
@@ -7097,7 +7114,7 @@
7097
7114
  {
7098
7115
  "id": "openwop.it.certification-bundle-redaction.the-canary-in-a-discovery-document-key-is-still-a-rejection-keys-are-evidence",
7099
7116
  "file": "certification-bundle-redaction.test.ts",
7100
- "line": 172,
7117
+ "line": 192,
7101
7118
  "title": "the canary in a discovery-document KEY is still a rejection (keys are evidence)",
7102
7119
  "explicitId": "openwop.it.certification-bundle-redaction.the-canary-in-a-discovery-document-key-is-still-a-rejection-keys-are-evidence",
7103
7120
  "citations": [
@@ -7110,7 +7127,7 @@
7110
7127
  {
7111
7128
  "id": "openwop.it.certification-bundle-redaction.the-target-configuration-digest-is-a-digest-the-api-key-is-not-a-substring-of-an",
7112
7129
  "file": "certification-bundle-redaction.test.ts",
7113
- "line": 178,
7130
+ "line": 198,
7114
7131
  "title": "the target-configuration digest is a digest: the API key is not a substring of any string in the document",
7115
7132
  "explicitId": "openwop.it.certification-bundle-redaction.the-target-configuration-digest-is-a-digest-the-api-key-is-not-a-substring-of-an",
7116
7133
  "citations": [
@@ -26911,7 +26928,7 @@
26911
26928
  {
26912
26929
  "id": "openwop.it.v2-id-grammar.every-id-on-the-run-snapshot-matches-its-ids-schema-json-kind",
26913
26930
  "file": "v2-id-grammar.test.ts",
26914
- "line": 43,
26931
+ "line": 53,
26915
26932
  "title": "every id on the run snapshot matches its ids.schema.json kind",
26916
26933
  "explicitId": "openwop.requirement.0170.id-grammar.snapshot",
26917
26934
  "citations": [
@@ -26930,7 +26947,7 @@
26930
26947
  {
26931
26948
  "id": "openwop.it.v2-id-grammar.every-id-on-the-run-events-matches-its-ids-schema-json-kind",
26932
26949
  "file": "v2-id-grammar.test.ts",
26933
- "line": 53,
26950
+ "line": 63,
26934
26951
  "title": "every id on the run events matches its ids.schema.json kind",
26935
26952
  "explicitId": "openwop.requirement.0170.id-grammar.events",
26936
26953
  "citations": [
@@ -26948,7 +26965,7 @@
26948
26965
  {
26949
26966
  "id": "openwop.it.v2-id-grammar.a-run-id-whose-tenant-segment-is-not-the-caller-s-is-refused",
26950
26967
  "file": "v2-id-grammar.test.ts",
26951
- "line": 68,
26968
+ "line": 78,
26952
26969
  "title": "a run id whose tenant segment is not the caller's is refused",
26953
26970
  "explicitId": "openwop.requirement.0170.id-grammar.tenant-binding",
26954
26971
  "citations": [
@@ -26968,6 +26985,33 @@
26968
26985
  }
26969
26986
  ]
26970
26987
  },
26988
+ {
26989
+ "id": "openwop.it.v2-id-grammar.a-bare-unbound-run-id-in-a-major-2-path-admitted-under-the-caller-s-tenant-throu",
26990
+ "file": "v2-id-grammar.test.ts",
26991
+ "line": 88,
26992
+ "title": "a bare (unbound) run id in a major-2 path: admitted under the caller's tenant through the overlap, refused 400 validation_error after it",
26993
+ "explicitId": "openwop.requirement.0170.id-grammar.bare-id",
26994
+ "citations": [
26995
+ {
26996
+ "section": "spec/v2/core/identity.md §5",
26997
+ "requirement": null,
26998
+ "interpolated": true
26999
+ },
27000
+ {
27001
+ "section": "spec/v2/core/identity.md §5",
27002
+ "requirement": "a resource reached by its bare id MUST still be named by its bound projection in the major-2 response body (versioning.md §5) — the affordance is on the path parameter, never in a document"
27003
+ },
27004
+ {
27005
+ "section": "spec/v2/core/identity.md §5",
27006
+ "requirement": null,
27007
+ "interpolated": true
27008
+ },
27009
+ {
27010
+ "section": "spec/v2/core/errors.md",
27011
+ "requirement": "the refusal MUST carry validation_error — not id_tenant_mismatch (no segment to mismatch) and not not_found (a form the host no longer admits is not looked up)"
27012
+ }
27013
+ ]
27014
+ },
26971
27015
  {
26972
27016
  "id": "openwop.it.v2-idempotency-key-grammar.a-22-character-key-inside-the-grammar-is-accepted",
26973
27017
  "file": "v2-idempotency-key-grammar.test.ts",
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "_comment": "Provenance of @openwop/spec-artifacts (RFC 0168 §D.2). files: SHA-256 per file; the conformance suite compares the installed peer against dist/spec-artifacts.lock.json at start.",
3
3
  "package": "@openwop/spec-artifacts",
4
- "version": "2.0.9",
5
- "corpusTag": "v2.0.9",
4
+ "version": "2.0.11",
5
+ "corpusTag": "v2.0.11",
6
6
  "files": {
7
7
  "api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
8
8
  "api/asyncapi.yaml": "d5ecb9ee6114582be3b1f662c84bfac9ae96dae7bacb853e461168f70a8e1c7d",
9
9
  "api/grpc/openwop.proto": "c3e72bb17cba514ee98feb6434e6c9b6ea6795bfd086489ec69fd882dd1ad977",
10
10
  "api/openapi.yaml": "39081c59fb696159806b0f2f9a42e7e9ff830d622fcf2ad4159b21357580a955",
11
11
  "api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
12
- "api/seams-v2.yaml": "85832b378ab3396f031b1503d17a7d252dd2f9bbf753324e05601cf8d50c6b96",
13
- "api/v2/asyncapi.yaml": "029fccaec2975cf943e11465a9ea98149096d061da2893c438337906e647dc92",
14
- "api/v2/openapi.yaml": "a406c3723d88f520c387d5e47ff9cabb32ac0cac7b1825c8c768ab6ec0cf27bb",
12
+ "api/seams-v2.yaml": "94727fc973ebe2ed2fbe54dfec1300bc56d23b27f6d5f248672b68926921d64e",
13
+ "api/v2/asyncapi.yaml": "8d8728a51cd6113cabb5c0ee700ddcae4b8232633f86024fcbbba5818d47ccc8",
14
+ "api/v2/openapi.yaml": "aa98fa0c2efc8dfede85f94113e900b4bec5432d3b679b8e574a8d01a061a22e",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
16
  "schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
17
17
  "schemas/a2a-task-state.schema.json": "c9365918f993f943b4b619d42551eb066a1ed33a08d895d51b395432a5b1f1bc",
@@ -99,28 +99,28 @@
99
99
  "schemas/trigger-subscription.schema.json": "4f7b067f20032073b140c4bb15dc9a9914c4f6421a252ff1ff261cbdd8edf270",
100
100
  "schemas/ui-plugin-message.schema.json": "346638babf1f8ea7b161cde75c0038b518cd2cc5bf0631772e20a9b74c96b5dd",
101
101
  "schemas/v2/a2a-task-state.schema.json": "aa9bd0c15f653b7ecdb17427d61b1bccaf8156972c4b1a437dd3a8afb717b318",
102
- "schemas/v2/a2ui-surface-delta-frame.schema.json": "43c10ebfd13c987520f0448df92fe74b52b846833763dfbbd5d35636bf75e8d9",
103
- "schemas/v2/agent-deployment-transition.schema.json": "d9fe6f2fa4dda8598bab4e5c051f68473ac0729dc0ebf31605954dda7abf5a80",
104
- "schemas/v2/agent-deployment.schema.json": "dd1293ef8f20678bc36fc95fc4e5fcd135acbb10c61c1ddec8bc023e3f22622c",
105
- "schemas/v2/agent-eval-suite.schema.json": "b2875edfc28b39c104fbf6f854ecfb81fe2d90b18f828ffd8d3232ef883cd208",
106
- "schemas/v2/agent-inventory-response.schema.json": "bbf19724986a867a59ef51c115a43ece1cc4a003836b2f7f45dfefb3078db8f9",
107
- "schemas/v2/agent-manifest.schema.json": "006ca6163ca8f9dc9fb91ca1ce01bd4c523f118680f6cbad971a4ac09e975381",
108
- "schemas/v2/agent-org-chart.schema.json": "e64f1bcc6c0e9d3fc39c363e8e5e73b2b1fc49275b92f89b0cb941a7a80d67f2",
102
+ "schemas/v2/a2ui-surface-delta-frame.schema.json": "6889c5da3fe7e76cb614038c0eea6774776fa640448558b258eb87fb50cb9b64",
103
+ "schemas/v2/agent-deployment-transition.schema.json": "5fb65556576e3d8705a4834d6ab8ab47389123c40f206d39ba672bc03302c544",
104
+ "schemas/v2/agent-deployment.schema.json": "5581fea8ed8bf99ff05c8df9d3cc007fa2b94a446e7a37a68ea73a513d55b6cd",
105
+ "schemas/v2/agent-eval-suite.schema.json": "61b184eea19386243eef6e1d9e50d282ae453d71ed3259243d51d73285a16769",
106
+ "schemas/v2/agent-inventory-response.schema.json": "3f6ce47bab255391f3f546363bc75b3409175050ca8c3ce214be16d6eb67e338",
107
+ "schemas/v2/agent-manifest.schema.json": "0e6927f1d19ca404dffeb545faf81fa5b1acbaeb1c99b48dd5179071316407ce",
108
+ "schemas/v2/agent-org-chart.schema.json": "113b079e1dce95908467cbef7f83e076fdb82aab5b0678fc1a43e32bfc9cbadf",
109
109
  "schemas/v2/agent-ref.schema.json": "a86e98f7ffcbc4d7da145633e3010842e05e23e153d53c431d28dd72da0ec9de",
110
- "schemas/v2/agent-roster-entry.schema.json": "801c7d7aca7bd497df56fdaa68b671eef5193d2b96eb836a8bf438790cb1f0e2",
111
- "schemas/v2/agent-roster-response.schema.json": "682d1d1f4c184b14488a270de54c71a685e400e0184e88564cc01e2400367c5b",
110
+ "schemas/v2/agent-roster-entry.schema.json": "72132a80a3eb305e83727fbf6e105d7bd1790b481231d6ff864fd18d5554fc27",
111
+ "schemas/v2/agent-roster-response.schema.json": "f646b8a5af9ee884e1a86a9c84ec29f10dd3dbb275711ce79db27bee981d4a1d",
112
112
  "schemas/v2/ai-envelope.schema.json": "0dc7dfbf032a37239ffd81fc2d67f999a40782ff5074866a56e414164cdee809",
113
- "schemas/v2/annotation-create.schema.json": "c2fc19d055be29bc499e53c4975a562eb76981e12c521a82f63f5827ac6ff6c7",
114
- "schemas/v2/annotation.schema.json": "75c6785775d00e62bc1282626c2c27eafde0f93a01c7e80e4e5e985055907475",
113
+ "schemas/v2/annotation-create.schema.json": "ec32c22a243b343eeb6956a23be2d76ba5b50c8018ec3ec9d6efa5a1d9b0c792",
114
+ "schemas/v2/annotation.schema.json": "eb4813cdaa8e325412cfcaeeee5c8a28f3751c946dec64e546c7ba0ad3aad8d4",
115
115
  "schemas/v2/artifact-type-pack-manifest.schema.json": "85630d1b760d0b41cf5a6fad2acfada39abaf7f0befd95cc4334e1e883e9615d",
116
- "schemas/v2/audit-verify-result.schema.json": "cb54c4d89ba5dfb73f49aa8959a980be65c2dc2a87a12f71ba36d3e836fe85da",
116
+ "schemas/v2/audit-verify-result.schema.json": "a748dfadb5155adfde8a91045e76b0db6695005fe559035d71670fdd55edb3ba",
117
117
  "schemas/v2/budget-policy.schema.json": "c7449daeb6e1d95e7a047b8c2814d3d54748697c27046972834874867d9b5f4c",
118
- "schemas/v2/capabilities.schema.json": "792669b2cc7b1fd44ce9a4fd3e79696ad1277734e29299a4504b39eda15913ed",
118
+ "schemas/v2/capabilities.schema.json": "6219faf634cbd137824b66df3a1c71cc5fd964d56f8fc2f6089f226650dde6b5",
119
119
  "schemas/v2/certification-bundle.schema.json": "d2cbc5cadddff13630a528dc9673162f582c035c6a6663ca02c1ac53e8fec40c",
120
120
  "schemas/v2/channel-presence-payload.schema.json": "9475d46f358d21c19126e78204987a5633979917a2293653354ef5c5e044d54e",
121
121
  "schemas/v2/channel-written-payload.schema.json": "ccecff3c71a3275ad8db035ac0e04db25e6f8d23cf091ddbcd5a60d1be1cbb6b",
122
122
  "schemas/v2/chat-card-pack-manifest.schema.json": "8df0d9d273ae22efc6876d5586833f1d4c2e551edc3ac6cf6e8f0719329f03dd",
123
- "schemas/v2/compact-tool-descriptor.schema.json": "fc03f20c254c0ddc2cba7e7d5a6c46080ebd2cf9fae36f3c2510cbf88aadebdc",
123
+ "schemas/v2/compact-tool-descriptor.schema.json": "b5c418d72082d5b713bf318212a83397e5be98e56d25ca056302b41b8dd8273b",
124
124
  "schemas/v2/compensation-policy.schema.json": "b3c1142637f5bf980f2b0bb67adb47e8cb7abb99a0405a4b6816ccf5d9c089d6",
125
125
  "schemas/v2/compensation-projection.schema.json": "f46977c7076347b5473faa8c4bbe73d8321c33b33c98fff2f26ee7cbada4ed98",
126
126
  "schemas/v2/configurable.schema.json": "33a6884047e2490f723733bf8b74d3b9164e00374cfea6dc3eac5a70dc368861",
@@ -142,7 +142,7 @@
142
142
  "schemas/v2/envelopes/schema.response.schema.json": "d049a081267c06c28879c331b147f3ed821b14ccfd93005c67aff64db0a8c68e",
143
143
  "schemas/v2/envelopes/ui.a2ui-surface.schema.json": "0a06fbbdc8ba797a6818c79336a98e211ac5ba5747945b4e4098072cd3974d79",
144
144
  "schemas/v2/error-envelope.schema.json": "72d3332fad8fa36d07439c830acf352eefc6da2379374faa2ebbe49e98b8cb08",
145
- "schemas/v2/eval-summary.schema.json": "bcb165148531d33b7dc01de83da3a049d19ebbef6d12300b17a234c7b03d77d9",
145
+ "schemas/v2/eval-summary.schema.json": "9d31025752d0f67ad3b8a32e82a5314972c530ef9f780dd82e484ae8c5131198",
146
146
  "schemas/v2/export-bundle.schema.json": "bd28905a7729437ab7f2ad8bb33e13a371b2bf5605a7eb2962a0d246de312d69",
147
147
  "schemas/v2/form-content-pack-manifest.schema.json": "307884e57946703b6f86ec283034bf56beb68bea4a000743ebbc9eebeed31b07",
148
148
  "schemas/v2/frontend-plugin-manifest.schema.json": "fed6b8ddb3056c53b1bfc91525410a4445a3c37a8bf9eb9f6f9964ffea26d993",
@@ -151,29 +151,29 @@
151
151
  "schemas/v2/heartbeat-state-changed.schema.json": "eb68d0082aa996650e3a6b8ccf32457f959cebc7e6728cc7c331b9d171a4bef6",
152
152
  "schemas/v2/ids.schema.json": "05c98e3f8a72fa79be123765bd9c2ea7c6620188d69df8524160399225316cd6",
153
153
  "schemas/v2/localized-content-language-settings.schema.json": "92240f19a2b4b2f05ab41a797f703485a28813848035478e72f395edd04c1d35",
154
- "schemas/v2/localized-content-page-response.schema.json": "476b5f2775b42e35e679814a4ce846a7be1d5311d3a748c10df4275a78b0fb27",
155
- "schemas/v2/localized-content-page.schema.json": "717bb51fbc64d0b34eb0a4b35c8b463d08d505267bf251b4cbc67e0a016a567a",
154
+ "schemas/v2/localized-content-page-response.schema.json": "ef48329749758afb21e4d3cbc3f017abb911f75726819457f35b9f1390332c75",
155
+ "schemas/v2/localized-content-page.schema.json": "e0f97660121955d6705d935f34e748adb739ac70f8536a009cd8d6f303735151",
156
156
  "schemas/v2/localized-content-section.schema.json": "29c7bad0ad9ebc6c45211282e8afb3fdb35a4ca075ce0404b6e2c29f35fa5313",
157
157
  "schemas/v2/memory-entry.schema.json": "290576ae84f4fef4cdf975932b87963283b11c50ea3d2654b32cd96aa6537c3d",
158
158
  "schemas/v2/memory-list-options.schema.json": "f66703bccb68efb28391c273abb66aa117ee5b5ce475c477846ed700c336afcd",
159
159
  "schemas/v2/node-pack-manifest.schema.json": "0da826ca63a29026f7a9551748048104910e0d9962db6bad2b50b97a24a9ea30",
160
160
  "schemas/v2/orchestrator-decision.schema.json": "2da17538e84cfbe70151eb118f1ccf670cff58b8697148e394074d32ac4af6b4",
161
- "schemas/v2/org-chart-responsibility-view.schema.json": "ebac7d2889bfd659bcf30311efb0d6eb0c7e940b972b6b62da69c7351b5bb586",
161
+ "schemas/v2/org-chart-responsibility-view.schema.json": "8a2565e4cdc6737a3946a70799f93642808b92f9c8d56dcdff3a0782053b89a1",
162
162
  "schemas/v2/pack-lockfile.schema.json": "2685417702c105146b780cd99154296a46578fbb10d81c8c713348ee26fea898",
163
163
  "schemas/v2/prompt-kind.schema.json": "67251c937a5a91fb97c0422050e808dc0c162a9b8561366814cf87b0a7f62e00",
164
- "schemas/v2/prompt-pack-manifest.schema.json": "a51667da3d601043d2c33fddaafa572e9dd7e47b8840393742b80eaf6238a1a5",
164
+ "schemas/v2/prompt-pack-manifest.schema.json": "8e77d23840250640d26f2f99c1c5902e79bf4a6974cae2d6c6637176236bddb9",
165
165
  "schemas/v2/prompt-ref.schema.json": "11538fa36a7a652c93100cac4f1857eb4130137b546e3d94890618636d44b729",
166
166
  "schemas/v2/prompt-template.schema.json": "494fc83028cb3af5dbb8a943d0e83b2348f7775e9fa02369162bcb58a6f81559",
167
167
  "schemas/v2/proposal.schema.json": "cb5ea4fcb54be94fd2a92cf2799357dc73335181a89f5766f432d69e21cc3a80",
168
168
  "schemas/v2/registry-version-manifest.schema.json": "270704ebc6bae63be949efbf9503578938b103733db9da3cb8fcbee1cd7684dd",
169
- "schemas/v2/residency.schema.json": "af2658a7104cbcacd0e20a91226271c4a77f91ef46ae97612768fe743e6091d8",
170
- "schemas/v2/run-ancestry-response.schema.json": "907e36341ba83a2147a5d76963950b6dcd47506b663ecbccae9dfa553f3724d3",
171
- "schemas/v2/run-diff-response.schema.json": "6ab6cd265dc7e0a2086c6659c46aa0e7b43e6302949f0adac5d7deb1c7c0ecd3",
172
- "schemas/v2/run-event-payloads.schema.json": "3a7161af78b0dd2730309371f5ea8b889d92a37c708db05e4354209f12ffbe30",
169
+ "schemas/v2/residency.schema.json": "3fd23c9732beb140504e81560fc426eb6764d547e56da5fb92c5a5c7bf04db3a",
170
+ "schemas/v2/run-ancestry-response.schema.json": "334c17f2062dea62726ca1d07b8be3f169cdc422af843c1def5fb317039814c9",
171
+ "schemas/v2/run-diff-response.schema.json": "8bdb2045c274580038bd74781f7d062f5049839797c164a89de4ed89edc53a74",
172
+ "schemas/v2/run-event-payloads.schema.json": "1d3ccffc4e506d3109c17990e01976ba935e61672562684b9635743d56af3f57",
173
173
  "schemas/v2/run-event.schema.json": "1a9946374af9323b71635d00fed22abe02c300fa2cf3ae3e9c30f3e553e408ec",
174
- "schemas/v2/run-options.schema.json": "ff240805478158cbd2f661979c5eb4f5eda11f9c053b7ba587cb0211a9252d74",
174
+ "schemas/v2/run-options.schema.json": "f0e9f494d80086c2ed2cf9fe87df54e5b4e66601699c4a1089f4edbc7ba58867",
175
175
  "schemas/v2/run-orchestrator-decided-event.schema.json": "bb76e15ff72e669f12938684033b55dc567470e1a378e19ffc3fa0e4034ab5a7",
176
- "schemas/v2/run-snapshot.schema.json": "0ee8a50c6be210db415a7c4345298e70a6ae3dd0dd764ccc514bc1c501437586",
176
+ "schemas/v2/run-snapshot.schema.json": "65a3c82220da2e1a54324665d517dc0e627225f3d37ef75b43bef3fb7e3bac0e",
177
177
  "schemas/v2/security-advisory.schema.json": "6a382f737730a738d634266315e9d335e5760020a0cdd9f30727c2f987a01770",
178
178
  "schemas/v2/self-hosted-runner-dispatch-frame.schema.json": "d8129f11d4e731d17ea951e66faaea2086081d13958b62ceaaa359fd6637dd7d",
179
179
  "schemas/v2/self-hosted-runner-registration.schema.json": "93d60003a668cacb0eeac37699ca2884c6b79332d9494cb9e0a1752ac8f8a16c",
@@ -181,14 +181,14 @@
181
181
  "schemas/v2/subject-link.schema.json": "1fc941d6c14af886534009f2ffa6611e2035eb07e30b1df918c50d6bafff026c",
182
182
  "schemas/v2/subject.schema.json": "094e981fb20052a911393e219ea5f2065d8b8417b65080a70c5ea99fd960a8fe",
183
183
  "schemas/v2/suspend-request.schema.json": "28451d35f4f49a075d26b2be7e0d1cd2287a196b3d41819edf146fdc8c862756",
184
- "schemas/v2/tool-descriptor.schema.json": "c7f96ddb5c5a1cda5a40078b9b70dd0026658c42c92394c369c54a0427458527",
184
+ "schemas/v2/tool-descriptor.schema.json": "658b18ef173e0f12d732f92d4866a9b3daeb8f88b493db8b025a577e09dd681c",
185
185
  "schemas/v2/trigger-event.schema.json": "7227fb9a130c33374fa8eab35e4448c4e8f22c962b8f271781cf4dbb5e4288b7",
186
- "schemas/v2/trigger-subscription-registration.schema.json": "202f7ab9c29e78249e0ad04341ac53377b506d24a0cbfbb7bb54494e80165832",
186
+ "schemas/v2/trigger-subscription-registration.schema.json": "04bc314a07c7e8b565577cb8df5821d968ce2af915900dfdccd4a24e928d20db",
187
187
  "schemas/v2/trigger-subscription.schema.json": "074bec7f016aa9f4d60359c3a093296cc847045510f01e0a9dceaf4fa5084d12",
188
188
  "schemas/v2/ui-plugin-message.schema.json": "879d65867c01656b3962d64283e66364dcb8f626ad26c5fcf2d2d55a79ae0ce9",
189
189
  "schemas/v2/webhook-delivery.schema.json": "66025cd7b7899b6897b61c61e99ec2c554e232a6c9742124441adcd81912cef0",
190
190
  "schemas/v2/workflow-chain-pack-manifest.schema.json": "22801f14845a3df30660e6f31a41e1a3fbff2425bf56c8a475eab96f1c95882a",
191
- "schemas/v2/workflow-definition.schema.json": "da7e565cee6ecf3bcc9d46833bad224f579f8db8a378ae22ff3fbf58b1348f38",
191
+ "schemas/v2/workflow-definition.schema.json": "9aafddec32b84eb6aec3185be024850f1b9444c6193568771b0b636595fef136",
192
192
  "schemas/v2/workload-identity.schema.json": "1f589debfac6d70e0908da511550ec69a27b4bcc8314551cd843b6d923605efc",
193
193
  "schemas/v2/workspace-file-create.schema.json": "b6b5260857318c3b67de0231ed15e3a746f525880a763e7b36a7854178dd10d0",
194
194
  "schemas/v2/workspace-file.schema.json": "d2ed39e8c229dde33b56905c8df6f8b2ff056c97e1c5f615e3124baa54a6254f",
@@ -199,7 +199,7 @@
199
199
  "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
200
200
  "spec/v1/alias-detectors.json": "fee4594ef49953953ffcd0b3813300067d16b3e65ebff2aac722034ac9b3f545",
201
201
  "spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
202
- "spec/v1/core-standard-manifest.json": "63799de159579793a95daae66accdc4aa7149eb96994235b0841c0d2ffa132bc",
202
+ "spec/v1/core-standard-manifest.json": "ef8d7fe2710c243c6d2451720167d8da0bbbebb6fb0ee64a420a3efdc296d8fe",
203
203
  "spec/v1/deprecations.json": "1d5acb69a9b8ccb57275a95605f74aef1d920685f8407c9d382a46b59dc803bb",
204
204
  "spec/v1/deprecations.schema.json": "18c87e78bedc210431f795ae44c5b5d202f2f3317850d5cf86867d4f1fa1cdfb",
205
205
  "spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
@@ -211,27 +211,27 @@
211
211
  "spec/v1/migrations.schema.json": "886779aa6c22e646db097f5df210adb018a4dd14a7b815465a18c8a7056c8f72",
212
212
  "spec/v1/operation-path-manifest.json": "5f5f4e3842669371730ebbd1aace3fb794192615f0018dc144f484ba5db82ac3",
213
213
  "spec/v1/spec-gaps.json": "6cc9962c6b969f632e07a78b52a4f61447ff579e2990cbae989866f584a86042",
214
- "spec/v2/README.md": "453b56e0f9dd84889e90f3e25efda53e550275a112bc72a970252e00335b408b",
215
- "spec/v2/core/capabilities.md": "3ea74e9cfbca23723e4fadac7d53a44f59940dfe9942e086533103e3677507df",
216
- "spec/v2/core/conformance.md": "3cae1183d3155572004f192d0c64b921151f4f9905ce2a6a3e29f475334de688",
217
- "spec/v2/core/connection-packs.md": "460019f7bc9b5c7473eaa5e2825d8e28ff2941306fbd7ba5dfe4c9e174fcbe64",
218
- "spec/v2/core/errors.md": "acb7a04a0c4ac136eb8dcd8549e8bc6da19a80753bf81d903202c7008cb8802f",
219
- "spec/v2/core/events.md": "ceee1671e7b585026be61ffa210759157b292d3b56c352bef5d0de98a3db9983",
220
- "spec/v2/core/form-content-packs.md": "de63b1bb72bfde0c31aeb31d1f09d8f9297550604c771fd65f26d7459eac7be0",
221
- "spec/v2/core/headers.md": "18c09184e5b64c5f28e4aa8c028b13da074bd9a4ca8e6ae56290faa402c277c0",
222
- "spec/v2/core/idempotency.md": "9452888f1ed70ffafc4a4f6c770511b3d4e7277009d39b8b8bbca26f5a3c0a32",
223
- "spec/v2/core/identity.md": "3ba7016e9f78aac6f6ec5be8a2c577c0309e3e62e2ceb6070c2eabbfc7d1b507",
224
- "spec/v2/core/interop.md": "e5c166cd94050539a694cce01d4b733bd8bfbbf4d30584d6cd8ee1a85009afc5",
225
- "spec/v2/core/interrupt.md": "a320811c337cb498207cfdc87e24bea8715689101be299f4eb2ae1ea54e6f202",
226
- "spec/v2/core/overview.md": "6c3d32ded128237ddb9af12bcfaac4079162464906519d0f2861bba6c675ae18",
227
- "spec/v2/core/packs.md": "1c6bad59fd77a90bed9c904c7d59f1649dd21cf70986962194b10650533a46b5",
228
- "spec/v2/core/persistence.md": "8e4af460d7390945ffd88731edf69f95a462f55d6569a7d375bf802ca8ddff95",
229
- "spec/v2/core/replay.md": "5647bc44e994d9fcb2d2fc8acbb6c0f66e1262228b7efc5a1035eb1d3b38a23f",
230
- "spec/v2/core/runs.md": "ceae2f80fc915c12a22682205e2bec797d311b53dc8883000a4f02d002192778",
231
- "spec/v2/core/security-defaults.md": "0837314be15d4278f1a835b97f101ba794a025952ad39fd408c6e319756ebf71",
232
- "spec/v2/core/versioning.md": "d9506ec2e009a84c1ac02924af2867cf2d6b90c872902fe1b0979a8908e494d0",
233
- "spec/v2/core/webhooks.md": "9a946db3a17f72adba8ad493dc004c856237681f823303599dd45c83b522226b",
234
- "spec/v2/core/workflow-chain-packs.md": "e0e78926ebb6aa217cf35191d401a51903610469c5d0c464307888fb89e69037",
214
+ "spec/v2/README.md": "202d95478ebbc7eb5efd2c3016168054bc16b263085eadb42d132e50d80c2e40",
215
+ "spec/v2/core/capabilities.md": "fee5eb083ec5f6173c57e2673f4d5a264807f71ad00ef8847e51cefc552eea84",
216
+ "spec/v2/core/conformance.md": "dc42444698fdac31bdb8cb9de0c4d36b25ee770158b6e4bab59c67f19560fa35",
217
+ "spec/v2/core/connection-packs.md": "7f767bb12a39e737503625b163e088c432d2095c98a1040ae78044008101234f",
218
+ "spec/v2/core/errors.md": "b46467fe39cf87fb3514ae8d498939a61cd7df306e73eaf5112833a8b6afb740",
219
+ "spec/v2/core/events.md": "2f3aedf07e9267ff559cf48b106d433ce76d7ff1bb6fd5f4ddcaaa9edc3c0cb0",
220
+ "spec/v2/core/form-content-packs.md": "405486e105b6d3b0bf727317af3dfc91af06f727d5bdf8991c71ddeef61d9bae",
221
+ "spec/v2/core/headers.md": "882dfb34febcc669812c7cd1b0c640b6f40fc7a8e806cf49eef8d3ad1b8ec1c3",
222
+ "spec/v2/core/idempotency.md": "220faad1e01c49cd2f5ab4849c5cd276850a11213906de38caba96a72c132320",
223
+ "spec/v2/core/identity.md": "d2c87c8c59ac92cb1df1550afe910d069a21b6967a966c2d54b9c247a15646e9",
224
+ "spec/v2/core/interop.md": "54a0e2c8b4ada51d5fa37f3828777a1f0ccf67120af79fcfbfad4fab9e2fa9ba",
225
+ "spec/v2/core/interrupt.md": "c44bd714563224aa2c3806376a07b265017140be2643d7a3ac340198159ab36d",
226
+ "spec/v2/core/overview.md": "3fef48a6e5b2a6ba01b881d48a3b671d5345c33475d15555dae6946599c25eb9",
227
+ "spec/v2/core/packs.md": "00ea589b627f8027d39e4c99463530129815c973b437c8fbef1ad05d952051bf",
228
+ "spec/v2/core/persistence.md": "682f2bad8c87df8af021e276c02f502e3d1131465d332e960f46e5caa66dedf0",
229
+ "spec/v2/core/replay.md": "ebc4a530e583c31447e95457ca9a3f64e8a51d71ce838a66705b7ae88ee55ab7",
230
+ "spec/v2/core/runs.md": "de5ed935f86edb7f5f3aeb7ae1f714ec1b586786c0096773ef0f58514cbc7edb",
231
+ "spec/v2/core/security-defaults.md": "cf51b04fab31ef82376d08f33b5a62af9014be1e6db7240bf2493c37cf62d360",
232
+ "spec/v2/core/versioning.md": "e98bb5de4261f6fac71e22be9f3a5b5e88060ea2b6d1d88870e4af88b097801b",
233
+ "spec/v2/core/webhooks.md": "a1a32fb8f283f0bbc57cb35cdd5272b79657f1bb97df13a8f8daa232d06e8fc5",
234
+ "spec/v2/core/workflow-chain-packs.md": "004935d6776658d1c33d06fa4fe9aba40efae4a9ca1e5031d759316d3144adaf",
235
235
  "spec/v2/declaration.json": "bd8a1dcee899a4478ac96b52da6ee07e1cb5fce4465939ac32e19e52f4d5c6b2",
236
236
  "spec/v2/declaration.schema.json": "eac5f8080bd572f147bd57d0c4ac8ad73b14536d9236222490ae29a33b21e184",
237
237
  "spec/v2/errors.json": "f179414a92f30b5dadf26e9e57699649a67324bd7186d5287647d17605d137d3",
@@ -268,8 +268,8 @@
268
268
  "spec/v2/path-manifest.json": "034152e09b1458c66810d4050e20a273b2b9b8fe2d92b66e8b819477de58a1be",
269
269
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
270
270
  "spec/v2/profiles.json": "0636f19fceae625390003a347e70ef4797d84766b5c24ce8a02cea52aadebca4",
271
- "spec/v2/release.json": "14eb79136a01caa739684a20dbe15384c2f5420b30f942e6e328e8c66c446745",
271
+ "spec/v2/release.json": "5e98d2087cd69d3d2a7a6ab011c2f8ee6c86a7e3c98630267b6175d067f23649",
272
272
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624"
273
273
  },
274
- "corpusCommit": "e84395f18a99d9266ba94e9eddb23a7e73ef5c33"
274
+ "corpusCommit": "2487cf4f9855684a4e874ec7cd8ad0a80b7b3ca6"
275
275
  }
package/src/cli.ts CHANGED
@@ -663,13 +663,20 @@ async function runCertify(args: ParsedArgs, baseUrl: string, apiKey: string): Pr
663
663
  if (!vk || !vkId) { process.stderr.write('openwop-conformance --certify: --evidence-tier independent needs --verifier-key and --verifier-key-id (RFC 0168 §E.2)\n'); process.exit(2); }
664
664
  v3.verifierSignature = verifierSign(unsigned, vk, vkId);
665
665
  }
666
- const secrets3 = evidenceSecretsFromEnv(process.env, [apiKey, signingKeyPem]);
666
+ // The published keyId is never a secret, whichever variable carried it
667
+ // (evidenceSecretsFromEnv: the `_ID` exclusion is the rule, `except` is
668
+ // the belt for a host that names its env var differently).
669
+ const secrets3 = evidenceSecretsFromEnv(process.env, [apiKey, signingKeyPem], [keyId]);
667
670
  const scrubbed3 = scrubEvidence(v3, secrets3);
668
671
  const v3Out = scrubbed3.value as BundleV3;
669
672
  const audit3 = verifyBundleV3(v3Out, { hostPublicKeyPem: publicKeyFromPrivate(signingKeyPem) });
670
673
  const emitterDefects = audit3.rejections.filter((r) => !['blocked-certified', 'relaxed-profile-certified', 'independent-unverifiable'].includes(r.kind));
671
674
  if (emitterDefects.length > 0) {
672
- process.stderr.write('openwop-conformance --certify: assembled v3 bundle FAILED self-verification (emitter defect):\n' + emitterDefects.map((r) => ` - [${r.kind}] ${r.detail}`).join('\n') + '\n');
675
+ // Keep the rejected bundle: a self-verification failure with nothing to
676
+ // inspect sent a host patching a scratch copy of this CLI to see it.
677
+ const rejectedPath = `${outPath}.rejected.json`;
678
+ writeFileSync(rejectedPath, `${JSON.stringify(v3Out, null, 2)}\n`);
679
+ process.stderr.write('openwop-conformance --certify: assembled v3 bundle FAILED self-verification (emitter defect):\n' + emitterDefects.map((r) => ` - [${r.kind}] ${r.detail}`).join('\n') + `\n the rejected bundle is at ${rejectedPath} (NOT a certification artifact — do not check it in)\n`);
673
680
  process.exit(2);
674
681
  }
675
682
  const v3Schema = JSON.parse(readFileSync(join(SCHEMAS_DIR, 'v2', 'certification-bundle.schema.json'), 'utf8')) as Record<string, unknown>;
@@ -342,15 +342,32 @@ export function scrubEvidence<T>(value: T, secrets: readonly string[]): ScrubRes
342
342
  * The secrets a `--certify` run must keep out of its own evidence: the
343
343
  * credential it was handed, plus every `OPENWOP_*` environment value that
344
344
  * names a key/token/secret/password, plus the conformance canary.
345
+ *
346
+ * An *identifier* of a key is not the key. `OPENWOP_BUNDLE_SIGNING_KEY_ID`
347
+ * (and `_VERIFIER_KEY_ID`) name the public `keyId` RFC 0168 §E.2 requires the
348
+ * host to publish in `discovery.document.signingKeys[]`; matching them on
349
+ * `KEY` scrubbed that id out of the embedded document AFTER `discovery.sha256`
350
+ * was taken, so every host that passed the id by env failed its own bundle's
351
+ * self-verification (`discovery.document` ≠ `discovery.sha256`) — suites 2.0.8
352
+ * and 2.0.9, found by MyndHyve 2026-09-10 across three cuts; the same host had
353
+ * passed the day before by passing the id as a flag. Names ending in `_ID`
354
+ * are excluded, and a caller may name values that MUST stay visible (`except`)
355
+ * regardless of which variable carried them.
345
356
  */
346
- export function evidenceSecretsFromEnv(env: NodeJS.ProcessEnv, extra: readonly (string | undefined)[] = []): string[] {
357
+ export function evidenceSecretsFromEnv(
358
+ env: NodeJS.ProcessEnv,
359
+ extra: readonly (string | undefined)[] = [],
360
+ except: readonly (string | undefined)[] = [],
361
+ ): string[] {
362
+ const keep = new Set(except.filter((s): s is string => s !== undefined && s.trim() !== ''));
347
363
  const out = new Set<string>();
348
364
  for (const [k, v] of Object.entries(env)) {
349
365
  if (!k.startsWith('OPENWOP_')) continue;
350
366
  if (!/(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)/.test(k)) continue;
351
- if (v !== undefined && v.trim() !== '') out.add(v);
367
+ if (/_ID$/.test(k)) continue; // a key's identifier is published, not secret
368
+ if (v !== undefined && v.trim() !== '' && !keep.has(v)) out.add(v);
352
369
  }
353
- for (const s of extra) if (s !== undefined && s.trim() !== '') out.add(s);
370
+ for (const s of extra) if (s !== undefined && s.trim() !== '' && !keep.has(s)) out.add(s);
354
371
  out.add(CONFORMANCE_SECRET_CANARY);
355
372
  return [...out];
356
373
  }
@@ -154,6 +154,26 @@ describe('RFC 0148 §C — certification-bundle-redaction: secret canaries never
154
154
  expect(secrets.sort(), req('openwop.it.certification-bundle-redaction.evidencesecretsfromenv-selects-openwop-key-token-secret-password-variables-the-h', 'RFC 0148 §C', 'evidenceSecretsFromEnv selects OPENWOP_* key/token/secret/password variables, the handed credential, and ALWAYS the canary')).toEqual([API_KEY, 'k1', 'p1', 's1', 't1', CONFORMANCE_SECRET_CANARY].sort());
155
155
  });
156
156
 
157
+ it('evidenceSecretsFromEnv never scrubs a key IDENTIFIER: *_ID variables are excluded and `except` values stay visible whichever variable carried them', () => {
158
+ // Suites 2.0.8–2.0.10: OPENWOP_BUNDLE_SIGNING_KEY_ID matched on KEY and the
159
+ // published keyId was redacted out of discovery.document after its digest —
160
+ // three MyndHyve cuts failed self-verification on 2026-09-10 for this alone.
161
+ const env = {
162
+ OPENWOP_BUNDLE_SIGNING_KEY: '-----BEGIN PRIVATE KEY-----\nsecret\n-----END PRIVATE KEY-----',
163
+ OPENWOP_BUNDLE_SIGNING_KEY_ID: 'myndhyve-bundle-2026-09',
164
+ OPENWOP_BUNDLE_VERIFIER_KEY_ID: 'steward-verifier-1',
165
+ OPENWOP_API_KEY: 'k1',
166
+ OPENWOP_ODDLY_NAMED_KEY: 'published-key-id-in-a-secret-shaped-variable',
167
+ } as NodeJS.ProcessEnv;
168
+ const secrets = evidenceSecretsFromEnv(env, [], ['published-key-id-in-a-secret-shaped-variable']);
169
+ expect(secrets, req('openwop.it.certification-bundle-redaction.key-identifier-never-scrubbed', 'RFC 0168 §E.2', 'the keyId a host publishes in discovery.document.signingKeys[] MUST survive evidence scrubbing — a *_ID variable is an identifier, not a secret')).not.toContain('myndhyve-bundle-2026-09');
170
+ expect(secrets).not.toContain('steward-verifier-1');
171
+ expect(secrets, req('openwop.it.certification-bundle-redaction.key-identifier-never-scrubbed', 'RFC 0168 §E.2', 'a value the emitter names as `except` (the keyId it is about to publish) is never scrubbed, whichever variable carried it')).not.toContain('published-key-id-in-a-secret-shaped-variable');
172
+ expect(secrets).toContain('k1');
173
+ expect(secrets.some((s) => s.includes('BEGIN PRIVATE KEY'))).toBe(true);
174
+ expect(secrets).toContain(CONFORMANCE_SECRET_CANARY);
175
+ });
176
+
157
177
  it('verifyBundleV2 REJECTS a bundle carrying the conformance canary anywhere, and accepts the scrubbed twin', () => {
158
178
  const rs = rows();
159
179
  rs[1] = { ...(rs[1] as BundleV2Requirement), detail: `resolved ${CONFORMANCE_SECRET_CANARY} in plain text` };
@@ -9,6 +9,16 @@
9
9
  * by hand so a failure names the field). A crafted id whose tenant segment is
10
10
  * not the caller's MUST be refused — `403 id_tenant_mismatch`, or `404
11
11
  * not_found` where the host chooses not to leak existence.
12
+ *
13
+ * Suite 2.0.10 adds the bare-id leg (`identity.md` §5, stated 2026-09-10):
14
+ * a major-2 request whose tenant-bound path parameter carries only the opaque
15
+ * segment — the v1 spelling of the run this caller just created — is the
16
+ * overlap's affordance. While the host advertises a `1.x` member it MUST
17
+ * resolve the bare id under the caller's tenant and answer 200 with the BOUND
18
+ * id in the body (the same rule `v2-dual-stack-negotiation` measures for a
19
+ * `/v1/`-created run); once no `1.x` member is advertised it MUST refuse the
20
+ * bare form `400 validation_error`. The leg decides which branch applies from
21
+ * live discovery, so it is the expiry's witness as well as the affordance's.
12
22
  */
13
23
 
14
24
  import { describe, it, expect } from 'vitest';
@@ -74,4 +84,24 @@ describe('v2 id-grammar (RFC 0170 §D.1)', () => {
74
84
  expect(code === 'id_tenant_mismatch' || code === 'not_found', req('openwop.requirement.0170.id-grammar.tenant-binding', DOC, `the refusal MUST carry id_tenant_mismatch or not_found (got ${String(code)})`)).toBe(true);
75
85
  expect(res.status === 403 ? code === 'id_tenant_mismatch' : code === 'not_found', req('openwop.requirement.0170.id-grammar.tenant-binding', 'spec/v2/core/errors.md', 'the code MUST be answered with its registered HTTP status (id_tenant_mismatch → 403, not_found → 404)')).toBe(true);
76
86
  });
87
+
88
+ it('a bare (unbound) run id in a major-2 path: admitted under the caller\'s tenant through the overlap, refused 400 validation_error after it', async () => {
89
+ const doc = await discovery();
90
+ if (!doc) return softSkip('blocked', 'v2 discovery unreachable — /.well-known/openwop did not answer 200 with a JSON body under OpenWOP-Version: 2.0');
91
+ const versions = Array.isArray(doc['protocolVersions']) ? (doc['protocolVersions'] as unknown[]).filter((v): v is string => typeof v === 'string') : [];
92
+ const overlap = versions.some((v) => v.startsWith('1.'));
93
+ const c = await createRun();
94
+ if ('reason' in c) return softSkip('blocked', c.reason);
95
+ if (!RUN_ID.test(c.runId)) return softSkip('blocked', `the created runId is not tenant-bound (${c.runId}) — the snapshot leg reports that; nothing to strip here`);
96
+ const bare = c.runId.slice(c.runId.indexOf('/') + 1);
97
+ const res = await http(() => driver.get(`/runs/${encodeURIComponent(bare)}`));
98
+ if (res === null) return softSkip('blocked', 'GET /runs/{bare runId} unreachable (fetch failed)');
99
+ if (overlap) {
100
+ expect(res.status, req('openwop.requirement.0170.id-grammar.bare-id', DOC, `through the overlap (protocolVersions ${versions.join(', ')} carries a 1.x member) a bare run id on a major-2 path MUST resolve under the caller's tenant — 200 for the run this caller just created; got ${res.status} ${readErrorCode(res.json) ?? ''}`)).toBe(200);
101
+ expect((res.json as { runId?: unknown } | undefined)?.runId, req('openwop.requirement.0170.id-grammar.bare-id', DOC, 'a resource reached by its bare id MUST still be named by its bound projection in the major-2 response body (versioning.md §5) — the affordance is on the path parameter, never in a document')).toBe(c.runId);
102
+ } else {
103
+ expect(res.status, req('openwop.requirement.0170.id-grammar.bare-id', DOC, `after the overlap (protocolVersions ${versions.join(', ')} carries no 1.x member) the bare form MUST be refused 400 validation_error — the v1 spelling has nothing left to bridge; got ${res.status} ${readErrorCode(res.json) ?? ''}`)).toBe(400);
104
+ expect(readErrorCode(res.json), req('openwop.requirement.0170.id-grammar.bare-id', 'spec/v2/core/errors.md', 'the refusal MUST carry validation_error — not id_tenant_mismatch (no segment to mismatch) and not not_found (a form the host no longer admits is not looked up)')).toBe('validation_error');
105
+ }
106
+ });
77
107
  });