@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.
- package/CHANGELOG.md +64 -0
- package/dist/cli.js +5 -1
- package/dist/lib/certification-bundle-v3.js +29 -0
- package/dist/spec-artifacts.lock.json +2 -2
- package/fixtures/connection-packs/connection-pack-acme-widgets.json +31 -0
- package/fixtures.md +2 -1
- package/package.json +2 -2
- package/requirement-aliases.json +167 -2
- package/requirements.json +610 -770
- 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 +27 -0
- package/src/lib/memoryAttribution.ts +30 -3
- package/src/scenarios/memory-attribution-replay-stable.test.ts +30 -6
- package/src/scenarios/v2-provider-conflict.test.ts +33 -15
- package/src/scenarios/v2-run-fork-prefix.test.ts +85 -1
- package/src/scenarios/v2-unmapped-type-refused.test.ts +82 -5
|
@@ -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.
|
|
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.
|
|
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: {
|
|
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);
|