Skip to content

feat(core): phase 7a — configuration model and platform primitives - #43

Merged
Wahbeh-Mohammad merged 1 commit into
18-phase-7from
19-phase-7a-configuration-platform-primitives
Aug 27, 2026
Merged

feat(core): phase 7a — configuration model and platform primitives#43
Wahbeh-Mohammad merged 1 commit into
18-phase-7from
19-phase-7a-configuration-platform-primitives

Conversation

@Wahbeh-Mohammad

Copy link
Copy Markdown
Contributor

Closes #19. Ships CFG-1..CFG-38, RECOV-33, and NFR-15 per
docs/superpowers/specs/2026-07-28-phase7a-configuration-design.md.

What changed

New packages/core/src/config/ (ten files, no internal barrel):

  • configuration.ts + duration.ts — layered Configuration,
    ConfigurationBuilder, CFG-14 key constants, the CFG-13 global slot,
    defaultConfiguration(). Three tiers, not four: Node has no ambient property
    store, so the production property seam is empty while the seam stays
    substitutable.
  • clock.tsClock/defaultClock. One wait primitive; sleep rejects
    with the caller's own abort reason.
  • proxy.tsProxyOptions, resolution, glob bypass, credential masking.
    Types and resolution only; no Transport consumes them until Phase 8.
  • http-date.ts, retryable.ts, identifiers.ts,
    equality.ts, build-info.ts, client-identity-step.ts — the
    shared primitives 5a/5b/7b would otherwise each have rebuilt.

NFR-15 closes via build-time codegen: scripts/gen-version.mjs writes
src/generated/version.ts as the prebuild step, so the SDK reports its real
version with no runtime package.json read on any runtime.

938 unit tests (+63) and 48 Node-conformance tests (+12). Every CI gate green.

Reviews done

Three sequential passes — shape, adversarial, reader — each re-fetching the
governing rules. Four defects caught that would have shipped:

  • HTTP_PROXY=http://proxy:80 resolved to null (WHATWG normalizes a special
    scheme's default port away; CFG-25 bans guessing an absent port, not
    discarding a written one).
  • defaultConfiguration().getInt('constructor', 7) threw TypeError
    process.env['constructor'] returns Object.prototype.constructor, breaking
    CFG-5/6/7's never-throw contract on the production wiring.
  • ReDoS: an operator-supplied NO_PROXY glob blocked the event loop for 38s on
    the per-request path. Now a linear matcher, 0.02ms.
  • resolveProxyOptions threw a bare URIError on a literal % in credentials,
    against CFG-24's explicit "MUST NOT throw".

Also fixed: sleep silently collapsed any delay past the 32-bit timer clamp to
~1ms; an unsanitized ambient navigator.userAgent would have made the default
client-identity step reject every outbound request.

The reader pass rewrote eight tests that were coverage rather than
verification, each proven by applying the mutation, observing the failure, and
reverting.

Open / deferred items

All registered in docs/open-items.md (G1–G19). Needing a decision:

  • G4CFG-28's global-config convenience resolver is a MAY, not built.
  • G15hasForbiddenNameByte permits a space inside a header name.
    Phase 1 code, surfaced here because clientIdentityStep is the first caller
    forwarding a caller-supplied header name into it. Reported, not touched.

Scheduled with owners: CFG-24's warning half and a failing config seam's
invisibility both wait on 7b's Logger (G10, G14); CFG-35's throwable axis is
5a's (G5); clientIdentityStep's barrel promotion and folder placement wait on
whichever phase publishes the pipeline surface (G1, G11).

Ships the layered configuration model, the injectable time seam, the proxy
model, and the shared date/identifier/equality/retryability primitives several
earlier phases were about to duplicate, per
product-spec/16-configuration.md (CFG-1..CFG-38), appendix C's RECOV-33 and
NFR-15, and
docs/superpowers/specs/2026-07-28-phase7a-configuration-design.md.

New `packages/core/src/config/`, no `index.ts` barrel
(docs/knowledge/module-organization.md:18 bans internal barrels; consumers
import the files directly):

- `configuration.ts` — `Configuration` (an interface over a hidden
  `LayeredConfiguration`), `ConfigurationBuilder`, the CFG-14 key constants,
  the CFG-13 global slot, and `defaultConfiguration()` (CFG-1..CFG-14, CFG-37,
  CFG-38). Three tiers, not four: Node has no ambient key/value store distinct
  from `process.env`, so the *production* property seam is empty while the seam
  itself stays substitutable — which is what makes CFG-3/CFG-4's
  normalized-vs-raw split, and CFG-24/CFG-26's property tier, testable at all.
- `duration.ts` — CFG-7's grammar (ISO-8601, `<number><unit>`, bare
  milliseconds), split out of `configuration.ts` on one-concept-per-file.
  Rejects the ambiguous month designator rather than guessing.
- `clock.ts` — `Clock`/`defaultClock` (CFG-15..CFG-17). One wait primitive, not
  the reference's blocking-sleep/scheduled-delay pair: Node has no carrier
  threads to distinguish them against. `sleep` rejects with the signal's own
  abort reason, which is this runtime's expression of CFG-17's "re-assert the
  cancellation status before propagating".
- `proxy.ts` — `ProxyOptions`, `createProxyOptions`, `formatProxyOptions`,
  `shouldBypassProxy`, `resolveProxyOptions` (CFG-22..CFG-28). Types and
  resolution only; no concrete `Transport` consumes them until Phase 8, the
  same `Serde<T>`-before-`codec-json` precedent Phase 2 set. CFG-22's masking is
  a free function, not a `toString()` interface member — every object satisfies
  such a member through `Object.prototype`, so declaring it would state a
  contract the type cannot enforce.
- `http-date.ts` — `formatHttpDate`/`parseHttpDate` (CFG-29..CFG-31), the
  module 5a's `pacing.ts` imports instead of keeping a private parser. Never
  `Date.parse`; every field range-checked, and `Date.UTC` avoided because it
  maps a four-digit year below 100 onto the
- `retryable.ts` — `RETRYABLE_STATUSES`/`isRetryableStatus` (CFG-35), the one
  definition 5a's `classify.ts` re-exports.
  mutation: CFG-35 calls this set a hard contract, and a contract a caller can
  edit is not one.
- `identifiers.ts` — `randomUuid` over `globalThis.crypto` (CFG-32), no
  `node:crypto`.
- `equality.ts` — `deepEqual`/`deepHash` (CFG-33, CFG-34). `@internal`; no
  requirement gives a caller access.
- `build-info.ts` — `getBuildInfo` (CFG-36), feature-detected across Node, Bun,
  Deno, and browsers, resolved on first accehe
  module keeps `sideEffects: false`.
- `client-identity-step.ts` — `clientIdentitposes
  onto the first existing value and preserves every other one, which needs the
  value list rebuilt explicitly since `Headet
  `set` and one-more `add`.

NFR-15 closes via build-time codegen: `packamjs`
writes `src/generated/version.ts` from `package.json` as the `prebuild` step of
both the package and root `build`. No runtimneeds
`node:fs`/`import.meta.url` tricks that woulalf of
core's runtime floor reporting the `unknown` placeholder NFR-15 forbids. The
generated file is committed so an unbuilt `brsion.

Barrel: every symbol above except `deepEqual`/`deepHash` and
`clientIdentityStep`, whose `StepDescriptor` return type is part of the still-
internal pipeline authoring surface (open it

Three review passes — shape, adversarial, red
rules. Four defects they caught that would o

- `HTTP_PROXY=http://proxy:80` resolved to `ecial
  scheme's default port to the empty string,
  `http://p:80` from `http://p`. CFG-25 bans *guessing* an absent port, not
  discarding one the operator wrote.
- `defaultConfiguration().getInt('constructor', 7)` threw `TypeError` on Node
  against the built artifact — `process.env[
  `Object.prototype.constructor`, so `getString` returned a function while its
  signature said `string`, breaking CFG-5/6/he
  production wiring.
- `NO_PROXY="*a*a*a*a*a*a*a*a*a*b"` against he
  event loop for 38 seconds. `shouldBypassProxy` is synchronous on the
  per-request path. The regex glob is now a ms),
  which also fixed an escaped `\*` matching nothing.
- `resolveProxyOptions` threw a bare `URIErr
  credentials — `decodeURIComponent` sat outside the `try`, against CFG-24's
  explicit "MUST NOT throw".

Also fixed: `sleep` silently collapsed any damp to
~1ms, turning a config typo into a hot retry loop; and an unsanitized ambient
`navigator.userAgent` would have made the deject
every outbound request with a `HeaderValidationError`.

Tests: 63 new across eleven colocated files plus twelve Node-runtime cases in
`test/node-conformance/config-primitives.tesmer,
and WebCrypto behavior that is Bun's independent implementation, not Node's).
Each header cites the IDs it exercises. The ts
that were coverage rather than verification — one whose assertion was the
implementation body, and one where wiring `msed
every assertion in both suites, leaving CFG-16's whole point unverified. Each
rewrite is proven by applying the named mutaand
reverting.

Deliberate deferrals, registered in docs/open-items.md (G1..G19) rather than
left silent: CFG-24's warning half and a faity
both wait on 7b's `Logger` (G10, G14); CFG-35's throwable axis is 5a's (G5);
`clientIdentityStep`'s barrel promotion and hever
phase publishes the pipeline surface (G1, G11); CFG-28's global-config
convenience resolver is unbuilt and awaitingo
tooling rather than this phase: no import-cycle gate exists in CI at all (G12),
and no `fast-check` property in the package rds
that `hasForbiddenNameByte` permits a space inside a header name, surfaced here
because `clientIdentityStep` is the first calied
header name into it — Phase 1 code, reported not touched.
@Wahbeh-Mohammad
Wahbeh-Mohammad merged commit 350435a into 18-phase-7 Aug 27, 2026
3 checks passed
@Wahbeh-Mohammad
Wahbeh-Mohammad deleted the 19-phase-7a-configuration-platform-primitives branch August 27, 2026 09:50
Wahbeh-Mohammad added a commit that referenced this pull request Aug 27, 2026
Ships the layered configuration model, the injectable time seam, the proxy
model, and the shared date/identifier/equality/retryability primitives several
earlier phases were about to duplicate, per
product-spec/16-configuration.md (CFG-1..CFG-38), appendix C's RECOV-33 and
NFR-15, and
docs/superpowers/specs/2026-07-28-phase7a-configuration-design.md.

New `packages/core/src/config/`, no `index.ts` barrel
(docs/knowledge/module-organization.md:18 bans internal barrels; consumers
import the files directly):

- `configuration.ts` — `Configuration` (an interface over a hidden
  `LayeredConfiguration`), `ConfigurationBuilder`, the CFG-14 key constants,
  the CFG-13 global slot, and `defaultConfiguration()` (CFG-1..CFG-14, CFG-37,
  CFG-38). Three tiers, not four: Node has no ambient key/value store distinct
  from `process.env`, so the *production* property seam is empty while the seam
  itself stays substitutable — which is what makes CFG-3/CFG-4's
  normalized-vs-raw split, and CFG-24/CFG-26's property tier, testable at all.
- `duration.ts` — CFG-7's grammar (ISO-8601, `<number><unit>`, bare
  milliseconds), split out of `configuration.ts` on one-concept-per-file.
  Rejects the ambiguous month designator rather than guessing.
- `clock.ts` — `Clock`/`defaultClock` (CFG-15..CFG-17). One wait primitive, not
  the reference's blocking-sleep/scheduled-delay pair: Node has no carrier
  threads to distinguish them against. `sleep` rejects with the signal's own
  abort reason, which is this runtime's expression of CFG-17's "re-assert the
  cancellation status before propagating".
- `proxy.ts` — `ProxyOptions`, `createProxyOptions`, `formatProxyOptions`,
  `shouldBypassProxy`, `resolveProxyOptions` (CFG-22..CFG-28). Types and
  resolution only; no concrete `Transport` consumes them until Phase 8, the
  same `Serde<T>`-before-`codec-json` precedent Phase 2 set. CFG-22's masking is
  a free function, not a `toString()` interface member — every object satisfies
  such a member through `Object.prototype`, so declaring it would state a
  contract the type cannot enforce.
- `http-date.ts` — `formatHttpDate`/`parseHttpDate` (CFG-29..CFG-31), the
  module 5a's `pacing.ts` imports instead of keeping a private parser. Never
  `Date.parse`; every field range-checked, and `Date.UTC` avoided because it
  maps a four-digit year below 100 onto the
- `retryable.ts` — `RETRYABLE_STATUSES`/`isRetryableStatus` (CFG-35), the one
  definition 5a's `classify.ts` re-exports.
  mutation: CFG-35 calls this set a hard contract, and a contract a caller can
  edit is not one.
- `identifiers.ts` — `randomUuid` over `globalThis.crypto` (CFG-32), no
  `node:crypto`.
- `equality.ts` — `deepEqual`/`deepHash` (CFG-33, CFG-34). `@internal`; no
  requirement gives a caller access.
- `build-info.ts` — `getBuildInfo` (CFG-36), feature-detected across Node, Bun,
  Deno, and browsers, resolved on first accehe
  module keeps `sideEffects: false`.
- `client-identity-step.ts` — `clientIdentitposes
  onto the first existing value and preserves every other one, which needs the
  value list rebuilt explicitly since `Headet
  `set` and one-more `add`.

NFR-15 closes via build-time codegen: `packamjs`
writes `src/generated/version.ts` from `package.json` as the `prebuild` step of
both the package and root `build`. No runtimneeds
`node:fs`/`import.meta.url` tricks that woulalf of
core's runtime floor reporting the `unknown` placeholder NFR-15 forbids. The
generated file is committed so an unbuilt `brsion.

Barrel: every symbol above except `deepEqual`/`deepHash` and
`clientIdentityStep`, whose `StepDescriptor` return type is part of the still-
internal pipeline authoring surface (open it

Three review passes — shape, adversarial, red
rules. Four defects they caught that would o

- `HTTP_PROXY=http://proxy:80` resolved to `ecial
  scheme's default port to the empty string,
  `http://p:80` from `http://p`. CFG-25 bans *guessing* an absent port, not
  discarding one the operator wrote.
- `defaultConfiguration().getInt('constructor', 7)` threw `TypeError` on Node
  against the built artifact — `process.env[
  `Object.prototype.constructor`, so `getString` returned a function while its
  signature said `string`, breaking CFG-5/6/he
  production wiring.
- `NO_PROXY="*a*a*a*a*a*a*a*a*a*b"` against he
  event loop for 38 seconds. `shouldBypassProxy` is synchronous on the
  per-request path. The regex glob is now a ms),
  which also fixed an escaped `\*` matching nothing.
- `resolveProxyOptions` threw a bare `URIErr
  credentials — `decodeURIComponent` sat outside the `try`, against CFG-24's
  explicit "MUST NOT throw".

Also fixed: `sleep` silently collapsed any damp to
~1ms, turning a config typo into a hot retry loop; and an unsanitized ambient
`navigator.userAgent` would have made the deject
every outbound request with a `HeaderValidationError`.

Tests: 63 new across eleven colocated files plus twelve Node-runtime cases in
`test/node-conformance/config-primitives.tesmer,
and WebCrypto behavior that is Bun's independent implementation, not Node's).
Each header cites the IDs it exercises. The ts
that were coverage rather than verification — one whose assertion was the
implementation body, and one where wiring `msed
every assertion in both suites, leaving CFG-16's whole point unverified. Each
rewrite is proven by applying the named mutaand
reverting.

Deliberate deferrals, registered in docs/open-items.md (G1..G19) rather than
left silent: CFG-24's warning half and a faity
both wait on 7b's `Logger` (G10, G14); CFG-35's throwable axis is 5a's (G5);
`clientIdentityStep`'s barrel promotion and hever
phase publishes the pipeline surface (G1, G11); CFG-28's global-config
convenience resolver is unbuilt and awaitingo
tooling rather than this phase: no import-cycle gate exists in CI at all (G12),
and no `fast-check` property in the package rds
that `hasForbiddenNameByte` permits a space inside a header name, surfaced here
because `clientIdentityStep` is the first calied
header name into it — Phase 1 code, reported not touched.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant