@openwop/openwop-conformance 1.102.0 → 1.104.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "1.102.0",
3
+ "version": "1.104.0",
4
4
  "description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "_comment": "Provenance of this vendored schemas/ copy. See conformance/README.md \u00a7\"Resolving the contract\". Compare against the stamp in your installed @openwop/openwop-conformance to detect a stale hand-copied contract.",
3
- "suiteVersion": "1.102.0",
4
- "corpusCommit": "6fa46a846fa43fd14b8499fb38d8230a9bbb6eb3"
3
+ "suiteVersion": "1.104.0",
4
+ "corpusCommit": "f31b9244c8ecb9743d79a140c19221ecae72e06e"
5
5
  }
@@ -48,6 +48,9 @@ import { describe, it, expect } from 'vitest';
48
48
  import { driver } from '../lib/driver.js';
49
49
  import { pollUntilTerminal } from '../lib/polling.js';
50
50
  import { isFixtureAdvertised } from '../lib/fixtures.js';
51
+ import { readFileSync } from 'node:fs';
52
+ import { join } from 'node:path';
53
+ import { V1_DIR } from '../lib/paths.js';
51
54
 
52
55
  const WORKFLOW_ID = 'conformance-replay-side-effect';
53
56
  const EFFECT_NODE = 'effect';
@@ -182,3 +185,89 @@ describe.skipIf(SKIP_NO_FIXTURE)('replay-side-effect-suppression: a replay does
182
185
  ).toBe(true);
183
186
  });
184
187
  });
188
+
189
+ /**
190
+ * `replay.md` requirement 4 — the manifest declaration is a classification FLOOR.
191
+ *
192
+ * A pack manifest may declare `role: "side-effect"`. Until 2026-08-14 the only
193
+ * stated purpose of that field was that it "drives engine scheduling", so a host
194
+ * was free to classify side-effecting nodes from a private list and never read
195
+ * it. A tier-1 host did exactly that and a shipped `core.storage.blob-put` node
196
+ * performed real object-store `PUT`s **during a replay**, past two independent
197
+ * guards, because neither consulted the manifest that had declared the node
198
+ * side-effecting all along.
199
+ *
200
+ * Requirement 4 makes the declaration binding: a host's own classifier is a floor
201
+ * ABOVE it and never a substitute — more nodes may be treated as side-effecting,
202
+ * never fewer.
203
+ *
204
+ * **This is a coherence gate, not a behavioral witness, and the distinction
205
+ * matters.** Proving a host honours requirement 4 needs that host advertising
206
+ * `recorded-outcome`, a pack node declaring the role, and an observable external
207
+ * effect across a replay — none of which the suite can synthesize. What it CAN
208
+ * do is ensure the declaration and the obligation cannot drift apart: if someone
209
+ * renames the role value, or drops the requirement, this fails. RFC 0148 §A
210
+ * resolves the behavioral half to `blocked`, not to a pass.
211
+ */
212
+ describe.skipIf(V1_DIR === null)('replay.md req 4 — the manifest role is a classification floor', () => {
213
+ const dir = V1_DIR as string;
214
+ const replay = () => readFileSync(join(dir, 'replay.md'), 'utf8');
215
+ const manifest = () =>
216
+ readFileSync(join(dir, '..', '..', 'schemas', 'node-pack-manifest.schema.json'), 'utf8');
217
+
218
+ it('the manifest still offers the role value the requirement binds', () => {
219
+ // If `side-effect` is renamed or dropped from the taxonomy, requirement 4
220
+ // binds a value nothing can declare — a rule with no reachable trigger.
221
+ expect(
222
+ manifest().includes('side-effect'),
223
+ 'node-pack-manifest.schema.json §role MUST still describe the `side-effect` value that ' +
224
+ 'replay.md requirement 4 makes binding. A requirement whose trigger no longer exists is ' +
225
+ 'not enforcement, it is decoration.',
226
+ ).toBe(true);
227
+ });
228
+
229
+ it('replay.md binds that declaration, naming the schema it comes from', () => {
230
+ const doc = replay();
231
+ expect(
232
+ /MUST\*{0,2} be treated as side-effecting/.test(doc) && doc.includes('node-pack-manifest.schema.json'),
233
+ 'replay.md §"Requirements when a host declares `sideEffectSuppression: \\"recorded-outcome\\"`" ' +
234
+ 'MUST bind a manifest-declared `role: "side-effect"` to the suppression obligation, and MUST ' +
235
+ 'name the schema the declaration comes from so a reader can find it.',
236
+ ).toBe(true);
237
+ });
238
+
239
+ it('a throw is named a backstop, not a discharge', () => {
240
+ // The question a tier-1 host asked after deriving the floor: does a guarded
241
+ // seam that THROWS discharge requirement 4, or must the replay succeed with
242
+ // the recorded outcome? Requirements 1 and 2 are two obligations — a throw
243
+ // satisfies "do not perform" and not "resolve the outcome" — but requirement
244
+ // 4 did not say so, and the answer decides whether hundreds of typeIds need
245
+ // classification or only seam coverage. An ambiguity that changes the size
246
+ // of the work by two orders of magnitude is not a footnote.
247
+ const doc = replay();
248
+ expect(
249
+ doc.includes('backstop, not a discharge'),
250
+ 'replay.md requirement 4 MUST state that a guarded-seam throw is a backstop rather than a ' +
251
+ 'discharge. It satisfies requirement 1 (no effect) and not requirement 2 (resolve the ' +
252
+ 'recorded outcome), and a host reading the floor as "make it throw" ships a replay that ' +
253
+ 'fails where this section says it must succeed.',
254
+ ).toBe(true);
255
+ expect(
256
+ /safe and\s+non-conformant/.test(doc),
257
+ 'replay.md requirement 4 MUST name the posture a throw-only host is in — safe and ' +
258
+ 'non-conformant — because "nothing escapes" is a weaker claim than `recorded-outcome` ' +
259
+ 'makes, and a host that conflates them advertises in good faith and still lies.',
260
+ ).toBe(true);
261
+ });
262
+
263
+ it('the floor direction is stated, not left to inference', () => {
264
+ // The dangerous reading is symmetric: "my classifier disagrees, so the
265
+ // manifest is wrong." The requirement is one-directional and says so.
266
+ expect(
267
+ replay().includes('MUST NOT classify fewer'),
268
+ 'replay.md requirement 4 MUST state the direction: a host classifier may treat MORE nodes as ' +
269
+ 'side-effecting and never fewer. Without the direction, a host that trusts its own list over ' +
270
+ 'the manifest can read the clause as permission to disagree.',
271
+ ).toBe(true);
272
+ });
273
+ });