@openwop/openwop-conformance 2.0.0-rc.36 → 2.0.0-rc.38
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/README.md
CHANGED
|
@@ -112,7 +112,7 @@ Exit code is non-zero on any failed assertion. `--certify` distinguishes: `0`
|
|
|
112
112
|
|
|
113
113
|
## What's Covered
|
|
114
114
|
|
|
115
|
-
The current suite has
|
|
115
|
+
The current suite has 502 scenario files under `src/scenarios/`.
|
|
116
116
|
- 2026-09-03 (suite `1.157.0 -> 1.158.0`, gap G17): NEW `idempotency-concurrent-claim.test.ts` — drives the new `host-sample-test-seams.md` §25 concurrent duplicate-delivery seam for the RFC 0150 §B / `idempotency.md` §"Concurrent duplicates (Layer 2)" atomic-claim MUST, which is unconditional and had no witness of any kind. Asserts every executor mints the SAME `logicalInvocationId` **before** asserting `delivered === 1` — without the identity check a host passes by minting different ids and never colliding, one effect because nothing raced. Not profile-gated and so not opt-out-able (the obligation is unconditional); an unmounted seam records `blocked`, which is not certifiable. Graduates `layer2-invocation-claim-atomic` reference-impl -> protocol.
|
|
117
117
|
- 2026-08-19 (suite `1.137.0 → 1.138.0`): NEW `durability-poison-exhaustion.test.ts` — RFC 0158 §C.8, the FIRST row of that RFC's conformance table to land. Asserts what `failure-path.test.ts` cannot: not just that deterministically failing work reaches terminal, but that attempts STOP — counted on the log, re-counted after a scaled quiet window, asserted unchanged. A host still redelivering records more. Seam-gated on the existing event-log seam (`blocked` = unobservable, not unmet) and outside every profile floor.
|
|
118
118
|
- 2026-08-19 (suite `1.136.15 → 1.137.0`): NEW `replay-fanout-suppression.test.ts` — capability-gated on `webhooks.supported`, **outside every profile floor**; witnesses `replay.md` §"Host-initiated fan-out is an external effect", which was the largest normative MUST NOT on the replay surface with no scenario and no SECURITY invariant. Three legs in ONE `it` against ONE receiver and ONE subscription — a positive control, the MUST NOT, and a `branch` boundary leg — because "no delivery arrived" passes identically when delivery never worked, so absence is asserted only after presence is proven on that exact wiring. A host with an SSRF guard correctly refuses the loopback receiver and records `blocked`: **unobservable, not unmet.**
|
|
@@ -457,7 +457,7 @@ Server-required (added in 1.7.0):
|
|
|
457
457
|
| ------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
458
458
|
| **Redaction** | [`capabilities.md`](../spec/v1/capabilities.md) §"Secrets" + NFR-7 + §"aiProviders" | Vendor-neutral assertions that the server doesn't leak secret material. Three scenario groups: (a) discovery shape contract — `secrets` + `aiProviders` advertisements are well-formed regardless of `secrets.supported`; when `supported === true`, scopes MUST be non-empty + `resolution === 'host-managed'`; `byok ⊆ supported`. (b) bearer-token redaction — invalid Bearer canary in `Authorization` header is not echoed in the 401 response body. (c) credentialRef echo control — gated on `secrets.supported === true`; canary planted in `configurable.ai.credentialRef` MUST NOT appear in any RunEvent payload (poll-based capture; transport-agnostic). Uses runtime-built canary fixtures (`lib/canaries.ts`) that defeat static secret scanners. 6 scenarios. |
|
|
459
459
|
|
|
460
|
-
Current source tree:
|
|
460
|
+
Current source tree: 502 scenario files. Use [`coverage.md`](./coverage.md) for current grade/gap tracking.
|
|
461
461
|
|
|
462
462
|
## Remaining Gaps
|
|
463
463
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "@openwop/spec-artifacts",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
4
|
-
"stampSha256": "
|
|
3
|
+
"version": "2.0.0-rc.38",
|
|
4
|
+
"stampSha256": "3fe6266c2542cbeb718b22938297cba87b0deb4567b213dbef983a51d2b35155"
|
|
5
5
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openwop/openwop-conformance",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.38",
|
|
4
4
|
"description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -56,6 +56,6 @@
|
|
|
56
56
|
"@openwop/spec-artifacts": "file:../spec-artifacts"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@openwop/spec-artifacts": "2.0.0-rc.
|
|
59
|
+
"@openwop/spec-artifacts": "2.0.0-rc.38"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/requirements.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"$comment": "GENERATED by conformance/scripts/generate-requirement-registry.mjs — do not edit. One record per it()/test() in src/scenarios. Ids: openwop.it.<file-stem>.<title-slug>[~n] (src/lib/requirement-ids.ts). A record with id null has an interpolated title; its run-time row is keyed by the rendered title and maps here by file+line only. Renamed ids need a row in requirement-aliases.json.",
|
|
3
3
|
"generatedFrom": "src/scenarios/*.test.ts",
|
|
4
4
|
"counts": {
|
|
5
|
-
"files":
|
|
6
|
-
"tests":
|
|
7
|
-
"withStableId":
|
|
5
|
+
"files": 549,
|
|
6
|
+
"tests": 2125,
|
|
7
|
+
"withStableId": 2125,
|
|
8
8
|
"interpolatedTitles": 0,
|
|
9
9
|
"explicitIds": 2057
|
|
10
10
|
},
|
|
@@ -28038,6 +28038,35 @@
|
|
|
28038
28038
|
}
|
|
28039
28039
|
]
|
|
28040
28040
|
},
|
|
28041
|
+
{
|
|
28042
|
+
"id": "openwop.it.v2-stream-sse-projection.every-data-frame-on-the-major-2-stream-carries-the-tenant-bound-runid-of-the-run",
|
|
28043
|
+
"file": "v2-stream-sse-projection.test.ts",
|
|
28044
|
+
"line": 52,
|
|
28045
|
+
"title": "every data: frame on the major-2 stream carries the tenant-bound runId of the run it belongs to",
|
|
28046
|
+
"explicitId": null,
|
|
28047
|
+
"citations": [
|
|
28048
|
+
{
|
|
28049
|
+
"section": "spec/v2/core/identity.md §5",
|
|
28050
|
+
"requirement": null,
|
|
28051
|
+
"interpolated": true
|
|
28052
|
+
},
|
|
28053
|
+
{
|
|
28054
|
+
"section": null,
|
|
28055
|
+
"requirement": null,
|
|
28056
|
+
"interpolated": true
|
|
28057
|
+
},
|
|
28058
|
+
{
|
|
28059
|
+
"section": null,
|
|
28060
|
+
"requirement": null,
|
|
28061
|
+
"interpolated": true
|
|
28062
|
+
},
|
|
28063
|
+
{
|
|
28064
|
+
"section": null,
|
|
28065
|
+
"requirement": null,
|
|
28066
|
+
"interpolated": true
|
|
28067
|
+
}
|
|
28068
|
+
]
|
|
28069
|
+
},
|
|
28041
28070
|
{
|
|
28042
28071
|
"id": "openwop.it.v2-subject-link-record.the-link-formed-across-scim-and-saml-is-a-closed-subjectlink-record",
|
|
28043
28072
|
"file": "v2-subject-link-record.test.ts",
|
package/scenario-majors.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "GENERATED by conformance/scripts/generate-scenario-majors.mjs (RFC 0168 §D.3). Do not edit; add a file to BOTH_MAJORS in the generator to target both majors.",
|
|
3
3
|
"counts": {
|
|
4
|
-
"files":
|
|
4
|
+
"files": 502,
|
|
5
5
|
"v1": 445,
|
|
6
|
-
"v2":
|
|
6
|
+
"v2": 58
|
|
7
7
|
},
|
|
8
8
|
"majors": {
|
|
9
9
|
"a2a-1-0-agent-card.test.ts": [
|
|
@@ -1378,6 +1378,9 @@
|
|
|
1378
1378
|
"v2-run-completed-outputs.test.ts": [
|
|
1379
1379
|
2
|
|
1380
1380
|
],
|
|
1381
|
+
"v2-stream-sse-projection.test.ts": [
|
|
1382
|
+
2
|
|
1383
|
+
],
|
|
1381
1384
|
"v2-subject-link-record.test.ts": [
|
|
1382
1385
|
2
|
|
1383
1386
|
],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "Provenance of @openwop/spec-artifacts (RFC 0168 §D.2). files: SHA-256 per file; the conformance suite compares the installed peer against dist/spec-artifacts.lock.json at start.",
|
|
3
3
|
"package": "@openwop/spec-artifacts",
|
|
4
|
-
"version": "2.0.0-rc.
|
|
4
|
+
"version": "2.0.0-rc.38",
|
|
5
5
|
"corpusTag": null,
|
|
6
6
|
"files": {
|
|
7
7
|
"api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
"api/grpc/openwop.proto": "c3e72bb17cba514ee98feb6434e6c9b6ea6795bfd086489ec69fd882dd1ad977",
|
|
10
10
|
"api/openapi.yaml": "39081c59fb696159806b0f2f9a42e7e9ff830d622fcf2ad4159b21357580a955",
|
|
11
11
|
"api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
|
|
12
|
-
"api/seams-v2.yaml": "
|
|
13
|
-
"api/v2/asyncapi.yaml": "
|
|
14
|
-
"api/v2/openapi.yaml": "
|
|
12
|
+
"api/seams-v2.yaml": "6a9d80a1b6e0854f31a49d3cf24b74c9f9d7fb3bf667fa4fafbad0b6d5587cfc",
|
|
13
|
+
"api/v2/asyncapi.yaml": "593e3bbe491f22b16e814feba6612e3bbdf94b95bbec9257fbf68eb0e5ae185f",
|
|
14
|
+
"api/v2/openapi.yaml": "975bdab57e6ce837a615e32f4284b1de9701155dcdf208b89da28cc9a9839330",
|
|
15
15
|
"api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
|
|
16
16
|
"schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
|
|
17
17
|
"schemas/a2a-task-state.schema.json": "c9365918f993f943b4b619d42551eb066a1ed33a08d895d51b395432a5b1f1bc",
|
|
@@ -199,7 +199,7 @@
|
|
|
199
199
|
"schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
|
|
200
200
|
"spec/v1/alias-detectors.json": "fee4594ef49953953ffcd0b3813300067d16b3e65ebff2aac722034ac9b3f545",
|
|
201
201
|
"spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
|
|
202
|
-
"spec/v1/core-standard-manifest.json": "
|
|
202
|
+
"spec/v1/core-standard-manifest.json": "3ac636a1fa387e3aede5648e1d002873c74ebb90f14aa23cc766fb293c24b38f",
|
|
203
203
|
"spec/v1/deprecations.json": "1d5acb69a9b8ccb57275a95605f74aef1d920685f8407c9d382a46b59dc803bb",
|
|
204
204
|
"spec/v1/deprecations.schema.json": "18c87e78bedc210431f795ae44c5b5d202f2f3317850d5cf86867d4f1fa1cdfb",
|
|
205
205
|
"spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
|
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
"spec/v2/path-manifest.json": "034152e09b1458c66810d4050e20a273b2b9b8fe2d92b66e8b819477de58a1be",
|
|
231
231
|
"spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
|
|
232
232
|
"spec/v2/profiles.json": "1f0ee40491131cf561ef19a0394c47ba476b4567514fe16befb6cada4a10a5a4",
|
|
233
|
-
"spec/v2/release.json": "
|
|
233
|
+
"spec/v2/release.json": "fa794ab0e06c4a574b7bbd8a207dc131c024ce8ca460e1add3a48659bd35e146"
|
|
234
234
|
},
|
|
235
|
-
"corpusCommit": "
|
|
235
|
+
"corpusCommit": "16c4d2ac92f8af03452ab80880211bbd274eda0d"
|
|
236
236
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `spec/v2/core/events.md` §SSE frames + `identity.md` §5 — every `data:` frame
|
|
3
|
+
* on the major-2 run stream names the run by its tenant-bound id (suite 2.0.0,
|
|
4
|
+
* target major 2; unaided).
|
|
5
|
+
*
|
|
6
|
+
* This scenario exists because both production hosts shipped the same defect
|
|
7
|
+
* and the suite could not see it. Each built one projection seam for the
|
|
8
|
+
* tenant-bound `runId` and each found an emitter outside it on the stream
|
|
9
|
+
* path: one host's SSE route had three `res.write` sites and no per-frame
|
|
10
|
+
* projector at all; the other's per-frame path projected while its `batch`
|
|
11
|
+
* flush wrote the raw array. Sixteen green mount tests on one host all used
|
|
12
|
+
* `res.json` handlers. Of the 56 `v2-*` files, none read a stream frame —
|
|
13
|
+
* `grep -E '^v2-.*(sse|stream)'` was empty — so both defects were invisible to
|
|
14
|
+
* the suite by construction and were found by a live witness and by a peer's
|
|
15
|
+
* report. A tier-1 host asked for this file by name.
|
|
16
|
+
*
|
|
17
|
+
* The assertion is one line: every frame's `runId` is the bound id the create
|
|
18
|
+
* returned. It is asserted per frame rather than on the first, because the
|
|
19
|
+
* batch-flush variant projects the first frame and not the rest.
|
|
20
|
+
*
|
|
21
|
+
* @see spec/v2/core/events.md §SSE frames
|
|
22
|
+
* @see spec/v2/core/identity.md §5
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { describe, it, expect } from 'vitest';
|
|
26
|
+
import { driver, type OpenWOPResponse } from '../lib/driver.js';
|
|
27
|
+
import { v2Discovery, v2Validator } from '../lib/v2.js';
|
|
28
|
+
import { streamEvents } from '../lib/era2-seed.js';
|
|
29
|
+
import { readErrorCode } from '../lib/error-envelope.js';
|
|
30
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
31
|
+
import { req } from '../lib/requirement-ids.js';
|
|
32
|
+
|
|
33
|
+
const ID = 'openwop.requirement.0171.stream-sse-projection';
|
|
34
|
+
const DOC = 'spec/v2/core/events.md §SSE frames';
|
|
35
|
+
const NOOP_WORKFLOW_ID = 'conformance-noop';
|
|
36
|
+
const RUN_ID = /^[A-Za-z0-9._~-]{1,128}\/[A-Za-z0-9._~-]{16,128}$/;
|
|
37
|
+
|
|
38
|
+
async function http(fn: () => Promise<OpenWOPResponse>): Promise<OpenWOPResponse | null> {
|
|
39
|
+
try { return await fn(); } catch { return null; }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function createRun(): Promise<{ runId: string } | { reason: string }> {
|
|
43
|
+
try { if (!(await v2Discovery())) return { reason: 'v2 discovery unreachable' }; } catch { return { reason: 'v2 discovery unreachable' }; }
|
|
44
|
+
const res = await http(() => driver.post('/runs', { workflowId: NOOP_WORKFLOW_ID }));
|
|
45
|
+
if (res === null) return { reason: 'POST /runs unreachable (fetch failed)' };
|
|
46
|
+
const runId = (res.json as { runId?: unknown } | undefined)?.runId;
|
|
47
|
+
if (res.status !== 201 || typeof runId !== 'string') return { reason: `POST /runs {workflowId: ${NOOP_WORKFLOW_ID}} answered ${res.status} ${readErrorCode(res.json) ?? ''} — the smallest valid create was refused (fixture not seeded?)`.trim() };
|
|
48
|
+
return { runId };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe('v2 stream-sse-projection (events.md §SSE frames)', () => {
|
|
52
|
+
it('every data: frame on the major-2 stream carries the tenant-bound runId of the run it belongs to', async () => {
|
|
53
|
+
const c = await createRun();
|
|
54
|
+
if ('reason' in c) return softSkip('blocked', c.reason);
|
|
55
|
+
expect(RUN_ID.test(c.runId), req(ID, 'spec/v2/core/identity.md §5', `the created runId MUST be tenant-bound before the stream can be held to it (got ${c.runId})`)).toBe(true);
|
|
56
|
+
|
|
57
|
+
const s = await streamEvents(c.runId);
|
|
58
|
+
if (s === null) return softSkip('blocked', 'GET /runs/{runId}/events (SSE, OpenWOP-Version: 2.0) unreachable (fetch failed)');
|
|
59
|
+
if (s.status !== 200) return softSkip('blocked', `GET /runs/{runId}/events (SSE) answered ${s.status} for the run just created`);
|
|
60
|
+
if (s.events.length === 0) return softSkip('blocked', 'the stream delivered no data: frames within the window — nothing to hold to the grammar');
|
|
61
|
+
|
|
62
|
+
const validate = v2Validator('run-event');
|
|
63
|
+
let i = 0;
|
|
64
|
+
for (const ev of s.events) {
|
|
65
|
+
i += 1;
|
|
66
|
+
const rid = (ev as { runId?: unknown }).runId;
|
|
67
|
+
expect(
|
|
68
|
+
typeof rid === 'string' && RUN_ID.test(rid),
|
|
69
|
+
req(ID, DOC, `frame ${i} (${String((ev as { type?: unknown }).type)}): data.runId MUST be tenant-bound <tenantId>/<opaque> (identity.md §5) — a stream frame is a rendering of the run event and every rendering of a v2 runId uses the same grammar; a bare storage id here is the projection seam missing the stream path (got ${JSON.stringify(rid)})`),
|
|
70
|
+
).toBe(true);
|
|
71
|
+
expect(
|
|
72
|
+
rid,
|
|
73
|
+
req(ID, DOC, `frame ${i}: data.runId MUST be the run's own bound id — asserted on EVERY frame, not the first, because a batch flush that skips the per-frame projector projects frame 1 and not the rest`),
|
|
74
|
+
).toBe(c.runId);
|
|
75
|
+
const r = validate(ev);
|
|
76
|
+
expect(r.ok, req(ID, DOC, `frame ${i}: data MUST be a valid RunEventDoc (run-event.schema.json) — ${r.errors}`)).toBe(true);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
package/src/setup.ts
CHANGED
|
@@ -40,6 +40,8 @@ import { softSkipDisposition } from './lib/soft-skip.js';
|
|
|
40
40
|
import { ItIdAllocator, takeExplicitRequirementId } from './lib/requirement-ids.js';
|
|
41
41
|
import { SPEC_COHERENCE_SCENARIOS, SPEC_COHERENCE_DETAIL } from './lib/spec-coherence.js';
|
|
42
42
|
import type { DiscoveryPayload } from './lib/profiles.js';
|
|
43
|
+
import { targetMajor } from './lib/seams.js';
|
|
44
|
+
import { softSkip } from './lib/soft-skip.js';
|
|
43
45
|
|
|
44
46
|
const SUITE_INIT_TIMEOUT_MS = 5_000;
|
|
45
47
|
|
|
@@ -277,6 +279,62 @@ function _fileOf(task: { file?: { filepath?: string; name?: string } } | undefin
|
|
|
277
279
|
const f = task?.file?.filepath ?? task?.file?.name;
|
|
278
280
|
return typeof f === 'string' && f.length > 0 ? basename(f) : null;
|
|
279
281
|
}
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// The applicability check and the assertion MUST run under the same contract.
|
|
284
|
+
//
|
|
285
|
+
// A scenario's registration in scenario-majors.json says which target majors it
|
|
286
|
+
// is written for. The driver reads OPENWOP_TARGET_MAJOR to decide which header
|
|
287
|
+
// and path space every probe uses. Nothing connected the two: a lane that ran
|
|
288
|
+
// vitest directly over all 501 files at the default (major 1) executed every
|
|
289
|
+
// major-2 scenario with major-1 requests. The scenarios' own gates call
|
|
290
|
+
// v2Discovery(), which sets the header EXPLICITLY, so the gate passed and the
|
|
291
|
+
// probe went out as v1 — a check that proved the host speaks v2 with one
|
|
292
|
+
// request, then tested a v2 requirement with a request that did not.
|
|
293
|
+
//
|
|
294
|
+
// Measured on a tier-1 host 2026-09-04: three "host defects" reported from that
|
|
295
|
+
// lane, all of which evaporated at major 2; the host was one edit from "fixing"
|
|
296
|
+
// correct behaviour. And in the other direction: 4 of 56 v2 files fail on every
|
|
297
|
+
// host forever under a major-1 driver, so a gate that runs them that way is red
|
|
298
|
+
// by construction and gets reasoned past. Both are the same defect: a scoped
|
|
299
|
+
// signal read as unscoped, with the scope nowhere in the output.
|
|
300
|
+
//
|
|
301
|
+
// So: a file whose registered majors do not include the driver's target major
|
|
302
|
+
// is INAPPLICABLE to this lane, recorded as such with the reason, and never
|
|
303
|
+
// probes. Files not in the registry (coherence checks, lib tests) are untouched.
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
const SCENARIO_MAJORS: Record<string, number[]> = (() => {
|
|
306
|
+
try {
|
|
307
|
+
const p = join(PKG_ROOT_PATH, 'scenario-majors.json');
|
|
308
|
+
if (!existsSync(p)) return {};
|
|
309
|
+
return (JSON.parse(readFileSync(p, 'utf8')) as { majors?: Record<string, number[]> }).majors ?? {};
|
|
310
|
+
} catch {
|
|
311
|
+
return {};
|
|
312
|
+
}
|
|
313
|
+
})();
|
|
314
|
+
|
|
315
|
+
beforeEach((ctx) => {
|
|
316
|
+
const p = (expect.getState() as { testPath?: string }).testPath;
|
|
317
|
+
if (!p) return;
|
|
318
|
+
const file = basename(p);
|
|
319
|
+
const majors = SCENARIO_MAJORS[file];
|
|
320
|
+
if (!majors) return;
|
|
321
|
+
const lane = targetMajor();
|
|
322
|
+
if (majors.includes(lane)) return;
|
|
323
|
+
const detail = `registered for target major ${majors.join('/')} and this lane runs at major ${lane} (OPENWOP_TARGET_MAJOR) — the probe would go out under a contract the scenario's gate does not use; select files with --target-major or set the variable`;
|
|
324
|
+
// Two records, one per resolver. The per-TEST disposition is read from the
|
|
325
|
+
// requirement journal (the same entry behaviorGate writes), so the row lands
|
|
326
|
+
// as `inapplicable` with this reason rather than `skipped`, which under RFC
|
|
327
|
+
// 0148 would claim the operator opted out. The per-FILE note covers the
|
|
328
|
+
// all-skipped fallback in resolveFileRecord.
|
|
329
|
+
try {
|
|
330
|
+
recordRequirement('openwop.family.lane-target-major', 'inapplicable', detail, { scenarioFile: file });
|
|
331
|
+
} catch {
|
|
332
|
+
/* never fail a test for bookkeeping */
|
|
333
|
+
}
|
|
334
|
+
softSkip('inapplicable', detail);
|
|
335
|
+
ctx.skip();
|
|
336
|
+
});
|
|
337
|
+
|
|
280
338
|
beforeAll(({}, suite) => {
|
|
281
339
|
// Mark the ledger journal BEFORE the file's tests run, so a behaviorGate
|
|
282
340
|
// decision made by the very first test is inside the file's window.
|