<!-- GENERATED from docs/standards/coding-standard.md — do not edit by hand; run pnpm build:skill-references -->

# Platform Coding Standard — *Lazy inside, strict at the seam*

**Status:** Canonical. This is the instruction set for every coding agent (skills + subagents) that writes or ports `@platform-modules/*` code.
**Audience:** AI coding agents first, humans second. Authored as ordered ladders + checklists, not prose — agents follow ladders.
**Scope:** TypeScript / ESM monorepo packages (`@platform-modules/*`). `-react`/UI packages add the §6 overlay.
**Sits under:** `docs/specs/2026-06-13-monorepo-architecture-synthesis.md` (the layer DAG + governance patterns). This doc governs *how code is written*; that doc governs *what gets built and where*.
**Amendments:** 2026-07-07 — whether-to-build reconciled to the generalizability directive (owner, 2026-06-18; supersedes adoption-count); dead `@platform-modules/events` seam removed (dissolved 2026-06-14); §4 gains dependency-classification + agent-legibility boxes; R3 requires a bounded cache. **Convention: every future rule change carries a date + what it supersedes** — an undated rule edit is how this doc drifted behind doctrine.

---

## 0. The one principle

> **Be lazy in the implementation. Be strict at the seam.**

Two boundaries exist, and most code-quality arguments are confused because they only see one:

| Boundary | Question it answers | Owned by |
|---|---|---|
| **Trust boundary** | "Can untrusted data corrupt/leak state?" | every line, always |
| **Reuse/version boundary** (the *seam*) | "12 apps consume this published contract — does it survive an implementation swap?" | the module's public exports |

- **YAGNI-minimalism** (build only what's needed; native-first; no speculative structure) guards the trust boundary and minimizes everything else. Its unit of analysis is *a snippet inside one app*. It has **no concept of a reuse/version seam** — because it never reasons about a published package consumed by N apps across versions.
- The **platform** lives or dies on that second boundary. A `@platform-modules/*` package *is* its seam: a typed contract that must stay stable while the implementation underneath is swapped, optimized, or re-ported.

So we **apply the YAGNI/minimalism ladder for everything inside a function**, and **add a boundary contract for everything a module exposes.** The **over-engineered** style (speculative layers, cargo-cult ceremony) violates *both* — heavy inside, leaky at the seam — and is rejected outright.

---

## 1. The three styles, judged

| | **Over-engineered** (speculative generality) | **YAGNI-minimalism** | **Target** (lazy impl + typed seam) |
|---|---|---|---|
| Inside a function | speculative layers, classes, DI chains, hand-rolled algos, custom infra | native > stdlib > one line; YAGNI | native primitives (`Intl.*`, `URL`), pure functions, zero deps |
| At the seam | leaks ORM rows, no typed contract, no tree-shaking | *no seam concept* — inlines, "upgrade later" TODOs | typed generics, subpath exports, `sideEffects:false`, co-located tests |
| Optimizes for | nothing (cargo-cult robustness) | **LOC reduction in one app** | **reuse across apps** (mostly) |
| Verdict | **Reject.** This is exactly what the platform's YAGNI + generalizability governance (`CLAUDE.md` §3) exists to prevent. | **Adopt the ladder for inside-a-function** (§2). Insufficient *alone* as a library standard — see §1.1. | **Closest to target.** It is the minimalism ladder *already wrapped in a library seam* (exemplified by the `@platform-modules/i18n` pilot). But it is not a clean exemplar — see §3. |

### 1.1 Why minimalism alone is insufficient here (the precise delta)

Do **not** say "YAGNI ignores security" — false. The minimalism ladder explicitly refuses to cut trust-boundary validation, data-loss handling, security, or a11y. The gap is narrower and unassailable:

> **Pure YAGNI/minimalism reasons over one-app snippets, not published packages. It cannot see the reuse/version seam — the one boundary this platform is built on.**

Concretely, minimalism-alone has no position on these, because the question never arises in a single app:
- **Types as contract** — the export signature *is* the API; it must be designed, not incidental.
- **Packaging** — subpath exports, `sideEffects:false`, zero-dep core vs peer-dep adapters, semver.
- **Swap survival** — the seam must outlive `tax/rates-table` → `tax/stripe`, `flatpickr` → native, source-impl → re-port.
- **Seam errors** — `throw new HTTPException(404)` is fine in one app; a reusable seam must throw *typed, contextful* errors (the user wants "easy to debug").
- **"Upgrade later" deferral comments at a core seam are a smell** — "deliberately under-built, fix later" is acceptable inside an app, **not** at a contract 12 apps depend on. The seam *is* the product. (Such comments are still fine on adapter *internals*.)

Minimalism measures success in lines deleted. We measure success in **coupling avoided and seams kept stable.** A slightly longer, well-typed, memoized seam beats a one-liner that leaks its implementation.

---

## 2. The implementation ladder (inside a function — YAGNI / minimalism)

Before writing code, stop at the first rung that holds:

```
1. Does this need to exist?        → no: skip it            (YAGNI = nobody asked: no current/near consumer.
                                                              Whether-to-BUILD = generalizability test, CLAUDE.md §3
                                                              — NOT adoption count; superseded 2026-06-18)
2. Platform-native primitive?      → use it                 (Intl.*, URL, Temporal, Web Crypto, structuredClone…)
3. Stdlib / already-installed dep? → use it
4. One line?                       → one line
5. Only then: the minimum that works
```

Hard floor (never on the chopping block): **trust-boundary validation, data-loss handling, security, accessibility.**

Refuse, every time:
- Speculative layers (repository/service/controller around one call) — add the layer when the *2nd caller* appears, not before.
- Hand-rolling what the runtime ships (sort, cache, date-parse, uuid, debounce).
- A class where a pure function + data closes it.
- A dependency you'd use < 5% of, or that auto-mutates audit-relevant data (e.g. self-updating tax rates).
- Identity wrappers / twin types that exist only to look layered (see §3, flaw B).

---

## 3. The pilot is not clean — it *proves* the standard must be explicit

`@platform-modules/i18n` shipped. Reading its source surfaces three defects that are the **exact failure modes** this standard names. They are not nitpicks — each is a class of bug that porting-from-prior-apps smuggles into reusable seams.

**Flaw A — source-app assumption baked into a seam default.**
`packages/i18n/src/format.ts:9`
```ts
export function formatCurrency(locale: string, value: number, currency = 'ILS'): string
```
`'ILS'` is a source-app home-market assumption frozen into a default that 12 apps will inherit silently. A US/EU consumer gets shekels and never sees the bug until production.
> **Rule R1 — No source-app defaults at a seam.** A reusable export has **no** locale/currency/tenant/region default. Make it required, or resolve it from explicit config. Porting MUST strip the source app's home-market assumptions. This is the #1 thing prior-app code smuggles in.

**Flaw B — over-engineered ceremony that YAGNI would delete.**
`packages/i18n/src/index.ts:1-19` — `I18nConfig<L>` and `I18n<L>` are structurally identical, and `createI18n` is an identity copy:
```ts
export function createI18n<L>(config) { return { locales: config.locales, defaultLocale: config.defaultLocale, messages: config.messages } }
```
Two names for one shape + a function that returns its input = ceremony with zero contract value. The pilot already has an over-engineering smell.
> **Rule R2 — One type per shape; no identity constructors.** A factory earns its place only by *validating, normalizing, freezing, or deriving*. If it returns its input, delete it and export the type.

**Flaw C — "use native" done the slow way (the performance line a pure LOC lens misses).**
`format.ts:2,6,10` and `plural.ts:4` construct a fresh `Intl.*Format` / `Intl.PluralRules` **per call**:
```ts
return new Intl.NumberFormat(locale, options).format(value)   // every call rebuilds the formatter
```
Formatting a list of N prices = N formatter constructions — `Intl.*Format` construction is the heavy part; `.format()` is cheap. This is the classic Intl perf bug.
> **Rule R3 — Memoize expensive natives by bounded key (shipped 2026-07-07).** "Use the native primitive" (rung 2) is right; "construct it on every call" is wrong. Cache `Intl.*Format`/`PluralRules`/`RegExp`/`TextEncoder` etc. via the shared bounded memo leaf `@platform-modules/util/memo`, never an unbounded request-keyed map. Unbounded memo at a public seam is a §1 hard-floor DoS bug: attacker-controlled key cardinality can turn "perf optimization" into memory growth. Key on stable primitives or a canonicalized options shape; plain `JSON.stringify` on an options object is not canonical because object key order changes the cache key. A cache that needs invalidation, TTL, or peeking is a `@platform-modules/cache` (L1) concern, not `util/memo` — do not stretch `util/memo` past a bounded pure-memoize seam. Native-and-fast, not native-and-naive.

The takeaway is the whole reason this doc exists: **good instincts (native, pure, typed) are not enough — the rules must be written down**, or every port re-introduces A/B/C.

---

## 4. The boundary checklist (what every `@platform-modules/*` export must satisfy)

A change to a public export isn't done until **all** hold. This is the seam contract — the strict half.

- [ ] **Types are the contract.** Public signatures are explicit and intentional (no inferred `any`, no leaked internal types). Generics constrain (`<L extends string>`), not decorate. Breaking the signature = semver major.
- [ ] **No source-app defaults (R1).** No home-market/tenant/locale/currency baked into a default.
- [ ] **No identity ceremony (R2).** Every exported factory validates/normalizes/freezes/derives, or it's a type.
- [ ] **Expensive natives memoized, bounded (R3, shipped 2026-07-07).** No `new Intl.*`/`RegExp`/encoder rebuilt per call on a hot path; no unbounded cache either — use the shared bounded memo leaf `@platform-modules/util/memo`. Never a second shared memo abstraction. Treat request-keyed unbounded memo as a §1 hard-floor DoS risk. Plain `JSON.stringify` of an options object is key-order-sensitive, so use stable primitive keys or canonicalized option objects.
- [ ] **Zero-dep core; provider SDKs are peers behind an adapter.** Foundation (L0) code imports nothing at runtime beyond sibling `@platform-modules/*` leaves. Provider SDKs (Stripe, AWS, CF) are `peerDependencies` behind an **adapter**, never bundled into core.
- [ ] **Dependency classification (2026-06-17 — semver-cascade-safe).** Sibling `@platform-modules/*` deps = regular `dependencies` pinned `workspace:^` (genuinely-optional → `optionalDependencies`) — **never `peerDependencies`**. Peers are reserved for adopter-supplied externals only: framework (`react`/`react-dom`), provider SDKs, `drizzle-orm`, `@cloudflare/workers-types`. Sibling-as-peer force-majors every dependent on any non-patch bump (the spurious `1.0.0` cascade). Rationale: `CLAUDE.md` §6.
- [ ] **Adapter seam for anything swappable.** Provider/engine variation hides behind a typed seam (`TaxAdapter`, `MailAdapter`). Core depends on the seam, never the provider. The seam survives swaps; impls come and go.
- [ ] **Subpath export per capability.** `"./rtl"`, `"./format"`, … with `sideEffects:false`, so a consumer importing one capability tree-shakes the rest. ESM-only (`"type":"module"`), `types`+`import` conditions, `files:["dist"]`.
- [ ] **Thin core + importable capabilities (R5).** Carry the *most mature prior-app form*: a thin universal core + each generic non-universal capability as its own tree-shakeable subpath (or composed from a sibling module). Never discard a generic mature capability to the host as "app-specific" — only app-*domain* code (this app's entities/rules/schema/wiring) stays host. Adopt, don't invent; freeze the contract only when a **non-reference-app** consumer confirms it. Full ladder: §5A.
- [ ] **Errors are typed, contextful, uniform in shape.** Throw a named error carrying a stable `code` string + *what failed and the inputs that mattered* — not a bare status/`throw "bad"`. Export a **structural type-guard** (`isXError()`) as the cross-package identity check — **never `instanceof` across packages** (deduped copies break it). The platform's "easy to debug" requirement lives here; uniform shape across modules means an agent that learned one module's errors handles all.
- [ ] **Trust boundary enforced at the seam, not assumed.** Whitelist what leaves (response schemas — the one thing the api-endpoint example *kept*), validate what enters. Multi-tenant scoping is enforced in `@platform-modules/db`/`@platform-modules/tenancy`, never trusted from the caller.
- [ ] **One co-located behavioral test per export** (`*.test.ts`, vitest) proving the contract — fallback paths and edge inputs, not just the happy line. Tests pin the seam so the lazy implementation underneath can be rewritten fearlessly — **and double as the export's usage example**: readable standalone, so an agent wires the seam from test + types without reading the impl.
- [ ] **Package is self-describing (agent-legible).** The seam is discoverable and callable from machine surface alone: `exports` map + DTS + a current `docs/registry.json` entry. If an agent must read the impl to wire the seam, the seam fails this box.
- [ ] **Audit-hardened paths carry their conformance test (R4 / §5.1).** If the boundary spec marks the path hardened, the ported/carried security test (`secaudit-*`) is present and green, and the body was *preserved*, not re-derived.

---

## 5. Porting from prior apps (the platform's main code source)

Most code is *ported*, not written fresh. Porting is where over-engineered ceremony and source-app assumptions leak in. The pipeline:

```
source-app impl ──▶ run §2 ladder ──▶ strip source-app assumptions (R1) ──▶ collapse ceremony (R2) ──▶ design the seam (§4) ──▶ pin with a test
```

- **Adoption count = sourcing signal, NOT a build gate (superseded 2026-06-18 by the generalizability test — `CLAUDE.md` §3).** Build at ×0/×1 when the owner needs it AND one contract generalizes to other future app classes. One reference is still not a *contract*: freeze the API only on a **non-reference** consumer (R5 guardrail, §5A).
- **Convergence must be structural, not superficial.** Three wallet tables that *look* alike are not a ledger module until they share the same append/idempotency/balance/typed-reason *contract*. Prove the seam, then port.
- **A re-port is a swap.** If the seam was designed right (§4), swapping source-impl-A for source-impl-B touches no consumer.

### 5.1 Audit-hardened prior-app code — preserve, don't re-derive

Some prior-app paths already passed a security/UX audit; the fixes are **baked into the source** (money · refund · webhook · session · password — see the security-audit boundary specs, e.g. `docs/specs/2026-06-13-auth-engine-security-audit.md`). The §5 pipeline above assumes you *transform* the source. **For audit-hardened code that is precisely the hazard:** an agent re-deriving the logic re-introduces the exact bugs the audit fixed — many were *logic* bugs, not all test-catchable, and the original auditor may be gone, so they will not be re-found.

> **Rule R4 — Preserve the hardened body; design only the seam (Chesterton's Fence).** Where the boundary spec marks a path audit-hardened, port its body **byte-faithfully**. Do not rewrite, "tidy", or re-derive the algorithm — *don't tear down a fence until you know why it was put there.* Many audit fixes are non-obvious logic bugs with no failing test to catch the regression; the rationale may be gone but the hardening is real. R1 (strip source-app defaults) still binds *at the seam*; R2/R3 apply to the *seam wrapper you add*, never to the hardened core. The §2 minimalism ladder does **not** license rewriting audited logic — preservation outranks LOC.

- **Carry the source's executable security tests** (`secaudit-*.spec` and equivalents) in as module conformance tests. With the auditor unavailable, these tests *are* the re-auditor: they fail when a port drifts. An audit-hardened export is not done until its conformance test is present and green (§4).
- **A forced change to the hardened body is a kickback, not a quiet rewrite.** If the seam genuinely cannot wrap the source body unchanged, **stop** and surface a ported-vs-source diff for review — the code analog of the spec-first kickback (`CLAUDE.md` §1).
- **Knowing it's hardened:** the boundary spec cites an audit or marks the path "preserve". A money/auth/webhook path the spec is *silent* on is a spec gap — kick back and amend the spec before porting (never guess hardness).

---

## 5A. Capability layering — thin core, importable growth (Rule R5)

> **Carry the most mature prior-app form, layered — never the laziest form, flattened.** A module is a thin *universal core* + each generic non-universal capability as its own tree-shakeable subpath (or composed from a sibling module). §2 laziness governs the *implementation* of each capability; it must never buy "thin" by *discarding* a mature prior-app capability into the host.

**The failure this fixes (observed, not hypothetical).** Agents shipped `@platform-modules/ai` as a chat-completion-plus-fallback seam and `@platform-modules/i18n` as a flat string table — dumping mature capability (a DB-backed idempotent job runner, a tool-use agent harness, namespaced + lazy-loaded message bundles, a usage→cost layer) into "keep in host" as if it were app-specific. It was generic infrastructure every scaling app needs. **Over-thinning is the mirror of over-engineering and just as wrong:** it strands maturity already paid for and ships a toy where a foundation was asked for. The platform exists to seed *full, scalable* apps — so a module's job is the mature capability set, layered for opt-in, not the smallest thing that compiles.

**R5 — the placement ladder.** First a gate, then for each capability a prior app has that is *not* in the universal core, stop at the first rung that holds:

```
GATE  Does a mature prior app implement this today (or is it cross-class forward-needed per synthesis §3)?
        no  → STOP. YAGNI — do not invent it. (§2 rung 1 still binds.)
        yes → split the capability:  generic mechanism  vs  app-specific impl/config/rules.
1. App-specific residue (this app's entities, business rules, schema field names, concrete wiring)
        → HOST. Always.            (issue-refund's body, deal-moderation rules, the nameHe/nameEn field list)
2. Generic mechanism already owned by / composable from an EXISTING module?
        → ROUTE or COMPOSE there. Never re-subpath it here.
          (ai's job lifecycle COMPOSES @platform-modules/jobs — it does NOT re-implement a runner;
           a11y font-scale/contrast prefs are NOT i18n — own module or host, never an i18n subpath)
3. Generic mechanism, wanted by a CLASS of apps, expressible as a stable contract
        → IMPORTABLE CAPABILITY: its own subpath, sideEffects:false, heavy libs as peers.
          (agent tool-registry, namespaced+lazy i18n loader, usage→price layer, Zod error-map, locale field-picker)
4. Universal to every consumer of this module
        → CORE.
```

**Maturity rule.** When a capability exists in several forms (a thin form in the module, a richer form in a prior app; or differing prior-app forms), adopt the **most mature form** as the basis, then thin its *implementation* via §2 — never write a fresh minimal version and strand the prior app's maturity. *Port the mature form and layer it*, not *rewrite a minimal version and drop the rest*.

**Guardrails — these keep R5 from becoming bloat; all four bind:**
- **Tree-shakeable opt-in.** Every carried capability is a separate entrypoint (§4); a consumer importing only the core pays zero bytes for the rest. *This is what licenses building the capability now* — growth-ready ≠ heavy.
- **Adopt, don't invent.** R5 promotes capability that exists *mature in a prior app* (or is cross-class forward-needed per synthesis §3). It does **not** license speculative frameworks — "never a speculative framework" (`CLAUDE.md` §3) and §2 rung 1 still bind. *Ship maturity you already have; never ship futures you are guessing.*
- **Contract frozen only on non-reference-app convergence.** The reference app is also the first consumer — re-importing into the reference app proves swap-survival, not contract *generality*. A capability adapted from a single source ships **experimental (`0.x` / documented unstable)** until a **non-reference** app (or a clearly convergent second shape) exercises it; only then does it freeze under semver. Honors "convergence > count": build the ×1 mature capability, but do not *promise* its API until a non-reference app confirms the shape.
- **Gate-1 stays at module granularity.** R5 grows capability *inside* an already-justified module; it never spawns a speculative standalone module (rung 2 routes overlap *to* an existing module instead). The delete-test still kills modules whose complexity merely relocates.

**YAGNI, reconciled (read before quoting "default to growth").** YAGNI bars *inventing* surface no one needs. R5 bars *discarding* mature surface you already have. Both serve the seam, from opposite sides. Sequence the two questions:
1. *Should this capability exist at all?* → **YAGNI / the GATE.** No mature prior app and not forward-needed ⇒ stop.
2. *We already have it mature — host or module?* → **R5 ladder.** Generic ⇒ importable; app-domain ⇒ host.

**"Default to growth" applies only inside step 2:** among capabilities that are *already mature*, prefer importable-capability over host-dump. It never licenses building for an imagined future — refusing that is step 1's job.

---

## 6. UI / `-react` overlay (e.g. `@platform-modules/i18n-react`)

On top of §2–§4, UI packages add:

- **A11y is hard-floor, not a polish pass** — semantic elements, `aria-current`/labels, keyboard nav, focus management. Prefer the native accessible primitive (`<input type="date">`) over a JS re-implementation — it's *also* the YAGNI-correct, fewer-lines answer.
- **Responsive is hard-floor, not a polish pass** (UI packages + `-react` + templates). Every UI export MUST work phone→large-screen with no adopter layout rewrite. *(Canonical home of the responsive token contract — `CLAUDE.md` north star points here; edit here, never re-copy.)*
  - **Primitives/components → container queries.** Adapt to the container, NEVER the viewport — a headless primitive does not know the host's page layout. Tailwind v4 `@container` variants over hand-written `@media`.
  - **Page shells/layouts → viewport breakpoints** — the only place viewport width is known.
  - Viewport breakpoints come from `ui-tokens` `@theme` `--breakpoint-*` custom properties; container-query variants use Tailwind's **stock** `--container-*` scale — `ui-tokens` **MUST NOT override `--container-*`** (shared namespace with `max-w-*`/`w-*` — overriding it silently rescales those width utilities). NEVER a hand-typed `768px` literal in a component.
  - DO NOT ship a fixed-width layout container, fixed grid-column count, or desktop-only table. `Table` MUST degrade to a card/scroll pattern on narrow containers.
  - Enforcement = AST rule (mechanical: ban fixed-width layout containers / raw breakpoint literals) **+** multi-viewport Playwright visual test (semantic: legible at 375px). A static rule alone does NOT prove responsive.
- **Hydration directive is a deliberate choice** — pick the minimal island boundary; provider context must cross island boundaries explicitly (see the consumer-harness spec). SSR-safe data fetching.
- **No client dep where a platform/CSS/native feature exists** (date input, `dialog`, `details`, container queries, `Intl` in the browser).
- **One package per framework adapter — flat siblings, never an umbrella.** Each `@platform-modules/<core>-react` is its own package: exactly one core dep + its own `react` peer. *Not* a `@platform-modules/<core>/react` subpath, *not* a shared `@platform-modules/react/*` umbrella. The split is a **dependency-seam split, not a feature split** — a `./react` subpath would give the whole core a `react` peer and poison its zero-dep guarantee (§4); an umbrella would have to dep *every* core it adapts → fan-in hub that breaks swap-survival (drop a core ⇒ dangling subpath) and couples semver (a breaking `react/realtime` force-bumps `react/i18n` consumers). Subpaths are for capability slices *within one dep profile* (`@platform-modules/i18n/rtl|format|plural`); the framework axis (`-react`, future `-astro`/`-vue`) gets its own package. Shared `-react` internals, if they emerge, go in a zero-extra-core leaf (`@platform-modules/util/react`), not an umbrella. Adapter scope is **headless** — hook + render-prop (the `LanguageSwitcher` pattern); the host owns landmarks and app-specific markup (login forms, dropzones).

---

## 7. Wiring into agents

- This file is the source of truth. Skills/subagents that write `@platform-modules/*` code load §0–§2 (the principle + ladder) on entry and §4 (the checklist) before declaring an export done.
- `/code-review` and the `code-quality-reviewer` skill check diffs against §4 + the R1–R3 rules.
- The ladder (§2) and checklist (§4) are the agent-facing surface. Everything else is the *why* — keep it, but agents act on the lists.

---

## 8. Server / Workers runtime (module-scoped)

Applies to any `@platform-modules/*` that runs server-side under CF Workers (§5). These govern code written **inside a module** — *not* a host app's request lifecycle. Middleware order, CSRF/ban chains, and auth-on-every-mutation are the **consuming app's** concern, never a module's; a `@platform-modules/*` package never owns the host's middleware. All four assume the web-standard `Request`/`Response` seam.

- **No top-level `await`, no import-time side-effects.** Module scope re-runs on every isolate spin-up. A side-effect (DB connect, env read, client construct) belongs in a lazily-invoked function, never at import; a top-level `await` blocks isolate start. → **reject**.
- **DB access through the `@platform-modules/db` adapter seam — no TCP driver.** Workers have no raw TCP: reach Postgres over an HTTP/fetch driver *behind the `@platform-modules/db` seam*. The concrete driver (Neon HTTP, Hyperdrive, D1) is an adapter choice, never hard-coded into a consumer module. (**R1** — multiple prior apps all picking Neon is a source-app default, not a universal; §5 lists D1/Hyperdrive as first-class.) → **reject** a module importing a concrete driver directly.
- **Async side-effects via a queue seam (`@platform-modules/jobs` / outbox pattern), not inline on the request path.** Work that can outlive the response (mail, webhook fan-out, derived writes) is enqueued, not awaited inline — keeps the request CPU-bounded (Workers CPU floor) and makes the side-effect retryable. → **flag** inline side-effects on the hot path.
- **Idempotency on externally-retried handlers.** Anything an external system retries (webhook receivers, queue consumers) must be idempotent — dedup key or upsert; never assume exactly-once. Retries are the contract, not the exception. → **reject** a non-idempotent retried handler.

---

### TL;DR for the agent

1. **Inside a function:** climb the §2 ladder, stop at the first rung that holds. Be as lazy as YAGNI allows.
2. **At an export:** satisfy every §4 box. Be as strict as the seam demands.
3. **Porting:** strip source-app defaults (R1), collapse identity ceremony (R2), memoize natives with bounded `@platform-modules/util/memo` keys (R3), design the seam, pin with a test. Never ship unbounded request-keyed memo; that is a §1 hard-floor DoS bug. Never key by plain `JSON.stringify` of an options object; key order is unstable. **Audit-hardened body → preserve byte-faithfully, don't re-derive; carry its `secaudit-*` test (R4 / §5.1).**
4. **Capability surface (§5A / R5):** carry the *most mature prior-app form* — thin core + each generic capability as a tree-shakeable subpath (or composed from a sibling module); only app-domain code stays host. Adopt don't invent; freeze the contract only when a **non-reference-app** consumer confirms it.
5. **Server/Workers (§8):** no top-level `await` / import-time side-effects; DB behind the `@platform-modules/db` seam (no TCP driver); async side-effects via a queue; idempotent retried handlers.
6. **Never** ship over-engineered ceremony, never an "upgrade later" deferral comment *at a public seam*, and never thin a module by *dumping* mature generic capability to the host.
