@openwop/openwop-conformance 2.35.0 → 2.35.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # `@openwop/openwop-conformance` Changelog
2
2
 
3
+ ## [2.35.1] — 2026-09-22 — the opt-out check could never fire on a real bundle
4
+
5
+ - **RFC 0195 §3's `opted-out-but-advertised` rule, as shipped in 2.35.0, derived an empty set from every real bundle.** It read `openwop.profile.<name>` rows, and those rows never reach a bundle; the skipped rows that do named no profile. Measured on MyndHyve's committed 2.35.0 bundle, four declared opt-outs: `--verify` printed `opted out: (none)`. The rule's self-tests used synthetic rows that no real bundle contains — a check that cannot fail on the thing it exists to check.
6
+ - **The opted-out name now rides on the signed per-test rows' detail** (`…via OPENWOP_OPTED_OUT_PROFILES: <name>`), and the verifier derives the set from that as well as from the old carrier. Measured on the reference host with `connections.packsSupported` opted out: the requirement rows a bundle carries (`0177.provider-conflict.*`) now name it. A regression test uses rows shaped exactly like the committed MyndHyve bundle and pins that they derive nothing without the name.
7
+ - Bundles cut before 2.35.1 carry no names and derive an empty set — honest, not an error. RFC 0195 returns to `Active` until a committed bundle shows the rule deriving real opt-outs.
8
+ - Suite patch; corpus release stays `2.35.0`.
9
+
3
10
  ## [2.35.0] — 2026-09-21 — blocked means blocked, the declarations are signed, and the header a subscriber reads is checked
4
11
 
5
12
  ### Security
package/README.md CHANGED
@@ -11,7 +11,7 @@
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.35.0 @openwop/spec-artifacts@2.35.0
14
+ npm install --legacy-peer-deps @openwop/openwop-conformance@2.35.1 @openwop/spec-artifacts@2.35.1
15
15
  # or run without install:
16
16
  npx @openwop/openwop-conformance --base-url https://api.example.com --api-key hk_test_...
17
17
  ```
@@ -74,11 +74,28 @@ export function publicKeyFromPrivate(privateKeyPem) {
74
74
  }
75
75
  const OPT_OUT_ROW = 'openwop.profile.';
76
76
  /** The opt-outs a bundle's signed rows record. */
77
+ /**
78
+ * 2.35.1. The 2.35.0 version read only `openwop.profile.<name>` rows — which
79
+ * never reach a bundle — so on MyndHyve's real bundle (four declared opt-outs)
80
+ * it derived NOTHING, and `opted-out-but-advertised` could not fire: a rule
81
+ * pinned only by synthetic self-test rows. The name now rides on the signed
82
+ * per-test rows' detail (`…OPENWOP_OPTED_OUT_PROFILES: <name>`); both carriers
83
+ * are read. A bundle cut before 2.35.1 carries no names and derives an empty set.
84
+ */
85
+ const OPT_OUT_DETAIL = /OPENWOP_OPTED_OUT_PROFILES: ([a-z][A-Za-z0-9_.-]*)/;
77
86
  export function optedOutFromRows(rows) {
78
- return rows
79
- .filter((r) => r.id.startsWith(OPT_OUT_ROW) && r.result === 'skipped' && (r.detail ?? '').includes('OPENWOP_OPTED_OUT_PROFILES'))
80
- .map((r) => r.id.slice(OPT_OUT_ROW.length))
81
- .sort();
87
+ const names = new Set();
88
+ for (const r of rows) {
89
+ if (r.result !== 'skipped')
90
+ continue;
91
+ const d = r.detail ?? '';
92
+ if (r.id.startsWith(OPT_OUT_ROW) && d.includes('OPENWOP_OPTED_OUT_PROFILES'))
93
+ names.add(r.id.slice(OPT_OUT_ROW.length));
94
+ const m = OPT_OUT_DETAIL.exec(d);
95
+ if (m)
96
+ names.add(m[1]);
97
+ }
98
+ return [...names].sort();
82
99
  }
83
100
  export function verifyBundleV3(bundle, opts = {}) {
84
101
  const rejections = [];
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "package": "@openwop/spec-artifacts",
3
- "version": "2.35.0",
4
- "stampSha256": "164c05f7216f16c89a05636375b4e5eaebce3c461eb3c24fb5a992750633765e"
3
+ "version": "2.35.1",
4
+ "stampSha256": "4b3e43f8c7b34b218c358dd8e8bbd8ca06b480346c58962abc297349937f5ae9"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "2.35.0",
3
+ "version": "2.35.1",
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.35.0"
59
+ "@openwop/spec-artifacts": "2.35.1"
60
60
  }
61
61
  }
@@ -1,7 +1,7 @@
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.35.0",
4
+ "version": "2.35.1",
5
5
  "corpusTag": "v2.35.0",
6
6
  "files": {
7
7
  "api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
@@ -201,7 +201,7 @@
201
201
  "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
202
202
  "spec/v1/alias-detectors.json": "40069d5976eeb6ba1384a648e57e5cfd673db3fce36175115fb8293bee9664d4",
203
203
  "spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
204
- "spec/v1/core-standard-manifest.json": "ea55ec7d5a86d574479c545f3cb62ea6689ae91e4411c4ba6b27c22ca4d1f980",
204
+ "spec/v1/core-standard-manifest.json": "f053ba1cbab7b8a0b79d1af9f9453f263197af0b5146a14e01dd8900516017ba",
205
205
  "spec/v1/deprecations.json": "307083ce29c23fd406015951f99a30d78d6187ff061d38dc9732f62191b40f3f",
206
206
  "spec/v1/deprecations.schema.json": "18c87e78bedc210431f795ae44c5b5d202f2f3317850d5cf86867d4f1fa1cdfb",
207
207
  "spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
@@ -282,5 +282,5 @@
282
282
  "spec/v2/release.json": "5b9a956506b011835f1bd2fec54064d9cc8657d8a45a371314850da377d69069",
283
283
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624"
284
284
  },
285
- "corpusCommit": "a7ff2254a6ed9cbd47e327a95eb280e4aaf23699"
285
+ "corpusCommit": "5281a4ee3f878f9fe401e6f6a9faf33558cdf894"
286
286
  }
@@ -105,7 +105,13 @@ export function behaviorGate(profileName: string, advertised: boolean): boolean
105
105
  recordRequirement(
106
106
  profileRequirementId(profileName),
107
107
  'skipped',
108
- 'operator declared an honest opt-out via OPENWOP_OPTED_OUT_PROFILES',
108
+ // 2.35.1: the NAME rides on the detail. This row id never reaches a
109
+ // bundle; the per-test rows that do inherit this detail, and they are
110
+ // inside witnessSha256 — so the verifier can derive which profile or
111
+ // family was opted out from signed rows (RFC 0195 §3). Until 2.35.1 the
112
+ // detail named nothing and the verifier derived an empty set on every
113
+ // real bundle.
114
+ `operator declared an honest opt-out via OPENWOP_OPTED_OUT_PROFILES: ${profileName}`,
109
115
  );
110
116
  // eslint-disable-next-line no-console
111
117
  console.warn(
@@ -163,11 +163,25 @@ export interface V3Verdict {
163
163
 
164
164
  const OPT_OUT_ROW = 'openwop.profile.';
165
165
  /** The opt-outs a bundle's signed rows record. */
166
+ /**
167
+ * 2.35.1. The 2.35.0 version read only `openwop.profile.<name>` rows — which
168
+ * never reach a bundle — so on MyndHyve's real bundle (four declared opt-outs)
169
+ * it derived NOTHING, and `opted-out-but-advertised` could not fire: a rule
170
+ * pinned only by synthetic self-test rows. The name now rides on the signed
171
+ * per-test rows' detail (`…OPENWOP_OPTED_OUT_PROFILES: <name>`); both carriers
172
+ * are read. A bundle cut before 2.35.1 carries no names and derives an empty set.
173
+ */
174
+ const OPT_OUT_DETAIL = /OPENWOP_OPTED_OUT_PROFILES: ([a-z][A-Za-z0-9_.-]*)/;
166
175
  export function optedOutFromRows(rows: ReadonlyArray<{ readonly id: string; readonly result: string; readonly detail?: string }>): string[] {
167
- return rows
168
- .filter((r) => r.id.startsWith(OPT_OUT_ROW) && r.result === 'skipped' && (r.detail ?? '').includes('OPENWOP_OPTED_OUT_PROFILES'))
169
- .map((r) => r.id.slice(OPT_OUT_ROW.length))
170
- .sort();
176
+ const names = new Set<string>();
177
+ for (const r of rows) {
178
+ if (r.result !== 'skipped') continue;
179
+ const d = r.detail ?? '';
180
+ if (r.id.startsWith(OPT_OUT_ROW) && d.includes('OPENWOP_OPTED_OUT_PROFILES')) names.add(r.id.slice(OPT_OUT_ROW.length));
181
+ const m = OPT_OUT_DETAIL.exec(d);
182
+ if (m) names.add(m[1] as string);
183
+ }
184
+ return [...names].sort();
171
185
  }
172
186
 
173
187
  export function verifyBundleV3(bundle: BundleV3, opts: VerifyV3Options = {}): V3Verdict {