@openwop/openwop-conformance 2.0.3 → 2.0.5

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.
@@ -11,9 +11,21 @@
11
11
  *
12
12
  * An era-2 log carrying one `foo.bar` row is seeded through the event-log seed
13
13
  * seam (lib/era2-seed.ts); the read is driven through poll and, where `replay`
14
- * is advertised, through a fork. A vendor-prefixed control (read under its own
15
- * name) needs an org registered under `extensions` in `spec/v2/declaration.json`
16
- * that the suite does not own, so that half is not driven.
14
+ * is advertised, through a fork.
15
+ *
16
+ * THE CONTROL, and why it was missing. Until this revision only the REFUSAL
17
+ * half was driven, and a host that refuses EVERY type the codemap does not name
18
+ * — including a registered vendor type it is required to pass through — went
19
+ * green on the whole file while violating `persistence.md` §The codemap is data.
20
+ * A one-sided rule is not witnessed by its own negative case. The positive half
21
+ * needs an org registered in the `extensions` object of `spec/v2/declaration.json`
22
+ * that no host owns; the registry did not exist, so the suite could not drive
23
+ * it and this docblock recorded the gap instead of closing it. `extensions` now
24
+ * exists and holds `example`, reserved by the protocol and never assignable to
25
+ * a vendor (RFC 2606 precedent), so `example.thing-happened` is a type that is
26
+ * registered, unmapped, and owned by nobody — exactly the control this file
27
+ * lacked. The two legs are each other's non-vacuity check: refusing both is a
28
+ * defect, accepting both is a defect, and only the pair can tell them apart.
17
29
  *
18
30
  * @see spec/v2/core/persistence.md §The reader rule
19
31
  * @see spec/v2/core/events.md §Reading an era-2 log
@@ -24,11 +36,35 @@ import { v2Discovery, gateFamily } from '../lib/v2.js';
24
36
  import { readErrorCode, readRetriable } from '../lib/error-envelope.js';
25
37
  import { softSkip } from '../lib/soft-skip.js';
26
38
  import { req } from '../lib/requirement-ids.js';
27
- import { era2Gate, eventsOf, forkRun, pollEvents, seedEra2Log, type SeedEvent } from '../lib/era2-seed.js';
39
+ import { codemapV1toV2, era2Gate, eventsOf, forkRun, pollEvents, registeredOrgs, seedEra2Log, type SeedEvent } from '../lib/era2-seed.js';
28
40
 
29
41
  const DOC = 'spec/v2/core/persistence.md §The reader rule';
42
+ const MAP = 'spec/v2/core/persistence.md §The codemap is data';
30
43
  const CODE = 'event_type_unmapped';
44
+ const ID_VENDOR = 'openwop.requirement.0176.vendor-type-passthrough';
31
45
  const UNMAPPED = 'foo.bar';
46
+ /** Registered (`extensions` in the declaration), unmapped, owned by no host. */
47
+ const VENDOR = 'example.thing-happened';
48
+ const orgOf = (type: string): string => type.split('.')[0] ?? '';
49
+
50
+ /**
51
+ * The two types this file drives are only meaningful under preconditions the
52
+ * corpus can move: `foo` must stay unregistered, `example` must stay
53
+ * registered, and neither type may acquire a codemap row. Until this revision
54
+ * those were a note inside a seeded payload — prose in a place no runner reads,
55
+ * asserting a fact nothing checked. If `foo` is ever registered, the refusal
56
+ * leg would quietly begin testing the opposite rule and still pass. Checked
57
+ * here so the file fails loudly instead.
58
+ */
59
+ function preconditions(): { ok: true } | { ok: false; kind: 'blocked' | 'inapplicable'; reason: string } {
60
+ const registered = registeredOrgs();
61
+ if (registered === undefined) return { ok: false, kind: 'inapplicable', reason: 'spec/v2/declaration.json is not resolvable in this layout — the vendor-org registry decides which half of the reader rule each type exercises, and guessing it would make the suite the registry' };
62
+ if (registered.has(orgOf(UNMAPPED))) return { ok: false, kind: 'blocked', reason: `the refusal leg drives ${UNMAPPED}, whose org '${orgOf(UNMAPPED)}' is NOW REGISTERED in spec/v2/declaration.json extensions — it is a vendor type that must pass through, not an unmapped one that must be refused; pick an unregistered org for this leg` };
63
+ if (!registered.has(orgOf(VENDOR))) return { ok: false, kind: 'blocked', reason: `the control leg needs org '${orgOf(VENDOR)}' registered in spec/v2/declaration.json extensions (registered: ${[...registered].join(', ') || 'none'}) — without a registered org the positive half of the vendor rule cannot be driven at all` };
64
+ const map = codemapV1toV2();
65
+ for (const t of [UNMAPPED, VENDOR]) if (map.has(t)) return { ok: false, kind: 'blocked', reason: `${t} now has a codemap row (→ ${String(map.get(t))}) — both legs require a type the codemap does not name` };
66
+ return { ok: true };
67
+ }
32
68
 
33
69
  async function discovery(): Promise<Record<string, unknown> | null> {
34
70
  try { return await v2Discovery(); } catch { return null; }
@@ -39,7 +75,18 @@ function unmappedLog(): SeedEvent[] {
39
75
  const ts = (i: number) => new Date(t0 + i * 1000).toISOString();
40
76
  return [
41
77
  { type: 'run.started', sequence: 0, payload: { workflowId: 'conformance-noop' }, timestamp: ts(0) },
42
- { type: UNMAPPED, sequence: 1, payload: { note: 'not a codemap row, not a vendor org' }, timestamp: ts(1) },
78
+ { type: UNMAPPED, sequence: 1, payload: {}, timestamp: ts(1) },
79
+ { type: 'run.completed', sequence: 2, payload: { durationMs: 2000 }, timestamp: ts(2) },
80
+ ];
81
+ }
82
+
83
+ /** The same shape carrying a REGISTERED vendor type instead of an unmapped one. */
84
+ function vendorLog(): SeedEvent[] {
85
+ const t0 = Date.parse('2026-01-15T12:00:00.000Z');
86
+ const ts = (i: number) => new Date(t0 + i * 1000).toISOString();
87
+ return [
88
+ { type: 'run.started', sequence: 0, payload: { workflowId: 'conformance-noop' }, timestamp: ts(0) },
89
+ { type: VENDOR, sequence: 1, payload: { marker: 'era2-vendor-passthrough' }, timestamp: ts(1) },
43
90
  { type: 'run.completed', sequence: 2, payload: { durationMs: 2000 }, timestamp: ts(2) },
44
91
  ];
45
92
  }
@@ -50,6 +97,8 @@ describe('RFC 0176 §A.3 — unmapped-type-refused (seam-gated)', () => {
50
97
  if (!doc) return softSkip('blocked', 'discovery unreachable');
51
98
  const gate = era2Gate(doc);
52
99
  if (gate !== null && !gate.ok) return softSkip(gate.kind, gate.reason);
100
+ const pre = preconditions();
101
+ if (!pre.ok) return softSkip(pre.kind, pre.reason);
53
102
  const seeded = await seedEra2Log(unmappedLog(), 'completed');
54
103
  if (!seeded.ok) return softSkip(seeded.kind, seeded.reason);
55
104
  const res = await pollEvents(seeded.runId);
@@ -68,12 +117,40 @@ describe('RFC 0176 §A.3 — unmapped-type-refused (seam-gated)', () => {
68
117
  ).toBe(false);
69
118
  });
70
119
 
120
+ it('a REGISTERED vendor type the codemap does not name is read under its own name, unchanged', async () => {
121
+ const doc = await discovery();
122
+ if (!doc) return softSkip('blocked', 'discovery unreachable');
123
+ const gate = era2Gate(doc);
124
+ if (gate !== null && !gate.ok) return softSkip(gate.kind, gate.reason);
125
+ const pre = preconditions();
126
+ if (!pre.ok) return softSkip(pre.kind, pre.reason);
127
+ const seeded = await seedEra2Log(vendorLog(), 'completed');
128
+ if (!seeded.ok) return softSkip(seeded.kind, seeded.reason);
129
+ const res = await pollEvents(seeded.runId);
130
+ if (res === null) return softSkip('blocked', 'GET /runs/{runId}/events/poll unreachable (fetch failed)');
131
+ // The whole point of the control: this is the SAME log shape as the refusal
132
+ // leg with one segment changed, so a 500 here says the host is refusing on
133
+ // "not in the codemap" and not on "not a registered org" — the tolerant
134
+ // reader's mirror image, and just as wrong.
135
+ expect(
136
+ res.status,
137
+ req(ID_VENDOR, MAP, `a vendor-prefixed type whose org IS registered MUST be read under its own name unchanged, not refused — got ${res.status}${res.status === 500 ? ` ${readErrorCode(res.json) ?? ''}: the host refuses every type the codemap does not name, which fails the registered half of the rule` : ''}`.trim()),
138
+ ).toBe(200);
139
+ const types = eventsOf(res.json).map((e) => String(e.type));
140
+ expect(
141
+ types.includes(VENDOR),
142
+ req(ID_VENDOR, MAP, `the ${VENDOR} row MUST appear under its own name — read back [${types.join(', ')}]; a translated or dropped row is a private mapping, and the codemap is the only authority`),
143
+ ).toBe(true);
144
+ });
145
+
71
146
  it('a fork of the same log is refused for the same reason — the rule binds every reader', async () => {
72
147
  const doc = await discovery();
73
148
  if (!doc) return softSkip('blocked', 'discovery unreachable');
74
149
  const gate = era2Gate(doc);
75
150
  if (gate !== null && !gate.ok) return softSkip(gate.kind, gate.reason);
76
151
  if (!(await gateFamily('replay'))) return softSkip('inapplicable', 'replay family not advertised (gate recorded under openwop.family.replay) — the fork reader has no surface');
152
+ const pre = preconditions();
153
+ if (!pre.ok) return softSkip(pre.kind, pre.reason);
77
154
  const log = unmappedLog();
78
155
  const seeded = await seedEra2Log(log, 'completed');
79
156
  if (!seeded.ok) return softSkip(seeded.kind, seeded.reason);