Skip to content

docs(reference): document httpSurface, the redaction trio, and the token parsers - #746

Open
rejifald wants to merge 3 commits into
mainfrom
claude/interesting-elbakyan-1d8366
Open

docs(reference): document httpSurface, the redaction trio, and the token parsers#746
rejifald wants to merge 3 commits into
mainfrom
claude/interesting-elbakyan-1d8366

Conversation

@rejifald

@rejifald rejifald commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Seven value exports on the stitchapi root barrel appeared on no page under apps/docs/content/docs — not in Reference, not in any guide:

Export Source Status before
httpSurface ./surface undocumented (graphqlSurface / verdictOf had one mention each)
registerSecretKey, isSecretKey, redactSecretsDeep ./util undocumented
duration, size, rate ./util undocumented

Are any of them internal-but-exported?

No — that was the first thing checked, and all seven are deliberately public with a written reason. The barrel comments in packages/core/src/index.ts argue each one, and public-api-surface.spec.ts pins the parsers and verdictOf present while pinning classifyStatus / httpInterpret absent ("three names on the barrel for one decision invites composing the wrong one"). So all seven are documented, on the Reference pages that already own their neighbours — no new pages, no manifest entries beyond a description re-sync.

What changed

reference/helpers.mdx — two new sections:

  • Secret redaction — the trio, framed off the denylist they widen (exact names vs. contained stems), with apiKey({ in: 'query', name })'s automatic registration named as the reason most callers never touch the manual hook.
  • Duration, size, and rate tokens — the three grammars (CONTRACT.md P17/P25), each tied to the fields it actually backs (timeout.total, cache.ttl, circuit.cooldown · serve.body.max, @stitchapi/shell's buffer.max · throttle.rate), plus why rate.parse throws where the other two fall back to undefined: a cap's fallback leaves the ceiling where it was, but a rate's would silently remove the limit. Both directions are documented — see the note on the stacked core change below.

reference/surfaces.mdx — a httpSurface subsection under "http — the default": the default as a named value (compose resolves an omitted kind to it, so __config.kind is never an empty slot), and the one place importing it earns its keep — kind: httpSurface opting a member out of a surface inherited via extends. This page was also the only Reference page with no See also (AUTHORING rule 7); it has one now.

Reviewer notes

Three anti-pattern callouts (AUTHORING rule 10), the first two from behaviour verified against the built runtime, not inferred from source:

  • isSecretKey('authorization') and isSecretKey('x-api-key') are false. Headers are scrubbed against a separate denylist (redactHeaders), so reaching for this predicate as a header check silently under-redacts. This is the one finding here I'd most want a second pair of eyes on.
  • size on a *.chars field is a category errorstream.buffer.chars / trace.body.chars count UTF-16 code units of decoded text, not bytes off the socket. This one got sharper under the rename: the old parseBytes said "bytes" at the call site and size does not, so the type is now the only thing warning the reader.
  • Don't compose httpSurface.interpret into your own surface — it ends in "the body is the value", the http surface's own choice of result. Compose verdictOf instead. This is exactly the mis-composition the barrel comment warns about.

Two adjacent fixes the sweep turned up:

  • guides/resilience/throttle.mdx linked "duration token" at /docs/reference/config-types, a page that says nothing about the grammar. It now points at helpers#parseduration, which does (rule 6, one source of truth per fact).
  • The manifest description for reference/helpers was stale; re-synced.

Known gap left out of scope: registerSecretKey / isSecretKey / redactSecretsDeep are not in the FUNCTIONS pin list in packages/core/test/public-api-surface.spec.ts, so an accidental removal of the trio would still slip past the suite. Deliberately not fixed here rather than widening a docs-only diff into core's tests; being handled separately.

Stacked on the core rename

The three parsers this page was written to document became parse/format pairs while this
PR was open — parseDuration/parseBytes/parseRate are now duration/size/rate, one
namespace per dimension, in the shape bytes uses. That core change is merged into this branch so
twoslash has a surface to check against; it drops out of this diff once it lands on main.
Review it as a stack — the core commit first, this docs commit second.

The three subsections are written against the new API and gained the encode direction:

duration.format(90_000); // '1.5m'
size.format(1537); // '1537b' — '1.5009765625kb' is exact and useless
rate.format({ count: 2, per: 1000 }); // '2/s'

format is the exact inverse of parseparse(format(v)) returns v unchanged — which is
the clause the page leans on when it says a token is safe to write back. It is also where these
differ from ms, whose ms(90_000) is '2m' and reads back as 120 000.

One new anti-pattern callout came with the new direction rather than with the rename: format is
for values a person reads or re-authors, never for an emitted field.
Every emitted duration stays
a raw-ms number (P17's complement), and formatting one into a payload turns a number every reader
can use into a string each of them has to parse.

The known gap named below is now closedpublic-api-surface.spec.ts pins duration/size/
rate present as parse/format pairs, pins the three old names absent, and the redaction trio is
no longer the only unpinned group. That landed with the core change, not here.

Gates

Gate Result
pnpm test (apps/docs) 69 passed, 1 skipped
pnpm check:types (apps/docs) clean
pnpm check:docs-links 116 routes, all resolve
pnpm check:format clean
pnpm gen:docs no-op (0 stubs, no diff)

Re-verified after the rename: the real next build ran end to end on the merged branch, so twoslash type-checked every block against the new surface, and it was confirmed non-vacuous by putting a stale parseDuration import back into one block and watching the build fail on it. Twoslash runs at next build, not in the gates above, so all 25 blocks on the three edited pages were first run through createTwoslasher with the drafts-twoslash.spec.ts config — 25/25 green, and the harness was confirmed to fail a deliberately broken block rather than pass vacuously. The pre-push build-docs hook then ran the real next build end to end, so twoslash has since verified them for real.

🤖 Generated with Claude Code

@rejifald

Copy link
Copy Markdown
Owner Author

Stacked on #753 (feat(core)!: the token grammars become parse/format pairs), which is now open against main.

#753 is merged into this branch so twoslash has a surface to check against — that is why the core files show up in this diff. Merge #753 first; those commits drop out of here once it lands, leaving this PR docs-only as intended.

rejifald added a commit that referenced this pull request Aug 16, 2026
`parseDuration`, `parseBytes` and `parseRate` are replaced by `duration`, `size`
and `rate` — one namespace per dimension, each carrying both directions, in the
shape `bytes` uses (`bytes.parse` / `bytes.format`).

The grammars only ever decoded. Making them public (#746) gave them callers who
need to write a token back — a CLI printing the cap it enforced, a config
round-trip, an error message quoting a limit in the grammar its author wrote —
and each would have hand-rolled an encoder, which is the drift the export exists
to prevent, running backwards.

`format` is the EXACT inverse of `parse`: `parse(format(v))` returns `v`
unchanged for every value `parse` can produce, pinned as a property over the
whole numeric range rather than a table of cases. This is where the pair departs
from `ms`, whose `ms(90_000)` is `'2m'` and reads back as 120_000 — a lossy
encode is fine in a log line and disqualifying in anything that writes a value
back, and P25's "a typo can never widen a cap" only holds if the encode
direction cannot widen one either. Where no unit divides cleanly the base unit
wins: `90_001` is `'90001ms'`, `1537` is `'1537b'` and not the exact-but-
unreadable `'1.5009765625kb'`.

The `ms`/`bytes` one-function overload is structurally unavailable here: P17/P25
widen every authored field to `number | string` and each read site funnels
through the parser, so `parse(5_000)` must return `5_000`. Overloading the
number arm would have broken every call site core makes of its own rule.

Hard break, no alias (P19 scopes that obligation to the GA channel; this is rc).
The old names are pinned absent from the barrel so they cannot drift back.

Bundle: +0.41 / +0.20 KB gzip (entry / `import { stitch }`). Two thirds of the
naive cost was recovered first — internals call plain functions with the
namespaces as a thin facade, and each `format`'s unit table lives inside its
function, since at module scope the minifier merges adjacent tables into one
`var` statement that a live parse-side declarator then pins. What remains is
~0.1 KB: esbuild will not split an object literal to drop a dead property, so
`format` ships wherever `parse` is live. The advertised whole-entry figure
crosses 24 → 25 kB; propagated across the six `bundle-advertised-size` sites.

Reference docs for the pair land in #746, which documented the parsers this
replaces.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@rejifald
rejifald force-pushed the claude/interesting-elbakyan-1d8366 branch 2 times, most recently from ee3ba09 to 3189a9f Compare August 16, 2026 14:57
rejifald and others added 3 commits August 16, 2026 18:06
…ken parsers

Seven value exports on the `stitchapi` root barrel appeared on no page under
`apps/docs/content/docs` — not in Reference, not in a guide. Each is deliberately
public with a documented reason (the barrel comments in `packages/core/src/index.ts`,
and `public-api-surface.spec.ts`, which pins the parsers and `verdictOf` present
while pinning `classifyStatus`/`httpInterpret` ABSENT), so all seven are covered on
the Reference pages that already own their neighbours rather than on new pages.

`reference/helpers.mdx` gains two sections:

- **Secret redaction** — `registerSecretKey` / `isSecretKey` / `redactSecretsDeep`,
  framed off the denylist they widen (exact names vs. contained stems), with
  `apiKey({ in: 'query', name })`'s automatic registration named as the reason most
  callers never touch the manual hook.
- **Duration, size, and rate tokens** — `parseDuration` / `parseBytes` / `parseRate`
  (CONTRACT.md P17/P25), each tied to the fields it actually backs, plus why
  `parseRate` throws where the other two fall back to `undefined`: a cap's fallback
  keeps the ceiling where it was, but a rate's would silently remove the limit.

`reference/surfaces.mdx` gains a `httpSurface` subsection under "http — the default":
the default as a named value (`compose` resolves an omitted `kind` to it, so
`__config.kind` is never an empty slot) and the one place importing it earns its
keep — `kind: httpSurface` opting a member out of a surface inherited via `extends`.
The page was also the only Reference page with no `See also` (AUTHORING rule 7); it
has one now.

Two anti-pattern callouts, both verified against the built runtime rather than
inferred from the source:

- `isSecretKey('authorization')` and `isSecretKey('x-api-key')` are FALSE — headers
  are scrubbed against a separate denylist (`redactHeaders`), so reaching for this
  as a header check silently under-redacts.
- `parseBytes` on a `*.chars` field is a category error: `stream.buffer.chars` and
  `trace.body.chars` count UTF-16 code units of decoded text, not bytes.

A third steers surface authors to `verdictOf` rather than `httpSurface.interpret`,
which is exactly the mis-composition the barrel comment warns about — that hook ends
in "the body is the value", the http surface's own choice of result.

Two adjacent fixes the sweep turned up:

- `guides/resilience/throttle.mdx` linked "duration token" at
  `/docs/reference/config-types`, a page that says nothing about the grammar. It now
  points at `helpers#parseduration`, which does (one source of truth per fact).
- The manifest description for `reference/helpers` was stale; re-synced.

Gates: `pnpm test` (69 passed, 1 skipped) and `pnpm check:types` in apps/docs,
`pnpm check:docs-links` (116 routes) and `pnpm check:format` at the root, and
`pnpm gen:docs` stays a no-op. Twoslash runs at `next build`, not in those gates, so
all 25 blocks on the three edited pages were run through `createTwoslasher` with the
`drafts-twoslash.spec.ts` config — 25/25 green, harness confirmed to fail a
deliberately broken block rather than pass vacuously.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`reference/seam.mdx` (#743) links to `reference/surfaces`, but nothing pointed
back — and the surfaces page mints seam members in "Both spellings, one engine"
(`api.stitch({ kind: downloadSurface, … })`), so it is a real neighbour, not just
a co-resident of the Reference folder. AUTHORING rule 7 wants that link both ways.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The three parsers this page documented became namespace pairs — `parseDuration`,
`parseBytes` and `parseRate` are now `duration`, `size` and `rate`, each carrying
both directions in the shape `bytes` uses.

The three subsections are rewritten against the new API. Each keeps the framing
it had — the fields the grammar actually backs, the `chars` category-error
callout, the fail-loud asymmetry on `rate` — and gains the encode direction and
the round-trip clause that makes it usable: `parse(format(v))` returns `v`
unchanged, which is what separates these from `ms`, whose `ms(90_000)` is `'2m'`
and reads back as 120_000.

Three things the rename changed rather than just renamed:

- a new anti-pattern callout on `duration` — `format` is for values a person
  reads or re-authors, never for an emitted field, which stays a raw-ms number
  (CONTRACT.md P17's complement). The encode direction is new, so this way of
  misusing it is new too;
- the `chars` callout now says why it matters MORE than it did: `size` no longer
  spells `Bytes` at the call site, so the type is the only thing left warning a
  reader that a `'64kb'` on decoded text is a category error;
- `rate.format` rejects the same three values `rate.parse` throws on, rather than
  writing a token that would throw on the way back in.

`throttle.mdx`'s "duration token" link follows the heading: `helpers#parseduration`
→ `helpers#duration`. The manifest description says "token grammars" where it said
"parsers", since the page no longer documents only the decode half.

Verified by the real `next build`, so twoslash type-checked every block against
the new surface — and confirmed non-vacuous by putting a stale `parseDuration`
import back into one block and watching the build fail on it.

Stacked on the core change (`feat(core)!: the token grammars become
`parse`/`format` pairs`), merged in here so twoslash has a surface to check
against. That commit drops out of this diff once it lands on main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rejifald
rejifald force-pushed the claude/interesting-elbakyan-1d8366 branch from 3189a9f to 492b778 Compare August 16, 2026 15:12
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