@openwop/openwop-conformance 2.0.0-rc.48 → 2.0.0-rc.50
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/dist/spec-artifacts.lock.json +2 -2
- package/package.json +2 -2
- package/requirement-aliases.json +3 -1
- package/requirements.json +28 -7
- package/schemas/CORPUS-STAMP.json +10 -10
- package/src/scenarios/v2-run-annotation-not-event.test.ts +1 -1
- package/src/scenarios/v2-run-bulk-cancel.test.ts +14 -6
- package/src/scenarios/v2-run-cancel.test.ts +3 -3
- package/src/scenarios/v2-run-pause-resume.test.ts +6 -2
|
@@ -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.50",
|
|
4
|
+
"stampSha256": "9047f5b272c57e56d3bae6ed8df7c6544b01b67533e069da34a1c5c4a33d3295"
|
|
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.50",
|
|
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.50"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/requirement-aliases.json
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "old id → new id. Add a row when a test title is reworded so bundles citing the old id still resolve.",
|
|
3
|
-
"aliases": {
|
|
3
|
+
"aliases": {
|
|
4
|
+
"openwop.it.v2-run-cancel.cancelling-a-terminal-run-answers-409-run-terminal-or-200-idempotently-the-prose": "openwop.it.v2-run-cancel.cancelling-a-terminal-run-is-refused-409-run-terminal"
|
|
5
|
+
}
|
|
4
6
|
}
|
package/requirements.json
CHANGED
|
@@ -28147,7 +28147,7 @@
|
|
|
28147
28147
|
{
|
|
28148
28148
|
"id": "openwop.it.v2-run-bulk-cancel.results-come-back-in-request-order-each-id-processed-independently-a-foreign-id",
|
|
28149
28149
|
"file": "v2-run-bulk-cancel.test.ts",
|
|
28150
|
-
"line":
|
|
28150
|
+
"line": 63,
|
|
28151
28151
|
"title": "results[] come back in request order, each id processed independently, a foreign id refused in its entry and never as a top-level 403",
|
|
28152
28152
|
"explicitId": null,
|
|
28153
28153
|
"citations": [
|
|
@@ -28172,7 +28172,12 @@
|
|
|
28172
28172
|
"interpolated": true
|
|
28173
28173
|
},
|
|
28174
28174
|
{
|
|
28175
|
-
"section":
|
|
28175
|
+
"section": "api/v2/openapi.yaml bulkCancelRuns results[].error",
|
|
28176
|
+
"requirement": null,
|
|
28177
|
+
"interpolated": true
|
|
28178
|
+
},
|
|
28179
|
+
{
|
|
28180
|
+
"section": "spec/v2/core/identity.md §5",
|
|
28176
28181
|
"requirement": null,
|
|
28177
28182
|
"interpolated": true
|
|
28178
28183
|
},
|
|
@@ -28184,16 +28189,21 @@
|
|
|
28184
28189
|
]
|
|
28185
28190
|
},
|
|
28186
28191
|
{
|
|
28187
|
-
"id": "openwop.it.v2-run-cancel.cancelling-a-terminal-run-
|
|
28192
|
+
"id": "openwop.it.v2-run-cancel.cancelling-a-terminal-run-is-refused-409-run-terminal",
|
|
28188
28193
|
"file": "v2-run-cancel.test.ts",
|
|
28189
28194
|
"line": 65,
|
|
28190
|
-
"title": "cancelling a terminal run
|
|
28195
|
+
"title": "cancelling a terminal run is refused 409 run_terminal",
|
|
28191
28196
|
"explicitId": null,
|
|
28192
28197
|
"citations": [
|
|
28193
28198
|
{
|
|
28194
28199
|
"section": null,
|
|
28195
28200
|
"requirement": null,
|
|
28196
28201
|
"interpolated": true
|
|
28202
|
+
},
|
|
28203
|
+
{
|
|
28204
|
+
"section": null,
|
|
28205
|
+
"requirement": "the refusal MUST be run_terminal",
|
|
28206
|
+
"interpolated": true
|
|
28197
28207
|
}
|
|
28198
28208
|
]
|
|
28199
28209
|
},
|
|
@@ -28387,7 +28397,7 @@
|
|
|
28387
28397
|
"interpolated": true
|
|
28388
28398
|
},
|
|
28389
28399
|
{
|
|
28390
|
-
"section":
|
|
28400
|
+
"section": null,
|
|
28391
28401
|
"requirement": null,
|
|
28392
28402
|
"interpolated": true
|
|
28393
28403
|
},
|
|
@@ -28397,8 +28407,9 @@
|
|
|
28397
28407
|
"interpolated": true
|
|
28398
28408
|
},
|
|
28399
28409
|
{
|
|
28400
|
-
"section":
|
|
28401
|
-
"requirement":
|
|
28410
|
+
"section": null,
|
|
28411
|
+
"requirement": null,
|
|
28412
|
+
"interpolated": true
|
|
28402
28413
|
}
|
|
28403
28414
|
]
|
|
28404
28415
|
},
|
|
@@ -28439,6 +28450,16 @@
|
|
|
28439
28450
|
"requirement": null,
|
|
28440
28451
|
"interpolated": true
|
|
28441
28452
|
},
|
|
28453
|
+
{
|
|
28454
|
+
"section": null,
|
|
28455
|
+
"requirement": null,
|
|
28456
|
+
"interpolated": true
|
|
28457
|
+
},
|
|
28458
|
+
{
|
|
28459
|
+
"section": null,
|
|
28460
|
+
"requirement": null,
|
|
28461
|
+
"interpolated": true
|
|
28462
|
+
},
|
|
28442
28463
|
{
|
|
28443
28464
|
"section": "spec/v2/core/events.md §run.paused",
|
|
28444
28465
|
"requirement": null,
|
|
@@ -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.50",
|
|
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": "cf2510ff1fa3f7f7b37221115261e7db1121cdfaa8cdc8d5a651ba367c9cf263",
|
|
13
|
+
"api/v2/asyncapi.yaml": "9488e843f433ac656d1984819dbdddfcbb95c099a57afbfe1d33ce8b039c3199",
|
|
14
|
+
"api/v2/openapi.yaml": "7b8bdf41af39f3662c90cca68e270ac9bc12318f398af0b2083a0235380ede4b",
|
|
15
15
|
"api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
|
|
16
16
|
"schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
|
|
17
17
|
"schemas/a2a-task-state.schema.json": "c9365918f993f943b4b619d42551eb066a1ed33a08d895d51b395432a5b1f1bc",
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
"schemas/v2/envelopes/schema.request.schema.json": "be2c5f7aa881cdd252ccff47ac02193659e1393633a1541f4297383205d384a2",
|
|
142
142
|
"schemas/v2/envelopes/schema.response.schema.json": "d049a081267c06c28879c331b147f3ed821b14ccfd93005c67aff64db0a8c68e",
|
|
143
143
|
"schemas/v2/envelopes/ui.a2ui-surface.schema.json": "0a06fbbdc8ba797a6818c79336a98e211ac5ba5747945b4e4098072cd3974d79",
|
|
144
|
-
"schemas/v2/error-envelope.schema.json": "
|
|
144
|
+
"schemas/v2/error-envelope.schema.json": "2e09ac2c32a0c0b7098b0ffeaa2822b5dd82b69c23bb3c07d6be2053e2102b19",
|
|
145
145
|
"schemas/v2/eval-summary.schema.json": "bcb165148531d33b7dc01de83da3a049d19ebbef6d12300b17a234c7b03d77d9",
|
|
146
146
|
"schemas/v2/export-bundle.schema.json": "bd28905a7729437ab7f2ad8bb33e13a371b2bf5605a7eb2962a0d246de312d69",
|
|
147
147
|
"schemas/v2/form-content-pack-manifest.schema.json": "307884e57946703b6f86ec283034bf56beb68bea4a000743ebbc9eebeed31b07",
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
"schemas/v2/run-diff-response.schema.json": "6ab6cd265dc7e0a2086c6659c46aa0e7b43e6302949f0adac5d7deb1c7c0ecd3",
|
|
172
172
|
"schemas/v2/run-event-payloads.schema.json": "10f56762616535d01f411afdd98a70ecafc0a961f77aa68cf164b1da458464b4",
|
|
173
173
|
"schemas/v2/run-event.schema.json": "1a9946374af9323b71635d00fed22abe02c300fa2cf3ae3e9c30f3e553e408ec",
|
|
174
|
-
"schemas/v2/run-options.schema.json": "
|
|
174
|
+
"schemas/v2/run-options.schema.json": "ff240805478158cbd2f661979c5eb4f5eda11f9c053b7ba587cb0211a9252d74",
|
|
175
175
|
"schemas/v2/run-orchestrator-decided-event.schema.json": "bb76e15ff72e669f12938684033b55dc567470e1a378e19ffc3fa0e4034ab5a7",
|
|
176
176
|
"schemas/v2/run-snapshot.schema.json": "0ee8a50c6be210db415a7c4345298e70a6ae3dd0dd764ccc514bc1c501437586",
|
|
177
177
|
"schemas/v2/security-advisory.schema.json": "6a382f737730a738d634266315e9d335e5760020a0cdd9f30727c2f987a01770",
|
|
@@ -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": "418d5aea57422fc9b1d66b5373632c2bcbecf8687940cde32692bc5d6541c233",
|
|
203
203
|
"spec/v1/deprecations.json": "1d5acb69a9b8ccb57275a95605f74aef1d920685f8407c9d382a46b59dc803bb",
|
|
204
204
|
"spec/v1/deprecations.schema.json": "18c87e78bedc210431f795ae44c5b5d202f2f3317850d5cf86867d4f1fa1cdfb",
|
|
205
205
|
"spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
|
|
@@ -213,7 +213,7 @@
|
|
|
213
213
|
"spec/v1/spec-gaps.json": "6cc9962c6b969f632e07a78b52a4f61447ff579e2990cbae989866f584a86042",
|
|
214
214
|
"spec/v2/declaration.json": "c15a6a8f08ebd326d1b60398d93d7f7d5abe5c1b8284874fdb13b59f44443210",
|
|
215
215
|
"spec/v2/declaration.schema.json": "b0881a76228dfbd1b85a2ec7b7259a60197f768344535cdb475db6ff719b0e54",
|
|
216
|
-
"spec/v2/errors.json": "
|
|
216
|
+
"spec/v2/errors.json": "63583dec5a4b6de0bacc2894b0752b3da128a0afed80f3dc244e552bc0dd0e55",
|
|
217
217
|
"spec/v2/event-codemap.json": "37869a03cdfd8758b57e6f350641a6ebbd383231cebda51f5473ad85f94ab74c",
|
|
218
218
|
"spec/v2/event-codemap.schema.json": "ce2d0ca085d7668d7ef745d2560b385ec6881d24a6d4b47c49931986b2ba642d",
|
|
219
219
|
"spec/v2/ext/provider-idempotency/registry.json": "5b5fecad604fb4ab38da39f5abe697a4e57b05406c398f8afeb377331b2bd965",
|
|
@@ -230,8 +230,8 @@
|
|
|
230
230
|
"spec/v2/path-manifest.json": "034152e09b1458c66810d4050e20a273b2b9b8fe2d92b66e8b819477de58a1be",
|
|
231
231
|
"spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
|
|
232
232
|
"spec/v2/profiles.json": "5019ac8209540bfbeee9c3232530d4e599be0ab926d817ff48edcec8419eb209",
|
|
233
|
-
"spec/v2/release.json": "
|
|
233
|
+
"spec/v2/release.json": "7b0a86de05e0890ccaaeb4319d2b8201d08c09b5019c1cde8814295ba9967323",
|
|
234
234
|
"spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624"
|
|
235
235
|
},
|
|
236
|
-
"corpusCommit": "
|
|
236
|
+
"corpusCommit": "fa29c2ba4251e780acebbe199555b02d8cfa9c92"
|
|
237
237
|
}
|
|
@@ -51,7 +51,7 @@ describe('v2 run-annotation-not-event (runs.md §Annotations)', () => {
|
|
|
51
51
|
|
|
52
52
|
const ann = await http(() => driver.post(`/runs/${enc(runId)}/annotations`, { signal: { kind: 'label', label: 'conformance' }, note: 'openwop conformance annotation' }));
|
|
53
53
|
if (ann === null) return softSkip('blocked', 'POST /runs/{runId}/annotations unreachable (fetch failed)');
|
|
54
|
-
if (ann.status === 501 || ann.status === 404) return softSkip('
|
|
54
|
+
if (ann.status === 501 || ann.status === 404) return softSkip('blocked', `POST /runs/{runId}/annotations answered ${ann.status} although feedback is advertised — the surface is not mounted (an unadvertised feedback answers 404 not_found; 501 is not a registered shape)`);
|
|
55
55
|
expect(ann.status, req(ID, DOC, `createAnnotation MUST answer 201 — got ${ann.status} ${readErrorCode(ann.json) ?? ''}`.trim())).toBe(201);
|
|
56
56
|
const check = v2Validator('annotation')(ann.json);
|
|
57
57
|
expect(check.ok, req(ID, 'schemas/v2/annotation.schema.json', `the 201 body MUST validate: ${check.errors}`)).toBe(true);
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import { describe, it, expect } from 'vitest';
|
|
25
25
|
import { driver, type OpenWOPResponse } from '../lib/driver.js';
|
|
26
|
-
import { v2Discovery } from '../lib/v2.js';
|
|
26
|
+
import { v2Discovery, v2Validator } from '../lib/v2.js';
|
|
27
27
|
import { readErrorCode } from '../lib/error-envelope.js';
|
|
28
28
|
import { softSkip } from '../lib/soft-skip.js';
|
|
29
29
|
import { req } from '../lib/requirement-ids.js';
|
|
@@ -42,7 +42,7 @@ async function create(): Promise<{ runId: string } | { reason: string }> {
|
|
|
42
42
|
if (res.status !== 201 || typeof runId !== 'string') return { reason: `POST /runs answered ${res.status} ${readErrorCode(res.json) ?? ''} — create refused`.trim() };
|
|
43
43
|
return { runId };
|
|
44
44
|
}
|
|
45
|
-
interface Entry { runId?: unknown; ok?: unknown; status?: unknown; error?:
|
|
45
|
+
interface Entry { runId?: unknown; ok?: unknown; status?: unknown; error?: unknown }
|
|
46
46
|
|
|
47
47
|
describe('v2 run-bulk-cancel (runs.md §Cancel)', () => {
|
|
48
48
|
it('101 ids are refused 400 validation_error with details.maxRunIds', async () => {
|
|
@@ -53,7 +53,10 @@ describe('v2 run-bulk-cancel (runs.md §Cancel)', () => {
|
|
|
53
53
|
if (res.status === 404) return softSkip('blocked', 'POST /runs:bulk-cancel answered 404 — bulkCancelRuns is a core operation (runs.md §Surface) and is not mounted');
|
|
54
54
|
expect(res.status, req(ID, DOC, `over the cap (RECOMMENDED 100) the host MUST answer 400 — got ${res.status}`)).toBe(400);
|
|
55
55
|
expect(readErrorCode(res.json), req(ID, DOC, 'the refusal MUST be validation_error')).toBe('validation_error');
|
|
56
|
-
|
|
56
|
+
// The v2 envelope is { error: <code>, message, details? } — `details` at the ROOT
|
|
57
|
+
// (schemas/v2/error-envelope.schema.json). rc.48/rc.49 read `error.details`
|
|
58
|
+
// and reported a host defect that was this reader's; retracted in rc.50.
|
|
59
|
+
const max = (res.json as { details?: { maxRunIds?: unknown } } | null)?.details?.maxRunIds;
|
|
57
60
|
expect(typeof max === 'number' && max >= 1 && max <= 100, req(ID, DOC, `details.maxRunIds MUST state the cap (got ${String(max)})`)).toBe(true);
|
|
58
61
|
});
|
|
59
62
|
|
|
@@ -72,11 +75,16 @@ describe('v2 run-bulk-cancel (runs.md §Cancel)', () => {
|
|
|
72
75
|
expect(entries.map((e) => e.runId), req(ID, DOC, 'results[] MUST be in request order')).toEqual(runIds);
|
|
73
76
|
const foreign = entries[1]!;
|
|
74
77
|
expect(foreign.ok, req(ID, DOC, 'the foreign-tenant entry MUST be ok: false')).toBe(false);
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
// An entry's `error` IS the error envelope (api/v2/openapi.yaml: `$ref error-envelope`):
|
|
79
|
+
// { error: <code>, message, details? } nested under the entry — so the code is
|
|
80
|
+
// `entry.error.error`, read the same way as a top-level envelope.
|
|
81
|
+
const env = v2Validator('error-envelope')(foreign.error);
|
|
82
|
+
expect(env.ok, req(ID, 'api/v2/openapi.yaml bulkCancelRuns results[].error', `an ok: false entry's error MUST be the error envelope { error: <code>, message, details? } (schemas/v2/error-envelope.schema.json; the OpenAPI $refs it for results[].error) — got ${JSON.stringify(foreign.error)}: ${env.errors}`)).toBe(true);
|
|
83
|
+
const fcode = String(readErrorCode(foreign.error));
|
|
84
|
+
expect(['id_tenant_mismatch', 'not_found'].includes(fcode), req(ID, 'spec/v2/core/identity.md §5', `an id whose tenant segment is not the caller's MUST be refused inside the entry with id_tenant_mismatch (or not_found where existence is not leaked) — identity.md §5 applies inside a bulk entry exactly as on a path; run_forbidden is for a same-tenant run the caller may not cancel — got ${fcode}`)).toBe(true);
|
|
77
85
|
for (const own of [entries[0]!, entries[2]!]) {
|
|
78
86
|
const okShape = own.ok === true && ['cancelling', 'cancelled'].includes(String(own.status));
|
|
79
|
-
const terminalShape = own.ok === false &&
|
|
87
|
+
const terminalShape = own.ok === false && readErrorCode(own.error) === 'run_terminal';
|
|
80
88
|
expect(okShape || terminalShape, req(ID, DOC, `an own entry MUST be ok: true with cancelling|cancelled, or ok: false run_terminal when the noop already completed — got ${JSON.stringify(own)}`)).toBe(true);
|
|
81
89
|
}
|
|
82
90
|
});
|
|
@@ -62,7 +62,7 @@ async function eventTypes(runId: string): Promise<string[] | null> {
|
|
|
62
62
|
const TERMINAL = new Set(['completed', 'failed', 'cancelled']);
|
|
63
63
|
|
|
64
64
|
describe('v2 run-cancel (runs.md §Cancel)', () => {
|
|
65
|
-
it('cancelling a terminal run
|
|
65
|
+
it('cancelling a terminal run is refused 409 run_terminal', async () => {
|
|
66
66
|
if (!(await discovery())) return softSkip('blocked', 'v2 discovery unreachable');
|
|
67
67
|
const c = await create({ workflowId: NOOP });
|
|
68
68
|
if ('reason' in c) return softSkip('blocked', c.reason);
|
|
@@ -72,8 +72,8 @@ describe('v2 run-cancel (runs.md §Cancel)', () => {
|
|
|
72
72
|
if (res === null) return softSkip('blocked', 'POST /runs/{runId}/cancel unreachable (fetch failed)');
|
|
73
73
|
const code = readErrorCode(res.json);
|
|
74
74
|
const status = (res.json as { status?: unknown } | null)?.status;
|
|
75
|
-
|
|
76
|
-
expect(
|
|
75
|
+
expect(res.status, req(ID, DOC, `a cancel on a run that is already terminal (${s}) MUST be refused 409 run_terminal — got ${res.status} ${code ?? String(status)}; a 200 echoing the terminal state is outside §Cancel's 200 grammar { runId, status: cancelling | cancelled }`)).toBe(409);
|
|
76
|
+
expect(code, req(ID, DOC, 'the refusal MUST be run_terminal')).toBe('run_terminal');
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
it('cancelling a running run answers 200 with cancelling|cancelled, emits run.cancelled within 5 s, and the snapshot reads cancelled', async () => {
|
|
@@ -66,11 +66,11 @@ describe('v2 run-pause-resume (runs.md §Pause and resume)', () => {
|
|
|
66
66
|
if (pause === null) return softSkip('blocked', 'POST /runs/{runId}:pause unreachable (fetch failed)');
|
|
67
67
|
if (pause.status === 404) return softSkip('blocked', 'POST /runs/{runId}:pause answered 404 — pauseRun is a core operation (runs.md §Surface) and is not mounted');
|
|
68
68
|
expect(pause.status, req(ID, DOC, `pause on a terminal (${s}) run MUST answer 409 — got ${pause.status} ${readErrorCode(pause.json) ?? ''}`.trim())).toBe(409);
|
|
69
|
-
expect(
|
|
69
|
+
expect(readErrorCode(pause.json), req(ID, DOC, `a pause refused because the run is terminal MUST carry run_terminal (got ${String(readErrorCode(pause.json))})`)).toBe('run_terminal');
|
|
70
70
|
const resume = await http(() => driver.post(`/runs/${enc(c.runId)}:resume`, {}));
|
|
71
71
|
if (resume === null) return softSkip('blocked', 'POST /runs/{runId}:resume unreachable (fetch failed)');
|
|
72
72
|
expect(resume.status, req(ID, DOC, `resume on a run that is not paused MUST answer 409 — got ${resume.status} ${readErrorCode(resume.json) ?? ''}`.trim())).toBe(409);
|
|
73
|
-
expect(
|
|
73
|
+
expect(readErrorCode(resume.json), req(ID, DOC, `a resume refused because the run is terminal MUST carry run_terminal (got ${String(readErrorCode(resume.json))})`)).toBe('run_terminal');
|
|
74
74
|
if (!isFixtureAdvertised(DELAY)) return softSkip('inapplicable', `${DELAY} fixture not advertised — the positive pause/resume leg cannot run, so the two 409s above are witnessed without their control (a host answering 409 to everything would pass them)`);
|
|
75
75
|
});
|
|
76
76
|
|
|
@@ -91,6 +91,10 @@ describe('v2 run-pause-resume (runs.md §Pause and resume)', () => {
|
|
|
91
91
|
expect(pb?.status, req(ID, DOC, `the 202 MUST carry status paused (got ${String(pb?.status)})`)).toBe('paused');
|
|
92
92
|
const again = await http(() => driver.post(`/runs/${enc(c.runId)}:pause`, {}));
|
|
93
93
|
expect(again?.status ?? null, req(ID, DOC, `a second pause on a paused run MUST answer 409 — got ${again?.status ?? 'no response'}`)).toBe(409);
|
|
94
|
+
expect(readErrorCode(again?.json), req(ID, DOC, `a pause refused on a non-terminal run MUST carry run_state_conflict (got ${String(readErrorCode(again?.json))})`)).toBe('run_state_conflict');
|
|
95
|
+
// `details` sits at the envelope ROOT (schemas/v2/error-envelope.schema.json), not under `error`.
|
|
96
|
+
const runStatus = (again?.json as { details?: { runStatus?: unknown } } | null)?.details?.runStatus;
|
|
97
|
+
expect(runStatus, req(ID, DOC, `run_state_conflict MUST carry details.runStatus naming the status that refused it (got ${String(runStatus)})`)).toBe('paused');
|
|
94
98
|
const resume = await http(() => driver.post(`/runs/${enc(c.runId)}:resume`, { reason: 'conformance' }));
|
|
95
99
|
expect(resume?.status ?? null, req(ID, DOC, `resumeRun on a paused run MUST answer 202 — got ${resume?.status ?? 'no response'} ${readErrorCode(resume?.json) ?? ''}`.trim())).toBe(202);
|
|
96
100
|
const rb = resume?.json as { runId?: unknown; status?: unknown } | null;
|