@openwop/openwop-conformance 2.45.0 → 2.45.2
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 +37 -0
- package/dist/spec-artifacts.lock.json +2 -2
- package/package.json +2 -2
- package/requirements.json +138 -133
- package/schemas/CORPUS-STAMP.json +10 -10
- package/src/lib/run-list-walk.ts +65 -0
- package/src/scenarios/agentConfidenceEscalation.test.ts +8 -1
- package/src/scenarios/agentMemoryCrossTenantIsolation.test.ts +8 -1
- package/src/scenarios/agentMemoryRedactionContract.test.ts +8 -1
- package/src/scenarios/agentMemoryRoundTrip.test.ts +8 -1
- package/src/scenarios/agentMemoryTtlExpiry.test.ts +8 -1
- package/src/scenarios/agentMessageReducer.test.ts +8 -1
- package/src/scenarios/agentMetadata.test.ts +8 -1
- package/src/scenarios/agentPackCatalog.test.ts +13 -5
- package/src/scenarios/agentPackExport.test.ts +8 -2
- package/src/scenarios/agentPackHandoffSchemaValidation.test.ts +17 -2
- package/src/scenarios/agentPackInstall.test.ts +8 -2
- package/src/scenarios/agentPackProvenance.test.ts +8 -1
- package/src/scenarios/agentReasoningEvents.test.ts +9 -1
- package/src/scenarios/agentReasoningStreaming.test.ts +14 -1
- package/src/scenarios/append-ordering.test.ts +7 -2
- package/src/scenarios/bulk-cancel.test.ts +11 -1
- package/src/scenarios/cap-breach.test.ts +8 -1
- package/src/scenarios/channel-ttl.test.ts +7 -1
- package/src/scenarios/conversationCapabilityNegotiation.test.ts +8 -1
- package/src/scenarios/conversationLifecycle.test.ts +8 -1
- package/src/scenarios/conversationReplayDeterminism.test.ts +8 -2
- package/src/scenarios/conversationVsLegacySuspend.test.ts +8 -1
- package/src/scenarios/cost-attribution.test.ts +17 -4
- package/src/scenarios/debugBundle.test.ts +16 -5
- package/src/scenarios/dispatch-cross-worker-handoff.test.ts +10 -2
- package/src/scenarios/dispatch-output-mapping.test.ts +20 -4
- package/src/scenarios/dispatchLoop.test.ts +7 -1
- package/src/scenarios/durability-poison-exhaustion.test.ts +7 -2
- package/src/scenarios/highConcurrency.test.ts +11 -1
- package/src/scenarios/host-callback-declaration.test.ts +10 -1
- package/src/scenarios/identity-passthrough.test.ts +7 -1
- package/src/scenarios/interrupt-approval.test.ts +25 -5
- package/src/scenarios/interrupt-auth-required-resume.test.ts +9 -3
- package/src/scenarios/interrupt-external-event-correlation.test.ts +9 -2
- package/src/scenarios/interrupt-parent-child-cascade.test.ts +10 -2
- package/src/scenarios/interrupt-quorum-resolution.test.ts +9 -2
- package/src/scenarios/interrupt-token-matrix.test.ts +11 -3
- package/src/scenarios/interruptRace.test.ts +13 -3
- package/src/scenarios/multi-agent-confidence-escalation.test.ts +14 -3
- package/src/scenarios/multi-agent-handoff-state-machine.test.ts +15 -3
- package/src/scenarios/multi-node-ordering.test.ts +7 -1
- package/src/scenarios/node-failed-payload-shape.test.ts +7 -1
- package/src/scenarios/orchestratorConservativePath.test.ts +8 -1
- package/src/scenarios/orchestratorDispatch.test.ts +9 -1
- package/src/scenarios/orchestratorTermination.test.ts +8 -1
- package/src/scenarios/owner-subject-echo.test.ts +9 -2
- package/src/scenarios/pause-resume.test.ts +19 -8
- package/src/scenarios/prompt-all-four-kinds-events.test.ts +9 -2
- package/src/scenarios/prompt-end-to-end-events.test.ts +10 -2
- package/src/scenarios/replay-fork-arbitrary.test.ts +11 -4
- package/src/scenarios/restart-during-run.test.ts +7 -1
- package/src/scenarios/secret-leakage-otel-attribute.test.ts +14 -5
- package/src/scenarios/stream-text-fixture.test.ts +8 -2
- package/src/scenarios/streamReconnect.test.ts +8 -2
- package/src/scenarios/subworkflow.test.ts +9 -1
- package/src/scenarios/v2-error-registry.test.ts +7 -10
- package/src/scenarios/v2-run-list.test.ts +14 -16
- package/src/scenarios/version-fold.test.ts +8 -2
|
@@ -23,6 +23,7 @@ import { driver } from '../lib/driver.js';
|
|
|
23
23
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
24
24
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
25
25
|
import { req } from '../lib/requirement-ids.js';
|
|
26
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
26
27
|
|
|
27
28
|
const WORKFLOW_ID = 'conformance-cap-breach';
|
|
28
29
|
const RECURSION_LIMIT = 3;
|
|
@@ -37,8 +38,13 @@ interface RunEvent {
|
|
|
37
38
|
readonly payload?: unknown;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
42
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
43
|
+
(!isFixtureAdvertised(WORKFLOW_ID)) ? ['blocked', `the \`${WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
44
|
+
|
|
45
|
+
describe('cap-breach: conformance-cap-breach fixture fails with recursion_limit_exceeded', () => {
|
|
41
46
|
it('emits cap.breached + transitions to terminal failed when configurable.recursionLimit is exceeded', async () => {
|
|
47
|
+
if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
|
|
42
48
|
const create = await driver.post('/v1/runs', {
|
|
43
49
|
workflowId: WORKFLOW_ID,
|
|
44
50
|
configurable: { recursionLimit: RECURSION_LIMIT },
|
|
@@ -106,6 +112,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('cap-breach: conformance-cap-breach fixture fai
|
|
|
106
112
|
});
|
|
107
113
|
|
|
108
114
|
it('cap.breached precedes run.failed in the event sequence (breach detected before over-limit node fires)', async () => {
|
|
115
|
+
if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
|
|
109
116
|
const create = await driver.post('/v1/runs', {
|
|
110
117
|
workflowId: WORKFLOW_ID,
|
|
111
118
|
configurable: { recursionLimit: RECURSION_LIMIT },
|
|
@@ -23,6 +23,7 @@ import { driver } from '../lib/driver.js';
|
|
|
23
23
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
24
24
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
25
25
|
import { req } from '../lib/requirement-ids.js';
|
|
26
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
26
27
|
|
|
27
28
|
const WORKFLOW_ID = 'conformance-channel-ttl';
|
|
28
29
|
const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
|
|
@@ -32,8 +33,13 @@ interface ChannelEntry {
|
|
|
32
33
|
_ts: number;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
37
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
38
|
+
(!isFixtureAdvertised(WORKFLOW_ID)) ? ['blocked', `the \`${WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
39
|
+
|
|
40
|
+
describe('channel-ttl: conformance-channel-ttl drops entries older than ttlMs at write time', () => {
|
|
36
41
|
it('after the post-TTL write, the channel contains exactly one entry with value "d"', async () => {
|
|
42
|
+
if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
|
|
37
43
|
const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
|
|
38
44
|
expect(create.status).toBe(201);
|
|
39
45
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -22,14 +22,21 @@ import { driver } from '../lib/driver.js';
|
|
|
22
22
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
23
23
|
import { isConversationPrimitiveSupported } from '../lib/multi-agent-capabilities.js';
|
|
24
24
|
import { req } from '../lib/requirement-ids.js';
|
|
25
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
25
26
|
|
|
26
27
|
const FIXTURE = 'conformance-conversation-capability-negotiation';
|
|
27
28
|
// Inverted gate: this scenario runs when host does NOT advertise the
|
|
28
29
|
// capability, to verify the refusal contract.
|
|
29
30
|
const SKIP = isConversationPrimitiveSupported() || !isFixtureAdvertised(FIXTURE);
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
33
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
34
|
+
(isConversationPrimitiveSupported()) ? ['inapplicable', `the host advertises the conversation primitive; this scenario covers a host that does not`] as const :
|
|
35
|
+
(!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
36
|
+
|
|
37
|
+
describe('conversationCapabilityNegotiation: refusal contract', () => {
|
|
32
38
|
it('host without conversationPrimitive capability refuses conversation-bearing workflow', async () => {
|
|
39
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
33
40
|
const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
|
|
34
41
|
// The host MUST reject — either at workflow registration (404/400)
|
|
35
42
|
// or at run-create (400). What MUST NOT happen is a successful
|
|
@@ -23,12 +23,19 @@ import { pollUntilTerminal } from '../lib/polling.js';
|
|
|
23
23
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
24
24
|
import { isConversationPrimitiveSupported } from '../lib/multi-agent-capabilities.js';
|
|
25
25
|
import { req } from '../lib/requirement-ids.js';
|
|
26
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
26
27
|
|
|
27
28
|
const FIXTURE = 'conformance-conversation-lifecycle';
|
|
28
29
|
const SKIP = !isConversationPrimitiveSupported() || !isFixtureAdvertised(FIXTURE);
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
32
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
33
|
+
(!isConversationPrimitiveSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isConversationPrimitiveSupported() is false)`] as const :
|
|
34
|
+
(!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
35
|
+
|
|
36
|
+
describe('conversationLifecycle: open → exchange → close round-trip', () => {
|
|
31
37
|
it('emits all three lifecycle events with matching conversationId; no exchanges after close', async () => {
|
|
38
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
32
39
|
const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
|
|
33
40
|
expect(create.status, req('openwop.it.conversationLifecycle.emits-all-three-lifecycle-events-with-matching-conversationid-no-exchanges-after', 'RFCS/0005-conversation.md', 'emits all three lifecycle events with matching conversationId; no exchanges after close')).toBe(201);
|
|
34
41
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -19,15 +19,21 @@ import { driver } from '../lib/driver.js';
|
|
|
19
19
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
20
20
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
21
21
|
import { isConversationPrimitiveSupported } from '../lib/multi-agent-capabilities.js';
|
|
22
|
-
import { softSkip } from '../lib/soft-skip.js';
|
|
22
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
23
23
|
import { req } from '../lib/requirement-ids.js';
|
|
24
24
|
import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
|
|
25
25
|
|
|
26
26
|
const FIXTURE = 'conformance-conversation-replay';
|
|
27
27
|
const SKIP = !isConversationPrimitiveSupported() || !isFixtureAdvertised(FIXTURE);
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
30
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
31
|
+
(!isConversationPrimitiveSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isConversationPrimitiveSupported() is false)`] as const :
|
|
32
|
+
(!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
33
|
+
|
|
34
|
+
describe('conversationReplayDeterminism: replay-fork preserves conversation log', () => {
|
|
30
35
|
it('forked run yields byte-equal conversation channel projection', async () => {
|
|
36
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
31
37
|
// unfailable-leg audit wave 2, 2026-09-27: the replay-fork gate used to be
|
|
32
38
|
// the fork response itself (404/501 → soft-skip 'inapplicable'), reached
|
|
33
39
|
// only AFTER the create/terminal asserts — a host with no fork surface
|
|
@@ -21,12 +21,19 @@ import { pollUntilTerminal } from '../lib/polling.js';
|
|
|
21
21
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
22
22
|
import { isConversationPrimitiveSupported } from '../lib/multi-agent-capabilities.js';
|
|
23
23
|
import { req } from '../lib/requirement-ids.js';
|
|
24
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
24
25
|
|
|
25
26
|
const FIXTURE = 'conformance-conversation-vs-clarification';
|
|
26
27
|
const SKIP = !isConversationPrimitiveSupported() || !isFixtureAdvertised(FIXTURE);
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
30
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
31
|
+
(!isConversationPrimitiveSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isConversationPrimitiveSupported() is false)`] as const :
|
|
32
|
+
(!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
33
|
+
|
|
34
|
+
describe('conversationVsLegacySuspend: distinct event surfaces', () => {
|
|
29
35
|
it('conversation suspend emits conversation.* events, not clarification.*', async () => {
|
|
36
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
30
37
|
const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
|
|
31
38
|
expect(create.status).toBe(201);
|
|
32
39
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -37,7 +37,7 @@ import { pollUntilTerminal } from '../lib/polling.js';
|
|
|
37
37
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
38
38
|
import { getCollector, waitForRunSpans } from '../lib/otel-collector.js';
|
|
39
39
|
import { req } from '../lib/requirement-ids.js';
|
|
40
|
-
import { softSkip } from '../lib/soft-skip.js';
|
|
40
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Callback-shaped: the host exports OTLP metrics to the suite's collector.
|
|
@@ -75,8 +75,17 @@ const OPENWOP_COST_ATTRIBUTE_NAMES: readonly string[] = [
|
|
|
75
75
|
* in snake_case / kebab-case neighbors still match. */
|
|
76
76
|
const CREDENTIAL_SHAPE_RE = /(?<![A-Za-z0-9_])(?:sk-(?:ant-|proj-)?[A-Za-z0-9_-]{20,}|Bearer\s+[A-Za-z0-9._~+/=-]{20,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,})(?![A-Za-z0-9])|CANARY-openwop-CONFORMANCE-NEVER-SECRET[A-Za-z0-9_-]*/g;
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
79
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
80
|
+
(!isFixtureAdvertised(NOOP_WORKFLOW_ID)) ? ['blocked', `the \`${NOOP_WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
81
|
+
|
|
82
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
83
|
+
const GATE_WHY_2: readonly [SoftSkipKind, string] =
|
|
84
|
+
(!isFixtureAdvertised(COST_EMIT_WORKFLOW_ID)) ? ['blocked', `the \`${COST_EMIT_WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
85
|
+
|
|
86
|
+
describe('cost-attribution: metrics.openwopCost forward-compat shape (G6)', () => {
|
|
79
87
|
it('on any run, IF metrics.openwopCost is present, its shape MUST match the spec', async () => {
|
|
88
|
+
if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
|
|
80
89
|
// Use the noop fixture so we don't depend on AI nodes. The fixture
|
|
81
90
|
// doesn't emit recordCost, so metrics.openwopCost will typically be
|
|
82
91
|
// absent — that's allowed. The assertion is forward-compat: when
|
|
@@ -132,8 +141,9 @@ describe.skipIf(SKIP_NO_NOOP)('cost-attribution: metrics.openwopCost forward-com
|
|
|
132
141
|
// scenarios assert shape conformance + non-negative-integer/number
|
|
133
142
|
// constraints, not exact numeric equality, so any host-canary works.
|
|
134
143
|
|
|
135
|
-
describe
|
|
144
|
+
describe('cost-attribution: end-to-end roundtrip via conformance.cost.emit (G6 / O4)', () => {
|
|
136
145
|
it('metrics.openwopCost MUST carry the canary cost shape after the fixture node runs', async () => {
|
|
146
|
+
if (SKIP_NO_COST_EMIT) return softSkip(...GATE_WHY_2);
|
|
137
147
|
// Try to start the cost-emit fixture workflow. If the host doesn't
|
|
138
148
|
// advertise the fixture surface (production deployments don't), we
|
|
139
149
|
// get 404 / 422 back and skip the scenario.
|
|
@@ -221,6 +231,7 @@ describe.skipIf(SKIP_NO_COST_EMIT)('cost-attribution: end-to-end roundtrip via c
|
|
|
221
231
|
});
|
|
222
232
|
|
|
223
233
|
it('cost-emit fixture run MUST emit a node.completed event for the cost-emitting node', async () => {
|
|
234
|
+
if (SKIP_NO_COST_EMIT) return softSkip(...GATE_WHY_2);
|
|
224
235
|
const create = await driver.post('/v1/runs', {
|
|
225
236
|
workflowId: 'openwop-smoke-cost-emit',
|
|
226
237
|
});
|
|
@@ -247,7 +258,7 @@ describe.skipIf(SKIP_NO_COST_EMIT)('cost-attribution: end-to-end roundtrip via c
|
|
|
247
258
|
});
|
|
248
259
|
});
|
|
249
260
|
|
|
250
|
-
describe
|
|
261
|
+
describe('cost-attribution: G6 / O4 allowlist + redaction (live OTel spans)', () => {
|
|
251
262
|
// Drives the `openwop-smoke-cost-emit` fixture, which posts arbitrary
|
|
252
263
|
// `attrs` into `conformance.cost.emit` — a mix of (a) all 7
|
|
253
264
|
// allowlisted attribute names, (b) one non-allowlisted key
|
|
@@ -260,6 +271,7 @@ describe.skipIf(SKIP_NO_COST_EMIT)('cost-attribution: G6 / O4 allowlist + redact
|
|
|
260
271
|
// collector isn't available, matching `otel-emission.test.ts`).
|
|
261
272
|
|
|
262
273
|
it('only allowlisted openwop.cost.* attributes reach the OTel span (G6 close criteria — allowlist enforcement)', async () => {
|
|
274
|
+
if (SKIP_NO_COST_EMIT) return softSkip(...GATE_WHY_2);
|
|
263
275
|
if (!getCollector()) {
|
|
264
276
|
// eslint-disable-next-line no-console
|
|
265
277
|
console.warn('[cost-attribution] OTel collector not started; set OPENWOP_OTEL_COLLECTOR=true to run');
|
|
@@ -299,6 +311,7 @@ describe.skipIf(SKIP_NO_COST_EMIT)('cost-attribution: G6 / O4 allowlist + redact
|
|
|
299
311
|
});
|
|
300
312
|
|
|
301
313
|
it('credential-shaped canaries do NOT leak to any OTel attribute (G6 close criteria — redaction)', async () => {
|
|
314
|
+
if (SKIP_NO_COST_EMIT) return softSkip(...GATE_WHY_2);
|
|
302
315
|
if (!getCollector()) {
|
|
303
316
|
// eslint-disable-next-line no-console
|
|
304
317
|
console.warn('[cost-attribution] OTel collector not started; set OPENWOP_OTEL_COLLECTOR=true to run');
|
|
@@ -43,7 +43,7 @@ import { pollUntilTerminal } from '../lib/polling.js';
|
|
|
43
43
|
import { CANARY_MARKER, getCanary } from '../lib/canaries.js';
|
|
44
44
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
45
45
|
import { req } from '../lib/requirement-ids.js';
|
|
46
|
-
import { blockedDespiteAssertions, softSkip } from '../lib/soft-skip.js';
|
|
46
|
+
import { blockedDespiteAssertions, softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
47
47
|
import { makeTraceparent } from '../lib/trace-context.js';
|
|
48
48
|
|
|
49
49
|
const NOOP_WORKFLOW_ID = 'conformance-noop';
|
|
@@ -69,8 +69,13 @@ async function isAdvertised(): Promise<boolean> {
|
|
|
69
69
|
return body.debugBundle?.supported === true;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
73
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
74
|
+
(!isFixtureAdvertised(NOOP_WORKFLOW_ID)) ? ['blocked', `the \`${NOOP_WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
75
|
+
|
|
76
|
+
describe('debug-bundle: GET /v1/runs/{runId}/debug-bundle response shape', () => {
|
|
73
77
|
it('host advertising capabilities.debugBundle.supported returns 200 with valid bundle', async () => {
|
|
78
|
+
if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
|
|
74
79
|
if (!(await isAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isAdvertised())` returned early (skip-equivalent)'); // skip-equivalent
|
|
75
80
|
|
|
76
81
|
const create = await driver.post('/v1/runs', { workflowId: NOOP_WORKFLOW_ID });
|
|
@@ -125,6 +130,7 @@ describe.skipIf(SKIP_NO_NOOP)('debug-bundle: GET /v1/runs/{runId}/debug-bundle r
|
|
|
125
130
|
});
|
|
126
131
|
|
|
127
132
|
it('hosts not advertising debugBundle return 404 on the endpoint', async () => {
|
|
133
|
+
if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
|
|
128
134
|
if (await isAdvertised()) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `await isAdvertised()` returned early (skip-equivalent for hosts that DO advertise)'); // skip-equivalent for hosts that DO advertise
|
|
129
135
|
|
|
130
136
|
// Use any runId — even a synthetic one — since the host should 404
|
|
@@ -137,8 +143,9 @@ describe.skipIf(SKIP_NO_NOOP)('debug-bundle: GET /v1/runs/{runId}/debug-bundle r
|
|
|
137
143
|
});
|
|
138
144
|
});
|
|
139
145
|
|
|
140
|
-
describe
|
|
146
|
+
describe('debug-bundle: invariants per debug-bundle.md', () => {
|
|
141
147
|
it('metrics.eventCount equals events.length', async () => {
|
|
148
|
+
if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
|
|
142
149
|
if (!(await isAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isAdvertised())` returned early');
|
|
143
150
|
|
|
144
151
|
const create = await driver.post('/v1/runs', { workflowId: NOOP_WORKFLOW_ID });
|
|
@@ -159,6 +166,7 @@ describe.skipIf(SKIP_NO_NOOP)('debug-bundle: invariants per debug-bundle.md', ()
|
|
|
159
166
|
});
|
|
160
167
|
|
|
161
168
|
it('redactionApplied=true is incompatible with redactionMode=passthrough', async () => {
|
|
169
|
+
if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
|
|
162
170
|
if (!(await isAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isAdvertised())` returned early');
|
|
163
171
|
|
|
164
172
|
const create = await driver.post('/v1/runs', { workflowId: NOOP_WORKFLOW_ID });
|
|
@@ -179,6 +187,7 @@ describe.skipIf(SKIP_NO_NOOP)('debug-bundle: invariants per debug-bundle.md', ()
|
|
|
179
187
|
});
|
|
180
188
|
|
|
181
189
|
it('bundle events agree with /events/poll for the same run', async () => {
|
|
190
|
+
if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
|
|
182
191
|
if (!(await isAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isAdvertised())` returned early');
|
|
183
192
|
|
|
184
193
|
const create = await driver.post('/v1/runs', { workflowId: NOOP_WORKFLOW_ID });
|
|
@@ -202,8 +211,9 @@ describe.skipIf(SKIP_NO_NOOP)('debug-bundle: invariants per debug-bundle.md', ()
|
|
|
202
211
|
});
|
|
203
212
|
});
|
|
204
213
|
|
|
205
|
-
describe
|
|
214
|
+
describe('debug-bundle: redaction inheritance per SECURITY/invariants.yaml secret-leakage-debug-bundle', () => {
|
|
206
215
|
it('canary in workflow inputs MUST NOT appear verbatim in the bundle response', async () => {
|
|
216
|
+
if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
|
|
207
217
|
if (!(await isAdvertised())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!(await isAdvertised())` returned early');
|
|
208
218
|
|
|
209
219
|
const canary = getCanary('byok-credential-ref').value;
|
|
@@ -233,11 +243,12 @@ describe.skipIf(SKIP_NO_NOOP)('debug-bundle: redaction inheritance per SECURITY/
|
|
|
233
243
|
});
|
|
234
244
|
});
|
|
235
245
|
|
|
236
|
-
describe
|
|
246
|
+
describe('debug-bundle: spans join the trace (RFC 0207 §C, v1 only, non-gating)', () => {
|
|
237
247
|
const LEG = 'openwop.it.debugBundle.spans-join-the-trace';
|
|
238
248
|
const KINDS = new Set(['internal', 'server', 'client', 'producer', 'consumer']);
|
|
239
249
|
const CODES = new Set(['unset', 'ok', 'error']);
|
|
240
250
|
it('span traceId / kind / status have the RFC 0207 shape and the run span joins the caller trace', async () => {
|
|
251
|
+
if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
|
|
241
252
|
if (!(await isAdvertised())) return softSkip('inapplicable', 'debugBundle not advertised by this host');
|
|
242
253
|
const tp = makeTraceparent();
|
|
243
254
|
// Run create and the bundle read are asserted by the legs above; here they
|
|
@@ -26,7 +26,7 @@ import { driver } from '../lib/driver.js';
|
|
|
26
26
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
27
27
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
28
28
|
import { req } from '../lib/requirement-ids.js';
|
|
29
|
-
import { softSkip, blockedDespiteAssertions } from '../lib/soft-skip.js';
|
|
29
|
+
import { softSkip, blockedDespiteAssertions, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
30
30
|
|
|
31
31
|
const PARENT = 'conformance-dispatch-cross-worker-handoff';
|
|
32
32
|
const CHILD_A = 'conformance-dispatch-cross-worker-handoff-child-a';
|
|
@@ -48,8 +48,15 @@ interface RunSnapshot {
|
|
|
48
48
|
readonly variables?: Record<string, unknown>;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
52
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
53
|
+
(!isFixtureAdvertised(PARENT)) ? ['blocked', `the \`${PARENT}\` fixture is not advertised`] as const :
|
|
54
|
+
(!isFixtureAdvertised(CHILD_A)) ? ['blocked', `the \`${CHILD_A}\` fixture is not advertised`] as const :
|
|
55
|
+
(!isFixtureAdvertised(CHILD_B)) ? ['blocked', `the \`${CHILD_B}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
56
|
+
|
|
57
|
+
describe('dispatch-cross-worker-handoff: sequential child→parent→child variable flow (RFC 0022 §A + §D)', () => {
|
|
52
58
|
it('HVMAP-1c: child-a writes via perWorkerOutputMappings; child-b reads via perWorkerInputMappings; shared parent bag is the handoff channel', async () => {
|
|
59
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
53
60
|
const create = await driver.post('/v1/runs', { workflowId: PARENT });
|
|
54
61
|
expect(create.status).toBe(201);
|
|
55
62
|
const parentRunId = (create.json as { runId: string }).runId;
|
|
@@ -95,6 +102,7 @@ describe.skipIf(SKIP)('dispatch-cross-worker-handoff: sequential child→parent
|
|
|
95
102
|
});
|
|
96
103
|
|
|
97
104
|
it('HVMAP-1c-override: per-worker mapping overrides default mapping per §A effectiveInputMapping precedence', async () => {
|
|
105
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
98
106
|
const PARENT_OVERRIDE = 'conformance-dispatch-per-worker-override';
|
|
99
107
|
if (!isFixtureAdvertised(PARENT_OVERRIDE)) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!isFixtureAdvertised(PARENT_OVERRIDE)` returned early (fixture not seeded — soft-skip)'); // fixture not seeded — soft-skip
|
|
100
108
|
const create = await driver.post('/v1/runs', { workflowId: PARENT_OVERRIDE });
|
|
@@ -23,7 +23,7 @@ import { driver } from '../lib/driver.js';
|
|
|
23
23
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
24
24
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
25
25
|
import { req } from '../lib/requirement-ids.js';
|
|
26
|
-
import { softSkip } from '../lib/soft-skip.js';
|
|
26
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
27
27
|
|
|
28
28
|
const PARENT = 'conformance-dispatch-output-mapping';
|
|
29
29
|
const CHILD = 'conformance-dispatch-output-mapping-child';
|
|
@@ -34,8 +34,22 @@ interface RunSnapshot {
|
|
|
34
34
|
readonly variables?: Record<string, unknown>;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
38
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
39
|
+
(!isFixtureAdvertised(PARENT)) ? ['blocked', `the \`${PARENT}\` fixture is not advertised`] as const :
|
|
40
|
+
(!isFixtureAdvertised(CHILD)) ? ['blocked', `the \`${CHILD}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
41
|
+
|
|
42
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
43
|
+
const GATE_WHY_2: readonly [SoftSkipKind, string] =
|
|
44
|
+
(!isFixtureAdvertised('conformance-dispatch-deterministic-fail-child')) ? ['blocked', `the \`conformance-dispatch-deterministic-fail-child\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
45
|
+
|
|
46
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
47
|
+
const GATE_WHY_3: readonly [SoftSkipKind, string] =
|
|
48
|
+
(!isFixtureAdvertised('conformance-dispatch-cancellable-child')) ? ['blocked', `the \`conformance-dispatch-cancellable-child\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
49
|
+
|
|
50
|
+
describe('dispatch-output-mapping: child → parent variable harvest (RFC 0022 §A)', () => {
|
|
38
51
|
it('HVMAP-1b: outputMapping harvests child variables into parent variables on terminal completed', async () => {
|
|
52
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
39
53
|
const create = await driver.post('/v1/runs', { workflowId: PARENT });
|
|
40
54
|
expect(create.status).toBe(201);
|
|
41
55
|
const parentRunId = (create.json as { runId: string }).runId;
|
|
@@ -98,8 +112,9 @@ async function registerParent(childFixtureId: string): Promise<string | null> {
|
|
|
98
112
|
return workflowId;
|
|
99
113
|
}
|
|
100
114
|
|
|
101
|
-
describe
|
|
115
|
+
describe('dispatch-output-mapping: HVMAP-1b-failed (RFC 0022 §B)', () => {
|
|
102
116
|
it('child terminates `failed` → outputMapping MUST be skipped; parent.parentResult stays at sentinel', async () => {
|
|
117
|
+
if (!isFixtureAdvertised('conformance-dispatch-deterministic-fail-child')) return softSkip(...GATE_WHY_2);
|
|
103
118
|
const parentId = await registerParent('conformance-dispatch-deterministic-fail-child');
|
|
104
119
|
if (!parentId) return softSkip('blocked', 'precondition not met — `!parentId` returned early (workflow-register seam not exposed — soft-skip) (seam, prior step, or fixture unavailable)'); // workflow-register seam not exposed — soft-skip
|
|
105
120
|
const create = await driver.post('/v1/runs', { workflowId: parentId });
|
|
@@ -119,8 +134,9 @@ describe.skipIf(!isFixtureAdvertised('conformance-dispatch-deterministic-fail-ch
|
|
|
119
134
|
});
|
|
120
135
|
});
|
|
121
136
|
|
|
122
|
-
describe
|
|
137
|
+
describe('dispatch-output-mapping: HVMAP-1b-cancelled (RFC 0022 §B)', () => {
|
|
123
138
|
it('child terminates `cancelled` → outputMapping MUST be skipped; parent.parentResult stays at sentinel', async () => {
|
|
139
|
+
if (!isFixtureAdvertised('conformance-dispatch-cancellable-child')) return softSkip(...GATE_WHY_3);
|
|
124
140
|
const parentId = await registerParent('conformance-dispatch-cancellable-child');
|
|
125
141
|
if (!parentId) return softSkip('blocked', 'precondition not met — `!parentId` returned early (soft-skip) (seam, prior step, or fixture unavailable)'); // soft-skip
|
|
126
142
|
const create = await driver.post('/v1/runs', { workflowId: parentId });
|
|
@@ -16,12 +16,18 @@ import { driver } from '../lib/driver.js';
|
|
|
16
16
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
17
17
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
18
18
|
import { req } from '../lib/requirement-ids.js';
|
|
19
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
19
20
|
|
|
20
21
|
const DISPATCH_LOOP_WORKFLOW_ID = 'conformance-dispatch-loop';
|
|
21
22
|
const SKIP_NO_FIXTURE = !isFixtureAdvertised(DISPATCH_LOOP_WORKFLOW_ID);
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
25
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
26
|
+
(!isFixtureAdvertised(DISPATCH_LOOP_WORKFLOW_ID)) ? ['blocked', `the \`${DISPATCH_LOOP_WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
27
|
+
|
|
28
|
+
describe('dispatchLoop: core.dispatch consumes OrchestratorDecision', () => {
|
|
24
29
|
it('host correctly processes orchestrator decisions and terminates', async () => {
|
|
30
|
+
if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
|
|
25
31
|
// 1. Create the run
|
|
26
32
|
const create = await driver.post('/v1/runs', { workflowId: DISPATCH_LOOP_WORKFLOW_ID });
|
|
27
33
|
expect(create.status).toBe(201);
|
|
@@ -47,7 +47,7 @@ import { describe, expect, it } from 'vitest';
|
|
|
47
47
|
import { driver } from '../lib/driver.js';
|
|
48
48
|
import { pollUntilTerminal, scaledTimeoutMs } from '../lib/polling.js';
|
|
49
49
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
50
|
-
import { blockedDespiteAssertions } from '../lib/soft-skip.js';
|
|
50
|
+
import { blockedDespiteAssertions, softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
51
51
|
import { queryTestEvents, requireEvents } from '../lib/event-log-query.js';
|
|
52
52
|
import { recordRequirement } from '../lib/requirement-ledger.js';
|
|
53
53
|
import { requirementIdForFile } from '../lib/scenario-disposition.js';
|
|
@@ -88,8 +88,13 @@ if (SKIP_NO_FIXTURE) {
|
|
|
88
88
|
recordRequirement(REQ, 'inapplicable', `fixture ${WORKFLOW_ID} not advertised`);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
92
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
93
|
+
(!isFixtureAdvertised(WORKFLOW_ID)) ? ['inapplicable', `fixture ${WORKFLOW_ID} not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
94
|
+
|
|
95
|
+
describe('RFC 0158 §C.8 — poison work terminates within a bounded number of attempts', () => {
|
|
92
96
|
it('reaches terminal AND stops being retried, asserted on the log rather than on the status alone', async () => {
|
|
97
|
+
if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
|
|
93
98
|
const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
|
|
94
99
|
expect(create.status, req('openwop.it.durability-poison-exhaustion.reaches-terminal-and-stops-being-retried-asserted-on-the-log-rather-than-on-the',
|
|
95
100
|
'rest-endpoints.md',
|
|
@@ -26,6 +26,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
26
26
|
import { driver } from '../lib/driver.js';
|
|
27
27
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
28
28
|
import { req } from '../lib/requirement-ids.js';
|
|
29
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
29
30
|
|
|
30
31
|
const WORKFLOW_ID = 'conformance-idempotent';
|
|
31
32
|
const SKIP_SCALE = process.env.OPENWOP_SKIP_SCALE_PRODUCTION === '1';
|
|
@@ -64,12 +65,18 @@ async function createRun(body: unknown, key?: string): Promise<RunCreateResult>
|
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
describe.
|
|
68
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
69
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
70
|
+
(SKIP_SCALE) ? ['skipped', `the operator opted out of the scale scenario (OPENWOP_SKIP_SCALE_PRODUCTION=1)`] as const :
|
|
71
|
+
(!isFixtureAdvertised(WORKFLOW_ID)) ? ['blocked', `the \`${WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
72
|
+
|
|
73
|
+
describe(
|
|
68
74
|
'high-concurrency: parallel POST /v1/runs per scale-profiles.md §"Conformance scenarios"',
|
|
69
75
|
() => {
|
|
70
76
|
it(
|
|
71
77
|
'10 parallel requests with same key yield ONE runId and 9 replays',
|
|
72
78
|
async () => {
|
|
79
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
73
80
|
const key = freshKey('parallel-same-key');
|
|
74
81
|
const body = { workflowId: WORKFLOW_ID, inputs: { nonce: 'parallel-1' } };
|
|
75
82
|
|
|
@@ -124,6 +131,7 @@ describe.skipIf(SKIP)(
|
|
|
124
131
|
it(
|
|
125
132
|
'10 parallel requests with distinct keys yield 10 distinct runIds',
|
|
126
133
|
async () => {
|
|
134
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
127
135
|
const body = { workflowId: WORKFLOW_ID, inputs: { nonce: 'parallel-2' } };
|
|
128
136
|
|
|
129
137
|
const results = await Promise.all(
|
|
@@ -173,6 +181,7 @@ describe.skipIf(SKIP)(
|
|
|
173
181
|
it(
|
|
174
182
|
'5 sequential retries with same key 100ms apart all succeed (idempotency cache survives retry storm)',
|
|
175
183
|
async () => {
|
|
184
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
176
185
|
const key = freshKey('retry-storm');
|
|
177
186
|
const body = { workflowId: WORKFLOW_ID, inputs: { nonce: 'retry-storm' } };
|
|
178
187
|
|
|
@@ -227,6 +236,7 @@ describe.skipIf(SKIP)(
|
|
|
227
236
|
it(
|
|
228
237
|
'concurrent distinct-key requests respect advertised idempotency cache retention',
|
|
229
238
|
async () => {
|
|
239
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
230
240
|
// This is a structural assertion against /.well-known/openwop, NOT a
|
|
231
241
|
// wall-clock test. We can't realistically wait 24h for cache
|
|
232
242
|
// expiration in a conformance run. Instead we assert the host
|
|
@@ -20,6 +20,7 @@ import { join } from 'node:path';
|
|
|
20
20
|
import { SCENARIOS_DIR } from '../lib/paths.js';
|
|
21
21
|
import { HARNESS_DOUBLE_MODULES } from '../lib/host-callback.js';
|
|
22
22
|
import { req } from '../lib/requirement-ids.js';
|
|
23
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
23
24
|
|
|
24
25
|
const DECLARATION = /export\s+const\s+REQUIRES_HOST_CALLBACK\s*[:=]/;
|
|
25
26
|
/**
|
|
@@ -57,10 +58,15 @@ function scan(dir: string): Scenario[] {
|
|
|
57
58
|
});
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
62
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
63
|
+
(SCENARIOS_DIR === null) ? ['inapplicable', `the published package does not bundle the scenario sources this check reads, so nothing about the host is exercised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
64
|
+
|
|
65
|
+
describe('host-callback declaration (conformance/README §"Where the suite runs")', () => {
|
|
61
66
|
const all = SCENARIOS_DIR === null ? [] : scan(SCENARIOS_DIR);
|
|
62
67
|
|
|
63
68
|
it('the scan reaches the scenario corpus', () => {
|
|
69
|
+
if (SCENARIOS_DIR === null) return softSkip(...GATE_WHY);
|
|
64
70
|
// Guard: an empty scan makes every leg below vacuously true, which is the
|
|
65
71
|
// shape RFC 0148 §C found in the floor verifier. A gate that passes by
|
|
66
72
|
// having looked at nothing is worse than no gate, because it reports clean.
|
|
@@ -74,6 +80,7 @@ describe.skipIf(SCENARIOS_DIR === null)('host-callback declaration (conformance/
|
|
|
74
80
|
});
|
|
75
81
|
|
|
76
82
|
it('every scenario driving a harness double declares the callback', () => {
|
|
83
|
+
if (SCENARIOS_DIR === null) return softSkip(...GATE_WHY);
|
|
77
84
|
const undeclared = all
|
|
78
85
|
.filter((s) => s.doubles.length > 0 && !s.declared && !s.optedOut)
|
|
79
86
|
.map((s) => `${s.file} (imports ${s.doubles.join(', ')})`);
|
|
@@ -91,6 +98,7 @@ describe.skipIf(SCENARIOS_DIR === null)('host-callback declaration (conformance/
|
|
|
91
98
|
});
|
|
92
99
|
|
|
93
100
|
it('the declaration states a reason rather than a bare flag', () => {
|
|
101
|
+
if (SCENARIOS_DIR === null) return softSkip(...GATE_WHY);
|
|
94
102
|
// A boolean records that somebody ticked a box. A sentence records what a
|
|
95
103
|
// consumer must route, and is checkable against the scenario body by anyone
|
|
96
104
|
// reading the diff — the same annotated-vs-bare rule RFC 0149 §D applies to
|
|
@@ -110,6 +118,7 @@ describe.skipIf(SCENARIOS_DIR === null)('host-callback declaration (conformance/
|
|
|
110
118
|
});
|
|
111
119
|
|
|
112
120
|
it('nothing declares a callback it does not make', () => {
|
|
121
|
+
if (SCENARIOS_DIR === null) return softSkip(...GATE_WHY);
|
|
113
122
|
// The reverse direction, and the one that keeps the list honest as the
|
|
114
123
|
// corpus moves. A declaration left behind after the double was removed
|
|
115
124
|
// would tell a consumer to route something nobody needs — a stale claim,
|
|
@@ -14,12 +14,18 @@ import { driver } from '../lib/driver.js';
|
|
|
14
14
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
15
15
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
16
16
|
import { req } from '../lib/requirement-ids.js';
|
|
17
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
17
18
|
|
|
18
19
|
const WORKFLOW_ID = 'conformance-identity';
|
|
19
20
|
const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
23
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
24
|
+
(!isFixtureAdvertised(WORKFLOW_ID)) ? ['blocked', `the \`${WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
25
|
+
|
|
26
|
+
describe('identity: conformance-identity fixture echoes payload input to variables', () => {
|
|
22
27
|
it('arbitrary nested JSON payload round-trips through inputs → variables', async () => {
|
|
28
|
+
if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
|
|
23
29
|
const payload = {
|
|
24
30
|
stringField: 'hello',
|
|
25
31
|
intField: 42,
|