@openwop/openwop-conformance 1.136.9 → 1.136.11

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.136.9",
3
+ "version": "1.136.11",
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.136.9",
4
- "corpusCommit": "6d2c33ba0be0e4d6934f5f9471600c66e8c5c183"
3
+ "suiteVersion": "1.136.11",
4
+ "corpusCommit": "d52dd83825840335271db2bd1696476b4bbcb634"
5
5
  }
@@ -20,7 +20,10 @@
20
20
  */
21
21
 
22
22
  import { describe, it, expect } from 'vitest';
23
+ import { existsSync, readFileSync } from 'node:fs';
24
+ import { join } from 'node:path';
23
25
  import { verifyBundle, verifyBundleProfile, PROFILE_FLOOR_SCENARIOS } from '../lib/profiles.js';
26
+ import { SCENARIOS_DIR } from '../lib/paths.js';
24
27
 
25
28
  /** Derives `openwop-core` and `openwop-stream-sse` (transports omitted ⇒ all). */
26
29
  const streamingDiscovery = {
@@ -135,5 +138,67 @@ describe('RFC 0148 §C — floor enforcement is not vacuous', () => {
135
138
  for (const { profile, scenario } of all) {
136
139
  expect(scenario, `${profile} floor cites a non-scenario filename`).toMatch(/\.test\.ts$/);
137
140
  }
141
+
142
+ // 2026-08-18: this leg's NAME promised "files that exist" and it only
143
+ // matched the `.test.ts` suffix — a string check wearing an existence
144
+ // check's name. The phantom `audit-log-verification.test.ts` floor row sat
145
+ // in `openwop-core-standard` until `--certify` hit it against a live host,
146
+ // because nothing here opened the directory.
147
+ if (SCENARIOS_DIR === null) return; // published layout ships src/, but stay honest if it ever does not
148
+ const dir = SCENARIOS_DIR as string;
149
+ const missing = all
150
+ .filter(({ scenario }) => !existsSync(join(dir, scenario)))
151
+ .map(({ profile, scenario }) => `${profile} → ${scenario}`);
152
+ expect(
153
+ missing,
154
+ 'a floor cites a scenario file that does not exist — that requirement can never be satisfied, ' +
155
+ 'so the profile can never certify, for a reason unrelated to any host',
156
+ ).toEqual([]);
157
+ });
158
+
159
+ it('no floor scenario is corpus-only — a floor must be provable from the published package', () => {
160
+ // openwop-app's suggestion, and it is right that this be a red test rather
161
+ // than a paragraph: the failure mode is SOMEONE LATER adding a corpus
162
+ // self-check to a floor, and prose in a PR body will not be in front of
163
+ // them.
164
+ //
165
+ // A scenario that can only run in a repo checkout (it reads `spec/` or
166
+ // `RFCS/` through `V1_DIR`) records `blocked` in the published layout,
167
+ // where the package ships no corpus by design. `blocked` is correct there
168
+ // — RFC 0148 §A defines it as a missing dependency — but a `blocked`
169
+ // requirement in a claimed profile invalidates that profile. So a
170
+ // corpus-only scenario in a floor makes the profile UNCERTIFIABLE from the
171
+ // npm tarball, permanently, for a reason no host can fix. It is the mirror
172
+ // of the phantom-row trap above: that one named a file that does not
173
+ // exist, this one names a file that cannot execute where certification is
174
+ // measured.
175
+ if (SCENARIOS_DIR === null) return;
176
+ const dir = SCENARIOS_DIR as string;
177
+
178
+ /** Every `it`/`test` in the file is V1_DIR-guarded, or every `describe` is. */
179
+ const isCorpusOnly = (source: string): boolean => {
180
+ if (!source.includes('V1_DIR')) return false;
181
+ const guard = /\(V1_DIR === null\)/;
182
+ const its = [...source.matchAll(/\b(?:it|test)(\.skipIf\([^)]*\))?\s*\(/g)];
183
+ const describes = [...source.matchAll(/\bdescribe(\.skipIf\([^)]*\))?\s*\(/g)];
184
+ const allItsGuarded = its.length > 0 && its.every((m) => guard.test(m[1] ?? ''));
185
+ const allDescribesGuarded = describes.length > 0 && describes.every((m) => guard.test(m[1] ?? ''));
186
+ return allItsGuarded || allDescribesGuarded;
187
+ };
188
+
189
+ const offenders = Object.entries(PROFILE_FLOOR_SCENARIOS)
190
+ .flatMap(([profile, floor]) => floor.required.map((scenario) => ({ profile, scenario })))
191
+ .filter(({ scenario }) => {
192
+ const path = join(dir, scenario);
193
+ return existsSync(path) && isCorpusOnly(readFileSync(path, 'utf8'));
194
+ })
195
+ .map(({ profile, scenario }) => `${profile} → ${scenario}`);
196
+
197
+ expect(
198
+ offenders,
199
+ 'a profile floor cites a CORPUS-ONLY scenario (all of its tests are guarded on `V1_DIR === null`). ' +
200
+ 'It records `blocked` in the published layout, so that profile can never certify from the npm ' +
201
+ 'tarball — no host can fix it. Keep corpus self-checks out of floors.',
202
+ ).toEqual([]);
138
203
  });
139
204
  });
@@ -13,16 +13,21 @@
13
13
  * events; a tier-2 host suppressed them, which leaves the replayed log SHORT
14
14
  * of the source's and breaks RFC 0041 §C byte-equivalence).
15
15
  *
16
- * The divergence is the spec's, not the hosts': RFC 0057 §D says the host
17
- * "MUST re-emit the recorded events from the log" and then, in a
18
- * non-normative implementation note in the same section, blesses the
19
- * reference host for "suppress[ing] rather than re-emit[ting]". Resolving
20
- * that contradiction is a normative decision (it plausibly makes one shipped
21
- * host non-conformant), so this leg does NOT pick a side. What it stops
22
- * doing is passing silently: an empty replay now records `blocked` naming
23
- * the contradiction, per RFC 0148 §A a leg that cannot observe MUST NOT
24
- * read as a pass. Once §D is resolved, the winning side becomes an assertion
25
- * here.
16
+ * The spec was never actually ambiguous, and an earlier revision of this file
17
+ * said it was that was wrong. `replay.md` §"Determinism guarantees"
18
+ * caveat 5 (Stable, unchanged since v1.2) names this exact case: recorded-fact
19
+ * events such as `memory.written` "are fixed history. On replay against a
20
+ * checkpoint a host MUST re-emit them from the event log and MUST NOT
21
+ * regenerate their identifiers or timestamps." RFC 0041 §C independently
22
+ * requires caching "emitted events" so a replay reproduces the observable
23
+ * sequence. RFC 0057 §D's implementation note appeared to bless suppression
24
+ * and conceded in its own words that it satisfied only the "MUST NOT
25
+ * regenerate" half; that note is retired (RFC 0057, Correction 2026-08-18).
26
+ *
27
+ * So this asserts BOTH halves — every replayed id is a recorded id, AND the
28
+ * source's recorded ids all reappear. A suppressing host fails, and always
29
+ * should have: nothing normative changed, the instrument just could not see
30
+ * it before.
26
31
  *
27
32
  * Gated on `capabilities.memory.attribution.emitsWriteEvents`; soft-skips
28
33
  * when unadvertised, when the seeded run wrote no memory, or when the host
@@ -68,19 +73,26 @@ describe('memory-attribution-replay-stable (RFC 0057 §D)', () => {
68
73
  }
69
74
 
70
75
  const replayed = await memoryWrittenEvents(forkId);
71
- if (replayed.length === 0) {
72
- // NOT a pass. The source run recorded `memory.written` events and the
73
- // replay carries none, so this host is on the "suppress" side of the
74
- // RFC 0057 §D contradiction. Whether that is conformant is undecided;
75
- // that it is unobserved here is not (RFC 0148 §A).
76
- return softSkip(
77
- 'blocked',
78
- `replay emitted no memory.written while the source recorded ${recordedIds.size} — ` +
79
- 'the host suppresses rather than re-emits. RFC 0057 §D requires re-emission in its ' +
80
- 'normative half and blesses suppression in its implementation note; until that ' +
81
- 'contradiction is resolved this leg records the divergence instead of passing on it.',
82
- );
83
- }
76
+ // Half 1 — MUST re-emit. The source's recorded ids all reappear on the
77
+ // replay. This is the half that went unasserted for months: the loop below
78
+ // is vacuously true on an empty array, so a host that suppressed the
79
+ // re-emission entirely passed.
80
+ const replayedIds = new Set(
81
+ replayed.map((e) => memoryIdOf(e.payload)).filter((x): x is string => x !== null),
82
+ );
83
+ const missing = [...recordedIds].filter((id) => !replayedIds.has(id));
84
+ expect(
85
+ missing,
86
+ driver.describe(
87
+ 'replay.md §"Determinism guarantees" caveat 5',
88
+ 'recorded-fact events are fixed history: a replay MUST re-emit the source run\'s ' +
89
+ '`memory.written` events from the log. A replay whose log is SHORT of the source\'s is not ' +
90
+ 'a reproduction of the observable sequence (RFC 0041 §C). Suppressing the write is not ' +
91
+ 'enough — that satisfies only the MUST-NOT-regenerate half',
92
+ ),
93
+ ).toEqual([]);
94
+
95
+ // Half 2 — MUST NOT regenerate. Every id on the replay is one the source recorded.
84
96
  for (const e of replayed) {
85
97
  const id = memoryIdOf(e.payload);
86
98
  expect(