@openwop/openwop-conformance 2.0.4 → 2.0.6
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 +93 -0
- package/dist/cli.js +5 -1
- package/dist/lib/certification-bundle-v3.js +29 -0
- package/dist/lib/paths.js +4 -0
- package/dist/spec-artifacts.lock.json +2 -2
- package/package.json +2 -2
- package/requirement-aliases.json +167 -2
- package/requirements.json +606 -766
- package/scenario-majors.json +3 -2
- package/schemas/CORPUS-STAMP.json +49 -11
- package/src/cli.ts +5 -1
- package/src/lib/certification-bundle-v3.ts +54 -1
- package/src/lib/era2-seed.ts +83 -2
- package/src/lib/memoryAttribution.ts +30 -3
- package/src/lib/paths.ts +19 -0
- package/src/scenarios/memory-attribution-replay-stable.test.ts +30 -6
- package/src/scenarios/v2-run-fork-prefix.test.ts +85 -1
- package/src/scenarios/v2-unmapped-type-refused.test.ts +80 -5
|
@@ -11,9 +11,27 @@
|
|
|
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.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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.
|
|
29
|
+
*
|
|
30
|
+
* BECAUSE THEY CHECK OPPOSITE HALVES, THEY GATE SEPARATELY (`refusalGate` /
|
|
31
|
+
* `controlGate`). Suite 2.0.5 gated both on the control leg's precondition and
|
|
32
|
+
* so disabled the refusal witness wherever the registry did not resolve — on
|
|
33
|
+
* exactly the hosts it exists to catch. A precondition belongs to the leg that
|
|
34
|
+
* needs it, never to the file.
|
|
17
35
|
*
|
|
18
36
|
* @see spec/v2/core/persistence.md §The reader rule
|
|
19
37
|
* @see spec/v2/core/events.md §Reading an era-2 log
|
|
@@ -24,11 +42,26 @@ import { v2Discovery, gateFamily } from '../lib/v2.js';
|
|
|
24
42
|
import { readErrorCode, readRetriable } from '../lib/error-envelope.js';
|
|
25
43
|
import { softSkip } from '../lib/soft-skip.js';
|
|
26
44
|
import { req } from '../lib/requirement-ids.js';
|
|
27
|
-
import { era2Gate, eventsOf, forkRun, pollEvents, seedEra2Log, type SeedEvent } from '../lib/era2-seed.js';
|
|
45
|
+
import { codemapV1toV2, era2Gate, eventsOf, forkRun, pollEvents, registeredOrgs, seedEra2Log, unmappedRefusalGate, vendorControlGate, type Gate, type SeedEvent } from '../lib/era2-seed.js';
|
|
28
46
|
|
|
29
47
|
const DOC = 'spec/v2/core/persistence.md §The reader rule';
|
|
48
|
+
const MAP = 'spec/v2/core/persistence.md §The codemap is data';
|
|
30
49
|
const CODE = 'event_type_unmapped';
|
|
50
|
+
const ID_VENDOR = 'openwop.requirement.0176.vendor-type-passthrough';
|
|
31
51
|
const UNMAPPED = 'foo.bar';
|
|
52
|
+
/** Registered (`extensions` in the declaration), unmapped, owned by no host. */
|
|
53
|
+
const VENDOR = 'example.thing-happened';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The gate logic is PURE and lives in lib/era2-seed (`unmappedRefusalGate` /
|
|
57
|
+
* `vendorControlGate`) so its truth table can be measured directly — see
|
|
58
|
+
* `src/lib/era2-unmapped-gates.test.ts`. The rows that matter cannot be
|
|
59
|
+
* exercised from here: they are the layouts where the registry does NOT
|
|
60
|
+
* resolve, which is where the 2.0.5 defect lived and where a scenario running
|
|
61
|
+
* against a live host can never put itself.
|
|
62
|
+
*/
|
|
63
|
+
const refusalGate = (): Gate => unmappedRefusalGate(registeredOrgs(), codemapV1toV2(), UNMAPPED);
|
|
64
|
+
const controlGate = (): Gate => vendorControlGate(registeredOrgs(), codemapV1toV2(), VENDOR);
|
|
32
65
|
|
|
33
66
|
async function discovery(): Promise<Record<string, unknown> | null> {
|
|
34
67
|
try { return await v2Discovery(); } catch { return null; }
|
|
@@ -39,7 +72,18 @@ function unmappedLog(): SeedEvent[] {
|
|
|
39
72
|
const ts = (i: number) => new Date(t0 + i * 1000).toISOString();
|
|
40
73
|
return [
|
|
41
74
|
{ type: 'run.started', sequence: 0, payload: { workflowId: 'conformance-noop' }, timestamp: ts(0) },
|
|
42
|
-
{ type: UNMAPPED, sequence: 1, payload: {
|
|
75
|
+
{ type: UNMAPPED, sequence: 1, payload: {}, timestamp: ts(1) },
|
|
76
|
+
{ type: 'run.completed', sequence: 2, payload: { durationMs: 2000 }, timestamp: ts(2) },
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The same shape carrying a REGISTERED vendor type instead of an unmapped one. */
|
|
81
|
+
function vendorLog(): SeedEvent[] {
|
|
82
|
+
const t0 = Date.parse('2026-01-15T12:00:00.000Z');
|
|
83
|
+
const ts = (i: number) => new Date(t0 + i * 1000).toISOString();
|
|
84
|
+
return [
|
|
85
|
+
{ type: 'run.started', sequence: 0, payload: { workflowId: 'conformance-noop' }, timestamp: ts(0) },
|
|
86
|
+
{ type: VENDOR, sequence: 1, payload: { marker: 'era2-vendor-passthrough' }, timestamp: ts(1) },
|
|
43
87
|
{ type: 'run.completed', sequence: 2, payload: { durationMs: 2000 }, timestamp: ts(2) },
|
|
44
88
|
];
|
|
45
89
|
}
|
|
@@ -50,6 +94,8 @@ describe('RFC 0176 §A.3 — unmapped-type-refused (seam-gated)', () => {
|
|
|
50
94
|
if (!doc) return softSkip('blocked', 'discovery unreachable');
|
|
51
95
|
const gate = era2Gate(doc);
|
|
52
96
|
if (gate !== null && !gate.ok) return softSkip(gate.kind, gate.reason);
|
|
97
|
+
const pre = refusalGate();
|
|
98
|
+
if (!pre.ok) return softSkip(pre.kind, pre.reason);
|
|
53
99
|
const seeded = await seedEra2Log(unmappedLog(), 'completed');
|
|
54
100
|
if (!seeded.ok) return softSkip(seeded.kind, seeded.reason);
|
|
55
101
|
const res = await pollEvents(seeded.runId);
|
|
@@ -68,12 +114,41 @@ describe('RFC 0176 §A.3 — unmapped-type-refused (seam-gated)', () => {
|
|
|
68
114
|
).toBe(false);
|
|
69
115
|
});
|
|
70
116
|
|
|
117
|
+
it('a REGISTERED vendor type the codemap does not name is read under its own name, unchanged', async () => {
|
|
118
|
+
const doc = await discovery();
|
|
119
|
+
if (!doc) return softSkip('blocked', 'discovery unreachable');
|
|
120
|
+
const gate = era2Gate(doc);
|
|
121
|
+
if (gate !== null && !gate.ok) return softSkip(gate.kind, gate.reason);
|
|
122
|
+
const pre = controlGate();
|
|
123
|
+
if (!pre.ok) return softSkip(pre.kind, pre.reason);
|
|
124
|
+
const seeded = await seedEra2Log(vendorLog(), 'completed');
|
|
125
|
+
if (!seeded.ok) return softSkip(seeded.kind, seeded.reason);
|
|
126
|
+
const res = await pollEvents(seeded.runId);
|
|
127
|
+
if (res === null) return softSkip('blocked', 'GET /runs/{runId}/events/poll unreachable (fetch failed)');
|
|
128
|
+
// The whole point of the control: this is the SAME log shape as the refusal
|
|
129
|
+
// leg with one segment changed, so a 500 here says the host is refusing on
|
|
130
|
+
// "not in the codemap" and not on "not a registered org" — the tolerant
|
|
131
|
+
// reader's mirror image, and just as wrong.
|
|
132
|
+
expect(
|
|
133
|
+
res.status,
|
|
134
|
+
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()),
|
|
135
|
+
).toBe(200);
|
|
136
|
+
const types = eventsOf(res.json).map((e) => String(e.type));
|
|
137
|
+
expect(
|
|
138
|
+
types.includes(VENDOR),
|
|
139
|
+
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`),
|
|
140
|
+
).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
71
143
|
it('a fork of the same log is refused for the same reason — the rule binds every reader', async () => {
|
|
72
144
|
const doc = await discovery();
|
|
73
145
|
if (!doc) return softSkip('blocked', 'discovery unreachable');
|
|
74
146
|
const gate = era2Gate(doc);
|
|
75
147
|
if (gate !== null && !gate.ok) return softSkip(gate.kind, gate.reason);
|
|
76
148
|
if (!(await gateFamily('replay'))) return softSkip('inapplicable', 'replay family not advertised (gate recorded under openwop.family.replay) — the fork reader has no surface');
|
|
149
|
+
// The fork leg drives the UNMAPPED type, so it takes the refusal gate.
|
|
150
|
+
const pre = refusalGate();
|
|
151
|
+
if (!pre.ok) return softSkip(pre.kind, pre.reason);
|
|
77
152
|
const log = unmappedLog();
|
|
78
153
|
const seeded = await seedEra2Log(log, 'completed');
|
|
79
154
|
if (!seeded.ok) return softSkip(seeded.kind, seeded.reason);
|