Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/2026-08-26-execution-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@dexpace/core": patch
---

Internal: execution context promotion chain and bounded store for product-spec §7 (CTX-1..20). No public API change.
24 changes: 24 additions & 0 deletions .changeset/2026-08-26-recovery-chain-primitives.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'@dexpace/core': patch
---

Add the recovery-chain primitives for product-spec §8.2 (`RECOV-1`–`RECOV-16`). No public API change.

Everything this adds lives under `packages/core/src/recovery/` plus two package-root helpers, and none of it is
re-exported from `src/index.ts` — `packages/core/etc/core.api.md` is byte-identical before and after. `patch`
rather than an empty changeset because files under `packages/` did change: the published tarball carries the
new `dist/recovery/*.js` and `dist/suppress.js`, and a consumer stepping through the package in a debugger will
see them.

What landed: `Outcome<T>` with `success`/`failure`/`fold`; `RequestRecoveryChain` and `ResponseRecoveryChain`
(defensive copies on both, concurrency-safe by construction); `dispatchWithRecovery`, whose single `try`/`catch`
wraps both the request chain and the transport hop so no throwable from either can bypass the recovery hooks;
`wrapCancellation`; and `statusMappingStep`, a thin response step over Phase 3b's unchanged `toHttpError()`.
`assertNever` joins `invariant.ts` as the codebase's first discriminated-union `default` case.

One consumer-visible-in-principle detail worth recording: `RECOV-12` pairs a step's throwable with a close
failure, which is what `SuppressedError` is for — and `SuppressedError` reached Node only in 24.0.0, against
this package's `>=20.3` floor. Rather than raise the floor and drop Node 18, 20 and 22 for one error class,
`suppress()` uses the native class where the runtime has one and returns a shape-compatible stand-in (`name`,
`error`, `suppressed`) where it does not. Code that catches one of these should read its fields, not test
`instanceof SuppressedError`.
5 changes: 5 additions & 0 deletions .changeset/2026-08-26-stage-based-pipeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@dexpace/core": patch
---

Internal: stage-based pipeline runtime for product-spec §8.1 (PIPE-1..40). No public API change.
6 changes: 3 additions & 3 deletions bunfig.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
# Scope discovery to the workspace packages. Without this, `bun test` also collects
# test/node-conformance/*.test.mjs -- the Node-only layer that exists precisely because it must NOT
# run on Bun (checkpoint 5.9). Running it under both runners would inflate the unit count and quietly
# erase the distinction the suite was added to draw. It also keeps `scripts/*.test.mjs` repo
# tooling, run via `bun run test:knowledge` (`node --test`) out of both the run and the 80% floor,
# which is a statement about `packages/core`.
# erase the distinction the suite was added to draw. It likewise keeps `scripts/*.test.mjs` -- repo
# tooling, run via `bun run test:knowledge` (`node --test`) -- out of both the run and the coverage
# floor, which is a statement about `packages/core`.
root = "packages"
coverage = true
coverageThreshold = 0.8
Expand Down
202 changes: 189 additions & 13 deletions docs/open-items.md

Large diffs are not rendered by default.

214 changes: 132 additions & 82 deletions docs/superpowers/plans/2026-07-25-phase4b-recovery-chain.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ Verification of the three Phase 4 implementation plans —
**Legend:** ✅ Planned, implemented and tested — 🚫 Not built (permanent simplification, named reason) —
⏳ Deferred (named target phase) — N/A Not applicable in this port.

**Status:** the plans are reviewed and corrected as of 2026-07-26 (see *Review findings applied*, below) but
**not yet executed**. Every ✅ means "the plan builds and tests it," not "it is on `main`."
**Status:** the plans are reviewed and corrected as of 2026-07-26 (see *Review findings applied*, below). **4b
is executed as of 2026-08-26**; 4a and 4c are not. For the §8.2 table a ✅ now means "built, tested and on the
branch"; everywhere else it still means "the plan builds and tests it."

---

Expand Down Expand Up @@ -77,7 +78,7 @@ Verification of the three Phase 4 implementation plans —
| RECOV-9 | SHOULD | Recovery steps should return a Failure rather than throw | ✅ | Satisfied structurally — both shapes are handled identically, documented rather than enforced |
| RECOV-10 | MUST | Unwrap: Success returns the response; Failure rethrows the throwable **unchanged** | ✅ | 4b Task 6 — asserted with `rejects.toBe(typedError)`, identity not message |
| RECOV-11 | MUST | Wrapping a cancellation throwable re-asserts the cancellation signal | ✅ (reframed) | 4b Task 4. An `AbortSignal` is durable once fired and the SDK never holds the caller's `AbortController`, so there is nothing to re-assert; the helper is `failure(error)` and **never throws**, which is what keeps RECOV-2 absolute. Ledgered |
| RECOV-12 | MUST | A step throwing while holding a Success closes that response exactly once, close error `suppressed`, original primary | ✅ | 4b Task 3 (`toFailureClosingSuccess`, hand-built `SuppressedError` — never `using`, whose auto-generated one inverts the priority). Close observed via the body stream's `cancel()` hook, since `Response` is frozen |
| RECOV-12 | MUST | A step throwing while holding a Success closes that response exactly once, close error `suppressed`, original primary | ✅ | 4b Task 3 (`toFailureClosingSuccess`) over Task 1b's guarded `suppress()` — never `using`, whose auto-generated `SuppressedError` inverts the priority, and never `new SuppressedError(...)`, which is absent on the declared floor. Close observed via the body stream's `cancel()` hook, since `Response` is frozen. Re-forced from real Node in `test/node-conformance/recovery-chain.test.mjs` |
| RECOV-13 | MUST | A deliberately *returned* different outcome is never auto-closed | ✅ | 4b Task 3 — only a caught throw reaches the close path; asserted for both a substitute Failure and a substitute Success |
| RECOV-14 | MUST | Step lists immutable; response chain copies both | ✅ | 4b Tasks 2 and 3 — the request chain is copied too, which the reference does not do and the requirement's own text recommends. Ledgered |
| RECOV-15 | MUST | Only 400..599 map to the typed exception; every other status passes through | ✅ | 4b Task 5, delegating to Phase 3b's unchanged `toHttpError()` |
Expand Down Expand Up @@ -155,6 +156,7 @@ Verification of the three Phase 4 implementation plans —
| Negative-space assertions | styleguide 11.9 | ✅ | Duplicate-key install, no-op closes, cross-stage edits, missing anchors, reserved SEND, continuation reuse, transport `close()` never called |
| Options object over positional params | `max-params: 3` | ✅ | `ContextInit` (4a), `DispatchConfig` (4b), `CursorInit` (4c). No `eslint-disable` anywhere in Phase 4 |
| Fakes over mocks; no owned interface mocked | styleguide 11.3 | ✅ | File-local `Transport` stubs throughout; no `FakeTransport`, no `mock.module`, and (as of the 2026-07-26 review) no patched `Response` method and no patched `contextStore` singleton |
| A runtime-divergent surface gets a `test/node-conformance/` case | `test/node-conformance/README.md` membership rule | ✅ | 4b: `recovery-chain.test.mjs` — `SuppressedError`'s presence is the divergence (Bun and current Node have it, the 20.3 floor does not), plus `RECOV-12`'s release-exactly-once over Node's own Web Streams |
| Every test file cites its requirement IDs | Phase 1 convention, for Phase 9 | ✅ | Top-of-file comment in every test file across all three plans |
| 80% aggregate coverage floor | `NFR-5` | ✅ | Each phase's gate task |

Expand Down Expand Up @@ -197,6 +199,34 @@ stay accurate except where noted here. Full text in the roadmap's *Open Findings

---

## Phase 4b execution (2026-08-26)

Both of 4b's open decisions closed before execution; neither changed a `RECOV-*` disposition above.

| Item | Resolution |
|---|---|
| **F1 (blocker, cross-phase):** `SuppressedError` absent on the declared floor | Branch (b) — `packages/core/src/suppress.ts` ships `suppress(error, suppressed, message)`, native class where the runtime has one and a shape-compatible stand-in where it does not, global read per call. Branch (a) was disqualified on evidence: `SuppressedError` reached Node in **24.0.0**, so raising the floor means dropping Node 18, 20 and 22 for one error class, against a floor of `>=20.3` set by `AbortSignal.any()`. The helper discharges the obligation for 5a, 6a, 6b and 6c too — they substitute the call when they execute |
| **F2:** zero `invariant()` assertions across `recovery/` | Deviation Ledger row in 4b's design, naming the concrete cost (a step returning `undefined` poisons the fold silently). Project-wide inconsistency — 1/2/3b/4a ship zero, 4c ships fifteen — so Phase 10 settles the density rule once rather than 4b becoming the one module that differs |
| Merge residue | The phase-3 merge left `bunfig.toml` with a duplicated `[test] root` key, which TOML rejects — `bun test` failed to load bunfig at all on this branch. Fixed in its own commit before any 4b work |

**Gate evidence (all exit 0), every step both CI jobs run, in order:** `bun install --frozen-lockfile`,
`typecheck`, `lint`, `build`, `bun test --coverage` (588 tests across 50 files; 98.68% funcs / 99.73% lines
against the 80% floor), `api` with `packages/core/etc/core.api.md` byte-identical, `lint:publish`,
`verify:dual-consumption`, `verify:consumer-types`, `verify:seam-1`, `verify:runtime-floor`, `audit`, and the
`node-conformance` job's `test:node` (36 cases, 35 before this phase). Also `test:knowledge`, which CI does not
run. Structural: no `node:` import, no `enum`, no `recovery/index.ts`, SPDX on line 1 of all 15 new files, no
import cycle anywhere under `packages/core/src`.

**Three review passes ran before this was called done.** Pass 1 (corpus-driven) found a dead `satisfies`
statement reaching the published `dist/`, two test files that could not survive parallel execution, a missing
type-level test for the exported generic `Outcome<T>`, an untranscribed `RECOV-15` conformance clause, and two
step-down-rule violations. Pass 2 (normative-text-driven) found a **`RECOV-8` violation**: `apply()` could
throw a `TypeError` when a step returned a non-outcome, against "MUST NOT throw under any input" — closed by
making `toFailureClosingSuccess` total; plus an unguarded `String()` in `assertNever`'s default message. Pass 3
re-ran every CI step and swept the structure. What survives is in `docs/open-items.md` under Phase 4b.

---

## Deferred out of Phase 4

| Item | Target | Note |
Expand Down
12 changes: 11 additions & 1 deletion docs/superpowers/plans/2026-07-26-phase5a-retry.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,21 @@ classification, backoff math, and pacing parsing take no I/O and no clock — dr
engine, not two stacks** — `RETRY-28` explicitly instructs a unifying port to make the total-timeout opt-in, which
`RetrySettings.totalTimeoutMs` does.

**Tech Stack:** TypeScript 5.8+, native `SuppressedError`, `fast-check` for the four invariant-bearing pure
**Tech Stack:** TypeScript 5.8+, Phase 4b's guarded `suppress()` helper (never native `SuppressedError` — see below), `fast-check` for the four invariant-bearing pure
functions, `bun test`. No new runtime dependencies — `SEAM-1` untouched. No `node:` imports — core's zero-`node:`
invariant, mechanically enforced since the scaffold, still holds (the RFC 1123 parser and the timer are both
platform-neutral).

> ### ✅ F1 CLOSED — use `suppress()`, not `new SuppressedError(...)`
>
> Resolved 2026-08-26 in Phase 4b as branch (b): `packages/core/src/suppress.ts` ships
> `suppress(error, suppressed, message)` — native `SuppressedError` when `globalThis.SuppressedError` exists, a
> shape-compatible stand-in (`name`, `error`, `suppressed`) when it does not. The native class reached Node only
> in **24.0.0** and `engines.node` is `>=20.3`, so the direct form neither type-checks (not in this package's
> `lib`) nor runs on the floor. Every `new SuppressedError(...)` below becomes `suppress(...)`, and every
> `toBeInstanceOf(SuppressedError)` becomes an assertion on that shape — the `instanceof` form would silently
> assert nothing on the floor runtime.

**Prerequisite:** This plan assumes Phases 0, 1, 2, 3a, 3b, 4a, 4b, and 4c are implemented exactly as their plans
specify, **plus Phase 7a's `Clock` seam** (added by the 2026-07-28 Phase 7a brainstorm's retrofit — see the
"`Clock` retrofit" note in `docs/superpowers/specs/2026-07-28-phase7a-configuration-design.md`, Scope section).
Expand Down
26 changes: 9 additions & 17 deletions docs/superpowers/plans/2026-07-28-phase6b-sse.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,18 @@ The parser is a stateful class (`SSE-15`/`SSE-16` need observable state, `SSE-17
generator lives one layer up in the facade, where ownership belongs.

**Tech Stack:** TypeScript 5.8+, `bun test`, `fast-check` for the two chunk-independence/round-trip properties,
native `SuppressedError`. No new runtime dependencies. No `node:` imports. **No serde imports at all** — enforced
Phase 4b's guarded `suppress()` helper. No new runtime dependencies. No `node:` imports. **No serde imports at all** — enforced
by a new build script, not by review.

> ### ⛔ BLOCKED on the same cross-phase item as Phase 4b — do not execute Tasks 5–7 yet
> ### ✅ F1 CLOSED — use `suppress()`, not `new SuppressedError(...)`
>
> **`SuppressedError` does not exist on the declared runtime floor.** `SSE-29` and `SSE-36` are implemented here
> with `new SuppressedError(...)`, and `engines.node` is `">=18.17"`. `SuppressedError` is a V8 global from the
> full Explicit Resource Management proposal and is absent on every 18.x runtime — Node backported
> `Symbol.dispose`/`Symbol.asyncDispose` on their own, not the error type. Adding `esnext.disposable` to `lib`
> supplies the *type* only, so `new SuppressedError(...)` type-checks, passes `bun test` locally, and then throws
> `ReferenceError: SuppressedError is not defined` under Task 9's `bun run verify:node-floor` / `bun run
> test:node` on the pinned 18.17.0 runner. That is exactly the `NFR-10` trap
> `docs/knowledge/tooling-and-quality-gates.md:60-61` describes.
>
> This is **not 6b's decision to make**: `plans/2026-07-25-phase4b-recovery-chain.md:24-48` already raised it as
> a blocker naming Phases 5a, 6a, 6b and 6c, with two options on the table — raise `engines.node`, or add a
> runtime-guarded `suppress(primary, secondary)` helper in `packages/core/src/`. Whichever lands, lands in all
> five. If the guarded-helper option is chosen, every `new SuppressedError(...)` below becomes
> `suppress(primary, secondary, message)` and the `toBeInstanceOf(SuppressedError)` assertions become assertions
> on that helper's shape. Tasks 1–4 and 8 are unaffected and can proceed.
> Resolved 2026-08-26 in Phase 4b as branch (b): `packages/core/src/suppress.ts` ships
> `suppress(error, suppressed, message)`, which constructs the native `SuppressedError` when
> `globalThis.SuppressedError` exists and a shape-compatible stand-in (`name`, `error`, `suppressed`) when it
> does not. `SuppressedError` reached Node only in **24.0.0** and `engines.node` is `>=20.3`, so the direct form
> neither type-checks (it is not in this package's `lib`) nor runs on the floor. Every `new SuppressedError(...)`
> below becomes `suppress(...)`, and every `toBeInstanceOf(SuppressedError)` becomes an assertion on that shape —
> the `instanceof` form would silently assert nothing on the floor runtime. No decision left to make here.

**Prerequisite:** Phases 0 through **5c** implemented as their plans specify. **6a is deliberately *not* a
prerequisite** — `SSE-37` (MUST) forbids any serde dependency in core SSE, so this phase imports nothing 6a
Expand Down
Loading
Loading