@openwop/openwop-conformance 1.53.0 → 1.53.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,9 @@
1
1
  # `@openwop/openwop-conformance` Changelog
2
2
 
3
+ ## [1.53.1] — 2026-07-06 — fix: purpose-propagation behavioral legs are seam-gated, not advertisement-gated
4
+
5
+ `purpose-propagation.test.ts`'s four two-hop behavioral legs gated on `capabilities.purposePropagation.supported` being advertised. That made a non-vacuous graduation witness impossible: RFC 0128 is `Active`, and `capabilities.md` §purposePropagation prohibits advertising `purposePropagation.supported:true` until it reaches `Accepted` — so under `OPENWOP_REQUIRE_BEHAVIOR=true` an honest-off host could only soft-skip (vacuous), yet an advertising host would breach the pre-Accepted prohibition. Re-gated the legs onto **seam availability** (the `POST /v1/host/sample/purpose-propagation/forward` seam returning non-404) instead of the advert — matching how RFC 0122's `self-hosted-runner.test.ts` behavioral legs are seam-gated and witnessed non-vacuously with `selfHostedRunner.supported:false`. The host now witnesses honest-OFF: the seam runs behind its feature flag, the assertions execute against real onward/dropped output, and non-vacuity is proven by the live seam + the steward's independent curl. No scenario-count change (386); schema-probe layer + RFC 0127 scenario unchanged.
6
+
3
7
  ## [1.53.0] — 2026-07-06 — RFC 0127 streaming/CDC trigger sources + RFC 0128 purpose-propagation labels (CDP Track-2)
4
8
 
5
9
  Adds `trigger-stream-cdc-sources.test.ts` + `purpose-propagation.test.ts` (suite 384 → 386) for the two `Active` CDP Track-2 RFCs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "1.53.0",
3
+ "version": "1.53.1",
4
4
  "description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,12 +20,17 @@
20
20
  * - the `capabilities.purposePropagation` family shape: `supported`
21
21
  * REQUIRED, `additionalProperties:false`.
22
22
  *
23
- * B. Capability-gated behavioral legs via the two-hop seam
23
+ * B. SEAM-gated behavioral legs via the two-hop seam
24
24
  * `POST /v1/host/sample/purpose-propagation/forward` (the suite plays
25
25
  * hop A — the labelled sender — and hop C — the onward receiver — around
26
- * the host at B). Soft-skips when the seam is unwired (404/405);
27
- * REQUIRED once `purposePropagation.supported` is advertised
28
- * (advertise-only-what-you-honor):
26
+ * the host at B). Soft-skips ONLY when the seam is unwired (404/405)
27
+ * deliberately NOT gated on `capabilities.purposePropagation.supported`,
28
+ * so the graduation witness runs on a host that is honest-OFF (advert
29
+ * prohibited until RFC 0128 is `Accepted`) yet serves the seam behind its
30
+ * feature flag. This mirrors RFC 0122's `self-hosted-runner` seam-gated
31
+ * legs (witnessed non-vacuously with `selfHostedRunner.supported:false`).
32
+ * Non-vacuity is proven by the seam being live + assertions running, and
33
+ * by the steward's independent curl — not by the advert:
29
34
  * 1. survive/narrow — a forwarded label arrives ⊆ what B received;
30
35
  * 2. never-widen — strictly no purpose beyond the input set;
31
36
  * 3. derived output — a merge of two labelled inputs arrives ⊆ their
@@ -48,8 +53,6 @@ import Ajv2020 from 'ajv/dist/2020.js';
48
53
  import addFormats from 'ajv-formats';
49
54
  import { SCHEMAS_DIR, FIXTURES_DIR } from '../lib/paths.js';
50
55
  import { driver } from '../lib/driver.js';
51
- import { behaviorGate } from '../lib/behavior-gate.js';
52
- import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
53
56
 
54
57
  const HTTP_SKIP = !process.env.OPENWOP_BASE_URL;
55
58
  const SEAM = '/v1/host/sample/purpose-propagation/forward';
@@ -103,20 +106,25 @@ describe('purpose-propagation: label schema (always-on, server-free)', () => {
103
106
  });
104
107
  });
105
108
 
106
- describe.skipIf(HTTP_SKIP)('purpose-propagation: two-hop onward behavior (capability-gated)', () => {
109
+ describe.skipIf(HTTP_SKIP)('purpose-propagation: two-hop onward behavior (seam-gated)', () => {
107
110
  async function seamPost(body: Record<string, unknown>): Promise<ForwardResponse | null> {
108
111
  const res = await driver.post(SEAM, body);
109
112
  if (res.status === 404 || res.status === 405) return null; // seam unwired — soft-skip
110
113
  return (res.json as ForwardResponse | undefined) ?? {};
111
114
  }
112
115
 
113
- async function gate(): Promise<boolean> {
114
- const fam = await readCapabilityFamily<{ supported?: boolean }>('purposePropagation');
115
- return behaviorGate('purposePropagation', fam?.supported === true);
116
- }
116
+ // SEAM-gated, NOT advertisement-gated (matches self-hosted-runner.test.ts). The
117
+ // behavioral legs drive the `purpose-propagation/forward` seam and soft-skip on a
118
+ // 404 (seam unwired). They deliberately do NOT gate on
119
+ // `capabilities.purposePropagation.supported`, because the graduation witness runs
120
+ // on a host that is honest-OFF (the advert is prohibited until RFC 0128 is
121
+ // `Accepted`, `capabilities.md` §purposePropagation) yet serves the seam behind its
122
+ // feature flag — exactly how RFC 0122's `self-hosted-runner` scenario witnessed
123
+ // non-vacuously with `selfHostedRunner.supported:false`. Non-vacuity is proven by
124
+ // the seam being live (assertions run against real onward/dropped output), verified
125
+ // by the steward's independent curl, NOT by an advertised capability.
117
126
 
118
127
  it('a forwarded label survives ⊆ the received set (re-emit; MAY narrow, MUST NOT widen)', async () => {
119
- if (!(await gate())) return;
120
128
  const input = ['analytics', 'marketing-email'];
121
129
  const res = await seamPost({ mode: 'forward', records: [{ id: 'r1', permittedPurposes: input, data: { k: 1 } }] });
122
130
  if (res === null) return;
@@ -135,7 +143,6 @@ describe.skipIf(HTTP_SKIP)('purpose-propagation: two-hop onward behavior (capabi
135
143
  });
136
144
 
137
145
  it('a derived (merged) output arrives ⊆ the intersection of contributing labelled inputs', async () => {
138
- if (!(await gate())) return;
139
146
  const res = await seamPost({
140
147
  mode: 'merge',
141
148
  records: [
@@ -155,7 +162,6 @@ describe.skipIf(HTTP_SKIP)('purpose-propagation: two-hop onward behavior (capabi
155
162
  });
156
163
 
157
164
  it('an unlabelled input adds no constraint to a merge', async () => {
158
- if (!(await gate())) return;
159
165
  const res = await seamPost({
160
166
  mode: 'merge',
161
167
  records: [
@@ -173,7 +179,6 @@ describe.skipIf(HTTP_SKIP)('purpose-propagation: two-hop onward behavior (capabi
173
179
  });
174
180
 
175
181
  it('[]-labelled data is fail-closed dropped from onward emission (positive control: unlabelled twin forwards)', async () => {
176
- if (!(await gate())) return;
177
182
  const res = await seamPost({
178
183
  mode: 'forward',
179
184
  records: [