@openwop/openwop-conformance 2.44.8 → 2.44.9

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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # `@openwop/openwop-conformance` Changelog
2
2
 
3
+ ## [2.44.9] — 2026-09-29 — a floor gated on a withheld fixture records `blocked`, not nothing
4
+
5
+ - **Nine v1 floor files no longer skip at describe level** (#1686). Each of the following gated every test with `describe.skipIf(!isFixtureAdvertised(…))`: `eventOrdering`, `failure-path`, `idempotency`, `interrupt-clarification`, `replay-fork`, `runs-lifecycle`, `stream-modes`, `stream-modes-buffer` and `stream-modes-mixed`. When every test in a file is describe-skipped, vitest runs none of `setup.ts`'s hooks for it, so the file's floor requirement got no ledger row. `--certify` then counted the floor unclassified and rejected the whole certification, not just the profile. `byok-roundtrip` hit this on openwop-app's 2026-09-28 major-1 cut (`certified: none`) and was fixed in #1708. Each test in the nine files now opens with `if (SKIP) return softSkip('blocked', NO_FIXTURE_REASON)`, so a withheld fixture records `blocked` with the fixture named. That denies only the affected profile. It is `blocked`, not `inapplicable`: the host advertises the capability the floor witnesses, so the requirement applies to the captured discovery, and a withheld fixture is exactly RFC 0148 §A's `blocked` ("a required seam, fixture … was unavailable"). `byok-roundtrip`'s `blocked` (#1708) stands for the same reason. Measured against a stub host that advertises no fixtures, with the ledger on: before, `runs-lifecycle` wrote no ledger row; after, `openwop.floor.runs-lifecycle` is `blocked` with the reason. A test that passed before still passes: the gates are unchanged, only where they sit.
6
+ - **New self-test, `describe-level-skip.test.ts`** (server-free, `test:self`). It fails when a v1 floor file (`PROFILE_FLOOR_SCENARIOS`, `required` plus every `conditional` branch) can skip every test at describe level. A gate on having no target (`!process.env.OPENWOP_BASE_URL`) is exempt, since with no host there is no bundle. A ratchet also holds the 56 non-floor files with the same shape at a ceiling of 56. Those rows resolve to a report-derived `blocked` that names no reason; that is the right disposition, and they certify nothing either way. Sabotage: reverting `runs-lifecycle` to its describe-level gate fails the floor rule, and adding a new all-gated file fails the ratchet.
7
+
3
8
  ## [2.44.8] — 2026-09-29 — every subscribed event of a run is delivered
4
9
 
5
10
  - **`0173.webhook-delivery-complete`: every matching event reaches a healthy receiver at least once** (#1781). A new leg in `v2-webhook-durable-delivery` (target major 2, gated on `webhooks` and `conformance-noop`) reads `webhooks.md` §Durability, "deliver each matching event at least once", across a whole run; no row did before. It registers one subscription to `run.started`, `node.started`, `node.completed` and `run.completed` with a receiver that answers `204` to everything, and runs the noop fixture. The expected set is every logged event of a subscribed type (`GET /runs/{runId}/events/poll`), keyed by `sequence`. Each must arrive at least once, deduplicated on `(webhookId, runId, sequence)`; duplicates are counted, never held against the host. The wait is `retryWaitFor`: the advertised `maxElapsedMs` plus the grace (RFC 0225), else the cap or the floor. The row convicts when some of the run's subscribed events arrived and others did not, or when nothing arrived and nothing reached the listener; nothing of this run arriving while other traffic did is `blocked`. A pass carries an `observed:` detail (counts, duplicates, slowest first arrival, wait path). The leg unregisters its subscription. Measured on the v2 reference host (openwop-examples `d0255b4b`): `executed-pass` in 255 ms; with its `run.started` fan-out removed, `executed-fail` naming `run.started (sequence 0)` after the 90 s window, and the file's other five legs stay green, which is the gap: myndhyve#560's lost `run.started` fan-out was convicted only incidentally, under the wrong MUST (#1780). Surveyed against the three hosts' code: each delivers every subscribed type and passes on its certified deployment (MyndHyve's window is its advertised 600000 ms plus the grace).
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "package": "@openwop/spec-artifacts",
3
- "version": "2.44.8",
4
- "stampSha256": "1f667cb424fa4cd6a005d9b6766bb5ec941de708197976cdc8a638677579e0a4"
3
+ "version": "2.44.9",
4
+ "stampSha256": "7803a5db18abdf8649235a2665ae9bb801aea6ee7584cdd13ada047fd25a8993"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "2.44.8",
3
+ "version": "2.44.9",
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.44.8"
59
+ "@openwop/spec-artifacts": "2.44.9"
60
60
  }
61
61
  }
package/requirements.json CHANGED
@@ -12791,7 +12791,7 @@
12791
12791
  {
12792
12792
  "id": "openwop.it.eventOrdering.events-from-a-single-poll-have-non-decreasing-sequence-numbers",
12793
12793
  "file": "eventOrdering.test.ts",
12794
- "line": 53,
12794
+ "line": 55,
12795
12795
  "title": "events from a single poll have non-decreasing sequence numbers",
12796
12796
  "explicitId": "openwop.it.eventOrdering.events-from-a-single-poll-have-non-decreasing-sequence-numbers",
12797
12797
  "citations": [
@@ -12805,7 +12805,7 @@
12805
12805
  {
12806
12806
  "id": "openwop.it.eventOrdering.repeated-polls-of-a-terminal-run-yield-identical-event-sequences",
12807
12807
  "file": "eventOrdering.test.ts",
12808
- "line": 78,
12808
+ "line": 81,
12809
12809
  "title": "repeated polls of a terminal run yield identical event sequences",
12810
12810
  "explicitId": "openwop.it.eventOrdering.repeated-polls-of-a-terminal-run-yield-identical-event-sequences",
12811
12811
  "citations": [
@@ -12823,7 +12823,7 @@
12823
12823
  {
12824
12824
  "id": "openwop.it.eventOrdering.event-stream-contains-exactly-one-of-run-completed-run-failed-run-cancelled",
12825
12825
  "file": "eventOrdering.test.ts",
12826
- "line": 110,
12826
+ "line": 114,
12827
12827
  "title": "event stream contains exactly one of run.completed / run.failed / run.cancelled",
12828
12828
  "explicitId": "openwop.it.eventOrdering.event-stream-contains-exactly-one-of-run-completed-run-failed-run-cancelled",
12829
12829
  "citations": [
@@ -12836,7 +12836,7 @@
12836
12836
  {
12837
12837
  "id": "openwop.it.eventOrdering.the-terminal-event-is-the-last-event-in-the-stream",
12838
12838
  "file": "eventOrdering.test.ts",
12839
- "line": 131,
12839
+ "line": 136,
12840
12840
  "title": "the terminal event is the LAST event in the stream",
12841
12841
  "explicitId": "openwop.it.eventOrdering.the-terminal-event-is-the-last-event-in-the-stream",
12842
12842
  "citations": [
@@ -13103,7 +13103,7 @@
13103
13103
  {
13104
13104
  "id": "openwop.it.failure-path.post-v1-runs-accepts-the-run-and-run-terminates-as-failed-with-structured-error",
13105
13105
  "file": "failure-path.test.ts",
13106
- "line": 17,
13106
+ "line": 19,
13107
13107
  "title": "POST /v1/runs accepts the run and run terminates as failed with structured error",
13108
13108
  "explicitId": "openwop.it.failure-path.post-v1-runs-accepts-the-run-and-run-terminates-as-failed-with-structured-error",
13109
13109
  "citations": [
@@ -14869,7 +14869,7 @@
14869
14869
  {
14870
14870
  "id": "openwop.it.idempotency.returns-same-runid-twice-and-sets-openwop-idempotent-replay-on-the-replay",
14871
14871
  "file": "idempotency.test.ts",
14872
- "line": 31,
14872
+ "line": 33,
14873
14873
  "title": "returns same runId twice and sets openwop-Idempotent-Replay on the replay",
14874
14874
  "explicitId": "openwop.it.idempotency.returns-same-runid-twice-and-sets-openwop-idempotent-replay-on-the-replay",
14875
14875
  "citations": [
@@ -14894,7 +14894,7 @@
14894
14894
  {
14895
14895
  "id": "openwop.it.idempotency.returns-409-when-the-body-changes-under-the-same-key",
14896
14896
  "file": "idempotency.test.ts",
14897
- "line": 70,
14897
+ "line": 73,
14898
14898
  "title": "returns 409 when the body changes under the same key",
14899
14899
  "explicitId": "openwop.it.idempotency.returns-409-when-the-body-changes-under-the-same-key",
14900
14900
  "citations": [
@@ -15354,7 +15354,7 @@
15354
15354
  {
15355
15355
  "id": "openwop.it.interrupt-clarification.run-suspends-at-ask-answers-payload-drives-terminal-completed",
15356
15356
  "file": "interrupt-clarification.test.ts",
15357
- "line": 20,
15357
+ "line": 22,
15358
15358
  "title": "run suspends at ask, answers payload drives terminal completed",
15359
15359
  "explicitId": "openwop.it.interrupt-clarification.run-suspends-at-ask-answers-payload-drives-terminal-completed",
15360
15360
  "citations": [
@@ -22575,7 +22575,7 @@
22575
22575
  {
22576
22576
  "id": "openwop.it.replay-fork.produces-a-new-run-that-reaches-terminal-completed",
22577
22577
  "file": "replay-fork.test.ts",
22578
- "line": 60,
22578
+ "line": 61,
22579
22579
  "title": "produces a new run that reaches terminal `completed`",
22580
22580
  "explicitId": "openwop.it.replay-fork.produces-a-new-run-that-reaches-terminal-completed",
22581
22581
  "citations": [
@@ -22608,7 +22608,7 @@
22608
22608
  {
22609
22609
  "id": "openwop.it.replay-fork.produces-a-new-run-that-reaches-terminal-completed~2",
22610
22610
  "file": "replay-fork.test.ts",
22611
- "line": 105,
22611
+ "line": 107,
22612
22612
  "title": "produces a new run that reaches terminal `completed`",
22613
22613
  "explicitId": "openwop.it.replay-fork.produces-a-new-run-that-reaches-terminal-completed~2",
22614
22614
  "citations": [
@@ -22621,7 +22621,7 @@
22621
22621
  {
22622
22622
  "id": "openwop.it.replay-fork.rejects-negative-fromseq-with-400",
22623
22623
  "file": "replay-fork.test.ts",
22624
- "line": 142,
22624
+ "line": 145,
22625
22625
  "title": "rejects negative fromSeq with 400",
22626
22626
  "explicitId": "openwop.it.replay-fork.rejects-negative-fromseq-with-400",
22627
22627
  "citations": [
@@ -22634,7 +22634,7 @@
22634
22634
  {
22635
22635
  "id": "openwop.it.replay-fork.rejects-fromseq-beyond-source-event-log-length-with-422",
22636
22636
  "file": "replay-fork.test.ts",
22637
- "line": 161,
22637
+ "line": 165,
22638
22638
  "title": "rejects fromSeq beyond source event log length with 422",
22639
22639
  "explicitId": "openwop.it.replay-fork.rejects-fromseq-beyond-source-event-log-length-with-422",
22640
22640
  "citations": [
@@ -22647,7 +22647,7 @@
22647
22647
  {
22648
22648
  "id": "openwop.it.replay-fork.rejects-replay-mode-with-non-empty-runoptionsoverlay-overlay-is-branch-only",
22649
22649
  "file": "replay-fork.test.ts",
22650
- "line": 182,
22650
+ "line": 187,
22651
22651
  "title": "rejects replay mode with non-empty runOptionsOverlay (overlay is branch-only)",
22652
22652
  "explicitId": "openwop.it.replay-fork.rejects-replay-mode-with-non-empty-runoptionsoverlay-overlay-is-branch-only",
22653
22653
  "citations": [
@@ -22660,7 +22660,7 @@
22660
22660
  {
22661
22661
  "id": "openwop.it.replay-fork.rejects-fork-on-a-non-existent-run-with-404",
22662
22662
  "file": "replay-fork.test.ts",
22663
- "line": 208,
22663
+ "line": 214,
22664
22664
  "title": "rejects fork on a non-existent run with 404",
22665
22665
  "explicitId": "openwop.it.replay-fork.rejects-fork-on-a-non-existent-run-with-404",
22666
22666
  "citations": [
@@ -23735,7 +23735,7 @@
23735
23735
  {
23736
23736
  "id": "openwop.it.runs-lifecycle.post-v1-runs-returns-201-with-runid-per-rest-endpoints-md",
23737
23737
  "file": "runs-lifecycle.test.ts",
23738
- "line": 22,
23738
+ "line": 24,
23739
23739
  "title": "POST /v1/runs returns 201 with runId per rest-endpoints.md",
23740
23740
  "explicitId": "openwop.it.runs-lifecycle.post-v1-runs-returns-201-with-runid-per-rest-endpoints-md",
23741
23741
  "citations": [
@@ -23756,7 +23756,7 @@
23756
23756
  {
23757
23757
  "id": "openwop.it.runs-lifecycle.reaches-terminal-completed-within-bounded-time-per-fixtures-md-noop-spec",
23758
23758
  "file": "runs-lifecycle.test.ts",
23759
- "line": 41,
23759
+ "line": 44,
23760
23760
  "title": "reaches terminal `completed` within bounded time per fixtures.md noop spec",
23761
23761
  "explicitId": "openwop.it.runs-lifecycle.reaches-terminal-completed-within-bounded-time-per-fixtures-md-noop-spec",
23762
23762
  "citations": [
@@ -23773,7 +23773,7 @@
23773
23773
  {
23774
23774
  "id": "openwop.it.runs-lifecycle.get-v1-runs-nonexistentid-returns-404-or-403-per-rest-endpoints-md",
23775
23775
  "file": "runs-lifecycle.test.ts",
23776
- "line": 59,
23776
+ "line": 63,
23777
23777
  "title": "GET /v1/runs/{nonexistentId} returns 404 (or 403) per rest-endpoints.md",
23778
23778
  "explicitId": "openwop.it.runs-lifecycle.get-v1-runs-nonexistentid-returns-404-or-403-per-rest-endpoints-md",
23779
23779
  "citations": [
@@ -25058,7 +25058,7 @@
25058
25058
  {
25059
25059
  "id": "openwop.it.stream-modes-buffer.accepts-bufferms-in-range-and-emits-at-least-one-event-batch-frame",
25060
25060
  "file": "stream-modes-buffer.test.ts",
25061
- "line": 36,
25061
+ "line": 38,
25062
25062
  "title": "accepts bufferMs in range and emits at least one event: batch frame",
25063
25063
  "explicitId": "openwop.it.stream-modes-buffer.accepts-bufferms-in-range-and-emits-at-least-one-event-batch-frame",
25064
25064
  "citations": [
@@ -25079,7 +25079,7 @@
25079
25079
  {
25080
25080
  "id": "openwop.it.stream-modes-buffer.rejects-out-of-range-bufferms-with-400-validation-error",
25081
25081
  "file": "stream-modes-buffer.test.ts",
25082
- "line": 72,
25082
+ "line": 75,
25083
25083
  "title": "rejects out-of-range bufferMs with 400 validation_error",
25084
25084
  "explicitId": "openwop.it.stream-modes-buffer.rejects-out-of-range-bufferms-with-400-validation-error",
25085
25085
  "citations": [
@@ -25092,7 +25092,7 @@
25092
25092
  {
25093
25093
  "id": "openwop.it.stream-modes-buffer.forces-flush-on-terminal-run-completed-arrives-bundled-in-a-batch-before-the-tim",
25094
25094
  "file": "stream-modes-buffer.test.ts",
25095
- "line": 90,
25095
+ "line": 94,
25096
25096
  "title": "forces flush on terminal — run.completed arrives bundled in a batch BEFORE the timer fires",
25097
25097
  "explicitId": "openwop.it.stream-modes-buffer.forces-flush-on-terminal-run-completed-arrives-bundled-in-a-batch-before-the-tim",
25098
25098
  "citations": [
@@ -25110,7 +25110,7 @@
25110
25110
  {
25111
25111
  "id": "openwop.it.stream-modes-buffer.bufferms-0-behaves-identically-to-omitting-per-event-mode",
25112
25112
  "file": "stream-modes-buffer.test.ts",
25113
- "line": 134,
25113
+ "line": 139,
25114
25114
  "title": "bufferMs=0 behaves identically to omitting (per-event mode)",
25115
25115
  "explicitId": "openwop.it.stream-modes-buffer.bufferms-0-behaves-identically-to-omitting-per-event-mode",
25116
25116
  "citations": [
@@ -25123,7 +25123,7 @@
25123
25123
  {
25124
25124
  "id": "openwop.it.stream-modes-mixed.accepts-streammode-updates-messages-and-emits-a-server-closed-stream",
25125
25125
  "file": "stream-modes-mixed.test.ts",
25126
- "line": 34,
25126
+ "line": 36,
25127
25127
  "title": "accepts streamMode=updates,messages and emits a server-closed stream",
25128
25128
  "explicitId": "openwop.it.stream-modes-mixed.accepts-streammode-updates-messages-and-emits-a-server-closed-stream",
25129
25129
  "citations": [
@@ -25144,7 +25144,7 @@
25144
25144
  {
25145
25145
  "id": "openwop.it.stream-modes-mixed.rejects-streammode-values-updates-with-400-unsupported-stream-mode",
25146
25146
  "file": "stream-modes-mixed.test.ts",
25147
- "line": 64,
25147
+ "line": 67,
25148
25148
  "title": "rejects streamMode=values,updates with 400 + unsupported_stream_mode",
25149
25149
  "explicitId": "openwop.it.stream-modes-mixed.rejects-streammode-values-updates-with-400-unsupported-stream-mode",
25150
25150
  "citations": [
@@ -25169,7 +25169,7 @@
25169
25169
  {
25170
25170
  "id": "openwop.it.stream-modes-mixed.rejects-streammode-updates-bogus-one-bad-mode-fails-the-whole-list",
25171
25171
  "file": "stream-modes-mixed.test.ts",
25172
- "line": 98,
25172
+ "line": 102,
25173
25173
  "title": "rejects streamMode=updates,bogus (one bad mode fails the whole list)",
25174
25174
  "explicitId": "openwop.it.stream-modes-mixed.rejects-streammode-updates-bogus-one-bad-mode-fails-the-whole-list",
25175
25175
  "citations": [
@@ -25182,7 +25182,7 @@
25182
25182
  {
25183
25183
  "id": "openwop.it.stream-modes-mixed.mixed-mode-union-updates-debug-sees-every-event-updates-sees",
25184
25184
  "file": "stream-modes-mixed.test.ts",
25185
- "line": 116,
25185
+ "line": 121,
25186
25186
  "title": "mixed mode union: updates,debug sees every event updates sees",
25187
25187
  "explicitId": "openwop.it.stream-modes-mixed.mixed-mode-union-updates-debug-sees-every-event-updates-sees",
25188
25188
  "citations": [
@@ -25196,7 +25196,7 @@
25196
25196
  {
25197
25197
  "id": "openwop.it.stream-modes.emits-at-least-run-started-run-completed-and-server-closes-the-stream",
25198
25198
  "file": "stream-modes.test.ts",
25199
- "line": 35,
25199
+ "line": 37,
25200
25200
  "title": "emits at least run.started + run.completed and server closes the stream",
25201
25201
  "explicitId": "openwop.it.stream-modes.emits-at-least-run-started-run-completed-and-server-closes-the-stream",
25202
25202
  "citations": [
@@ -25217,7 +25217,7 @@
25217
25217
  {
25218
25218
  "id": "openwop.it.stream-modes.returns-400-and-a-structured-error-body",
25219
25219
  "file": "stream-modes.test.ts",
25220
- "line": 60,
25220
+ "line": 63,
25221
25221
  "title": "returns 400 and a structured error body",
25222
25222
  "explicitId": "openwop.it.stream-modes.returns-400-and-a-structured-error-body",
25223
25223
  "citations": [
@@ -25242,7 +25242,7 @@
25242
25242
  {
25243
25243
  "id": "openwop.it.stream-modes.returns-200-emits-at-least-one-event-server-closes-per-stream-modes-md-values",
25244
25244
  "file": "stream-modes.test.ts",
25245
- "line": 90,
25245
+ "line": 94,
25246
25246
  "title": "returns 200 + emits at least one event + server-closes per stream-modes.md §values",
25247
25247
  "explicitId": "openwop.it.stream-modes.returns-200-emits-at-least-one-event-server-closes-per-stream-modes-md-values",
25248
25248
  "citations": [
@@ -25259,7 +25259,7 @@
25259
25259
  {
25260
25260
  "id": "openwop.it.stream-modes.debug-stream-is-a-superset-of-updates-per-stream-modes-md-mode-mapping",
25261
25261
  "file": "stream-modes.test.ts",
25262
- "line": 114,
25262
+ "line": 119,
25263
25263
  "title": "debug stream is a superset of updates per stream-modes.md mode-mapping",
25264
25264
  "explicitId": "openwop.it.stream-modes.debug-stream-is-a-superset-of-updates-per-stream-modes-md-mode-mapping",
25265
25265
  "citations": [
@@ -1,17 +1,17 @@
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.44.8",
5
- "corpusTag": "v2.44.8",
4
+ "version": "2.44.9",
5
+ "corpusTag": "v2.44.9",
6
6
  "files": {
7
7
  "api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
8
8
  "api/asyncapi.yaml": "d5ecb9ee6114582be3b1f662c84bfac9ae96dae7bacb853e461168f70a8e1c7d",
9
9
  "api/grpc/openwop.proto": "c3e72bb17cba514ee98feb6434e6c9b6ea6795bfd086489ec69fd882dd1ad977",
10
10
  "api/openapi.yaml": "69464bc0e1e71ef3b543a73d4fa4f67202343d2603c80af8033f6c3619ef1b30",
11
11
  "api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
12
- "api/seams-v2.yaml": "e0a08989687d29d95bc10702aca5a31fcc846cf8f55ae0dd362fb4666fbdf8f3",
13
- "api/v2/asyncapi.yaml": "ab568b0539dcb71c77a3a3218a433cab6fa1f3a7dd04a6b70824e2abba46a104",
14
- "api/v2/openapi.yaml": "7cd9c6703ee7e27f06dea079ca41ec7f0227501f6f2b6ab30ba233bafdf1e63e",
12
+ "api/seams-v2.yaml": "57cf1d472f2bf9bf61bcd2239b9fd43277072d02a367d7a85725aea0e01300db",
13
+ "api/v2/asyncapi.yaml": "40cb3ee66657a736b92c6f79241169e3669486058a65e143d99509d41d691d67",
14
+ "api/v2/openapi.yaml": "08581315aa90da54f9a1c56f8b972a801eec99bda752d92c3ea2b16113b33a92",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
16
  "schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
17
17
  "schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
@@ -204,7 +204,7 @@
204
204
  "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
205
205
  "spec/v1/alias-detectors.json": "0b2f28808ffe0b9d98a187775038c3406bb70919747a788a6e4f866e293ae6d3",
206
206
  "spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
207
- "spec/v1/core-standard-manifest.json": "635549fa9beb8e0ee92a31461803d1170fea36a5738d79aaaa0ef0c1ae150234",
207
+ "spec/v1/core-standard-manifest.json": "cdc802f553a24b2ea8288370a54a9cf9a88d65bb80ec33c258c8fe2d2737edd1",
208
208
  "spec/v1/deprecations.json": "a3293072ea951857395d05f0437264e88965f1e9019ff61c55bacfa9c1aaa17c",
209
209
  "spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
210
210
  "spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
@@ -303,9 +303,9 @@
303
303
  "spec/v2/path-manifest.json": "17298a245c5da9a70aa7ab0b7f2db4277e06209d6fcdd7187a8b4cea179c08f3",
304
304
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
305
305
  "spec/v2/profiles.json": "180beb9ef2d161766fa23b2987e7e829851e3d316273147cf6a4a3f324befd6b",
306
- "spec/v2/release.json": "623dc26d0c678f0050670e702c73cf5a1dea4b3f5a09284e920843897417527a",
306
+ "spec/v2/release.json": "0219a1874302b4f3f2e8494d3c972cb649483838f53076e612c04bdf298c56dd",
307
307
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
308
- "spec/v2/surface-baseline.json": "2f618a23b875d663316b0aebebd07fbfc9795fa01396935d898f9b8123e5a9a6"
308
+ "spec/v2/surface-baseline.json": "88e9e211ec1c8d532c413b7407a15f15a3001099b9cda77f2bcc694028a72544"
309
309
  },
310
- "corpusCommit": "a6a1325b67b6dc08122aa78d3abb62ebf7959d11"
310
+ "corpusCommit": "16a175bf4ad0485d8c1eefe97a83bfbc8d1057bc"
311
311
  }
@@ -21,9 +21,11 @@ import { pollUntilTerminal } from '../lib/polling.js';
21
21
  import { isFixtureAdvertised } from '../lib/fixtures.js';
22
22
  import { req } from '../lib/requirement-ids.js';
23
23
  import { blockedDespiteAssertions } from '../lib/soft-skip.js';
24
+ import { softSkip } from '../lib/soft-skip.js';
24
25
 
25
26
  const NOOP_WORKFLOW_ID = 'conformance-noop';
26
27
  const SKIP_NO_NOOP = !isFixtureAdvertised(NOOP_WORKFLOW_ID);
28
+ const NO_FIXTURE_REASON = `precondition not met — \`${NOOP_WORKFLOW_ID}\` is not advertised, so this host cannot witness the eventOrdering floor. RFC 0148 §A: \`blocked\`, naming the fixture (#1686: a describe-level skip recorded no disposition, and an unrecorded floor rejected the whole certification)`;
27
29
 
28
30
  interface RawEvent {
29
31
  // Event shape may use `seq` or `sequence` depending on host's event-
@@ -49,8 +51,9 @@ function getSeq(event: RawEvent): number | null {
49
51
  // always yields a readable events[] of at least two. A host that does not was
50
52
  // never observed ordering anything — until 2026-09-27 these recorded a
51
53
  // partial-witness PASS.
52
- describe.skipIf(SKIP_NO_NOOP)('event-ordering: polling returns events in monotonic order', () => {
54
+ describe('event-ordering: polling returns events in monotonic order', () => {
53
55
  it('events from a single poll have non-decreasing sequence numbers', async () => {
56
+ if (SKIP_NO_NOOP) return softSkip('blocked', NO_FIXTURE_REASON);
54
57
  const create = await driver.post('/v1/runs', { workflowId: NOOP_WORKFLOW_ID });
55
58
  expect(create.status).toBe(201);
56
59
  const runId = (create.json as { runId: string }).runId;
@@ -76,6 +79,7 @@ describe.skipIf(SKIP_NO_NOOP)('event-ordering: polling returns events in monoton
76
79
  });
77
80
 
78
81
  it('repeated polls of a terminal run yield identical event sequences', async () => {
82
+ if (SKIP_NO_NOOP) return softSkip('blocked', NO_FIXTURE_REASON);
79
83
  const create = await driver.post('/v1/runs', { workflowId: NOOP_WORKFLOW_ID });
80
84
  expect(create.status).toBe(201);
81
85
  const runId = (create.json as { runId: string }).runId;
@@ -106,8 +110,9 @@ describe.skipIf(SKIP_NO_NOOP)('event-ordering: polling returns events in monoton
106
110
  });
107
111
  });
108
112
 
109
- describe.skipIf(SKIP_NO_NOOP)('event-ordering: terminal run has at most one terminal event', () => {
113
+ describe('event-ordering: terminal run has at most one terminal event', () => {
110
114
  it('event stream contains exactly one of run.completed / run.failed / run.cancelled', async () => {
115
+ if (SKIP_NO_NOOP) return softSkip('blocked', NO_FIXTURE_REASON);
111
116
  const create = await driver.post('/v1/runs', { workflowId: NOOP_WORKFLOW_ID });
112
117
  expect(create.status).toBe(201);
113
118
  const runId = (create.json as { runId: string }).runId;
@@ -129,6 +134,7 @@ describe.skipIf(SKIP_NO_NOOP)('event-ordering: terminal run has at most one term
129
134
  });
130
135
 
131
136
  it('the terminal event is the LAST event in the stream', async () => {
137
+ if (SKIP_NO_NOOP) return softSkip('blocked', NO_FIXTURE_REASON);
132
138
  const create = await driver.post('/v1/runs', { workflowId: NOOP_WORKFLOW_ID });
133
139
  expect(create.status).toBe(201);
134
140
  const runId = (create.json as { runId: string }).runId;
@@ -9,12 +9,15 @@ import { driver } from '../lib/driver.js';
9
9
  import { pollUntilTerminal } from '../lib/polling.js';
10
10
  import { isFixtureAdvertised } from '../lib/fixtures.js';
11
11
  import { req } from '../lib/requirement-ids.js';
12
+ import { softSkip } from '../lib/soft-skip.js';
12
13
 
13
14
  const WORKFLOW_ID = 'conformance-failure';
14
15
  const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
16
+ const NO_FIXTURE_REASON = `precondition not met — \`${WORKFLOW_ID}\` is not advertised, so this host cannot witness the failure-path floor. RFC 0148 §A: \`blocked\`, naming the fixture (#1686: a describe-level skip recorded no disposition, and an unrecorded floor rejected the whole certification)`;
15
17
 
16
- describe.skipIf(SKIP_NO_FIXTURE)('failure: conformance-failure fixture reaches terminal `failed`', () => {
18
+ describe('failure: conformance-failure fixture reaches terminal `failed`', () => {
17
19
  it('POST /v1/runs accepts the run and run terminates as failed with structured error', async () => {
20
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
18
21
  const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
19
22
  expect(create.status, req('openwop.it.failure-path.post-v1-runs-accepts-the-run-and-run-terminates-as-failed-with-structured-error',
20
23
  'rest-endpoints.md',
@@ -19,16 +19,19 @@ import { driver } from '../lib/driver.js';
19
19
  import { isFixtureAdvertised } from '../lib/fixtures.js';
20
20
  import { readErrorCode } from '../lib/error-envelope.js';
21
21
  import { req } from '../lib/requirement-ids.js';
22
+ import { softSkip } from '../lib/soft-skip.js';
22
23
 
23
24
  const WORKFLOW_ID = 'conformance-idempotent';
24
25
  const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
26
+ const NO_FIXTURE_REASON = `precondition not met — \`${WORKFLOW_ID}\` is not advertised, so this host cannot witness the idempotency floor. RFC 0148 §A: \`blocked\`, naming the fixture (#1686: a describe-level skip recorded no disposition, and an unrecorded floor rejected the whole certification)`;
25
27
 
26
28
  function freshKey(suffix: string): string {
27
29
  return `openwop-conformance-${Date.now()}-${Math.random().toString(36).slice(2, 8)}-${suffix}`;
28
30
  }
29
31
 
30
- describe.skipIf(SKIP_NO_FIXTURE)('idempotency: same key + same body replays per idempotency.md §Layer 1', () => {
32
+ describe('idempotency: same key + same body replays per idempotency.md §Layer 1', () => {
31
33
  it('returns same runId twice and sets openwop-Idempotent-Replay on the replay', async () => {
34
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
32
35
  const key = freshKey('replay');
33
36
  const body = { workflowId: WORKFLOW_ID, inputs: { nonce: 'abc-123' } };
34
37
 
@@ -66,8 +69,9 @@ describe.skipIf(SKIP_NO_FIXTURE)('idempotency: same key + same body replays per
66
69
  });
67
70
  });
68
71
 
69
- describe.skipIf(SKIP_NO_FIXTURE)('idempotency: same key + different body conflicts per idempotency.md §Layer 1', () => {
72
+ describe('idempotency: same key + different body conflicts per idempotency.md §Layer 1', () => {
70
73
  it('returns 409 when the body changes under the same key', async () => {
74
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
71
75
  const key = freshKey('conflict');
72
76
 
73
77
  const first = await driver.post(
@@ -11,13 +11,16 @@ import { driver } from '../lib/driver.js';
11
11
  import { pollUntilStatus, pollUntilTerminal } from '../lib/polling.js';
12
12
  import { isFixtureAdvertised } from '../lib/fixtures.js';
13
13
  import { req } from '../lib/requirement-ids.js';
14
+ import { softSkip } from '../lib/soft-skip.js';
14
15
 
15
16
  const WORKFLOW_ID = 'conformance-clarification';
16
17
  const NODE_ID = 'ask';
17
18
  const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
19
+ const NO_FIXTURE_REASON = `precondition not met — \`${WORKFLOW_ID}\` is not advertised, so this host cannot witness the interrupt-clarification floor. RFC 0148 §A: \`blocked\`, naming the fixture (#1686: a describe-level skip recorded no disposition, and an unrecorded floor rejected the whole certification)`;
18
20
 
19
- describe.skipIf(SKIP_NO_FIXTURE)('interrupt: clarification answers resume to `completed`', () => {
21
+ describe('interrupt: clarification answers resume to `completed`', () => {
20
22
  it('run suspends at ask, answers payload drives terminal completed', async () => {
23
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
21
24
  const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
22
25
  expect(create.status).toBe(201);
23
26
  const runId = (create.json as { runId: string }).runId;
@@ -33,6 +33,7 @@ import { req } from '../lib/requirement-ids.js';
33
33
 
34
34
  const SOURCE_WORKFLOW_ID = 'conformance-noop';
35
35
  const SKIP_NO_NOOP = !isFixtureAdvertised(SOURCE_WORKFLOW_ID);
36
+ const NO_FIXTURE_REASON = `precondition not met — \`${SOURCE_WORKFLOW_ID}\` is not advertised, so this host cannot witness the replay-fork floor. RFC 0148 §A: \`blocked\`, naming the fixture (#1686: a describe-level skip recorded no disposition, and an unrecorded floor rejected the whole certification)`;
36
37
 
37
38
  async function fetchReplayModes(): Promise<readonly string[]> {
38
39
  const res = await driver.get('/.well-known/openwop', { authenticated: false });
@@ -56,8 +57,9 @@ async function startAndFinishNoop(): Promise<string> {
56
57
  return runId;
57
58
  }
58
59
 
59
- describe.skipIf(SKIP_NO_NOOP)('replay: fork from fromSeq=0 in replay mode', () => {
60
+ describe('replay: fork from fromSeq=0 in replay mode', () => {
60
61
  it('produces a new run that reaches terminal `completed`', async (ctx) => {
62
+ if (SKIP_NO_NOOP) return softSkip('blocked', NO_FIXTURE_REASON);
61
63
  const modes = await fetchReplayModes();
62
64
  if (!modes.includes('replay')) {
63
65
  // Visible skip — earlier this was a silent `return` that
@@ -101,8 +103,9 @@ describe.skipIf(SKIP_NO_NOOP)('replay: fork from fromSeq=0 in replay mode', () =
101
103
  });
102
104
  });
103
105
 
104
- describe.skipIf(SKIP_NO_NOOP)('replay: fork from fromSeq=0 in branch mode with empty overlay', () => {
106
+ describe('replay: fork from fromSeq=0 in branch mode with empty overlay', () => {
105
107
  it('produces a new run that reaches terminal `completed`', async (ctx) => {
108
+ if (SKIP_NO_NOOP) return softSkip('blocked', NO_FIXTURE_REASON);
106
109
  const modes = await fetchReplayModes();
107
110
  if (!modes.includes('branch')) {
108
111
  softSkip('inapplicable', "host does not advertise the `branch` fork mode — this leg's rule has no path to apply");
@@ -130,7 +133,7 @@ describe.skipIf(SKIP_NO_NOOP)('replay: fork from fromSeq=0 in branch mode with e
130
133
  });
131
134
  });
132
135
 
133
- describe.skipIf(SKIP_NO_NOOP)('replay: validation errors', () => {
136
+ describe('replay: validation errors', () => {
134
137
  // Earlier each of these tests had a silent `return;` early-exit
135
138
  // when the host advertised no replay modes (or only the wrong mode for
136
139
  // the assertion). That collapsed unexercised paths into vacuous green
@@ -140,6 +143,7 @@ describe.skipIf(SKIP_NO_NOOP)('replay: validation errors', () => {
140
143
  // "exercised the validation path and got the expected error code."
141
144
 
142
145
  it('rejects negative fromSeq with 400', async (ctx) => {
146
+ if (SKIP_NO_NOOP) return softSkip('blocked', NO_FIXTURE_REASON);
143
147
  const modes = await fetchReplayModes();
144
148
  if (modes.length === 0) {
145
149
  softSkip('inapplicable', "host advertises no usable fork mode for this leg");
@@ -159,6 +163,7 @@ describe.skipIf(SKIP_NO_NOOP)('replay: validation errors', () => {
159
163
  });
160
164
 
161
165
  it('rejects fromSeq beyond source event log length with 422', async (ctx) => {
166
+ if (SKIP_NO_NOOP) return softSkip('blocked', NO_FIXTURE_REASON);
162
167
  const modes = await fetchReplayModes();
163
168
  if (modes.length === 0) {
164
169
  softSkip('inapplicable', "host advertises no usable fork mode for this leg");
@@ -180,6 +185,7 @@ describe.skipIf(SKIP_NO_NOOP)('replay: validation errors', () => {
180
185
  });
181
186
 
182
187
  it('rejects replay mode with non-empty runOptionsOverlay (overlay is branch-only)', async (ctx) => {
188
+ if (SKIP_NO_NOOP) return softSkip('blocked', NO_FIXTURE_REASON);
183
189
  const modes = await fetchReplayModes();
184
190
  if (!modes.includes('replay')) {
185
191
  // The rule under test (`replay` + non-empty `runOptionsOverlay`
@@ -206,6 +212,7 @@ describe.skipIf(SKIP_NO_NOOP)('replay: validation errors', () => {
206
212
  });
207
213
 
208
214
  it('rejects fork on a non-existent run with 404', async (ctx) => {
215
+ if (SKIP_NO_NOOP) return softSkip('blocked', NO_FIXTURE_REASON);
209
216
  const modes = await fetchReplayModes();
210
217
  if (modes.length === 0) {
211
218
  softSkip('inapplicable', "host advertises no usable fork mode for this leg");
@@ -14,12 +14,15 @@ 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 } from '../lib/soft-skip.js';
17
18
 
18
19
  const NOOP_WORKFLOW_ID = 'conformance-noop';
19
20
  const SKIP_NO_NOOP = !isFixtureAdvertised(NOOP_WORKFLOW_ID);
21
+ const NO_FIXTURE_REASON = `precondition not met — \`${NOOP_WORKFLOW_ID}\` is not advertised, so this host cannot witness the runs-lifecycle floor. RFC 0148 §A: \`blocked\`, naming the fixture (#1686: a describe-level skip recorded no disposition, and an unrecorded floor rejected the whole certification)`;
20
22
 
21
- describe.skipIf(SKIP_NO_NOOP)('run lifecycle: conformance-noop fixture', () => {
23
+ describe('run lifecycle: conformance-noop fixture', () => {
22
24
  it('POST /v1/runs returns 201 with runId per rest-endpoints.md', async () => {
25
+ if (SKIP_NO_NOOP) return softSkip('blocked', NO_FIXTURE_REASON);
23
26
  const res = await driver.post('/v1/runs', { workflowId: NOOP_WORKFLOW_ID });
24
27
 
25
28
  expect(res.status, req('openwop.it.runs-lifecycle.post-v1-runs-returns-201-with-runid-per-rest-endpoints-md',
@@ -39,6 +42,7 @@ describe.skipIf(SKIP_NO_NOOP)('run lifecycle: conformance-noop fixture', () => {
39
42
  });
40
43
 
41
44
  it('reaches terminal `completed` within bounded time per fixtures.md noop spec', async () => {
45
+ if (SKIP_NO_NOOP) return softSkip('blocked', NO_FIXTURE_REASON);
42
46
  const create = await driver.post('/v1/runs', { workflowId: NOOP_WORKFLOW_ID });
43
47
  expect(create.status).toBe(201);
44
48
  const runId = (create.json as { runId: string }).runId;
@@ -57,6 +61,7 @@ describe.skipIf(SKIP_NO_NOOP)('run lifecycle: conformance-noop fixture', () => {
57
61
  });
58
62
 
59
63
  it('GET /v1/runs/{nonexistentId} returns 404 (or 403) per rest-endpoints.md', async () => {
64
+ if (SKIP_NO_NOOP) return softSkip('blocked', NO_FIXTURE_REASON);
60
65
  const res = await driver.get('/v1/runs/openwop-conformance-this-run-id-does-not-exist');
61
66
  expect(
62
67
  [403, 404].includes(res.status),
@@ -23,17 +23,20 @@ import { subscribe } from '../lib/sse.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
27
 
27
28
  const WORKFLOW_ID = 'conformance-delay';
28
29
  const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
30
+ const NO_FIXTURE_REASON = `precondition not met — \`${WORKFLOW_ID}\` is not advertised, so this host cannot witness the stream-modes-buffer floor. RFC 0148 §A: \`blocked\`, naming the fixture (#1686: a describe-level skip recorded no disposition, and an unrecorded floor rejected the whole certification)`;
29
31
 
30
32
  interface RunEventDoc {
31
33
  readonly type: string;
32
34
  readonly sequence: number;
33
35
  }
34
36
 
35
- describe.skipIf(SKIP_NO_FIXTURE)('stream-modes-buffer: ?bufferMs= aggregation hint', () => {
37
+ describe('stream-modes-buffer: ?bufferMs= aggregation hint', () => {
36
38
  it('accepts bufferMs in range and emits at least one event: batch frame', async () => {
39
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
37
40
  const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
38
41
  expect(create.status).toBe(201);
39
42
  const runId = (create.json as { runId: string }).runId;
@@ -70,6 +73,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('stream-modes-buffer: ?bufferMs= aggregation hi
70
73
  });
71
74
 
72
75
  it('rejects out-of-range bufferMs with 400 validation_error', async () => {
76
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
73
77
  const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
74
78
  const runId = (create.json as { runId: string }).runId;
75
79
 
@@ -88,6 +92,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('stream-modes-buffer: ?bufferMs= aggregation hi
88
92
  });
89
93
 
90
94
  it('forces flush on terminal — run.completed arrives bundled in a batch BEFORE the timer fires', async () => {
95
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
91
96
  const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
92
97
  const runId = (create.json as { runId: string }).runId;
93
98
 
@@ -132,6 +137,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('stream-modes-buffer: ?bufferMs= aggregation hi
132
137
  });
133
138
 
134
139
  it('bufferMs=0 behaves identically to omitting (per-event mode)', async () => {
140
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
135
141
  const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
136
142
  const runId = (create.json as { runId: string }).runId;
137
143
 
@@ -22,16 +22,19 @@ import { subscribe, type SseEvent } from '../lib/sse.js';
22
22
  import { 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 } from '../lib/soft-skip.js';
25
26
 
26
27
  const WORKFLOW_ID = 'conformance-delay';
27
28
  const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
29
+ const NO_FIXTURE_REASON = `precondition not met — \`${WORKFLOW_ID}\` is not advertised, so this host cannot witness the stream-modes-mixed floor. RFC 0148 §A: \`blocked\`, naming the fixture (#1686: a describe-level skip recorded no disposition, and an unrecorded floor rejected the whole certification)`;
28
30
 
29
31
  function eventTypes(events: readonly SseEvent[]): string[] {
30
32
  return events.map((e) => e.event);
31
33
  }
32
34
 
33
- describe.skipIf(SKIP_NO_FIXTURE)('stream-modes-mixed: comma-separated subsets', () => {
35
+ describe('stream-modes-mixed: comma-separated subsets', () => {
34
36
  it('accepts streamMode=updates,messages and emits a server-closed stream', async () => {
37
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
35
38
  const create = await driver.post('/v1/runs', {
36
39
  workflowId: WORKFLOW_ID,
37
40
  inputs: { delayMs: 500 },
@@ -62,6 +65,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('stream-modes-mixed: comma-separated subsets',
62
65
  });
63
66
 
64
67
  it('rejects streamMode=values,updates with 400 + unsupported_stream_mode', async () => {
68
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
65
69
  const create = await driver.post('/v1/runs', {
66
70
  workflowId: WORKFLOW_ID,
67
71
  inputs: { delayMs: 100 },
@@ -96,6 +100,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('stream-modes-mixed: comma-separated subsets',
96
100
  });
97
101
 
98
102
  it('rejects streamMode=updates,bogus (one bad mode fails the whole list)', async () => {
103
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
99
104
  const create = await driver.post('/v1/runs', {
100
105
  workflowId: WORKFLOW_ID,
101
106
  inputs: { delayMs: 100 },
@@ -114,6 +119,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('stream-modes-mixed: comma-separated subsets',
114
119
  });
115
120
 
116
121
  it('mixed mode union: updates,debug sees every event updates sees', async () => {
122
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
117
123
  // Run twice — once with updates only, once with updates,debug.
118
124
  // The mixed-mode response MUST be a superset of the updates-only
119
125
  // response (union semantics).
@@ -12,9 +12,11 @@ import { driver } from '../lib/driver.js';
12
12
  import { subscribe, type SseEvent } from '../lib/sse.js';
13
13
  import { isFixtureAdvertised } from '../lib/fixtures.js';
14
14
  import { req } from '../lib/requirement-ids.js';
15
+ import { softSkip } from '../lib/soft-skip.js';
15
16
 
16
17
  const WORKFLOW_ID = 'conformance-delay';
17
18
  const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
19
+ const NO_FIXTURE_REASON = `precondition not met — \`${WORKFLOW_ID}\` is not advertised, so this host cannot witness the stream-modes floor. RFC 0148 §A: \`blocked\`, naming the fixture (#1686: a describe-level skip recorded no disposition, and an unrecorded floor rejected the whole certification)`;
18
20
 
19
21
  async function startDelayRun(delayMs: number): Promise<string> {
20
22
  const create = await driver.post('/v1/runs', {
@@ -31,8 +33,9 @@ function eventTypes(events: readonly SseEvent[]): string[] {
31
33
  return events.map((e) => e.event);
32
34
  }
33
35
 
34
- describe.skipIf(SKIP_NO_FIXTURE)('stream-modes: updates (default) closes on terminal event', () => {
36
+ describe('stream-modes: updates (default) closes on terminal event', () => {
35
37
  it('emits at least run.started + run.completed and server closes the stream', async () => {
38
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
36
39
  const runId = await startDelayRun(1_000);
37
40
  const { events, closedBy } = await subscribe(
38
41
  `/v1/runs/${encodeURIComponent(runId)}/events?streamMode=updates`,
@@ -56,8 +59,9 @@ describe.skipIf(SKIP_NO_FIXTURE)('stream-modes: updates (default) closes on term
56
59
  });
57
60
  });
58
61
 
59
- describe.skipIf(SKIP_NO_FIXTURE)('stream-modes: invalid streamMode is rejected', () => {
62
+ describe('stream-modes: invalid streamMode is rejected', () => {
60
63
  it('returns 400 and a structured error body', async () => {
64
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
61
65
  const runId = await startDelayRun(1_000);
62
66
  const res = await driver.get(
63
67
  `/v1/runs/${encodeURIComponent(runId)}/events?streamMode=does-not-exist`,
@@ -86,8 +90,9 @@ describe.skipIf(SKIP_NO_FIXTURE)('stream-modes: invalid streamMode is rejected',
86
90
  });
87
91
  });
88
92
 
89
- describe.skipIf(SKIP_NO_FIXTURE)('stream-modes: values mode is reachable + closes on terminal', () => {
93
+ describe('stream-modes: values mode is reachable + closes on terminal', () => {
90
94
  it('returns 200 + emits at least one event + server-closes per stream-modes.md §values', async () => {
95
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
91
96
  const runId = await startDelayRun(1_000);
92
97
  const result = await subscribe(
93
98
  `/v1/runs/${encodeURIComponent(runId)}/events?streamMode=values`,
@@ -110,8 +115,9 @@ describe.skipIf(SKIP_NO_FIXTURE)('stream-modes: values mode is reachable + close
110
115
  });
111
116
  });
112
117
 
113
- describe.skipIf(SKIP_NO_FIXTURE)('stream-modes: debug emits at least as many events as updates', () => {
118
+ describe('stream-modes: debug emits at least as many events as updates', () => {
114
119
  it('debug stream is a superset of updates per stream-modes.md mode-mapping', async () => {
120
+ if (SKIP_NO_FIXTURE) return softSkip('blocked', NO_FIXTURE_REASON);
115
121
  const runIdUpdates = await startDelayRun(1_000);
116
122
  const updatesResult = await subscribe(
117
123
  `/v1/runs/${encodeURIComponent(runIdUpdates)}/events?streamMode=updates`,