@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 +45 -0
- package/dist/cli.js +9 -2
- package/dist/lib/certification-bundle-verify.js +17 -3
- package/dist/spec-artifacts.lock.json +2 -2
- package/package.json +2 -2
- package/requirements.json +53 -9
- package/schemas/CORPUS-STAMP.json +57 -57
- package/src/cli.ts +9 -2
- package/src/lib/certification-bundle-verify.ts +20 -3
- package/src/scenarios/certification-bundle-redaction.test.ts +20 -0
- package/src/scenarios/v2-id-grammar.test.ts +30 -0
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openwop/openwop-conformance",
|
|
3
|
-
"version": "2.0.
|
|
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.
|
|
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":
|
|
7
|
-
"withStableId":
|
|
6
|
+
"tests": 2153,
|
|
7
|
+
"withStableId": 2153,
|
|
8
8
|
"interpolatedTitles": 0,
|
|
9
|
-
"explicitIds":
|
|
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.
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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.
|
|
5
|
-
"corpusTag": "v2.0.
|
|
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": "
|
|
13
|
-
"api/v2/asyncapi.yaml": "
|
|
14
|
-
"api/v2/openapi.yaml": "
|
|
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": "
|
|
103
|
-
"schemas/v2/agent-deployment-transition.schema.json": "
|
|
104
|
-
"schemas/v2/agent-deployment.schema.json": "
|
|
105
|
-
"schemas/v2/agent-eval-suite.schema.json": "
|
|
106
|
-
"schemas/v2/agent-inventory-response.schema.json": "
|
|
107
|
-
"schemas/v2/agent-manifest.schema.json": "
|
|
108
|
-
"schemas/v2/agent-org-chart.schema.json": "
|
|
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": "
|
|
111
|
-
"schemas/v2/agent-roster-response.schema.json": "
|
|
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": "
|
|
114
|
-
"schemas/v2/annotation.schema.json": "
|
|
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": "
|
|
116
|
+
"schemas/v2/audit-verify-result.schema.json": "a748dfadb5155adfde8a91045e76b0db6695005fe559035d71670fdd55edb3ba",
|
|
117
117
|
"schemas/v2/budget-policy.schema.json": "c7449daeb6e1d95e7a047b8c2814d3d54748697c27046972834874867d9b5f4c",
|
|
118
|
-
"schemas/v2/capabilities.schema.json": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
155
|
-
"schemas/v2/localized-content-page.schema.json": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
170
|
-
"schemas/v2/run-ancestry-response.schema.json": "
|
|
171
|
-
"schemas/v2/run-diff-response.schema.json": "
|
|
172
|
-
"schemas/v2/run-event-payloads.schema.json": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
215
|
-
"spec/v2/core/capabilities.md": "
|
|
216
|
-
"spec/v2/core/conformance.md": "
|
|
217
|
-
"spec/v2/core/connection-packs.md": "
|
|
218
|
-
"spec/v2/core/errors.md": "
|
|
219
|
-
"spec/v2/core/events.md": "
|
|
220
|
-
"spec/v2/core/form-content-packs.md": "
|
|
221
|
-
"spec/v2/core/headers.md": "
|
|
222
|
-
"spec/v2/core/idempotency.md": "
|
|
223
|
-
"spec/v2/core/identity.md": "
|
|
224
|
-
"spec/v2/core/interop.md": "
|
|
225
|
-
"spec/v2/core/interrupt.md": "
|
|
226
|
-
"spec/v2/core/overview.md": "
|
|
227
|
-
"spec/v2/core/packs.md": "
|
|
228
|
-
"spec/v2/core/persistence.md": "
|
|
229
|
-
"spec/v2/core/replay.md": "
|
|
230
|
-
"spec/v2/core/runs.md": "
|
|
231
|
-
"spec/v2/core/security-defaults.md": "
|
|
232
|
-
"spec/v2/core/versioning.md": "
|
|
233
|
-
"spec/v2/core/webhooks.md": "
|
|
234
|
-
"spec/v2/core/workflow-chain-packs.md": "
|
|
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": "
|
|
271
|
+
"spec/v2/release.json": "5e98d2087cd69d3d2a7a6ab011c2f8ee6c86a7e3c98630267b6175d067f23649",
|
|
272
272
|
"spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624"
|
|
273
273
|
},
|
|
274
|
-
"corpusCommit": "
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
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
|
});
|