@openwop/openwop-conformance 2.42.2 → 2.42.3

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,34 @@
1
1
  # `@openwop/openwop-conformance` Changelog
2
2
 
3
+ ## [2.42.3] — 2026-09-27 — the unfailable-leg audit: 19 legs a non-conforming host could pass now measure their requirement
4
+
5
+ - **The unfailable-leg audit: 19 legs that a non-conforming host could pass now measure their requirement.** A scanner (`scripts/audit-unfailable-legs.mjs`, an audit aid, not a gate) flagged 66 legs that witness an Accepted RFC's Falsifiability row and have a shape this corpus has shipped as a defect before: satisfiable bounds (the 1 ≤ 1 no-refire leg), loops over a possibly empty list, `if`-guarded assertions, always-defined checks, and the host's own ledger as the only witness. A reviewer judged each one against a single bar: would a host that breaks the rule record `executed-pass`?
6
+ - **Confirmed and fixed (12):**
7
+ - `0173.effect-identity-business-key`: a no-op run's empty ledger passed on setup asserts.
8
+ - `.retry`: an omitted `providerKey` folded to `''`, so "one distinct key" passed.
9
+ - `0205.artifact-url-part-scoped`: a 3xx to a public pre-signed URL passed.
10
+ - `0176.era-2-append-vocabulary`: cancel appends names that are the same in v1 and v2, so the writer rule was never tested. It now records `blocked` unless a renamed type is appended. Its `era` leg now requires `eventLogSchemaVersion`.
11
+ - `0171.poll-cursor-v2.shape`: `lastSequence` must equal the log's highest sequence, and `status`/`isTerminal` must say terminal.
12
+ - `0173.webhook-durable-delivery`: exponential backoff must actually grow, second gap ≥ 1.5× the first.
13
+ - `0175.mrtr-rounds-ceiling.refused`: the refusal must come at the ceiling, not before it.
14
+ - `0215.no-head-of-line`: a serial dispatcher with a short timeout passed as a partial witness. It now fails below the concurrency floor and is otherwise `blocked`.
15
+ - `0187.bound-id-kinds.per-kind`, effectId/deliveryId: no-op runs record `inapplicable` before any assertion.
16
+ - `0173.effect-seam-manifest`: removed a loop that could not fail.
17
+ - RFC 0173 cites `effect-seam-no-refire` for replay suppression.
18
+ - RFC 0186 says its row is a schema-validation witness.
19
+ - **Plausible, tightened (7):**
20
+ - `0201.message-id-stable`: all four delivery keys are required, otherwise `blocked`.
21
+ - `0169.profiles-derived-only`: records need `status`, `since` and `witness`; a tautology is removed.
22
+ - `0200.inbound-credential-no-passthrough`: waits for THIS run's delivery.
23
+ - `0168.advertised-fixtures-exist`: samples across the whole list.
24
+ - `0198.task-update-approver-checked`: polls about 3 s rather than one 750 ms read.
25
+ - `0201.endpoint-verification`: a late echo control and a scaled grace; a missing control is `blocked`.
26
+ - `0207.a2a-malformed-ignored`: the task must not fail or be rejected.
27
+ - **Not defects:** 47 of the 66 flags; each reviewer recorded its reason.
28
+ - **Measured against the v2 reference host** (loopback regression lane) before merge.
29
+ - **RFC 0217 gap G1 closes in the contract peer.** `spec/v1/gaps.json` records MyndHyve's certified production witness of `0217.dead-letter-read-after-unregister` (openwop#1631). No scenario change.
30
+ - **Version moved ahead of publication.** `@openwop/openwop-conformance` and its exact-pinned peer `@openwop/spec-artifacts` move to `2.42.3` because `2.42.2` is tagged. Not tagged, not published.
31
+
3
32
  ## [2.42.2] — 2026-09-26 — four conformance legs now measure what their spec says: effect re-fire, keying fallback, A2UI JSON values, digest-safe scrubbing
4
33
 
5
34
  - **`0173.effect-seam-no-refire` can detect a re-fire.** The leg asserted that the replay fork's ledger count was at most the parent's. The seam fires one attempt on the source run, so a host that re-fires on replay read 1 ≤ 1 and passed, over exactly the defect the leg exists for. ws3 measured it: openwop-app with `sideEffecting` dropped still passed. The leg now fails if the fork records any attempt the parent never made, using the new `lib/effect-refire.ts`. An inherited row repeats a parent attempt's `(nodeId, attempt, at)`, while a re-fire is a new attempt, and one parent row covers at most one fork row. A host whose fork projection is empty passes, as does one that projects the parent's attempts as history. The `effect-ledger-projection` schema description, which said the scenario "compares row COUNTS", is corrected. New suite self-test `src/lib/effect-refire.test.ts` uses a re-firing host stub as its first leg. It is sabotage-proved: with the old count rule restored, that leg fails.
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  # --legacy-peer-deps is REQUIRED, not optional: the exact peer pin is what npm's
12
12
  # default resolver refuses. npm 10.9 fails outright with
13
13
  # "Cannot read properties of null (reading 'edgesOut')" — use npm >= 11.
14
- npm install --legacy-peer-deps @openwop/openwop-conformance@2.42.2 @openwop/spec-artifacts@2.42.2
14
+ npm install --legacy-peer-deps @openwop/openwop-conformance@2.42.3 @openwop/spec-artifacts@2.42.3
15
15
  # or run without install:
16
16
  npx @openwop/openwop-conformance --base-url https://api.example.com --api-key hk_test_...
17
17
  ```
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "package": "@openwop/spec-artifacts",
3
- "version": "2.42.2",
4
- "stampSha256": "1410563585447c32dced7e6679caf5968a68e9de88c39a5188d527336b86e784"
3
+ "version": "2.42.3",
4
+ "stampSha256": "06eaa8ddc41c7ab3bb1af3b2b6b3566042a92b50a2249914946657c16490896d"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "2.42.2",
3
+ "version": "2.42.3",
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.42.2"
59
+ "@openwop/spec-artifacts": "2.42.3"
60
60
  }
61
61
  }
package/requirements.json CHANGED
@@ -14752,7 +14752,7 @@
14752
14752
  {
14753
14753
  "id": "openwop.it.inbound-credential-no-passthrough.no-webhook-delivery-carries-the-caller-s-authorization-cookie-or-proxy-authoriza",
14754
14754
  "file": "inbound-credential-no-passthrough.test.ts",
14755
- "line": 70,
14755
+ "line": 71,
14756
14756
  "title": "no webhook delivery carries the caller's Authorization, Cookie or Proxy-Authorization",
14757
14757
  "explicitId": "openwop.requirement.0200.inbound-credential-no-passthrough",
14758
14758
  "citations": [
@@ -27568,7 +27568,7 @@
27568
27568
  {
27569
27569
  "id": "openwop.it.v2-artifact-a2a-shape.records-why-export-alias-equivalence-has-no-wire-witness",
27570
27570
  "file": "v2-artifact-a2a-shape.test.ts",
27571
- "line": 132,
27571
+ "line": 138,
27572
27572
  "title": "records why export-alias-equivalence has no wire witness",
27573
27573
  "explicitId": "openwop.requirement.0205.export-alias-equivalence",
27574
27574
  "citations": [
@@ -27753,7 +27753,7 @@
27753
27753
  {
27754
27754
  "id": "openwop.it.v2-bound-id-kinds.deliveryid-every-record-in-the-dead-letter-read-carries-a-bound-id-in-the-caller",
27755
27755
  "file": "v2-bound-id-kinds.test.ts",
27756
- "line": 230,
27756
+ "line": 235,
27757
27757
  "title": "deliveryId: every record in the dead-letter read carries a bound id in the caller's tenant",
27758
27758
  "explicitId": "openwop.requirement.0187.bound-id-kinds.per-kind",
27759
27759
  "citations": [
@@ -28805,7 +28805,7 @@
28805
28805
  {
28806
28806
  "id": "openwop.it.v2-effect-identity-business-key.the-same-provider-key-is-presented-across-two-transport-retries",
28807
28807
  "file": "v2-effect-identity-business-key.test.ts",
28808
- "line": 96,
28808
+ "line": 107,
28809
28809
  "title": "the same provider key is presented across two transport retries",
28810
28810
  "explicitId": "openwop.requirement.0173.effect-identity-business-key.retry",
28811
28811
  "citations": [
@@ -28819,6 +28819,11 @@
28819
28819
  "requirement": null,
28820
28820
  "interpolated": true
28821
28821
  },
28822
+ {
28823
+ "section": "spec/v2/core/idempotency.md §Layer 2: effect identity",
28824
+ "requirement": null,
28825
+ "interpolated": true
28826
+ },
28822
28827
  {
28823
28828
  "section": "spec/v2/core/idempotency.md §Layer 2: effect identity",
28824
28829
  "requirement": null,
@@ -28866,11 +28871,6 @@
28866
28871
  "requirement": null,
28867
28872
  "interpolated": true
28868
28873
  },
28869
- {
28870
- "section": "RFC 0173 §C.1",
28871
- "requirement": null,
28872
- "interpolated": true
28873
- },
28874
28874
  {
28875
28875
  "section": "effect-seam-manifest.schema.json seams[].branchReFires",
28876
28876
  "requirement": null,
@@ -28881,7 +28881,7 @@
28881
28881
  {
28882
28882
  "id": "openwop.it.v2-effect-seam-manifest.the-replay-facet-names-the-manifest-address-as-the-constant-host-effect-seams",
28883
28883
  "file": "v2-effect-seam-manifest.test.ts",
28884
- "line": 118,
28884
+ "line": 112,
28885
28885
  "title": "the replay facet names the manifest address as the constant /host/effect-seams",
28886
28886
  "explicitId": "openwop.requirement.0173.effect-seam-manifest.facet",
28887
28887
  "citations": [
@@ -28981,7 +28981,7 @@
28981
28981
  {
28982
28982
  "id": "openwop.it.v2-era-2-append-vocabulary.the-run-keeps-the-era-it-was-created-with-an-append-does-not-promote-it-to-era-3",
28983
28983
  "file": "v2-era-2-append-vocabulary.test.ts",
28984
- "line": 133,
28984
+ "line": 165,
28985
28985
  "title": "the run keeps the era it was created with; an append does not promote it to era 3",
28986
28986
  "explicitId": "openwop.requirement.0176.era-2-append-vocabulary",
28987
28987
  "citations": [
@@ -29588,13 +29588,23 @@
29588
29588
  {
29589
29589
  "section": "spec/v2/core/interop.md §\"Trace context\" (RFC 0207)",
29590
29590
  "requirement": "SendMessage MUST answer a normal result"
29591
+ },
29592
+ {
29593
+ "section": "spec/v2/core/interop.md §\"Trace context\" (RFC 0207)",
29594
+ "requirement": null,
29595
+ "interpolated": true
29596
+ },
29597
+ {
29598
+ "section": "spec/v2/core/interop.md §\"Trace context\" (RFC 0207)",
29599
+ "requirement": null,
29600
+ "interpolated": true
29591
29601
  }
29592
29602
  ]
29593
29603
  },
29594
29604
  {
29595
29605
  "id": "openwop.it.v2-interop-trace-context.a2a-interface-metadata-openwop-traceparent-is-the-parent-not-the-transport-heade",
29596
29606
  "file": "v2-interop-trace-context.test.ts",
29597
- "line": 335,
29607
+ "line": 358,
29598
29608
  "title": "A2A interface: metadata.openwop.traceparent is the parent, not the transport header",
29599
29609
  "explicitId": "openwop.requirement.0207.a2a-inbound-adopts",
29600
29610
  "citations": [
@@ -30498,7 +30508,7 @@
30498
30508
  {
30499
30509
  "id": "openwop.it.v2-mcp-tasks.a-mount-that-lists-the-tasks-extension-in-server-discover-also-lists-extensions",
30500
30510
  "file": "v2-mcp-tasks.test.ts",
30501
- "line": 247,
30511
+ "line": 248,
30502
30512
  "title": "a mount that lists the Tasks extension in server/discover also lists extensions in mcp.features",
30503
30513
  "explicitId": "openwop.requirement.0198.advertised-via-discover",
30504
30514
  "citations": [
@@ -30512,7 +30522,7 @@
30512
30522
  {
30513
30523
  "id": "openwop.it.v2-mcp-tasks.a-tools-call-declaring-the-extension-on-a-run-that-is-not-terminal-is-answered-c",
30514
30524
  "file": "v2-mcp-tasks.test.ts",
30515
- "line": 254,
30525
+ "line": 255,
30516
30526
  "title": "a tools/call declaring the extension on a run that is not terminal is answered CreateTaskResult",
30517
30527
  "explicitId": "openwop.requirement.0198.task-when-nonterminal",
30518
30528
  "citations": [
@@ -30551,7 +30561,7 @@
30551
30561
  {
30552
30562
  "id": "openwop.it.v2-mcp-tasks.taskid-is-the-run-id-in-its-projected-wire-form-with-an-opaque-segment-of-at-lea",
30553
30563
  "file": "v2-mcp-tasks.test.ts",
30554
- "line": 274,
30564
+ "line": 275,
30555
30565
  "title": "taskId is the run id in its projected wire form, with an opaque segment of at least 22 characters",
30556
30566
  "explicitId": "openwop.requirement.0198.task-id-is-run-id",
30557
30567
  "citations": [
@@ -30575,7 +30585,7 @@
30575
30585
  {
30576
30586
  "id": "openwop.it.v2-mcp-tasks.tasks-get-projects-input-required-keyed-by-interruptid-and-a-failed-run-as-compl",
30577
30587
  "file": "v2-mcp-tasks.test.ts",
30578
- "line": 290,
30588
+ "line": 291,
30579
30589
  "title": "tasks/get projects input_required keyed by interruptId, and a failed run as completed with isError true",
30580
30590
  "explicitId": "openwop.requirement.0198.task-status-projection",
30581
30591
  "citations": [
@@ -30607,7 +30617,7 @@
30607
30617
  {
30608
30618
  "id": "openwop.it.v2-mcp-tasks.tasks-update-answered-twice-resolves-the-interrupt-once-and-the-task-completes",
30609
30619
  "file": "v2-mcp-tasks.test.ts",
30610
- "line": 313,
30620
+ "line": 314,
30611
30621
  "title": "tasks/update answered twice resolves the interrupt once and the task completes",
30612
30622
  "explicitId": "openwop.requirement.0198.task-update-resolves-once",
30613
30623
  "citations": [
@@ -30635,7 +30645,7 @@
30635
30645
  {
30636
30646
  "id": "openwop.it.v2-mcp-tasks.a-tasks-update-from-a-caller-not-in-approverslist-resolves-nothing",
30637
30647
  "file": "v2-mcp-tasks.test.ts",
30638
- "line": 333,
30648
+ "line": 334,
30639
30649
  "title": "a tasks/update from a caller not in approversList resolves nothing",
30640
30650
  "explicitId": "openwop.requirement.0198.task-update-approver-checked",
30641
30651
  "citations": [
@@ -30663,7 +30673,7 @@
30663
30673
  {
30664
30674
  "id": "openwop.it.v2-mcp-tasks.tasks-get-appends-nothing-to-the-run-log",
30665
30675
  "file": "v2-mcp-tasks.test.ts",
30666
- "line": 352,
30676
+ "line": 365,
30667
30677
  "title": "tasks/get appends nothing to the run log",
30668
30678
  "explicitId": "openwop.requirement.0198.task-get-read-only",
30669
30679
  "citations": [
@@ -30677,7 +30687,7 @@
30677
30687
  {
30678
30688
  "id": "openwop.it.v2-mcp-tasks.an-unreadable-task-is-answered-32602-exactly-as-a-nonexistent-one",
30679
30689
  "file": "v2-mcp-tasks.test.ts",
30680
- "line": 367,
30690
+ "line": 380,
30681
30691
  "title": "an unreadable task is answered -32602 exactly as a nonexistent one",
30682
30692
  "explicitId": "openwop.requirement.0198.task-unreadable-not-found",
30683
30693
  "citations": [
@@ -30709,7 +30719,7 @@
30709
30719
  {
30710
30720
  "id": "openwop.it.v2-mcp-tasks.a-subscriptions-listen-acknowledgement-leaves-out-a-task-the-caller-cannot-read",
30711
30721
  "file": "v2-mcp-tasks.test.ts",
30712
- "line": 400,
30722
+ "line": 413,
30713
30723
  "title": "a subscriptions/listen acknowledgement leaves out a task the caller cannot read",
30714
30724
  "explicitId": "openwop.requirement.0198.listen-omits-unreadable",
30715
30725
  "citations": [
@@ -30737,7 +30747,7 @@
30737
30747
  {
30738
30748
  "id": "openwop.it.v2-mcp-tasks.tasks-cancel-cancels-the-run-on-a-terminal-task-it-is-acknowledged-and-appends-n",
30739
30749
  "file": "v2-mcp-tasks.test.ts",
30740
- "line": 422,
30750
+ "line": 435,
30741
30751
  "title": "tasks/cancel cancels the run; on a terminal task it is acknowledged and appends nothing",
30742
30752
  "explicitId": "openwop.requirement.0198.task-cancel-cancels-run",
30743
30753
  "citations": [
@@ -30769,7 +30779,7 @@
30769
30779
  {
30770
30780
  "id": "openwop.it.v2-mcp-tasks.a-client-disconnect-before-the-host-answers-a-task-less-tools-call-cancels-the-r",
30771
30781
  "file": "v2-mcp-tasks.test.ts",
30772
- "line": 441,
30782
+ "line": 454,
30773
30783
  "title": "a client disconnect before the host answers a task-less tools/call cancels the run it started",
30774
30784
  "explicitId": "openwop.requirement.0198.disconnect-cancels-run",
30775
30785
  "citations": [
@@ -30792,7 +30802,7 @@
30792
30802
  {
30793
30803
  "id": "openwop.it.v2-mcp-tasks.a-disconnect-after-createtaskresult-never-affects-the-run",
30794
30804
  "file": "v2-mcp-tasks.test.ts",
30795
- "line": 461,
30805
+ "line": 474,
30796
30806
  "title": "a disconnect after CreateTaskResult never affects the run",
30797
30807
  "explicitId": "openwop.requirement.0198.task-survives-disconnect",
30798
30808
  "citations": [
@@ -30811,7 +30821,7 @@
30811
30821
  {
30812
30822
  "id": "openwop.it.v2-mcp-tasks.a-blocking-tools-call-on-an-sse-stream-whose-run-is-cancelled-carries-no-notific",
30813
30823
  "file": "v2-mcp-tasks.test.ts",
30814
- "line": 476,
30824
+ "line": 489,
30815
30825
  "title": "a blocking tools/call on an SSE stream whose run is cancelled carries no notifications/cancelled and ends isError true",
30816
30826
  "explicitId": "openwop.requirement.0198.no-server-cancelled",
30817
30827
  "citations": [
@@ -30917,6 +30927,11 @@
30917
30927
  "requirement": null,
30918
30928
  "interpolated": true
30919
30929
  },
30930
+ {
30931
+ "section": "interop.md §The MCP round ceiling",
30932
+ "requirement": null,
30933
+ "interpolated": true
30934
+ },
30920
30935
  {
30921
30936
  "section": "interop.md §The MCP round ceiling",
30922
30937
  "requirement": null,
@@ -31680,7 +31695,7 @@
31680
31695
  {
31681
31696
  "id": "openwop.it.v2-poll-cursor-v2.the-response-is-the-closed-runid-events-lastsequence-status-isterminal",
31682
31697
  "file": "v2-poll-cursor-v2.test.ts",
31683
- "line": 60,
31698
+ "line": 65,
31684
31699
  "title": "the response is the closed { runId, events, lastSequence, status, isTerminal }",
31685
31700
  "explicitId": "openwop.requirement.0171.poll-cursor-v2.shape",
31686
31701
  "citations": [
@@ -31714,14 +31729,24 @@
31714
31729
  },
31715
31730
  {
31716
31731
  "section": "spec/v2/core/events.md §Poll",
31717
- "requirement": "lastSequence MUST equal the highest sequence returned when the response carries the whole log"
31732
+ "requirement": null,
31733
+ "interpolated": true
31734
+ },
31735
+ {
31736
+ "section": "spec/v2/core/events.md §Poll",
31737
+ "requirement": "isTerminal is whether the run is terminal: the run was observed terminal, so isTerminal MUST be true"
31738
+ },
31739
+ {
31740
+ "section": "spec/v2/core/events.md §Poll",
31741
+ "requirement": null,
31742
+ "interpolated": true
31718
31743
  }
31719
31744
  ]
31720
31745
  },
31721
31746
  {
31722
31747
  "id": "openwop.it.v2-poll-cursor-v2.omitting-the-cursor-returns-from-the-first-event-sequence-0",
31723
31748
  "file": "v2-poll-cursor-v2.test.ts",
31724
- "line": 77,
31749
+ "line": 100,
31725
31750
  "title": "omitting the cursor returns from the first event (sequence 0)",
31726
31751
  "explicitId": "openwop.requirement.0171.poll-cursor-v2.from-first",
31727
31752
  "citations": [
@@ -31734,7 +31759,7 @@
31734
31759
  {
31735
31760
  "id": "openwop.it.v2-poll-cursor-v2.aftersequence-n-returns-only-events-with-sequence-n",
31736
31761
  "file": "v2-poll-cursor-v2.test.ts",
31737
- "line": 87,
31762
+ "line": 110,
31738
31763
  "title": "afterSequence=N returns only events with sequence > N",
31739
31764
  "explicitId": "openwop.requirement.0171.poll-cursor-v2.after-sequence",
31740
31765
  "citations": [
@@ -31756,7 +31781,7 @@
31756
31781
  {
31757
31782
  "id": "openwop.it.v2-poll-cursor-v2.a-cursor-past-the-end-of-the-log-returns-200-with-an-empty-events-array",
31758
31783
  "file": "v2-poll-cursor-v2.test.ts",
31759
- "line": 121,
31784
+ "line": 144,
31760
31785
  "title": "a cursor past the end of the log returns 200 with an empty events array",
31761
31786
  "explicitId": "openwop.requirement.0171.poll-cursor-v2.past-end",
31762
31787
  "citations": [
@@ -31840,7 +31865,7 @@
31840
31865
  {
31841
31866
  "id": "openwop.it.v2-profiles-derived-only.the-v2-document-carries-no-root-profiles",
31842
31867
  "file": "v2-profiles-derived-only.test.ts",
31843
- "line": 43,
31868
+ "line": 48,
31844
31869
  "title": "the v2 document carries no root `profiles[]`",
31845
31870
  "explicitId": "openwop.requirement.0169.profiles-derived-only.no-root-profiles",
31846
31871
  "citations": [
@@ -31853,7 +31878,7 @@
31853
31878
  {
31854
31879
  "id": "openwop.it.v2-profiles-derived-only.every-registry-profile-is-derivable-from-the-document-the-discovery-core-predica",
31855
31880
  "file": "v2-profiles-derived-only.test.ts",
31856
- "line": 49,
31881
+ "line": 54,
31857
31882
  "title": "every registry profile is derivable from the document; the discovery-core predicate holds on a valid document",
31858
31883
  "explicitId": "openwop.requirement.0169.profiles-derived-only.derivable",
31859
31884
  "citations": [
@@ -31866,11 +31891,6 @@
31866
31891
  "requirement": null,
31867
31892
  "interpolated": true
31868
31893
  },
31869
- {
31870
- "section": "spec/v2/core/capabilities.md §7",
31871
- "requirement": null,
31872
- "interpolated": true
31873
- },
31874
31894
  {
31875
31895
  "section": "spec/v2/core/capabilities.md §7",
31876
31896
  "requirement": "openwop-discovery-core (metadata protocolVersions + preferredVersion, both REQUIRED at the root) MUST derive true from every valid v2 document"
@@ -33227,7 +33247,7 @@
33227
33247
  {
33228
33248
  "id": "openwop.requirement.0215.no-head-of-line",
33229
33249
  "file": "v2-webhook-delivery-isolation.test.ts",
33230
- "line": 111,
33250
+ "line": 113,
33231
33251
  "title": null,
33232
33252
  "explicitId": "openwop.requirement.0215.no-head-of-line",
33233
33253
  "citations": [
@@ -33256,7 +33276,8 @@
33256
33276
  },
33257
33277
  {
33258
33278
  "section": "webhooks.md §Durability (RFC 0215 §A.1, §A.2)",
33259
- "requirement": "the held subscriptions' attempts MUST have been dispatched"
33279
+ "requirement": null,
33280
+ "interpolated": true
33260
33281
  },
33261
33282
  {
33262
33283
  "section": "webhooks.md §Durability (RFC 0215 §A.1, §A.2)",
@@ -33358,6 +33379,11 @@
33358
33379
  "requirement": null,
33359
33380
  "interpolated": true
33360
33381
  },
33382
+ {
33383
+ "section": "webhooks.md §Durability",
33384
+ "requirement": null,
33385
+ "interpolated": true
33386
+ },
33361
33387
  {
33362
33388
  "section": "webhooks.md §Surfaces",
33363
33389
  "requirement": "DELETE /webhooks/{webhookId} MUST answer 204"
@@ -33367,7 +33393,7 @@
33367
33393
  {
33368
33394
  "id": "openwop.it.v2-webhook-durable-delivery.an-exhausted-delivery-is-dead-lettered-never-dropped",
33369
33395
  "file": "v2-webhook-durable-delivery.test.ts",
33370
- "line": 360,
33396
+ "line": 376,
33371
33397
  "title": "an exhausted delivery is dead-lettered, never dropped",
33372
33398
  "explicitId": "openwop.requirement.0173.webhook-durable-delivery.dead-letter",
33373
33399
  "citations": [
@@ -33401,7 +33427,7 @@
33401
33427
  {
33402
33428
  "id": "openwop.it.v2-webhook-durable-delivery.the-dead-letter-read-is-served-and-its-records-carry-no-payload",
33403
33429
  "file": "v2-webhook-durable-delivery.test.ts",
33404
- "line": 454,
33430
+ "line": 470,
33405
33431
  "title": "the dead-letter read is served and its records carry no payload",
33406
33432
  "explicitId": "openwop.requirement.0188.dead-letter-read",
33407
33433
  "citations": [
@@ -33414,7 +33440,7 @@
33414
33440
  {
33415
33441
  "id": "openwop.it.v2-webhook-durable-delivery.after-unregister-the-dead-letter-read-answers-404-not-found-as-for-a-subscriptio",
33416
33442
  "file": "v2-webhook-durable-delivery.test.ts",
33417
- "line": 475,
33443
+ "line": 491,
33418
33444
  "title": "after unregister, the dead-letter read answers 404 not_found, as for a subscription that never existed",
33419
33445
  "explicitId": "openwop.requirement.0217.dead-letter-read-after-unregister",
33420
33446
  "citations": [
@@ -33441,7 +33467,7 @@
33441
33467
  {
33442
33468
  "id": "openwop.it.v2-webhook-durable-delivery.a-dead-letter-record-carries-no-delivered-payload",
33443
33469
  "file": "v2-webhook-durable-delivery.test.ts",
33444
- "line": 508,
33470
+ "line": 524,
33445
33471
  "title": "a dead-letter record carries no delivered payload",
33446
33472
  "explicitId": "openwop.requirement.0188.dead-letter-content-free",
33447
33473
  "citations": [
@@ -33478,7 +33504,7 @@
33478
33504
  {
33479
33505
  "id": "openwop.it.v2-webhook-endpoint-verification.no-echo-wrong-echo-and-a-redirect-are-refused-and-persist-nothing-an-echo-is-acc",
33480
33506
  "file": "v2-webhook-endpoint-verification.test.ts",
33481
- "line": 64,
33507
+ "line": 68,
33482
33508
  "title": "no-echo, wrong-echo and a redirect are refused and persist nothing; an echo is accepted",
33483
33509
  "explicitId": "openwop.requirement.0201.endpoint-verification",
33484
33510
  "citations": [
@@ -33497,6 +33523,11 @@
33497
33523
  "requirement": null,
33498
33524
  "interpolated": true
33499
33525
  },
33526
+ {
33527
+ "section": "RFC 0201 §D.14",
33528
+ "requirement": null,
33529
+ "interpolated": true
33530
+ },
33500
33531
  {
33501
33532
  "section": "RFC 0201 §D.13–§D.14",
33502
33533
  "requirement": null,
@@ -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.42.2",
5
- "corpusTag": "v2.42.2",
4
+ "version": "2.42.3",
5
+ "corpusTag": "v2.42.3",
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": "0777cffd47bdaad6482bc910064a42f793d74ae8e8921c84f498b1670f254f21",
13
- "api/v2/asyncapi.yaml": "b87e6d39591f6967c6a6154657863c4573dd9833e7b6b8f4ca3472e9d92c9352",
14
- "api/v2/openapi.yaml": "12ee6cf90544a49863f9238ba8ddd2d1333f913f26e122819e399ec65e6b682b",
12
+ "api/seams-v2.yaml": "6824903e9a7d33a6913878d46ad0a1e25746b596c6ab2ae7aa6c31d72babfa63",
13
+ "api/v2/asyncapi.yaml": "45ee1825660a8d6336e145042e6e6e7f69d63d823fb7a6af8f5e9c46ec93c45f",
14
+ "api/v2/openapi.yaml": "d45f0d99b6d57b45a30640583c0aa828163ba778768dd58835246ed5dad212b1",
15
15
  "api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
16
16
  "schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
17
17
  "schemas/a2a-task-state.schema.json": "75d5049dea8bd873ff0e7546f1c60c8d36c219bec7084264360be54a8be30ae1",
@@ -204,13 +204,13 @@
204
204
  "schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
205
205
  "spec/v1/alias-detectors.json": "2401fcb1c18cdd688c018b3d716ae6bca85c5e356220ee2b793bd9d81872412d",
206
206
  "spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
207
- "spec/v1/core-standard-manifest.json": "c38981a02d063f68cffd5d22cf8e10390a3b12af4271e2819dc753711fc9616f",
207
+ "spec/v1/core-standard-manifest.json": "4271942ecca7c7455cb6318572bdb5a53ddc34f8d8914e2a68c64b091d131a0f",
208
208
  "spec/v1/deprecations.json": "2d03f4729810280147ea08c630ea65434f2dc5370567d0aac41c3595f8337d40",
209
209
  "spec/v1/deprecations.schema.json": "3e393c405d2a41b467d8c5e3c468549078df1ce6a6d2588fc488097b95d9b55b",
210
210
  "spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
211
211
  "spec/v1/event-codemap.schema.json": "d05933b2e88103aff51a2f774df97b9bda065e0fc88dbd9b114cc47a32189174",
212
212
  "spec/v1/extensions.json": "79a60754aa16cbdbdb604f8e5c00038af535a1690a4d07bd5ff7c38f0256a7bc",
213
- "spec/v1/gaps.json": "e99603a9c0e14f69fc3dd5ff6269f75a7b25aa2eb28051f9e2e1758fd08265b9",
213
+ "spec/v1/gaps.json": "361fb29a27dc933c2462a64f32a5a6a6ecf8e6d9fb46039550cb5cfa2eb0ab13",
214
214
  "spec/v1/gaps.schema.json": "8fd83259f556553c9df0f53e7a82ca8c2a4771d471197f69b4e2ae0ceeacfd66",
215
215
  "spec/v1/migrations.json": "8839497d6a4830b53fa7867014a8336f0d725ef2346352e54fb580b7947897af",
216
216
  "spec/v1/migrations.schema.json": "886779aa6c22e646db097f5df210adb018a4dd14a7b815465a18c8a7056c8f72",
@@ -294,9 +294,9 @@
294
294
  "spec/v2/path-manifest.json": "17298a245c5da9a70aa7ab0b7f2db4277e06209d6fcdd7187a8b4cea179c08f3",
295
295
  "spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
296
296
  "spec/v2/profiles.json": "0636f19fceae625390003a347e70ef4797d84766b5c24ce8a02cea52aadebca4",
297
- "spec/v2/release.json": "f8aeec2581bb0b934b2667ac72680f2caf6259b2ca7312e716598e9302573966",
297
+ "spec/v2/release.json": "1b3dde14cbaf3cfc4eab1ae9a461aab6d357c9c4cda511047087c0b57b400055",
298
298
  "spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624",
299
- "spec/v2/surface-baseline.json": "e2ce7ec18f217f08dd13d6ff1895ea4030b0a76965b774689fb6b52bb20b745a"
299
+ "spec/v2/surface-baseline.json": "dc52941e7f3e19da5151bb5c994af9b18b137a5a3b34ee0b63bc9cb2789dc52a"
300
300
  },
301
- "corpusCommit": "10e78e04bc0dac6cb93a0ef00eda5dc7547b4ee8"
301
+ "corpusCommit": "d310f781e2dcb00d670e26fc23668c2d61548304"
302
302
  }
@@ -42,6 +42,7 @@ import { softSkip } from '../lib/soft-skip.js';
42
42
  import { req } from '../lib/requirement-ids.js';
43
43
  import { targetMajor } from '../lib/seams.js';
44
44
  import { startModalReceiver } from '../lib/webhook-receiver.js';
45
+ import { deliveriesFor } from '../lib/standard-webhooks.js';
45
46
  import { projectBoundId } from '../lib/bound-id.js';
46
47
 
47
48
  const DOC = 'spec/v1/auth.md §"Onward hops"; spec/v2/core/security-defaults.md §"Onward hops" (RFC 0200 §E)';
@@ -93,9 +94,19 @@ describe('RFC 0200 §E — inbound-credential-no-passthrough (the caller\'s cred
93
94
  });
94
95
  if (created.status !== 201) return softSkip('blocked', `could not start ${FIXTURE} (${created.status}) — no run, no delivery, nothing to inspect`);
95
96
 
96
- const delivered = await waitFor(() => rx.hits.some((h) => !h.verification), WAIT_MS);
97
+ // Unfailable-leg audit, 2026-09-26: the wait accepted ANY non-verification
98
+ // hit, so a foreign run.completed (a parallel scenario's run) could end it
99
+ // before THIS run's delivery — the one carrying the canaries' run — arrived.
100
+ // The wait now requires a delivery for this run; every hit is still scanned.
101
+ const runId = (created.json as { runId?: unknown } | null)?.runId;
102
+ if (typeof runId !== 'string' || runId === '') return softSkip('blocked', `POST ${runsPath()} answered 201 without a runId — this run's delivery cannot be told apart from another run's`);
103
+ const ours = (): number => (subscriptionId !== null
104
+ ? deliveriesFor(rx.hits, subscriptionId, runId)
105
+ : rx.hits.filter((h) => { if (h.verification) return false; try { return (JSON.parse(h.body) as { runId?: unknown }).runId === runId; } catch { return false; } })
106
+ ).length;
107
+ const delivered = await waitFor(() => ours() > 0, WAIT_MS);
97
108
  // A hop that never happened leaks nothing: this is `blocked`, not a pass.
98
- if (!delivered) return softSkip('blocked', `no delivery reached the suite-owned receiver within ${WAIT_MS}ms — an unmade hop cannot witness that a credential did not cross it`);
109
+ if (!delivered) return softSkip('blocked', `no delivery for run ${runId} reached the suite-owned receiver within ${WAIT_MS}ms (${rx.hits.filter((h) => !h.verification).length} other capture(s)) — an unmade hop cannot witness that a credential did not cross it`);
99
110
 
100
111
  const canaries: Array<[string, string]> = [['the suite\'s own API key', env.apiKey], ['the Cookie canary', c1], ['the Proxy-Authorization canary', c2]];
101
112
  const found: string[] = [];
@@ -65,9 +65,16 @@ describe('v2-advertised-fixtures-exist (RFC 0168 §C.3)', () => {
65
65
  const ids = advertisedIds(doc);
66
66
  if (ids.length === 0) return softSkip('inapplicable', 'the host advertises no fixtures[]');
67
67
 
68
- // Deterministic sample: the first N by sort order, so a failure is
68
+ // Deterministic sample spread across the sorted list, so a failure is
69
69
  // reproducible and a host cannot pass by luck of ordering.
70
- const sample = [...ids].sort().slice(0, SAMPLE);
70
+ // Unfailable-leg audit, 2026-09-26: the sample was the first N by sort
71
+ // order, so an unseeded fixture sorting 6th or later was never attempted.
72
+ // Indices round(i*(n-1)/(SAMPLE-1)), deduped, reach both ends and between.
73
+ const sorted = [...new Set(ids)].sort();
74
+ const picks = sorted.length <= SAMPLE
75
+ ? sorted.map((_, i) => i)
76
+ : Array.from({ length: SAMPLE }, (_, i) => Math.round((i * (sorted.length - 1)) / (SAMPLE - 1)));
77
+ const sample = [...new Set(picks)].map((i) => sorted[i]!);
71
78
  const unreachable: string[] = [];
72
79
  let attempted = 0;
73
80
  for (const id of sample) {
@@ -79,7 +86,14 @@ describe('v2-advertised-fixtures-exist (RFC 0168 §C.3)', () => {
79
86
  // 201 is the claim honoured. A 4xx that names the workflow as unknown is
80
87
  // the drift this leg exists to catch; any other status is a different
81
88
  // problem and is not judged here.
82
- if (res.status !== 201) unreachable.push(`${id} → ${res.status}`);
89
+ // The comment above always said only an unknown-workflow refusal is
90
+ // judged, but the code counted ANY non-201. The 2026-09-26 audit's wider
91
+ // sample then hit fixtures that need an Idempotency-Key or specific
92
+ // inputs (a validation 4xx) and failed a conforming host. v2 has no
93
+ // dedicated unknown-workflow code, so the signal is 404, or a 4xx whose
94
+ // details.field names workflowId.
95
+ const field = (res.json as { details?: { field?: unknown } } | null)?.details?.field;
96
+ if (res.status === 404 || (res.status >= 400 && res.status < 500 && field === 'workflowId')) unreachable.push(`${id} → ${res.status}`);
83
97
  }
84
98
  if (attempted === 0) return softSkip('blocked', 'no advertised fixture could be attempted');
85
99
  expect(
@@ -118,12 +118,18 @@ describe('RFC 0205 §A.3 — a url Part does not resolve beyond the caller\'s au
118
118
  const tenantB = process.env['OPENWOP_TEST_TENANT_B_API_KEY'];
119
119
  if (!tenantB) return softSkip('blocked', 'the artifact carries a url Part but OPENWOP_TEST_TENANT_B_API_KEY is not supplied — the second-tenant leg cannot run');
120
120
  const base = loadEnv().baseUrl;
121
+ // unfailable-leg audit, 2026-09-26: the probes use redirect:'manual' and
122
+ // only failed on a 2xx, so a url that answered 302 to a public pre-signed
123
+ // object-store URL — readable by tenant B and by anyone — passed. A 3xx is
124
+ // the host resolving the url for that caller; the only answers that do not
125
+ // resolve it are a refusal (401/403) or a non-disclosing 404.
126
+ const REFUSED = new Set([401, 403, 404]);
121
127
  for (const u of urls) {
122
128
  const abs = new URL(u, base).toString();
123
129
  const asB = await fetch(abs, { headers: { Authorization: `Bearer ${tenantB}` }, redirect: 'manual' });
124
- expect(asB.status >= 200 && asB.status < 300, req('openwop.requirement.0205.artifact-url-part-scoped', 'RFC 0205 §A.3', `${abs} MUST NOT answer 2xx to tenant B's credential (got ${asB.status})`)).toBe(false);
130
+ expect(REFUSED.has(asB.status), req('openwop.requirement.0205.artifact-url-part-scoped', 'RFC 0205 §A.3', `${abs} MUST NOT resolve for tenant B's credential: expected 401/403/404, got ${asB.status}${asB.status >= 300 && asB.status < 400 ? ` (a redirect to ${asB.headers.get('location') ?? 'no Location'} resolves the url)` : ''}`)).toBe(true);
125
131
  const anon = await fetch(abs, { redirect: 'manual' });
126
- expect(anon.status >= 200 && anon.status < 300, req('openwop.requirement.0205.artifact-url-part-scoped', 'RFC 0205 §A.3', `${abs} MUST NOT answer 2xx with no credential (got ${anon.status})`)).toBe(false);
132
+ expect(REFUSED.has(anon.status), req('openwop.requirement.0205.artifact-url-part-scoped', 'RFC 0205 §A.3', `${abs} MUST NOT resolve with no credential: expected 401/403/404, got ${anon.status}${anon.status >= 300 && anon.status < 400 ? ` (a redirect to ${anon.headers.get('location') ?? 'no Location'} resolves the url)` : ''}`)).toBe(true);
127
133
  }
128
134
  });
129
135
  });