@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
|
@@ -12,6 +12,7 @@ import { driver } from '../lib/driver.js';
|
|
|
12
12
|
import { pollUntilStatus, pollUntilTerminal } from '../lib/polling.js';
|
|
13
13
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
14
14
|
import { req } from '../lib/requirement-ids.js';
|
|
15
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
15
16
|
|
|
16
17
|
const WORKFLOW_ID = 'conformance-approval';
|
|
17
18
|
const NODE_ID = 'gate';
|
|
@@ -23,8 +24,21 @@ const SKIP_NO_REFINE = !isFixtureAdvertised(REFINE_WORKFLOW_ID);
|
|
|
23
24
|
const EDIT_ACCEPT_WORKFLOW_ID = 'conformance-approval-edit-accept';
|
|
24
25
|
const SKIP_NO_EDIT_ACCEPT = !isFixtureAdvertised(EDIT_ACCEPT_WORKFLOW_ID);
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
28
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
29
|
+
(!isFixtureAdvertised(WORKFLOW_ID)) ? ['blocked', `the \`${WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
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_2: readonly [SoftSkipKind, string] =
|
|
33
|
+
(!isFixtureAdvertised(REFINE_WORKFLOW_ID)) ? ['blocked', `the \`${REFINE_WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
34
|
+
|
|
35
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
36
|
+
const GATE_WHY_3: readonly [SoftSkipKind, string] =
|
|
37
|
+
(!isFixtureAdvertised(EDIT_ACCEPT_WORKFLOW_ID)) ? ['blocked', `the \`${EDIT_ACCEPT_WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
38
|
+
|
|
39
|
+
describe('interrupt: approval accept resumes to `completed`', () => {
|
|
27
40
|
it('run suspends at gate, accept resolution drives terminal completed', async () => {
|
|
41
|
+
if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
|
|
28
42
|
const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
|
|
29
43
|
expect(create.status).toBe(201);
|
|
30
44
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -52,8 +66,9 @@ describe.skipIf(SKIP_NO_FIXTURE)('interrupt: approval accept resumes to `complet
|
|
|
52
66
|
});
|
|
53
67
|
});
|
|
54
68
|
|
|
55
|
-
describe
|
|
69
|
+
describe('interrupt: invalid resolve payload rejected per resumeSchema', () => {
|
|
56
70
|
it('400 (or 422) when action is not in {accept, reject}', async () => {
|
|
71
|
+
if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
|
|
57
72
|
const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
|
|
58
73
|
expect(create.status).toBe(201);
|
|
59
74
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -80,8 +95,9 @@ describe.skipIf(SKIP_NO_FIXTURE)('interrupt: invalid resolve payload rejected pe
|
|
|
80
95
|
});
|
|
81
96
|
});
|
|
82
97
|
|
|
83
|
-
describe
|
|
98
|
+
describe('interrupt: resolving an unknown interrupt returns 404', () => {
|
|
84
99
|
it('400/404 when nodeId does not match an active interrupt', async () => {
|
|
100
|
+
if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
|
|
85
101
|
const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
|
|
86
102
|
expect(create.status).toBe(201);
|
|
87
103
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -116,8 +132,9 @@ describe.skipIf(SKIP_NO_FIXTURE)('interrupt: resolving an unknown interrupt retu
|
|
|
116
132
|
* adding `refine` to that fixture's `actions` would change a registered workflow
|
|
117
133
|
* definition every host already serves.
|
|
118
134
|
*/
|
|
119
|
-
describe
|
|
135
|
+
describe('interrupt: refine resolution carries action + refineFeedback (RFC 0183)', () => {
|
|
120
136
|
it('a refine resolve round-trips `action` and the structured feedback it requires', async () => {
|
|
137
|
+
if (SKIP_NO_REFINE) return softSkip(...GATE_WHY_2);
|
|
121
138
|
const create = await driver.post('/v1/runs', { workflowId: REFINE_WORKFLOW_ID });
|
|
122
139
|
expect(create.status).toBe(201);
|
|
123
140
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -168,6 +185,7 @@ describe.skipIf(SKIP_NO_REFINE)('interrupt: refine resolution carries action + r
|
|
|
168
185
|
});
|
|
169
186
|
|
|
170
187
|
it('refuses a refine resolution that supplies no refineFeedback', async () => {
|
|
188
|
+
if (SKIP_NO_REFINE) return softSkip(...GATE_WHY_2);
|
|
171
189
|
const create = await driver.post('/v1/runs', { workflowId: REFINE_WORKFLOW_ID });
|
|
172
190
|
expect(create.status).toBe(201);
|
|
173
191
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -193,8 +211,9 @@ describe.skipIf(SKIP_NO_REFINE)('interrupt: refine resolution carries action + r
|
|
|
193
211
|
* witnesses one of §A.2's two conditional MUSTs; until this block the other was
|
|
194
212
|
* stated in `spec/v2/core/interrupt.md` §Approval and witnessed nowhere.
|
|
195
213
|
*/
|
|
196
|
-
describe
|
|
214
|
+
describe('interrupt: edit-accept resolution carries action + editedArtifactData (RFC 0183)', () => {
|
|
197
215
|
it('an edit-accept resolve round-trips `action` and the edited artifact it requires', async () => {
|
|
216
|
+
if (SKIP_NO_EDIT_ACCEPT) return softSkip(...GATE_WHY_3);
|
|
198
217
|
const create = await driver.post('/v1/runs', { workflowId: EDIT_ACCEPT_WORKFLOW_ID });
|
|
199
218
|
expect(create.status).toBe(201);
|
|
200
219
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -245,6 +264,7 @@ describe.skipIf(SKIP_NO_EDIT_ACCEPT)('interrupt: edit-accept resolution carries
|
|
|
245
264
|
});
|
|
246
265
|
|
|
247
266
|
it('refuses an edit-accept resolution that supplies no editedArtifactData', async () => {
|
|
267
|
+
if (SKIP_NO_EDIT_ACCEPT) return softSkip(...GATE_WHY_3);
|
|
248
268
|
const create = await driver.post('/v1/runs', { workflowId: EDIT_ACCEPT_WORKFLOW_ID });
|
|
249
269
|
expect(create.status).toBe(201);
|
|
250
270
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -24,14 +24,19 @@ import { driver } from '../lib/driver.js';
|
|
|
24
24
|
import { pollUntilStatus, pollUntilTerminal } from '../lib/polling.js';
|
|
25
25
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
26
26
|
import { req } from '../lib/requirement-ids.js';
|
|
27
|
-
import { softSkip } from '../lib/soft-skip.js';
|
|
27
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
28
28
|
|
|
29
29
|
const WORKFLOW_ID = 'conformance-interrupt-auth-required';
|
|
30
30
|
const NODE_ID = 'gate';
|
|
31
31
|
const SKIP = !isFixtureAdvertised(WORKFLOW_ID);
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
34
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
35
|
+
(!isFixtureAdvertised(WORKFLOW_ID)) ? ['blocked', `the \`${WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
36
|
+
|
|
37
|
+
describe('interrupt: auth-required — bearer resume succeeds', () => {
|
|
34
38
|
it('valid bearer with approvals:respond drives terminal completed', async () => {
|
|
39
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
35
40
|
const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
|
|
36
41
|
expect(create.status).toBe(201);
|
|
37
42
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -53,8 +58,9 @@ describe.skipIf(SKIP)('interrupt: auth-required — bearer resume succeeds', ()
|
|
|
53
58
|
});
|
|
54
59
|
});
|
|
55
60
|
|
|
56
|
-
describe
|
|
61
|
+
describe('interrupt: auth-required — insufficient scope returns 403', () => {
|
|
57
62
|
it('bearer without approvals:respond scope is rejected', async () => {
|
|
63
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
58
64
|
// This scenario requires a separate test-only credential that lacks
|
|
59
65
|
// `approvals:respond`. Drivers wire it via the OPENWOP_TEST_LOW_SCOPE_KEY
|
|
60
66
|
// env var; when absent the test skips (rather than passing trivially).
|
|
@@ -23,6 +23,7 @@ import { driver } from '../lib/driver.js';
|
|
|
23
23
|
import { pollUntilStatus, 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-interrupt-external-event';
|
|
28
29
|
const SKIP = !isFixtureAdvertised(WORKFLOW_ID);
|
|
@@ -43,8 +44,13 @@ async function fetchInterruptToken(runId: string): Promise<string | null> {
|
|
|
43
44
|
return null;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
48
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
49
|
+
(!isFixtureAdvertised(WORKFLOW_ID)) ? ['blocked', `the \`${WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
50
|
+
|
|
51
|
+
describe('interrupt: external-event — matching correlation resumes', () => {
|
|
47
52
|
it('signed-token POST with matching correlation drives terminal completed', async () => {
|
|
53
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
48
54
|
const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
|
|
49
55
|
expect(create.status).toBe(201);
|
|
50
56
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -78,8 +84,9 @@ describe.skipIf(SKIP)('interrupt: external-event — matching correlation resume
|
|
|
78
84
|
});
|
|
79
85
|
});
|
|
80
86
|
|
|
81
|
-
describe
|
|
87
|
+
describe('interrupt: external-event — mismatched correlation rejected', () => {
|
|
82
88
|
it('correlation mismatch returns 422 and leaves run suspended', async () => {
|
|
89
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
83
90
|
const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
|
|
84
91
|
expect(create.status).toBe(201);
|
|
85
92
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -22,6 +22,7 @@ import { driver } from '../lib/driver.js';
|
|
|
22
22
|
import { pollUntilStatus, pollUntilTerminal } from '../lib/polling.js';
|
|
23
23
|
import { isFixtureAdvertised } from '../lib/fixtures.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 PARENT_WORKFLOW = 'conformance-interrupt-parent-child-cancel';
|
|
27
28
|
const CHILD_WORKFLOW = 'conformance-interrupt-parent-child-cancel-child';
|
|
@@ -41,8 +42,14 @@ async function findChildRunId(parentRunId: string): Promise<string | null> {
|
|
|
41
42
|
return child?.runId ?? null;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
46
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
47
|
+
(!isFixtureAdvertised(PARENT_WORKFLOW)) ? ['blocked', `the \`${PARENT_WORKFLOW}\` fixture is not advertised`] as const :
|
|
48
|
+
(!isFixtureAdvertised(CHILD_WORKFLOW)) ? ['blocked', `the \`${CHILD_WORKFLOW}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
49
|
+
|
|
50
|
+
describe('interrupt: parent/child — parent cancel cascades to child', () => {
|
|
45
51
|
it('child transitions to cancelled when parent is cancelled mid-suspend', async () => {
|
|
52
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
46
53
|
const create = await driver.post('/v1/runs', { workflowId: PARENT_WORKFLOW });
|
|
47
54
|
expect(create.status).toBe(201);
|
|
48
55
|
const parentRunId = (create.json as { runId: string }).runId;
|
|
@@ -73,8 +80,9 @@ describe.skipIf(SKIP)('interrupt: parent/child — parent cancel cascades to chi
|
|
|
73
80
|
});
|
|
74
81
|
});
|
|
75
82
|
|
|
76
|
-
describe
|
|
83
|
+
describe('interrupt: parent/child — post-cascade resolve of child returns 410/409', () => {
|
|
77
84
|
it('attempting to resolve the cascaded child interrupt is rejected', async () => {
|
|
85
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
78
86
|
const create = await driver.post('/v1/runs', { workflowId: PARENT_WORKFLOW });
|
|
79
87
|
expect(create.status).toBe(201);
|
|
80
88
|
const parentRunId = (create.json as { runId: string }).runId;
|
|
@@ -22,6 +22,7 @@ import { driver } from '../lib/driver.js';
|
|
|
22
22
|
import { pollUntilStatus, pollUntilTerminal } from '../lib/polling.js';
|
|
23
23
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
24
24
|
import { req } from '../lib/requirement-ids.js';
|
|
25
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
25
26
|
|
|
26
27
|
// Profile gating: a host claims `openwop-interrupt-quorum` support by
|
|
27
28
|
// advertising the fixture. Hosts that don't support quorum semantics
|
|
@@ -30,8 +31,13 @@ const WORKFLOW_ID = 'conformance-interrupt-quorum';
|
|
|
30
31
|
const NODE_ID = 'gate';
|
|
31
32
|
const SKIP = !isFixtureAdvertised(WORKFLOW_ID);
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
35
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
36
|
+
(!isFixtureAdvertised(WORKFLOW_ID)) ? ['blocked', `the \`${WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
37
|
+
|
|
38
|
+
describe('interrupt: quorum — three accepts resume to completed', () => {
|
|
34
39
|
it('first two accepts persist without resuming; third accept drives terminal completed', async () => {
|
|
40
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
35
41
|
const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
|
|
36
42
|
expect(create.status).toBe(201);
|
|
37
43
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -72,8 +78,9 @@ describe.skipIf(SKIP)('interrupt: quorum — three accepts resume to completed',
|
|
|
72
78
|
});
|
|
73
79
|
});
|
|
74
80
|
|
|
75
|
-
describe
|
|
81
|
+
describe('interrupt: quorum — majority reject fails the gate', () => {
|
|
76
82
|
it('two rejects out of three votes trigger the majority-reject termination', async () => {
|
|
83
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
77
84
|
const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
|
|
78
85
|
expect(create.status).toBe(201);
|
|
79
86
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -31,7 +31,7 @@ import { driver } from '../lib/driver.js';
|
|
|
31
31
|
import { pollUntilStatus } from '../lib/polling.js';
|
|
32
32
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
33
33
|
import { req } from '../lib/requirement-ids.js';
|
|
34
|
-
import { blockedDespiteAssertions } from '../lib/soft-skip.js';
|
|
34
|
+
import { blockedDespiteAssertions, softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
35
35
|
|
|
36
36
|
const FIXTURE = 'conformance-interrupt-external-event';
|
|
37
37
|
const SKIP = !isFixtureAdvertised(FIXTURE);
|
|
@@ -42,8 +42,13 @@ function randomBytesB64(length: number): string {
|
|
|
42
42
|
).toString('base64url');
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
46
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
47
|
+
(!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
48
|
+
|
|
49
|
+
describe('interrupt-token-matrix: GET /v1/interrupts/{token} negative paths', () => {
|
|
46
50
|
it('malformed token returns 400 or 404 (NEVER 200)', async () => {
|
|
51
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
47
52
|
const malformed = '!!!not-a-valid-token!!!';
|
|
48
53
|
const res = await driver.get(`/v1/interrupts/${encodeURIComponent(malformed)}`);
|
|
49
54
|
expect([400, 404]).toContain(res.status);
|
|
@@ -54,6 +59,7 @@ describe.skipIf(SKIP)('interrupt-token-matrix: GET /v1/interrupts/{token} negati
|
|
|
54
59
|
});
|
|
55
60
|
|
|
56
61
|
it('well-formed but unknown token returns 404', async () => {
|
|
62
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
57
63
|
// Plausibly-shaped opaque token that the host has no record of.
|
|
58
64
|
const unknown = `tok_${randomBytesB64(32)}`;
|
|
59
65
|
const res = await driver.get(`/v1/interrupts/${encodeURIComponent(unknown)}`);
|
|
@@ -64,8 +70,9 @@ describe.skipIf(SKIP)('interrupt-token-matrix: GET /v1/interrupts/{token} negati
|
|
|
64
70
|
});
|
|
65
71
|
});
|
|
66
72
|
|
|
67
|
-
describe
|
|
73
|
+
describe('interrupt-token-matrix: POST /v1/interrupts/{token} negative paths', () => {
|
|
68
74
|
it('replay after successful resolve returns 409 or 404', async () => {
|
|
75
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
69
76
|
// Drive a run to suspension; capture the real token; resolve once;
|
|
70
77
|
// replay the same POST and assert it doesn't succeed twice.
|
|
71
78
|
const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
|
|
@@ -129,6 +136,7 @@ describe.skipIf(SKIP)('interrupt-token-matrix: POST /v1/interrupts/{token} negat
|
|
|
129
136
|
});
|
|
130
137
|
|
|
131
138
|
it('unknown token returns 404 on POST', async () => {
|
|
139
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
132
140
|
const unknown = `tok_${randomBytesB64(32)}`;
|
|
133
141
|
const res = await driver.post(`/v1/interrupts/${encodeURIComponent(unknown)}`, {
|
|
134
142
|
correlation: { orderId: 'noop', status: 'whatever' },
|
|
@@ -24,7 +24,7 @@ import { driver } from '../lib/driver.js';
|
|
|
24
24
|
import { pollUntil } from '../lib/polling.js';
|
|
25
25
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
26
26
|
import { req } from '../lib/requirement-ids.js';
|
|
27
|
-
import { softSkip } from '../lib/soft-skip.js';
|
|
27
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
28
28
|
|
|
29
29
|
const APPROVAL_WORKFLOW_ID = 'conformance-approval';
|
|
30
30
|
const NOOP_WORKFLOW_ID = 'conformance-noop';
|
|
@@ -43,10 +43,19 @@ async function hostClaimsInterrupts(): Promise<boolean> {
|
|
|
43
43
|
return (body.supportedEnvelopes as string[]).includes('clarification.request');
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
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: readonly [SoftSkipKind, string] =
|
|
48
|
+
(!isFixtureAdvertised(APPROVAL_WORKFLOW_ID)) ? ['blocked', `the \`${APPROVAL_WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
49
|
+
|
|
50
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
51
|
+
const GATE_WHY_2: readonly [SoftSkipKind, string] =
|
|
52
|
+
(!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;
|
|
53
|
+
|
|
54
|
+
describe('interrupt-race: concurrent cancel + resolve dispatch deterministically', () => {
|
|
47
55
|
it(
|
|
48
56
|
'concurrent cancel + interrupt-resolve resolves to one of: cancelled or completed',
|
|
49
57
|
async () => {
|
|
58
|
+
if (SKIP_NO_APPROVAL) return softSkip(...GATE_WHY);
|
|
50
59
|
if (!(await hostClaimsInterrupts())) return softSkip('blocked', 'precondition not met — `!(await hostClaimsInterrupts())` returned early (skip-equivalent) (seam, prior step, or fixture unavailable)'); // skip-equivalent
|
|
51
60
|
|
|
52
61
|
// Phase 1: start a workflow that suspends at an approval gate.
|
|
@@ -155,8 +164,9 @@ describe.skipIf(SKIP_NO_APPROVAL)('interrupt-race: concurrent cancel + resolve d
|
|
|
155
164
|
);
|
|
156
165
|
});
|
|
157
166
|
|
|
158
|
-
describe
|
|
167
|
+
describe('interrupt-race: cancel against a non-suspended run is well-formed', () => {
|
|
159
168
|
it('cancel of a completed run returns 200 with the existing terminal status (idempotent)', async () => {
|
|
169
|
+
if (SKIP_NO_NOOP) return softSkip(...GATE_WHY_2);
|
|
160
170
|
// Self-test that doesn't require a race. Runs against any host
|
|
161
171
|
// that supports cancel (every conforming host does).
|
|
162
172
|
const create = await driver.post('/v1/runs', { workflowId: 'conformance-noop' });
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
import { describe, it, expect } from 'vitest';
|
|
50
|
-
import { softSkip } from '../lib/soft-skip.js';
|
|
50
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
51
51
|
import { driver } from '../lib/driver.js';
|
|
52
52
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
53
53
|
import { pollUntil } from '../lib/polling.js';
|
|
@@ -83,8 +83,13 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
87
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
88
|
+
(!process.env.OPENWOP_BASE_URL) ? ['inapplicable', `no target: OPENWOP_BASE_URL is unset`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
89
|
+
|
|
90
|
+
describe('multi-agent-confidence-escalation: capability shape (RFC 0039 §A)', () => {
|
|
87
91
|
it('confidenceEscalationFloor (when advertised) MUST be in [0.5, 1.0]', async () => {
|
|
92
|
+
if (HTTP_SKIP) return softSkip(...GATE_WHY);
|
|
88
93
|
const d = await readDiscovery();
|
|
89
94
|
if (d === null) return softSkip('blocked', 'precondition not met — `d === null` returned early (seam, prior step, or fixture unavailable)');
|
|
90
95
|
const em = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel;
|
|
@@ -101,8 +106,14 @@ describe.skipIf(HTTP_SKIP)('multi-agent-confidence-escalation: capability shape
|
|
|
101
106
|
});
|
|
102
107
|
});
|
|
103
108
|
|
|
104
|
-
|
|
109
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
110
|
+
const GATE_WHY_2: readonly [SoftSkipKind, string] =
|
|
111
|
+
(!process.env.OPENWOP_BASE_URL) ? ['inapplicable', `no target: OPENWOP_BASE_URL is unset`] as const :
|
|
112
|
+
(!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
113
|
+
|
|
114
|
+
describe('multi-agent-confidence-escalation: behavioral (RFC 0039 §A)', () => {
|
|
105
115
|
it('happy-path: low-confidence decision → confidence-escalated event + clarification interrupt + zero dispatch events', async () => {
|
|
116
|
+
if (BEHAVIORAL_SKIP) return softSkip(...GATE_WHY_2);
|
|
106
117
|
const d = await readDiscovery();
|
|
107
118
|
const supported = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.supported === true;
|
|
108
119
|
const versionRaw = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.version;
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
33
|
import { describe, it, expect } from 'vitest';
|
|
34
|
-
import { softSkip } from '../lib/soft-skip.js';
|
|
34
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
35
35
|
import { driver } from '../lib/driver.js';
|
|
36
36
|
import { executionModelVersionMax } from '../lib/execution-model-version.js';
|
|
37
37
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
@@ -62,8 +62,13 @@ async function readDiscovery(): Promise<DiscoveryDoc | null> {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
66
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
67
|
+
(!process.env.OPENWOP_BASE_URL) ? ['inapplicable', `no target: OPENWOP_BASE_URL is unset`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
68
|
+
|
|
69
|
+
describe('multi-agent-handoff-state-machine: advertisement shape (RFC 0037 §C)', () => {
|
|
66
70
|
it('capabilities.multiAgent.executionModel (when present) conforms to RFC 0037 §C', async () => {
|
|
71
|
+
if (HTTP_SKIP) return softSkip(...GATE_WHY);
|
|
67
72
|
const d = await readDiscovery();
|
|
68
73
|
if (d === null) return softSkip('blocked', 'discovery unavailable — skip (d === null)');
|
|
69
74
|
const executionModel = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel;
|
|
@@ -108,8 +113,15 @@ const PARENT_FIXTURE = 'conformance-multi-agent-handoff';
|
|
|
108
113
|
const CHILD_FIXTURE = 'conformance-multi-agent-handoff-child';
|
|
109
114
|
const BEHAVIORAL_SKIP = HTTP_SKIP || !isFixtureAdvertised(PARENT_FIXTURE) || !isFixtureAdvertised(CHILD_FIXTURE);
|
|
110
115
|
|
|
111
|
-
|
|
116
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
117
|
+
const GATE_WHY_2: readonly [SoftSkipKind, string] =
|
|
118
|
+
(!process.env.OPENWOP_BASE_URL) ? ['inapplicable', `no target: OPENWOP_BASE_URL is unset`] as const :
|
|
119
|
+
(!isFixtureAdvertised(PARENT_FIXTURE)) ? ['blocked', `the \`${PARENT_FIXTURE}\` fixture is not advertised`] as const :
|
|
120
|
+
(!isFixtureAdvertised(CHILD_FIXTURE)) ? ['blocked', `the \`${CHILD_FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
121
|
+
|
|
122
|
+
describe('multi-agent-handoff-state-machine: behavioral 4-event causation chain (RFC 0037 §"Handoff state machine")', () => {
|
|
112
123
|
it('happy-path: dispatch.began → dispatch.succeeded → child.completed → output.harvested fire in causation order', async () => {
|
|
124
|
+
if (BEHAVIORAL_SKIP) return softSkip(...GATE_WHY_2);
|
|
113
125
|
const d = await readDiscovery();
|
|
114
126
|
const advertised = capabilityFamily<{ executionModel?: { [k: string]: unknown; crossHostCausation?: Record<string, unknown>; replayDeterminism?: Record<string, unknown> } }>(d, 'multiAgent')?.executionModel?.supported === true;
|
|
115
127
|
if (!advertised) return softSkip('inapplicable', 'soft-skip — host honest about not implementing');
|
|
@@ -15,6 +15,7 @@ import { driver } from '../lib/driver.js';
|
|
|
15
15
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
16
16
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
17
17
|
import { req } from '../lib/requirement-ids.js';
|
|
18
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
18
19
|
|
|
19
20
|
const WORKFLOW_ID = 'conformance-multi-node';
|
|
20
21
|
const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
|
|
@@ -27,8 +28,13 @@ interface RunEvent {
|
|
|
27
28
|
readonly sequence: number;
|
|
28
29
|
}
|
|
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
|
+
(!isFixtureAdvertised(WORKFLOW_ID)) ? ['blocked', `the \`${WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
34
|
+
|
|
35
|
+
describe('multi-node: conformance-multi-node fixture emits node.completed in topological order', () => {
|
|
31
36
|
it('a, b, c node.completed events arrive in DAG order by sequence', async () => {
|
|
37
|
+
if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
|
|
32
38
|
const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
|
|
33
39
|
expect(create.status).toBe(201);
|
|
34
40
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -26,6 +26,7 @@ import { pollUntilTerminal } from '../lib/polling.js';
|
|
|
26
26
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
27
27
|
import { SCHEMAS_DIR } from '../lib/paths.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-failure';
|
|
31
32
|
const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
|
|
@@ -41,8 +42,13 @@ function nodeFailedValidator() {
|
|
|
41
42
|
return ajv.compile(schema);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
46
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
47
|
+
(!isFixtureAdvertised(WORKFLOW_ID)) ? ['blocked', `the \`${WORKFLOW_ID}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
48
|
+
|
|
49
|
+
describe('run-event-payloads.schema.json §nodeFailed — node.failed carries a structured error object', () => {
|
|
45
50
|
it('every node.failed on the conformance-failure run validates against §nodeFailed (error: { code, message } is an object, not a string)', async () => {
|
|
51
|
+
if (SKIP_NO_FIXTURE) return softSkip(...GATE_WHY);
|
|
46
52
|
const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
|
|
47
53
|
expect(create.status, req('openwop.it.node-failed-payload-shape.every-node-failed-on-the-conformance-failure-run-validates-against-nodefailed-er', 'rest-endpoints.md', 'POST /v1/runs MUST return 201 for the failing fixture')).toBe(201);
|
|
48
54
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -23,12 +23,19 @@ import { driver } from '../lib/driver.js';
|
|
|
23
23
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
24
24
|
import { isOrchestratorSupported } 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-orchestrator-low-confidence';
|
|
28
29
|
const SKIP = !isOrchestratorSupported() || !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
|
+
(!isOrchestratorSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isOrchestratorSupported() 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('orchestratorConservativePath: CP-1 low-confidence suspend', () => {
|
|
31
37
|
it('supervisor below threshold suspends with reason=low-confidence; ratified decision follows after resume', async () => {
|
|
38
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
32
39
|
const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
|
|
33
40
|
expect(create.status).toBe(201);
|
|
34
41
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
isDispatchSupported,
|
|
29
29
|
} from '../lib/multi-agent-capabilities.js';
|
|
30
30
|
import { req } from '../lib/requirement-ids.js';
|
|
31
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
31
32
|
|
|
32
33
|
const FIXTURE = 'conformance-orchestrator-dispatch';
|
|
33
34
|
const SKIP =
|
|
@@ -35,8 +36,15 @@ const SKIP =
|
|
|
35
36
|
!isDispatchSupported() ||
|
|
36
37
|
!isFixtureAdvertised(FIXTURE);
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
40
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
41
|
+
(!isOrchestratorSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isOrchestratorSupported() is false)`] as const :
|
|
42
|
+
(!isDispatchSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isDispatchSupported() is false)`] as const :
|
|
43
|
+
(!isFixtureAdvertised(FIXTURE)) ? ['blocked', `the \`${FIXTURE}\` fixture is not advertised`] as const : ['blocked', 'the gate held for no named reason'] as const;
|
|
44
|
+
|
|
45
|
+
describe('orchestratorDispatch: supervisor → dispatch → next-worker', () => {
|
|
39
46
|
it('emits runOrchestrator.decided{next-worker} between supervisor + dispatch', async () => {
|
|
47
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
40
48
|
const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
|
|
41
49
|
expect(create.status).toBe(201);
|
|
42
50
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -21,12 +21,19 @@ import { pollUntilTerminal } from '../lib/polling.js';
|
|
|
21
21
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
22
22
|
import { isOrchestratorSupported } 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-orchestrator-terminate';
|
|
26
27
|
const SKIP = !isOrchestratorSupported() || !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
|
+
(!isOrchestratorSupported()) ? ['inapplicable', `the host does not advertise the capability this scenario covers (isOrchestratorSupported() 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('orchestratorTermination: terminate decision → run.completed (CO-3)', () => {
|
|
29
35
|
it('terminate is the final orchestrator decision; run completes cleanly', async () => {
|
|
36
|
+
if (SKIP) return softSkip(...GATE_WHY);
|
|
30
37
|
const create = await driver.post('/v1/runs', { workflowId: FIXTURE });
|
|
31
38
|
expect(create.status, req('openwop.it.orchestratorTermination.terminate-is-the-final-orchestrator-decision-run-completes-cleanly', 'RFCS/0006-orchestrator.md', 'terminate is the final orchestrator decision; run completes cleanly')).toBe(201);
|
|
32
39
|
const runId = (create.json as { runId: string }).runId;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { describe, it, expect } from 'vitest';
|
|
18
18
|
import { driver } from '../lib/driver.js';
|
|
19
19
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
20
|
-
import { softSkip } from '../lib/soft-skip.js';
|
|
20
|
+
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
21
21
|
import { pollUntil } from '../lib/polling.js';
|
|
22
22
|
import { req } from '../lib/requirement-ids.js';
|
|
23
23
|
|
|
@@ -81,8 +81,13 @@ function actorDepth(s: Subject | undefined): number {
|
|
|
81
81
|
return d;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
/** Why the gate below holds, as RFC 0148 §A names it (openwop#1686: a describe-level skip recorded no disposition). */
|
|
85
|
+
const GATE_WHY: readonly [SoftSkipKind, string] =
|
|
86
|
+
(!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;
|
|
87
|
+
|
|
88
|
+
describe('owner-subject-echo: consistency, echo, fork (RFC 0165 §B — gated on a host that emits owner.subject)', () => {
|
|
85
89
|
it('subject is consistent with the owner triple (§B.2) and the actor chain is bounded (§B.5)', async () => {
|
|
90
|
+
if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
|
|
86
91
|
const runId = await createRun();
|
|
87
92
|
const snap = await snapshot(runId);
|
|
88
93
|
const subject = snap.owner?.subject;
|
|
@@ -105,6 +110,7 @@ describe.skipIf(SKIP_NO_NOOP)('owner-subject-echo: consistency, echo, fork (RFC
|
|
|
105
110
|
});
|
|
106
111
|
|
|
107
112
|
it('run.started echoes the snapshot subject verbatim (§B.2, RFC 0048 §C)', async () => {
|
|
113
|
+
if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
|
|
108
114
|
const runId = await createRun();
|
|
109
115
|
const snap = await snapshot(runId);
|
|
110
116
|
if (snap.owner?.subject === undefined) {
|
|
@@ -122,6 +128,7 @@ describe.skipIf(SKIP_NO_NOOP)('owner-subject-echo: consistency, echo, fork (RFC
|
|
|
122
128
|
});
|
|
123
129
|
|
|
124
130
|
it('a fork copies owner.tenant and owner.subject verbatim onto the child (§B.4)', async () => {
|
|
131
|
+
if (SKIP_NO_NOOP) return softSkip(...GATE_WHY);
|
|
125
132
|
const sourceRunId = await createRun();
|
|
126
133
|
const source = await snapshot(sourceRunId);
|
|
127
134
|
if (source.owner?.subject === undefined) {
|