@openwop/openwop-conformance 2.4.7 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +13 -1
- package/dist/cli.js +113 -2
- package/dist/spec-artifacts.lock.json +2 -2
- package/package.json +2 -2
- package/requirements.json +93 -18
- package/schemas/CORPUS-STAMP.json +29 -29
- package/src/cli.ts +112 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# `@openwop/openwop-conformance` Changelog
|
|
2
2
|
|
|
3
|
+
## [2.6.0] — 2026-09-18 — a relaxed profile cannot certify, and five corpus ids for the umbrella
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`--certify` wrote `certified: true` for a profile carrying a relaxation.**
|
|
8
|
+
`security-defaults.md` §Relaxations and RFC 0173 §A.2 both say it MUST NOT.
|
|
9
|
+
The predicate had no relaxation term and parsed relaxations on the next line;
|
|
10
|
+
the emitter then excluded `relaxed-profile-certified` from its self-check and
|
|
11
|
+
exited 0, so only `--verify` disagreed with the shipped file. Relaxations are
|
|
12
|
+
now parsed first and folded into the verdict, matched exactly as the verifier
|
|
13
|
+
matches them.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Five `src/coherence/` wrappers minting `openwop.requirement.0167.*`, so the v2
|
|
18
|
+
umbrella's falsifiability table stops passing rule 4 on zero ids. One spawns
|
|
19
|
+
`check-codemods.mjs --at-active`, which the merge gate never ran.
|
|
20
|
+
- `apps/workflow-engine` added to the public-docs banned-pattern scan.
|
|
21
|
+
|
|
22
|
+
## [2.5.0] — 2026-09-18 — `--verify`, for auditing a bundle you did not cut
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **`--verify <bundle> [--host-key <pem>]`** audits a certification bundle with
|
|
27
|
+
no host and no corpus clone. Exit `0` verified · `1` rejected · **`2` coherent
|
|
28
|
+
but NOT independently verified** · `3` not a bundle. Exit 2 is load-bearing:
|
|
29
|
+
without it, `--verify` with no key returns green and the program re-creates
|
|
30
|
+
the vacuous pass that bundle v3 exists to eliminate. The output states what
|
|
31
|
+
the command does NOT do — it never re-runs anything, so a host that measured
|
|
32
|
+
itself wrongly and signed the result verifies clean here.
|
|
33
|
+
|
|
3
34
|
## [2.4.7] — 2026-09-18 — schema examples are validated now
|
|
4
35
|
|
|
5
36
|
**Why a patch.** No conformance code changes; the corpus version moves because
|
package/README.md
CHANGED
|
@@ -11,11 +11,21 @@
|
|
|
11
11
|
# --legacy-peer-deps is REQUIRED, not optional: the exact peer pin is what npm's
|
|
12
12
|
# default resolver refuses. npm 10.9 fails outright with
|
|
13
13
|
# "Cannot read properties of null (reading 'edgesOut')" — use npm >= 11.
|
|
14
|
-
npm install --legacy-peer-deps @openwop/openwop-conformance@2.
|
|
14
|
+
npm install --legacy-peer-deps @openwop/openwop-conformance@2.6.0 @openwop/spec-artifacts@2.6.0
|
|
15
15
|
# or run without install:
|
|
16
16
|
npx @openwop/openwop-conformance --base-url https://api.example.com --api-key hk_test_...
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
### Auditing someone else's bundle
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx @openwop/openwop-conformance --verify bundle-v3.json --host-key host.pub.pem
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
No host, no clone. Exit `0` verified · `1` rejected · **`2` coherent but not independently verified** · `3` not a bundle.
|
|
26
|
+
|
|
27
|
+
Exit 2 is the one that matters: without `--host-key` the signature is unchecked, and a bundle that merely *hangs together* is not a bundle that has been *verified*. The command also prints what it does **not** do — it never re-runs anything, so a host that measured itself wrongly and signed the result verifies clean here. It audits an attestation, not a host.
|
|
28
|
+
|
|
19
29
|
> **Spec:** [github.com/openwop/openwop](https://github.com/openwop/openwop) · See `CHANGELOG.md` below for release history.
|
|
20
30
|
|
|
21
31
|
The suite is intentionally self-contained — it does NOT depend on the reference implementation. A spec-compliant server written in any language can run this suite against itself by spinning up its server, exporting the env vars, and running `npx vitest run`.
|
|
@@ -89,6 +99,8 @@ Run `npm run test` for normal CI cadence; `npm run test:strict` when claiming fu
|
|
|
89
99
|
|
|
90
100
|
| Variable | Effect |
|
|
91
101
|
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
102
|
+
| `OPENWOP_HOST_RELAXATIONS` | Sets `host.relaxations[]` in the bundle. **It relaxes nothing in the suite** — it is your declaration that the HOST runs relaxed, and the suite cannot detect an undeclared one. A profile carrying a relaxation **cannot certify** (`security-defaults.md` §Relaxations, RFC 0173 §A.2), and since 2.6.0 the emitter enforces that rather than writing `certified: true` and leaving the contradiction to `--verify`. JSON array of `{obligation, durability, reason}`; `durability` is `session \| deployment \| persisted` — **not** `permanent`. Unparseable JSON exits 2; a wrong enum value exits 2 later, at schema validation. |
|
|
103
|
+
| `OPENWOP_TARGET_MAJOR` | Selects the scenario lane. **Only the literal `"2"` selects major 2** — unlike `--target-major`, which validates and exits 2 on a bad value, the env var falls back to major 1 for `"1"`, `"3"`, `"two"` or empty. The runner and every vitest worker MUST see the same value: when they disagreed, the worker minted `openwop.floor.*` while the runner looked up `openwop.scenario.*`, producing a vacuous-pass emitter defect and **no bundle at all**. Prefer `--target-major`; set this only when driving `vitest` directly. |
|
|
92
104
|
| `OPENWOP_REQUIRE_BEHAVIOR=true` | Capability-gated scenarios (audit-log integrity, rate-limit envelope, multi-region idempotency, `configurableSchema`, webhook sig versioning, etc.) FAIL instead of skipping when the host doesn't advertise the profile. Lets a host claim "full coverage" mechanically. See [`coverage.md`](./coverage.md) §"Capability-gated scenarios". |
|
|
93
105
|
| `OPENWOP_TEST_PUBLIC_REGISTRY=true` | Runs `registry-public.test.ts` against the hosted registry at `packs.openwop.dev`. Skipped by default so the suite doesn't depend on outbound connectivity. |
|
|
94
106
|
| `OPENWOP_OTEL_COLLECTOR=true` | Boots the in-suite OTLP collector for `otel-emission.test.ts`, `otel-trace-propagation.test.ts`, `metric-emission.test.ts`, and `otel-emission-grpc.test.ts`. The collector accepts OTLP/HTTP-JSON (`application/json`), OTLP/HTTP-protobuf (`application/x-protobuf` — hand-rolled decoder at `src/lib/otlp-protobuf.ts`), and OTLP/gRPC (when `OPENWOP_OTEL_COLLECTOR_GRPC=true` — h2c HTTP/2 + hand-rolled framing at `src/lib/grpc-framing.ts`). Zero new npm deps. Hosts may emit via `OTEL_EXPORTER_OTLP_PROTOCOL=http/json`, `http/protobuf`, or `grpc`. Skipped by default. **Run OTel scenarios with `--no-file-parallelism`** — each vitest worker spawns its own collector and only one can bind the same port, so concurrent file execution causes ephemeral-port fallbacks that don't receive the host's OTLP traffic. |
|
package/dist/cli.js
CHANGED
|
@@ -62,6 +62,7 @@ function parseArgs(argv) {
|
|
|
62
62
|
let bundleVersion = '3';
|
|
63
63
|
let hostBuild;
|
|
64
64
|
let evidenceTier = 'self';
|
|
65
|
+
let verifyPath, verifyHostKeyPath;
|
|
65
66
|
let signingKeyPath, signingKeyId, verifierKeyPath, verifierKeyId;
|
|
66
67
|
let targetMajor;
|
|
67
68
|
let maxWorkers = parseMaxWorkers(process.env.OPENWOP_MAX_WORKERS, 'OPENWOP_MAX_WORKERS');
|
|
@@ -169,6 +170,12 @@ function parseArgs(argv) {
|
|
|
169
170
|
// `OPENWOP_REQUIRE_BEHAVIOR=true` did anything. So a cut that named the
|
|
170
171
|
// flag measured the LOOSE answer and reported the strict one — the exact
|
|
171
172
|
// failure shape the runbook warns about, in the tool that measures it.
|
|
173
|
+
case '--verify':
|
|
174
|
+
verifyPath = nextValue();
|
|
175
|
+
break;
|
|
176
|
+
case '--host-key':
|
|
177
|
+
verifyHostKeyPath = nextValue();
|
|
178
|
+
break;
|
|
172
179
|
case '--require-behavior':
|
|
173
180
|
process.env['OPENWOP_REQUIRE_BEHAVIOR'] = 'true';
|
|
174
181
|
break;
|
|
@@ -202,6 +209,8 @@ function parseArgs(argv) {
|
|
|
202
209
|
verifierKeyPath,
|
|
203
210
|
verifierKeyId,
|
|
204
211
|
maxWorkers,
|
|
212
|
+
verifyPath,
|
|
213
|
+
verifyHostKeyPath,
|
|
205
214
|
};
|
|
206
215
|
}
|
|
207
216
|
/** Parse a `--max-workers` / `OPENWOP_MAX_WORKERS` value: a positive integer, else exit 2. */
|
|
@@ -260,6 +269,17 @@ Certification (RFC 0089):
|
|
|
260
269
|
to <out.json>. Requires --base-url (and --api-key as usual).
|
|
261
270
|
|
|
262
271
|
Other:
|
|
272
|
+
--verify <bundle> Audit a certification bundle WITHOUT a host. Exit codes:
|
|
273
|
+
0 verified — no rejections, host signature checked,
|
|
274
|
+
certified list re-derived from the bundle's own
|
|
275
|
+
discovery document
|
|
276
|
+
1 rejected — one or more problems, each printed
|
|
277
|
+
2 coherent but NOT independently verified (no
|
|
278
|
+
--host-key, or the certified list is the emitter's
|
|
279
|
+
word). Do not quote this as "verified"
|
|
280
|
+
3 the file is missing or is not a bundle
|
|
281
|
+
--host-key <pem> The key --verify checks the host signature under. Without
|
|
282
|
+
it the signature is unverified and --verify exits 2.
|
|
263
283
|
--require-behavior Strict mode: an ADVERTISED behaviour that cannot be
|
|
264
284
|
observed FAILS instead of soft-skipping (RFC 0148 §B).
|
|
265
285
|
Equivalent to OPENWOP_REQUIRE_BEHAVIOR=true.
|
|
@@ -620,7 +640,16 @@ async function runCertify(args, baseUrl, apiKey) {
|
|
|
620
640
|
// `certified` IS the verdict (RFC 0148 §A; RFC 0168 §E.1 adds the bundle-wide
|
|
621
641
|
// blocked rule). Until rc.45 it was `!notHeld && !rejected && blocked === 0`
|
|
622
642
|
// and never read `certifiable` — an empty v2 floor certified on no evidence.
|
|
623
|
-
|
|
643
|
+
// Relaxations are parsed BEFORE the verdict, because they are part of it.
|
|
644
|
+
// `security-defaults.md` §Relaxations is explicit: "A bundle that records a
|
|
645
|
+
// relaxation MUST NOT certify the profile the relaxed obligation belongs to"
|
|
646
|
+
// (RFC 0173 §A.2). The emitter computed `certified` with no relaxation term —
|
|
647
|
+
// and parsed relaxations on the NEXT line, so it was structurally impossible
|
|
648
|
+
// for one to affect the verdict — then excluded `relaxed-profile-certified`
|
|
649
|
+
// from its own self-check. The shipped artifact asserted `certified: true`
|
|
650
|
+
// for a profile that cannot certify, and only `--verify` disagreed with the
|
|
651
|
+
// file. A reader who parses `.certified` — the obvious thing to do — got the
|
|
652
|
+
// wrong answer.
|
|
624
653
|
let relaxations;
|
|
625
654
|
if (process.env['OPENWOP_HOST_RELAXATIONS']) {
|
|
626
655
|
try {
|
|
@@ -631,6 +660,12 @@ async function runCertify(args, baseUrl, apiKey) {
|
|
|
631
660
|
process.exit(2);
|
|
632
661
|
}
|
|
633
662
|
}
|
|
663
|
+
// RFC 0173 §A.2: a recorded relaxation denies the profile its obligation
|
|
664
|
+
// belongs to. Matched the same way the verifier matches it, so the emitter
|
|
665
|
+
// and `verifyBundleV3` cannot disagree about the same bundle.
|
|
666
|
+
const relaxedObligations = new Set((relaxations ?? []).map((r) => r.obligation));
|
|
667
|
+
const relaxedProfile = (p) => [...relaxedObligations].some((o) => p.includes(o));
|
|
668
|
+
const claimed3 = claimedProfiles.filter((p) => !(p in DEPRECATED_PROFILE_ALIASES)).map((p) => ({ id: p, evidenceTier: args.evidenceTier, witnessCount: witnessCountFor(p), certified: !relaxedProfile(p) && (verdictFor(p)?.certifiable ?? false) && !notHeld.has(p) && !rejectedProfiles.some((v) => v.profile === p) && totals3.blocked === 0 }));
|
|
634
669
|
const lockPath = resolvePath(conformanceRoot, 'dist', 'spec-artifacts.lock.json');
|
|
635
670
|
const lock = existsSync(lockPath) ? JSON.parse(readFileSync(lockPath, 'utf8')) : undefined;
|
|
636
671
|
const nonPass = rows3.filter((r) => r.result !== 'executed-pass');
|
|
@@ -668,7 +703,10 @@ async function runCertify(args, baseUrl, apiKey) {
|
|
|
668
703
|
const scrubbed3 = scrubEvidence(v3, secrets3);
|
|
669
704
|
const v3Out = scrubbed3.value;
|
|
670
705
|
const audit3 = verifyBundleV3(v3Out, { hostPublicKeyPem: publicKeyFromPrivate(signingKeyPem) });
|
|
671
|
-
|
|
706
|
+
// `relaxed-profile-certified` is no longer excused: the emitter now computes
|
|
707
|
+
// the relaxation into `certified`, so if the verifier still finds that
|
|
708
|
+
// contradiction the bundle is genuinely malformed and MUST NOT be written.
|
|
709
|
+
const emitterDefects = audit3.rejections.filter((r) => !['blocked-certified', 'independent-unverifiable'].includes(r.kind));
|
|
672
710
|
if (emitterDefects.length > 0) {
|
|
673
711
|
// Keep the rejected bundle: a self-verification failure with nothing to
|
|
674
712
|
// inspect sent a host patching a scratch copy of this CLI to see it.
|
|
@@ -811,6 +849,79 @@ async function main() {
|
|
|
811
849
|
process.stdout.write(HELP_TEXT);
|
|
812
850
|
process.exit(0);
|
|
813
851
|
}
|
|
852
|
+
// `--verify <bundle.json>` — audit a bundle WITHOUT a host and without cloning
|
|
853
|
+
// the corpus. `verifyBundleV3` was exported but only ever called as the
|
|
854
|
+
// emitter's self-check, so auditing a third party's certification claim meant
|
|
855
|
+
// writing a driver script — which means in practice nobody did, and a
|
|
856
|
+
// certification program whose verifier ships only as a library function is
|
|
857
|
+
// asking to be taken on trust.
|
|
858
|
+
//
|
|
859
|
+
// The exit contract has FOUR values on purpose. "Structurally coherent" and
|
|
860
|
+
// "verified" are different facts, and collapsing them is how the vacuous pass
|
|
861
|
+
// gets rebuilt: without exit 2, every CI that runs `--verify` with no key
|
|
862
|
+
// prints green and reports "verified".
|
|
863
|
+
if (args.verifyPath !== undefined) {
|
|
864
|
+
if (!existsSync(args.verifyPath)) {
|
|
865
|
+
process.stderr.write(`openwop-conformance --verify: no such file: ${args.verifyPath}\n`);
|
|
866
|
+
process.exit(3);
|
|
867
|
+
}
|
|
868
|
+
let bundle;
|
|
869
|
+
try {
|
|
870
|
+
bundle = JSON.parse(readFileSync(args.verifyPath, 'utf8'));
|
|
871
|
+
}
|
|
872
|
+
catch (e) {
|
|
873
|
+
process.stderr.write(`openwop-conformance --verify: ${args.verifyPath} is not readable JSON — ${e.message}\n`);
|
|
874
|
+
process.exit(3);
|
|
875
|
+
}
|
|
876
|
+
const hostKey = args.verifyHostKeyPath !== undefined ? readFileSync(args.verifyHostKeyPath, 'utf8') : undefined;
|
|
877
|
+
const verifierKey = args.verifierKeyPath !== undefined ? readFileSync(args.verifierKeyPath, 'utf8') : undefined;
|
|
878
|
+
const verdict = verifyBundleV3(bundle, {
|
|
879
|
+
...(hostKey !== undefined ? { hostPublicKeyPem: hostKey } : {}),
|
|
880
|
+
...(verifierKey !== undefined ? { verifierPublicKeyPem: verifierKey } : {}),
|
|
881
|
+
});
|
|
882
|
+
const t = bundle.results?.totals ?? {};
|
|
883
|
+
const out = [
|
|
884
|
+
`bundle: ${args.verifyPath}`,
|
|
885
|
+
`host: ${bundle.host?.name ?? '?'} build ${bundle.host?.build?.kind ?? '?'}:${String(bundle.host?.build?.id ?? '?').slice(0, 12)}`,
|
|
886
|
+
`suite: ${bundle.suite?.version ?? '?'} (this CLI is ${suiteVersion()})`,
|
|
887
|
+
`totals: executedPass=${t.executedPass ?? '?'} executedFail=${t.executedFail ?? '?'} blocked=${t.blocked ?? '?'} inapplicable=${t.inapplicable ?? '?'} skipped=${t.skipped ?? '?'}`,
|
|
888
|
+
`certified: ${verdict.certifiedProfiles.length > 0 ? verdict.certifiedProfiles.join(', ') : '(none)'}`,
|
|
889
|
+
'',
|
|
890
|
+
'What this command does NOT do:',
|
|
891
|
+
' · It does not re-run anything. A host that measured itself wrongly, and signed',
|
|
892
|
+
' the result, verifies clean here. This audits an attestation, not a host.',
|
|
893
|
+
' · It does not resolve keyId against the live host. It checks the signature under',
|
|
894
|
+
' the key YOU supply with --host-key; it cannot tell you that key is the host\u2019s.',
|
|
895
|
+
' · It does not judge suite currency. The suite version is reported above as data:',
|
|
896
|
+
' an older bundle measured less, and whose fact that is belongs to its emitter.',
|
|
897
|
+
'',
|
|
898
|
+
];
|
|
899
|
+
if (verdict.rejections.length > 0) {
|
|
900
|
+
out.push(`REJECTED \u2014 ${verdict.rejections.length} problem(s):`);
|
|
901
|
+
for (const r of verdict.rejections)
|
|
902
|
+
out.push(` [${r.kind}]${r.profile ? ` (${r.profile})` : ''} ${r.detail}`);
|
|
903
|
+
process.stdout.write(out.join('\n') + '\n');
|
|
904
|
+
process.exit(1);
|
|
905
|
+
}
|
|
906
|
+
const gaps = [];
|
|
907
|
+
if (!verdict.signatureVerified)
|
|
908
|
+
gaps.push('the host signature was NOT verified (no --host-key supplied)');
|
|
909
|
+
if (!verdict.derivabilityChecked)
|
|
910
|
+
gaps.push('the certified list was NOT re-derived from the bundle\u2019s discovery document \u2014 it is the emitter\u2019s word');
|
|
911
|
+
if ((bundle.claimedProfiles ?? []).some((p) => p.evidenceTier === 'independent') && !verdict.verifierSignatureVerified)
|
|
912
|
+
gaps.push('an `independent` tier is claimed but the verifier signature was NOT verified (no --verifier-key)');
|
|
913
|
+
if (gaps.length > 0) {
|
|
914
|
+
out.push('COHERENT, NOT INDEPENDENTLY VERIFIED \u2014 zero rejections, but:');
|
|
915
|
+
for (const g of gaps)
|
|
916
|
+
out.push(` \u00b7 ${g}`);
|
|
917
|
+
out.push('', 'Do not quote this as "verified". Exit 2 exists to keep that distinction.');
|
|
918
|
+
process.stdout.write(out.join('\n') + '\n');
|
|
919
|
+
process.exit(2);
|
|
920
|
+
}
|
|
921
|
+
out.push('VERIFIED \u2014 zero rejections, host signature checked, certified list re-derived.');
|
|
922
|
+
process.stdout.write(out.join('\n') + '\n');
|
|
923
|
+
process.exit(0);
|
|
924
|
+
}
|
|
814
925
|
// Env vars OVERRIDE flags only when the flag was unset (consistent
|
|
815
926
|
// with the rest of the harness — env wins on the absence of CLI input).
|
|
816
927
|
const env = { ...process.env };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openwop/openwop-conformance",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
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.
|
|
59
|
+
"@openwop/spec-artifacts": "2.6.0"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/requirements.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"$comment": "GENERATED by conformance/scripts/generate-requirement-registry.mjs — do not edit. One record per it()/test() in src/scenarios. Ids: openwop.it.<file-stem>.<title-slug>[~n] (src/lib/requirement-ids.ts). A record with id null has an interpolated title; its run-time row is keyed by the rendered title and maps here by file+line only. Renamed ids need a row in requirement-aliases.json.",
|
|
3
3
|
"generatedFrom": "src/scenarios/*.test.ts",
|
|
4
4
|
"counts": {
|
|
5
|
-
"files":
|
|
6
|
-
"tests":
|
|
7
|
-
"withStableId":
|
|
5
|
+
"files": 576,
|
|
6
|
+
"tests": 2179,
|
|
7
|
+
"withStableId": 2179,
|
|
8
8
|
"interpolatedTitles": 0,
|
|
9
|
-
"explicitIds":
|
|
9
|
+
"explicitIds": 2132
|
|
10
10
|
},
|
|
11
11
|
"records": [
|
|
12
12
|
{
|
|
@@ -35390,7 +35390,7 @@
|
|
|
35390
35390
|
{
|
|
35391
35391
|
"id": "openwop.it.spec-corpus-validity.scans-public-docs-and-schemas",
|
|
35392
35392
|
"file": "spec-corpus-validity.test.ts",
|
|
35393
|
-
"line":
|
|
35393
|
+
"line": 1406,
|
|
35394
35394
|
"title": "scans public docs and schemas",
|
|
35395
35395
|
"explicitId": "openwop.it.spec-corpus-validity.scans-public-docs-and-schemas",
|
|
35396
35396
|
"citations": [
|
|
@@ -35403,7 +35403,7 @@
|
|
|
35403
35403
|
{
|
|
35404
35404
|
"id": "openwop.it.spec-corpus-validity.has-no-private-implementation-or-pre-v1-breadcrumbs",
|
|
35405
35405
|
"file": "spec-corpus-validity.test.ts",
|
|
35406
|
-
"line":
|
|
35406
|
+
"line": 1412,
|
|
35407
35407
|
"title": null,
|
|
35408
35408
|
"explicitId": "openwop.it.spec-corpus-validity.has-no-private-implementation-or-pre-v1-breadcrumbs",
|
|
35409
35409
|
"citations": [
|
|
@@ -35417,7 +35417,7 @@
|
|
|
35417
35417
|
{
|
|
35418
35418
|
"id": "openwop.it.spec-corpus-validity.every-fixture-id-mentioned-in-fixtures-md-has-a-corresponding-json",
|
|
35419
35419
|
"file": "spec-corpus-validity.test.ts",
|
|
35420
|
-
"line":
|
|
35420
|
+
"line": 1441,
|
|
35421
35421
|
"title": "every fixture id mentioned in fixtures.md has a corresponding JSON",
|
|
35422
35422
|
"explicitId": "openwop.it.spec-corpus-validity.every-fixture-id-mentioned-in-fixtures-md-has-a-corresponding-json",
|
|
35423
35423
|
"citations": [
|
|
@@ -35431,7 +35431,7 @@
|
|
|
35431
35431
|
{
|
|
35432
35432
|
"id": "openwop.it.spec-corpus-validity.every-fixture-json-file-is-referenced-by-fixtures-md",
|
|
35433
35433
|
"file": "spec-corpus-validity.test.ts",
|
|
35434
|
-
"line":
|
|
35434
|
+
"line": 1483,
|
|
35435
35435
|
"title": "every fixture JSON file is referenced by fixtures.md",
|
|
35436
35436
|
"explicitId": "openwop.it.spec-corpus-validity.every-fixture-json-file-is-referenced-by-fixtures-md",
|
|
35437
35437
|
"citations": [
|
|
@@ -35445,7 +35445,7 @@
|
|
|
35445
35445
|
{
|
|
35446
35446
|
"id": "openwop.it.spec-corpus-validity.a-sample-bundle-validates-against-conformance-certification-bundle-schema-json",
|
|
35447
35447
|
"file": "spec-corpus-validity.test.ts",
|
|
35448
|
-
"line":
|
|
35448
|
+
"line": 1536,
|
|
35449
35449
|
"title": "a sample bundle validates against conformance-certification-bundle.schema.json",
|
|
35450
35450
|
"explicitId": "openwop.it.spec-corpus-validity.a-sample-bundle-validates-against-conformance-certification-bundle-schema-json",
|
|
35451
35451
|
"citations": [
|
|
@@ -35459,7 +35459,7 @@
|
|
|
35459
35459
|
{
|
|
35460
35460
|
"id": "openwop.it.spec-corpus-validity.verifybundle-accepts-a-claim-that-is-discovery-derivable-and-floor-proven-b",
|
|
35461
35461
|
"file": "spec-corpus-validity.test.ts",
|
|
35462
|
-
"line":
|
|
35462
|
+
"line": 1542,
|
|
35463
35463
|
"title": "verifyBundle ACCEPTS a claim that is discovery-derivable AND floor-proven (§B)",
|
|
35464
35464
|
"explicitId": "openwop.it.spec-corpus-validity.verifybundle-accepts-a-claim-that-is-discovery-derivable-and-floor-proven-b",
|
|
35465
35465
|
"citations": [
|
|
@@ -35472,7 +35472,7 @@
|
|
|
35472
35472
|
{
|
|
35473
35473
|
"id": "openwop.it.spec-corpus-validity.verifybundle-rejects-a-profile-its-discovery-document-does-not-derive-b-1",
|
|
35474
35474
|
"file": "spec-corpus-validity.test.ts",
|
|
35475
|
-
"line":
|
|
35475
|
+
"line": 1549,
|
|
35476
35476
|
"title": "verifyBundle REJECTS a profile its discovery document does not derive (§B(1))",
|
|
35477
35477
|
"explicitId": "openwop.it.spec-corpus-validity.verifybundle-rejects-a-profile-its-discovery-document-does-not-derive-b-1",
|
|
35478
35478
|
"citations": [
|
|
@@ -35485,7 +35485,7 @@
|
|
|
35485
35485
|
{
|
|
35486
35486
|
"id": "openwop.it.spec-corpus-validity.verifybundle-rejects-a-bundle-missing-a-floor-scenario-b-2",
|
|
35487
35487
|
"file": "spec-corpus-validity.test.ts",
|
|
35488
|
-
"line":
|
|
35488
|
+
"line": 1559,
|
|
35489
35489
|
"title": "verifyBundle REJECTS a bundle missing a floor scenario (§B(2))",
|
|
35490
35490
|
"explicitId": "openwop.it.spec-corpus-validity.verifybundle-rejects-a-bundle-missing-a-floor-scenario-b-2",
|
|
35491
35491
|
"citations": [
|
|
@@ -35498,7 +35498,7 @@
|
|
|
35498
35498
|
{
|
|
35499
35499
|
"id": "openwop.it.spec-corpus-validity.the-committed-bundle-file-exists-generated-by-certify",
|
|
35500
35500
|
"file": "spec-corpus-validity.test.ts",
|
|
35501
|
-
"line":
|
|
35501
|
+
"line": 1596,
|
|
35502
35502
|
"title": "the committed bundle file exists (generated by --certify)",
|
|
35503
35503
|
"explicitId": "openwop.it.spec-corpus-validity.the-committed-bundle-file-exists-generated-by-certify",
|
|
35504
35504
|
"citations": [
|
|
@@ -35512,7 +35512,7 @@
|
|
|
35512
35512
|
{
|
|
35513
35513
|
"id": "openwop.it.spec-corpus-validity.the-committed-reference-bundle-validates-against-the-bundle-schema-a",
|
|
35514
35514
|
"file": "spec-corpus-validity.test.ts",
|
|
35515
|
-
"line":
|
|
35515
|
+
"line": 1600,
|
|
35516
35516
|
"title": "the committed reference bundle validates against the bundle schema (§A)",
|
|
35517
35517
|
"explicitId": "openwop.it.spec-corpus-validity.the-committed-reference-bundle-validates-against-the-bundle-schema-a",
|
|
35518
35518
|
"citations": [
|
|
@@ -35526,7 +35526,7 @@
|
|
|
35526
35526
|
{
|
|
35527
35527
|
"id": "openwop.it.spec-corpus-validity.verifybundle-rejects-the-committed-reference-bundle-it-is-invalidated-rfc-0148-d",
|
|
35528
35528
|
"file": "spec-corpus-validity.test.ts",
|
|
35529
|
-
"line":
|
|
35529
|
+
"line": 1607,
|
|
35530
35530
|
"title": "verifyBundle REJECTS the committed reference bundle — it is `invalidated` (RFC 0148 §D)",
|
|
35531
35531
|
"explicitId": "openwop.it.spec-corpus-validity.verifybundle-rejects-the-committed-reference-bundle-it-is-invalidated-rfc-0148-d",
|
|
35532
35532
|
"citations": [
|
|
@@ -35547,7 +35547,7 @@
|
|
|
35547
35547
|
{
|
|
35548
35548
|
"id": "openwop.it.spec-corpus-validity.discovery-sha256-is-the-canonical-json-sha-256-of-the-captured-discovery-documen",
|
|
35549
35549
|
"file": "spec-corpus-validity.test.ts",
|
|
35550
|
-
"line":
|
|
35550
|
+
"line": 1633,
|
|
35551
35551
|
"title": "discovery.sha256 is the canonical-JSON SHA-256 of the captured discovery.document",
|
|
35552
35552
|
"explicitId": "openwop.it.spec-corpus-validity.discovery-sha256-is-the-canonical-json-sha-256-of-the-captured-discovery-documen",
|
|
35553
35553
|
"citations": [
|
|
@@ -35560,7 +35560,7 @@
|
|
|
35560
35560
|
{
|
|
35561
35561
|
"id": "openwop.it.spec-corpus-validity.the-requestbody-closes-at-the-composition-unevaluatedproperties-not-inside-a-bra",
|
|
35562
35562
|
"file": "spec-corpus-validity.test.ts",
|
|
35563
|
-
"line":
|
|
35563
|
+
"line": 1694,
|
|
35564
35564
|
"title": "the requestBody closes at the composition (unevaluatedProperties), not inside a branch",
|
|
35565
35565
|
"explicitId": "openwop.it.spec-corpus-validity.the-requestbody-closes-at-the-composition-unevaluatedproperties-not-inside-a-bra",
|
|
35566
35566
|
"citations": [
|
|
@@ -35573,7 +35573,7 @@
|
|
|
35573
35573
|
{
|
|
35574
35574
|
"id": "openwop.it.spec-corpus-validity.canonical-createrun-bodies-pass-the-composed-schema-an-undeclared-property-fails",
|
|
35575
35575
|
"file": "spec-corpus-validity.test.ts",
|
|
35576
|
-
"line":
|
|
35576
|
+
"line": 1709,
|
|
35577
35577
|
"title": "canonical createRun bodies PASS the composed schema; an undeclared property FAILS",
|
|
35578
35578
|
"explicitId": "openwop.it.spec-corpus-validity.canonical-createrun-bodies-pass-the-composed-schema-an-undeclared-property-fails",
|
|
35579
35579
|
"citations": [
|
|
@@ -36221,6 +36221,81 @@
|
|
|
36221
36221
|
}
|
|
36222
36222
|
]
|
|
36223
36223
|
},
|
|
36224
|
+
{
|
|
36225
|
+
"id": "openwop.it.v2-umbrella-alias-covered.the-corpus-gate-script-exits-0",
|
|
36226
|
+
"file": "v2-umbrella-alias-covered.test.ts",
|
|
36227
|
+
"line": 32,
|
|
36228
|
+
"title": "the corpus gate script exits 0",
|
|
36229
|
+
"explicitId": "openwop.requirement.0167.alias-covered",
|
|
36230
|
+
"citations": [
|
|
36231
|
+
{
|
|
36232
|
+
"section": "RFC 0167 §E.2",
|
|
36233
|
+
"requirement": null,
|
|
36234
|
+
"interpolated": true
|
|
36235
|
+
}
|
|
36236
|
+
]
|
|
36237
|
+
},
|
|
36238
|
+
{
|
|
36239
|
+
"id": "openwop.it.v2-umbrella-child-parts.the-corpus-gate-script-exits-0",
|
|
36240
|
+
"file": "v2-umbrella-child-parts.test.ts",
|
|
36241
|
+
"line": 33,
|
|
36242
|
+
"title": "the corpus gate script exits 0",
|
|
36243
|
+
"explicitId": "openwop.requirement.0167.child-parts",
|
|
36244
|
+
"citations": [
|
|
36245
|
+
{
|
|
36246
|
+
"section": "RFC 0167 §C",
|
|
36247
|
+
"requirement": null,
|
|
36248
|
+
"interpolated": true
|
|
36249
|
+
},
|
|
36250
|
+
{
|
|
36251
|
+
"section": "RFC 0167 §C",
|
|
36252
|
+
"requirement": null,
|
|
36253
|
+
"interpolated": true
|
|
36254
|
+
}
|
|
36255
|
+
]
|
|
36256
|
+
},
|
|
36257
|
+
{
|
|
36258
|
+
"id": "openwop.it.v2-umbrella-codemod-at-active.the-corpus-gate-script-exits-0",
|
|
36259
|
+
"file": "v2-umbrella-codemod-at-active.test.ts",
|
|
36260
|
+
"line": 32,
|
|
36261
|
+
"title": "the corpus gate script exits 0",
|
|
36262
|
+
"explicitId": "openwop.requirement.0167.codemod-at-active",
|
|
36263
|
+
"citations": [
|
|
36264
|
+
{
|
|
36265
|
+
"section": "RFC 0167 §D.3",
|
|
36266
|
+
"requirement": null,
|
|
36267
|
+
"interpolated": true
|
|
36268
|
+
}
|
|
36269
|
+
]
|
|
36270
|
+
},
|
|
36271
|
+
{
|
|
36272
|
+
"id": "openwop.it.v2-umbrella-codemod-legs.the-corpus-gate-script-exits-0",
|
|
36273
|
+
"file": "v2-umbrella-codemod-legs.test.ts",
|
|
36274
|
+
"line": 32,
|
|
36275
|
+
"title": "the corpus gate script exits 0",
|
|
36276
|
+
"explicitId": "openwop.requirement.0167.codemod-legs",
|
|
36277
|
+
"citations": [
|
|
36278
|
+
{
|
|
36279
|
+
"section": "RFC 0167 §D.3",
|
|
36280
|
+
"requirement": null,
|
|
36281
|
+
"interpolated": true
|
|
36282
|
+
}
|
|
36283
|
+
]
|
|
36284
|
+
},
|
|
36285
|
+
{
|
|
36286
|
+
"id": "openwop.it.v2-umbrella-migration-deprecation.the-corpus-gate-script-exits-0",
|
|
36287
|
+
"file": "v2-umbrella-migration-deprecation.test.ts",
|
|
36288
|
+
"line": 32,
|
|
36289
|
+
"title": "the corpus gate script exits 0",
|
|
36290
|
+
"explicitId": "openwop.requirement.0167.migration-deprecation-bound",
|
|
36291
|
+
"citations": [
|
|
36292
|
+
{
|
|
36293
|
+
"section": "RFC 0167 §D.1",
|
|
36294
|
+
"requirement": null,
|
|
36295
|
+
"interpolated": true
|
|
36296
|
+
}
|
|
36297
|
+
]
|
|
36298
|
+
},
|
|
36224
36299
|
{
|
|
36225
36300
|
"id": "openwop.it.v2-witness-required.the-corpus-gate-script-exits-0",
|
|
36226
36301
|
"file": "v2-witness-required.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.
|
|
5
|
-
"corpusTag": "v2.
|
|
4
|
+
"version": "2.6.0",
|
|
5
|
+
"corpusTag": "v2.6.0",
|
|
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": "7f6637d38e3c4e6abd26486045250e49510e47f88c369d182c4c58f9100e641b",
|
|
13
|
+
"api/v2/asyncapi.yaml": "8cbc0edc6dbf62726ff48e1a1d5f6ac90e2eecee42b005ab5b3d093aa2d11f81",
|
|
14
|
+
"api/v2/openapi.yaml": "ea0138814ca4766e2575ab5964ff03c7dba0a41ba7041897f5f1d1ac5808680b",
|
|
15
15
|
"api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
|
|
16
16
|
"schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
|
|
17
17
|
"schemas/a2a-task-state.schema.json": "c9365918f993f943b4b619d42551eb066a1ed33a08d895d51b395432a5b1f1bc",
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"schemas/v2/audit-verify-result.schema.json": "a748dfadb5155adfde8a91045e76b0db6695005fe559035d71670fdd55edb3ba",
|
|
117
117
|
"schemas/v2/budget-policy.schema.json": "c7449daeb6e1d95e7a047b8c2814d3d54748697c27046972834874867d9b5f4c",
|
|
118
118
|
"schemas/v2/capabilities.schema.json": "fa9b80a9107fc322ecadbac808e7fbe5e16ccf0af1a6bf673181faa8379dde33",
|
|
119
|
-
"schemas/v2/certification-bundle.schema.json": "
|
|
119
|
+
"schemas/v2/certification-bundle.schema.json": "0bcf8f297266505ca39d30eea8100e3c0fb048f2651717fd746da8129fcb7920",
|
|
120
120
|
"schemas/v2/channel-presence-payload.schema.json": "1c20ad810cb311d47aa0b95d928490826b2468fd161f884cd29e867053e6782f",
|
|
121
121
|
"schemas/v2/channel-written-payload.schema.json": "ccecff3c71a3275ad8db035ac0e04db25e6f8d23cf091ddbcd5a60d1be1cbb6b",
|
|
122
122
|
"schemas/v2/chat-card-pack-manifest.schema.json": "8df0d9d273ae22efc6876d5586833f1d4c2e551edc3ac6cf6e8f0719329f03dd",
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
"schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
|
|
201
201
|
"spec/v1/alias-detectors.json": "e90916db12d2297e2e560b2d54d2c4702d6cb3bef7179a575844687a7ec17262",
|
|
202
202
|
"spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
|
|
203
|
-
"spec/v1/core-standard-manifest.json": "
|
|
203
|
+
"spec/v1/core-standard-manifest.json": "de1f6bd9dc3f602b6893d66cbfb79a055457e18d0be5ff70a2611ee69f672727",
|
|
204
204
|
"spec/v1/deprecations.json": "c81c8f71460936c8d489d8270477c7661cf4a5d7b7bad86c98899750b5656027",
|
|
205
205
|
"spec/v1/deprecations.schema.json": "18c87e78bedc210431f795ae44c5b5d202f2f3317850d5cf86867d4f1fa1cdfb",
|
|
206
206
|
"spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
|
|
@@ -213,26 +213,26 @@
|
|
|
213
213
|
"spec/v1/operation-path-manifest.json": "5f5f4e3842669371730ebbd1aace3fb794192615f0018dc144f484ba5db82ac3",
|
|
214
214
|
"spec/v1/spec-gaps.json": "eb3bfcb9c7d05c9a6845d4a40ec9493cfaa5a671932daf1af561310136fdabe8",
|
|
215
215
|
"spec/v2/README.md": "5d49f56f41e06b44312c61ad8144b786c9a90ec42fd0660a40d89520ef206137",
|
|
216
|
-
"spec/v2/core/capabilities.md": "
|
|
217
|
-
"spec/v2/core/conformance.md": "
|
|
218
|
-
"spec/v2/core/connection-packs.md": "
|
|
219
|
-
"spec/v2/core/errors.md": "
|
|
220
|
-
"spec/v2/core/events.md": "
|
|
221
|
-
"spec/v2/core/form-content-packs.md": "
|
|
222
|
-
"spec/v2/core/headers.md": "
|
|
223
|
-
"spec/v2/core/idempotency.md": "
|
|
224
|
-
"spec/v2/core/identity.md": "
|
|
225
|
-
"spec/v2/core/interop.md": "
|
|
226
|
-
"spec/v2/core/interrupt.md": "
|
|
227
|
-
"spec/v2/core/overview.md": "
|
|
228
|
-
"spec/v2/core/packs.md": "
|
|
229
|
-
"spec/v2/core/persistence.md": "
|
|
230
|
-
"spec/v2/core/replay.md": "
|
|
231
|
-
"spec/v2/core/runs.md": "
|
|
232
|
-
"spec/v2/core/security-defaults.md": "
|
|
233
|
-
"spec/v2/core/versioning.md": "
|
|
234
|
-
"spec/v2/core/webhooks.md": "
|
|
235
|
-
"spec/v2/core/workflow-chain-packs.md": "
|
|
216
|
+
"spec/v2/core/capabilities.md": "35696991582729c86b5760f292251734e37cee45398fa5179764cefa5a91037f",
|
|
217
|
+
"spec/v2/core/conformance.md": "a451c0a2ba79d93c55e540ccef480eba7de9602d59bbb03eead05daafde0beac",
|
|
218
|
+
"spec/v2/core/connection-packs.md": "2e53c92476f68304139faf0c370874dc27bbba56a127a07e0906ff06edbb8ff1",
|
|
219
|
+
"spec/v2/core/errors.md": "c3ac131dff970ab7574629d72735ace52cc56cd4a7ab5d1206ce91f382bc9a68",
|
|
220
|
+
"spec/v2/core/events.md": "6ab8c727df3ebc5a60de3837c82a0075b4357d6f7e28435c6f80585687d41a76",
|
|
221
|
+
"spec/v2/core/form-content-packs.md": "d4c5295dfd28972be205c87c34d0d34ced64f4a2561ebff7f9d24b436d83df25",
|
|
222
|
+
"spec/v2/core/headers.md": "6c5eb48d998c141b5f0cb61fea926833a072748fdc4034e1a90a4dfe5448659c",
|
|
223
|
+
"spec/v2/core/idempotency.md": "803cd85947b1b2151cd2fb42cf0e0c52c9bcd19cdc6c23dcf34b1eddc600921a",
|
|
224
|
+
"spec/v2/core/identity.md": "ae24870df315b527dfe9ecf7a4c26582bcc1113da7954f44acf69bf463ac390d",
|
|
225
|
+
"spec/v2/core/interop.md": "8d8f4b040cb3625044c6aa586c8dcf5e9d15797de3d3b1cea631f44b8924701c",
|
|
226
|
+
"spec/v2/core/interrupt.md": "bdb2879a52a2504d84146e46108c4a4c2dd3a6f27865a295c80375b331cced21",
|
|
227
|
+
"spec/v2/core/overview.md": "7dc00ace32d4e4ce929a383651f0029fc4b7091ec87487b1bf26ead1be209e0e",
|
|
228
|
+
"spec/v2/core/packs.md": "3254fd060c2b690cea70ef2b1b1a119711bb04fb5f13bf8936d4d718df9b2322",
|
|
229
|
+
"spec/v2/core/persistence.md": "e75c6d8cc6a31747f8559931c420ed0debbdec16ae01fed28df5aa79a51e5899",
|
|
230
|
+
"spec/v2/core/replay.md": "dee513b6ca313a0647c853e713af94a3e077f3e58f71851ddb0ef8adcb3b0afe",
|
|
231
|
+
"spec/v2/core/runs.md": "b4c0bb2869077fcbf26e15bc6bbac06819f68b8241944b903a275a37db1c71fa",
|
|
232
|
+
"spec/v2/core/security-defaults.md": "9ab1d1a88ccd0929f354422b151ec6d3164e8cd3784f8e58bb6f11cb7ee1e7d5",
|
|
233
|
+
"spec/v2/core/versioning.md": "27b88cde88f9060567ef7e46f4897b6be8892ade9f4a46c2ab16e314c515eabd",
|
|
234
|
+
"spec/v2/core/webhooks.md": "ecec4d22a35aeafd34276bb0579ae28610e79ab8850ebf42188cb51bc946a093",
|
|
235
|
+
"spec/v2/core/workflow-chain-packs.md": "26bb65721f826285b7d90c3cb62fa7e9fb2414c1bc6e0fd25b33209168c3102e",
|
|
236
236
|
"spec/v2/declaration.json": "a09bac61c1b825e4a77b5abc0f86f1f8c20c381e0650eb08c2f6d0ef0aaec1dc",
|
|
237
237
|
"spec/v2/declaration.schema.json": "c86e3083fb9789c1958ecb4576cc6f71aa90e6f339521139238543ed08211dd4",
|
|
238
238
|
"spec/v2/errors.json": "4ff032033f02d7e3fb4204581456408ba5e7642b438c1f349bbd1c264e63e419",
|
|
@@ -271,8 +271,8 @@
|
|
|
271
271
|
"spec/v2/path-manifest.json": "a6f4e652e866889e8bde815874062f62929aee95dc8e58e7152c56adee262163",
|
|
272
272
|
"spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
|
|
273
273
|
"spec/v2/profiles.json": "0636f19fceae625390003a347e70ef4797d84766b5c24ce8a02cea52aadebca4",
|
|
274
|
-
"spec/v2/release.json": "
|
|
274
|
+
"spec/v2/release.json": "2c0e277e6ef93e5f439947c546f3ec10696d1f3a7235dedaf5d6973954d1652d",
|
|
275
275
|
"spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624"
|
|
276
276
|
},
|
|
277
|
-
"corpusCommit": "
|
|
277
|
+
"corpusCommit": "dff4852f36271da77a30a88494cde1e027199d7c"
|
|
278
278
|
}
|
package/src/cli.ts
CHANGED
|
@@ -75,6 +75,10 @@ interface ParsedArgs {
|
|
|
75
75
|
readonly signingKeyPath: string | undefined;
|
|
76
76
|
readonly signingKeyId: string | undefined;
|
|
77
77
|
readonly verifierKeyPath: string | undefined;
|
|
78
|
+
/** `--verify <bundle.json>` — audit a bundle someone else cut, without cloning the corpus. */
|
|
79
|
+
readonly verifyPath: string | undefined;
|
|
80
|
+
/** `--host-key <pem>` — the key `--verify` checks the host signature under. Absent ⇒ the verdict is INCOMPLETE, not clean. */
|
|
81
|
+
readonly verifyHostKeyPath: string | undefined;
|
|
78
82
|
readonly verifierKeyId: string | undefined;
|
|
79
83
|
/**
|
|
80
84
|
* S43 (2026-08-18) — cap on concurrently running scenario FILES, forwarded to
|
|
@@ -100,6 +104,7 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
|
100
104
|
let bundleVersion: '2' | '3' = '3';
|
|
101
105
|
let hostBuild: ParsedArgs['hostBuild'];
|
|
102
106
|
let evidenceTier: ParsedArgs['evidenceTier'] = 'self';
|
|
107
|
+
let verifyPath: string | undefined, verifyHostKeyPath: string | undefined;
|
|
103
108
|
let signingKeyPath: string | undefined, signingKeyId: string | undefined, verifierKeyPath: string | undefined, verifierKeyId: string | undefined;
|
|
104
109
|
let targetMajor: 1 | 2 | undefined;
|
|
105
110
|
let maxWorkers: number | undefined = parseMaxWorkers(process.env.OPENWOP_MAX_WORKERS, 'OPENWOP_MAX_WORKERS');
|
|
@@ -187,6 +192,12 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
|
187
192
|
// `OPENWOP_REQUIRE_BEHAVIOR=true` did anything. So a cut that named the
|
|
188
193
|
// flag measured the LOOSE answer and reported the strict one — the exact
|
|
189
194
|
// failure shape the runbook warns about, in the tool that measures it.
|
|
195
|
+
case '--verify':
|
|
196
|
+
verifyPath = nextValue();
|
|
197
|
+
break;
|
|
198
|
+
case '--host-key':
|
|
199
|
+
verifyHostKeyPath = nextValue();
|
|
200
|
+
break;
|
|
190
201
|
case '--require-behavior':
|
|
191
202
|
process.env['OPENWOP_REQUIRE_BEHAVIOR'] = 'true';
|
|
192
203
|
break;
|
|
@@ -223,6 +234,8 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
|
223
234
|
verifierKeyPath,
|
|
224
235
|
verifierKeyId,
|
|
225
236
|
maxWorkers,
|
|
237
|
+
verifyPath,
|
|
238
|
+
verifyHostKeyPath,
|
|
226
239
|
};
|
|
227
240
|
}
|
|
228
241
|
|
|
@@ -283,6 +296,17 @@ Certification (RFC 0089):
|
|
|
283
296
|
to <out.json>. Requires --base-url (and --api-key as usual).
|
|
284
297
|
|
|
285
298
|
Other:
|
|
299
|
+
--verify <bundle> Audit a certification bundle WITHOUT a host. Exit codes:
|
|
300
|
+
0 verified — no rejections, host signature checked,
|
|
301
|
+
certified list re-derived from the bundle's own
|
|
302
|
+
discovery document
|
|
303
|
+
1 rejected — one or more problems, each printed
|
|
304
|
+
2 coherent but NOT independently verified (no
|
|
305
|
+
--host-key, or the certified list is the emitter's
|
|
306
|
+
word). Do not quote this as "verified"
|
|
307
|
+
3 the file is missing or is not a bundle
|
|
308
|
+
--host-key <pem> The key --verify checks the host signature under. Without
|
|
309
|
+
it the signature is unverified and --verify exits 2.
|
|
286
310
|
--require-behavior Strict mode: an ADVERTISED behaviour that cannot be
|
|
287
311
|
observed FAILS instead of soft-skipping (RFC 0148 §B).
|
|
288
312
|
Equivalent to OPENWOP_REQUIRE_BEHAVIOR=true.
|
|
@@ -653,9 +677,24 @@ async function runCertify(args: ParsedArgs, baseUrl: string, apiKey: string): Pr
|
|
|
653
677
|
// `certified` IS the verdict (RFC 0148 §A; RFC 0168 §E.1 adds the bundle-wide
|
|
654
678
|
// blocked rule). Until rc.45 it was `!notHeld && !rejected && blocked === 0`
|
|
655
679
|
// and never read `certifiable` — an empty v2 floor certified on no evidence.
|
|
656
|
-
|
|
680
|
+
// Relaxations are parsed BEFORE the verdict, because they are part of it.
|
|
681
|
+
// `security-defaults.md` §Relaxations is explicit: "A bundle that records a
|
|
682
|
+
// relaxation MUST NOT certify the profile the relaxed obligation belongs to"
|
|
683
|
+
// (RFC 0173 §A.2). The emitter computed `certified` with no relaxation term —
|
|
684
|
+
// and parsed relaxations on the NEXT line, so it was structurally impossible
|
|
685
|
+
// for one to affect the verdict — then excluded `relaxed-profile-certified`
|
|
686
|
+
// from its own self-check. The shipped artifact asserted `certified: true`
|
|
687
|
+
// for a profile that cannot certify, and only `--verify` disagreed with the
|
|
688
|
+
// file. A reader who parses `.certified` — the obvious thing to do — got the
|
|
689
|
+
// wrong answer.
|
|
657
690
|
let relaxations: BundleV3['host']['relaxations'];
|
|
658
691
|
if (process.env['OPENWOP_HOST_RELAXATIONS']) { try { relaxations = JSON.parse(process.env['OPENWOP_HOST_RELAXATIONS']) as BundleV3['host']['relaxations']; } catch { process.stderr.write('openwop-conformance --certify: OPENWOP_HOST_RELAXATIONS is not JSON\n'); process.exit(2); } }
|
|
692
|
+
// RFC 0173 §A.2: a recorded relaxation denies the profile its obligation
|
|
693
|
+
// belongs to. Matched the same way the verifier matches it, so the emitter
|
|
694
|
+
// and `verifyBundleV3` cannot disagree about the same bundle.
|
|
695
|
+
const relaxedObligations = new Set((relaxations ?? []).map((r) => r.obligation));
|
|
696
|
+
const relaxedProfile = (p: string): boolean => [...relaxedObligations].some((o) => p.includes(o));
|
|
697
|
+
const claimed3 = claimedProfiles.filter((p) => !(p in DEPRECATED_PROFILE_ALIASES)).map((p) => ({ id: p, evidenceTier: args.evidenceTier, witnessCount: witnessCountFor(p), certified: !relaxedProfile(p) && (verdictFor(p)?.certifiable ?? false) && !notHeld.has(p) && !rejectedProfiles.some((v) => v.profile === p) && totals3.blocked === 0 }));
|
|
659
698
|
const lockPath = resolvePath(conformanceRoot, 'dist', 'spec-artifacts.lock.json');
|
|
660
699
|
const lock = existsSync(lockPath) ? (JSON.parse(readFileSync(lockPath, 'utf8')) as { version: string; stampSha256: string }) : undefined;
|
|
661
700
|
const nonPass = rows3.filter((r) => r.result !== 'executed-pass');
|
|
@@ -690,7 +729,10 @@ async function runCertify(args: ParsedArgs, baseUrl: string, apiKey: string): Pr
|
|
|
690
729
|
const scrubbed3 = scrubEvidence(v3, secrets3);
|
|
691
730
|
const v3Out = scrubbed3.value as BundleV3;
|
|
692
731
|
const audit3 = verifyBundleV3(v3Out, { hostPublicKeyPem: publicKeyFromPrivate(signingKeyPem) });
|
|
693
|
-
|
|
732
|
+
// `relaxed-profile-certified` is no longer excused: the emitter now computes
|
|
733
|
+
// the relaxation into `certified`, so if the verifier still finds that
|
|
734
|
+
// contradiction the bundle is genuinely malformed and MUST NOT be written.
|
|
735
|
+
const emitterDefects = audit3.rejections.filter((r) => !['blocked-certified', 'independent-unverifiable'].includes(r.kind));
|
|
694
736
|
if (emitterDefects.length > 0) {
|
|
695
737
|
// Keep the rejected bundle: a self-verification failure with nothing to
|
|
696
738
|
// inspect sent a host patching a scratch copy of this CLI to see it.
|
|
@@ -861,6 +903,74 @@ async function main(): Promise<never> {
|
|
|
861
903
|
process.exit(0);
|
|
862
904
|
}
|
|
863
905
|
|
|
906
|
+
// `--verify <bundle.json>` — audit a bundle WITHOUT a host and without cloning
|
|
907
|
+
// the corpus. `verifyBundleV3` was exported but only ever called as the
|
|
908
|
+
// emitter's self-check, so auditing a third party's certification claim meant
|
|
909
|
+
// writing a driver script — which means in practice nobody did, and a
|
|
910
|
+
// certification program whose verifier ships only as a library function is
|
|
911
|
+
// asking to be taken on trust.
|
|
912
|
+
//
|
|
913
|
+
// The exit contract has FOUR values on purpose. "Structurally coherent" and
|
|
914
|
+
// "verified" are different facts, and collapsing them is how the vacuous pass
|
|
915
|
+
// gets rebuilt: without exit 2, every CI that runs `--verify` with no key
|
|
916
|
+
// prints green and reports "verified".
|
|
917
|
+
if (args.verifyPath !== undefined) {
|
|
918
|
+
if (!existsSync(args.verifyPath)) {
|
|
919
|
+
process.stderr.write(`openwop-conformance --verify: no such file: ${args.verifyPath}\n`);
|
|
920
|
+
process.exit(3);
|
|
921
|
+
}
|
|
922
|
+
let bundle: BundleV3;
|
|
923
|
+
try {
|
|
924
|
+
bundle = JSON.parse(readFileSync(args.verifyPath, 'utf8')) as BundleV3;
|
|
925
|
+
} catch (e) {
|
|
926
|
+
process.stderr.write(`openwop-conformance --verify: ${args.verifyPath} is not readable JSON — ${(e as Error).message}\n`);
|
|
927
|
+
process.exit(3);
|
|
928
|
+
}
|
|
929
|
+
const hostKey = args.verifyHostKeyPath !== undefined ? readFileSync(args.verifyHostKeyPath, 'utf8') : undefined;
|
|
930
|
+
const verifierKey = args.verifierKeyPath !== undefined ? readFileSync(args.verifierKeyPath, 'utf8') : undefined;
|
|
931
|
+
const verdict = verifyBundleV3(bundle, {
|
|
932
|
+
...(hostKey !== undefined ? { hostPublicKeyPem: hostKey } : {}),
|
|
933
|
+
...(verifierKey !== undefined ? { verifierPublicKeyPem: verifierKey } : {}),
|
|
934
|
+
});
|
|
935
|
+
const t = bundle.results?.totals ?? {};
|
|
936
|
+
const out: string[] = [
|
|
937
|
+
`bundle: ${args.verifyPath}`,
|
|
938
|
+
`host: ${bundle.host?.name ?? '?'} build ${bundle.host?.build?.kind ?? '?'}:${String(bundle.host?.build?.id ?? '?').slice(0, 12)}`,
|
|
939
|
+
`suite: ${bundle.suite?.version ?? '?'} (this CLI is ${suiteVersion()})`,
|
|
940
|
+
`totals: executedPass=${t.executedPass ?? '?'} executedFail=${t.executedFail ?? '?'} blocked=${t.blocked ?? '?'} inapplicable=${t.inapplicable ?? '?'} skipped=${t.skipped ?? '?'}`,
|
|
941
|
+
`certified: ${verdict.certifiedProfiles.length > 0 ? verdict.certifiedProfiles.join(', ') : '(none)'}`,
|
|
942
|
+
'',
|
|
943
|
+
'What this command does NOT do:',
|
|
944
|
+
' · It does not re-run anything. A host that measured itself wrongly, and signed',
|
|
945
|
+
' the result, verifies clean here. This audits an attestation, not a host.',
|
|
946
|
+
' · It does not resolve keyId against the live host. It checks the signature under',
|
|
947
|
+
' the key YOU supply with --host-key; it cannot tell you that key is the host\u2019s.',
|
|
948
|
+
' · It does not judge suite currency. The suite version is reported above as data:',
|
|
949
|
+
' an older bundle measured less, and whose fact that is belongs to its emitter.',
|
|
950
|
+
'',
|
|
951
|
+
];
|
|
952
|
+
if (verdict.rejections.length > 0) {
|
|
953
|
+
out.push(`REJECTED \u2014 ${verdict.rejections.length} problem(s):`);
|
|
954
|
+
for (const r of verdict.rejections) out.push(` [${r.kind}]${r.profile ? ` (${r.profile})` : ''} ${r.detail}`);
|
|
955
|
+
process.stdout.write(out.join('\n') + '\n');
|
|
956
|
+
process.exit(1);
|
|
957
|
+
}
|
|
958
|
+
const gaps: string[] = [];
|
|
959
|
+
if (!verdict.signatureVerified) gaps.push('the host signature was NOT verified (no --host-key supplied)');
|
|
960
|
+
if (!verdict.derivabilityChecked) gaps.push('the certified list was NOT re-derived from the bundle\u2019s discovery document \u2014 it is the emitter\u2019s word');
|
|
961
|
+
if ((bundle.claimedProfiles ?? []).some((p) => p.evidenceTier === 'independent') && !verdict.verifierSignatureVerified) gaps.push('an `independent` tier is claimed but the verifier signature was NOT verified (no --verifier-key)');
|
|
962
|
+
if (gaps.length > 0) {
|
|
963
|
+
out.push('COHERENT, NOT INDEPENDENTLY VERIFIED \u2014 zero rejections, but:');
|
|
964
|
+
for (const g of gaps) out.push(` \u00b7 ${g}`);
|
|
965
|
+
out.push('', 'Do not quote this as "verified". Exit 2 exists to keep that distinction.');
|
|
966
|
+
process.stdout.write(out.join('\n') + '\n');
|
|
967
|
+
process.exit(2);
|
|
968
|
+
}
|
|
969
|
+
out.push('VERIFIED \u2014 zero rejections, host signature checked, certified list re-derived.');
|
|
970
|
+
process.stdout.write(out.join('\n') + '\n');
|
|
971
|
+
process.exit(0);
|
|
972
|
+
}
|
|
973
|
+
|
|
864
974
|
// Env vars OVERRIDE flags only when the flag was unset (consistent
|
|
865
975
|
// with the rest of the harness — env wins on the absence of CLI input).
|
|
866
976
|
const env: NodeJS.ProcessEnv = { ...process.env };
|