feat(core): stage-based pipeline — product-spec §8.1 (PIPE-1..PIPE-40) - #38
Merged
Merged
Conversation
…, XCUT-14) Ships the per-call correlation state the pipeline is built on, per product-spec/07-execution-context-model.md and docs/superpowers/specs/2026-07-25-phase4a-execution-context-design.md. New `packages/core/src/context/`, layered instrumentation → errors → context → store, with no `index.ts` barrel (docs/knowledge/module-organization.md:18 bans internal barrels; 4c imports the files directly): - `instrumentation.ts` — the `InstrumentationBundle` shape (CTX-14) and its frozen no-op default (CTX-15, CTX-20). `activeSpan`/`tracerFactory` stay typed `unknown`: a real tracing adapter owns their shape, deferred to Phase 7. - `errors.ts` — `DuplicateContextKeyError extends DexpaceError`, carrying the offending `key` as a readonly field (CTX-8). - `context.ts` — `DispatchContext`/`RequestContext`/`ExchangeContext` as a frozen discriminated union over plain data, with `create*` factories and the two one-way promotions (CTX-1, CTX-2, CTX-3, CTX-5, CTX-6, CTX-7, CTX-16). No classes: nothing here owns a lifecycle. Call keys are `Symbol()`, never a trace-derived string. The factories and both promotions freeze the instrumentation bundle in place — `Object.freeze` is shallow, so freezing only the context would leave a caller-supplied bundle writable behind the `instrumentation` slot, and the flavors are interfaces, so a literal-built context can reach a promotion without passing a factory. - `store.ts` — `ContextStore`, a bounded `Map` with a post-insert drain loop, plus the process-wide `contextStore` singleton (CTX-7..13, CTX-18, CTX-19). Also the subject of XCUT-14, which names "context registries" first among the caller-keyed process-lived maps that must be capped — and is the only appendix-B conformance row this code satisfies, since appendix B has no CTX section at all. Nothing enters the public barrel: `context/` is SDK-internal correlation plumbing, and `packages/core/etc/core.api.md` is byte-identical. Tests: 45 across four colocated files, each header citing the IDs it exercises. A 23-mutant sweep over the module kills 21; one survivor was an equivalent mutant, and the other — collapsing `#drain`'s loop into a single check-then-evict — is unkillable by construction, since both callers set one key before draining so the map never exceeds cap + 1. The loop is kept because CTX-12 and XCUT-14 mandate the shape for runtimes with real concurrency; both `#drain` and its describe block say so, and it is registered as open item A6. Verified against the CI-pinned toolchain rather than the local one: every `ci` job step under bun 1.3.14 (`.bun-version`), and node-conformance on both matrix legs — the 20.3.0 floor and lts/* (v24.20.0) — 31 pass each. The context module itself was additionally exercised against the built artifact on both Node versions, confirming the plan's claim that nothing here is runtime-divergent. Also fixes bunfig.toml, where merging the Phase 3 and knowledge-CLI branches left `root = "packages"` twice under `[test]`; Bun refuses a config with a redefined key, so `bun test` failed to start at all. Deliberate deferrals, all registered in docs/open-items.md rather than left silent: CTX-17's positive half (install-on-first-promotion) belongs to 4c, which owns the store handle; real W3C Trace Context generation to Phase 7; `contextsEqual()` unscheduled; and CTX-8's message clause (a Symbol's description names the flavor, not the instance) awaiting a decision as A5.
…ECOV-16)
Phase 4b. Ships `packages/core/src/recovery/` — six files, no folder barrel,
nothing on the published API surface (`packages/core/etc/core.api.md` is
byte-identical):
- `outcome.ts` Outcome<T>, success/failure/fold (RECOV-1)
- `request-chain.ts` sequential fold, empty = identity, throw
propagates for the orchestrator to convert (RECOV-3, 14)
- `response-chain.ts` response phase on Success only, recovery phase
always, close-on-throw exactly once with the
original throwable primary, no auto-close on a
deliberately returned substitute (RECOV-4..9, 12..14)
- `cancellation.ts` wrapCancellation — never throws, which is what
keeps RECOV-2 absolute (RECOV-11)
- `status-mapping.ts` a thin response step over 3b's unchanged
toHttpError() (RECOV-15, 16)
- `orchestrator.ts` dispatchWithRecovery — one try/catch over the
request chain and the transport hop; the final
unwrap rethrows by identity (RECOV-2, 10)
Plus two package-root helpers: `assertNever` in `invariant.ts` (the codebase's
first discriminated-union `default`) and `suppress()` in `suppress.ts`.
F1, the cross-phase blocker, resolved to branch (b)
---------------------------------------------------
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 `engines.node >=20.3` floor (set by `AbortSignal.any()`),
and is absent from the `lib` it compiles against, so the direct form neither
type-checks nor runs there. Raising the floor would drop Node 18, 20 and 22 for
one error class. `suppress()` uses the native class where the runtime has one
and a shape-compatible stand-in where it does not, reading the global per call.
Callers assert the shape, never `instanceof SuppressedError` — that form would
silently assert nothing on the floor. Phases 5a, 6a, 6b and 6c reached for the
native class on the same premise; their docs now point at the helper.
F2 resolved as a Deviation Ledger row: the zero-vs-fifteen `invariant()` split
with 4c is project-wide, so Phase 10 settles the density rule once.
Three review passes
-------------------
Pass 1, against the knowledge corpus: a dead `statusMappingStep;` statement
reaching the published dist/ (`satisfies` erases to its operand, not to
nothing); two test files that could not survive parallel execution because they
deleted a global; no type-level test for the exported generic `Outcome<T>`; the
RECOV-15 conformance clause tested on the step in isolation rather than through
the chain; two step-down-rule violations.
Pass 2, against the normative text: **a RECOV-8 violation** — `apply()` could
raise `TypeError: undefined is not an object` when a step returned a
non-outcome, against "MUST NOT throw under any input". `toFailureClosingSuccess`
is now total: the discriminant read and the `close()` call share one `try`, so a
misbehaving step becomes a Failure with its own throwable still primary. Also an
unguarded `String()` in `assertNever`'s default message, which throws on a
null-prototype object.
Pass 3: re-ran every step of both CI jobs, swept the structure, and wrote what
survives into `docs/open-items.md` section F.
Also fixes a merge residue: the phase-3 merge left `bunfig.toml` with a
duplicated `[test] root` key, which TOML rejects, so `bun test` failed to load
bunfig at all on this branch.
Verification (all exit 0)
-------------------------
`bun install --frozen-lockfile`, typecheck, lint, build, `bun test --coverage`
(588 tests / 50 files, 98.68% funcs / 99.73% lines against the 80% floor), api,
lint:publish, verify:dual-consumption, verify:consumer-types, verify:seam-1,
verify:runtime-floor, audit, test:node (36 cases, +1 file covering the
SuppressedError guard and RECOV-12 over Node's own Web Streams), test:knowledge.
No `node:` import, no `enum`, no internal barrel, SPDX on line 1 of all 15 new
files, no import cycle under `packages/core/src`.
Refs: #8
Phase 4c. Ships `packages/core/src/pipeline/` — the fixed-stage step composition runtime, its builder, the per-call cursor/fork mechanism, and the execution-context-store wiring 4a deferred here. Plumbing only: no pillar step bodies, no standard-resilience preset, nothing added to the public barrel. - `stage.ts` — `Stage` as a string-literal union plus `STAGE_ORDER` and `PILLAR_STAGES`. No TS `enum` (`erasableSyntaxOnly`); inserting a stage later is one splice and touches no existing stage identity (PIPE-1..4, PIPE-8). - `step.ts` — `Step`/`StepContext`/`Next`/`StepDescriptor`. A step is a function wrapped in a descriptor carrying a `type` symbol, which is what PIPE-6's reference identity and PIPE-18/19's anchor matching key off. - `cursor.ts` — one recursive dispatcher per call. `ctx.next` and every `ctx.fork()` are one-shot closures over it, pinned to the same target position, sharing a single mutable in-flight request so a substitution sticks for the whole call (PIPE-9..PIPE-17). - `runtime.ts` — `Runtime implements Transport`: empty-pipeline fast path, context install/promote/evict-in-`finally` on both paths, and `exchangeSource` so the exchange context describes the request that was actually sent (PIPE-9, PIPE-10, PIPE-25..27, CTX-17). - `builder.ts` — stage-bucketed surgical edits with fail-fast validation at the mutating call, flattened once at `build()` (PIPE-7, PIPE-18..PIPE-25, PIPE-38). - `errors.ts` — five flat `DexpaceError` leaves, each rendering its identifying symbols into its own message. Tests are colocated and cite their PIPE IDs, including fast-check properties for the builder's ordering laws (PIPE-22, PIPE-38) and the driven probe test for PIPE-1/PIPE-2's stage ordering. Deliberately deferred, each named in the design doc or the roadmap's Deferred Items Log: PIPE-17's "readable by any step" clause and `StepContext.signal` (Phase 5a Task 1), PIPE-24/PIPE-35/PIPE-39 (Phase 5+), PIPE-2's redirect/retry half and PIPE-40's 2-hop clause (Phase 5b/5c). Open finding F9 — the cursor does not observe the caller's `AbortSignal` between steps — stays undecided in the roadmap and must be settled before 5a Task 1. Full CI sequence green locally: typecheck, lint, build, test --coverage (690 tests, pipeline files at 100%), api (report byte-identical), lint:publish, verify:dual-consumption, verify:consumer-types, verify:seam-1, verify:runtime-floor, audit, test:node.
Wahbeh-Mohammad
force-pushed
the
9-phase-4c-stage-based-pipeline
branch
from
August 26, 2026 17:59
726f05b to
c78c327
Compare
Wahbeh-Mohammad
added a commit
that referenced
this pull request
Aug 26, 2026
…eline (#39) * feat(core): add the execution context model — Phase 4a (CTX-1..CTX-20, XCUT-14) (#36) Ships the per-call correlation state the pipeline is built on, per product-spec/07-execution-context-model.md and docs/superpowers/specs/2026-07-25-phase4a-execution-context-design.md. New `packages/core/src/context/`, layered instrumentation → errors → context → store, with no `index.ts` barrel (docs/knowledge/module-organization.md:18 bans internal barrels; 4c imports the files directly): - `instrumentation.ts` — the `InstrumentationBundle` shape (CTX-14) and its frozen no-op default (CTX-15, CTX-20). `activeSpan`/`tracerFactory` stay typed `unknown`: a real tracing adapter owns their shape, deferred to Phase 7. - `errors.ts` — `DuplicateContextKeyError extends DexpaceError`, carrying the offending `key` as a readonly field (CTX-8). - `context.ts` — `DispatchContext`/`RequestContext`/`ExchangeContext` as a frozen discriminated union over plain data, with `create*` factories and the two one-way promotions (CTX-1, CTX-2, CTX-3, CTX-5, CTX-6, CTX-7, CTX-16). No classes: nothing here owns a lifecycle. Call keys are `Symbol()`, never a trace-derived string. The factories and both promotions freeze the instrumentation bundle in place — `Object.freeze` is shallow, so freezing only the context would leave a caller-supplied bundle writable behind the `instrumentation` slot, and the flavors are interfaces, so a literal-built context can reach a promotion without passing a factory. - `store.ts` — `ContextStore`, a bounded `Map` with a post-insert drain loop, plus the process-wide `contextStore` singleton (CTX-7..13, CTX-18, CTX-19). Also the subject of XCUT-14, which names "context registries" first among the caller-keyed process-lived maps that must be capped — and is the only appendix-B conformance row this code satisfies, since appendix B has no CTX section at all. Nothing enters the public barrel: `context/` is SDK-internal correlation plumbing, and `packages/core/etc/core.api.md` is byte-identical. Tests: 45 across four colocated files, each header citing the IDs it exercises. A 23-mutant sweep over the module kills 21; one survivor was an equivalent mutant, and the other — collapsing `#drain`'s loop into a single check-then-evict — is unkillable by construction, since both callers set one key before draining so the map never exceeds cap + 1. The loop is kept because CTX-12 and XCUT-14 mandate the shape for runtimes with real concurrency; both `#drain` and its describe block say so, and it is registered as open item A6. Verified against the CI-pinned toolchain rather than the local one: every `ci` job step under bun 1.3.14 (`.bun-version`), and node-conformance on both matrix legs — the 20.3.0 floor and lts/* (v24.20.0) — 31 pass each. The context module itself was additionally exercised against the built artifact on both Node versions, confirming the plan's claim that nothing here is runtime-divergent. Also fixes bunfig.toml, where merging the Phase 3 and knowledge-CLI branches left `root = "packages"` twice under `[test]`; Bun refuses a config with a redefined key, so `bun test` failed to start at all. Deliberate deferrals, all registered in docs/open-items.md rather than left silent: CTX-17's positive half (install-on-first-promotion) belongs to 4c, which owns the store handle; real W3C Trace Context generation to Phase 7; `contextsEqual()` unscheduled; and CTX-8's message clause (a Symbol's description names the flavor, not the instance) awaiting a decision as A5. * feat(core): recovery-chain primitives — product-spec §8.2 (RECOV-1..RECOV-16) (#37) Phase 4b. Ships `packages/core/src/recovery/` — six files, no folder barrel, nothing on the published API surface (`packages/core/etc/core.api.md` is byte-identical): - `outcome.ts` Outcome<T>, success/failure/fold (RECOV-1) - `request-chain.ts` sequential fold, empty = identity, throw propagates for the orchestrator to convert (RECOV-3, 14) - `response-chain.ts` response phase on Success only, recovery phase always, close-on-throw exactly once with the original throwable primary, no auto-close on a deliberately returned substitute (RECOV-4..9, 12..14) - `cancellation.ts` wrapCancellation — never throws, which is what keeps RECOV-2 absolute (RECOV-11) - `status-mapping.ts` a thin response step over 3b's unchanged toHttpError() (RECOV-15, 16) - `orchestrator.ts` dispatchWithRecovery — one try/catch over the request chain and the transport hop; the final unwrap rethrows by identity (RECOV-2, 10) Plus two package-root helpers: `assertNever` in `invariant.ts` (the codebase's first discriminated-union `default`) and `suppress()` in `suppress.ts`. F1, the cross-phase blocker, resolved to branch (b) --------------------------------------------------- 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 `engines.node >=20.3` floor (set by `AbortSignal.any()`), and is absent from the `lib` it compiles against, so the direct form neither type-checks nor runs there. Raising the floor would drop Node 18, 20 and 22 for one error class. `suppress()` uses the native class where the runtime has one and a shape-compatible stand-in where it does not, reading the global per call. Callers assert the shape, never `instanceof SuppressedError` — that form would silently assert nothing on the floor. Phases 5a, 6a, 6b and 6c reached for the native class on the same premise; their docs now point at the helper. F2 resolved as a Deviation Ledger row: the zero-vs-fifteen `invariant()` split with 4c is project-wide, so Phase 10 settles the density rule once. Three review passes ------------------- Pass 1, against the knowledge corpus: a dead `statusMappingStep;` statement reaching the published dist/ (`satisfies` erases to its operand, not to nothing); two test files that could not survive parallel execution because they deleted a global; no type-level test for the exported generic `Outcome<T>`; the RECOV-15 conformance clause tested on the step in isolation rather than through the chain; two step-down-rule violations. Pass 2, against the normative text: **a RECOV-8 violation** — `apply()` could raise `TypeError: undefined is not an object` when a step returned a non-outcome, against "MUST NOT throw under any input". `toFailureClosingSuccess` is now total: the discriminant read and the `close()` call share one `try`, so a misbehaving step becomes a Failure with its own throwable still primary. Also an unguarded `String()` in `assertNever`'s default message, which throws on a null-prototype object. Pass 3: re-ran every step of both CI jobs, swept the structure, and wrote what survives into `docs/open-items.md` section F. Also fixes a merge residue: the phase-3 merge left `bunfig.toml` with a duplicated `[test] root` key, which TOML rejects, so `bun test` failed to load bunfig at all on this branch. Verification (all exit 0) ------------------------- `bun install --frozen-lockfile`, typecheck, lint, build, `bun test --coverage` (588 tests / 50 files, 98.68% funcs / 99.73% lines against the 80% floor), api, lint:publish, verify:dual-consumption, verify:consumer-types, verify:seam-1, verify:runtime-floor, audit, test:node (36 cases, +1 file covering the SuppressedError guard and RECOV-12 over Node's own Web Streams), test:knowledge. No `node:` import, no `enum`, no internal barrel, SPDX on line 1 of all 15 new files, no import cycle under `packages/core/src`. Refs: #8 * feat(core): stage-based pipeline — product-spec §8.1 (PIPE-1..PIPE-40) (#38) * feat(core): add the execution context model — Phase 4a (CTX-1..CTX-20, XCUT-14) Ships the per-call correlation state the pipeline is built on, per product-spec/07-execution-context-model.md and docs/superpowers/specs/2026-07-25-phase4a-execution-context-design.md. New `packages/core/src/context/`, layered instrumentation → errors → context → store, with no `index.ts` barrel (docs/knowledge/module-organization.md:18 bans internal barrels; 4c imports the files directly): - `instrumentation.ts` — the `InstrumentationBundle` shape (CTX-14) and its frozen no-op default (CTX-15, CTX-20). `activeSpan`/`tracerFactory` stay typed `unknown`: a real tracing adapter owns their shape, deferred to Phase 7. - `errors.ts` — `DuplicateContextKeyError extends DexpaceError`, carrying the offending `key` as a readonly field (CTX-8). - `context.ts` — `DispatchContext`/`RequestContext`/`ExchangeContext` as a frozen discriminated union over plain data, with `create*` factories and the two one-way promotions (CTX-1, CTX-2, CTX-3, CTX-5, CTX-6, CTX-7, CTX-16). No classes: nothing here owns a lifecycle. Call keys are `Symbol()`, never a trace-derived string. The factories and both promotions freeze the instrumentation bundle in place — `Object.freeze` is shallow, so freezing only the context would leave a caller-supplied bundle writable behind the `instrumentation` slot, and the flavors are interfaces, so a literal-built context can reach a promotion without passing a factory. - `store.ts` — `ContextStore`, a bounded `Map` with a post-insert drain loop, plus the process-wide `contextStore` singleton (CTX-7..13, CTX-18, CTX-19). Also the subject of XCUT-14, which names "context registries" first among the caller-keyed process-lived maps that must be capped — and is the only appendix-B conformance row this code satisfies, since appendix B has no CTX section at all. Nothing enters the public barrel: `context/` is SDK-internal correlation plumbing, and `packages/core/etc/core.api.md` is byte-identical. Tests: 45 across four colocated files, each header citing the IDs it exercises. A 23-mutant sweep over the module kills 21; one survivor was an equivalent mutant, and the other — collapsing `#drain`'s loop into a single check-then-evict — is unkillable by construction, since both callers set one key before draining so the map never exceeds cap + 1. The loop is kept because CTX-12 and XCUT-14 mandate the shape for runtimes with real concurrency; both `#drain` and its describe block say so, and it is registered as open item A6. Verified against the CI-pinned toolchain rather than the local one: every `ci` job step under bun 1.3.14 (`.bun-version`), and node-conformance on both matrix legs — the 20.3.0 floor and lts/* (v24.20.0) — 31 pass each. The context module itself was additionally exercised against the built artifact on both Node versions, confirming the plan's claim that nothing here is runtime-divergent. Also fixes bunfig.toml, where merging the Phase 3 and knowledge-CLI branches left `root = "packages"` twice under `[test]`; Bun refuses a config with a redefined key, so `bun test` failed to start at all. Deliberate deferrals, all registered in docs/open-items.md rather than left silent: CTX-17's positive half (install-on-first-promotion) belongs to 4c, which owns the store handle; real W3C Trace Context generation to Phase 7; `contextsEqual()` unscheduled; and CTX-8's message clause (a Symbol's description names the flavor, not the instance) awaiting a decision as A5. * feat(core): recovery-chain primitives — product-spec §8.2 (RECOV-1..RECOV-16) Phase 4b. Ships `packages/core/src/recovery/` — six files, no folder barrel, nothing on the published API surface (`packages/core/etc/core.api.md` is byte-identical): - `outcome.ts` Outcome<T>, success/failure/fold (RECOV-1) - `request-chain.ts` sequential fold, empty = identity, throw propagates for the orchestrator to convert (RECOV-3, 14) - `response-chain.ts` response phase on Success only, recovery phase always, close-on-throw exactly once with the original throwable primary, no auto-close on a deliberately returned substitute (RECOV-4..9, 12..14) - `cancellation.ts` wrapCancellation — never throws, which is what keeps RECOV-2 absolute (RECOV-11) - `status-mapping.ts` a thin response step over 3b's unchanged toHttpError() (RECOV-15, 16) - `orchestrator.ts` dispatchWithRecovery — one try/catch over the request chain and the transport hop; the final unwrap rethrows by identity (RECOV-2, 10) Plus two package-root helpers: `assertNever` in `invariant.ts` (the codebase's first discriminated-union `default`) and `suppress()` in `suppress.ts`. F1, the cross-phase blocker, resolved to branch (b) --------------------------------------------------- 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 `engines.node >=20.3` floor (set by `AbortSignal.any()`), and is absent from the `lib` it compiles against, so the direct form neither type-checks nor runs there. Raising the floor would drop Node 18, 20 and 22 for one error class. `suppress()` uses the native class where the runtime has one and a shape-compatible stand-in where it does not, reading the global per call. Callers assert the shape, never `instanceof SuppressedError` — that form would silently assert nothing on the floor. Phases 5a, 6a, 6b and 6c reached for the native class on the same premise; their docs now point at the helper. F2 resolved as a Deviation Ledger row: the zero-vs-fifteen `invariant()` split with 4c is project-wide, so Phase 10 settles the density rule once. Three review passes ------------------- Pass 1, against the knowledge corpus: a dead `statusMappingStep;` statement reaching the published dist/ (`satisfies` erases to its operand, not to nothing); two test files that could not survive parallel execution because they deleted a global; no type-level test for the exported generic `Outcome<T>`; the RECOV-15 conformance clause tested on the step in isolation rather than through the chain; two step-down-rule violations. Pass 2, against the normative text: **a RECOV-8 violation** — `apply()` could raise `TypeError: undefined is not an object` when a step returned a non-outcome, against "MUST NOT throw under any input". `toFailureClosingSuccess` is now total: the discriminant read and the `close()` call share one `try`, so a misbehaving step becomes a Failure with its own throwable still primary. Also an unguarded `String()` in `assertNever`'s default message, which throws on a null-prototype object. Pass 3: re-ran every step of both CI jobs, swept the structure, and wrote what survives into `docs/open-items.md` section F. Also fixes a merge residue: the phase-3 merge left `bunfig.toml` with a duplicated `[test] root` key, which TOML rejects, so `bun test` failed to load bunfig at all on this branch. Verification (all exit 0) ------------------------- `bun install --frozen-lockfile`, typecheck, lint, build, `bun test --coverage` (588 tests / 50 files, 98.68% funcs / 99.73% lines against the 80% floor), api, lint:publish, verify:dual-consumption, verify:consumer-types, verify:seam-1, verify:runtime-floor, audit, test:node (36 cases, +1 file covering the SuppressedError guard and RECOV-12 over Node's own Web Streams), test:knowledge. No `node:` import, no `enum`, no internal barrel, SPDX on line 1 of all 15 new files, no import cycle under `packages/core/src`. Refs: #8 * feat(core): stage-based pipeline — product-spec §8.1 (PIPE-1..PIPE-40) Phase 4c. Ships `packages/core/src/pipeline/` — the fixed-stage step composition runtime, its builder, the per-call cursor/fork mechanism, and the execution-context-store wiring 4a deferred here. Plumbing only: no pillar step bodies, no standard-resilience preset, nothing added to the public barrel. - `stage.ts` — `Stage` as a string-literal union plus `STAGE_ORDER` and `PILLAR_STAGES`. No TS `enum` (`erasableSyntaxOnly`); inserting a stage later is one splice and touches no existing stage identity (PIPE-1..4, PIPE-8). - `step.ts` — `Step`/`StepContext`/`Next`/`StepDescriptor`. A step is a function wrapped in a descriptor carrying a `type` symbol, which is what PIPE-6's reference identity and PIPE-18/19's anchor matching key off. - `cursor.ts` — one recursive dispatcher per call. `ctx.next` and every `ctx.fork()` are one-shot closures over it, pinned to the same target position, sharing a single mutable in-flight request so a substitution sticks for the whole call (PIPE-9..PIPE-17). - `runtime.ts` — `Runtime implements Transport`: empty-pipeline fast path, context install/promote/evict-in-`finally` on both paths, and `exchangeSource` so the exchange context describes the request that was actually sent (PIPE-9, PIPE-10, PIPE-25..27, CTX-17). - `builder.ts` — stage-bucketed surgical edits with fail-fast validation at the mutating call, flattened once at `build()` (PIPE-7, PIPE-18..PIPE-25, PIPE-38). - `errors.ts` — five flat `DexpaceError` leaves, each rendering its identifying symbols into its own message. Tests are colocated and cite their PIPE IDs, including fast-check properties for the builder's ordering laws (PIPE-22, PIPE-38) and the driven probe test for PIPE-1/PIPE-2's stage ordering. Deliberately deferred, each named in the design doc or the roadmap's Deferred Items Log: PIPE-17's "readable by any step" clause and `StepContext.signal` (Phase 5a Task 1), PIPE-24/PIPE-35/PIPE-39 (Phase 5+), PIPE-2's redirect/retry half and PIPE-40's 2-hop clause (Phase 5b/5c). Open finding F9 — the cursor does not observe the caller's `AbortSignal` between steps — stays undecided in the roadmap and must be settled before 5a Task 1. Full CI sequence green locally: typecheck, lint, build, test --coverage (690 tests, pipeline files at 100%), api (report byte-identical), lint:publish, verify:dual-consumption, verify:consumer-types, verify:seam-1, verify:runtime-floor, audit, test:node. * docs: add changesets for phase 4a & phase 4c.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #9.
What changed
Adds
packages/core/src/pipeline/, which satisfies product-spec §8.1. The phase shipsplumbing only. It ships no pillar step bodies, no resilience preset, and nothing new on
the public API surface. The committed API report does not change.
stage.ts—Stageas a string-literal union, plusSTAGE_ORDERandPILLAR_STAGES.A TS
enumis not allowed, becauseerasableSyntaxOnlyis on. To add a stage later,splice one entry into
STAGE_ORDER. No existing stage identity changes (PIPE-1..4, PIPE-8).step.ts—Step,StepContext,Next, andStepDescriptor. A step is a function.The descriptor holds a
typesymbol. PIPE-6 keys reference identity on that symbol.PIPE-18/19 match anchors on it.
cursor.ts— one recursive dispatcher per call.ctx.nextand eachctx.fork()areone-shot closures over that dispatcher. Both target the same position. All of them share
one mutable in-flight request, so a substitution holds for the rest of the call
(PIPE-9..PIPE-17).
runtime.ts—Runtime implements Transport. An empty pipeline dispatches directly tothe transport. A pipeline with steps installs the call context, promotes it, and evicts
it in a
finallyon both the resolve path and the throw path.exchangeSourcebuildsthe exchange context from the request that was sent, not from the request the caller
supplied (PIPE-9, PIPE-10, PIPE-25..27, CTX-17).
builder.ts— stage-bucketed surgical edits. Validation is fail-fast at the mutatingcall.
build()flattens once (PIPE-7, PIPE-18..PIPE-25, PIPE-38).errors.ts— five flatDexpaceErrorleaves. Each error puts its identifying symbolsin its own message.
Tests are colocated and cite their PIPE IDs. They include fast-check properties for the
builder ordering laws and a driven probe test for stage order.
All CI steps pass locally: typecheck, lint, build,
test --coverage(690 tests, pipelinefiles at 100%), api, lint:publish, verify:dual-consumption, verify:consumer-types,
verify:seam-1, verify:runtime-floor, audit, and test:node.
Reviews made
Three review passes ran against
docs/knowledge/and against §8.1 conformance clauses.No behavioral defect was found. Each pass added tags, docs, or tests.
@throwsTSDoc to every throwing builder method. Added tests for thetwo untested PIPE-12 clauses: a step that short-circuits, and a step that substitutes the
response. Added tests for PIPE-10/PIPE-11 concurrency, for the frozen step view, and for
the copied constructor array.
@internalrelease tagsdded a test forPIPE-26 nesting, which proves that a pipelie's transport and
that the caller options survive both hops. kiness across
forks. Added a test for PIPE-23 rollback on the
SENDpath.because the plan had assumed none. Recordede design Testing
section. Made the pillar guard read on one
Open items & deferred items
to the transport, but does not check the signal beorted()raises aDOMExceptionthat the SDK taxonomy does nase 5a Task 1.The roadmap tracks it.
StepContexignalgo toPhase 5a Task 1. No step in this phase can read them.
go to Phase 5b/5c.
step family.
only, so
test:noderan on v26 here. CI covers both legs.