Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions docs/plans/2026/10/02/101-deep-dive-gaps-bugs/01-core-schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 01 — core, schema

> Part of [`overview.md`](overview.md). Depends on: none. Tier: 0.
> Row keys (`s1-t01 #4`) resolve through the table in the overview.

## Files to change
| Where | Change | Row |
|---|---|---|
| `packages/core/src/retry.ts:77`, `:128` | screen `attempts` and `timeBudgetMs` with `finiteCount` | `s1-t01 #4` |
| `packages/core/src/flight-gate.ts:68` | screen `maxConcurrent` / `maxQueued` | `s1-t01` gaps |
| `packages/core/src/context.ts:304` | child signal composes the parent's (`AbortSignal.any`) | `s2-cds #7` |
| `packages/core/src/config.ts:304-399`, `config-merge.ts:15`, `config-site.ts:112` | list-ness / object-ness screened before use; one closed-set or boolean check per key | `s2-cds #8, #9` |
| `packages/core/src/error-reporter-sentry.ts:118-127` | `meta` and `scope.extra` through `renderMetaRecord`; nested under `extra.meta` so framework keys win | `s2-cds #6` |
| `packages/core/src/logger.ts:63-92` | redaction matches credential stems / suffixes and the framework's own `passwordHash`, `tokenHash`, `keyHash` | `s1-sec M2` |
| `packages/core/src/logger.ts:305` | unknown `LOG_LEVEL` refused, as `createLogger({level})` | `s1-t01` gaps |
| `packages/core/src/otlp.ts:99`, `:178`, `:212` | endpoint joined on `url.pathname`; per-signal `…_HEADERS`; `intValue` only for safe integers, non-finite dropped | `s2-cds` low |
| `packages/core/src/registrar.ts:108`, `:130` | `fix:` names the owning package (`task` → `jobs`, `mutator` → `action`, `route` → `render`) | `s2-cds` low |
| `packages/core/src/sampler.ts:142` | `parentbased_always_on` gets its own case | `s1-t01` low |
| `packages/core/src/image/probe.ts:70-81`, `png-pixels.ts:126,172`, `raster.ts:27-33`, `image/errors.ts:58` | AVIF needs `avif` / `avis`; `assertPixelBudget` from the header before inflate; zero size → `imageDecodeFailed` | `s2-cds` low |
| `packages/core/src/secrets-errors.ts:81` | fix branches on `at` | `s2-cds` low |
| `packages/core/src/nearest-name.ts:27` | cutoff scales with length | `s1-t01` low |
| `packages/core/src/host-rules.ts:70` | address-class floor (`classifyAddress`), opt-out named | `s2-sec L1` |
| **new** `packages/core/src/public-cause.ts` | `hasPublicCause(code)` moves down from `packages/http/src/problem-meta.ts:56,136`; http re-imports it | `s2-sec H1` |
| `packages/schema/src/iso-date.ts:22` | day checked against the month | `s1-t01 #5` |
| `packages/schema/src/coerce.ts:15,81,89,115` | `Array.isArray` guard on object / record; each union member tried; decimal-only numerics | `s1-t01 #16` |
| `packages/schema/src/standard.ts:103`, `builder.ts:237` | thenable test, not `instanceof Promise` | `s2-cds` low |
| `packages/schema/src/builder.ts:47`, `:198` | `isPlainObject` requires `Object.prototype` or `null`; `.default()` runs the fallback through `check` at declaration | `s2-cds` low |
| `packages/schema/src/errors.ts:122,128` | `format({docs})`, `retry: 'terminal'`, rendered `meta` | `s2-cds` low |
| `packages/schema/src/validators.ts:457` | `t.url` requires `href`-stable input | `s2-cds` low |

## Steps
1. Bounds first (`retry`, `flight-gate`): copy `packages/core/src/backoff.ts:51-54`. The `retry` NaN case loops forever — write the test with a call cap, not a timeout.
2. `public-cause.ts`: move the predicate and its code list verbatim; `packages/http/src/problem-meta.ts` re-exports nothing — it imports. Export from `packages/core/src/index.ts` by name. Slices 10 (`mcp`, `ai`) adopt it; do not touch them here.
3. Config: follow `packages/core/src/config-pwa.ts:191` and `config-health.ts:42` (`readinessModeIssue`). Every refusal is `X_CONFIG_INVALID` with the key path. Do **not** delete `defaultTimeZone` / `defaultCurrency` / `roles` / `jobs.backoff` here — unread keys are slice 15.
4. Redaction: one matcher shared by `logger.ts` and `packages/action/src/audit-input.ts:71` (slice 06 adopts it). Keep the exact-key list as a fast path.
5. Schema: `iso-date` reuses the rule of `packages/time/src/plain-date.ts:41-48` — schema is tier 0 and cannot import `time`; restate the 12-entry table with a comment naming the twin, and add both to a parity test.
6. `.default()` on a fallback that fails its own schema throws at declaration — grep `packages/schema/src/errors.ts` for `X_SCHEMA_DEFAULT_UNSHAREABLE` and add a sibling code only if that one does not fit.

## Tests
- Each row's own test file is named in its findings row. New files: `packages/core/src/public-cause.test.ts`.
- A date parity table shared by `packages/schema/src/iso-date.test.ts` and `packages/time/src/plain-date.test.ts`.
- `bun test packages/core packages/schema`

## Owned elsewhere
- `packages/core/src/cursor.ts:52` (empty cursor secret) — 2026-09-28 plan, slice 01.
- `packages/core/src/seal-keys.ts:50-59` (one AES key for every purpose, `s1-sec L2`) — owner call, slice 15.
- 15 `app.config.ts` loaders (`s1-arch #2`) — slice 14.

## Done when
- `retry(work, { attempts: NaN })` throws a coded refusal; `t.date.parse('2026-02-30')` refuses.
- `validate()` returns `X_CONFIG_INVALID` — never a `TypeError` — for every input in `s2-cds #8, #9`.
- `hasPublicCause` has one definition (`grep -rn 'hasPublicCause' packages/*/src` shows one `export`).
- `bun run typecheck`, `bun run boundaries` green.
40 changes: 40 additions & 0 deletions docs/plans/2026/10/02/101-deep-dive-gaps-bugs/02-db.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 02 — db

> Part of [`overview.md`](overview.md). Depends on: 01. Tier: 1.

## Files to change
| Where | Change | Row |
|---|---|---|
| `packages/db/src/generate.ts:180` (`diffTable`), `:87` | a primary-key arm: `drop constraint <table>_pkey` / `add primary key (…)`, reversed in `down`; never record a key no statement produced | `s2-cds #1` |
| `packages/db/src/drift.ts:189`, `:193` | compare the two key lists; a difference carries the `alter table` pair as `fix:` | `s2-cds #2` |
| `packages/db/src/schema-dump-table.ts:37`, `catalog-fold.ts:48` | carry `attgenerated` (`'s' \| 'v'`); spell `stored` / `virtual` | `s2-cds #3` |
| `packages/db/src/introspect-catalog.ts:131`, `catalog-objects.ts:132` | triggers filtered to rendered relations (the `known.has` filter at `:98`); a dropped trigger named in `unrendered` | `s2-cds #4` |
| `packages/db/src/catalog-objects.ts:152` | `unrenderedRows` adds extended statistics, `relforcerowsecurity`, non-default `attstorage`, unpopulated materialized views | `s2-cds #5` |
| `packages/db/src/introspect.ts:12`, `:218` | expression index keys kept (left join, marked); `dataType` from `format_type(atttypid, atttypmod)` as `catalog-relations.ts:105` | `s2-cds` low |
| `packages/db/src/statement-funnel.ts:40` | `encodeBoundParameters` before the driver `try`; an Invalid Date is a coded refusal | `s1-t01 #3` |
| `packages/db/src/array-parameter.ts:37` | a `Uint8Array` element is refused or encoded as `bytea`, never `String()` | `s1-t01` low, `s3-be` |
| `packages/db/src/transaction.ts:353` | a nested scope stating `client` ≠ `outer.tx.origin`, `isolation`, `readOnly` or `deferrable` is `X_INVARIANT` — the argument at `:348` | `s1-t01 #10` |
| `packages/db/src/sqlstate.ts:35` | the shape requires a digit | `s1-t01 #15` |
| `packages/db/src/dependent-view.ts:133-140` | schema filter | `s2-cds` gaps |

## Steps
1. Primary key: decide generate-or-refuse per shape. Single-column rename and add / drop of a key column are generable; a key change on a table with inbound foreign keys is not — refuse with `migrationIrreversible` (grep `packages/db/src/errors.ts`) naming the referencing constraints. Either way the snapshot records only what `up` produced.
2. Check the CLI side before closing row 1: `packages/db/src/generate.ts:388-392` says an empty `up` re-records the hash sidecar — find that reader in `packages/cli/src/db-generate.ts` and prove it now sees a statement or a refusal.
3. Drift: reuse `changedIndex` on `<table>_pkey` if the finding shape fits; else a `changed-primary-key` kind in `packages/db/src/drift-findings.ts`. The nullability skip must use the *declared* key only.
4. Dump rows 3–5: each fix is proven by `loadSchemaDump` round trip on PGlite — the existing harness in `packages/db/src/schema-dump.test.ts`. After the change run `bun run schema-dumps --check`; if either tracked app's dump moves, regenerate with `bun run schema-dumps` in the same commit.
5. Funnel: the encode step throws its own code. `array-parameter.ts` documents the ragged case as `X_INVARIANT`; add an Invalid-Date refusal beside it.

## Tests
- `packages/db/src/generate.test.ts`, `drift.test.ts`, `schema-dump.test.ts`, `introspect.test.ts`, `statement-funnel.test.ts`, `transaction.test.ts`, `sqlstate.test.ts`.
- One `live` case for the key change on Postgres 17: `packages/db/src/generate.live.test.ts` (new; follow the nearest `*.live.test.ts` in the package for service wiring).
- `bun test packages/db`

## Owned elsewhere
- `packages/db/src/transaction.ts:301` (COMMIT on an aborted tx), `:253`, nested savepoint serialisation — **2026-09-28 plan, slice 02. Land that first**: row 10 here edits the same file.
- `packages/db/src/transaction.ts:308-315` (ambiguous COMMIT rejection, `s1-con` low) — fold into that slice.
- Boot-time framework DDL without a lock (`s1-con #5`) — slice 12 (the caller is `cli`); it uses `withAdvisoryLock` from `packages/db/src/migrate.ts:276` unchanged.

## Done when
- A `primaryKey` change yields a non-empty `up` or a refusal; `diffSchema` reports a key difference.
- A virtual generated column, a trigger on a partitioned table and `force row level security` each round-trip or appear in `unrendered`.
- `bun run schema-dumps --check` and the `drift` verify step green.
47 changes: 47 additions & 0 deletions docs/plans/2026/10/02/101-deep-dive-gaps-bugs/03-tier1-services.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 03 — time, cache, storage, seo, i18n, flags

> Part of [`overview.md`](overview.md). Depends on: 01. Tier: 1. Path-disjoint from 02.

## Files to change
| Where | Change | Row |
|---|---|---|
| `packages/time/src/cron-occurrence.ts:102` | a wall time mapping to an instant not after `after` under `overlap: 'first'` retries with `'second'` | `s1-t01 #1` |
| `packages/time/src/business.ts:65` | each candidate rebuilt from the calendar date + the original wall time | `s1-t01 #9` |
| `packages/time/src/cron-parse.ts:180,195` | extra range / step parts and prefix-matched names refused | `s1-t01` low |
| `packages/time/src/zoned.ts:172`, `duration.ts:155`, `plain-date.ts:128`, `format.ts:130` | non-integer / NaN refused with a code; year-10000 refused; `formatRelative` by calendar day | `s1-t01` low |
| **new export** `packages/time/src/zoned.ts` | `isoInZone`, moved from `packages/cli/src/tasks-facts.ts:33` (slice 14 deletes the local) | `s2-arch L5` |
| `packages/cache/src/cdn.ts:131`, `:46-48` | purge the entity key with every row tag; `cacheHeaders` emits the entity key with every row tag; `assertPurgeableKeys` at emission | `s1-t01 #2`, `s2-sec L8` |
| `packages/cache/src/tiers.ts:300`, `lru.ts:138` | a refused `set` in `fill` deletes the key in that tier | `s1-t01 #13` |
| `packages/cache/src/lru.ts:53` | `estimateBytes` walks `Map` / `Set` | `s1-t01` low |
| `packages/cache/src/fence.ts:43-44`, `redis.ts:311-335` | a per-tag generation in Redis, bumped by the bust, compared inside the `SET` script | `s1-con #8` (narrowed in `s3-be`) |
| `packages/storage/src/upload.ts:114` | read the ISO-BMFF major brand at offset 8 | `s1-t01 #6` |
| `packages/storage/src/image.ts:76,91,149` | `variantKey` keeps the source extension; width / height through `finiteCount`; height floored at 1; scale clamped at 1 | `s1-t01 #14`, low |
| `packages/storage/src/attachment.ts:146-147` | `promoteAttachment` takes the policy, refuses on `stat().size`; a gone source with a present destination answers the destination | `s2-sec M5`, `s2-con` low |
| `packages/storage/src/driver-s3.ts:282`, `:161` | `get()` takes a byte cap; a missing `lastModified` is "unknown", never epoch 0 | `s2-sec M5`, `s1-t01` low |
| `packages/storage/src/signed-url.ts:186`, `path.ts:115`, `grant.ts:91` | compare against the base's pathname; `isWithinOrg(key, '')` → `false`; the refusal names `grantUpload` (also `grant.test.ts:187,218`, `packages/storage/CLAUDE.md:19`) | `s1-t01` low, `s3-be` New 5 |
| `packages/seo/src/images.ts:13`, `:172` | default `formats` are what the configured driver encodes; `usableWidths` clamped at an exported `MAX_IMAGE_WIDTH` | `s1-t01 #11, #12` |
| `packages/seo/src/rss.ts:126` | Atom `<author>`, `<rights>`, `<icon>`; RSS item authors | `s1-t01 #17` |
| `packages/seo/src/robots.ts:70-75` | `disallow` appended to every group; a `*` group emitted when none exists | `s2-ui #7` |
| `packages/seo/src/meta.ts:162`, `locale-tags.ts:19-21`, `README.md:99` | brand match on word boundary; `ogLocaleTag` from `Intl.Locale` language + region; README matches `packages/core/src/image/pipeline.ts:22-24` | lows |
| `packages/i18n/src/translator.ts:75` | always interpolate | `s2-ui` low |
| `packages/i18n/src/define-catalogs.ts:54`, `locales.ts:123` | `assertLocale` every key before the register loop; a non-numeric `q` is 0 | `s1-t01` low, gaps |
| `packages/flags/src/flag.ts:127`, `runtime.ts:46` | `isIsoDateTime` from core; `reportEveryMs` screened | `s1-t01 #18`, gaps |

## Steps
1. Cron: prove with `*/5 * * * *`, `Europe/Berlin`, walking from `2026-10-25T00:50Z` — twelve occurrences in 01:00Z–02:00Z, and `nextCronOccurrence` agreeing with `matchesCron` at every one.
2. CDN symmetry is a wire change at the edge — one extra key per row-tagged response. State the measured header growth in the CHANGELOG entry. `tagMatches` is the rule every other tier keeps; test all four tiers against one fixture table.
3. Redis fence (`s1-con #8`): contract-level only — two isolated module instances over `packages/cache/src/redis-fake.ts`. If the interleaving cannot be made to fail there, record the row as dropped; do not ship an untested script change. The never-retried boot subscribe (`packages/cli/src/runtime-cache.ts:185-190`) is slice 12.
4. `i18n` always-interpolate changes output for a message containing a literal brace and no vars — run `bun run x -- i18n check --json` in both tracked apps and fix any catalog it now flags.

## Tests
- Files named per row. `packages/cache/src/tag-parity.test.ts` (new) — one fixture through LRU, Redis fake, memo, CDN.
- `bun test packages/time packages/cache packages/storage packages/seo packages/i18n packages/flags`

## Owned elsewhere
- `packages/money/src/format.ts:154` (`trimZeroFraction`), `packages/storage/src/driver-local.ts:279-294` (prefix keys, non-atomic `put`), `signed-url.ts:74` (disk base) — 2026-09-28 plan, slice 01. `s1-t01 #7, #8` and `s2-con` low re-prove them.
- `packages/money/src/allocate.ts:22`, `format.ts:127` (bare `RangeError`) — add to that slice; same files.

## Done when
- No hour of a fall-back night is skipped; a row-tag purge clears a collection-keyed response and the reverse.
- AVIF, HEIC, MOV, M4A pass `validateUpload` when the policy allows them.
- Both tracked apps' `x i18n check` green; `bun run scripts/reference-app-gate.ts` green.
Loading
Loading