diff --git a/csharp/03-nullability-and-the-type-system.md b/csharp/03-nullability-and-the-type-system.md index a2bdaa1..7ed5b83 100644 --- a/csharp/03-nullability-and-the-type-system.md +++ b/csharp/03-nullability-and-the-type-system.md @@ -15,14 +15,14 @@ public static class UserParser if (dto is null) return new Result.Err("dto was null"); if (string.IsNullOrWhiteSpace(dto.Email)) return new Result.Err("email required"); - var id = UserId.From(dto.Id); // validated factory mints the brand (3.7) + var id = UserId.From(dto.Id); // validated factory generates the brand (3.7) var roles = dto.Roles ?? []; // null from the wire collapses to empty, here (3.4) return new Result.Ok(new User(id, dto.Email, roles)); } } ``` -The boundary receives a nullable `UserDto?` and either returns a fully non-null `User` or a typed failure — no `!`, no `default!`, no `dynamic` (3.1, 3.3, 3.5). The one place a `null` from the wire is handled is explicit and local (3.4). `UserId` is a branded value type minted by a validating factory (3.7), and every public field is a `record` property carrying value semantics (3.8). The interior never sees an absence value it did not ask for. +The boundary receives a nullable `UserDto?` and either returns a fully non-null `User` or a typed failure — no `!`, no `default!`, no `dynamic` (3.1, 3.3, 3.5). The one place a `null` from the wire is handled is explicit and local (3.4). `UserId` is a branded value type generated by a validating factory (3.7), and every public field is a `record` property carrying value semantics (3.8). The interior never sees an absence value it did not ask for. ## Rules diff --git a/csharp/05-methods-and-functions.md b/csharp/05-methods-and-functions.md index 091bb44..b88d94f 100644 --- a/csharp/05-methods-and-functions.md +++ b/csharp/05-methods-and-functions.md @@ -112,7 +112,7 @@ public string Describe() // good — bloc **Reasoning, step by step:** 1. A call with three or more positional arguments is a row of unlabelled values at the call site — `Settle(amount, rate, account, true)` — where a reader cannot tell which `decimal` is which and a transposed pair compiles silently. Collecting them into an immutable `record` gives every argument a name at the call site, lets the caller use object-initializer or `with` syntax, and makes adding a field a non-breaking change instead of another positional slot (chapter [06](./06-types-and-data-modeling.md)). -2. The record also becomes the home for the validation and the defaults: `required` members force the caller to supply what matters, `init` defaults fill the rest, and the parse-don't-validate boundary (chapter [03](./03-nullability-and-the-type-system.md)) can mint a proven options value once. Two well-named parameters need no ceremony; the threshold is three, where the loss of labelling starts to cost correctness. +2. The record also becomes the home for the validation and the defaults: `required` members force the caller to supply what matters, `init` defaults fill the rest, and the parse-don't-validate boundary (chapter [03](./03-nullability-and-the-type-system.md)) can generate a proven options value once. Two well-named parameters need no ceremony; the threshold is three, where the loss of labelling starts to cost correctness. **Worked example:** ```csharp diff --git a/csharp/06-types-and-data-modeling.md b/csharp/06-types-and-data-modeling.md index fb813eb..e3ae991 100644 --- a/csharp/06-types-and-data-modeling.md +++ b/csharp/06-types-and-data-modeling.md @@ -109,7 +109,7 @@ public readonly record struct ExpiryMonth : new Result.Err("month out of range"); } ``` -**Enforcement:** review of boundary modules; the private constructor makes the factory the only mint. +**Enforcement:** review of boundary modules; the private constructor makes the factory the only generator. ### 6.5 — Force construction-time completeness with `required` and `init`, not multi-step setters. diff --git a/ruby/05-methods.md b/ruby/05-methods.md index 08cfdcf..ec87369 100644 --- a/ruby/05-methods.md +++ b/ruby/05-methods.md @@ -39,7 +39,7 @@ def tax_for(subtotal, jurisdiction:) end ``` -`Money` is minted in exactly one place — `Money.parse`, the parse-constructor that validates the integer-cents value before returning an immutable value object. Nothing else creates a `Money`. The public method sits above the two helpers it calls, so the file reads top-down (5.4). Guard clauses assert the preconditions and leave the happy path flush left (5.3). The keyword argument `now:` keeps the call site legible without a positional slot that callers can transpose (5.4 rule). A postcondition pair verifies the sum from both of its parts before return (5.12). Each method holds one level of abstraction — `settle_order` orchestrates named steps and touches no arithmetic itself (5.2). The helpers reach a `Money` only through `Money.parse`, the single parse-constructor that validates (per chapter 03). +`Money` is generated in exactly one place — `Money.parse`, the parse-constructor that validates the integer-cents value before returning an immutable value object. Nothing else creates a `Money`. The public method sits above the two helpers it calls, so the file reads top-down (5.4). Guard clauses assert the preconditions and leave the happy path flush left (5.3). The keyword argument `now:` keeps the call site legible without a positional slot that callers can transpose (5.4 rule). A postcondition pair verifies the sum from both of its parts before return (5.12). Each method holds one level of abstraction — `settle_order` orchestrates named steps and touches no arithmetic itself (5.2). The helpers reach a `Money` only through `Money.parse`, the single parse-constructor that validates (per chapter 03). ## Rules diff --git a/skills/typescript-bun-styleguide/reference/checklist.md b/skills/typescript-bun-styleguide/reference/checklist.md index eb1b804..0caab0f 100644 --- a/skills/typescript-bun-styleguide/reference/checklist.md +++ b/skills/typescript-bun-styleguide/reference/checklist.md @@ -32,7 +32,7 @@ Bun runtime additions on top of `typescript-styleguide`. Additive only — never - Every route declares request AND response schemas; the outbound `.parse()` is a leak tripwire. - Handlers are thin: parse already-validated input, call one plain domain function, return; no Hono types in domain code. Test via `app.request()`, not a live socket or MSW. - One centralized `app.onError(mapError)` maps domain errors to `problem+json`; handlers never craft a 5xx; unknown errors map to a generic 500 carrying only the correlation id. -- Every request carries a `correlationId`, minted or accepted once at the edge, propagated through `AsyncLocalStorage` via `store.run`. +- Every request carries a `correlationId`, generated or accepted once at the edge, propagated through `AsyncLocalStorage` via `store.run`. - Set server timeouts explicitly: `idleTimeout` on the `Bun.serve` config and a per-route `timeout(...)`, kept under the upstream LB idle timeout. - Rate-limit at the edge with bounded store state (LRU max size or Redis TTL), `429` + `Retry-After`. - Health endpoints are honest: liveness does no dependency I/O (restart signal); readiness checks dependencies and fails first during drain (traffic signal). diff --git a/skills/typescript-styleguide/reference/checklist.md b/skills/typescript-styleguide/reference/checklist.md index 20fac76..34af026 100644 --- a/skills/typescript-styleguide/reference/checklist.md +++ b/skills/typescript-styleguide/reference/checklist.md @@ -39,7 +39,7 @@ One section per chapter. Read on demand for a full audit; the SKILL digest cover - Prefer optional `?` over `| undefined` in object types (`exactOptionalPropertyTypes` enforces the difference). - Narrow with the weakest tool that works: discriminant, then `typeof`/`instanceof`/`in`, then a custom guard. - Unit-test every custom type guard (`x is T`) with positive and negative cases. -- Brand domain primitives in high-rigor modules; mint only through a validating constructor (the one sanctioned `as`). +- Brand domain primitives in high-rigor modules; generate only through a validating constructor (the one sanctioned `as`). - Put `readonly`/`ReadonlyArray`/`Readonly` in every public signature. - Constrain every generic; add no gratuitous type parameters; annotate variance (`in`/`out`) on public generic interfaces. - Write erasable syntax only: no `enum`, runtime `namespace`, parameter properties, or `import =`. Consume a codegen `enum` only at the boundary, convert to a union inside. diff --git a/typescript-bun/03-http-services.md b/typescript-bun/03-http-services.md index ab56ef0..43dfc08 100644 --- a/typescript-bun/03-http-services.md +++ b/typescript-bun/03-http-services.md @@ -18,7 +18,7 @@ import { mapError } from './error-map.ts'; // the one domain-error const app = new Hono(); app.use('*', async (c, next) => { // 3.6 — correlate before any handler runs - const correlationId = c.req.header('x-request-id') ?? randomUUID(); // accept inbound or mint + const correlationId = c.req.header('x-request-id') ?? randomUUID(); // accept inbound or generate await runWithRequestContext({ correlationId }, next); // store.run wraps the rest of the request (ch. 06) }); app.onError(mapError); // 3.5 — handlers never craft 5xx; one map owns error → problem+json @@ -35,7 +35,7 @@ app.get( export default { fetch: app.fetch, idleTimeout: 30 }; // 3.7 — Bun.serve picks this up; idleTimeout is the slowloris guard ``` -Every byte is parsed before the handler sees it and every reply field is schema-checked on the way out (3.3); the handler is glue over `getUser`, a function the unit tests call with no HTTP in sight (3.4); failures route through one `onError` (3.5); a correlation id is minted or accepted once and threaded through `AsyncLocalStorage` so every downstream log line carries it (3.6); the server idle timeout is set explicitly on the exported `Bun.serve` config (3.7). This is the Hono-on-`Bun.serve` idiom (3.1) the rest of the chapter justifies rule by rule. +Every byte is parsed before the handler sees it and every reply field is schema-checked on the way out (3.3); the handler is glue over `getUser`, a function the unit tests call with no HTTP in sight (3.4); failures route through one `onError` (3.5); a correlation id is generated or accepted once and threaded through `AsyncLocalStorage` so every downstream log line carries it (3.6); the server idle timeout is set explicitly on the exported `Bun.serve` config (3.7). This is the Hono-on-`Bun.serve` idiom (3.1) the rest of the chapter justifies rule by rule. ## Rules @@ -128,14 +128,14 @@ app.onError((err, c) => { ### 3.6 — Every request carries a correlation id. **Reasoning, step by step:** -1. A middleware registered with `app.use('*', ...)` either accepts an inbound `x-request-id` (so a trace spans services) or mints a fresh UUID when none arrives, via `randomUUID` from `node:crypto`. The id is decided once, at the very front of the request, before any handler or domain call. +1. A middleware registered with `app.use('*', ...)` either accepts an inbound `x-request-id` (so a trace spans services) or generates a fresh UUID when none arrives, via `randomUUID` from `node:crypto`. The id is decided once, at the very front of the request, before any handler or domain call. 2. It propagates through `AsyncLocalStorage` ([06](./06-logging.md)), which works unchanged on Bun, not by threading a parameter through every function. The middleware binds it with `store.run` wrapping the request continuation — `await store.run(ctx, next)` — the scoped form [06 §6.2](./06-logging.md) mandates over the mid-handler accessor it bans for leaking context into whatever runs next on the loop. Any code in the request's async context — domain function, repository, error handler — then reads the id from the store, so correlation survives `await` boundaries without polluting signatures. 3. Every log line during the request carries that id, which makes the one boundary log (3.5) joinable to all that led to it — parity with the kotlin-jvm correlation contract ([kotlin-jvm 06](../kotlin-jvm/06-logging.md)): one id, set at the edge, on every line, across the whole call tree. 4. The canonical log key is `correlationId`, set once here at the edge and read everywhere downstream ([06 §6.2](./06-logging.md)) — one name for the id in the child logger, the `AsyncLocalStorage` store, and every line, so a trace joins without reconciling synonyms. ```ts app.use('*', async (c, next) => { - const correlationId = c.req.header('x-request-id') ?? randomUUID(); // accept inbound or mint + const correlationId = c.req.header('x-request-id') ?? randomUUID(); // accept inbound or generate await runWithRequestContext({ correlationId }, next); // store.run wraps the request continuation, the scoped form ch. 06 §6.2 mandates }); ``` diff --git a/typescript-bun/05-serialization-and-validation.md b/typescript-bun/05-serialization-and-validation.md index da3d7ab..7f1add9 100644 --- a/typescript-bun/05-serialization-and-validation.md +++ b/typescript-bun/05-serialization-and-validation.md @@ -163,6 +163,6 @@ const stored = await s3.file(`thumbs/${id}`).bytes(); // s3.file (S3 ## Cross-references - zod at the boundary, `z.infer` as the single source of type truth: [core 10.7](../typescript/10-api-design.md). Boundary route rule, `unknown` inward, `any` banned, `undefined` over `null`: [core 03's §3.2, §3.5, §3.6](../typescript/03-the-type-system.md). -- Branded `Cents`, integer minor units, the parse-mint constructor: [core 05](../typescript/05-functions.md). +- Branded `Cents`, integer minor units, the parse-generate constructor: [core 05](../typescript/05-functions.md). - Null-versus-absent and time types, JVM parity: [kotlin-jvm serialization](../kotlin-jvm/05-serialization.md). - Parse every boundary, crash-only boot, dependency justification: BUN-3, BUN-1, BUN-4 ([README](./README.md)). Rows parsed at the edge: [persistence](./04-persistence.md). HTTP body limits and handlers: [HTTP services](./03-http-services.md). diff --git a/typescript-bun/06-logging.md b/typescript-bun/06-logging.md index c19f995..27438e9 100644 --- a/typescript-bun/06-logging.md +++ b/typescript-bun/06-logging.md @@ -71,7 +71,7 @@ log().info(`order ${orderId} placed with ${itemCount} items`); // bad — data m **Reasoning, step by step:** 1. Every log line for a request needs the same cross-cutting context: a correlation id, the principal, the tenant. Threading a `logger` or `ctx` parameter through every function to achieve that pollutes signatures all the way down and breaks the moment one layer forgets to pass it. 2. `AsyncLocalStorage` from `node:async_hooks` is the answer, and the direct analog of SLF4J's MDC in [kotlin-jvm/06-logging.md](../kotlin-jvm/06-logging.md) §6.6. Bun ships it through its `node:async_hooks` compatibility, and it works for this pattern: the store follows the async call graph across every `await`, timer, and microtask — the store set before an `await` is the store seen after it. Where the JVM bridges MDC across coroutine suspensions with `MDCContext`, here no bridging is needed. -3. Bind the store once at the boundary with `runWithRequestContext(ctx, fn)` and read it through a `log()` accessor that does `root.child(store.getStore())`. Correlation id and principal then appear on every line in that request's async subtree, no parameter passed. The canonical context key is `correlationId` — one name in the store, the child logger, and every line, so a trace joins downstream without reconciling synonyms ([03 §3.6](./03-http-services.md) mints it at the edge). Use `store.run` to scope it; never `enterWith` mid-handler, which leaks the context into whatever runs next on the loop. +3. Bind the store once at the boundary with `runWithRequestContext(ctx, fn)` and read it through a `log()` accessor that does `root.child(store.getStore())`. Correlation id and principal then appear on every line in that request's async subtree, no parameter passed. The canonical context key is `correlationId` — one name in the store, the child logger, and every line, so a trace joins downstream without reconciling synonyms ([03 §3.6](./03-http-services.md) generates it at the edge). Use `store.run` to scope it; never `enterWith` mid-handler, which leaks the context into whatever runs next on the loop. ```ts app.use('*', (c, next) => runWithRequestContext({ correlationId: c.req.header('x-request-id') ?? randomUUID(), principal: c.get('principal') }, next)); diff --git a/typescript-bun/08-build-and-distribution.md b/typescript-bun/08-build-and-distribution.md index 9fd6c22..1f2e0ef 100644 --- a/typescript-bun/08-build-and-distribution.md +++ b/typescript-bun/08-build-and-distribution.md @@ -119,7 +119,7 @@ await Bun.build({ **Reasoning, step by step:** 1. Supply-chain integrity runs in both directions, and both directions need a guarantee. Outbound: a consumer installing your package wants proof the tarball was built from the source it claims and not swapped by a compromised token. Inbound: your build wants proof the dependency tree it resolves is the exact one that was reviewed. -2. Outbound is npm provenance plus 2FA. `bun publish` carries the tarball, the access flag, and 2FA (`--auth-type`, `--otp`), but as of this writing it does not emit a provenance attestation — there is no `--provenance` flag. So the publish step in CI runs `npm publish --provenance` (from a CI runner with an OIDC identity), which attaches a signed, verifiable link from the artifact back to the commit and workflow that produced it. This is a registry-tooling choice, not a workflow regression: `bun` builds, installs, and audits the package; the one publish call that mints the attestation goes through `npm` until Bun ships provenance. 2FA on the publish step means a stolen token alone cannot push a release, and a laptop cannot mint provenance — which is the point: publishing moves to CI (BUN-4). When `bun publish` gains `--provenance`, the call swaps and the rest of the pipeline is unchanged. +2. Outbound is npm provenance plus 2FA. `bun publish` carries the tarball, the access flag, and 2FA (`--auth-type`, `--otp`), but as of this writing it does not emit a provenance attestation — there is no `--provenance` flag. So the publish step in CI runs `npm publish --provenance` (from a CI runner with an OIDC identity), which attaches a signed, verifiable link from the artifact back to the commit and workflow that produced it. This is a registry-tooling choice, not a workflow regression: `bun` builds, installs, and audits the package; the one publish call that generates the attestation goes through `npm` until Bun ships provenance. 2FA on the publish step means a stolen token alone cannot push a release, and a laptop cannot generate provenance — which is the point: publishing moves to CI (BUN-4). When `bun publish` gains `--provenance`, the call swaps and the rest of the pipeline is unchanged. 3. Inbound is the committed lockfile, no exceptions. `bun.lock` pins every transitive dependency to an exact version and integrity hash, so `bun install` resolves the reviewed tree and not a freshly-floated one. An uncommitted or `.gitignore`d lockfile means every install is an unreviewed code change ([../security.md](../security.md), BUN-4). ```jsonc diff --git a/typescript/03-the-type-system.md b/typescript/03-the-type-system.md index 35b8844..a567b70 100644 --- a/typescript/03-the-type-system.md +++ b/typescript/03-the-type-system.md @@ -77,7 +77,7 @@ parseUser(42); **Reasoning, step by step:** 1. An assertion is an unproven claim to the compiler: "trust me, this is a `T`." The compiler stops checking and believes you. When you are wrong, the failure surfaces far from the lie. -2. Reach for the proven alternatives first: `satisfies` checks a value against a type *without* widening it, and a guard or a parse (zod) establishes the type with a runtime check the compiler can see. When an assertion is genuinely unavoidable, such as minting a brand after validation (3.9) or narrowing a value the compiler cannot follow, it carries a comment stating why the claim holds. +2. Reach for the proven alternatives first: `satisfies` checks a value against a type *without* widening it, and a guard or a parse (zod) establishes the type with a runtime check the compiler can see. When an assertion is genuinely unavoidable, such as generating a brand after validation (3.9) or narrowing a value the compiler cannot follow, it carries a comment stating why the claim holds. **Worked example:** ```ts @@ -149,7 +149,7 @@ expect(isUser({id: 'u1'})).toBe(false); // negative space is man **Reasoning, step by step:** 1. In a structural type system every `string` is interchangeable, so `UserId`, `OrderId`, and a raw email are one type and the compiler will happily pass one where another is meant. A brand attaches a phantom tag: `type UserId = string & {readonly __brand: 'UserId'}`. It costs nothing at runtime (the field never exists) but makes the values nominally distinct. -2. A branded value can only be *created* through a parsing constructor that validates and mints it. That constructor is the single place an `as` is sanctioned (3.4), because the value is proven the line before. +2. A branded value can only be *created* through a parsing constructor that validates and generates it. That constructor is the single place an `as` is sanctioned (3.4), because the value is proven the line before. **Worked example:** ```ts diff --git a/typescript/05-functions.md b/typescript/05-functions.md index d0ff263..214122d 100644 --- a/typescript/05-functions.md +++ b/typescript/05-functions.md @@ -35,7 +35,7 @@ function taxFor(subtotal: Cents, jurisdiction: Jurisdiction): Cents { } ``` -`Cents` is minted in one place — a parse-constructor that validates, then performs the lone sanctioned `as` (3.9). Nothing else in the chapter casts to it: +`Cents` is generated in one place — a parse-constructor that validates, then performs the lone sanctioned `as` (3.9). Nothing else in the chapter casts to it: ```ts function cents(n: number): Cents { @@ -44,7 +44,7 @@ function cents(n: number): Cents { } ``` -The public function sits above the two helpers it calls, so the file reads top-down (5.4). Guard clauses assert the preconditions and leave the happy path flush left (5.3). The single object parameter keeps the call site legible (5.5), and a postcondition pair checks the sum is approached from both of its parts before it returns (5.7). Each function holds one level of abstraction — `settleInvoice` orchestrates named steps and touches no arithmetic itself (5.2). The helpers reach a `Cents` only through the `cents` mint, the single parse-constructor that validates and casts (3.9); `taxFor` guards the rate lookup because `noUncheckedIndexedAccess` (1.3) types the miss as `undefined`, so the flag forces the guard. +The public function sits above the two helpers it calls, so the file reads top-down (5.4). Guard clauses assert the preconditions and leave the happy path flush left (5.3). The single object parameter keeps the call site legible (5.5), and a postcondition pair checks the sum is approached from both of its parts before it returns (5.7). Each function holds one level of abstraction — `settleInvoice` orchestrates named steps and touches no arithmetic itself (5.2). The helpers reach a `Cents` only through the `cents` generator, the single parse-constructor that validates and casts (3.9); `taxFor` guards the rate lookup because `noUncheckedIndexedAccess` (1.3) types the miss as `undefined`, so the flag forces the guard. ## Rules diff --git a/typescript/06-classes-and-data-modeling.md b/typescript/06-classes-and-data-modeling.md index 80a190f..ad96e69 100644 --- a/typescript/06-classes-and-data-modeling.md +++ b/typescript/06-classes-and-data-modeling.md @@ -17,7 +17,7 @@ function assertNever(x: never): never { // defined once — see 6.5 throw new Error(`Unhandled variant: ${JSON.stringify(x)}`); } -// Parse-then-mint: validate the raw string, then the one sanctioned `as` (6.11). +// Parse-then-generate: validate the raw string, then the one sanctioned `as` (6.11). function toOrderId(raw: string): OrderId { if (raw === '') throw new Error('OrderId must be non-empty'); return raw as OrderId; // sanctioned: validated immediately above @@ -197,7 +197,7 @@ interface User {readonly id: UserId; readonly email: string} function createUser(id: string, rawEmail: string): User { const email = emailSchema.parse(rawEmail); // throws on invalid input - return {id: toUserId(id), email}; // constructor-free: parse-mint then assign (3.9, 6.11) + return {id: toUserId(id), email}; // constructor-free: parse-generate then assign (3.9, 6.11) } ``` diff --git a/typescript/10-api-design.md b/typescript/10-api-design.md index 78f38ba..b5bd613 100644 --- a/typescript/10-api-design.md +++ b/typescript/10-api-design.md @@ -1,6 +1,6 @@ # 10 — API Design -Designing the surface other code imports. A package's public API is a promise: every name you export is a contract you keep for every caller, in every refactor, until a major version lets you break it. This chapter is about exporting the least, exporting it deliberately, and shaping what you do export so the call site reads as a family. The verb taxonomy ([chapter 02](./02-naming-conventions.md)), branded parse-mint boundary ([chapter 03](./03-the-type-system.md)), failure documentation ([chapter 08](./08-error-handling.md)), and cancellation ([chapter 09](./09-concurrency.md)) are the raw material; here they compose into a surface. +Designing the surface other code imports. A package's public API is a promise: every name you export is a contract you keep for every caller, in every refactor, until a major version lets you break it. This chapter is about exporting the least, exporting it deliberately, and shaping what you do export so the call site reads as a family. The verb taxonomy ([chapter 02](./02-naming-conventions.md)), branded parse-generate boundary ([chapter 03](./03-the-type-system.md)), failure documentation ([chapter 08](./08-error-handling.md)), and cancellation ([chapter 09](./09-concurrency.md)) are the raw material; here they compose into a surface. ## What good looks like @@ -14,7 +14,7 @@ export {UserNotFoundError} from './errors.js'; import {z} from 'zod'; export type UserId = string & {readonly __brand: 'UserId'}; export const UserSchema = z.object({ - id: z.uuid().transform((s): UserId => s as UserId), // parse-mint (ch. 03) + id: z.uuid().transform((s): UserId => s as UserId), // parse-generate (ch. 03) email: z.email(), }).readonly(); export type User = z.infer; // single source of type truth @@ -125,7 +125,7 @@ listUsers(options?: ListOptions & CallOptions): AsyncIterable {} / ### 10.7 — Put a zod schema at every external boundary; `z.infer` is the single source of type truth for wire data. **Reasoning, step by step:** -1. Data crossing the wire — a `fetch` response, a request body, a queue message — arrives as `unknown` (3.2). It must be parsed into a domain type before the interior touches it, and the parser is a zod schema that validates shape and mints any brands (3.9) in one pass. +1. Data crossing the wire — a `fetch` response, a request body, a queue message — arrives as `unknown` (3.2). It must be parsed into a domain type before the interior touches it, and the parser is a zod schema that validates shape and generates any brands (3.9) in one pass. 2. The TypeScript type for that wire data is *derived from the schema*, never written alongside it: `type User = z.infer`. A hand-written `interface User` maintained next to the schema is two sources of truth that drift the moment one is edited and the other forgotten — and the drift is silent, because each compiles. One declaration, inferred, cannot drift from itself. 3. Parse at the boundary, then trust the type inside. The schema runs once where the data enters; downstream code consumes the inferred type with no re-validation. Call `.readonly()` on the schema: it freezes the parsed value at runtime *and* makes `z.infer` yield a readonly type, so the data is immutable from birth (3.10) with no separate `Readonly<>` wrapper to remember. @@ -193,7 +193,7 @@ for await (const u of listUsers()) if (u.isAdmin) break; // break stops early; l ## Cross-references - Client verb taxonomy (`get`/`list`/`create`/`upsert`/`update`/`delete`/`begin`) and call-site naming: [02-naming-conventions.md](./02-naming-conventions.md). -- Branded primitives, parse-mint, `unknown` at the boundary, `readonly` signatures: [03-the-type-system.md](./03-the-type-system.md). +- Branded primitives, parse-generate, `unknown` at the boundary, `readonly` signatures: [03-the-type-system.md](./03-the-type-system.md). - `max-params 3` forcing options objects: [01-formatting-and-tooling.md](./01-formatting-and-tooling.md); options-object construction: [05-functions.md](./05-functions.md). - `@throws` versus `Result`, documenting failure modes: [08-error-handling.md](./08-error-handling.md). `{ signal }` cancellation: [09-concurrency.md](./09-concurrency.md). - Barrels at the boundary, import cycles, tree-shaking: [12-module-organization.md](./12-module-organization.md). Semver and breaking-change discipline: [git-and-code-review.md](../git-and-code-review.md).