@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
|
@@ -20,12 +20,17 @@
|
|
|
20
20
|
* - the `capabilities.purposePropagation` family shape: `supported`
|
|
21
21
|
* REQUIRED, `additionalProperties:false`.
|
|
22
22
|
*
|
|
23
|
-
* B.
|
|
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
|
-
*
|
|
28
|
-
* (
|
|
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 (
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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: [
|