@openwop/openwop-conformance 2.42.8 → 2.43.0

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,26 @@
1
1
  # `@openwop/openwop-conformance` Changelog
2
2
 
3
+ ## [2.43.0] — 2026-09-28 — four RFCs: approval reject, registry operations, the audit anomaly shape, and a v2 home for audit-log integrity
4
+
5
+ - **RFC 0222: v2 registry lifecycle and signing keys, witnessed against a registry.** New `v2-registry-lifecycle.test.ts` (target major 2). It reads a registry, not the host: it runs when `OPENWOP_REGISTRY_URL` names one or `OPENWOP_TEST_PUBLIC_REGISTRY=true` names `packs.openwop.dev`, records `inapplicable` otherwise, and resolves every path through `.well-known` `endpoints.v2`. Leg 1 (`openwop.requirement.0222.yanked-version-lifecycle`): every pack index agrees with its version manifests on `yanked` and `versionDeprecated`, never names a yanked version `latest` while an unyanked one exists, and a yanked version's tarball and signature stay served. Leg 2 (`openwop.requirement.0222.signing-keys-cover-served-versions`): every served version names a key `signingKeys[]` still lists, whose `permittedNamespaces` admit it, and its integrity and signature over the in-tarball `pack.json` verify, whatever the key's `status`. Passes on `packs.openwop.dev` (198 versions; leg 1 `partial-witness`, no v2 version is yanked) and on a local tree with a yanked version; fails on seven sabotages, including the registry's previous `build-index.mjs`.
6
+ - **RFC 0220 `Active`: six extension families are `Stable`.** Packed `@openwop/spec-artifacts` files change (`spec/v2/declaration.json` `technical: stable` for chat, entities, kanban, knowledge, restTransport and webResearch; their `spec/v2/ext/*/README.md` read `Status: Stable.`). No scenario change.
7
+ - **RFC 0223: a rejected approval gate fails closed, and the failure is routable.** New `v2-approval-reject-disposition.test.ts` (target major 2, gated on the `interrupt` family). Four rows, so a host fails only what it misses. `0223.reject-fails-run`: `conformance-approval` resolved `reject` ends `failed`, with `approval_rejected` on the snapshot and on `run.failed`. `0223.reject-recorded`: `interrupt.resolved` carries `action: "reject"` and `decision: "rejected"`. `0223.reject-fails-node`: the gate's `node.failed` carries `approval_rejected` with `retryable: false`, and `run.failed.failedNodeId` is `gate`. `0223.quorum-reject-fails-run` (gated on `conformance-interrupt-quorum`, `inapplicable` when not advertised): one reject of three under `majority` leaves the run waiting and emits no `interrupt.resolved`, and the second fails the run with `approval_rejected`. Measured before release: the v2 reference host fails only `reject-recorded` (no `action`, fixed in its follow-up); openwop-app fails `reject-recorded` and `reject-fails-node`; MyndHyve fails on its `APPROVAL_REJECTED` spelling. The major-1 `interrupt-quorum-resolution` is unchanged: v1 registers no `approval_rejected`.
8
+ - **Coherence: `v2-error-registry-prose-parity` gains an RFC 0223 leg** (server-free, `0223.code-registered`): the registry row is a 422 that is not retriable, `interrupt.md` §Rejection names it, and `ApprovalData.onTimeout` declares no JSON-Schema `default`.
9
+ - **`fixtures.md`**: `conformance-approval` states its major-2 outcome after a reject; the quorum catalog row names the code.
10
+ - **RFC 0218 §C: new server-free `audit-anomaly-shape.test.ts` (both majors, requirement id `openwop.requirement.0218.anomaly-shape`).** It validates one anomaly of every `kind` against `audit-verify-result.schema.json` (v1 and v2), plus a legacy kind-less chain-break. It refuses the reference hosts' pre-§C `{ atSequence, kind, detail }`, a checkpoint kind without `checkpoint`, a `chain-break` without its hashes, mixed entries and an unknown `kind`. It also refuses `chainValid: true` beside an anomaly and `chainValid: false` with none. Five schema sabotages each turn a leg red.
11
+ - **`v2-surface-monotone-gate` gains four legs for the new-value conditional licence.** A `required` under a `then` selecting only new enum members passes and is reported. An old-value, mixed or unpinned selector is refused, and so is an unconditional `required` beside a licensed one.
12
+ - **RFC 0224: `audit-log-integrity` and `audit-checkpoint-signature` run at both majors.** At major 2 they gate on the new `auditLogIntegrity` family record (absent ⇒ `inapplicable` before any assertion) and call `/audit/verify`. Major 1 keeps its profile gate and `/v1/audit/verify`. Both resolve through the new `src/lib/auditIntegrity.ts`, and both are now in `BOTH_MAJORS`. At major 2 the shape leg validates the record against the generated family schema and parses the key as Ed25519 SPKI, and the verify leg validates against the v2 body schema. New cadence leg at both majors (`openwop.requirement.0224.checkpoint-cadence`): checkpoints are listed ascending, with no gap over the advertised `checkpointIntervalEntries`; it is `inapplicable` before a first checkpoint. Sabotage on the v2 reference host: signing the checkpoint object reddens the RFC 0218 signature row, and dropping a checkpoint from the verify body reddens the cadence row. The SQLite host at major 1 is executed-pass on both files.
13
+ - **`byok-roundtrip` records `blocked` instead of an unclassified return, so one unwitnessable floor no longer voids a whole cut.** The file was wrapped in `describe.skipIf(SKIP_NO_FIXTURE)`: on a host that does not advertise the `openwop-smoke-byok-roundtrip` fixture, every test was skipped before it could record a reason. A file that skips everything with no reason is an unclassified return (RFC 0148 §A), and an unclassified FLOOR row (`openwop.floor.byok-roundtrip`, `openwop-secrets`) in a claimed profile rejects the certification outright. Measured on openwop-app's 2026-09-28 post-deploy major-1 cut: 2,116 rows, `certified: none`, and that file the only unclassified row. The previous published cut (`f270cfb6`) carried the identical row. Each test now gates itself: with no fixture advertised it records `blocked`, naming the missing fixture and the seam behind it. A production posture that keeps `conformance.secret.echo` off therefore leaves `openwop-secrets` uncertified, which is honest, since the floor was not witnessed, and ONLY that profile: the cut's other claims certify. The first test's 404/422 branch moves from `inapplicable` to `blocked` in the same change. On a floor, `inapplicable` resolves to an `executed-pass` partial witness, so a host whose canary run cannot start was being credited with a floor it never demonstrated. The two sibling tests already answered the same condition with `blocked`. Run against app.openwop.dev (fixture not advertised): all three rows and `openwop.floor.byok-roundtrip` resolve `blocked`, with no unclassified return. `requirements.json` is regenerated for three moved line numbers only; no id or title changed. The other 31 files gated on a bare `SKIP_NO_FIXTURE` have the same shape and were not touched: none is a floor, so their unclassified rows do not decide a certification. They are left for a sweep rather than folded in here.
14
+ - **Suite `2.43.0`**, a minor: a new registry row changes the packed `@openwop/spec-artifacts` tree (`spec/v2/errors.json` and the generated `schemas/v2/error-envelope.schema.json`), and 2.42.9 is published. `@openwop/spec-artifacts` moves in lockstep at the same exact pin.
15
+
16
+ ## [2.42.9] — 2026-09-28 — a v2 delivery's workspaceId is checked, and two test-isolation and SHOULD-strength fixes
17
+
18
+ - **Scenario files that share a mock-AI node no longer race.** The mock-AI program hook is keyed by node id, and a node id belongs to a fixture. `envelope-completion-distinguishes-truncation` drives both envelope fixtures that `envelope-truncated` and `envelope-retry-attempted` also drive, so under the default parallel run one file could overwrite another's program. New `lib/mock-node-lock.ts` holds a cross-process lock per node id for the whole file, so files sharing a node run one after another. Serial runs (`--certify`, openwop-app's harness) are unchanged.
19
+ - **The RFC 0033 §B truncation budget legs compare real attempts and record the SHOULD.** Both legs asserted "retry budget > 50" (the fixture's value) as a MUST. §B makes the retry a MAY and the larger budget a SHOULD, so the old legs failed a conforming host that retried at the same budget and passed a host that ignored the fixture's `maxTokens`. They now read the first and retry budgets through the `dispatch-budgets` hook. A larger retry budget passes, and an unmet SHOULD is recorded as `inapplicable` with the observed values. An unserved hook is a partial witness. On openwop-app the rows are identical before and after, and the truncation leg passes with a real comparison.
20
+ - **`v2-webhook-delivery-shape` checks that `workspaceId` is present exactly when the run has a workspace.** `webhooks.md` §Delivery requires it and forbids substituting a value, but the leg only validated the schema, which cannot say "exactly when". It now compares the delivered `workspaceId` with the owner echo on the event. The v2 reference host sent `"default"` and fails without openwop-examples #99 (fixed there); openwop-app already conformed.
21
+ - **`--help` says `--bundle-version` defaults to 3** (#1693). The code has defaulted to 3 since 2.0.0-rc.1 and the deprecation note for `2` already said so; the help text still read `Default 2`.
22
+ - **Suite `2.42.9`**. `@openwop/spec-artifacts` moves in lockstep at the same exact pin.
23
+
3
24
  ## [2.42.8] — 2026-09-27 — RFC 0221, a closed audit schema again, and a dispatch race fixed
4
25
 
5
26
  - **`v2-preferred-version-default`: a header-less request on an unversioned operation path is served major 2.** This follows a Class 3 correction to `versioning.md` §1.3: the `preferredVersion` default applies to `/.well-known/openwop` only. The new leg reads `OpenWOP-Version` on a header-less `GET /runs/{unknown}`. It passes on the v2 reference host and fails when the host applies `preferredVersion` to every path.
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.8 @openwop/spec-artifacts@2.42.8
14
+ npm install --legacy-peer-deps @openwop/openwop-conformance@2.43.0 @openwop/spec-artifacts@2.43.0
15
15
  # or run without install:
16
16
  npx @openwop/openwop-conformance --base-url https://api.example.com --api-key hk_test_...
17
17
  ```
@@ -135,7 +135,7 @@ Exit code is non-zero on any failed assertion. `--certify` distinguishes: `0`
135
135
 
136
136
  ## What's Covered
137
137
 
138
- The current suite has 567 scenario files under `src/scenarios/`.
138
+ The current suite has 570 scenario files under `src/scenarios/`.
139
139
  - 2026-09-23 (suite 2.37.0 cycle, RFC 0213): NEW `v2-sse-last-event-id-cursor.test.ts` (a `Last-Event-ID` past the log is an exclusive cursor; a malformed id, when refused, is `400 validation_error`; the cursor never changes the answer for an unknown or foreign-tenant run — public test of `event-cursor-after-authorization`), `v2-idempotency-in-flight.test.ts` (five concurrent same-key creates yield one run; each loser is a marked replay or `409 idempotency_in_flight` with no retry timing in `details`; `partial-witness` when no loser was refused in flight) and `v2-interrupt-resolve-terminal.test.ts` (a run-scoped resolve after cancel or completion is `409 interrupt_already_resolved`, never `interrupt_cancelled`). All three sit off the core-standard floor until measured on the three bundle hosts.
140
140
  - 2026-09-03 (suite `1.157.0 -> 1.158.0`, gap G17): NEW `idempotency-concurrent-claim.test.ts` — drives the new `host-sample-test-seams.md` §25 concurrent duplicate-delivery seam for the RFC 0150 §B / `idempotency.md` §"Concurrent duplicates (Layer 2)" atomic-claim MUST, which is unconditional and had no witness of any kind. Asserts every executor mints the SAME `logicalInvocationId` **before** asserting `delivered === 1` — without the identity check a host passes by minting different ids and never colliding, one effect because nothing raced. Not profile-gated and so not opt-out-able (the obligation is unconditional); an unmounted seam records `blocked`, which is not certifiable. Graduates `layer2-invocation-claim-atomic` reference-impl -> protocol.
141
141
  - 2026-08-19 (suite `1.137.0 → 1.138.0`): NEW `durability-poison-exhaustion.test.ts` — RFC 0158 §C.8, the FIRST row of that RFC's conformance table to land. Asserts what `failure-path.test.ts` cannot: not just that deterministically failing work reaches terminal, but that attempts STOP — counted on the log, re-counted after a scaled quiet window, asserted unchanged. A host still redelivering records more. Seam-gated on the existing event-log seam (`blocked` = unobservable, not unmet) and outside every profile floor.
@@ -481,7 +481,7 @@ Server-required (added in 1.7.0):
481
481
  | ------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
482
482
  | **Redaction** | [`capabilities.md`](../spec/v1/capabilities.md) §"Secrets" + NFR-7 + §"aiProviders" | Vendor-neutral assertions that the server doesn't leak secret material. Three scenario groups: (a) discovery shape contract — `secrets` + `aiProviders` advertisements are well-formed regardless of `secrets.supported`; when `supported === true`, scopes MUST be non-empty + `resolution === 'host-managed'`; `byok ⊆ supported`. (b) bearer-token redaction — invalid Bearer canary in `Authorization` header is not echoed in the 401 response body. (c) credentialRef echo control — gated on `secrets.supported === true`; canary planted in `configurable.ai.credentialRef` MUST NOT appear in any RunEvent payload (poll-based capture; transport-agnostic). Uses runtime-built canary fixtures (`lib/canaries.ts`) that defeat static secret scanners. 6 scenarios. |
483
483
 
484
- Current source tree: 567 scenario files. Use [`coverage.md`](./coverage.md) for current grade/gap tracking.
484
+ Current source tree: 570 scenario files. Use [`coverage.md`](./coverage.md) for current grade/gap tracking.
485
485
 
486
486
  ## Remaining Gaps
487
487
 
package/coverage.md CHANGED
@@ -37,7 +37,7 @@
37
37
  | Per-workflow `configurableSchema` | `configurable-schema.test.ts` | A− | Negative validation + positive accepted-overlay + `GET /v1/workflows/{id}` schema-surface assertion all covered (CF-7 close-out, 2026-05-15). Grade moves C+ → A−. |
38
38
  | Append-reducer ordering | `append-ordering.test.ts` | B | Intra-engine sequence-order check; remaining: cross-engine ordering under a multi-engine fixture. |
39
39
  | Webhook signature algorithms | `webhook-sig-algorithm.test.ts`, `webhook-signed-delivery.test.ts`, `webhook-negative.test.ts`, `webhook-receiver-adversarial.test.ts` | A | Forward direction: discovery shape + end-to-end signed delivery with HMAC verification (`webhook-signed-delivery`); negative paths (SSRF guard / validation / unknown-unregister via `webhook-negative`). Reverse direction (CF-5 close-out 2026-05-15): `webhook-receiver-adversarial.test.ts` covers 6 paths — positive control + tampered body + tampered HMAC + stale timestamp + replayed signature + wrong algorithm + malformed signature header. Reference receiver implementation at `conformance/src/lib/webhook-receiver.ts` mirrors the SDK's `verifyWebhookSignature` helper. |
40
- | Audit-log integrity profile | `audit-log-integrity.test.ts` | A | Profile claim + `/v1/audit/verify` shape; checkpoint-signature verification under the advertised key (`audit-checkpoint-signature.test.ts`, RFC 0218) and the pinned preimage vectors (`audit-checkpoint-vectors.test.ts`); chain re-walk with `chainValid` and `checkpointsValid` bits. Tamper detection covered host-internally at `openwop-examples:examples/hosts/sqlite/test/audit-tamper.test.ts` (mutate-entry + forge-signature paths). CF-11 close-out 2026-05-15: cross-host checkpoint export via `openwop-examples:examples/hosts/postgres/src/audit-export.ts` + standalone verifier at `scripts/verify-audit-checkpoints.mjs`; `openwop-examples:examples/hosts/postgres/test/audit-checkpoint-export.test.ts` verifies 7 paths (positive + tampered-signature + non-monotonic-atSequence). The standalone verifier is additionally regression-guarded in `openwop:check` (step 7) against the committed `conformance/audit-export-samples/{valid,tampered}.json` bundles — accept-valid (exit 0) + reject-tampered (exit 1) — so a verifier regression is caught at the spec gate, not only in the postgres host's own test. |
40
+ | Audit-log integrity profile | `audit-log-integrity.test.ts` | A | Profile claim + `/v1/audit/verify` shape; checkpoint-signature verification under the advertised key (`audit-checkpoint-signature.test.ts`, RFC 0218) and the pinned preimage vectors (`audit-checkpoint-vectors.test.ts`); chain re-walk with `chainValid` and `checkpointsValid` bits; the anomaly shape per `kind` (RFC 0218 §C) server-free in `audit-anomaly-shape.test.ts`. Tamper detection covered host-internally at `openwop-examples:examples/hosts/sqlite/test/audit-tamper.test.ts` (mutate-entry + forge-signature paths). CF-11 close-out 2026-05-15: cross-host checkpoint export via `openwop-examples:examples/hosts/postgres/src/audit-export.ts` + standalone verifier at `scripts/verify-audit-checkpoints.mjs`; `openwop-examples:examples/hosts/postgres/test/audit-checkpoint-export.test.ts` verifies 7 paths (positive + tampered-signature + non-monotonic-atSequence). The standalone verifier is additionally regression-guarded in `openwop:check` (step 7) against the committed `conformance/audit-export-samples/{valid,tampered}.json` bundles — accept-valid (exit 0) + reject-tampered (exit 1) — so a verifier regression is caught at the spec gate, not only in the postgres host's own test. |
41
41
  | Multi-region idempotency capability | `multi-region-idempotency.test.ts` | C | Discovery enum coverage; remaining: cross-region partition simulation (requires multi-region harness). |
42
42
  | Public hosted registry (`packs.openwop.dev`) | `registry-public.test.ts` | A− | Discovery, index, and per-pack manifest assertions against the public registry. Opt-in via `OPENWOP_TEST_PUBLIC_REGISTRY=true` so default conformance runs don't depend on outbound `packs.openwop.dev` reachability. Remaining: tarball-fetch + signature-verify roundtrip. |
43
43
  | Workflow-chain packs (RFC 0013 — `spec/v1/workflow-chain-packs.md`) | `workflow-chain-pack-manifest-validation.test.ts`, `workflow-chain-pack-signature-verification.test.ts`, `workflow-chain-expansion.test.ts`, `workflow-chain-unresolvable-typeid.test.ts`, `workflow-chain-host-expansion.test.ts` | A (4 server-free + live-host harness-witnessed by openwop-app #1298, 6/6) | RFC 0013 promoted Draft → Active → Accepted 2026-05-18 on the 4 server-free scenarios (pure-library algorithm). **Erratum (2026-07-05):** the live-host leg (`workflow-chain-host-expansion.test.ts`, `POST /v1/host/sample/workflow-chain:expand`, 6 cases) actually **soft-skips against every host** — no reference host serves the seam and the `vendor.openwop.workflow-chain-sample` fixture was never bundled here — so it is re-gated onto the OPTIONAL `capabilities.workflowChainPacks.hostExpansionSeam` sub-flag (a test-harness advertisement), decoupled from the semantic `supported` claim so it no longer blocks the RFC 0124 `deferredParameters` flip. **A-lite (2026-07-05, suite `1.52.0`):** the `vendor.openwop.workflow-chain-sample` pack is now bundled at `fixtures/pack-manifests/workflow-chain-sample.pack.json` and the scenario **loads** it + derives expected output from the `expandChain()` reference library (no hardcode) — and a host wiring the `/…:expand` route + advertising `hostExpansionSeam` witnesses it. **Witnessed 2026-07-05:** openwop-app (#1298) serves the seam-gated route (404 prod, RFC 0117 posture) resolving the bundled `@1.52.0` fixture through its `expandChain()` → the 6 positive+negative legs run **6/6 non-vacuously** under `OPENWOP_REQUIRE_BEHAVIOR`, checked against the reference library via the host's returned `expansionId` — harness-tier (in-process, test-only seam; RFC 0035/0118 evidence tier). Closes RFC 0013's Phase-3 host-expansion acceptance item. RFC 0124's deferred path is separately covered by `workflow-chain-deferred-parameters.test.ts` (openwop-app `/chain/deferred-expand` seam #1292, gated legs 12/0). The matching spec doc `workflow-chain-packs.md` remains at `DRAFT v1.x` pending Phase B/C closure (parameter schema validation + cross-host expansion equivalence). |
package/dist/cli.js CHANGED
@@ -60,9 +60,8 @@ function parseArgs(argv) {
60
60
  let impl;
61
61
  let implVersion;
62
62
  let certify;
63
- // Suite 1.152.0: bundle v2 (RFC 0148) is the default. v1 stays reachable via
64
- // `--bundle-version 1` through the RFC 0148 migration window (ends
65
- // 2026-11-10) and is removed at v2.0 (spec/v1/deprecations.json).
63
+ // Suite 2.0.0-rc.1: bundle v3 (RFC 0168 §E) is the default. v2 stays reachable
64
+ // via `--bundle-version 2`, deprecated (RFC 0168 §E.3); v1 is gone.
66
65
  let bundleVersion = '3';
67
66
  let hostBuild;
68
67
  let evidenceTier = 'self';
@@ -265,7 +264,7 @@ Certification (RFC 0089):
265
264
  --signing-key <pem> v3: Ed25519 private key (PKCS8 PEM) that signs the bundle, or OPENWOP_BUNDLE_SIGNING_KEY.
266
265
  --signing-key-id <id> v3: the keyId the host publishes for that key, or OPENWOP_BUNDLE_SIGNING_KEY_ID.
267
266
  --evidence-tier <t> v3: self | steward | independent (independent needs --verifier-key/--verifier-key-id).
268
- --bundle-version <2|3> Certification bundle format. Default 2 (corrected 2026-09-03, RFC 0168 §E.4; the text said 1 while the code set 2). Version 2 (RFC 0148
267
+ --bundle-version <2|3> Certification bundle format. Default 3 (since suite 2.0.0-rc.1; 2 is DEPRECATED, RFC 0168 §E.3). Version 2 (RFC 0148
269
268
  §C) records per-requirement DISPOSITIONS instead of pass/fail/skip
270
269
  file lists, so "we could not check" stops being indistinguishable
271
270
  from "checked and it holds". See the note it prints.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "package": "@openwop/spec-artifacts",
3
- "version": "2.42.8",
4
- "stampSha256": "16b28d93516147ef6c72fd88e10bed168175639ab4c3feaa92859242e93ba00b"
3
+ "version": "2.43.0",
4
+ "stampSha256": "be64f880013ebf7f3327443eafa05d0e5e9eb993843d745981cc72bf18313133"
5
5
  }
package/fixtures.md CHANGED
@@ -61,7 +61,7 @@ All fixtures MUST advertise:
61
61
  | Prompt End-to-End | `conformance-prompt-end-to-end` | RFC 0027 + RFC 0029 end-to-end. Single `mock-ai` node with `config.systemPromptRef` set; host MUST emit `agent.promptResolved` + `prompt.composed` events during dispatch, then complete. Capability-gated on `capabilities.prompts.supported`. | `completed` | ≤ 10s |
62
62
  | Prompt All Four Kinds | `conformance-prompt-all-four-kinds` | RFC 0027 §A four-kind dispatch coverage with a MULTI-ENTRY `fewShotPromptRefs[]` array. Single `mock-ai` node with one ref per singular-kind slot (`systemPromptRef`, `userPromptRef`, `schemaHintPromptRef`) + two distinct templateIds in `fewShotPromptRefs[]`; host MUST emit 5 `agent.promptResolved` events (one per slot) AND 5 `prompt.composed` events. Multi-entry few-shot is the regression pin for `fewShotPromptRefs[slotIndex]` per-index resolution — a host that hard-codes `[0]` would emit the same template twice in the few-shot events and fail the per-templateId assertion. Capability-gated on `capabilities.prompts.supported`. | `completed` | ≤ 10s |
63
63
  | Dispatch Loop | `conformance-dispatch-loop` | Verifies `core.dispatch` loop mechanism | `completed` | ≤ 30s |
64
- | Interrupt — Quorum | `conformance-interrupt-quorum` | Verifies `openwop-interrupt-quorum` profile (multi-approver, majority rejection) | `completed` after 3 accepts, `failed` after quorum reject | unbounded (suspends) |
64
+ | Interrupt — Quorum | `conformance-interrupt-quorum` | Verifies `openwop-interrupt-quorum` profile (multi-approver, majority rejection) | `completed` after 3 accepts, `failed` after quorum reject (major 2: `approval_rejected`) | unbounded (suspends) |
65
65
  | Interrupt — External Event | `conformance-interrupt-external-event` | Verifies `openwop-interrupt-external-event` profile (correlation-matched callback) | `completed` after matching POST, `failed` on timeout | ≤ 60s (timeoutMs configured) |
66
66
  | Interrupt — Auth Required | `conformance-interrupt-auth-required` | Verifies `openwop-interrupt-auth-required` profile (bearer-token resume only) | `completed` after bearer resolve | unbounded (suspends) |
67
67
  | Interrupt — Parent/Child Cancel | `conformance-interrupt-parent-child-cancel` + `conformance-interrupt-parent-child-cancel-child` | Verifies `openwop-interrupt-parent-child` cancel cascade | `cancelled` (both runs) | ≤ 30s |
@@ -182,6 +182,7 @@ The `messages`-mode stream fixture (AI token streaming) is covered by the determ
182
182
  4. After client POSTs `{action: 'accept'}` to `/v1/runs/{runId}/interrupt`, server emits `approval.received` and resumes.
183
183
  5. Run reaches `completed`.
184
184
  - **Terminal status (after accept)**: `completed`.
185
+ - **Terminal status (after reject, major 2)**: `failed`, with `error.code` `approval_rejected` on the snapshot and on `run.failed`, and `failedNodeId: "gate"`. The fixture has no edges, so nothing routes the failure (`spec/v2/core/interrupt.md` §Rejection, RFC 0223; `v2-approval-reject-disposition.test.ts`).
185
186
  - **Resolve schema**: `{action: "accept" | "reject"}`. Server MUST reject any other shape with 400.
186
187
 
187
188
  ### `conformance-approval-refine`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "2.42.8",
3
+ "version": "2.43.0",
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.8"
59
+ "@openwop/spec-artifacts": "2.43.0"
60
60
  }
61
61
  }