# Changelog — @ai-matrx/records

## 0.99.151

UNVERIFIED: this release has no Unreleased entry and the package-input comparison could not be made (git diff bc1fc5c3ce..c1c5852b27 failed (commit not in this checkout)). Whether package inputs changed is unknown; consumer action is unknown.
Check: `git diff bc1fc5c3ce c1c5852b27 -- apps/shared/records`

## 0.99.150

- The Records package is rebuilt against current Content IR, Design System, and Icons workspace sources. Records runtime source is unchanged in this range; consumer action: no Records-specific migration is indicated, and consumers can follow the sibling release notes for APIs they use.

## 0.99.149

- `EntityListQuery.visibleScopes` now carries the visible composable lanes to source reads; `listSourceFilters` forwards them as reserved `__visible_lanes` transport metadata. A personal `All` total is derived only from the visible lane totals, and is omitted when a lane total is absent, invalid, unsafe, or overflows. Consumer action: only surfaces that use the visible-lane All-count contract must pass the visible lane vector and have their scoped-list RPC adapter consume `__visible_lanes`; preserve an omitted or undefined total instead of converting it to zero.

## 0.99.148

- Added: `/views` exports `setReadersTimeZone` and `readersTimeZone` (the zone a relative day is cut in), so a light host entry mirrors the person's zone without loading the root. Same module instance as the root's.

## 0.99.147

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 4a11216b9b 467bc8f112 -- apps/shared/records/src`

Changed shipped source files (4):
- `apps/shared/records/src/ai-field-presets.ts`
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/index.ts`
- `apps/shared/records/src/relativeDays.ts`

## 0.99.146

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 5a535efe67 4a11216b9b -- apps/shared/records/src`

Changed shipped source files (5):
- `apps/shared/records/src/ai-field-presets.ts`
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/history.ts`
- `apps/shared/records/src/offline/dataSource.ts`
- `apps/shared/records/src/record.ts`

## 0.99.145

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 5b7c48a99c 5a535efe67 -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.144

Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff e9d7fb471f 5b7c48a99c -- apps/shared/records/src`
has no shipped-source changes outside tests, fixtures and docs). No consumer action required.

## 0.99.143

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 28307dac39 e9d7fb471f -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doors.ts`

## 0.99.142

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 0c279b69be 28307dac39 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/offline/dataSource.ts`

## 0.99.141

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff f00b99c3df 0c279b69be -- apps/shared/records/src`

Changed shipped source files (4):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/core/index.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.140

Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff fe6f2bed19 f00b99c3df -- apps/shared/records/src`
has no shipped-source changes outside tests, fixtures and docs). No consumer action required.

## 0.99.139

- **A kept table never drops a change silently (2026-10-23, Matrx 2 walker: an offline clear said 0 waiting and the copy went back).** `RecordsMirror`: a pass waits for a send already on the wire before it reads a page, and a pull never lays a page older than the copy's own version over it; a cleared value given as `undefined` is queued as `null` (JSON stores drop undefined keys); a waiting update that carries no field is refused with a sentence, never reported as sent. Guard: `src/offline/no-silent-drop.test.ts` (red before, green now). Consumer action: none.

## 0.99.138

- Keep the in-memory preview resolver compatible with consumers that require erasable TypeScript syntax and reject unused parameters. Formula behavior is unchanged. Consumer action: none.

## 0.99.137

- **Computed previews resolve only requested cells (2026-10-23).** Replaced recursive whole-related-document expansion with operation-local cell memoization, so bidirectional rollups no longer multiply work exponentially. Filters evaluate only referenced cells and count rollups skip their unused `of` field. Consumer action: none.

## 0.99.136
- **Every module ships once across both sides: ONE ESM split pass (2026-10-23, the vault 0.1.32 template).** The ESM build was two split passes (server entries, "use client" entries), so `src/store.generated.ts` (253 KB) and `src/core/client.ts` (144 KB) shipped in a server chunk AND inside `react/index.js` (722 KB). Now every non-apart entry builds in one pass and shares chunks; only the client ENTRY files are stamped "use client". `react/index.js` 722 KB -> 59 KB; a host importing `/react` + `/core` + `/versions` + `/typed-table` loads 1521 KB -> 863 KB of records code. Guard: `check-ships-once --across` in `build` (red on the 0.99.135 dist). Consumer action: none.
- **Added `@ai-matrx/records/react/provider`: `RecordsProvider`, `useRecordsClient`, `useOptionalRecordsClient`, `useRecordsActor` alone.** The context's home is this entry; `/react` imports it by name, so a provider from one and a hook from the other meet (ESM and CJS). A shell that only mounts the provider and reads the client loads 785 KB instead of 863 KB (with `/core`, `/versions`, `/typed-table`). Consumer action: a host shell may switch `RecordsProvider` / `useRecordsClient` imports to `/react/provider`; `removeAppRow` / `upsertAppRow` come from `/typed-table`.

## 0.99.135

Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 5da59424ae ece1023750 -- apps/shared/records/src`
has no shipped-source changes outside tests, fixtures and docs). No consumer action required.

## 0.99.134

- **A view can be a swipe deck: layout `swipe` (2026-10-22).** `VIEW_LAYOUTS` / `LAYOUT_LABEL` ("Swipe"), link words (`swipe`, `deck`, `quiz`, `survey`, `questions`), and the spec keys `answerField` (`answer_field`: the column each answer is written to) and `conditionField` (`condition_field`: when a card is asked) through `viewSettings`, `viewSettingsFor`, the look and `viewFromRow`. 🚩 `STORE_KEEPS_SWIPE = true`: `custom.view_keys()` and `custom._view_key_value('kind')` admit `swipe`, `answer_field` and `condition_field` (applied to Matrx Main 2026-10-22; `VIEW_KEYS` matches the store's 61 keys), so a Swipe view is saved like any other; `false` holds it for the visit (the timeline's old path). `RECORDS_UI_VIEW_LAYOUTS` mirrors it. Consumer action: none.

## 0.99.133

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff a53838052a 60cd3951ab -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/questions/index.ts`

## 0.99.132

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 8e0ecb5a39 a53838052a -- apps/shared/records/src`

Changed shipped source files (4):
- `apps/shared/records/src/rule.ts`
- `apps/shared/records/src/templates/article.ts`
- `apps/shared/records/src/templates/describe/autofix.ts`
- `apps/shared/records/src/templates/describe/starter.ts`

## 0.99.131

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff a52923ddd6 8e0ecb5a39 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/forms/languages.ts`

## 0.99.130

- **`pickListIndex({ organization_id })` joins the pick-list doors (lane L58, 2026-10-22).** One call to `custom.pick_list_index`: the named pick lists a person may open in one organization (`PickListIndexDocument`). Guard: `a-pick-list-is-read-and-rewritten-through-its-own-doors.test.ts` names the door. Consumer action: none.

## 0.99.129

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 57d767ef7f 023ace36d2 -- apps/shared/records/src`

Changed shipped source files (8):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/validation.ts`
- `apps/shared/records/src/field.ts`
- `apps/shared/records/src/index.ts`
- `apps/shared/records/src/memory/doors.ts`
- `apps/shared/records/src/react/hooks.ts`
- `apps/shared/records/src/recordType.ts`
- `apps/shared/records/src/table.ts`

## 0.99.128

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 829e124414 57d767ef7f -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/drill.ts`

## 0.99.127

- **`clientInOrganization(client, organizationId)` (`@ai-matrx/records/core`, 2026-10-22).** The package's private `clientIn` (used by `upsertAppRow`/`removeAppRow`/typed tables) is now exported: a by-id door (history, restore preview, version read) takes the client's organization, and a row read through a client bound to another organization answers EMPTY. `clientInOrganization(client, row.organizationId)` returns the same client for the same/no organization, else one reused sibling. Guard: `src/__tests__/a-by-id-read-goes-through-the-client-of-the-rows-organization.test.ts`. Consumer action: delete a local copy (matrx-frontend `lib/records/clientInOrganization.ts`) and import this one.
- **`FieldPatch.target_tables` (lane CLOSE-LINK, 2026-10-22).** A link column can also point at other tables: `fieldUpdate({ field_id, patch: { target_tables: [tableId, …] } })`, `null` or `[]` = only its own table. `custom.field_update` (matrx-frontend migration `closelink_g`) keeps its own table first, sets `config.target_mode` "several", and refuses by name a table of another organization or dropping a table the column still links into. Consumer action: none.

## 0.99.126

- **`FieldPatch.target_tables` (lane CLOSE-LINK, 2026-10-22).** A link column can also point at other tables: `fieldUpdate({ field_id, patch: { target_tables: [tableId, …] } })`, `null` or `[]` = only its own table. `custom.field_update` (matrx-frontend migration `closelink_g`) keeps its own table first, sets `config.target_mode` "several", and refuses by name a table of another organization or dropping a table the column still links into. Consumer action: none.

## 0.99.125

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 04457e759d 7dd0861691 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/core/client.ts`

## 0.99.124

- **A kept table's new rows can be edited at once, (`@ai-matrx/records/offline`, 2026-10-21).** `offlineDataSource` now answers `record_headers` (the grid's version check before an edit) for the records the device copy holds: a row made on this device, or any row while offline, no longer refuses with "could not be checked". `GridRow.version` is new. Guard: `src/offline/dataSource.test.ts` (edit a new row online and offline; red without the header answer). Consumer action: none.

## 0.99.123

- **A kept table: every grid write offline, one record per edit, and the grid re-reads when the copy changes (`@ai-matrx/records/offline`, 2026-10-22).** `offlineDataSource` also takes `record_update_adding_choices` (the patch and its new choices wait together; `MirrorDoors.update(..., add)` sends them through `recordUpdateAddingChoices`) and `record_restore` (`RecordsMirror.restore`: undo of a delete not yet sent; one already archived in the store goes to `base`). `RecordsMirror.flush(tableId)` sends the queue WITHOUT a pull (each update reads only its own record, so field merge and keep-both conflicts stand; the breaker judges the queue) and batches a burst: calls during a flush share the next one. `gridPortFromMirror`'s `flush` is now optional and defaults to it. `copyChangesRealtime(tableId, onCopyChanged, base?)` is a realtime port that nudges the page (`records(null)`) when the device copy changes. Guards: `src/offline/kept-paths.test.ts`. Consumer action: drop a full-pass `flush` from `gridPortFromMirror` options (or point it at `mirror.flush`); bind `copyChangesRealtime` as `RecordsConfig.realtime` for a kept table.

## 0.99.122

- **One write path for a kept table, and its grid offline (`@ai-matrx/records/offline`, 2026-10-21).** `offlineDataSource(base, tableId, port, { onError, now })` is THE wrapper for a kept Table's screens: `record_update`, `record_write`, `record_write_many`, `record_change_many` and `record_delete` go into the device copy (a record the copy does not hold, or a change it cannot carry, goes to `base` as before); reads are remembered and served when the store cannot be reached, with this device's waiting changes laid over every row list (`overlay`). `gridPortFromMirror(mirror, tableId, { answers, flush, deleteQuietMs })` is its port: edits and new rows run the table's pass at once (flush on write); deletes wait for a short quiet period and go up as ONE batch, so a row-by-row mass delete is still held by the breaker. `memoryAnswers()`, `RecordsMirror.view` and `create(..., id)` are new. **Removed:** `mirrorWritePort` and `mirrorWritesSource` (folded in). Guards: `src/offline/dataSource.test.ts`, `src/offline/kept-writes.test.ts`, `src/offline/one-write-path.test.ts` (fails when the package grows a second write path). Consumer action: replace `mirrorWritesSource(source, mirrorWritePort(...))` with `offlineDataSource(source, tableId, gridPortFromMirror(mirror, tableId, { answers, flush }))` (Matrx 2 did).
- **`client.tableHomes` and `client.homeAdd` (lane CLOSE-LINK, CD-LINK-10, 2026-10-22).** Where a Table lives: `tableHomes({ table_id })` answers every Home (`{ home_id, name, declared }`, the declared one first; `custom.table_homes_named`, matrx-frontend migration `closelink_f`), and `homeAdd({ table_id, home_record_id })` places the Table in one more (`custom.home_add`, admin on the Table). `store.generated.ts` regenerated for the new door. Consumer action: none.

- **The permission ladder is read from `@ai-matrx/data/access/levels` (2026-10-21).** `src/level.ts` no longer keeps its own rank table: `STORE_PERMISSION_LEVELS` and the ranks are the data ladder with the retired `edit_content` rung inserted above `commenter`. Every word, order and comparison is unchanged (`the-record-level-ladder-keeps-its-five-words-and-order.test.ts`); the subpath imports nothing, so a headless install still needs no supabase-js. Consumer action: none.
- **`FieldPatch.carries` / `carries_max` / `loops` (lane CLOSE-LINK, CD-LINK-08/09, 2026-10-21).** A link column can carry access: with `carries: true`, whoever can see a linked record can see the record holding the link, at most at `carries_max` (`"viewer"`, the default, or `"editor"`); `loops: true` lets two records point at each other along it. Kept in `config` by `custom.field_update` (matrx-frontend migration `closelink_d`), which re-derives every carried link of the table when carrying changes. Consumer action: none.

## 0.99.121

- **History retention doors and dated-value periods (lane BUILD-HIST, acceptance tests 6 and 14, 2026-10-21).** `RecordsClient.historyRetention({ table_id? })` (`custom.history_retention`: the organization's floor and the table's days), `historyRetentionSet({ table_id, days })` and `historyRetentionFloorRaise({ days })`; under the floor is refused in the store's words. New `setValuePeriods(client, { record_id, field_key, periods })` stores when a dated field's value was true (`[{ from, to, value }]`, half-open days), copying the whole `_values` block against the version read and making today's period the plain value; helpers `valuePeriodsOf`, `periodInForce`, `valuePeriodsProblem`, `valuePeriodsPatch`. Guards: `src/__tests__/a-dated-value-keeps-every-period-and-every-other-field.test.ts`, `src/__tests__/history-retention-is-read-and-set-through-the-store.test.ts`. Consumer action: none.
- **`FieldPatch.binding` (lane CLOSE-LINK, CD-LINK-05, 2026-10-21).** A link column follows the linked record (`"live"`, the default) or keeps a copy of it from when it was linked (`"snapshot"`): `fieldUpdate({ field_id, patch: { binding: "snapshot" } })`. Kept in `config.binding` by `custom.field_update` (matrx-frontend migration `closelink_c`); turning snapshot on takes a copy for every link already there, a link made later takes its own, and a lookup through the link reads the copy. Consumer action: none.

## 0.99.120

- **`FieldPatch.binding` (lane CLOSE-LINK, CD-LINK-05, 2026-10-21).** A link column follows the linked record (`"live"`, the default) or keeps a copy of it from when it was linked (`"snapshot"`): `fieldUpdate({ field_id, patch: { binding: "snapshot" } })`. Kept in `config.binding` by `custom.field_update` (matrx-frontend migration `closelink_c`); turning snapshot on takes a copy for every link already there, a link made later takes its own, and a lookup through the link reads the copy. Consumer action: none.

## 0.99.119

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 87ac441108 033d2046c4 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/level.ts`

## 0.99.118

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 5266ed048e 87ac441108 -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/offline/index.ts`
- `apps/shared/records/src/offline/writes.ts`

## 0.99.117

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 453d5a6cc5 5266ed048e -- apps/shared/records/src`

Changed shipped source files (4):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/history.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.116

- **A device copy of a Table, with an offline queue (`@ai-matrx/records/offline`, 2026-10-21).** New subpath: `createRecordsMirror({ doors, store, breaker, log })` keeps chosen Tables' records on a device and takes its edits up through the store's own doors (`mirrorDoorsFrom(client)`: `read_records_keyset`, `read_record` with version, `record_write_many` with device-minted ids, `record_update` with the version just read, `record_delete`). Offline changes queue and land on reconnect; an edit is merged field by field against the copy it was made on, and a field two devices changed differently is a conflict that keeps both copies until the person picks; a queue of deletes, and records gone from the cloud, past the mass-delete breaker (file sync's floor / ratio / max) are held. It is the stand-in until data sync serves custom records, and every status says `stand_in: true`. `memoryMirrorStore()` for tests; a host brings its own durable store (Matrx 2: SQLite). Guard: `src/offline/mirror.test.ts` (offline-then-reconnect, network drop mid-pass, two-device conflict, mass-delete hold; red with the breaker, the merge or the offline stop removed). Consumer action: none.

## 0.99.115

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 8a9de9d1aa 8955ff0558 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/objectOrganization.ts`

## 0.99.114

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 6c5c815727 8a9de9d1aa -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/field.ts`

## 0.99.113

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 32ec1b4115 6c5c815727 -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/retype.ts`

## 0.99.111

- **`FieldPatch.limit_to_view` (lane CLOSE-LINK, 2026-10-21).** A link column's picker can be limited to one saved view of its target table: `fieldUpdate({ field_id, patch: { limit_to_view: viewId } })`, `null` takes it off; kept in `config.limit_to_view` by `custom.field_update` (matrx-frontend migration `closelink_b`), which refuses a view of another table, an unknown view, or a column that is not a link, by name. Consumer action: none.

- **Deliberate store refusals are PT409, never 40001 (2026-10-21).** `custom.has_visibility_at`, `custom.table_move` and `custom.whole_value_complete` raised 40001, which PostgREST retries without limit (a deliberate 40001 elsewhere took the database down that day). They now raise PT409 and read as `stale_write`; `store.generated.ts` regenerated. Consumer action: a caller that matched `stale_read` from those three now gets `stale_write`; update the package.
- **US spelling: the British spelling of "color" no longer exists in this package (Arman, 2026-10-21).** Every label, template string, comment, identifier, CSS class/variable and test name that spelled it the British way now says color. Guard: `design-system/src/us-spelling.test.ts` scans all of `apps/` for the word. Consumer action: none for imports; if you matched on a British-spelled identifier, class or CSS variable from this package, use the `color` spelling; update the package.
- **Typed workbook export, filter_ignored and the records client keys are back (lane STALE-REVERT-RESTORE, 2026-10-20).** Stale-tree commit 62cc7cc1c6 reverted `core/client.ts`, `core/io.ts`, `react/hooks.ts`, `record.ts`, `views/keys.ts` and the template plan; restored as of 62cc7cc1c6^ (later template-plan edits kept). Consumer action: none.
- **A chart counts each option of a many-value column (CHART-READ-2, 2026-10-19).** `RecordsClient.recordAggregate({ ..., splitMulti: true })` sends `p_split_multi` to `custom.record_aggregate` (applied to production with `custom.agg_sql`, `custom.dashboard_run` and `custom.interface_chart`): a row tagged A and B counts once under A and once under B, a multi-link counts per linked record id, a row with none counts once under none. Absent / false keeps the exact combination (grid group-by, summaries, drill, subscriptions are unchanged). Guard: records-ui `a-chart-view-counts-each-option-of-a-many-value-column.test.tsx` (red before). Consumer action: pass `splitMulti: true` from a chart, never from a grid.
- **A fresh template copy has its install id BEFORE the first call (TEMPLATE-INSTALL-2, 2026-10-19).** New `newTemplateInstallId()` on `@ai-matrx/records/templates`; `runTemplateDoor(..., "template_install_fresh", ...)` sends a caller-chosen `p_install_id` on EVERY call (the option's `installId`, else one minted before the first call and announced through the new `onInstallId`), and the store door (`custom._template_install_core`, applied to production) now accepts it: the same id is the same install (running, done or failed), never a second copy, concurrent calls included; an id owned by another person or organization is refused (42501). A refresh or a retry after a timeout inside the first ~4 s therefore no longer makes a second copy. `runTemplateDoor` for `template_uninstall` / `template_restore` also ends at once with a non-busy refusal the store names (`uninstall_stalled`) instead of asking again. Consumer action: pass `onInstallId` to keep the id for a refresh (the Spaces use-template page does); passing nothing still works.
- **An Interface chart for a reader the interface is shared with (AT-FIX-G1b, 2026-10-19).** New store door `custom.interface_chart(p_interface_id, p_chart_id)` (applied to production) and `RecordsClient.interfaceChart({ interface_id, chart_id })` -> one `DashboardBlockResult` (or `null`). The store works the aggregate out over the rows her interface access shows and only through columns the interface door exposes; a chart reading any other column answers `refused`, naming none. Guard: store suite `scripts/campaign-tests/atg1b_green.sql` (red before). Consumer action: none.
- **Follow one record (AT-FIX-G2, 2026-10-19).** `RecordsClient.recordFollowSet({ record_id, follow? })` and `recordFollowers({ record_id })` call the new store doors `custom.record_follow_set` / `custom.record_followers` (applied to production with `custom.record_follow`; types `RecordFollowers`, `RecordFollowSet`). While a person follows a record, a change or a comment by somebody else writes a bell notice (`custom.record.follow`, opening the record) in the same transaction as the change, off the outbox row and the comment insert the store already writes; no schedule. Guard: `src/__tests__/a-record-can-be-followed-through-two-store-doors.test.ts` (red before). Consumer action: none; records-ui's record panel draws the button.
- **A door asked without an argument says so (AT-FIX-G1, 2026-10-19).** An undefined `p_*` argument never travels, PostgREST answered PGRST202 and the package called it `store_not_served` ("the store is not serving the record store"), which is how archiving an Interface dashboard with a wrong id key read. `createDoorCall` now answers `invalid_argument` naming the missing argument when PGRST202/42883 comes back for a call carrying an undefined one. `SHOWN_TO_WHO_MAY_CHANGE` now reads "Only who added this row, or an admin, can change who sees it." Guard: `a-door-asked-without-its-argument-says-so-not-store-not-served.test.ts` (red before). Consumer action: none.

- **A copy's automations and pages are carried by ONE database function (COPY-EXTRAS, 2026-10-19).** The step existed twice (this package's `copyExtras` for small bases and matrx_records' `copy_extras` for the base-move job). Both are deleted; both call `custom.copy_table_extras(p_organization_id, p_pairs)` (applied to production): every automation of a source table is declared on its copy SWITCHED OFF and every page too, each table, column (by key) and view (by name) id pointed at the copy's own in one pass, a refusal named in `extras_refused`, only tables this person copied touched. New `RecordsClient.copyTableExtras({ organizationId, pairs })`. **Breaking, `BasePort`:** `automations`, `automationDeclare`, `automationSetEnabled`, `dashboards`, `dashboardDeclare`, `fieldsOf`, `viewsOf` are gone and `copyTableExtras` (required) replaces them; `pointAtCopies` and `idsOfCopies` are no longer exported. Guard: aidream `services/base_move_job/tests` `test_the_step_is_copied_by_no_second_hand` (red on the prior bytes). Consumer action: bind `copyTableExtras: (args) => client.copyTableExtras(args)` in your `BasePort`.
- **A rollup or lookup shows the look of the column it reads, installed and on display (ROLLUP-FORMAT, 2026-10-19).** "Material cost per unit (USD)" (a sum of money) read "56" with no $. The inference moves next to the formula one in `src/formulaLook.ts` (`derivedLookOf`, `withDerivedLooks`, `isDerivedField`, exported from the root): sum / avg / min / max keep the read column's look (money -> currency, percent -> percent, a unit -> suffix); count and every other measure stay plain numbers; a declared `display_format` always wins; chains (lookup -> formula -> rollup) settle. The install plan writes it onto each rollup / lookup `field_declare` spec (`derivedFieldSpec(field, look)`, `templateLooksOf(spec)` in `templates/plan`; a template's own `look` wins), `formulaLooksOf(table, inherited?)` takes the looks a table's lookups already carry, and `RecordsClient.fields` reads the far table of a derived column with no format (forward link, or back link through `reverseColumns`, three hops at most) so older installs read right. The preview's memory world calls the same function. Guard: `src/__tests__/a-rollup-and-lookup-show-the-look-of-the-column-they-read.test.ts` (red before). Consumer action: none.
- **A formula field is installed with a DECLARED number format, and one without falls back to the same inference (FORMULA-FORMAT, 2026-10-19).** The inference the preview used (`formulaLookOf`: a percent label is %, arithmetic over money columns is money, a ratio or words stay plain) moves to its one home `src/formulaLook.ts` and is exported from the root (`formulaLookOf`, `pickedLookOf`, `withFormulaLooks`); the preview's memory world calls it from there. The install plan (`compileInstallPlan`, so `custom.template_install` and the browser install) now writes `display_format` onto each formula field's `field_declare` spec (`formulaFieldSpec(field, look)`, `formulaLooksOf(table)`; a template's own `look` wins), so a person sees and changes it like any column's "Shows as". `RecordsClient.fields` applies `withFormulaLooks` to formula columns that have no `display_format` (older installs, hand-made formulas); a declared format is never touched. Guard: `src/__tests__/a-formula-field-is-installed-with-its-number-format.test.ts` (red before). Consumer action: none.
- **`useTypedTable` reads the rows while find-or-make runs (custom-experience lane I, 2026-10-19).** It awaited `ensureTypedTable` (`table_ensure` and its lookups) before `listAppRows` began, so every cold launchpad, Start or shell-layout read waited for both in sequence (~2.3 s live before the read went out). Ensure never changes a row and a table it makes is empty, so the two now run at once; status and errors are unchanged. Guard: `src/__tests__/a-typed-table-reads-while-it-ensures.test.tsx` (red before). Consumer action: none.
- **A timed-out call reads as a sentence (AT-SLOW, 2026-10-19).** `mapPgError` / `mapThrownError` answer SQLSTATE 57014 (`timed_out`) with `TIMED_OUT_MESSAGE`, "That took too long. Nothing was lost; try again.", drop the hint, and keep Postgres' own "canceling statement due to statement timeout" in `diagnostic`. It reached people raw on base move, Kanban "Make it work" and base rename. `TIMED_OUT_MESSAGE` exported from `/core`. Guard: `src/__tests__/a-timed-out-call-reads-as-a-sentence.test.ts` (red before). Consumer action: none; a screen keys Try again off `code === "timed_out"`.
- **A typed table is found in every organization in ONE request (custom-experience lane F, 2026-10-19).** `listAppRows` (and so `useTypedTable`) asked `custom.table_find` once per organization the person belongs to — 41 round trips for admin before a launchpad, Start or shell layout could draw. New door `custom.table_find_many(p_organization_ids uuid[], p_slug, p_kept_for)` (applied to production; it runs each organization through the same two walls and through `custom.table_find` itself, and answers only organizations that hold the table) and new client call `RecordsClient.tableFindMany({ organizationIds, slug, keptFor })` → `{ tables, refused }`. `listAppRows` makes one find, then reads rows only where the table lives. Guard: `a-typed-table-reads-its-own-table-and-says-what-it-cut.test.tsx` ("a list across 41 organizations finds its table in ONE request"). Consumer action: none; a hand-rolled loop of `tableFind` over organizations should call `tableFindMany`.
- **A version read is shared only for the content it was asked for (2026-10-19).** `useRecordVersions` keyed its shared `record_headers` read by record id alone for 10 s, so a re-read after the screen's own write (version 2) answered the re-read after a colleague's realtime change (version 3): the next edit named 2 and was refused as a conflict with a change the person had already seen. The key now carries what the rows hold; rows drawn again with new content share a read only while it is in flight. `askOnce` with `freshMs <= 0` shares only an in-flight answer. Guard: records-ui `a-write-never-overwrites-a-colleague.test.tsx` (the realtime-echo clause, red before). Consumer action: none.
- **`@ai-matrx/records/views` keeps a `pivot` layout and the keys it and the new chart shapes read (PIVOT-VIEW, 2026-10-18).** `VIEW_LAYOUTS`, `LAYOUT_LABEL`, `PAGE_VIEWS` (link words `pivot`, `crosstab`, `cross-tab`), `VIEW_KEYS` and `STORE_KEPT_LAYOUTS` gain `pivot`; new keys `column_field` (pivot columns / stacked series), `pivot_measure` (count, sum, avg, min, max), `x_field` (scatter), `presentation.chartLabels`, `presentation.chartLegend`; `SavedViewSpec` / `ViewSettings` carry `columnField`, `pivotMeasure`, `xField`. Templates: `TemplatePivotView` (`kind: "pivot"`, `rowField`, `columnField`, `measure`, `op`), capability word `view.pivot`, validated against the table's fields. Mirrors the migration `20261018121500_pivot_a_view_can_be_a_pivot_and_a_chart_can_be_more.sql` (applied). Consumer action: none.

- **Shared code ships once per side (2026-10-17).** The ESM build was `splitting: false`, so every entry inlined its own copy of every module it reached (76 modules in 2+ unstamped files; `index.js` 376 KB and `core/index.js` 685 KB each carried the client). ESM now builds in two split passes: the unstamped entries share `chunk-server-*.js` with no banner, the "use client" entries share stamped chunks. The light doors (`/first-page`, `/field-kinds`, `/door-call`) stay built apart, byte-identical, so they never import the shared chunk of `store.generated.ts`. CJS stays unsplit; declarations unchanged. ESM JS 4.58 MB -> 2.37 MB; every entry's export names identical (ESM and CJS). Guard: `../scripts/check-ships-once.mjs --apart first-page,field-kinds,door-call`, run in `build`. Consumer action: none.
- **`@ai-matrx/records/realtime` gains the presence port: `createRecordsPresencePort`, `peopleOf`, `presenceColor`, `storeTablePresenceChannel`** (row 183, who else is looking at this table). A private presence topic per Table (`custom:table-presence:<table_id>`, migration `table_presence_who_is_looking`, applied to production) on the host's realtime manager; publishes name, avatar, a stable color and the selected cell, never a value. Additive. Consumer action: none; a host binds it to records-ui's `presence` port.
- **A standard table's retired fields can be listed.** `entityFields({ token, include_retired: true })` (and `useEntityFields(token, { includeRetired: true })`) also answers the fields `entityFieldRetire` took away, live first; every field from this read carries `archived_at` (null while live). `Field.archived_at?` added. Needs the store migration `hr_archived_fields_entity_fields_reads_retired` (`custom.entity_fields` gains `p_include_retired boolean default false`; applied to production). A plain read sends the same two arguments as before. Guard: `src/__tests__/a-standard-tables-retired-field-can-be-listed-and-restored.test.ts`. Additive. Consumer action: none.
- **AI field presets: `AI_FIELD_PRESETS`, `aiFieldPreset`, `presetInstruction`, `presetsFor`** (`ai-field-presets.ts`): the ONE list of what an AI column does (Summarize, Keywords, Categorize, Translate, Extract, Sentiment, Describe image), shared by records-ui's "Let AI fill" panel and Spaces' AI autofill (`autofillKind` maps each to the kind the `spaces.autofill` mandate knows). `presetInstruction` writes the plain sentence (null while a language or an extract target is still missing); `presetsFor` offers Categorize only for a choice column and Describe image only beside an attachment column. Additive. Consumer action: none.
- **`isRuleFilter` on `/views`** (records-ui front door, 2026-10-17): the store's "is this filter a Rule" test, so a light reader (records-ui `/helpers`) never imports the root. Additive. Consumer action: none.
- **A view can be a Map or an Org chart.** `VIEW_LAYOUTS` gains `map` and `orgchart` (labels "Map", "Org chart"); `ViewSettings`/`SavedViewSpec` gain `mapField` (`map_field`), `mapLngField` (`map_lng_field`) and `parentField` (`parent_field`), read, written and kept in a personal look; both layouts keep `presentation.cardHidden`. Needs the store's `custom.view_keys()` migration `20261016213000_atviews_a_a_view_can_be_a_map_or_an_org_chart` (applied to production). Consumer action: none; a `Record<ViewLayout, …>` map of your own needs the two new keys.
- **New subpath `@ai-matrx/records/field-kinds`: the field-kind vocabulary and declaration builders without the ~600 KB client.** `fieldKindFor`, `newerKindOf`, `ratingMaxOf`, `isButtonField`, `atLeast`, `DECLARABLE_FIELD_KINDS`, `NEWER_FIELD_KINDS`, `PARITY_FIELD_TYPES`, `KERNEL_TABLES`, `declareTable`, `addFields`, `fieldDeclarationFor`, `tokenFor`, `DEFAULT_FIELDS` and their types. Pure re-exports of the modules `/core` already re-exports (same values, no copies; guard: `src/field-kinds/field-kinds-entry.test.ts`). Additive. Consumer action: a shell-reachable module that only classifies fields or builds a declaration imports from `/field-kinds` instead of `/core`.
- Tests: `vitest.setup.ts` gives jsdom a no-op `performance.mark` / `measure` so modules with load-trace markers import. Consumer action: none.
- **New subpath `@ai-matrx/records/door-call`: the light door clients a shell uses, without the ~600 KB client.** `lightScopeDoors(supabase)` (exactly `client.scopes`), `lightPickListDoors(supabase)` (`pickListGet` / `pickListForSelection` / `pickListUpdate`), `lightDoorCall(supabase)`, `createDoorCall`, `storeDoors`, `asRecordsDataSource` / `supabaseDataSource`, the bulk/service result helpers (`isBulkOpError`, `describeBulkFailures`, `isServiceFailure`) and `resolveObjectOrganization` / `readObjectOrganizationAnswer`. No table registry, no generated catalogue, no field-kind vocabulary (guard: `src/door-call/door-call-entry.test.ts`, which also proves every door the entry names is in the generated catalogue). Consumer action: a shell-reachable module that only asks scope / pick-list doors or `storeDoors` imports from `/door-call` instead of `/core`.
- **One door call.** The records client's `callDoor` is now `createDoorCall` (`src/core/doorCall.ts`) with the page bundle and the generated catalogue handed in as hooks, so primed answers, one-read-in-flight, the schema, `door_absent` and the error sink are one implementation for the full and the light clients. Behaviour unchanged. Consumer action: none.
- `ScopeDoors.tableProvision({ organization_id, item_id, scope_id, home_id })` (`custom.scope_table_provision`); `RecordsClient.scopeTableProvision` calls it with the client's organization. Additive. Consumer action: none.

## 0.99.110

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 920791a7ff 52fc93be02 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/templates/describe/autofix.ts`

## 0.99.109

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 31700667ba 920791a7ff -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/store.generated.ts`

## 0.99.108

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff d9d7d647b9 31700667ba -- apps/shared/records/src`

Changed shipped source files (5):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/io.ts`
- `apps/shared/records/src/react/hooks.ts`
- `apps/shared/records/src/record.ts`
- `apps/shared/records/src/views/keys.ts`

## 0.99.107

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 2fefc76fa0 d9d7d647b9 -- apps/shared/records/src`

Changed shipped source files (7):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/io.ts`
- `apps/shared/records/src/react/hooks.ts`
- `apps/shared/records/src/record.ts`
- `apps/shared/records/src/store.generated.ts`
- `apps/shared/records/src/templates/plan.ts`
- `apps/shared/records/src/views/keys.ts`

## 0.99.106

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 1d2e650802 2fefc76fa0 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/templates/plan.ts`

## 0.99.105

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 2b2a8eb272 1d2e650802 -- apps/shared/records/src`

Changed shipped source files (4):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/io.ts`
- `apps/shared/records/src/react/hooks.ts`
- `apps/shared/records/src/record.ts`

## 0.99.104

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff edbaf97840 2b2a8eb272 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/views/keys.ts`

## 0.99.103

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff cd51f03086 edbaf97840 -- apps/shared/records/src`

Changed shipped source files (4):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/declareTable.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.102

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff a567a3b2eb cd51f03086 -- apps/shared/records/src`

Changed shipped source files (5):
- `apps/shared/records/src/core/doorCall.ts`
- `apps/shared/records/src/core/pgError.ts`
- `apps/shared/records/src/core/storeDoors.ts`
- `apps/shared/records/src/core/transient.ts`
- `apps/shared/records/src/templates/plan.ts`

## 0.99.101

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 476ffb6ee4 a567a3b2eb -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/typedDate.ts`

## 0.99.100

Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 410a68fa1a 476ffb6ee4 -- apps/shared/records/src`
has no shipped-source changes outside tests, fixtures and docs). No consumer action required.

## 0.99.99

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 8f7b4d84d1 410a68fa1a -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/versions/askOnce.ts`

## 0.99.98

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff e35099348b 8f7b4d84d1 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/memory/doors.ts`

## 0.99.97

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 9c0ddc1922 e35099348b -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/core/client.ts`

## 0.99.96

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 65b1ff2a33 9c0ddc1922 -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.95

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 9dfc762c79 65b1ff2a33 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/templates/plan.ts`

## 0.99.94

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff cb137ce54d 9dfc762c79 -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.92

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff eeb0958a37 cb137ce54d -- apps/shared/records/src`

Changed shipped source files (4):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/history.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.91

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff eae8a63d01 eeb0958a37 -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/core/doorCall.ts`
- `apps/shared/records/src/rowControls.ts`

## 0.99.90

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 77f29a65b9 eae8a63d01 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/core/declareTable.ts`

## 0.99.89

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff bf7c116077 77f29a65b9 -- apps/shared/records/src`

Changed shipped source files (6):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/pgError.ts`
- `apps/shared/records/src/formulaLook.ts`
- `apps/shared/records/src/memory/world.ts`
- `apps/shared/records/src/templates/payloads.ts`
- `apps/shared/records/src/templates/plan.ts`


## 0.99.88

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff d12dfb0525 bf7c116077 -- apps/shared/records/src`

Changed shipped source files (4):
- `apps/shared/records/src/base.ts`
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.87

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 56606862a5 d12dfb0525 -- apps/shared/records/src`

Changed shipped source files (6):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/formulaLook.ts`
- `apps/shared/records/src/index.ts`
- `apps/shared/records/src/memory/world.ts`
- `apps/shared/records/src/templates/payloads.ts`
- `apps/shared/records/src/templates/plan.ts`

## 0.99.86

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 16f6a102eb 56606862a5 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/memory/world.ts`

## 0.99.85

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff f145ab8dd4 16f6a102eb -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/automation.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.84

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff a539b39945 f145ab8dd4 -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/templates/describe/index.ts`
- `apps/shared/records/src/templates/describe/starter.ts`

## 0.99.83

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff acce3dd470 a539b39945 -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/templates/plan.ts`
- `apps/shared/records/src/templates/types.ts`
- `apps/shared/records/src/templates/validate.ts`

## 0.99.82

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 88a722397b acce3dd470 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/react/useTypedTable.ts`

## 0.99.81

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 02a378797a 88a722397b -- apps/shared/records/src`

Changed shipped source files (4):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/store.generated.ts`
- `apps/shared/records/src/typed-table/rows.ts`

## 0.99.80

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 1020290262 02a378797a -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/templates/validate.ts`

## 0.99.78

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff fcfb249cc4 1020290262 -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/core/index.ts`
- `apps/shared/records/src/core/pgError.ts`

## 0.99.77

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 513544c6e1 fcfb249cc4 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/templates/plan.ts`

## 0.99.76

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff a243c8ae3d 513544c6e1 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/versions/askOnce.ts`

## 0.99.75

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 9a50ef7403 a243c8ae3d -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/versions/askOnce.ts`
- `apps/shared/records/src/versions/recordVersions.ts`

## 0.99.74

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 79928c8ab7 9a50ef7403 -- apps/shared/records/src`

Changed shipped source files (5):
- `apps/shared/records/src/templates/describe/starter.ts`
- `apps/shared/records/src/templates/payloads.ts`
- `apps/shared/records/src/templates/plan.ts`
- `apps/shared/records/src/templates/types.ts`
- `apps/shared/records/src/templates/validate.ts`

## 0.99.73

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 102b5edd78 79928c8ab7 -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/memory/world.ts`
- `apps/shared/records/src/templates/plan.ts`
- `apps/shared/records/src/templates/validate.ts`

## 0.99.72

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 86ee8ea661 102b5edd78 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/store.generated.ts`

## 0.99.71

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 58f624551a 86ee8ea661 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/memory/doors.ts`

## 0.99.70

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 3892386591 58f624551a -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/base.ts`
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/placement.ts`

## 0.99.69

Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 36181eb4d2 3892386591 -- apps/shared/records/src`
has no shipped-source changes outside tests, fixtures and docs). No consumer action required.

## 0.99.68

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 70b7da0f80 36181eb4d2 -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/memory/doors.ts`
- `apps/shared/records/src/templates/validate.ts`

## 0.99.67

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff f18c1407c2 70b7da0f80 -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/memory/doors.ts`
- `apps/shared/records/src/memory/index.ts`

## 0.99.66

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 0eb2f8603b f18c1407c2 -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/aggregate.ts`
- `apps/shared/records/src/memory/doors.ts`
- `apps/shared/records/src/templates/validate.ts`

## 0.99.65

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 5079bfa37e 0eb2f8603b -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/forms/forms.ts`
- `apps/shared/records/src/templates/catalogue.ts`
- `apps/shared/records/src/templates/describe/autofix.ts`

## 0.99.64

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 62116d54f1 5079bfa37e -- apps/shared/records/src`

Changed shipped source files (56):
- `apps/shared/records/src/button.ts`
- `apps/shared/records/src/choice.ts`
- `apps/shared/records/src/coerce.ts`
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/declareTable.ts`
- `apps/shared/records/src/core/doorCall.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/core/io.ts`
- `apps/shared/records/src/core/pgError.ts`
- `apps/shared/records/src/core/pickListDoors.ts`
- `apps/shared/records/src/core/storeDoors.ts`
- `apps/shared/records/src/core/supabase.ts`
- ...and 44 more

## 0.99.63

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff f3630e2dd0 62116d54f1 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/drill.ts`

## 0.99.62

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 92167249a3 f3630e2dd0 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/level.ts`

## 0.99.61

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 95a286343b 92167249a3 -- apps/shared/records/src`

Changed shipped source files (6):
- `apps/shared/records/src/memory/doors.ts`
- `apps/shared/records/src/memory/world.ts`
- `apps/shared/records/src/newerKinds.ts`
- `apps/shared/records/src/retype.ts`
- `apps/shared/records/src/templates/types.ts`
- `apps/shared/records/src/templates/validate.ts`

## 0.99.60

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff d6dd84595d 95a286343b -- apps/shared/records/src`

Changed shipped source files (5):
- `apps/shared/records/src/ai-field-presets.ts`
- `apps/shared/records/src/automation.ts`
- `apps/shared/records/src/data/index.ts`
- `apps/shared/records/src/data/links.ts`
- `apps/shared/records/src/realtime/presence.ts`

## 0.99.59

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff ec7de99183 d6dd84595d -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/drill.ts`

## 0.99.58

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 8627ec7150 ec7de99183 -- apps/shared/records/src`

Changed shipped source files (9):
- `apps/shared/records/src/store.generated.ts`
- `apps/shared/records/src/templates/describe/index.ts`
- `apps/shared/records/src/templates/describe/starter.ts`
- `apps/shared/records/src/templates/payloads.ts`
- `apps/shared/records/src/templates/plan.ts`
- `apps/shared/records/src/templates/types.ts`
- `apps/shared/records/src/templates/validate.ts`
- `apps/shared/records/src/views/keys.ts`
- `apps/shared/records/src/views/views.ts`

## 0.99.57

Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 6b61f37523 8627ec7150 -- apps/shared/records/src`
has no shipped-source changes outside tests, fixtures and docs). No consumer action required.

## 0.99.56

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 5db57e1f4e 6b61f37523 -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.55

Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff e56db81627 5db57e1f4e -- apps/shared/records/src`
has no shipped-source changes outside tests, fixtures and docs). No consumer action required.

## 0.99.54

Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 09c5974ddb e56db81627 -- apps/shared/records/src`
has no shipped-source changes outside tests, fixtures and docs). No consumer action required.

## 0.99.53

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff ed23614193 09c5974ddb -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.51

Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 25eb01daa8 94d6c34f76 -- apps/shared/records/src`
has no shipped-source changes outside tests, fixtures and docs). No consumer action required.

## 0.99.50

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff ee14c2d6c1 25eb01daa8 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/objectOrganization.ts`

## 0.99.49

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 22fd462394 ee14c2d6c1 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/objectOrganization.ts`

## 0.99.48

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 6bec2dd9dc 22fd462394 -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/memory/doors.ts`
- `apps/shared/records/src/memory/world.ts`
- `apps/shared/records/src/templates/validate.ts`

## 0.99.47

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 2597c9a6aa 6bec2dd9dc -- apps/shared/records/src`

Changed shipped source files (6):
- `apps/shared/records/src/button.ts`
- `apps/shared/records/src/memory/world.ts`
- `apps/shared/records/src/templates/payloads.ts`
- `apps/shared/records/src/templates/plan.ts`
- `apps/shared/records/src/templates/types.ts`
- `apps/shared/records/src/templates/validate.ts`

## 0.99.46

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 000d904df8 2597c9a6aa -- apps/shared/records/src`

Changed shipped source files (4):
- `apps/shared/records/src/templates/describe/autofix.ts`
- `apps/shared/records/src/templates/payloads.ts`
- `apps/shared/records/src/templates/types.ts`
- `apps/shared/records/src/templates/validate.ts`

## 0.99.45

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff bf42214a08 000d904df8 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/automation.ts`

## 0.99.44

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff f2b3d5e7c4 bf42214a08 -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/realtime/index.ts`
- `apps/shared/records/src/realtime/presence.ts`

## 0.99.43

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 50877ac145 f2b3d5e7c4 -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/first-page/seed.ts`

## 0.99.42

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 9a2e801ddf 50877ac145 -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/first-page/seed.ts`

## 0.99.41

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 4c976ac0a1 9a2e801ddf -- apps/shared/records/src`

Changed shipped source files (6):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/field.ts`
- `apps/shared/records/src/react/hooks.ts`
- `apps/shared/records/src/retype.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.40

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 3dac1c9a5d 4c976ac0a1 -- apps/shared/records/src`

Changed shipped source files (10):
- `apps/shared/records/src/ai-field-presets.ts`
- `apps/shared/records/src/button.ts`
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/io.ts`
- `apps/shared/records/src/field.ts`
- `apps/shared/records/src/index.ts`
- `apps/shared/records/src/store.generated.ts`
- `apps/shared/records/src/templates/payloads.ts`
- `apps/shared/records/src/templates/types.ts`
- `apps/shared/records/src/templates/validate.ts`

## 0.99.39

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 48e8c07305 3dac1c9a5d -- apps/shared/records/src`

Changed shipped source files (6):
- `apps/shared/records/src/core/pgError.ts`
- `apps/shared/records/src/memory/doors.ts`
- `apps/shared/records/src/templates/payloads.ts`
- `apps/shared/records/src/templates/plan.ts`
- `apps/shared/records/src/templates/types.ts`
- `apps/shared/records/src/templates/validate.ts`

## 0.99.38

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff e0da9af2b3 48e8c07305 -- apps/shared/records/src`

Changed shipped source files (6):
- `apps/shared/records/src/templates/payloads.ts`
- `apps/shared/records/src/templates/plan.ts`
- `apps/shared/records/src/templates/types.ts`
- `apps/shared/records/src/templates/validate.ts`
- `apps/shared/records/src/use-cases/validate.ts`
- `apps/shared/records/src/views/index.ts`

## 0.99.37

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 9f67d265e0 e0da9af2b3 -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/field.ts`
- `apps/shared/records/src/templates/validate.ts`

## 0.99.35

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff dc2aff6b5f c7712eed7c -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/field-kinds/index.ts`

## 0.99.34

Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 0e9e4ab124 dc2aff6b5f -- apps/shared/records/src`
has no shipped-source changes outside tests, fixtures and docs). No consumer action required.

## 0.99.33

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff ff7c99ff75 0e9e4ab124 -- apps/shared/records/src`

Changed shipped source files (7):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doorCall.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/core/pickListDoors.ts`
- `apps/shared/records/src/core/seed.ts`
- `apps/shared/records/src/door-call/index.ts`
- `apps/shared/records/src/scopes/doors.ts`

## 0.99.32

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff bfacbee0f5 ff7c99ff75 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/results-light.ts`

## 0.99.31
### Unreleased

- **A view can be a swipe deck: layout `swipe` (2026-10-22).** `VIEW_LAYOUTS` / `LAYOUT_LABEL` ("Swipe"), link words (`swipe`, `deck`, `quiz`, `survey`, `questions`), and the spec keys `answerField` (`answer_field`: the column each answer is written to) and `conditionField` (`condition_field`: when a card is asked) through `viewSettings`, `viewSettingsFor`, the look and `viewFromRow`. 🚩 `STORE_KEEPS_SWIPE = true`: `custom.view_keys()` and `custom._view_key_value('kind')` admit `swipe`, `answer_field` and `condition_field` (applied to Matrx Main 2026-10-22; `VIEW_KEYS` matches the store's 61 keys), so a Swipe view is saved like any other; `false` holds it for the visit (the timeline's old path). `RECORDS_UI_VIEW_LAYOUTS` mirrors it. Consumer action: none.
- **The permission ladder is read from `@ai-matrx/data/access/levels` (2026-10-21).** `src/level.ts` no longer keeps its own rank table: `STORE_PERMISSION_LEVELS` and the ranks are the data ladder with the retired `edit_content` rung inserted above `commenter`. Every word, order and comparison is unchanged (`the-record-level-ladder-keeps-its-five-words-and-order.test.ts`); the subpath imports nothing, so a headless install still needs no supabase-js. Consumer action: none.

- **`FieldPatch.limit_to_view` (lane CLOSE-LINK, 2026-10-21).** A link column's picker can be limited to one saved view of its target table: `fieldUpdate({ field_id, patch: { limit_to_view: viewId } })`, `null` takes it off; kept in `config.limit_to_view` by `custom.field_update` (matrx-frontend migration `closelink_b`), which refuses a view of another table, an unknown view, or a column that is not a link, by name. Consumer action: none.

- New subpath `@ai-matrx/records/results`: the result envelope and refusal mappers alone (`isRecordsErr`, `unwrapRecords`, `ok`, `err`, `mapPgError`, `mapThrownError`, `staleWriteDetail`, `archivedOrganizationId`, `MAPPED_SQLSTATES` and every result type), with no store client behind them. Additive. Consumer action: a module the app shell reaches that only narrows or maps a result imports these from `@ai-matrx/records/results` instead of the root or `/core`.
- `/core` now owns the table logic that lived in matrx-frontend `features/data-tables` (W1-G2b): `inferDataType`, `sanitizeFieldName`, `validateFieldName`, `isBulkOpError`, `describeBulkFailures`, `isServiceFailure`, and the types `DatasetField`, `DatasetRow`, `FieldDataType`, `Json`, `BulkOp` (+ `BulkInsertOp`/`BulkUpdateOp`/`BulkMergeOp`/`BulkCellOp`/`BulkDeleteOp`/`BulkOpError`/`BulkOpResult`/`BulkWriteResponse`) and `ServiceOk`/`ServiceErr`/`ServiceResult`. Additive: nothing existing changed. Consumer action: import these from `@ai-matrx/records/core` instead of `@/features/data-tables/{types,type-inference,field-name-key}`.

## 0.99.30

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 9e0559b49b 3702c2122e -- apps/shared/records/src`

Changed shipped source files (5):
- `apps/shared/records/src/entity/core/realtime.ts`
- `apps/shared/records/src/realtime/index.ts`
- `apps/shared/records/src/setup/index.ts`
- `apps/shared/records/src/typed-table/define.ts`
- `apps/shared/records/src/typed-table/index.ts`

## 0.99.29

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 409b5bc198 9e0559b49b -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/base.ts`
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/table.ts`

## 0.99.28
### Unreleased

- Bases (lane AT-BASE2): `duplicateBase` / `restoreSnapshot` put the copies in a NEW Home (`homeName`; `BaseCopied.homeId`), made by the door that makes a table's Home (`BasePort.createHome`) and moved in by the one move door, new `RecordsClient.recordReparent` (`custom.record_reparent`, `BasePort.moveInto`). A copy's automations and pages get the copy's column and view ids (`idsOfCopies`), and a refused automation, page or read says the store's own sentence in `extrasRefused`. `restoredBaseName`. `tableList()` exposes each table's Home as `home_id` (the record it sits in) and `kept_for` on `Table`.

- Registry: the view key `presentation.headerWrap` (boolean, grids) is declared, so a view can keep it.

## 0.99.27

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 4c4a847d55 5b24b338c9 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/base.ts`

## 0.99.26

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff e5e79298a5 4c4a847d55 -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.25

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff a293b83c1f e5e79298a5 -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/automation.ts`
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doors.ts`

## 0.99.24

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 8039f87bbd a293b83c1f -- apps/shared/records/src`

Changed shipped source files (4):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/doors.ts`
- `apps/shared/records/src/dashboard.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.23
### Unreleased

- AT-INTERFACE — `client.interfaceRead({ interface_id })` and `client.interfaceRows({ interface_id, element_id, record_id?, limit?, offset? })` over the new doors `custom.interface_read` / `custom.interface_rows`; types `InterfaceRead`, `InterfaceRows`, `InterfaceRowsField`. `store.generated.ts` regenerated. No consumer action.

## 0.99.22

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 152b8592f0 c55c74416d -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/retype.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.21

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 270152d48e 152b8592f0 -- apps/shared/records/src`

Changed shipped source files (3):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/retype.ts`
- `apps/shared/records/src/store.generated.ts`

## 0.99.20

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff b4d74a0e98 270152d48e -- apps/shared/records/src`

Changed shipped source files (2):
- `apps/shared/records/src/core/client.ts`
- `apps/shared/records/src/core/primedBundle.ts`

## 0.99.19

SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
Diff: `git diff 29b26c132f b4d74a0e98 -- apps/shared/records/src`

Changed shipped source files (1):
- `apps/shared/records/src/core/client.ts`

## 0.99.18
### Unreleased

- `store.generated.ts` regenerated from the live store: the vocabulary now lists Airtable's remaining formula functions
  (`custom.formula_node_kinds()`, migration `formula_a_the_airtable_functions_are_all_here`).
- `FORMULA_NODE_RESULTS` is generated from `custom.formula_node_kinds()`; `NewFieldDeclaration.agg` takes nine more aggregates.

## 0.99.17

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.16..npm/records/v0.99.17 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.16
### Unreleased

- **`@ai-matrx/records/table-kind` (page-weight campaign, 2026-10-13).** The table-kind vocabulary alone (`isTableKind`, `tableKindSlug`, `tableIdOf`, `tableKindMapping`, `fieldKindOf`, `tableJsonSchema`, `tableRenderSchema`, `TABLE_KIND_PREFIX` and its types; ~17 KB) so a renderer that only asks "is this a table kind?" never pulls the store root (~340 KB) into its chunk. The root still exports every name. Consumer action: a module that imports only table-kind names from the root may import them from `/table-kind`.

## 0.99.15

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.14..npm/records/v0.99.15 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.14

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.13..npm/records/v0.99.14 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.13

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.12..npm/records/v0.99.13 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.12

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.11..npm/records/v0.99.12 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.11

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.10..npm/records/v0.99.11 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.10

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.9..npm/records/v0.99.10 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.9

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.8..npm/records/v0.99.9 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.8

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.7..npm/records/v0.99.8 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.7

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.6..npm/records/v0.99.7 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.6

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.5..npm/records/v0.99.6 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.5
### Unreleased

- A table can name its records by a FORMULA over its own columns (`{First} & " " & {Last}`), kept beside the
  single title column. New doors `table_row_label` (read) and `table_row_label_set` (set; null or blank
  clears), as `client.tableRowLabel({ table_id })` and `client.tableRowLabelSet({ table_id, formula })`, with
  the `TableRowLabel` type. The store applies the result wherever a record is named (chips, peeks, pickers,
  search, back-links, notifications, the record page title); an unknown column is refused in a sentence.

## 0.99.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.3..npm/records/v0.99.4 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.2..npm/records/v0.99.3 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.1..npm/records/v0.99.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.1

AUTOMATIONS-AIRTABLE round 2 (store side, live): a condition compares by the property's kind (`custom._automation_cond_norm`: number / checkbox / date-time; words stay case-blind with an empty property read as "no"); notify steps keep `subject` and `link`; a "record enters view" whose view was removed writes one failed run in words and is switched off with `disabled_reason`.
- `AutomationItem.disabled_reason?: string | null`.
Consumer action: none required; show `disabled_reason` beside an automation that is off.

## 0.99.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.99.0..npm/records/v0.99.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.99.0

AUTOMATIONS-AIRTABLE (Airtable automation parity on Custom tables):
- `AutomationTrigger`: `record_updated` (`fields?`), `record_matches` (spec `condition`, or `view_id`), `button_pressed` (`field`).
- `AutomationAction`: `email` (`to` address / person / field / author, `subject`, `body`) and `slack` (`url`, `text`); `{Field name}` tokens.
- `client.automationTest({ automation_id, record_id? })` → `custom.automation_test` (`AutomationTested`); a run's `trigger` may be `on: "test"` with `of` and `condition_met`.
- `AutomationDeclared` carries `ai_on_schedule` / `says`: an agent step on a time trigger is saved switched off.
- `ButtonPressed` (`run_automation`): `run_id` may be null (status `skipped`), `runs` lists every automation the press started.
Consumer action: none required; editors may offer the new triggers/actions and a test-run button.

## 0.98.12

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.98.11..npm/records/v0.98.12 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.98.11

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.98.10..npm/records/v0.98.11 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.98.10

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.98.9..npm/records/v0.98.10 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.98.9

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.98.8..npm/records/v0.98.9 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.98.8

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.98.7..npm/records/v0.98.8 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.98.7

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.98.6..npm/records/v0.98.7 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.98.6

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.98.5..npm/records/v0.98.6 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.98.5

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.98.4..npm/records/v0.98.5 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.98.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.98.3..npm/records/v0.98.4 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.98.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.98.2..npm/records/v0.98.3 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.98.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.98.1..npm/records/v0.98.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.98.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.98.0..npm/records/v0.98.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.98.0

- `client.scopes.field(fieldId, organizationIds)`: one context field by id (read through
  `custom.read_records_by_ids` on the field kernel, then the field door), or a named `not_found`.
- `@ai-matrx/records/scopes`: `contextValueWrite(field, scopeId, raw, options?)` — THE one write an
  editor's value becomes (fence/list → `references`, `""`/`undefined` → a clear); `referencesFromFence`
  (inverse of `referenceFence`); `FIELD_KERNEL_ID`.
- Root: `unwrapRecords(result)` (data, or throw the store's sentence). Core: `mapThrownError(e, where)`
  (anything a host caught, as a `RecordsError`).
- Consumer action: delete host copies of these (web `referenceCell.contextValueWrite`,
  `scopeDoors.unwrapRecords`, chat inspector `unwrap`, host `mapPgError`) and import the package's.

## 0.97.6

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.97.5..npm/records/v0.97.6 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.97.5

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.97.4..npm/records/v0.97.5 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.97.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.97.3..npm/records/v0.97.4 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.97.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.97.2..npm/records/v0.97.3 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.97.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.97.1..npm/records/v0.97.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.97.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.97.0..npm/records/v0.97.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.97.0

New section `@ai-matrx/records/scopes` and `client.scopes` — SCOPES ARE CUSTOM DATA. A scope type is a
Table kept for context, a scope a Record, a context field a Field (column), a value its cell. The
`custom.context_*` doors (tree, paged types/scopes, search, scopes by id, fields, values, archived types
and scopes, system items, templates; type/scope/field create-update-archive-restore; the value write)
are one call each through the client's own door path, answering `ScopeType` / `Scope` / `ContextField` /
`ContextValue` in the store's words (`value` is the cell; `references` decodes a reference cell;
`context_policy` / store `sensitivity` on a field) and refusing in `RecordsError`. Rules:
`canShapeScopeType`, the slug rules (`sameSlug` reads `practice_areas` = `practice-areas`), and the
whole-text-in-a-file completion (`completeWholeValues`, `incompleteSaveRefusal`).

Consumer action: matrx-frontend `features/scopes` and `@ai-matrx/chat` move onto this section and drop
their own door calls, adapters and the archived `context.*` row types.

## 0.96.14

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.96.13..npm/records/v0.96.14 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.96.13

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.96.12..npm/records/v0.96.13 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.96.12

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.96.11..npm/records/v0.96.12 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.96.11

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.96.10..npm/records/v0.96.11 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.96.10

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.96.9..npm/records/v0.96.10 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.96.9

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.96.8..npm/records/v0.96.9 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.96.8

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.96.7..npm/records/v0.96.8 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.96.7

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.96.6..npm/records/v0.96.7 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.96.6

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.96.5..npm/records/v0.96.6 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.96.5

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.96.4..npm/records/v0.96.5 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.96.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.96.3..npm/records/v0.96.4 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.96.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.96.2..npm/records/v0.96.3 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.96.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.96.1..npm/records/v0.96.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.96.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.96.0..npm/records/v0.96.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.96.0 — the organization's calendar can be asked (lane NOTION-SMALL-4)

- **`client.aggCalendar()`** answers `{ time_zone, week_start }` from `custom.agg_calendar`, the same door the relative date filters and
  chart buckets are cut with (signed-in EXECUTE granted by `notionsmall4_b`). The calendar and timeline views start their weeks on it.
- Consumer action: none.

## Unreleased — the describe answer has a real schema (lane M3)

- **`describeResultJsonSchema()`** / **`parseDescribeTemplate()`** / `DESCRIBE_RESULT_KIND` (`@ai-matrx/records/templates`): the
  JSON Schema the `make.describe_template` agent is stored with, built from the SAME constants the check reads
  (`TEMPLATE_AUDIENCES`, `INDUSTRY_GROUPS`, `TEMPLATE_JOBS`, `TEACHES_FEATURES`), `__kind` declared. The deep parts travel as JSON text
  (a provider closes typed objects and drops keys the schema omits); `parseDescribeTemplate` is the one deserializer.
- `describeSpec` reads the audience "personal" as "individual" (what the agent used to write: live "personal is not one of…").
- `describeVocabulary().phone_rule` no longer offers a UK number.
- Tests: `describe-result-schema.test.ts`.
- **Consumer action:** `features/make` opens the answer with `parseDescribeTemplate` (done in matrx-frontend).

## 0.95.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.95.0..npm/records/v0.95.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.95.0 — a table exports as Markdown (lane FORMULA-2)

- **`exportMarkdown(fields, rows)`** (`@ai-matrx/records/core`): the same header and cells as `exportCsv` / `exportXlsx` (one cell
  writer, `toAoa`), written as a pipe table. A `|` in a cell is escaped, a line break becomes `<br>`, no columns is no table.
- Tests: `a-table-exports-as-a-markdown-table.test.ts`.
- **Consumer action:** none. `@ai-matrx/records-ui` uses it for "Download Markdown" / "Copy as Markdown".

## 0.94.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.94.0..npm/records/v0.94.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.94.0 — an archived row has a door back (lane F5)

From the live Applets audit (2026-10-09, R5): a reading-list Applet's archive confirm promised "you can restore it
later" and nothing could read an archived row — the table port's only list door, `custom.read_records_keyset`,
answers live rows only.
- **`Query.archived: true`** (`@ai-matrx/records/data`) answers a source's ARCHIVE: the table port reads
  `custom.read_records_archived` (the store's own archive door — same ladder, level and masking; newest archived
  first, paged by an `archived:<offset>` cursor; `where` / `search` / `sort` are not applied to it); the entity port
  asks the entity door's own archived lane. Each row carries `_archived_at` and `_archived_by_name`.
- **The data cache keeps the archive apart** (`data/react`): an archive list is never patched, dropped into or
  placed into by live-row bookkeeping; it is read again when the alias moves, after an archive, and after a restore.
- Tests: `an-archived-row-has-a-door-back.test.ts`.
- **Consumer action:** none. A host's archive/restore UI can now read the rows it restores.

## 0.93.6

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.93.5..npm/records/v0.93.6 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.93.5
### Unreleased
### automations that act when a date arrives and on a schedule (lane AUTOMATION-TIME)

`AutomationTrigger` gains `{ on: "date_arrives", field, offset_days?, at? }` and
`{ on: "schedule", every, at?, weekday?, day? }`; `AutomationValue` gains `{ in_days: N }`; `AutomationRun.trigger`
gains `fires_at`, `date_value`, `every`. Types only: the doors are unchanged and the store (migrations
`automationtime_a_`/`automationtime_b_`) runs the new triggers from its own five-minute tick.

**Consumer action:** none required; a host that draws an automation's trigger should handle the two new `on` values
(matrx-frontend's automations panel does).

## 0.93.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.93.3..npm/records/v0.93.4 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.93.3
### the grid's first page, headless; a person's look; one view binder

**New section `@ai-matrx/records/first-page`** — the grid's first page and the server's seed moved from records-ui
(`firstPage.ts`, `firstPageEntity.ts`, `entityQuery.ts`, `groupReads.ts`, the sort halves of `gridPage.ts` and
`defaultSortOf`): `askTablePageSeed`, `firstGridPageArgs`, `firstGridPageRequest`, `serverRowsOf`,
`askEntityBlockSeed`, `askChartSeed`, `askTileSeed`, `mergeSeeds`, `entityTableQuery`, `entityChartQuestion`,
`pageSortFor`, `sortAsFor`, `holdsNumbers`, `defaultSortOf`, `groupedPageSort`, `isPlainGrouping`,
`groupFactMeasures`, `summaryBarMeasures`, `choiceFieldKeys`, `viewFilterOf`, `SUMMARY_OPERATIONS`. A host that draws
its own view look passes its codec (`codec`) or binds it once (`bindFirstPageCodec`). It imports `/core` and
`/entity` by name, so the client and the entity port are the host's own instance (0.93.2 shipped this section with a
private copy of the client; fixed here). Zero React, zero design-system (section-isolation probe `./first-page`).

**`@ai-matrx/records/views` gains a person's look** (`ViewLook`, `lookPart`, `lookDocument`, `lookFromDocument`,
`applyLook`, `lookDiffers`, `saveToViewPlan`, `readLooks`, `keepLook`; moved from records-ui `viewLook.ts`) and
**`bindViewCodec(codec)`**, which returns every view and look door with the host's presentation codec bound.

**`storeFieldKind(field)`** — the store's field classifier with its kernel tables bound (the one reader
records-ui's `editorKindFor`, `/questions` and `/first-page` share).

**Consumer action:** none. records-ui re-exports every moved name with its grid look bound; a headless host imports
from `@ai-matrx/records/first-page` and `@ai-matrx/records/views`.

## 0.93.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.93.1..npm/records/v0.93.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.93.1
### saved questions and totals, headless

**New section `@ai-matrx/records/questions`** — saved questions and totals moved whole from records-ui: the spec
(`QuestionSpec`, `newQuestion`, `questionFromDefinition`), the asks (`askQuestion` over `custom.record_aggregate` and
`_as_of`, `flatWhere`, `unreadableKeys`, `measurableFields`) and where a question is kept (`questionsOf`,
`questionSave`, `questionArchive`). Columns are classified by the store's own `fieldKindFor`. Zero React, zero
design-system (section-isolation probe `./questions`).

**Consumer action:** none; records-ui re-exports every name.

## 0.93.0
### host ports: record organization, row agent

**`createRecords` takes two host ports.** `organizationOf` ("which organization owns this record", read from the
object through `custom.where_id_opens`; new `RecordOrganizationPort`, `whereIdOpensPort`,
`resolveObjectOrganization`, `readObjectOrganizationAnswer`, `ObjectOrganizationAnswer` — moved from the website) and
`rowAgent` ("run an agent on this row"; new `RowAgentPort`, `RowAgentTarget`). The setup answers
`organizationOf(id)` (default: the store's door) and `runAgentOnRow(target)` (refuses `not_supported` by name when no
runner is bound).

**Consumer action:** none. A host that keeps object-organization answers binds its own `organizationOf`; a host with
an agent runner passes `rowAgent`.

### saved views, headless

**New section `@ai-matrx/records/views`: saved views without any screen.** The view-key registry (`VIEW_KEYS`), the
one reader (`viewSettings`) and writer (`viewSettingsPatch`), the declarative spec (`SavedViewSpec`, `SavedView`), the
layouts and page-view words, and the doors that keep a view (`declareView`, `saveViewPatch`, `designateView`,
`viewFromRow`, `viewDocument`) moved here from `@ai-matrx/records-ui`. A view's look is a port
(`ViewPresentationCodec`): left out, it stays the plain JSON document the store keeps; records-ui binds its grid look.
Zero React, zero design-system (section-isolation probe `./views`).

**Consumer action:** none. A headless host imports saved views from `@ai-matrx/records/views`; records-ui's own
exports are unchanged.

### own-write ledger

**Two setups in one process hear each other's live writes.** The op ledger (how a client knows a notice is the echo
of its own write) was one per realm, so setup A's write was "ours" to setup B too and B dropped it. Own-write
detection is now per client: `createRecords` gives each setup its own ledger and hands it to its client, entity port
and live port. New `createOpLedger()` / `OpLedger`; `createRecordsClient({ opLedger })`,
`createRecordsRealtimePort({ ledger })`, `createEntityDataPort({ opLedger })`. Left out, the realm's default ledger
is used exactly as before (`mintOpId` / `isOwnOp` unchanged).

**Consumer action:** none. A host that builds its own client and live port by hand and runs two of them in one
realm passes the same `createOpLedger()` to each pair.

## 0.92.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.92.1..npm/records/v0.92.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.92.1

**`recordRead`'s version is opt-in: `recordRead({ record_id, withVersion: true })`.** 0.92.0 read the header on
every call, so a screen that draws first and versions later (the record form) waited for the slower of the two.

**Consumer action:** none for screens; a host that wants the version with the read passes `withVersion: true`.

## 0.92.0

**A member of an archived organization is told so.** Every store door used to refuse an archived organization with
"You are not a member of that organization", even to its members. The store now says "This organization is archived"
(same refusal code `door`, the store's sentence and restore hint carried verbatim) with DETAIL
`organization_archived:<organization id>`. New `archivedOrganizationId(error)` (from `@ai-matrx/records` and
`@ai-matrx/records/core`) reads that organization out so a screen can offer "Restore" (the owner's
`iam.organization_restore` door). A non-member still gets the old sentence and `null` from it.

**The withheld note is said once.** For a reader who may not see a confidential measure, `custom.record_aggregate`
used to repeat the same `_withheld` explanation on every group row; the store now says it once (first row) and
`liftWithheld` puts it on every row's `row.withheld`, so totals and charts read exactly what they read before.

**Consumer action:** none. Hosts that read `measures._withheld` off the raw door (not through the client) must read it
from the first row.

## 0.92.0

**New section `@ai-matrx/records/versions`** — the version rule for any screen a company builds, moved whole
from `@ai-matrx/records-ui`: `useRecordVersions` / `useTableVersion`, the write doors that carry the version
the person saw (`updateRecordAt`, `changeRecordsAt`, `movePipelineCardAt(s)`, `restoreAt`,
`updateOnTheWinningVersion`), `askOnce` (one question in flight per client) and `refusal` (a refusal shaped
like the store's own). React is the only peer it needs.

**`recordRead({ record_id, withVersion: true })` answers `version`.** The read asks the record's header in the
same round of requests, so a safe edit (`recordUpdate` with `expected_version`) needs no second call. `null`
when the version could not be read (the record still comes back). Off by default, so a screen that draws
before its version lands is never held back.

**Consumer action:** none. Hosts that read a record and then asked `recordHeaders` for its version can use
`read.data.version` instead.

## 0.91.0

**`typedAnswersFor(fields, values)` in `@ai-matrx/records/forms`** — what a stranger typed on a public form or
booking page, turned into values against the Fields that page published, with one refusal sentence in question
order (moved whole from the website's `features/unified-data/typedAnswers.ts`). Every public door a company builds
coerces the same way the grid, the import and our own pages do.

**Consumer action:** hosts with their own copy (the website's `typedAnswers.ts`) import
`typedAnswersFor` from `@ai-matrx/records/forms` and delete the copy.

## 0.90.0

**New section `@ai-matrx/records/setup`** — `createRecords({ auth: sessionAuth(supabase), organizationId })`:
one call that composes the typed client, live changes (one realtime manager over the session), search
across tables, platform records and a slot for the host's associations store. Signing in is a port
(`RecordsAuthPort`); an organization API key will plug in as a second mode without changing anything else.
The README opens with a quick start a company follows to list, create, edit, archive and watch records.

**Consumer action:** none.

## 0.89.0

**New section `@ai-matrx/records/forms`** — the form document (flows, looks, endings, jumps, embeds,
the form doors' arguments) and which read a public address does, moved whole from `@ai-matrx/records-ui`
(`forms.ts`, `publicBinding.ts`), so a company's own screens can build and run forms without ours.

**New section `@ai-matrx/records/search`** — `searchRecords(dataSource, { search, tableIds?, organizationIds? })`
and `listStoreRecordCandidates(dataSource, { search })`: search across every table the person can see
through `custom.records_search` (never narrowed by the active organization), moved from the website.

**Consumer action:** `@ai-matrx/records-ui` users need none. Hosts with their own copy of record search
(the website's `storeRecordCandidates.ts`) call `listStoreRecordCandidates(supabase, args)` instead.

## 0.88.1

**One data engine instance across sections.** `records/react` carried its own private copy of
`records/data/react` (its `DataPortContext` and host-event bus) because the build copies every module
into each entry that reaches it. It now imports the published `records/data/react`, as `records/entity/react`
does, so a `DataPortProvider` mounted from either section is the one every hook reads. The shared-state
gate gained a census: each module that holds React context or page-local state (data-engine provider,
seed provider, shape revisions) must live in exactly one section's dist files — proven red on a planted copy.

**Consumer action:** none.

## 0.88.0

**New sections `@ai-matrx/records/entity` and `@ai-matrx/records/entity/react`** — the former
`@ai-matrx/entity-data` package, moved in whole (same API, same behaviour): platform records (contacts,
projects, tasks) through the entity doors, the entity `DataPort`, entity live notices, and the
token-first hooks (`EntityDataProvider`, `useEntityList`, `useEntityWrite`, …). `records/entity/react`
imports the published `records/react` and `records/data/react` instead of carrying a private copy, so
the providers a host mounted are the ones it reads.

The shared-state gate (`scripts/check-shared-state.mjs`, run on the packed tarball) now also proves an
entity write's op id lands in the one op ledger (ESM and CJS) and that `entity/react` holds no private
copy of the data-engine or seed providers.

**Consumer action:** replace `@ai-matrx/entity-data`, `@ai-matrx/entity-data/core` and
`@ai-matrx/entity-data/port` with `@ai-matrx/records/entity`, and `@ai-matrx/entity-data/react` with
`@ai-matrx/records/entity/react`; then drop the `@ai-matrx/entity-data` dependency.

## 0.87.32

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.31..npm/records/v0.87.32 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.31

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.30..npm/records/v0.87.31 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.30

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.29..npm/records/v0.87.30 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.29

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.28..npm/records/v0.87.29 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.28

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.27..npm/records/v0.87.28 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.27

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.26..npm/records/v0.87.27 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.26

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.25..npm/records/v0.87.26 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.25

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.24..npm/records/v0.87.25 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.24

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.23..npm/records/v0.87.24 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.23

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.22..npm/records/v0.87.23 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.22

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.21..npm/records/v0.87.22 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.21

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.20..npm/records/v0.87.21 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.20

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.19..npm/records/v0.87.20 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.19

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.18..npm/records/v0.87.19 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.18

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.17..npm/records/v0.87.18 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.17

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.16..npm/records/v0.87.17 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.16

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.15..npm/records/v0.87.16 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.15
### lane AO: a blank format-checked field is no value

From the final live test of the Applet builder (v0.4.3018, 2026-10-08): Brand form, only the name filled,
Save → `record_write` 400 / 23514 "Contact Email is not written the way this field expects". The table data
port kept "" for email / url / phone as "words", but every one of them carries a `pattern` Rule (measured live
on `custom.field`: email 1209/1209, phone 999/999, url 236/236) and the store's pattern Rule refuses "".
- `blankToNull(type, value, field?)` (`./data`): "" is kept ONLY for free text (`text`, `long`, `long_text`,
  `rich_text`, `markdown`) whose Field carries no pattern Rule; every other column — every format the store
  checks (email, url, phone, address, color, date, time, json, tags, signature, array) and every typed one —
  writes a blank as null. `createTableDataPort` passes the column's Field.
- Tests: `a-blank-format-checked-field-is-no-value.test.ts` — the Brands field rows captured live, through the
  real `readTableShape` + `createTableDataPort`, judged by a mirror of `custom.validate_values` (red before).
- **Consumer action:** none. An Applet's blank optional email / link / phone now saves.

## 0.87.14

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.13..npm/records/v0.87.14 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.13

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.12..npm/records/v0.87.13 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.11

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.9..npm/records/v0.87.11 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.10

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.9..npm/records/v0.87.10 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.8

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.7..npm/records/v0.87.8 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.7

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.6..npm/records/v0.87.7 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.6

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.5..npm/records/v0.87.6 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.5

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.4..npm/records/v0.87.5 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.3..npm/records/v0.87.4 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.2..npm/records/v0.87.3 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.87.1..npm/records/v0.87.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.87.1

**One instance of shared state across every section.** Each built entry (root, core, react, realtime,
data, typed-table) and each module graph (ESM, CJS) carried its own copy of the op ledger and the
page-local change buses: `records/core` minted a write's op id in one copy while `records/realtime`
checked another, so a company's own write came back as somebody else's change, and a write announced by
the core client never reached `records/react` screens. The ledger, the record/table change listeners and
the removed-records set now live in `@ai-matrx/kit/global-slot` slots. Guard: `scripts/check-shared-state.mjs`,
run by `verify:tarball` in a consumer that installed the packed tarball (red on 0.87.0, green here).

**The headless package no longer installs the UI kit.** `ArchivedFilter` is spelled here
(`"active" | "archived" | "all"`) instead of imported from `@ai-matrx/design-system`, which moves to
devDependencies; `entity-list.types.test.ts` fails typecheck if the two vocabularies drift.

**Consumer action:** none for most hosts. A host that relied on records to install
`@ai-matrx/design-system` transitively must list it in its own dependencies.

## 0.87.0 — a Column says one value or many, which column names a row, and which source a link reads

From the Applets social planner live test (2026-10-08): a choice column is `list` whether it holds one word or
many, so a form could not tell Status (one word) from Platforms (several) and sent `status: ["Planned"]`, which
`record_write` refused.
- **`Column.multi`** — the store's own `custom.field.multi`, set by `readTableShape` on every column.
- **`Column.title`** — true on the table's `title_field` column (`readTableShape` reads the table beside its
  fields; a table it cannot read still answers its columns, with no title marked).
- **`Column.linksToSource`** — `createTableDataPort.columns()` names the alias, in the same port, that serves a
  relation's target table, so a form can list the linked rows to pick from.
- **Consumer action:** none required (all three are optional additions). A form choosing an input by
  `column.type === "list"` must read `column.multi` instead.

## 0.86.2

Fix the incomplete 0.86.0 release: Confidential-table header stubs are now
identified and removed from usable row lists, while their count is reported as
`locked` on record pages and keyset pages. The published client now includes
the supporting helpers and `RecordPage.locked` type.

## 0.86.0

TABLE-NAMES-SWEEP. **Breaking rename:** the `@ai-matrx/records/app-table` entry is now `@ai-matrx/records/typed-table`;
`defineAppTable` / `ensureAppTable` / `checkAppTable` / `useAppTable` / `archiveAppTableDeliberately` / `AppTable*` types become
`defineTypedTable` / `ensureTypedTable` / `checkTypedTable` / `useTypedTable` / `archiveTypedTableDeliberately` / `TypedTable*`;
`NewTableSpec.appTable` is `typedTable` and `keptByTheApp` is `platformOwned`. The stored `app_table` block and the
`kept_by_the_app` column keep their literal names until the database wave.

## 0.85.0

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.84.7..npm/records/v0.85.0 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.84.7

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.84.6..npm/records/v0.84.7 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.84.6

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.84.5..npm/records/v0.84.6 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.84.5
### Unreleased
### lane spaces-fixes-r39

- An archived record is no longer read again: `recordDelete` marks it removed (`markRecordRemoved`, lifted by `recordRestore`), and the live `useRecord` entry of a screen that closed (a peek) skips its re-read on the archive's change announcement and on realtime's echo of it — `custom.read_record` answered both with a 400.

**Consumer action:** none.

## 0.84.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.84.3..npm/records/v0.84.4 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.84.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.84.2..npm/records/v0.84.3 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.84.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.84.1..npm/records/v0.84.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.84.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.84.0..npm/records/v0.84.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.84.0 — an empty box is no value; a link is never sorted by its ids

- **`createTableDataPort` writes "" as null for every column that is not words** (`blankToNull`, exported from
  `./data/table-port`): a cleared date input sent `post_date: ""` and `record_write` refused it ("Post Date
  takes a date") with nothing on screen (Applets live test, 2026-10-08). Text columns keep "".
- **A sort by a link (relation) column is refused by name** ("\"Brand\" is a link, so the store cannot sort
  by it."): the store would order by the ids in the cell, a sort that looks like it did nothing.
- Tests red→green in `src/__tests__/an-empty-box-is-no-value-and-a-link-is-never-sorted-by-id.test.ts`.
- **Consumer action:** a page that sorted a table source by a link field sorts its rows on the page by the
  links' labels (`@ai-matrx/applets` `sortRows`).

## 0.83.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.83.1..npm/records/v0.83.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.83.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.83.0..npm/records/v0.83.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.83.0 — the gate parses with the one formula language

- The template gate parses formulas with `@ai-matrx/kit/formula` (one parser, the store's 35
  functions) and keeps only its exact-fraction evaluator (`evalFormula`, numbers read from the digits as
  typed). `parseFormula` is renamed `parseGateFormula`; the function list is
  `FORMULA_FUNCTION_NAMES` (from kit). An unknown function now reads "There is no function called
  `FOO`. A column goes in braces, like {FOO}." (architecture D6).

Consumer action: `parseFormula` / `FORMULA_FUNCTIONS` from `@ai-matrx/records` → `parseGateFormula` /
`FORMULA_FUNCTION_NAMES` (no consumer imports them).

## 0.82.1
### lane DESCRIBE-BENCH2 (sweep): the mechanical wrong shapes are read, not refused

- `autofixDescribeSpec` also rewrites, each with an auto-fix note and only when unambiguous: view `filters` written as a list of `{field, op, value}` (→ the store's map; several values → `anyOf`; a condition the store cannot express is dropped and SAID), `sort`/`orderBy` → `sorts`, `group_by`/`date_field`/… → `groupBy`/`dateField`/…, an exact duplicate view dropped; a field typed with `kind`/`type` (or by a plain label) → `parityType`; a formula over bare field names → `{Label}`; a relationship `inverseKey` that is already a field of the target (the store then read the rollup's link as a plain formula) renamed; a rollup named for a status with no filter → the filter when exactly one far-side choice/tick box is that word; a real-looking phone → a fictional one; a copy of the shared team_member block → an ordinary table (name, role, notes) — never a table whose fields are exactly one the person already has (a link-only reuse is not changed, and `describeCheck` no longer calls it a copy of the shared block).
- `describeVocabulary()` gains `view_filters_shape`, `field_shape`, `formula_shape`, `relationship_shape`, `phone_rule`, `team_rule`.

## Earlier in this release — a rollup or lookup in the wrong shape is read, not refused

- `autofixDescribeSpec` (so `describeCheck` and the live box) rewrites a rollup or lookup written as `{rollup:{relation,field,fn}}`, `{lookup:{relationField,targetField}}`, `rollupVia/rollupField/rollupFn`, `through/aggregate` and ~20 other spellings into the validator's `via` / `pick` / `agg`, with an auto-fix note, when the relation field, the far field and the aggregate word are unambiguous (sum/total, count, avg/average/mean, min, max …). A rollup through a single link makes that link a list (and its relationship `many`). An unknown relation or aggregate is left for the check to name — nothing is invented.
- `describeVocabulary()` gains `rollup_shape` and `lookup_shape`: the rule in one sentence and one tiny example in the validator's keys, handed to every describe arm.
- Confirmed: the 20% governance quota is a gallery bar and the describe profile does not refuse it (0.80.9; guarded by `governance-quota.test.ts`).
- Bench scripts read a raw answer through the platform's whole-document repair (`content-ir` json-extract), never a hand slice; the scorer re-runs the box's post-processing (safeReuses → describeCheck) with the current package.

**Consumer action:** none (matrx-frontend picks it up on update).

## 0.82.0 — workbook bytes from print, no alchemy

- Spreadsheet import/export reads and writes through `@ai-matrx/print/xlsx` (the one SheetJS
  engine). records no longer depends on `@ai-matrx/alchemy`, removing the records -> alchemy
  layer violation (architecture step 13).

Consumer action: none.

## 0.81.3
### lane DRILL-FACTS: a records page is its count

- `DrillRowsPage` loses `counted` and `settling`. The records behind a summary-backed number are now the same stored facts the number was counted from, cut at the same `as_of`, so they cannot differ and the door (`platform._drill_run_declared`, migration `drillfacts_d_a_records_page_is_its_count`) no longer sends a second count or a "… more has landed since the count" sentence. `measures` (what the records add up to) and `as_of` are unchanged.

**Consumer action:** stop reading `counted` / `settling` from a records page; show freshness from `as_of`.
## 0.81.2 (also in these bytes)
### lane TABLE-EDITING-EDGES: a date typed as bare digits is read

- `readTypedDate` (the one reader every typed-date door uses) now reads digits with no separators: `10142026` (MMDDYYYY, or DDMMYYYY by a day-first locale), `101426` (MMDDYY, the spreadsheet two-digit-year window) and `20261014` (YYYYMMDD, when it is the only reading), with an optional time after. `1014`, `1014202` and impossible days are still refused.

**Consumer action:** none.

## 0.81.2
### lane DESCRIBE-INSTALL: a form the box builds works the moment it is built

- `/templates`: the install plan now ends every `form_declare` step with an `anon_publish` step, so a form a template installs (a describe run's included) is PUBLISHED, not a draft whose link does not answer. NEW template knob `publishForms` (default true; `false` leaves the forms as drafts). `anon_publish` joins `TEMPLATE_PLAN_DOORS`; the server's closed list `custom._template_doors()` carries it (migration `describe_install_a_forms_a_template_installs_are_live`). The in-memory world marks the form `published`.
- The server now archives a Table's forms, inbound addresses, saved views and portals whenever the Table record is archived by any door (`custom.record_delete`), not only by `custom.table_archive`; 13 stranded forms were archived.
- `custom.templates` takes `installed_one_offs: true` with `installed_in`: the describe one-offs an organization installed and has not removed.

**Consumer action:** none (a host that lists installed one-offs passes `installed_one_offs`).

## 0.81.1
### lane SORT-REACTS

- A live page no longer patches a changed row in place when the change decides its place: `useRecordPage` / `useRecords` read the page again when a realtime notice or this page's own write changed a value the page is sorted by (the group key included), or any value on a filtered or searched page. A date edited to the top now moves to the top, and a row edited out of the filter leaves the page, without a reload. An edit that places nothing still patches in place and reads no page (`react/pageOrder.ts`).

**Consumer action:** none.

## 0.81.0
### SSR-ROWS: a page's first answers, asked on the server, drawn in the server's HTML

- NEW `/core`: `recordingDataSource(source)` — ask through it as the person, then `.seed()` is a `RecordsSeed` (`{ at, answers: [{ door, args, data }] }`): every door answer that came back, a bundle's (`table_page_bundle`, `record_page_bundle`) parts spread into answers of their own doors. Errors and refusing parts are never seeded. Also `seededDataSource`, `seedIndex`, `seedKey`; `canonicalArgs` is exported from the client.
- NEW `/react`: `<RecordsSeedProvider seed>` — under it every records hook runs its own door call against the seed in the FIRST pass (on the server, and while hydrating in the browser), suspending on that microtask-only run, so the answer is in the server's HTML and the browser hydrates onto the same answer and asks nothing. A hook whose call asks one door the seed does not hold is not seeded (loading in both passes, asked after hydration as before). The seed answers only until the provider's first commit; a seed older than `SEED_FRESH_MS` is drawn and re-asked. In the browser the seed's bundles are also primed (`primeTablePageBundle`), so effects after hydration open the table from them.
- NEW `/react`: `useSeededAnswer(key, run)` — the same, for a screen that holds a read in its own state: start from it, skip the first effect read.

**Consumer action:** none (additive). A server-rendering host: ask a seed as the person and mount `<RecordsSeedProvider>` above the screen.

## 0.80.9
### `@ai-matrx/records/templates` — the 20% governance quota is a gallery card's bar, not a one-off's

- `validateTemplate(spec, { profile: "describe" })` (and so `describeCheck`) no longer refuses a spec because fewer than 20% of its own fields are public, confidential or restricted. That quota teaches governance on a published gallery card and stays in the gallery bar; a person's one-off setup owes only the floor, which the describe autofix already raises (`sensitivityFloorOf`). The owner's /make "creator post tracker" answer (2026-10-08) was refused at "Only 2 of 27 fields say anything but internal"; it now passes the describe check with zero problems (`describe/governance-quota.test.ts`, real answer). "Every field says internal" is unchanged.

**Consumer action:** none.

## 0.80.8

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.80.4..npm/records/v0.80.8 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.80.7

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.80.4..npm/records/v0.80.7 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.80.6
### PAGE-BUNDLE-2: a record page opens in one call

RECORD PAGE BUNDLE: the records client answers a record page's own doors (`read_record`, `record_row_controls`, `my_levels` for the record, `record_history` head, `comment_thread`, `computed_provenance`, `record_headers`) from ONE `custom.record_page_bundle(p_organization_id, p_record_id)` call (live; SECURITY INVOKER; each part equals its door's own answer, verified part by part as admin@admin.com). Same contract as the table bundle: exact door + arguments, used once, 10 s; a refusing part is asked by its own door; a write that lands on the record drops what is still held. `primeRecordPageBundle({ organizationId, recordId, answer })` hands it an answer the server render asked as the same person.

**Consumer action:** none (additive). A host that seeds its record route calls `primeRecordPageBundle`.

## 0.80.5

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.80.4..npm/records/v0.80.5 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.80.4

The door catalogue (`store.generated.ts`) regenerated so `form_knobs` is known (0.80.3 was refused by its own real-doors gate and never published).

## 0.80.3

TYPEFORM-2: `client.formKnobs()` reads the organization's form knobs (`custom.form_knobs`: `show_owner_header`, `choice_auto_advance`) for the builder's preview.

**Consumer action:** none.

## 0.80.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.80.1..npm/records/v0.80.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.80.1
### PAGE-BUNDLE-2 fix

THE PRIMED BUNDLE IS SEEN BY EVERY ENTRY: the package's entries (`/core`, `/react`, `/data`, `/app-table`) each bundle their own copy of the client, so 0.80.0's module-level registry was four registries — a host priming through `/core` was never seen by the `RecordsProvider` (`/react`), and the bundle was asked again. The registry now lives on `globalThis` under `Symbol.for("@ai-matrx/records/primed-table-page-bundles")`.

**Consumer action:** none.

## 0.80.0
### PAGE-BUNDLE-2

A TABLE PAGE'S BUNDLE CAN BE ASKED BEFORE THE PAGE BOOTS: `primeTablePageBundle({ organizationId, tableId, viewId?, answer })` (from `@ai-matrx/records/core`) hands the records client a `custom.table_page_bundle` answer asked elsewhere — the server render, as the same person. The next client opening that Table in that organization uses it instead of asking, once, within 15 s. A primed failure or non-bundle answer is ignored and the bundle is asked as before; part-by-part matching is unchanged.

**Consumer action:** none (additive).

## 0.79.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.79.0..npm/records/v0.79.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.79.0

DUPLICATE PROPERTY (Notion N5): `client.fieldDuplicate({ field_id })` calls the new door `custom.field_duplicate` — the copy is declared right after the original ("<name> (1)", same kind, choices with their colors and keys, unit, link, formula, rules, sensitivity) and every record's value is copied into it in one statement. Answers `FieldDuplicated` (`field_id`, `key`, `label`, `values_copied`, `values`). A link column copies its definition only; formula / lookup / roll-up columns work themselves out. Needs the table's add-a-column rung, as `fieldDeclare`.

**Consumer action:** none (additive).

## 0.78.1

`durationText` (the duration kind's h:mm words) is `formatDurationSeconds(n, { granularity: "minute", signed: true })` from `@ai-matrx/kit/format` 0.34.0. Same text; a negative under half a second now reads `0:00` (was `-0:00`).

**Consumer action:** none.

## 0.78.0

PAGE-BUNDLE: a table page opens in one call. `custom.table_page_bundle` answers, in one round trip, the table row, fields, views, look, change actions, decorations, row actions, reverse columns, grid layout, the caller's level and recent imports. The client hands each part to the caller that asks that door with the same arguments (once, within 10 s), so every mapping, mask and refusal is unchanged; a refusing part, a missing door or a failed bundle is a miss and the door is asked itself.

- `RecordsDataSource.opensInOneCall` (optional): set by `supabaseDataSource`; a source without it (tests, replays) never asks the bundle.
- `store.generated.ts` regenerated from the live catalogue.

**Consumer action:** none. Measured on production as admin: 12 parts identical to their doors; ~0.2-0.6 s for the bundle against ~1.5-2.2 s for the doors one after another (`pnpm perf:bundle` in matrx-frontend).

## 0.77.3

UUID checks in app-table graduation, landing outcomes and template validation use `@ai-matrx/kit/uuid` `isUuidShape`; table-kind slugs use `isLowercaseUuidShape` (the same lower-case contract the local regex had). Scripts' console lines format sizes, durations and cost through kit. Package-twins collapse.

Consumer action: none.

## 0.77.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.77.1..npm/records/v0.77.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.77.1

- (0.77.0 never reached npm: its publish stopped on `table-kind-round-trip`'s golden check — the Python twin's golden still wrote the Rate Card formula literal as `{value: 25}` after cc7dc30f6a made the captured facts `{const: 25}`; the twin now says `const` too.)

## 0.77.0

- `formPreviewRoute({ table_id, questions, endings, values })` → `FormRoute` (`custom.form_preview_route`): the store's whole route through a draft form — showIf AND logic jumps, the ending reached and the score. Lane TYPEFORM-DUP.
- `formResults({ form_id })` → `FormResults` (`custom.form_results`): views, starts, completions, completion rate, drop-off and answers per question, endings, score and hidden fields.
- `store.generated.ts` regenerated from the live catalogue.

**Consumer action:** none.

## 0.76.20

- Automations on a table (lane AUTOMATION-DOOR): `client.automationDeclare` / `automations` / `automationArchive` / `automationRestore` / `automationSetEnabled` / `automationRuns` over the six `custom.automation*` doors, and the types in `src/automation.ts` (`AutomationSpec`, `AutomationDeclared`, `AutomationRun` …). A spec the store refuses comes back as `data.ok === false` with `_errors` (one sentence per path). The shape, in plain words with one example, is in FEATURE.md § Automations on a table. Test: `src/__tests__/automation.test.ts`; the doors' own proof is matrx-frontend `scripts/campaign-tests/automation_door_green.sql`.

**Consumer action:** none (additive; `pnpm sync-types`).

## 0.76.19

- (0.76.18 never reached npm: its publish stopped on a live-guard test that pinned which DDL guard refuses a client grant first; `real-doors.test.ts` now accepts either guard.)
- `AggregateOperation` and the template validator know the store's calculation-row measures `percent_empty`, `percent_filled` (100 × share of the rows asked; NULL over none) and `range` (largest minus smallest; a dashboard may take the range of a date) — `custom.agg_operations()` already answers them (Spaces N6). `storeAggregate` mirrors them; test in `src/templates/templates.test.ts`.

**Consumer action:** none (a wider union; `pnpm sync-types`).

## 0.76.17

- The template walk's internal cross-table break keys join on a unit separator instead of a pipe (they are identifiers, not markdown; the one-table-reader guard no longer reads them as a pipe-split table row). No behaviour change.

**Consumer action:** none.

## 0.76.16

- `importCsv` / `parseCsv` give back the values our own guarded exports were written from (`'+1 949 807 2145` → `+1 949 807 2145`), through kit 0.32.0's `parseDelimited` (AP-2 A1/A7). Test: `src/core/io.test.ts`. Needs kit 0.32.0.

## 0.76.15

- `parseCsv` / `importCsv` read through THE one reader, kit `parseDelimited` (AP-2; was a hand-rolled char loop). Needs kit 0.31.0.

**Consumer action:** none.

## 0.76.14

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.76.13..npm/records/v0.76.14 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.76.13

- `exportCsv` writes through THE one writer, `@ai-matrx/kit/delimited` (AP-2): formula-looking text gets a leading `'` (OWASP CSV injection); it had no formula guard before. Same header row, quoting and order.

**Consumer action:** none.

## 0.76.11

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.76.10..npm/records/v0.76.11 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.76.10

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.76.8..npm/records/v0.76.10 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.76.9

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.76.8..npm/records/v0.76.9 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.76.8

AP-2 (2026-10-07) — the kit and Alchemy doors are the only doors in this package (aidream guard `apps/shared/scripts/check-shared-doors.mjs`). New dependency `@ai-matrx/alchemy`: `exportXlsx`, `exportPageXlsx` and `importXlsx` go through Alchemy's one workbook engine (`/operate/formats/xlsx` `writeWorkbookBytes`, `/operate/read` `readWorkbook`), loaded on first use. SheetJS is no longer bundled into this package's dist (`noExternal: ["xlsx"]` removed). No consumer action.

## 0.76.7

- `createTableDataPort`: an entity-reference column (a custom row pointing at a PLATFORM record, `allowed_types`)
  is written as `{token, id}` pairs and read back with each pair's own token; `Column.linksTo` lists its kinds.
  Before, the port sent bare ids and the store refused every write ("Contact points at something on the platform,
  and it was given a string"), and reads labelled the link as a custom `record`. No consumer action required.
- Labels of those links come from `custom.entity_reference_words` (read for the viewer). Before, every platform
  link read "A record you have not been given access to", because `relation_words_many` reads custom rows only.

## 0.76.6

- A list's re-read never interrupts a read in flight. Notices arriving faster than a page reads restarted the
  list's re-read each time, so a page `loadMore` owed never got its turn (independent check, 2026-10-06). Re-reads
  asked for meanwhile now wait and run once, after any owed page has landed. No consumer action required.

## 0.76.5

- `useDataRows().loadMore` no longer loses its page. A re-read the engine starts while a next page is in
  flight (another person moved a row this list cannot place), or an optimistic write's cancel, ended that
  read silently and paging stalled until the next click. The hook now records the pages it owes and asks
  again the moment the list is idle. Pinned in `an-edit-that-lands-while-a-page-loads-is-not-lost`.
  No consumer action required.

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.76.4..npm/records/v0.76.5 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.76.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.76.3..npm/records/v0.76.4 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.76.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.76.2..npm/records/v0.76.3 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.76.2
### Unreleased

- A colleague's edit (or trash) that lands while the next page is loading is no longer lost: the store's
  answers are kept briefly and re-applied when a page read lands (independent acceptance found 15 of 45 edits
  stale when paging under writes).
- `MX005` (a word that names more than one choice or record — the store raised PT409 for it until 2026-10-06)
  maps to `invalid_argument`, never to `stale_write`.
- `@ai-matrx/records/data` exports `EntityAlias` (`{kind:"entity", token, organizationId?, label?}`) and `AnyAlias`, so
  a host routes platform aliases to `@ai-matrx/entity-data`'s port; `RecordsErrorCode` gains `organization_required`.

**The data runtime takes platform sources (Applets AP-3 phase B, unit UE).** In `@ai-matrx/records/data/react`:
- A row nobody's loaded page holds is put at the top of a plain list only when the source is a custom `table`
  (the engine asks `sources()` once, through the shared cache); a platform source's re-read can answer a row its
  list would not show, so its short lists are asked again (one exact re-read) and its deep lists count it in `newer`.
- A notice that cannot name its rows (`ids: null`) is batched with the others and bounded the same way: one re-read
  per short list, a `newer` mark on a deep one, never a re-read of every loaded page.
- An optimistic update merges `_custom` (new export `drawPatch`): editing one custom field never blanks the others.

## 0.76.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.76.0..npm/records/v0.76.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.76.0

**Template seed rows carry a row icon (CHAIR-TEMPLATE-UPGRADE).**

- `UseCaseRow.icon?: string` — a `@ai-matrx/icons` name (e.g. "Package"). `rowsFor` writes it as the
  record's own `icon`, so an installed template's rows draw their icon wherever a link to them shows
  (`custom.relation_words_with_icons_many` reads `data->>'icon'`).
- Store side (live 2026-10-06, migration `chair_template_upgrade_b_seed_rows_carry_an_icon`):
  `custom.record_platform_keys()` now lists `icon` beside `parent_id`, so a `fields` Table accepts it
  without a column; `custom.template_upgrade` sets the new version's row icon on rows the install
  already has, only where the row has none (counted as `counts.icons`). An icon a person set is never
  overwritten.

**Consumer action:** none. A template that wants row icons sets `icon` on its rows and bumps its version.

## 0.75.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.75.2..npm/records/v0.75.4 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.75.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.75.2..npm/records/v0.75.3 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.75.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.73.10..npm/records/v0.75.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.75.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.73.10..npm/records/v0.75.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.75.0

**Template upgrade (CHAIR-TEMPLATE-UPGRADE).** An install made from an older version of a template
can now be brought up to the newer one.

- New `upgradeTemplateInstall(source, organizationId, installId, templateId)` in
  `@ai-matrx/records/templates` — calls the store door `custom.template_upgrade`, never throws, retries
  a lock wait or timeout (the door is one transaction). Answers `TemplateUpgradeAnswer`: the install
  answer plus `upgraded`, `version`, `from_version`, `to_version`, `counts` per kind (`tables`, `fields`,
  `views`, `rows`, `fields_converted`, `links`, `targets_created`, `fields_retired`, `kept`) and
  `skipped` items with their reason. A second call answers `already` and changes nothing.
- `TemplateDoorAnswer` gains `newer_version` and `upgrade` (`TemplateUpgradeHint`): template_install's
  `already` answer now carries the install id and template id to upgrade with; `templateUpgradeHint(answer)`
  reads it.
- Store side (live 2026-10-06, migration `chair_template_upgrade_one_door`): new tables, fields, views and
  the seed rows of new tables are made; a choice column that became a link gets a new link column (the
  old column's place and name), every row's choice linked to the target record with the same title
  (case-insensitive, made when missing), and the old column retired with its values kept; a column the
  new version drops is retired, never dropped.
- `store.generated.ts` regenerated from live.

**Consumer action:** none required. A host that shows "newer version available" offers
`upgradeTemplateInstall`, then runs its host steps (agent copies) on the answer as after an install.

## 0.74.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.73.10..npm/records/v0.74.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.74.0

**The data runtime, phase A (Applets AP-3).** State management an Applet's author never thinks about.

- New `@ai-matrx/records/data` (headless): the Applets contract shapes (`Column`, `Row`, `Values`,
  `EntityRef`, `Query`, `Page`, `DataPort`), LIVE column lookup (`readTableShape` — a table addressed by
  id, its columns read at run time, rows keyed by the stable field key), the §4 filter grammar
  (`eq neq lt lte gt gte contains in empty`) compiled to the store's one filter shape, and
  `createTableDataPort` — the host's DataPort for table aliases: keyset paging, every row carrying its
  `_version`, `baseVersion` required on update, writes in the table's own organization, `rows:<alias>`
  event streams from the realtime port.
- New `@ai-matrx/records/data/react`: the frame-side layer on TanStack Query — records-owned keys, one
  request per key, keyset `loadMore`, optimistic writes with rollback and receipts, re-read of only the
  changed ids on a realtime bump (`DataPortProvider`, `useDataRows`, `useDataRow`, `useDataColumns`,
  `useDataSources`, `useDataMutations`).
- Live notices never re-read a whole list: a row nobody's page holds lands at the top of a plain
  newest-first list, re-reads a list of one or two pages, and is counted in `useDataRows().newer` on a
  deeper list (page 40 of 25,000 rows had grown to 6 s when every notice re-read every loaded page).
- `sources()` reports `canCreate` / `canUpdate` from the level the read door itself works out for the
  person on the table (a member who may edit was told `false`).
- Store side (live 2026-10-06, migration `ap3_a_rule_filter_reads_a_stored_column_without_working_out_the_row`):
  a rule filter on a STORED column reads the stored value instead of working out every computed column
  of every row — any `where` on the 25,000-row table timed out at 8 s; it now answers in about 2 s, with
  the same rows as before on every filter checked.
- New `@ai-matrx/records/realtime`: `createRecordsRealtimePort({ manager? })` — the store's live-updates
  port, moved here from matrx-frontend so every host binds the same one. It joins each table's private
  `custom:table:<id>` broadcast (`records.changed`) through `@ai-matrx/realtime` (the given manager, or
  the ambient one), one channel per table across readers, drops our own write's echo by op id, coalesces
  bursts into one re-read of the named ids, says `shape()` for a column change and re-reads all after a
  wake. Pass it as `RecordsConfig.realtime`; `createTableDataPort` turns it into `rows:<alias>` events.
- `useStoreTable` now runs on that layer: the same interface, but one request per question, keyset pages,
  live, optimistic, and every update carries the version the person saw. `archive` answers `void`.

**Consumer action:** a host that wrote its own `RecordsRealtimePort` binds `createRecordsRealtimePort` instead and deletes its copy. `@tanstack/react-query` (>=5.100) is now a peer of `@ai-matrx/records/react` and
`/data/react` (optional for the headless entries); mount a `QueryClientProvider` above them, or the hooks
share one page-wide client. Read `archive()`'s answer as `RecordsResult<void>`.

- **`client.listKeyset` — the page door, paged by a cursor (AP3-KEYSET-DOOR).** New store door
  `custom.read_records_keyset` (live on Matrx Main since 2026-10-06): the same rows, order, wall, ladder,
  field mask, filter, search and sort as `custom.read_records_page`, but each page starts after the last
  row's sort values and id, so a write landing mid-read never skips or repeats a row. Every row carries
  the `version` and `organization_id` it was read at; `ids` re-reads exactly those rows. The cursor is one
  opaque string; a cursor handed to a different sort, filter or search is refused by name (`22023`).
  Live proof on the Posts table (54 rows, as admin@admin.com, rolled back): paging 7 at a time gives the
  page door's ids in the page door's order (0 duplicates, 0 gaps) for the default order and for a
  mixed-direction sort on a column with nulls; two rows inserted between page 1 and page 2 cause 0
  repeats and 0 missing rows (the offset door repeated 2 and 1). Asked as an owed door until
  `pnpm records:generate` lists it in `STORE_DOORS`.

## 0.73.10

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.73.9..npm/records/v0.73.10 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.73.9

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.73.8..npm/records/v0.73.9 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.73.8

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.73.7..npm/records/v0.73.8 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.73.7

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.73.6..npm/records/v0.73.7 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.73.6

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.73.5..npm/records/v0.73.6 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.73.5

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.73.4..npm/records/v0.73.5 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.73.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.73.3..npm/records/v0.73.4 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.73.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.73.2..npm/records/v0.73.3 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.73.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.73.0..npm/records/v0.73.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.73.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.73.0..npm/records/v0.73.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.73.0

New subpath `./list` (P16): the list vocabulary moved from matrx-frontend — list scopes (`ListScope`, lanes, helpers) and the entity-list query, filters, sort, scope counts and facets.

## 0.72.24

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.23..npm/records/v0.72.24 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.23

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.14..npm/records/v0.72.23 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.22

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.14..npm/records/v0.72.22 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.21

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.14..npm/records/v0.72.21 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.20

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.14..npm/records/v0.72.20 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.19

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.14..npm/records/v0.72.19 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.18

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.14..npm/records/v0.72.18 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.17

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.14..npm/records/v0.72.17 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.16

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.14..npm/records/v0.72.16 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.15

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.14..npm/records/v0.72.15 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.14

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.12..npm/records/v0.72.14 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.13

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.12..npm/records/v0.72.13 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.12

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.11..npm/records/v0.72.12 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.11

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.10..npm/records/v0.72.11 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.10

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.7..npm/records/v0.72.10 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.9

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.7..npm/records/v0.72.9 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.8

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.7..npm/records/v0.72.8 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.7

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.6..npm/records/v0.72.7 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.6

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.1..npm/records/v0.72.6 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.5

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.1..npm/records/v0.72.5 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.1..npm/records/v0.72.4 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.1..npm/records/v0.72.3 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.72.1..npm/records/v0.72.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.72.1

`footprintOf` leaves out the parts a template does not make: a template saved from one table reads
"1 table · 3 agents", never "1 table · 0 views · 0 forms". No consumer action (live cards recomputed).

## 0.72.0

`footprintOf` counts every agent an install copies — the template's own plus `extraAgents` — and the
workflows it creates (`TemplateFootprint.workflows`, new). The card line now says "4 agents · 1 workflow"
where it said "1 agent" for a template that made four.
Consumer action: a gallery that lists footprint parts may show `workflows` (matrx-frontend
`features/make/gallery/catalogue.ts` does). Declared cards on live were recomputed 2026-10-05.

## 0.71.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.71.3..npm/records/v0.71.4 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.71.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.69.1..npm/records/v0.71.3 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.71.2

The value-access refusal mirror (`core/validation.ts`) and the live store (`custom.value_envelope_refusal`,
migration `t13_value_envelope_refusal_says_access`) both say "access belongs to a Field and to a Record…"
(T-13 wording); this drift and a template name clash kept 0.71.1 off npm.
Template validator: cross-file text rule indexed once per template (library check ~36 s), a walk's
submitted form row takes column defaults, a list link may hold no more records than the store allows.
G1 gate: a published template is held against the other published templates only, so a new draft that
reuses a published template's person or words is reported on the draft (and refused by
`templates:publish verify`) instead of turning the published one red and holding the release.
No consumer action.

## 0.71.1

`store.generated.ts` regenerated from live: `custom.context_template_apply` is gone (dropped by
TEMPLATES RETIRE-1); `custom.context_item_row_of` / `custom.scope_row_of` are new. 0.71.0 never
published (its catalogue still listed the dropped door). No consumer action.

## 0.71.0
### (TEMPLATES RETIRE-1, 2026-10-05 — breaking: removed exports; 0.70.0 never published)

The interim installers were retired onto the one template family (published templates read through
`custom.templates`, installed through `custom.template_install`):

- **Removed** `client.tableFromExample` (the use-case example tables), `exampleSpec`, `ExampleSpec`,
  `ExampleBuilt`. The store door `custom.table_from_example` stays — the family's install uses it.
- **Removed** the browser installer from `@ai-matrx/records/templates`: `installTemplate`,
  `archiveTemplateInstall`, `resumeArchive`, `rpcAgain`, `isTransient`, `runWalkImport`,
  `setArchivePacing`, `setSchemaCacheWait`, `todayIn` and the install/archive result types. The
  spec → door payload translators the plan compiles from moved to `templates/payloads.ts` and are
  still exported (`tableShellPayload`, `relationFieldSpec`, `rowsFor`, `viewSpec`, `formArgs`,
  `portalArgs`, `agentCopyRequest`, …).
- `@ai-matrx/records/use-cases` is unchanged (the persona factory and the catalogue read it).

**Consumer action:** a host that offered example tables or called `installTemplate` links to its
template gallery instead (matrx-frontend: `/make#make-templates`).

## 0.70.0
### (Kits → Template merge, slice 1 — 2026-10-04: there is ONE template product)

ADDITIVE ONLY: every published template validates, compiles and installs exactly as before.

- `TemplateSpec` gains optional `extraAgents[]` (more platform-agent copies: `key`, `platformAgent`,
  `agentReason`, `name`, `variables[{name, describes, binding}]`, `tryIt`), `workflows[]`
  (`{key, name, description, definition}` with `{{table:<token>}}` / `{{agent:<key>}}` placeholders;
  the primary agent's key is `agent`) and `guide[]` (display-only how-it-works steps, never the
  gated `walk`). `TemplateAgent` gains optional `tryIt`; `TemplateAgentVariable` optional `binding`.
- `TemplateMergeBinding`: the full merge-field binding with template-local handles — `semantic`
  (`collection` / `reference` / `value`), `table` (token), `rowIndex` (a seed row), `field`,
  `transform` (`list`), `limit`, `sort`, `match`, `missing`.
- `extrasProblems(spec, agents)` (called by `validateTemplate`): extras copy a REAL platform agent
  from the snapshot that declares each variable, say why in `agentReason` (8+ words), bind real
  tables / seed rows / fields; workflows and guide steps name only what the template carries. No Q6
  for extras. The gate's rules for the primary `agent` are unchanged.
- `compileInstallPlan` carries them: `plan.agent.bindings[i].binding`, `plan.extraAgents`,
  `plan.workflows` (row pointers compile to the seed row's key). `TemplateDoorAnswer.host` types the
  door's `host` block (`extra_agents`, `workflows`, `ids`: `tables.<token>`, `row.<token>.<rowKey>`),
  answered by `custom._template_answer` only for templates that carry them.

**Consumer action:** a host that installs templates copies `answer.host.extra_agents` and creates
`answer.host.workflows`, noting each through `custom.template_install_note` (kind `agent` /
`workflow`); `custom.template_uninstall` now archives both. matrx-frontend does this in
`features/make/gallery/installAgent.ts`. Hosts that never see a `host` block need no change.

## 0.69.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.68.10..npm/records/v0.69.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.69.1
### (ROW-TEMPLATES, 2026-10-04 — the 0.69.0 tag never published: its commit carried 0.68.17)

**Row templates** — Notion's database templates. `client.rowTemplates({ table_id })` (viewer) lists a
Table's templates (`RowTemplateRow`: `template_id`, `name`, `values` by Field key);
`client.rowTemplateDeclare({ table_id, spec: { name, values } })` (editor) saves one — the same name
again changes it, `{ template_id, archived: true }` archives it. Doors `custom.row_templates` /
`custom.row_template_declare`; `store.generated.ts` regenerated from live.

## 0.68.17

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.68.10..npm/records/v0.68.17 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.16

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.68.10..npm/records/v0.68.16 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.15

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.68.10..npm/records/v0.68.15 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.14

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.68.10..npm/records/v0.68.14 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.12

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.68.10..npm/records/v0.68.12 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.11

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.68.10..npm/records/v0.68.11 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.10

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.68.9..npm/records/v0.68.10 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.9

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.68.8..npm/records/v0.68.9 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.8

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.68.7..npm/records/v0.68.8 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.7

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.68.6..npm/records/v0.68.7 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.6

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.68.5..npm/records/v0.68.6 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.5

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.66.2..npm/records/v0.68.5 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.66.2..npm/records/v0.68.4 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.66.2..npm/records/v0.68.3 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.2
### Unreleased

- THE STORE IS NEVER OFF (owner ruling, 2026-10-03: "everything is on by default and things can
  only be turned off"). `storeIsOpen()` is deprecated and answers `true` whenever the door answers
  (`custom.store_is_open` now returns true for every organization); a refusal still means "could
  not reach". `metadataSearch` no longer asks the switch before each organization's read door,
  and `organizationsOpen` lists an organization as unavailable only when its door REFUSES — the
  reason "The custom data store is switched off for this organization." is gone.

Consumer action: none. Stop gating screens on `storeIsOpen()`.

## 0.68.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.66.2..npm/records/v0.68.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.68.0

- `landingOutcomesFor` reads the RECORD STORE only. It no longer reaches the retired kind store
  (`content_ir.kind_instance`), which the old-system-unreachable guard refused. `LANDING_TARGET_TYPES`
  is `{ record }`. `LandingStore` keeps `"kind_instance"` so a host's own legacy reader can hand its
  old rows to `outcomeForBlock` during the transition.
- Keep confirms (KINDS-GLUE wave 2 §5.2): `OUTPUT_KEPT_KEY` (`output_kept`) and `outputIsKept(doc)`;
  a kept output's `unconfirmed` is `false` whatever its value envelopes say. The owner keeps an
  output with `recordUpdate({ record_id, patch: { output_kept: true } })`.
- THE CODE DEPENDS ON IT (lane 12 P5): `ensureAppTable` marks every app table `code_depends: true`
  and stores `app_table.declared_in` (new optional `declared_in` on `defineAppTable`); an existing
  copy gets the mark on its next ensure. The store (lane12_h) then refuses archiving, renaming (slug
  or kept_for), moving or unmarking it through every door. `archiveAppTableDeliberately(client,
  { tableId, typedSlug, reason })` / `client.tableArchiveDeliberately` is the one way through — a
  person types the slug; an agent is refused. `NewTableSpec.codeDepends` for `ensureTable`.
- `RecordsConfig.knownDoors` now opens `callDoor` too (it already opened `doorReady`): a host can
  call a door the chair applied before `pnpm records:generate` lists it.

Consumer action: a host that drew old `content_ir.kind_instance` rows from `landingOutcomesFor`
reads them itself and merges them into `landings` (matrx-frontend: `kind-record-service.ts`).

## 0.67.0

- `pnpm tables:types <table id | slug> [--organization <id>] [--out <file>]` — a typed definition
  (`columns` map, `…Row`, `…Insert`) of an EXISTING store table, read off the live store (lane
  CHAIR-45S). The other direction of `defineAppTable`, for tables made in the app or through the
  Table API / SQL door.
- README: "Three ways to make a table" (code, structured JSON, SQL) with copy-paste examples.

Consumer action: none.

## 0.66.2
### Unreleased

- `client.migrateMerge({ winner_id, loser_id })` — merge one record into another through
  `custom.migrate_merge` (T5): values the winner lacks move across, differing ones are kept as
  ranked alternates with their source, links and children move to the winner, the loser is archived
  and its id resolves to the winner. Answers `RecordsMerged` with its `migration_id`.
- `client.migrateUndo({ migration_id })` — put a recorded Migration back (`custom.migrate_undo`): a
  merge, a retype, a rename. Answers `MigrationUndone`.
- Both announce the records they changed, so every screen holding them reads again.

Consumer action: none.

## 0.66.1

- `TableRestoreProgress` gains `left_reasons` (each row a restore left archived, with the store's own
  reason) and `events_open` (restoring events of the table still open — a re-archive mid-restore
  leaves two, and the run finishes both before `done`).

Consumer action: none.

## 0.66.0

Lane TABLE-ACTIONS — a table comes back in passes.

- **`tableRestore({ table_id, chunk })`** (`custom.table_restore`, new export `TableRestoreProgress`,
  `TABLE_RESTORE_PASS` = 20): one pass of bringing an archived table back — the table and its
  structure first, then its records in the order they went, then what was built on it, all from the
  one archive event; every pass stops itself at 1 s. Owed until the chair applies
  `tableactions_c_a_table_comes_back_in_passes_too.sql` to main.
- **`tableRestoreWhole({ table_id, onPass })`**: the loop — passes until `done`, halving on a
  statement timeout; on a store without the door yet it falls back to the one-call `recordRestore`.

Consumer action: restore a TABLE with `tableRestoreWhole`, never `recordRestore` (one statement
timed out on a table with records and things built on it).

## Unreleased

- **The record store's screen limits are knobs (2026-10-23, FB5, ADV-5 F4/F10).** New `client.recordsLimits()` (door `custom.records_limits`, asked once per client; a store without it answers `RECORDS_LIMITS_DEFAULTS`), `recordsLimitsFrom`, type `RecordsLimits`. A table copy's stall bound is the knob `custom/copy_stall_passes` (default 20, `COPY_STALL_PASSES`). `store.generated.ts` regenerated. Consumer action: none.
- `viewArchive({ view_id })` / `viewRestore({ view_id })`: a saved view's soft delete
  (`public.saved_view_archive('custom/records', id)`) and its restore (`public.entity_undelete`).
- The publish gate's grant check on `custom.record_aggregate` asks by name, every overload, so the
  door gaining `p_search` / `p_time_zone` no longer fails a gate on a door that is still granted.

## 0.65.5

Lane TABLE-ACTIONS fix round 3 — a copy is fast and safe while the organization keeps working.

- **`tableDuplicate` waits on "busy" and stops on "failed".** One pass at a time per copy: a second pass answers `busy` and the client waits a second and carries on. A source archived while it is copied stops the copy (not handed over); the call answers the store's sentence with the copy's id in `hint`, so a screen can offer to discard it.
- **`tableCopiesInProgress()`** (`custom.table_copies_in_progress`, new export `TableCopyInProgress`): the caller's own unfinished copies — carry one on with `tableDuplicateContinue` or discard it with `tableArchive`. A copy being made is hidden from everyone else (lists, Home, open, read, views, writes).
- **The finished answer says what moved in the source meanwhile**: `changedDuringCopy` (columns archived since, rows changed after they were copied, rows archived or added since). Each pass reports `pass` (setup ms, units, ms).

Consumer action: none required. No screen calls it yet.

## Unreleased

- `useRowActions(tableId)` (`/react`): a table's row actions through the data layer's cache — one
  read per table per tab, shared by every grid, page button and picker of the table (the remount
  law: a board tile waking from sleep, an Undo or a second view read `custom.row_actions` again).

## 0.65.4

- `entityRowWrite` asks its door through `owedDoor`: the catalogue (regenerated from main) no
  longer lists `custom.entity_row_write`, so `callDoor` refused it without asking and every record
  page fell back to the retired door. A store without it now answers `door_absent` naming W3a.

## 0.65.3

- `entity_row_write` is owed to main by lane 7 W3a (on the clone, not yet on main): named in
  `OWED_BY`, so the census reads the clone honestly. 0.65.1/0.65.2 tags carried a stale tree.

## 0.65.1
### (the 0.65.0 tag's gate, repaired — 0.65.0 never reached npm)

- `P0001` maps to `refused_by_rule`: `custom.context_scope_write` raises the parent rule in that
  class (the live-door census caught it on 0.65.0's tag).
- The live suite's "no standing" case carries the suite's own 150 s statement ceiling (finding a
  stranger organization outran the 30 s default on a freshly promoted clone).

### Consumer action

As 0.65.0 (hold + scope for hosts whose views sleep).

## 0.66.0
### (access ladder — custom data adoption, 2026-10-03)

- `TableFact` and `Table` carry `shown_to` and `published_to_web` (the law's "Shown to" and
  "Published to the web"); `withTableFacts` folds them on when `custom.table_facts` answers them.
  `visibility` stays as the store's worked-out lane word for older stores.
## 0.65.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.63.1..npm/records/v0.65.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.65.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.63.1..npm/records/v0.65.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.65.0
### (owner law 2026-10-02 — a view is a view: the data layer owns the data and the subscription)

- **Every `/react` read hook reads ONE cache entry** per (data source, organization + actor,
  question) — `src/react/dataCache.ts`. Siblings share it; a wake (`<Activity>`) or a remount
  inside `RECORDS_CACHE_GRACE_MS` (30 s) finds the answer on its first frame with zero door reads;
  `reload()` refreshes every reader; a refusal is never kept for the next reader. Replaces the
  2-second `share` window.
- **The live half belongs to the entry, not the view**: `useRecords` / `useRecordPage` open the
  table's realtime subscription once per entry and close it once, after the grace on the last
  release. **`useRecord(recordId, withComputed, { tableId })`** is now live: a change from another
  tab/person/agent re-reads the record into the entry (plus this page's own writes, as before).
- **`useRecordsHold(key)` + `<RecordsHoldScope holdKey>`**: a host whose views sleep owns the hold
  outside the sleeping part; every entry read under the scope stays open and live while the view
  sleeps, for any length of time, and is released (after the grace) only when the hold's owner
  unmounts. Changes landing while asleep patch the entry the view wakes to.
- `recordsCacheCounters()` for diagnostics. Test: `a-record-view-survives-sleep-and-remount`.

### Consumer action

A host whose record views sleep (the board's Table/Record tiles) mounts `useRecordsHold(tileId)`
outside the sleeping body and wraps the body in `<RecordsHoldScope holdKey={tileId}>`. Pass
`{ tableId }` to `useRecord` where the table is known. Nothing else changes.
### Also in 0.65.0 (lane KINDS-GLUE wave 2 slice 3 — where a message's kind outputs landed)

- **`landingOutcomesFor(dataSource, messageId, { conversationId? })`** (`/core`): every output a chat
  message produced, in BOTH stores during the transition — `produced_by` edges whose target is
  `record` (read through `custom.read_record`; landing facts `fingerprint`, `ordinal`, `kind`,
  `table_id`, `block_id`, `conversation_id` from the edge metadata) and the retired
  `content_ir_kind_instance` rows (edge or `metadata.source.message_id`). Each `Landing` says
  `saved` / `archived` (the store's 02000 to an admitted reader) / `unreadable` (with the store's
  sentence), the review flag (`unconfirmed`: any value last written by an agent, read through
  `custom.record_values_versioned` — `read_record` answers the bare document), and `fromSource`
  for a fork's copy.
- **`outcomeForBlock(outcomes, block)`** matches one block by (message, kind, fingerprint) — never by
  `block_id`; identical blocks are told apart by `ordinal`. A kind-store row saved with no
  fingerprint keeps matching by kind (the old card's truth until wave 5).
- `blockLandedInStore`, `valuesAreUnconfirmed`, `PRODUCED_BY_LABEL`, `LANDING_TARGET_TYPES`.

### Consumer action

None (additive). matrx-frontend's kind-record chrome reads it from this version on.
## 0.63.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.61.1..npm/records/v0.63.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.62.6
### (lane VIEWS-AND-FIELDS BL, the reverse column)

- **The reverse column's two reads (REL-9).** `reverseColumns({ table_id })` → `ReverseColumn[]`
  (`custom.reverse_columns`): one per relation column elsewhere that links to the table, on a table
  the reader may know, read-only and virtual — never in `fields`. `reverseLinksMany({ table_id,
  source_field_id, record_ids, limit, offset })` → `{ [record id]: { total, links: [{ id, words }] } }`
  (`custom.reverse_links_many`), sent in groups of 200 records; `limit` 1–50 pages one record's links.
  Hooks `useReverseColumns` / `useReverseLinks` (`/react`), re-read after any shape change or write
  on the page. Both doors are OWED (`door_absent` naming the file) until the chair applies
  `viewsfields_bl_a_link_shows_on_both_tables.sql` and `pnpm records:generate` lists them.
- `recordWrite` now announces the new record (`onRecordChanged`), like every other write door.
- **An owed door is not asked.** `doorReady(config, name)` (`/core`): true when the generated
  catalogue lists the door or `RecordsConfig.knownDoors` names it. `useReverseColumns` /
  `useReverseLinks` ask nothing and log nothing until then, so production stays quiet before the
  chair's apply and the regenerated catalogue switches them on with no code change.

### Consumer action

None.
## 0.62.5

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.61.1..npm/records/v0.62.5 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.63.0

Lane PLATFORM-APP-DATA wave 3 — the independent verifier's findings on app tables, each red first, then green.

- **Found by slug AND kept_for, everywhere.** `tableFind({ slug, keptFor })` sends `p_kept_for` (store:
  `custom.table_find(uuid, text, text, text)`); check, list and upsert of an app table never reach a person's own
  table of the same slug. With `keptFor` and no live match the store answers the archived one
  (`{ id, organization_id, archived_at }`; `Table.archived_at`).
- **An archived app table is never made again.** `custom.table_ensure` refuses (55000, the table's name and "Restore
  it from Archived tables", `detail.archived_table_id`); `upsertAppRow` answers that sentence, `useAppTable` keeps
  `status: "archived"`. The client's own archive search (which stopped after 200 tables) is gone: `checkAppTable`
  takes "archived" from `table_find`.
- **`person` scope hides other people's rows.** A person table is born with `row_defaults.shown_to = only_me`
  (`custom.table_row_defaults_set`), so the store's list predicate returns only the caller's own rows; hidden, never
  locked (README). Organization and person tables are reached by members at editor level on birth
  (`custom.share_lane_set`), so a member can add rows to a copy another member made. `NewTableSpec.rowDefaults` /
  `membersReach` (birth-only instructions to `ensureTable`).
- **Reads say what they cut.** `listAppRows` answers `{ rows, total, truncated }` (breaking: was a bare array) through
  `read_records_page`; `APP_ROWS_CEILING` = 10,000. `useAppTable` adds `total` and `truncated`.
- **Reads span every organization by default.** With no `organizationIds` from the host, `listAppRows` asks the store
  (`iam.my_orgs()`); refused, it says so — never a silent read of the active organization alone.
- **Drift `extra`.** A stored column the definition no longer declares (`FieldDriftAspect` gains `"extra"`, from
  `checkAppTable` and from `custom.table_ensure`'s drift).
- **One Home per organization for app tables**, "Kept by the app" (store side, under its own lock).
- **`RecordsProvider` without `actor`** throws a named error instead of a TypeError. README quick start fixed
  (`supabaseDataSource(supabase)`, `actor`, `<RecordsProvider config>`), compiled by `tsc`
  (`src/__tests__/readme-quick-start-compiles.tsx`).
- Clone tests archive through `tableArchive` (rows, columns and pick lists go with the table) and archive the pick
  lists' Homes; Homes still holding live tables stay.

## 0.62.4

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.61.1..npm/records/v0.62.4 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.62.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.61.1..npm/records/v0.62.3 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.62.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.61.1..npm/records/v0.62.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.62.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.61.1..npm/records/v0.62.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.62.0

Lane PLATFORM-APP-DATA wave 3, slices 1–2 — app tables: one code definition, one copy per organization.

- **New entry `@ai-matrx/records/app-table`.** `defineAppTable({ name, slug, scope, kept_for, owner?, key?, fields })`
  with `f.*` builders emitting exactly the `NewFieldSpec` `ensureTable` takes (keys kept, `__` included). Pure and
  frozen, with `specHash` (SHA-256 of the canonical field spec). `RowOf<D>` / `InsertOf<D>` are inferred: required →
  non-optional insert, computed kinds absent from insert, a choice is its literal union. No reason or date is
  recorded (Arman, 2026-10-02: the gate is about size, not time).
- **`ensureAppTable(client, def, { organizationId })`** — `custom.table_ensure` in that organization with
  `kept_by_the_app`, `kept_for` and an `app_table` block (`scope`, `owner`, `spec_hash`); a relation's target is
  ensured first; one in-flight request per (organization, slug, kept_for) per page; a statement timeout while
  another tab is making the table is asked again (at most twice). **`checkAppTable`** — ok / missing / archived /
  drifted plus `rowCount`, read-only. **`listAppRows` / `upsertAppRow` / `removeAppRow`**.
- **`useAppTable(def, { organizationId? })`** in `/react` → `{ rows, upsert, remove, loading, error, status, reload }`.
- `NewTableSpec.appTable` stores an `app_table` block on a table `ensureTable` makes.
- On a store without the lane-12 doors (the main database today) every call answers `door_absent`.

Consumer action: none required; nothing consumes it yet.


## 0.61.2

Lane PLATFORM-APP-DATA — `ensureTable` keeps every declared field key on a NEW table.

- **A new table's column keys are the ones declared.** `custom.table_ensure` sent only names to `table_declare`, which
  rebuilt each key from its name and collapsed `cards__flashcard` to `cards_flashcard`, so the table was refused
  ("the table does not declare a field called cards_flashcard"). It now passes each key through
  (`lane12_f_an_ensured_table_finds_its_kept_for_and_takes_a_home.sql`, clone only until applied). Adding a missing
  column to an existing table keeps its key exactly too. Guarded by the new clone test in
  `an-ensured-table-carries-what-an-agent-output-table-needs.test.ts`, as admin@admin.com and test@test.com.

Consumer action: none. No client code changed.


## 0.61.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.60.9..npm/records/v0.61.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

Lane TABLE-ACTIONS — a table of any size is duplicated in passes, and sharing travels only by name.

- **`tableDuplicate` loops passes.** `custom.table_duplicate` makes the copy and stops starting new work 1.5 s into the call; the client then calls `custom.table_duplicate_continue` (each pass stops starting new work 2.5 s in) until the store says `done`, reporting every pass to the new `onProgress` (`TableDuplicateProgress`: status, step, choice lists / fields / views / records done of total). No record ceiling. Measured on the nightly copy: 1,002 records × 11 columns, 19 calls, the slowest 4.5 s.
- **`tableDuplicateContinue({ copyId })`** resumes a copy a closed tab or a lost connection left unfinished (only the person who started it). Until finished the copy is kept out of every person's table list.
- **Copied views are made through the house door for a new view** and keep the source's "Shown to"; `left_behind.viewsSharedAsNewViews` names the ones that were "only me", `viewsNotCopied` any the door refused.

Consumer action: none required. No screen calls it yet.


## 0.61.0

Lane KINDS-GLUE (wave 3 slice 4) — a Table answered as a kind, and cards that hear a Field change.

- **`tableKindFacts(dataSource, tableIdOrSlug)`** (root `/core`) reads `custom.table_kind_facts` (N1) as the
  asking person and answers the door's JSON verbatim (`TableKindFacts`); a refusal is the store's sentence
  (`42501` → code `door`), never a throw. The client carries the same door as `client.tableKindFacts({ table_id })`.
- **`announceTableStructureChanged` / `onTableStructureChanged`** (`/core`): every structure door
  (`fieldDeclare` with its Table, `fieldUpdate`, `fieldUpdateRehomingChoices`, `fieldRetire`, `fieldRestore`,
  `migrateRetype` with `null`) announces after the store said yes, so a `table:<uuid>` card repaints on a rename.
- `store.generated.ts` regenerated from production (818 doors; nothing removed).

Consumer action: route any direct `custom.field_restore` call through `client.fieldRestore` so it announces.


Lane PLATFORM-APP-DATA (asked by lane KINDS-GLUE, board items L12-1 … L12-7) — `ensureTable` makes the
tables an app keeps.

- **Placement, kind and drafts facts are stored.** `NewTableSpec` takes `keptByTheApp`, `keptFor`, `kind`,
  `draftsFor`, `draftsStamp`; they are written on the table `ensureTable` (and `declareTable`) makes.
- **`keptFor` is part of the address.** `ensureTable` finds a table by slug AND `keptFor`: a person's table and
  an app's table of the same slug are different tables (`custom.table_ensure`, lane12_f).
- **`homeId`.** A new table can be put in an existing Home the caller may change; no second Home is made. A Home
  that is not in this organization (or archived, or not hers to change) is refused and nothing is made.
- **Full column declarations.** `NewFieldSpec.optionsTableId` reuses an existing choice list (sent as
  `config.options_table_id`); `NewFieldSpec.declaration` passes any other `custom.field_declare` key through
  verbatim, applied last.
- Proven on the clone as admin@admin.com and test@test.com: a 48-character slug, and a server path declaring the
  code tier (`app.actor_tier = system`) acting as the person, both work. `drift` is unchanged.
- `custom.table_find` now asks the record-store switch first (lane12_one_table_is_found_by_its_slug).

Consumer action: none required. Needs `lane12_f_an_ensured_table_finds_its_kept_for_and_takes_a_home.sql` on
production for `keptFor` matching and `homeId`; before it, `homeId` is ignored (a Home is made) and matching
is by slug alone.

## 0.60.9

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.60.7..npm/records/v0.60.9 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.60.8

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.60.7..npm/records/v0.60.8 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.60.7

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.60.6..npm/records/v0.60.7 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.60.6

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.126..npm/records/v0.60.6 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.60.5
### Unreleased

Lane CHAIR-DOORS-1 (asked by lane INTEGRATION) — metadata search hears its words.

- **`metadataSearch({ text, organization_id? })` searches.** It took `text` and never read it: every call refused as the absent door `metadata_search`. It now asks the read door about the Table and Field kernels in every organization asked (every page) and answers the Tables and Fields whose name, key, label or description holds `text`, case-insensitively — structure, never rows, the same verb as the server's `RecordStore.metadata_search`. Each match: `{ id, kind: "table" | "field", name, description, organization_id }`; a Field adds `key`, `table_id`, `field_type`. An organization that cannot answer is still named in `unavailable`. The absent-door entry `metadata_search` is gone from the door table.

Consumer action: none required; a caller that showed the door-absent refusal now gets matches.

## 0.60.4

Lane TABLE-ACTIONS fix round — `tableDuplicate` copies only what the caller can read.

- **A copy carries only what the caller can read.** Rows are the ones the store hands this person; values go through the read doors' own field mask, so a column they may not read is copied empty and named in `hiddenColumns` (column name → values left behind). Also reported: `recordsNotYoursLeft`, `privateViewsLeft` (another person's "only me" views), and `rowReferencesDropped` (where a reference to a row that was not copied — a view condition, a row action's value, a row color — was taken out).

Consumer action: none required. No screen calls it yet.

## 0.60.3

Lane PLATFORM-APP-DATA follow-ups on the 0.59.0 doors (clone-only until the chair's production apply). Part of this source was swept into the automatic 0.60.1 / 0.60.2 republishes mid-change; this entry describes the whole of it.

- **`ensureTable` / `tableEnsure` answer `drift`.** A table that already exists still keeps every column as stored (never retyped, relabelled or dropped), but each column whose stored type, format, label, multi, required or unique differs from the spec is now named: `drift: FieldDrift[]` (`{ field, aspect, stored, declared }`), `[]` when none, `null` only from a store too old to compare. `ensureTable` also answers `created`. New exports: `FieldDrift`, `FieldDriftAspect`, `parseFieldDrift`, `EnsureResult`. Store: `lane12_c_an_ensured_table_says_what_differs_from_its_spec.sql`.
- **`recordUpsert` with `expectedVersion` and no live record of that key is `stale_write`** (PT409, `current_version: 0`), never a new record. Without a version it is still write-or-update. Store: `lane12_d_an_expected_version_of_nothing_is_a_stale_write.sql`.
- Tests: the clone-backed suites archive every table and Home they made (found by slug, Home read from the table, retried, failures named) even when the clone times out; PostgREST's transient PGRST002 "Could not query the database for the schema cache" skips the plain-client clause by name as infrastructure.

Consumer action: `ensureTable`'s answer type is now `EnsureResult` (a superset of `DeclareResult`); none required.

## 0.60.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.126..npm/records/v0.60.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.60.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.126..npm/records/v0.60.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.60.0

Lane TABLE-ACTIONS — a table can be duplicated.

- **`tableDuplicate({ tableId, withRecords?, name?, organizationId? })`** over the new store door `custom.table_duplicate`: a NEW Table with the source's settings and look, every live column (new ids), every choice list as a new list (same words, colors, keys and order), every saved view (filters, sorts, layouts and every id inside them pointing at the copy), and with `withRecords` every live record (links to another row of the same table follow to the copy; links to other tables stay). Name defaults to "<name> (copy)", "(copy 2)", …; the destination defaults to the source's organization and must be one the caller belongs to. The source is never written. Answers `TableDuplicated` (new export, with `parseTableDuplicated`): what was copied, what stayed behind, the copy's path. Refusals carry the store's one sentence.
- The door is on the nightly clone only until the chair applies `tableactions_a_table_can_be_duplicated.sql` to the main database; until then the call asks the store and answers `door_absent` naming that file.

Consumer action: none required. No screen calls it yet.

## 0.59.0

Lane PLATFORM-APP-DATA (v6 lane 12), wave 1 — a page can find-or-create a small table and write rows by key.

- **`createRecordsClient` binds a plain supabase-js client itself.** The README's `dataSource: supabase` answered every call PGRST202 "Could not find the function public.table_kernel_id"; a client with `schema()`/`from()`/`auth` is now wrapped in `supabaseDataSource` once (an already-bound source is left alone, a host's own `rpc(fn, args, { schema })` adapter is used as is), and something with no `rpc` is refused by name naming `supabaseDataSource()`. New export `asRecordsDataSource`.
- **`tableFind({ slug } | { name })`** — one Table in ONE request (`custom.table_find`), read through the same read door as `tableRead`; `null` when none or not visible; the oldest visible when a slug repeats.
- **`ensureTable(client, NewTableSpec)` / `tableEnsure({ spec })`** — find-or-make by slug in one transaction (`custom.table_ensure`, advisory lock on organization+slug): two first saves at once get the same table; a second call adds missing columns, never retypes or drops one. `declareTable` is unchanged (a person's "Create table" always makes one).
- **`recordUpsert({ table_id, key, data, expectedVersion? })`** — write-or-update by a unique key column (`custom.record_upsert`), race-safe; a key column without a `unique` rule is refused by its name. `NewFieldSpec.unique` declares the rule; `FieldRule.kind` gains `"unique"`.
- The three doors are on the nightly clone only until the chair applies them to the main database. Until then each call asks the store and answers `door_absent` naming the owing file — never a crash, never an empty answer.

Consumer action: none required. Pages that hand-search `tableList()` for a slug can move to `tableFind` / `ensureTable` once the doors are on the main database.

## 0.58.127

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.126..npm/records/v0.58.127 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.126

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.125..npm/records/v0.58.126 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.125
### Unreleased

- **The older tables' words door is gone** (lane OLD-READERS-REMOVAL, 2026-10-01). Every relation cell lives in the record store now, so `relationWordsMany` reads only `custom.relation_words_many`: `RelationWordsManyArgs` is `{ field_id, record_ids }`; `RelationStore`, `RelationDisplaySpec`, `WORKBENCH_DOORS`, `WORKBENCH_SCHEMA` and `WorkbenchDoorKey` are removed (no consumer passed `store: "older"`). Consumer action: none.

## 0.58.124

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.123..npm/records/v0.58.124 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.123

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.122..npm/records/v0.58.123 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.122

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.120..npm/records/v0.58.122 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.121
### Unreleased

- **A measure over a column the reader may not read is a withheld state, never an error** (AGG-WITHHELD, 2026-10-01). `custom.record_aggregate` answers such a measure as `null` with the store's notice; `recordAggregate` lifts it to `AggregateRow.withheld` (new `liftWithheld`), so a read never errors for a column the person cannot see.

- **The drill contract says what a number counts, thresholds go both ways, and a Saved view carries its glance columns and its stacked chart** (lane DRILL-FLIP-FIXES, VERIFY-DRILL-FINAL R1, R2, R5, L1, L2, L3, 2026-10-01). `DrillMeasure.description` / `DrillDimension.description` (≤ `DRILL_DESCRIPTION_MAX` = 140 characters, a header tooltip); `DrillHaving.op` adds `"<="` and `"<"` (the door accepts them once `drillflip_a_threshold_can_be_at_most.sql` is applied); `DrillView.attributes` (Dimensions each group of the view holds one value of, shown beside its numbers) and `DrillView.chart.stack` (Measures drawn stacked). Definitions: `cost_per_request` (cost ÷ requests) on `ai_usage`, shown in every `spend_by_*` cut and the executions' conversation / session cuts; "Requests" and "Calls" describe what they count; the Spend cuts' explanations are the Dimensions' descriptions; `costliest_requests` lists person, agent, feature, model, provider, origin, outcome, organization and app; `ai_calls` gains `cx_tokens_by_day` (input vs output tokens, stacked). Proof: `the-flip-fixes-are-in-the-definitions.test.ts` (7; 6 red on HEAD). Consumer action: none; the owner re-syncs the definitions.

## 0.58.120

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.118..npm/records/v0.58.120 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.119

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.118..npm/records/v0.58.119 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.118

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.117..npm/records/v0.58.118 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.117

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.115..npm/records/v0.58.117 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.116
### Unreleased

- **Drill: a sum of Measures, a moment, choice colors** (lane DRILL-PARITY-LAST, 2026-09-30): `DrillMeasureOp` gains `"sum_of"` with `parts` (total tokens = in + cached + out, recomputed per group, Other and total); a `max`/`min` of a time column declares `unit: "time"` (a moment, never additive, no threshold); a choice may carry `color: DrillChartToken` (`DRILL_CHART_TOKENS`, `--matrx-chart-1…10|other`). `ai_usage` declares `tokens_total`, `last_activity` (the latest hour) and origin colors; `ai_usage_executions` declares `tokens_total`, `tool_calls`, `last_activity`, a `finish_reason` Dimension, and both records carry `finish_reason` + `tool_calls`. Needs the door file `drillparity_a_measure_can_add_measures_and_say_a_moment.sql` and the view file on the database first. Consumer action: a screen formats `unit: "time"` as a moment and may read `choices[].color`.

- **Metadata search, aggregate and owner lookup span organizations** (active-org law, 2026-09-30): new `organizationsOpen()` (which of the person's organizations can answer, unavailable ones WITH reason), `ownerOrganization({table_id|record_id})`; `metadataSearch({text, organization_id?})` now returns `{matches, organizations_covered, unavailable}` (each match carries `organization_id`; was a door-absent refusal typed `never`, still a refusal when no organization answers); `recordAggregate` takes optional `organization_id` and, with `config.organizationIds`, runs in the table's owning organization (rows carry `organization_id`). No `organizationIds` = unchanged. Consumer action: none required; matrx-extend drops its per-org fan-out.

- **`wholeValueSize` is kit's `formatFileSize`** (package-twins drain, 2026-09-30): `728 KB` unchanged; a pointer under 10 KB reads `5.0 KB` (was `5 KB`) and one under 1 KB reads `512 B` (was `1 KB`). New dependency: `@ai-matrx/kit` (`workspace:*`). Consumer action: none.

## 0.58.115

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.114..npm/records/v0.58.115 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.114

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.113..npm/records/v0.58.114 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.113

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.112..npm/records/v0.58.113 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.112

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.111..npm/records/v0.58.112 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.111
### Unreleased

- **Drill contract (lane DRILL-GAPS):** `DrillMeasureOp` gains `"percentile"` (with `p`, e.g. 0.9 = p90)
  and `"rate"` (a run rate: the window's sum ÷ the window's length × `per`, `"day"` or `"month"` = 30
  days); a ratio's `den` may be a list (added up) and a part may be another ratio; `scale` multiplies a
  ratio or a run rate (1000 = per 1,000); any non-ratio Measure may carry its own `where`; a Dimension
  may say `empty_label`; an invoker definition may declare `records` (its own rows). `DrillAdHocOp`
  excludes the three declared-only operations. kg_cost declares the six KG unit-economics numbers and
  its recent runs; workflow_runs gains the organization lane (rule `admin`).
  **Consumer action:** none required; screens that format Measures read `unit` (`usd`, `tokens`,
  `count`, `ms`, `share`, `times`, `characters`).

## 0.58.110

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.109..npm/records/v0.58.110 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.109

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.108..npm/records/v0.58.109 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.108

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.105..npm/records/v0.58.108 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.107

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.105..npm/records/v0.58.107 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.106

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.105..npm/records/v0.58.106 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.105

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.103..npm/records/v0.58.105 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.104

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.103..npm/records/v0.58.104 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.103

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.102..npm/records/v0.58.103 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.102

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.101..npm/records/v0.58.102 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.101

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.100..npm/records/v0.58.101 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.100
### Unreleased

- **The records behind a number are its ledger; every answer says as of** (lane DRILL-LEDGER-RECORDS, 2026-09-30; PROGRESS-DRILL-FINISH decisions 14, 15/26, 25). Contract additions in `drill.ts`: `DrillMeasure.op = "ratio"` with `num: string[]` / `den` (a ratio of the definition's own sum/count Measures, recomputed per group, Other and total); `DrillQuestion.having: DrillHaving[]` (`measure`, `op` `>=`/`>`, exactly one of `value` / `share_of_total` / `times_median` + `median_nonzero`, optional `knob` = a drill setting's address) applied BEFORE the cut into Other — a group that misses is added into Other so the answer still adds up, and the total row says how many met the rule; `DrillQuestion.offset` / `columns` typed; `DrillDefinition.views` (`DrillView`), `.findings` (`DrillFinding` with `knobs`), `.records` (`DrillRecords` — `fact`, `columns`, optional `labels`), `.stale_after_knob: string | null`; `DrillAnswerRow.as_of`, `DrillAnswer.as_of`, `DrillRowsPage.as_of` + `measures` (a definer definition's records, first page: what they add up to); `DrillAdHocOp`; `drillFindingKnob(definition, finding, knob)` → `drill.finding.<definition>.<finding>.<knob>`. The first records page also carries `counted` (the NUMBER's own sums for the same filter, as of `as_of`) and, only when a value landed on a counted row after the count, `settling` (`{measure: {counted, now, difference}}`) with `says` in words ("$1.23 more has landed since the count at 06:40 UTC. …") — the number and its records are equal as of the count and any difference is said (VERIFY-DRILL-LEDGER-RECORDS F1). `ai_usage.drill.ts` declares its records relation (`ai_usage_executions`, request-level columns labelled as the request's) and its stale line (`drill.usage.stale_after_minutes`); its header no longer claims a schedule that did not exist. `scripts/drill-sync.ts`: `--target clone` (reads the nightly copy), seeds a definition's finding settings (never overwriting a person's value) and removes only the ones it added in the inverse, PASTES the `-- based-on:` lines of a replaced definition into the file it writes (it used to print them only, so a replacing sync file was refused by db:apply), and the census fails a live finding whose setting is not registered. **Consumer action:** none required; a `DrillAnswerRow` / `DrillRowsPage` built by hand in a test now needs `as_of`, and a `DrillDefinition` needs `stale_after_knob`. Proof: `__tests__/the-records-behind-a-number-are-its-ledger.test.ts` (3 of 4 red on the previous `drill.ts` / `ai_usage.drill.ts`, all green); the database half is matrx-frontend `scripts/campaign-tests/drillledger_records_green.sql` on the dev clone.
- **One read door answers every table** (lane DRILL-STANDARD-DOOR, 2026-09-29): `drillDescribe` / `drillAsk` / `drillRows` ask a SOURCE — `{kind: "table", id}` (a custom Table) or `{kind: "entity", token}` (a standard table by its registry token, or a declared definition by its key) — through the doors `platform.drill_describe` / `drill_ask` / `drill_rows`, and answer in ONE shape: `DrillAnswer` (rows of kind group / other / total with groups, measures, row_count, the comparison columns, distinct_groups, labels, says). A standard table is counted AS THE PERSON (its own row security decides every row); groups past the "Groups shown before Other" setting fold into one Other row and the total row says so. New types in `@ai-matrx/records`: `DrillSource`, `DrillQuestion`, `DrillDefinition`, `DrillDimension`, `DrillMeasure`, `DrillPath`, `DrillAnswerRow`, `DrillAnswer`, `DrillRowsPage`, `DrillDeclaration` + `declareDrillDefinition()`. Declared definitions are `*.drill.ts` files compiled by `scripts/drill-sync.ts` (`pnpm drill:sync --lane <LANE>`); the census `pnpm check:drill-definitions-declare-lanes` (+ `:self-test`, red on a planted definer definition with no lane rule). Proof: `__tests__/one-door-answers-every-source.test.ts`.
- **A public choice is accepted by its key or its label** (lane HANDOVER, 2026-09-29): a stranger's booking page sent the Visit type's key ("follow_up"), and `coerceTypedAnswer` — on the page and again in the confirm route — answered "its options have not been read yet", because a public page never reads the column's options; nothing could be booked with a choice answered. A question carrying `public_choices` now matches a typed answer against them (key or label, any case), answering with the key; a word it does not offer is refused naming what it offers. Proof: `__tests__/a-public-choice-is-accepted-by-its-key-or-label.test.ts`, red on the old coercion.

## 0.58.98

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.97..npm/records/v0.58.98 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.97

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.96..npm/records/v0.58.97 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.96

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.95..npm/records/v0.58.96 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.95

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.89..npm/records/v0.58.95 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.94

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.89..npm/records/v0.58.94 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.93

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.89..npm/records/v0.58.93 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.92

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.89..npm/records/v0.58.92 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.91

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.89..npm/records/v0.58.91 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.90

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.89..npm/records/v0.58.90 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.89

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.78..npm/records/v0.58.89 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.88

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.78..npm/records/v0.58.88 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.87

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.78..npm/records/v0.58.87 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.86

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.78..npm/records/v0.58.86 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.85

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.78..npm/records/v0.58.85 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.84

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.78..npm/records/v0.58.84 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.83

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.78..npm/records/v0.58.83 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.82

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.78..npm/records/v0.58.82 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.81

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.78..npm/records/v0.58.81 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.80

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.78..npm/records/v0.58.80 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.79
### Unreleased

- **A subscription is read in its owner's clock** (lane HANDOVER, 2026-09-28): the store reads a schedule's time in `quiet_hours.tz` and falls back to UTC, and no caller without quiet hours sent a zone. `subscriptionDeclare` now passes every spec through `withCallersClock` (a zone the caller named is kept; none, and the caller's own is stamped). `QuietHours.start`/`end` are optional — a block may carry only the clock — and `hasQuietHours(q)` says whether it names a night. `callersTimeZone()` exported. Proof: `__tests__/a-summary-goes-at-its-owners-eight-oclock.test.ts`, red on the old client.

## 0.58.78

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.78 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.77

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.77 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.76

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.76 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.75

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.75 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.74

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.74 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.73

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.73 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.72

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.72 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.71

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.71 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.70

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.70 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.69

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.69 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.68

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.68 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.67

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.67 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.66

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.66 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.65
### Unreleased

- **The search door for an entity reference** (lane REFERENCE-CARRY, 2026-09-28): `entityReferenceSearch(source, organizationId, tokens, search, limit)` and the hook `useEntityReferenceSearch(tokens, search, enabled)` over `custom.entity_reference_search` — the things of those kinds this person may point a record at, each one the store's own write check would accept. `store.generated.ts` regenerated. No consumer action.
## 0.58.64

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.64 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.63

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.63 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.62

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.62 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.61

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.61 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.60

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.60 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.59

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.59 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.58

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.58 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.57

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.57 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.56

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.56 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.55

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.55 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.54

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.54 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.53

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.53 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.52

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.52 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.51

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.51 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.50

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.50 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.49

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.49 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.48

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.48 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.47

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.47 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.46

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.46 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.45

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.45 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.44

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.44 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.43

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.43 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.42

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.42 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.41

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.41 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.40

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.39..npm/records/v0.58.40 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.39

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.32..npm/records/v0.58.39 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.38

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.32..npm/records/v0.58.38 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.37

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.32..npm/records/v0.58.37 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.36

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.32..npm/records/v0.58.36 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.35

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.32..npm/records/v0.58.35 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.34

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.32..npm/records/v0.58.34 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.33

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.32..npm/records/v0.58.33 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.32

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.31..npm/records/v0.58.32 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.31

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.30..npm/records/v0.58.31 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.30
### Unreleased
### lane CHOICE-TAILS (2026-09-27): removing a choice that records still hold says where they go

- **New client door `fieldChoiceUsage({ field_id })`** over `custom.field_choice_usage`: how many records the caller may see hold each choice of one column, keyed by the option's id (type `ChoiceUsage`: `key`, `words`, `retired`, `records`).
- **New client door `fieldUpdateRehomingChoices({ field_id, patch, rehome?, cells_back? })`** over `custom.field_update_rehoming_choices`: the column's choices saved (through `field_update`) AND the records of each removed choice moved to another choice, kept as other values (only where the column takes other values) or cleared (each through `record_update`), in ONE transaction. `rehome` is `{ "<option id>": { then: "move", to } | { then: "keep" } | { then: "clear" } }` (type `ChoiceRehome`). The answer (`ChoicesRehomed`) carries `undo` — the list as it was and every cell as it was; sending `{ patch: { options: undo.options }, cells_back: undo.cells_back }` back through the same call puts both back in one transaction.
- **New pure helpers** `removedChoicesInUse(beforeIds, afterIds, usage)` (the removed choices the editor must ask about), `recordsUseSentence(n, words)` ("3 records use “X-ray”.") and `rehomeSentence(n, words, rehome, toWords?)`.
- `src/store.generated.ts` regenerated from the live store (the two doors, plus other lanes' live doors).
- **Proof:** `src/__tests__/a-removed-choice-says-where-its-records-go.test.ts` (a dental front desk's Visit Type: Cleaning · Exam · X-ray · Whitening) — its door clauses RED (`door_absent`) before the regeneration, GREEN after; the store half is `matrx-frontend/scripts/campaign-tests/choicetails_green.sql`.

#### Consumer action
- None required. A column editor that removes choices can now ask where their records go and pass `rehome`; without it a removed choice's records keep it as before.

## 0.58.29
### Unreleased
### lane CHOICE-COLUMN-EDIT (2026-09-27): edit a choice column's choices in place; words that are not a choice yet

- **`FieldPatch.options` is an ordered list of `string | { id, words }`** (new type `FieldOptionEdit`). `{id, words}` re-words that option in place (its key never changes, so every cell holding it reads the new words); plain words are the live same-words choice, a retired one brought back, or a new one; every choice takes its array position; a live choice the list leaves out is retired, never deleted. One transaction in `custom.field_update`, the same on a moved table as a native one.
- **`FieldPatch.options_add?: string[]`** adds words to a list column's choices without touching the others.
- **New client door `recordUpdateAddingChoices({ record_id, patch, add, expectedVersion? })`** over `custom.record_update_adding_choices`: `add` is `{ "<column key>": ["words"] }`; the words become a choice of that column and the cell takes them, one transaction. Mints an op id and announces the record changed, as `recordUpdate` does.
- **New client door `choiceNudge()`** over `custom.choice_nudge`: the Feature Knob `custom/choice_nudge` for the signed-in person — `"ask"` (default) · `"always_add"` · `"never_add"` (type `ChoiceNudge`, `asChoiceNudge`, `CHOICE_NUDGES`).
- **New pure helper `decideTypedChoice({ words, options, allowOther, nudge })`** — the ONE decision for words typed into a choice cell: `chosen` (with its key) · `ask` (with `keepOffered`, true only when the column takes other values) · `add` · `keep_as_typed` · `not_a_choice` · `empty`.
- `src/store.generated.ts` regenerated from the live store (the two doors and the `choice_nudge` knob, plus other lanes' live doors).
- **Proof:** `src/__tests__/a-typed-word-that-is-not-a-choice-is-decided-once.test.ts` (a software team's Account Type column: Cursor · Claude · Codex), RED on the previous bytes.

#### Consumer action
- A caller that built `options` as `string[]` still type-checks (plain words are still accepted), but an existing column's list is now REPLACED in order and anything left out is retired — send `{id, words}` for a choice you mean to keep or re-word.

## 0.58.28
### lane RENDER-AUDIT (2026-09-26)

- **A re-read page keeps every row that did not change.** `useRecords` / `useRecordPage` hand back the SAME object for a row the store answered exactly as before (same id, same everything it carries), so a page read after one write — the write door's own re-read, a reconnect, a colleague's new row — no longer gives every record a new identity and redraws every row of a grid. What is on screen is still exactly what the door answered.
- **The realtime subscription is no longer torn down on every page change.** `useLiveRows` listed the page's rows in its effect, so every patch, read or optimistic write left the channel and joined it again (and a notice in the gap landed on nobody). It now subscribes per (port, table, client) and reads the page it judges a notice against at the moment the notice lands.
- **An inline `onError` no longer rebuilds the client.** `RecordsProvider` holds one stable error sink that calls whatever the host passed last; a host writing `onError={(e) => …}` inline no longer re-reads every hook on every render. Whether a sink is bound at all is still part of the client's identity.

## 0.58.27

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.26..npm/records/v0.58.27 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.26

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.25..npm/records/v0.58.26 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.25

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.24..npm/records/v0.58.25 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.24 (2026-09-26 — consumer-safe SheetJS packaging)

* **Packaging:** SheetJS 0.20.3 is now compiled into the package instead of appearing as a remote
  tarball dependency. npm's consumer security policy rejects remote dependencies; installs are
  now registry-only and retain the maintained SheetJS release. No consumer action.

## 0.58.23 (2026-09-26 — secure SheetJS release)

* **Security:** replaced the stale npm-registry SheetJS 0.18.5 dependency with the official
  SheetJS 0.20.3 release. This removes the prototype-pollution and ReDoS advisories from both
  untrusted workbook import and XLSX export. No consumer action.

## 0.58.22 (2026-09-26 — lane data-tables-grid-overhaul)

* **`useTable` re-reads on the table's shape nudge** (`tableShapeChanged`, or the realtime port's
  table notice) exactly as `useFields` does. A table's own declaration moves with its shape — its
  row label (`title_field`), name and sort — so an open record panel now names its record by a new
  row label the moment it changes, instead of only once reopened. No consumer action.

## 0.58.21

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.20..npm/records/v0.58.21 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.20 (2026-09-26 — cut by the automatic republish; carries lane data-tables-grid-overhaul, merge tranche 6k)

* **`useRecordPage(tableId, { pageSize, page, filter, search, sort, viewId })`** (`/react`) — one page
  through `client.listPage` (`custom.read_records_page`): the store sorts, searches, filters and
  counts; `total` is the matching set this reader may see. Realtime-aware exactly like
  `useRecords`: a notice re-reads only the ids it names and patches them into the page (a row the
  page does not hold costs one page read). `tableId: null` reads nothing. The realtime half is one
  shared helper for both hooks. New types `UseRecordPageOptions`, `UseRecordPageState`.

## 0.58.19

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.17..npm/records/v0.58.19 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.18
### Unreleased

### Lane data-tables-grid-overhaul — DOOR-SPEED (2026-09-25)

* **`client.listPage({ table_id, filter?, search?, sort?, view_id?, limit?, offset? })`** — one page
  through the new `custom.read_records_page`: sorted (a choice column by the word a person sees),
  searched over the columns this reader may see, filtered, and COUNTED (`total` of the matching
  visible set). The record-store twin of the older store's paged door, so a grid never reads a whole
  table to draw one page. A sort on a hidden column or on a formula worked out on every read is
  refused by name. New types `RecordPageSort`, `RecordPage`.
* **`client.recordChangeMany({ table_id, changes })`** — inserts, updates (optional
  `expected_version`) and archives in ONE transaction through the new `custom.record_change_many`;
  every update lands in one statement. One refused change refuses the batch and nothing is saved;
  the hint names the change. New types `RecordChange`, `RecordChangeResult`.
* **`client.tableRead({ table_id })`** — one Table by id through `read_records_by_ids` on the Table
  kernel. The kernel id is now asked once per client (`tableList` shares it).
* **`useTable` reads one Table by id** (it listed up to 500 Tables to find one, and answered "no
  table" past the 500th). **`useTable`, `useTables`, `useMyLevels`, `useMyLevel` and `useFields`
  share ONE request per question** across sibling mounts (in flight, and for 2 s after); `reload()`
  always asks again. *Consumer action:* none — same answers, fewer requests.
* Both doors applied to production 2026-09-26 (`matrx-frontend/migrations/campaign/doorspeed_*.sql`);
  `store.generated.ts` regenerated.

## 0.58.17 (2026-09-25 — hand-corrected by PUBLISH-CHANGELOG-FIX: this is the version KERNEL-TAILS's commits actually shipped in, per `git log npm/records/v0.58.16..npm/records/v0.58.17 -- apps/shared/records`)

### Lane KERNEL-TAILS (2026-09-25)

* **No signed-out caller holds any grant in `custom`.** The anonymous EXECUTE on
  `custom._older_table_copy_refusal(uuid)` was a dead door (`anon` has no USAGE on the schema and
  the copy fence runs as the writer) and is revoked; its door row no longer declares anonymous
  callers. `real-doors.test.ts` now asserts 0 anon-executable functions in `custom` and 0 anonymous
  door rows for it, declared or not. No API change.

## 0.58.16 (2026-09-25 — hand-corrected by PUBLISH-CHANGELOG-FIX: this is the version SUITE-HEALTH-3's commit actually shipped in, per `git log npm/records/v0.58.15..npm/records/v0.58.16 -- apps/shared/records`)

### Lane SUITE-HEALTH-3 (2026-09-25)

* **Retired: `custom.context_resolve(p_organization_id, p_bindings, p_scope_slug)`** — the
  three-argument overload was a "retired shape" whose whole body forwarded to
  `custom.context_resolve(p_bindings)`, ignoring its organization and slug. It is revoked, removed
  from `platform.client_callable_door` and dropped (inverse kept:
  `matrx-frontend/migrations/inverse/suitehealth3_the_retired_context_resolve_shape_is_gone_down.sql`).
  `store.generated.ts` no longer lists it. *Consumer action:* none — every caller already uses the
  one-argument door (`RecordStore.context_resolve`).
* **`client.tablesLiveWhere` / `custom.where_tables_live` answers `record` for an older table the
  person may not open** (the older store's own read rule for her, or the one ladder on its copy) —
  exactly what an id nobody minted answers — so the door is no longer an existence oracle. A person
  who may open the table is answered as before. *Consumer action:* none; `record` then leads to the
  store's own not-found answer, as for any unknown id.
* **The copy fence's refusal names the table only to a caller `custom.assert_client_may_open`
  admits to the copy** (it used to name it to every caller with no signed-in person, including a
  visitor's public form); everyone else reads "this table". The write is refused either way.
* **`custom.read_records_in_view_order` pages through `custom.page_size`** — a page past the
  organization's ceiling (default 1000) or below one row is refused with 22023 and the ceiling in
  DETAIL, instead of being silently clamped to 500 or 1. *Consumer action:* none for records-ui's
  hand-order grid, which never asks outside 1..500.

## 0.58.15 (2026-09-25 — tagged by the auto release, published; changelog set by PUBLISH-SWEEP-8)

Carries every entry below that was Unreleased at the tag (npm/records/v0.58.15 = 5f4025632f).

### Lane RECORDS-ONE-DOOR (2026-09-25)

* **`client.tablesLiveWhere({ table_ids })`** — the typed `custom.where_tables_live` cutover
  door. It answers which store currently receives writes for every supplied table id. It is not
  `tableHome`: copied older/store tables share an id during COPY mode, and the organization's
  switch decides their writable store. *Consumer action:* hosts that select an older versus record
  write path must call this method instead of reaching the `custom` schema directly.

### Lane CONTEXT-VALUES-NAMED-2 (2026-09-25)

* **`custom.resolve_context` takes `p_system_item_refs text[] DEFAULT NULL`** — the System items the
  turn names (ids or keys); the door reads only those, and NULL or empty reads none (it used to hand
  back every deliverable System item). `store.generated.ts` regenerated.
  *Consumer action:* none for a browser — the door is the server's agent-context path; a caller that
  wants the platform's default System items passes the list from the knob
  `context/system_item_defaults`.

### Lane VIEW-STATE-PERSONAL (2026-09-25)

* **`viewLookRead({ table_id })` / `viewLookSet({ table_id, view_id, look })`** — the caller's own
  look at each saved view of a table (`custom.view_look_read` / `custom.view_look_set`, viewer): the
  view settings one person chose while looking, kept for her alone. `look: null` is "Reset to view".
  The shared view row is never written by these doors.
* **`viewDeclare` on an EXISTING view now needs editor on the table** (or the view is the caller's
  own and is not the table's default) — "Save to view"; a viewer is refused by name (`42501`,
  *"Only someone who can edit this table saves a change onto its view … for everyone"*). A NEW view
  is unchanged (viewer). **`viewRecordOrderSet` needs editor** (a hand-set order is the view's sort).
  *Consumer action:* send a person's picks through `viewLookSet`; call `viewDeclare` with a
  `view_id` only on an explicit save. `store.generated.ts` regenerated (707 doors).

### Lane STORE-READ-PERF-2 (2026-09-25)

* **The read doors ask the ladder once per class of records, not once per record.** No door's
  name-keyed answer moved (proved byte-identical on the dev clone and on production); they are
  faster. On production, server-side: the context lookup over AI Matrx → Feature (17 scopes)
  199 → 142 ms, a 200-row grid page 356 → 96 ms, `listByIds` of 200 rows 879 → 134 ms.
* **`read_record(..., p_by_id: true)` and `listByIds` / `custom.read_records_by_ids` by id now key
  EVERY declared Field by its id**, exactly as `list` / `read_records` / `read_records_matching`
  always did (DOOR-N-5: "name-keyed by default, id-keyed on request"). They used to rename only
  the Fields the reader could NOT see, so one record asked for by id came back keyed by name from
  the single-record door and by id from the page door, and its keys changed with the reader's
  rung. `_hidden`, `_choices`, `_alternates` and `_retired` keep the keys they had.
  *Consumer action:* none for this package's own calls (they read name-keyed). A caller that asked
  by id and then read values by NAME was depending on the defect and should ask by name.

## 0.58.14

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.13..npm/records/v0.58.14 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.13

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.12..npm/records/v0.58.13 -- apps/shared/records`).
No source changes intended and no consumer action required.

### Shipped in 0.58.13 — lane VIEW-SWITCH-NOT-DESIGNATION (2026-09-25)

The 0.58.13 auto-republish carried this source (verified by tarball, PUBLISH-SWEEP-7).

* **`viewDesignate({ table_id, view_id, layout })`** — the one door that designates how a Table
  opens (`custom.view_designate`, EDITOR on the table): the view becomes the table's only default
  and `layout`, when given, the layout everyone opens it in. `viewDeclare` now refuses a change to
  the default view's `layout` / `is_default`, and a second default, by name (22023) — a look at a
  table never changes how it opens (VERIFIER-21). `store.generated.ts` regenerated from the live
  store (adds `view_designate`).

## 0.58.12 (2026-09-25)

### Lane DECLARE-ENTITY-REF (2026-09-25)

* **Every column word reaches the door as `type`.** `declareTable` / `addFields` / `fieldDeclarationFor`
  mapped `text` / `long_text` / `number` synonyms to `plain` and sent EVERY other word as
  `parity_type` — but `custom._field_document_for` accepts `parity_type` only for the fourteen parity
  types, while its `type` arm resolves them all. So eight words were refused ("There is no field type
  called …"): `entity_reference`, `relation`, `signature`, `autonumber`, `created_time`,
  `modified_time`, `list`, `boolean`. The word is now sent as `type`, unmapped (multiline text still
  becomes `long_text`), and `NewFieldSpec.allowedTypes` carries an entity reference's kinds as
  `allowed_types`. Guard: `every-declarable-word-reaches-the-door-as-its-word.test.ts` holds the
  door's rule against every word it resolves. Proven live: a table with an
  `entity_reference → ai_model` column declared as admin, a record written with
  `{token: "ai_model", id}`, one `platform.associations` row answered.
  **Consumer action:** a caller that put `allowed_types` in `config` may keep it (the door reads both)
  or move it to `allowedTypes`; nothing else changes.

## 0.58.11 (2026-09-25 — published by PUBLISH-SWEEP-6)

0.58.10 was tagged but never reached npm: its publish gate (`real-doors.test.ts`, "no signed-out caller
reaches the store") counted `custom._older_table_copy_refusal(uuid)`, an anonymous door lane
WHERE-LIVES-SWITCH declared in `platform.client_callable_door`. The gate now counts only UNDECLARED
anon grants and also asserts anon holds no USAGE on schema `custom`. Everything under 0.58.10 below
ships here.

## 0.58.10 (2026-09-25 — tagged by PUBLISH-SWEEP-5; never published, see 0.58.11)

Carries S5-PRIME-2's decision hold (`WorkInboxItem.undo_seconds` / `undo_refusal`, `3e2c93da3c`; registry
689 doors), which records-ui 0.85.12 reads, so it publishes first. ORDER-FILTER shipped in 0.58.9 (below).

### Lane MOVER-BIG-VALUES (2026-09-25)

* **A value kept as a file** (`core/wholeValue.ts`): a value over the organization's ceiling for one
  value is kept as a file and the cell holds its first 1000 characters, its envelope pointing at a
  `whole_value_in_file` provenance entry. `wholeValueOf(document, key)` / `wholeValueOfRead(value)`
  recognise it; `withWholeValues(rows, readFileText)` makes an export write the file's whole content;
  `cellText` never passes the words off as the whole value (it says where the rest is).

### Lane S5-PRIME-2, VERIFIER-20 (2026-09-25)

* **`WorkInboxItem.undo_seconds` / `undo_refusal`** (uichamp_s5c, applied to production
  2026-09-25): how long a decision on the row is held with Undo — the organization's
  `custom/decision_undo_seconds` (default 5) for a pending approval or proposal, 0 with the store's
  sentence when a run already waits on the answer, null for work and closed rows. Registry
  regenerated (689 doors).

## 0.58.9

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.8..npm/records/v0.58.9 -- apps/shared/records`).
No source changes intended and no consumer action required.

### Lane ORDER-FILTER (2026-09-25)

* **The order door takes the view's filter.** `readInViewOrder({ view_id, filter, limit, offset })`
  now takes `filter` (`StoreFilter`: the flat map or a nested Rule — the view's own `where`, or a
  number clicked on a dashboard) and sends it as `p_filter` to `custom.read_records_in_view_order`
  (store file `orderfilter_a_hand_ordered_view_reads_exactly_its_filtered_rows.sql`, applied to
  production). The store reads exactly the matching rows in their places and pages THAT order; each
  row answers its stored position, and rows the filter hides keep theirs. No filter (absent, `null`
  or `{}`) sends no `p_filter`, the unfiltered door as before. `store.generated.ts` regenerated.
  **Consumer action:** a host that read the door without a filter and narrowed the rows afterwards
  (the Sheet, records-ui before this) passes the filter instead.

## 0.58.8 (2026-09-25 — published by PUBLISH-SWEEP-5)

Carries UI-FIX-19's `TableHomeDestination.slug` (`461302fbf7`), which records-ui 0.85.10 reads to tell
two same-named organizations apart in the move list, so it publishes first.

### Also in this release

### Lane UI-FIX-19 (2026-09-24)

* **A move destination carries its web address.** `TableHomeDestination.slug` (`string | null`),
  read by `parseTableHome` from `custom.table_home`'s destinations (store file
  `uifix19_a_destination_carries_its_address.sql`, applied). A screen draws it beside a name
  another destination also carries, so two organizations with one name can be told apart
  (VERIFIER-19 #9). **Consumer action:** a host that builds `TableHomeDestination` by hand adds
  `slug`.

## 0.58.7

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.6..npm/records/v0.58.7 -- apps/shared/records`).
No source changes intended and no consumer action required.

## Unreleased — lane SOURCE-KEY (2026-09-25)

* **A record-store table's event-source key is `record:<table id>`** (was `custom_record:<table id>`,
  the retired tier-2 table's word). `recordChangeTrigger(tableId)` now writes it; new
  `recordSourceKey(tableId)` is the one builder, and `recordSourceTable(key)` reads the table back
  from either key (rows saved before the move). The store moved first (lane SOURCE-KEY, applied to
  production 2026-09-25 03:29 UTC): `custom.record_change_actions`, `custom.table_webhook_declare`
  and the row-change producer write `record:<table id>`, read both, and turn an older client's
  `custom_record:` key into the new one on the way in, so a schedule saved by an older release
  still fires until the store refuses the old key (the next step, once this release is installed).
  **Consumer action:** anything that compares a trigger's or webhook's key to the literal
  `custom_record:` must use `recordSourceTable(key)`.

## 0.58.6 (2026-09-24 — published by PUBLISH-SWEEP-5)

0.58.5 (the release run's automatic tag, run 36082256533) never reached npm: the live census found
the store raising `XX000`, which STORE-TAILS-3 removed at the source (below). This release carries
S5-PRIME's and S5-PRIME-2's inbox doors, which records-ui 0.85.7 needs, so it publishes first.

### Lane STORE-TAILS-3 (2026-09-25)

* **`42P17` is mapped to `refused_by_rule`**: the store refuses, by name, a formula / lookup /
  rollup that would read itself round a circle (on declare and edit), and refuses a circle stored
  before that rule when it is worked out (applied to production 2026-09-25 02:51 UTC).
* **No door raises `XX000` any more**: `custom._view_key_value` judges `filters` and refuses a
  store-written or unknown view setting as a named `22023` (applied 03:02 UTC). The live census
  clause "maps every refusal class the store actually raises" is green again.
* **The live suite's no-standing clause reads its stranger organization from the write target**:
  a typed production id that the rehearsal branch does not hold made the FIXTURE answer 23503
  (`knob_override_organization_id_fkey`) before the door ever ran; the door itself answers 42501
  on every target (measured on the branch and read-only on production).
* `src/store.generated.ts` regenerated against the live store (686 doors; new server-only helpers
  `archive_event_of`, `far_value`, `record_value_one`, `field_cycle`,
  `field_context_policy_floor`, `context_policy_rank`). No consumer action.

### Lane S5-PRIME-2 (2026-09-24)

* **Snooze and clear, as one person's own inbox state** (uichamp_s5b + uichamp_s5b2, applied to
  production 2026-09-25 02:13 UTC): `inboxSnooze({ item_id, until })`, `inboxUnsnooze`,
  `inboxClear` (a decision still owed is refused, SQLSTATE 55000, "This one needs a decision"),
  `inboxUnclear`, and `inboxCounts({ everyOrganization })` — waiting / snoozed / cleared / overdue
  per organization, counted by the same predicate the inbox lists with (the shell badge's number).
  Types `WorkInboxView`, `InboxStateChange`, `InboxCounts`.
* **`workInbox({ view })`**: `"inbox"` (default) hides what this person snoozed or cleared,
  `"snoozed"` lists what they put off (soonest back first), `"done"` what they cleared.
  `WorkInboxItem` gains `snoozed_until`, `cleared_at`, `snoozed_count`, `cleared_count`.
* **`P0002` is mapped to `not_found`**: the inbox doors answer it for an id that is not in this
  person's inbox (an invented id, somebody else's item and another organization's alike).
* `src/store.generated.ts` regenerated against the live store (679 doors, knob
  `inbox_reminder_after_days`).

### Lane S5-PRIME (2026-09-24)

* **`WorkInboxItem` carries the table and, when closed, who decided it and when**: `table_id`,
  `table_name`, `decided_by`, `decided_by_name`, `decided_at`, `outcome` — the six columns
  `custom.work_inbox` now answers (uichamp_s5, applied to production 2026-09-25 01:07 UTC).
* **`WorkApprovalState`** = `pending | approved | declined | withdrawn`. `withdrawn` is the store's
  own close: archiving a record or table withdraws the approvals waiting on it, with the reason.
* **`WorkDecideVerdict` gains `archived`**: `work_decide_many` answers it when the thing an approval
  would change is archived (SQLSTATE 55000 from `work_approval_decide`, sentence names it).
* `src/store.generated.ts` regenerated against the live store (669 doors).

## 0.58.5

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.4..npm/records/v0.58.5 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.4 (2026-09-24 — published by PUBLISH-SWEEP-5)

**`src/store.generated.ts` is the live main database's catalogue after the 2026-09-24 production
applies** (666 doors; `a86b902c12`): `table_home` / `table_move`, `portal_form` / `portal_form_submit`,
`record_aggregate(…, p_compare)`, `pipeline_board`, `plural_of` and the rest now answer instead of
`door_absent`, and `entity_reference` is a declarable kind. 0.58.3 (the release run's automatic tag,
run 36079091025) never reached npm: `real-doors` found `portal_form` claimed absent while live, and
asked for `record_aggregate`'s old eight-argument signature. **`55000` is mapped** to
`refused_by_rule`: `custom.table_move` raises it when a move the table's present state does not allow
(a destination that cannot take it, something holding it, contents in a circle) is refused in the
store's own words and nothing moves.

### Lane SC-1-TAILS (2026-09-24)

* **`client.tableHome({ table_id })` and `client.tableMove({ table_id, to_organization_id, expected_version })`** — the
  store utility for "which organization is this table in, and move it" (`custom.table_home` / `custom.table_move`,
  SC-1' + SC-1-TAILS). Asked by the TABLE's id, never the bound organization; `null` = not given to this person;
  `door_absent` (and nothing called) on a store whose catalogue lacks the doors.
* `placement.ts` gains `TableHome`, `TableHomeDestination`, `TableMoved`, `parseTableHome`, `parseTableMoved` and
  `moveConsequence` (the sentence said before a move: columns, rows, trash, the tables that live inside it, links
  that reach back across the wall).
* **Consumer action:** matrx-frontend swaps `features/unified-data/where-it-lives/` for `@ai-matrx/records-ui`'s
  `WhereItLives` once both are installed. `pnpm records:generate` after the chair applies the doors.

## 0.58.3

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.2..npm/records/v0.58.3 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.1..npm/records/v0.58.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.1

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.58.0..npm/records/v0.58.1 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.58.0 (2026-09-24 — published by PUBLISH-SWEEP-5; folds every section below down to 0.57.3)

* **`src/store.generated.ts` is regenerated from the LIVE main database at publish time** (608 doors).
  The integrator sweep `79b505cfa5` had committed a catalogue generated against a rehearsal copy,
  claiming 30 doors production does not have (`form_preview_asks`, `table_row_counts`,
  `agg_*`, `pipeline_move_many`, `work_decide_many` …); `real-doors` refused it. Those doors answer
  `door_absent` naming their file until the chair's production window applies them and
  `pnpm records:generate` runs again. Some sections below reached main through integrator sweeps
  (`79b505cfa5`, `4bf803c452`) rather than their lanes' own commits.
* S2-PRIME's `StoreFilter` (`isRuleFilter`, `stableFilterKey`; `e7e53d3269`) and S6's client/door
  hunks (`4bf803c452`) ride in this release.

### Lane S3 PERIODS (2026-09-24)

* **A target read from a goal column.** `DashboardTarget` is a fixed `value` OR a `field` (a Field
  key of the block's Table) with `op` `sum | avg | min | max` (default `sum`): the store adds that
  column up over exactly the records the block's own number read — same filter, same current window,
  same reader. `DashboardTargetResult.value` is now `number | null` and carries `field`, `op` and
  `refused` (the store's sentence when this reader may not read the goal column; the block still
  answers its number). Store side: matrx-frontend
  `uichamp_s3_a_target_can_be_read_from_a_goal_column.sql` (chair applies after the S3 files).

### Lane S4 BATCH-DOORS (2026-09-24)

* **Two batch doors, one transaction each, one answer per item.**
  `pipelineMoveMany({moves: [{record_id, stage, expected_version, also?}]})` over
  `custom.pipeline_move_many`, and `workDecideMany({decisions: [{item, decision: "approve" | "decline",
  note?}]})` over `custom.work_decide_many`. Every item is judged exactly as the single door judges it
  (`pipeline_move` / `work_approval_decide`), in its own savepoint, so a refused item writes nothing and
  the rest still land. `expected_version` is required per card: a card somebody changed after the board
  loaded answers `changed_since_loaded` and is never overwritten. Verdicts: moves
  `moved | needs_approval | needs_fields | changed_since_loaded | no_right | refused`; decisions
  `approved | declined | already_decided | no_right | refused`. Cap: knob `custom/batch_items_max` (500).
  Types `PipelineMoveMany*`, `WorkDecideMany*`. Until the chair's production apply and
  `records:generate`, both answer `door_absent` naming lane S4.

### Lane DEFAULT-GRID-PARITY (2026-09-24)

* `NewFieldDeclaration.formula_text` and `FieldPatch.formula_text`: a formula in the language a
  person types (GRID-PRIMITIVES G3). The store parses it and keeps both the text and the expression.
* `FieldTypeWord` gains `autonumber`, `created_time` and `modified_time`, the words
  `custom.field_declare` takes for the store's own stamps (G5).
* For lane SC-R: `NewFieldDeclaration.allowed_types` and the `entity_reference` word on `FieldTypeWord`.

### Lane SC-R (2026-09-24)

* **A new field kind, `entity_reference`** — a column that points at a PLATFORM thing (a note, a web
  site, an agent, a workbook, a dataset, a conversation …) rather than at a record of one of your
  Tables. In the store it is a relation whose `config.target_mode` is `any`, restricted by
  `config.allowed_types` (entity tokens); each value is `{token, id}`, and every read door answers
  `{token, id, label}` with the label withheld from a reader who may not open the thing. Declare it
  with `fieldDeclare({ type: "entity_reference", allowed_types: ["note"] })`. `FIELD_KINDS` and
  `fieldKindFor` know it; `coerceTypedAnswer` takes `note:<id>` (or a bare id when the column allows one
  kind) and refuses words. File and Person stay `attachment` / `member`.
* **Helpers**: `isEntityReferenceConfig`, `entityReferenceKindsOf`, `entityRefsOf` (read a cell in any
  shape), `entityRefsToWrite` (what to send — `{token, id}` only, each thing once, the label dropped),
  and the two doors `entityReferenceKinds(source)` (`custom.entity_reference_kinds`, for the kind
  picker) and `entityReferenceWords(source, organizationId, refs)` (`custom.entity_reference_words`,
  the words of a chip before it is saved). Types `EntityRef`, `EntityRefWithWords`,
  `EntityReferenceKind`. Both doors are on the dev clone and the rehearsal branch;
  `pnpm records:generate` after the chair's production apply.

### Lane S3 (2026-09-24)

* **`recordAggregate({ …, compare })`** — `custom.record_aggregate(…, p_compare)`: the same question
  over a prior window (`previous_period`, `same_period_last_year`, or a fixed `range` baseline),
  answered under the same principal. Each row carries `prior_groups`, `prior_measures`,
  `prior_row_count`, `delta` ({current, prior, change, change_pct} per measure), `position` and the
  two `compare` windows as local ISO moments. Buckets are cut in the organization's calendar (knobs
  `custom/time_zone`, `custom/week_start`) and answer ISO 8601 with the organization's offset.
* **`dashboardRun({ …, compare, grain })`** — `custom.dashboard_run(…, p_compare, p_grain)`; blocks
  carry `compare` and `target` (`DashboardTarget`), and each block's answer adds `compare` (the
  windows), `totals`, `target` (current, progress, pace) and `compare_refused`.
* New types: `AggregateCompare`, `AggregateCompareKind`, `AggregateDelta`, `AggregateCompareWindows`,
  `DashboardTarget`, `DashboardTotals`, `DashboardTargetResult`. Both doors are on the dev clone and
  the rehearsal branch; `pnpm records:generate` after the chair's production apply.

### Lane FORMS-FIX-1 (2026-09-24)

* **`client.formPreviewAsks({ table_id, questions, values })`** — `custom.form_preview_asks`, the
  owner's form-preview door: which questions of a draft form are asked given the answers so far,
  answered by the same store loop as the public form (`custom._form_questions_asked` →
  `custom.rule_eval`), behind the organization wall and the caller's own viewer rung on the Table.
  It replaces `records-ui`'s direct `ruleEval` call from the browser, which every real database
  refuses. On the rehearsal branch; listed in `OWED_BY` until the chair's production apply and
  `pnpm records:generate`.

### Lane GRID-PRIMITIVES G9–G11 (2026-09-23)

* **Three cutover doors** (CUTOVER-PLAN.md F1, A3, F11/D5), on the rehearsal branch and waiting for
  the chair's production apply; until `pnpm records:generate` lists them each call answers
  `door_absent` naming its file. `tableListEverywhere()` — every table a picker may offer, from BOTH
  stores, in `get_user_tables`' shape plus `store` (`TableListEntry`). `tableSync({home_id, spec})`
  — refresh a sheet tab into ONE Table: new rows added, changed rows updated, dropped rows archived,
  columns the person added kept (`TableSyncSpec`, `TableSyncResult`). Pure `sheetSyncSpec()` turns a
  tab's cells into that spec and refuses repeated column names or row ids.
  `scopeTableProvision({home_id, item_id, scope_id})` — a context scope's template-bound Table in
  the store, one per item and scope. Tests: `grid.test.ts` (sheetSyncSpec).
* **G12** `tableRowCounts({table_ids})` (`TableRowCount`) and pure `withRowCounts(tables, counts)` —
  one call counts the rows each Table's reader may see, so a list never shows `row_count: null`;
  `recordHeaders({ids})` (`RecordHeader`) — created / updated / version of live rows the read door
  does not answer, `created_by` only as the caller's own id. Tests: `grid.test.ts` (withRowCounts).
* **G13** `viewRecordOrderSet({view_id, record_ids})` (`ViewOrderSet`) keeps a hand-set row order
  on a saved view; `readInViewOrder({view_id, limit, offset})` reads it back (placed rows by
  position, then the rest by created time); pure `moveInOrder(order, moved, toIndex)` builds the
  order a drag sends. Tests: `grid.test.ts` (moveInOrder).
* **Headless `declareTable`** (asked by INTEG-CLIENTS): `declareTable`, `addFields`,
  `fieldDeclarationFor`, `tokenFor`, `DEFAULT_FIELDS` and their types now live in
  `@ai-matrx/records/core` — no React, no host — so the extension's service worker and server-side
  births walk the one birth. `@ai-matrx/records-ui` re-exports them from here, unchanged for its
  callers. Tests: `declare-table.test.ts`.
* **G11 amended**: `scopeTableProvision({item_id, scope_id, home_id?})` — the Home is optional (the
  organization's own), and a scope provisioned before the move answers with its moved Table.
* **G14** `tableShareOutsideGrant({table_id, person, level})` (`OutsideShareAnswer`): an existing
  account outside the organization is granted at once and notified; no account → the invitation.

## 0.57.3 (unpublished — lane HUB-FIX)

* **The lane facts are their own call.** 0.57.2 folded `custom.table_facts` into every
  `tableList()`, so every screen that only LISTS tables paid a second round trip, and every
  test double that answers a table list had to answer it too (records-ui 0.84.5's publish gate:
  47 tests in 15 files red). `tableList()` is back to one door; new `tableFacts()` (door-presence:
  `null` when the catalogue has no such door) and pure `withTableFacts(tables, facts, me)`, which
  sets `visibility` and — only on the caller's own Tables — `created_by`. Exported from `/core`
  with `TableFact`. Tests: `table-list-facts.test.ts` (tableList never asks for the facts).

## 0.57.2 (unpublished — lane HUB-FIX, 2026-09-23; `custom.table_facts` is live on the main database, ledger 2026-09-23 11:51Z, and `STORE_DOORS` is regenerated to carry it)

* `tableList()` folds `custom.table_facts` onto every Table: `visibility` (a record column the
  Table document never carried, so every lane decision fell through to its default) and
  `created_by` — set ONLY to the caller's own id and only on Tables the caller made. The call is
  made only when `STORE_DOORS` carries the door (the door-presence pattern): a build generated
  without it asks nothing and returns the Tables exactly as before. (The code landed inside 0.57.1's commit `7f2a737913` by a sweep; this
  entry is its record.) Test `__tests__/table-list-facts.test.ts`.
* New `tableArchived({ table_id })`: the archived row for one Table, or `null` — paged over the
  Table kernel's archive, the same door a table's own archive uses.

## 0.57.1 (unpublished — lane RECORDS-UI-FIX, guide re-walk 2026-09-23)

* **A refusal is not an app error.** The default error sink printed every refusal through
  `console.error`, and matrx-frontend's error capture counts every `console.error`, so one
  refused value lit a red "2 errors" pill. The codes a person causes (`refused_by_rule`,
  `stale_write`, `invalid_argument`, `already_exists`, `missing_reference`) now go to
  `console.info`; everything else stays `console.error`. New exports (core):
  `isAPersonsRefusal`, `A_PERSONS_REFUSAL`. Test: `src/__tests__/a-refusal-is-never-an-app-error.test.ts`.
* **Every screen hears the writes made on its page.** `recordUpdate`, `recordDelete`,
  `recordRestore`, `restoreVersion` and `valueRestore` announce the record they wrote on
  success. New exports: core `announceRecordChanged` / `onRecordChanged`, react
  `useRecordChangeRevision(recordId)`; `useRecord` re-reads on it.

## Unreleased — STORE-RULE-GAPS (2026-09-23)

* `FieldRule` gains `example` (a pattern's "how to write it") and `min` (a length's shortest);
  `value` is optional for a min-only length rule. `NewFieldDeclaration.allow_other` (sent as
  `config.allow_other`) and `FieldPatch.allow_other`; `allowsOtherValues(field)`.
  `PredictedRefusal.detail`.
* The mirror predicts the store's new sentences word for word ("… has to be at least 20
  characters long"; a pattern with an example hints "Enter it like …" with `detail.example`),
  and no longer predicts a refusal for a list value that is not a uuid — the store holds option
  KEYS (CHOICE-VALUE) and a list that takes other values adds an unknown word.
* Needs matrx-frontend `storerulegaps_an_example_a_shortest_length_and_other_choices.sql`.

## 0.57.0

**The grid primitives are live, so their doors are in the catalogue.** (published by lane
PUBLISH-SWEEP-4; built by GRID-PRIMITIVES.) The 2026-09-23 chair window applied G1–G8 to the main
database and `pnpm records:generate` was re-run (`8df00afbb3`, `fa3434f967`): `store.generated.ts`
now lists 605 doors, so `tableDecorate`, `gridLayout`, `actionRun`, the table webhooks and the rest
of 0.56.0's grid methods reach the store instead of answering `door_absent`.

* **`recordChangeTrigger(tableId, { actions, changedFields })`** (G8) builds the event-schedule
  trigger that runs an agent when a row changes, the same way in either store; the scheduler's
  one matcher hears it, and the store switch is honoured.
* **`54000` maps to `store_limit`.** The grid doors refuse past an organization ceiling
  (`custom/example_rows_max`, `custom/table_webhooks_max`, the action-selection and decoration
  limits) with 54000 and the knob named in the hint; before this it would have read as `internal`.
* The refusal census now exempts exactly one raise: `custom.action_run`'s self-caught P0001 probe,
  and only while it is the sole raiser and the probe sentence is still in the live body.

Suite 111/111, `check:store-registry` OK against the live store.

## 0.56.0

**A Person or File column takes an id** (lane RELATION-TARGETS): `coerceTypedAnswer`'s member and
attachment arms pass a member's user id, an uploaded file's id or the kernel record's own id
through as the value, because the store resolves it to the Person / File record at every write
door; a typed NAME is still refused, with the control that holds it.

**And the grid's store primitives** (lane GRID-PRIMITIVES), below. Published by PUBLISH-SWEEP-4.

### The grid's store primitives

**Everything the /data grid rebuild needs from the store, as typed calls** (`src/grid.ts`):
`tableDecorate` / `tableDecorations` (row color by a choice column, ordered color rules, hand
highlights on rows, cells and columns — Fields and records by id), `gridLayout` (platform →
organization knob `custom/grid_layout` → the saved view's own `definition.layout`, each choice
saying where it came from), `formulaParse` (the older grid's formula text → the store's
expression), `actionDeclare` / `rowActions` / `actionRun` (a row action on a whole selection in
ONE transaction; `actionRefusals(error)` lists every record and field that refused),
`tableWebhookDeclare` / `tableWebhooks` / `tableWebhookArchive` (every record change out to a
signed webhook), `tableFromExample` (one real use case of `./use-cases`, built whole),
`autonumberBackfill`, `ioExport` and `exportXlsx` (the export door's own page as an .xlsx, a
withheld column named on its own sheet). `AggregateOperation` gains `median`, `filled`, `empty`,
`unique`.

**Consumer action:** none until the chair applies the five `gridprim_*` migrations to the main
database (01:00–04:00 PT) and `pnpm records:generate` lists the doors in `STORE_DOORS`. Until
then every new method answers `door_absent` naming GRID-PRIMITIVES — never a guess, never an
empty answer.

## 0.55.1

**0.55.0, actually published.** The tag `npm/records/v0.55.0` never reached the registry: its
publish gate failed on `pnpm typecheck` (OLD-TABLES-2's relation-words suite had four type
errors vitest never reads) and then on two `real-doors` clauses that still asserted the record
store OFF after STORE-ON's owner ruling made it ON by default. Both are fixed in the suites; no
runtime byte differs from what 0.55.0 describes below. The version moved rather than the tag
because a published tag is never rewritten. (lane PUBLISH-SWEEP-4.)

## 0.55.0

**Three things that were already in the source and had never reached a consumer.** (published
by lane PUBLISH-SWEEP-4; built by STORE-TXN, OLD-TABLES-2 and ORG-CLEANUP-2, each of which
landed its work marked "source only, not published".)

* **`recordWriteGraph`** — the typed client over `custom.record_write_graph` (lane STORE-TXN).
  A parent, the edges it stands on and the rows that belong to it in ONE transaction, with one
  op id for the whole graph. `recordWrite` and `recordWriteMany` are each one statement, so a
  caller needing a parent and its children to land together had no door: a loop is N
  transactions and a failure on child nineteen leaves a parent standing without the lines it
  promised. Not a second write path — the door calls `custom.record_write_many`,
  `public.assoc_add` and `platform.relation_set`, so every guard and trigger runs once as
  before. `RecordGraphWritten` counts the two edge writers apart (`edge_ids` / `relations`)
  instead of making a caller subtract.
* **`relationWordsMany` dispatches by store** (lane OLD-TABLES-2). A relation cell holds an id
  and a screen shows words, and there are two stores holding such cells: `custom.record` and
  the pre-doctrine `workbench.udt_*` tables, where most people's data actually sits. ONE client
  primitive now reads both, discriminated by an optional `store` — and **a caller that names
  none reaches exactly the door and the arguments it reached before**, so the thirteen live
  screens on `custom._words_for` change not one line. The older arm goes to
  `workbench.udt_row_words_many` through `WORKBENCH_DOORS` / `WORKBENCH_SCHEMA`, named by hand
  for the same reason `TRUST_DOORS` is (`records:generate` reads `custom` and only `custom`).
  The arms are asymmetric on purpose: the unified arm takes the FIELD because that is where the
  display spec is declared and judged; the older store has no declare-time judge, so its arm
  carries the spec. An id the door answered nothing for stays ABSENT from the map either way —
  inventing "Untitled" would erase the difference between a cell pointing at nothing and a row
  that was refused. New types: `RelationStore`, `RelationDisplaySpec`, `RelationWordsManyArgs`.
* **The `throwaway` placeholder pattern** (lane ORG-CLEANUP-2) — after 48 organizations named
  "Throwaway Dash 3fee58c2" walked back onto the owner's organization picker overnight and
  `PLACEHOLDER_PATTERNS` could not see one of them: 28 of the 48 carried no `zz` at all. It
  now catches "throwaway", "disposable org", "safe to delete" and "test only", and mirrors
  `iam.placeholder_in_a_name` on the platform database — a pattern added to one belongs in the
  other.

Also regenerated `store.generated.ts`, which was stale (`real-doors.test.ts` was red on main
claiming `record_write_graph` absent while it exists). Proof: `src/__tests__/relation-words-by-store.test.ts`
goes RED on 3 of 5 clauses with one line of the dispatch reversed. Suite at publish: 105/105
across 9 files.

## 0.54.0

**A formula can be re-aimed through a typed door.** (lane STORE-TAILS-2's ask, published by
PUBLISH-SWEEP-2.)

`custom.field_update` has carried `expr` in its behaviour arm since lane TAILS-2 — its spec
builder reads `coalesce(p_patch -> 'expr', v_old -> 'config' -> 'expr')` — but `FieldPatch`
never named the key. So the one edit a person makes to a formula that reads the wrong
column, re-aiming it, had no typed door in this package, and the client that needed it
reached for `custom.record_update` on the Field document instead: writing a column's
definition UNDER the store's guards rather than through them.

* `FieldPatch.expr?: Record<string, unknown>` — the expression in the same shape a Rule
  uses and read by the same evaluator. It belongs to the door's BEHAVIOUR arm, so it is
  sent WITH the word for what the column is (`type: "formula"`, or `parity_type` / `plain`);
  the settings arm has no `expr` key, so `expr` alone is not a change this door makes. The
  interface says so rather than leaving a caller to find out from silence.
* Guard: `src/__tests__/real-doors.test.ts` — "`expr` on a FieldPatch moves the expression;
  a re-type without one keeps it", live against the main store inside the suite's
  rollback-only transaction. Both halves are asserted, because "the expression is X
  afterwards" would pass on a door that ignored the patch. Every `{"field": …}` names the
  column BY ITS ID, which is what `custom._field_type_parity_guard` requires — a key is "a
  name rather than a column". Shown RED (the re-aimed expression still held the title
  column's id) then GREEN. Suite: 100/100.

## 0.53.0

**Archiving a table is a job, not a statement.** (lane FIX-11A, VERIFIER-11 finding F3.)

An admin of Greenline Landscaping Crew opened Settings on "Riverside Yard season tickets"
— 1,430 records the product's own import had just written — pressed **Delete this table**
and waited twelve seconds for `57014`, "canceling statement due to statement timeout …
Nothing was changed. Try a smaller page." There is no page. `custom.record_delete` aimed at
a Table recurses through everything the Table contains inside ONE transaction: a delete
rule, an access check, a history capture and an outbox row per record. That is unbounded by
construction — no timeout is long enough — and a store whose write path is batched and
whose delete path is not has one honest shape available to it.

* New client method `tableArchive({ table_id, chunk, includeTable })` over
  `custom.table_archive`, typed as `TableArchiveProgress`: `archived`, `remaining`,
  `total`, `archived_total`, `table_archived`, `done`, `chunk` and the store's own
  `message`. `chunk: 0` reports and changes nothing, so a screen can say how many records
  will be archived BEFORE a person commits to it.
* New hook `useTableArchive(tableId, { chunk, includeTable })` — it owns the loop, the
  numbers, the stop and the resume, so every screen that ever gets rid of a table inherits
  one behaviour instead of each re-deriving it. Resumable because it is STATELESS: what
  remains is read off the records themselves every pass, so a refused pass, a `stop()`, a
  closed tab or a deploy mid-run leave `interrupted` true and a `carryOn()` that continues
  from exactly where it stopped. It must never be reported as "nothing happened":
  `archived_total` is what actually landed.
* **The pass size is 50, measured, and it backs off.** One record costs about 35ms to
  archive on the live store, so a pass of 200 is ~7s against an 8s statement timeout — it
  died on `57014` on the first real table it was pointed at, which is the original defect
  one level up. When the store does run out of time the loop HALVES the pass and carries
  on (floor 10, `slowedDown` true so a screen can say so), because a pass that timed out
  changed nothing and retrying it smaller repeats no work.
* Nothing is hard-deleted: every row still goes through `custom.record_delete`, so every
  guard, history capture and outbox row happens and `recordRestore` still brings it back.

Proven on the main database on 2026-09-22 from the admin seat, through this package's own
client: 1,430 records archived in 25 passes across a deliberate cut and a resume (450 at
the cut, 1,430 at the end), then 312 more in 7 passes. Consumer action: a screen that
deletes a whole Table must call `useTableArchive`, never `useRecordMutation().remove`;
matrx-frontend's `pnpm check:table-archive-is-chunked` fails on the old shape.

## 0.52.0

**The portal archive doors are reachable.** (lane PUBLISH-SWEEP, closing ORG-CLEANUP's
own "left behind, named".)

* `custom.portal_archive`, `custom.portal_restore` and the archive-aware
  `custom.list_portals(org, 'active'|'archived'|'all')` landed on the main database on
  2026-09-22, and nine of Rincon Plumbing Co's ten identical portals were archived through
  them FROM A SCRIPT. This package knew none of them, so from every client those nine simply
  ceased to exist: `custom.portals` hides archived portals by design and there was no other
  reader, no restore, and no archive.
* New client methods: `listPortals({ archived })`, `portalArchive({ portal_id, confirm_title,
  reason })`, `portalRestore({ portal_id, confirm_title })`, over the three new door names.
* New vocabulary and words, so every screen says the same thing: `PORTAL_ARCHIVE_FILTERS` /
  `PortalArchiveFilter` / `isPortalArchiveFilter` (three values, no fourth — the door refuses
  a fourth `22023`), `PortalArchiveAnswer`, `portalArchiveConsequence`, `portalConfirmLine`,
  `portalTitleTypedBack` and `emptyPortalArchiveLine`. The typed-title confirmation is the
  DOOR's rule, not a screen's ceremony: ten portals can carry one title, so the title is not
  what picks this one and `23514` is what says so.
* `src/store.generated.ts` regenerated from the live store (570 doors), which also picks up
  IMPORT-2's `custom.io_import_declare_columns`.

**The import wizard adds the columns as its own step.** (lane IMPORT-2.)

* `importDeclareColumns` over `custom.io_import_declare_columns`, and the batch size the store
  itself sizes, so a wide file stops being one statement the store refuses.

**Consumer action:** none — everything here is additive.

## 0.51.0

**The seal is asked through its own door.** (lane FIX-10B, from VERIFIER-10 finding F6.)

* This package published `docSignatureIntact` and did NOT publish `docSignatureRead`, so the
  one screen that shows a signature asked the half that cannot answer. `custom.doc_signature_intact`
  is the INTERNAL half of that pair: SECURITY INVOKER over a doors-only table, so from a browser
  every seal check answered **`42501 permission denied for table doc_signature`** — measured live
  from the admin seat on 2026-09-22, on a signature this lane had just written successfully.
* `custom.doc_signature_read` is the door built for a client: SECURITY DEFINER, asking
  `assert_client_may_reach` and `assert_client_may_open` on the RECORD by name, and it returns the
  seal AND the verdict in one answer — it calls `doc_signature_intact` itself and merges the
  result, because a seal a person can read and a verdict they cannot is how a changed agreement
  keeps wearing somebody's signature.
* **`docSignatureIntact` is gone and `docSignatureRead` replaces it.** No deprecated twin: a door
  that cannot answer is not a fallback. One call per signature instead of two.
* **Consumer action:** replace `client.docSignatureIntact({ signature_id })` with
  `client.docSignatureRead({ signature_id })`. The answer is a superset — the same
  `intact` / `verdict` / `document_unchanged` / `value_unchanged` keys, plus the seal itself
  (`signer_name`, `signed_at`, `document_hash`, `document_version`, `record_id`, `field_key`).

## 0.50.0

**The store publishes every kind a column can be, and `signature` is one of them.** (lane FIX-10B-F6, from VERIFIER-10 finding F6.)

* Under a rendered document on a plumbing company's Crews table the screen said, in its own
  words: *"To ask somebody to sign one of these, Crews needs a signature column — a text
  column whose format is signature. Declare one in section 1 and the button appears here."*
  There is no such control in section 1, and there never was: the only list of column kinds
  any screen could read was `custom.parity_field_types()`, FLD-11's fourteen-row parity
  FLOOR. Every kind that is not on that floor — plain text, long text, number, a
  person-aimed relation and a signature — lived as an inline literal inside
  `custom._field_document_for`. So `custom.doc_sign`, granted to `authenticated` and
  accepting exactly one field shape, could not be reached from the product at all.
* **`DECLARABLE_FIELD_KINDS` and `DeclarableFieldKind` — the ONE registry**, generated from
  the store's new `custom.field_kinds()` by `pnpm records:generate`. It SELECTS from the
  parity floor rather than restating it, so a fifteenth parity type appears in both the day
  it appears in one. `ParityFieldType` is unchanged and still means what it always meant:
  which of the fourteen a field IS, which is a different question from what a person may
  pick.
* **`FieldTypeWord` now spans that registry**, so `{ type: "signature" }` is a declaration a
  typed caller can send. `custom._field_document_for` answers to the word (and to `sign`,
  `e_signature`, `esignature`), and its "there is no kind of column called X" refusal now
  names every kind instead of the parity floor alone.
* Consumer action: none required. `@ai-matrx/records-ui` 0.73.0 puts the kind on the
  Add-field menu.

## 0.49.0

**A number on a dashboard can be walked into.** (lane DRILL.)

* **`custom.read_records_matching` — the store's filtered read door**, and `client.list` now
  asks it for EVERY page, filtered or not, with `{}` when nobody asked a question. The grid,
  the board, the record list and a dashboard drill-through are therefore one door, decided by
  one ladder: a narrowed screen can never be a differently-judged screen. `list({ filter })`
  and `useRecords(tableId, { filter })` are how a caller asks.
* **`RecordFilter` — the ONE name for the store's filter shape.** It is not new and never was
  the aggregate's private shape: `platform.saved_view` has carried exactly this object of
  Field key → value as `definition -> 'filters'` since DOOR-18, `custom.view_declare` writes
  it, `custom.agg_view_admits` reads it, and `custom.record_aggregate` takes it as `p_filter`.
  A scalar is an equality, `null` is UNSET (the third state of a tick box), an object is a
  half-open time window. `SavedViewRow.filters` and `SavedViewSpecIn.filters` were
  `Record<string, unknown>` and are this now — an `unknown` there is a digest that quietly
  notifies about the wrong records.
* **One evaluator in the database, not two.** `custom.agg_sql`'s filter loop moved verbatim
  into `custom.record_filter_sql` and the aggregate CALLS it, so the number a chart draws and
  the rows a drill lands on are produced by the same bytes. Measured on Rincon Plumbing Co's
  41 live jobs: the chart says Cancelled 4, the door hands back RPC-1036…1039 and nothing
  else; the rendered choice label and the stored key both land on the same four; a window on
  `scheduled_date` answers 5 through both doors.
* **Why it is a new name and not a sixth argument** — the REALTIME-2 ruling, again: a defaulted
  `p_filter` beside the live five-argument `custom.read_records` makes every existing call
  ambiguous, and the only way to add it in place is to drop a live door.
* **`stableFilterKey`** — key-sorted JSON of a `RecordFilter`, so a React parent that rebuilds
  `{ status: "Cancelled" }` on every render re-reads the page when the QUESTION changes rather
  than on every render forever.

## 0.48.0

**A portal principal carries the link the office copies for them.** (lane TAILS-5.)

* **`PortalPrincipal.accept_path`** — `/invitations/portal/accept/<token>`, the same path
  `custom.portal_invite` hands back when somebody is asked, read off the same `iam.invitations`
  row and never re-templated. `custom.portal_card` carries it per person as of the migration
  `tails5b_the_portal_card_carries_the_link.sql`.
* **`null` is an answer, not an absence.** It is null when they have already followed the link,
  when the office took it back (a revoke withdraws the invitation in the same statement), when
  it expired, or when they arrived through the server's own magic-link route, which binds
  without an invitation at all. A screen that meets `null` says so in words — it never draws a
  dead control.
* **Why it exists**: PORTAL-BIND left this named — *"the office's own copy-link is one line
  short"*. The door answered the office once, with one sentence and one link, and the moment
  that sentence scrolled away the link was gone: the panel redraws from `portal_card`, which
  knew somebody was "invited and waiting" and had no way to say what they were waiting on. So
  texting a customer their link — which INVITE-DELIVERY measured as the ORDINARY case, not the
  fallback — meant inviting them a second time to see it again.

**Consumer action:** none. `accept_path` is a new field on an existing answer; nothing that
reads `PortalPrincipal` today has to change. A surface that offers a copy-link must handle the
`null` case with a sentence.

## 0.47.0

**A refusal this package does not recognise no longer becomes a sentence about this package.**
(lane TAILS-6.)

* **`mapPgError` wrote its own note ONTO `hint` — and `hint` is what every screen treats as
  THE REMEDY.** So when the client met a code it did not know, the person acting on the screen
  was handed this to act on:

  > `@ai-matrx/records did not recognise SQLSTATE  from checklistTemplates`

  Watched on the live seat by the GUIDE lane on 2026-09-21, on the Checklists tab of a table's
  rail, under the title "Something went wrong at our end". Reproduced here from the shapes
  supabase-js actually returns.
* **The note moves to `RecordsError.diagnostic`, with `unmapped: true` beside it.** It is still
  said out loud — nobody reads the silence as "handled" — but it is said to whoever debugs
  this and to nobody else. `hint` is the STORE's own words or it is absent.
* **New code `unreachable`, and it is how the bug got out.** A SQLSTATE is five characters;
  supabase-js puts `""` in `code` for a `FetchError` and the HTTP status (`"404"`, `"502"`) when
  the gateway answers with no body. Neither is a SQLSTATE, both used to land in `internal`, and
  `""` is not nullish so it was carried as one. They now classify as `unreachable` — the call
  never reached the store, nothing was written, and the same call can simply be made again,
  which is a thing a screen can honestly offer. `TypeError: Failed to fetch` is the browser
  talking to itself, so the message becomes "We could not reach your data." and the raw text
  goes to `diagnostic`.

**Consumer action:** none required. A host that reads `error.hint` gets the store's hint or
nothing, never this package's commentary; a host that switches on `error.code` should add
`unreachable` beside `internal` (`@ai-matrx/records-ui` 0.66.0 already has its title and remedy).

## 0.46.0

**A portal can say which Tables it shows.**

- **`portalTables()`** on the client and **`PortalTableRef`** — the one question a TABLE-scoped
  Portals rail has to ask. `custom.portals` takes no table, so the rail on a Table's page (beside
  Forms, Bookings and Dashboards, all table-scoped) decided emptiness from the ORGANIZATION's
  portal count: on Rincon Plumbing Co's Parts table it read "Portals 10" and listed ten cards
  belonging to Jobs, the empty state never rendered, and the whole "Build it / Ask an agent"
  offer was unreachable on every table of that organization.
  Same wall and same visibility gate as `portals`, so the two lists cannot disagree.
- Store registry regenerated against the live store: 559 doors, 1024 entity tokens.

## 0.45.0

**One coercion primitive: what a typed-in answer means to a Field, in one place.**

AGENT-BUILDS-2's closing walk, 2026-09-21, found Ironclad Mobile Mechanic's public booking
page **unbookable over any table with a number field**. A customer picked 2:00 PM, typed a
2019 Honda CR-V, pressed "Book it" and read:

> Vehicle Year takes a number, and it was given a string

and got no appointment. A browser input hands over a STRING; a Field takes a value. The
grid's paste and the CSV import each carried their own coercion — the two doors a STRANGER
uses carried none.

New, headless, and now the only body that answers the question:

- **`fieldKindFor(field, kernel)`** — the ONE classifier (behaviour, modifiers, format, unit,
  relation target, config). `@ai-matrx/records-ui`'s `editorKindFor` is this function with
  the kernel File/Person ids bound, so the editor a person types into and the coercion
  applied to what they typed cannot disagree about a column.
- **`coerceTypedAnswer(field, raw, opts)`** — one string → the Field's value, or a refusal
  naming the field **in plain words**, never its key. It coerces and never guesses: `0` for
  "about twelve" is how a table quietly becomes untrue.
- **`coerceTypedAnswers(fields, answers, opts)`** — a whole screen at once. Values already
  of the right shape are left alone; keys the table has no Field for are REPORTED rather
  than dropped, because naming an unknown key is the store's sentence to say.
- **`CoercibleField`** — the structural shape the primitive needs, so `CaptureField` and the
  Field documents `custom.form_public` / `custom.booking_public` publish to a stranger all
  satisfy it. Demanding a whole `Field` is how the capture sheet kept its own `Number(raw)`.
- **`FIELD_KINDS`** — the runtime list `FieldKind` is derived from, so a coverage test walks
  the source of truth rather than a retyped copy.
- **`ChoiceOptions`** moved here from records-ui, which re-exports it.

*Consumer action:* none required — everything is additive. A host that coerces typed-in text
itself should delete that code and call `coerceTypedAnswer`.

## 0.43.0

**The realtime port stops being half a contract, and a nudge re-reads only what moved.**

🚨 **BREAKING (one implementor):** `RecordsRealtimePort.subscribeRecords(args, onChange)` now
takes a `RecordsChangeSink` instead of `(recordId) => void`:

```ts
subscribeRecords(args, {
  records(ids: readonly Uuid[] | null) { … },  // null = "I cannot name them" = read the page
  shape() { … },                               // a column moved, not a row
})
```

*Consumer action:* a host binding this port passes the two-call sink and **deletes its
`tableShapeChanged` import**. That import was the tell: a host that wanted to say "the SHAPE
moved" reached around the port into `@ai-matrx/records/react`, because the contract had no way
to say it. A column appearing is not a row changing and the package owns the difference now.

* **`listByIds({ table_id, ids })`** — DOOR-1 addressed by an id SET, through the new
  `custom.read_records_by_ids`. The same ladder (`custom.visible_set`), the same level, the
  same field masking and the same page ceiling as `list`; an id the reader may not see simply
  does not come back. A separate door NAME rather than a sixth argument to `read_records`,
  because a defaulted argument beside a live Postgres function makes every existing call
  ambiguous — `function custom.read_records(uuid, uuid, boolean, integer, integer) is not
  unique`.
* **`useRecords` re-reads only the ids it was nudged with.** A whole-page read is now reserved
  for the three cases that need one: `records(null)`, a subset read the door refused, and a
  named id that is NEW to this page (only the page door knows where it belongs in the order,
  so the hook does not guess).
* **Echo suppression by op id.** Every `recordWrite` / `recordWriteMany` / `recordUpdate` mints
  a uuid and sends it as the platform envelope key `_op_id`. The store lifts it out before its
  undeclared-key guard and before storage — **it is never written onto the record** — and
  carries it back in the realtime notice as `op_id`. `isOwnOp(opId)` (new, from `ops.ts`) tells
  a port that a notice is this browser's own echo, so a host can delete
  `acceptEchoFromSelf: true` and stop re-reading a page it has already updated. One call is one
  operation: a `recordWriteMany` batch carries ONE id, and the store refuses a batch carrying
  two by name.
* `useAsync` gained `setData`, so a caller that has just read fresher rows **through the same
  door** can place them instead of throwing them away and reading the page again.

## 0.41.0

**`recordWriteMany` — the door for a paste that is bigger than a page.**

`custom.record_write_many` has existed since the store did; no method on
`RecordsClient` called it, so every bulk create went one `record_write` at a
time. A 120-line customer list pasted into a grid whose page ceiling is 50 was
120 round trips, each re-asking the switch, the organization wall and the
Table's ladder for an answer that cannot have changed between rows.

* `recordWriteMany({ table_id, rows, ids? })` asks all three ONCE per call and
  inserts the batch in one statement, returning the new ids IN THE ORDER THE
  ROWS WERE HANDED IN — so a caller can tie each id back to the line of the
  spreadsheet it came from.
* It CREATES only, and says so: an update carries a per-row optimistic version
  and a batch would have to either drop that or fail a hundred rows for one
  stale one.
* `store.generated.ts` regenerated against the live store (551 doors).

**Consumer action:** none. A caller writing rows one at a time still works;
`@ai-matrx/records-ui`'s grid paste now chunks through this instead.

## 0.40.0

**The parity floor this package types is asserted against the one the store ships.**

`checkbox` landed as the fourteenth parity type and `store.generated.ts` picked it
up — but nothing made that agreement a test. Everything that spelled the floor out
by hand went stale in the same hour: three of the store's own refusal hints, the
`FieldTypeWord` doc comment, `UseCaseField.parityType`'s doc comment and the
use-case README's list, all still saying thirteen. A person who asked for a
checkbox could be told by the store that checkboxes do not exist.

* `src/__tests__/real-doors.test.ts` gains three cases, in the one suite that runs
  against the live store: `PARITY_FIELD_TYPES` is exactly
  `custom.parity_field_types()` name for name (a diff NAMES the missing type, it
  never prints a count); every parity type and every behaviour the store ships is
  a legal `parityType` word in `RESOLVABLE_FIELD_TYPE_WORDS`; and `checkbox` is
  present with behaviour `boolean`, with a compile-time assertion that the
  `ParityFieldType` union still admits it. Shown RED: with the `checkbox` row
  removed from `store.generated.ts` all three fail and the first one says
  "expected 'attachment, currency, …' to be 'attachment, checkbox, …'".
* The four places that wrote the count or the list out by hand now point at the
  derived set instead. No number is written down anywhere in this package.
* `store.generated.ts` regenerated against the live store (542 doors, 1023 entity
  tokens); `pnpm check:store-registry` was RED on that drift before this release.

**Consumer action:** none.

**Why 0.40.0 and not 0.38.0.** This work landed on `main` a few minutes after
0.39.0 was tagged, so the published 0.39.0 does not contain it. The version number
moves rather than the tag.

## 0.39.0

**Two things a records screen could not know: that a table's shape had changed,
and how many records it was actually looking at.**

**A table's shape now travels.** A table's columns are read by several surfaces
at once — the grid draws them, the field rail lists them, a form builder offers
them, a filter picks among them — and each called `useFields(tableId)` and got
its OWN copy. Nothing joined them, so a column added in one left every other
one drawing a table that no longer existed. Measured on production 2026-09-21
at Ridgeline Physical Therapy: "Add field" succeeded, the panel confirmed it
with a read-back and closed, and the grid beside it did not show the column
until a full page reload. The panel was not lying — the column WAS made — but
the screen next to it was showing a table that was already out of date, which
is the same thing to the person looking at it.

`tableShapeChanged(tableId)` is one table-scoped revision. `useFields`
subscribes to it and registers its interest on read; `useFieldMutation`'s
`declare`, `update` AND `retire` all announce — all three, because retyping a
column and retiring one leave exactly the same staleness, and a fix that only
knew about `declare` would have left two thirds of the class open. A listener
re-reads THROUGH THE DOOR: the tick is a nudge and never data. It is
deliberately independent of realtime, so a host that binds no realtime port
behaves identically here — realtime only makes a shape change travel between
browsers.

**A visible total instead of a shrug.** `client.list` answered `total: null`
unconditionally, so a table holding ten records told its owner *"Unknown total
— showing the 10 records loaded so far"* — a screen refusing to say a thing it
knows. A SHORT PAGE already answers the question: if the door was asked for
`limit` and handed back fewer, the reader has reached the end of the set it is
allowed to see, so that set's size is exactly `offset + rows.length`. No count
is ever asked for, no second query is made, and a full page still answers null,
because "at least this many" is not a total.

🚨 That rests on one property of `custom.read_records`, and it is not the
obvious one. The door DOES clamp, through `custom.page_size` — the first draft
of the comment beside the inference said it did not, and writing the guard is
what caught that. What makes it safe is what the helper does at the ceiling: it
REFUSES BY NAME and reads nothing, rather than quietly serving fewer. So a
successful call that returns fewer rows than it asked for has reached the end
of the set, and nothing else. `src/__tests__/page-truth.test.ts` asserts the
refusal against the live store, so the day it softens into a silent shrink the
suite goes red and names the inference that has to change with it.

## 0.37.0

**Three doors the store already had and this client could not reach.**

Until this release the only way to a booking page or a saved view was to ask an
agent, because no method on `RecordsClient` called the doors that make them —
so the agent was not the FASTER path to those things, it was the ONLY one.

* `bookingDeclare` — `custom.booking_declare`, the owner's side of a booking
  page. `admin` on the Table, because a booking page decides what a stranger may
  write into it and which hours of the organization's week are on offer. It does
  NOT publish: `anonPublish` is still the one act that opens a public surface,
  for a booking page exactly as for a form. `BookingAvailability`,
  `BookingWindow` and `BookingDeclared` are the answer's shape — a screen shows
  the availability that came BACK, never the draft it sent, because the store
  owns every default.
* `views` — `custom.views`, the saved views of a Table. Narrowed in SQL as the
  definer to Tables the caller can already open, so a list can never reveal a
  Table.
* `viewDeclare` — `custom.view_declare`. VIEWER on the Table, not editor:
  writing down a question about a Table is not changing it. A subscription is
  written OVER a saved view, so without this door a person who had never saved
  one had nothing to subscribe to and "email me this every Monday" had nowhere
  to land.

## 0.36.0

Republish of 0.35.0's contents. 0.35.0 never reached npm: its gate was red on
this package's OWN live-doors fixture, which wrote an undeclared column `n` into
its probe table — refused, correctly, by `custom._undeclared_key_guard` (23514,
REC-1 / REC-51) the moment lane FIELD-TRUTH landed it. The door had not
regressed; the fixture was writing a column nobody had declared. Fixed at the
fixture, never at the guard.

## 0.35.0

**A gate a screen can read back, and a preview of what it would do today.**

A stage gate was saved as one Rule whose expression is an `or` of four arms, and
that compiled form was the only copy of it. `pipelineRead` handed a settings
screen the gate's name and its sentence and nothing else — so an editor built on
it would open the Birchwood Avenue renovation's "$5,000 needs a second quote"
rule with a BLANK condition and overwrite it the first time somebody pressed
Save.

`PipelineRule` now carries `when` and `demands` as they were written, and
`on_fail` — so an approval gate can never read back as a refusal.

`pipelineGatePreview({ table_id, stage, gate })` answers the one question
somebody writing a rule that refuses their colleagues' work actually has: of the
cards on this board today, how many would this turn away, and which three. It
takes the GATE rather than a rule id, because the whole point is judging one
that has not been saved. The store compiles it with the same compiler
`pipelineDeclare` uses, so this count and the refusal at the door cannot
disagree at the edge of the condition — which is the only place anybody looks.

**`PT428` is a real answer and this package called it `internal`.** The release
gate's live refusal census found it. A gate whose `on_fail` is
`require_approval` files the request and leaves the record where it was; a
caller meets that code whenever the move went through the ordinary write door.
It gets its own `needs_approval` code rather than joining `refused_by_rule`,
because the work was neither rejected nor done — a screen that drew it as a
refusal would tell somebody their work was turned down while it is being
considered.

**The door mirror had gone stale, twice.** `store.generated.ts` claimed
`pipeline_pending` was absent while the store had it, which is what turned the
0.32.0 publish red; 0.34.0 then went red on `pipeline_gate_preview` the same
way, because the regenerated file was lost to a merge in the shared checkout
before it was committed. Regenerated from the live store and committed with the
change it describes: 538 doors, 25 rule node kinds — which picks up
`sibling_count`, the node a second-quote demand is written with.

That census is the guard working, and it has now been the thing that found a
missing door or a missing refusal class six times. It is worth saying why it
keeps firing: this package mirrors a store several lanes are landing on at once,
so a mirror regenerated ten minutes before a commit is already a claim about the
past.

## 0.33.0

**One closed door beside a record took the record away.**

Cedar Ridge Dental's own owner opened a staff record in his own organization and
read "You do not have access to this" — above a version history, a running
onboarding checklist and a comment box that had all loaded perfectly. Reported
twice: by lane SCREENS-2 on that practice, and by crew E2, who asked the doors
from that very seat and found the record read answering correctly. It was never
the store.

`recordRead({ withComputed: true })` makes a second call, to
`custom.computed_provenance` — which Rule produced which worked-out value (FLD-9),
a footnote beside the record. Measured on the live catalogue, 2026-09-21:
`read_record` t, `record_history` t, `field_history` t, `computed_provenance` **f**.
That footnote's refusal was returned AS THE RECORD'S. `Peek` is the one caller
that asks with provenance, which is exactly why the record page refused and every
other panel on it did not.

A decorative read may never fail the primary one — and may not fail silently
either. The record comes back, `computed` is empty, and the new
`RecordRead.computedUnavailable` carries what the footnote's door said, so a
screen can say it could not look rather than draw nothing and mean two different
things by it. `src/__tests__/a-footnote-never-takes-the-record.test.ts` — three
clauses, two red on 0.32.0's bytes.

**Consumer action:** none. `computedUnavailable` is optional, and `null` when the
footnote read fine or was never asked for.

## 0.30.0

**The chair's two rulings on the no-fake-data law, made structural.**

Both are things crews ACTUALLY shipped under the law on the day it was written, so
they are now refusals rather than advice in a README.

`suffixed-mailbox` — a mailbox whose name-shaped local part ends in a bare counter
(`linda.reyes0@`, `wei.singh.m34@`) is a generated row wearing a person's name, and
a whole column of them reads to the owner as one fake person repeated. People in a
template are fictional but REALISTIC, holding mailboxes on the fictional business's
own domain. The pattern is narrow on purpose — two or more words joined by `.` `_`
`-` AND a trailing counter — so the real mailboxes a business has (`support365@`,
`ap2@`, `frontdesk@`) are left alone, and the suite asserts both halves.

`fixture-in-name`, plus a name-field check in the validator — "test fixture" is a
CLASSIFICATION the organization carries in its kind/metadata (`test_fixture: true`
in its settings, which is also how cleanup finds it), never part of a name. The
validator now reads the business name, its legal name, every table's name and both
its labels, the form's name and the pipeline's name, and refuses the parenthesised
suffix, the bare words, and the `Fixture …` PREFIX — which the whole-document junk
sweep cannot see, because there the word is simply the first word of a name. The
settings key itself is exempt, since that is where the classification belongs.

Both patterns ship in `./use-cases`, so aidream's guard, matrx-frontend's guard and
this library's own validator can never disagree about what junk means.

## 0.28.0

**The owner's side of a booking page — the one method that was standing between
`BookingSlots` and being the panel it should be.**

`bookings({ table_id? })` over `custom.bookings`, which has been live and granted to
`authenticated` since lane BOOKING landed and which no client could name. It is
modelled line for line on `forms()`, because a booking page IS an `anon_form`: the
same object, asked for its booking half, opened and closed by the same
`anonPublish`. `BookingSummary` (`src/rule.ts`) carries what makes the list worth
opening — booked, cancelled, still upcoming, how many slots somebody is holding
right now, and when the next appointment is — every number the store's own, because
a screen that counted appointments in the browser would drift from the unique index
that actually protects a slot.

`bookingPath(formId)` sits beside `publicFormPath` for the same reason: the page id
IS the capability, and the visitor's `booking_ref` is the separate secret that lets
ONE person manage the appointment they made.

The VISITOR's three doors (`booking_public`, `booking_hold`, `booking_confirm`) are
deliberately still absent from this client. They are server-lane, a signed-out
visitor reaches them through the host's own route, and a browser never calls them.

## 0.27.0

**"Give me a board of deals by stage" — the rules that judge every move become
something a screen can ask about before the finger lets go.**

A pipeline is not a new kind of thing and this release adds none. It is a Choice
Field a Table names as its stage field, plus transition Rules of the ONE Rule
object — so a move is an ordinary record write the store judges, History records
because it IS a write, and every surface already knows how to read.

`src/pipeline.ts`: `Pipeline`, `PipelineStage`, `PipelineRule`, `PipelineColumn`,
`PipelineVerdict`, `PipelineMissingField`, `PipelineDeclared`, `PipelineSpec`.

Six client methods, each one door: `pipelineRead`, `pipelineBoard`,
`pipelineTransitionRefusal`, `pipelineMove`, `pipelineDeclare`, `tableStageField`.

THIS PACKAGE ENFORCES NOTHING, and that is the point. `pipelineTransitionRefusal`
asks the store the same question the write will ask — the same Rules, through the
same evaluator, on the document the move WOULD produce — and hands back the store's
own sentence plus, when a stage demands columns that are empty, exactly which ones.
A board that decided in the browser would be a second opinion with nothing keeping
it equal to the first, and it would be the comfortable one, because it is the one
nobody can be refused by.

`mayDrag(level)` is the one thing this package DOES decide, and it is about the
person, not the rules: a viewer's card carries no drag handle at all rather than
one that looks live and snaps back every time.

## 0.26.0

**"Text me on a new lead; email me a Monday summary" — the second half of that
sentence becomes something the store can be told.**

A cadence was two words, `immediate` and `digest`, and `schedule` was free text
nothing read. `SubscriptionCadence` is now `instant | hourly | daily | weekly` —
the words `custom.agg_subscription_cadences()` answers and the digest runner acts
on. The two old words still work everywhere: the store normalises them, so a Rule
written last week and one written by the picker today mean the same thing.

`MySubscription` gained the three answers a notifications list actually needs and
could not ask for: `quiet_hours`, `last_sent_at` and `next_digest_at`. The last is
computed by the SAME store functions the runner uses, so "next Monday 08:00" on the
screen and the moment it fires cannot drift; it is null for a muted subscription,
because saying "next Monday" beside a switch that is off is a screen that lies.

Two doors that did not exist:

* `subscriptionDeclare({ table_id, spec })` — WRITE one. The store refuses a cadence
  no runner acts on, a channel this platform does not send on and quiet hours nobody
  can parse, each by name, then declares the Rule through `rule_declare` (REC-72: a
  Rule written as a record is invisible to every reader that goes looking for one).
* `subscriptionPreview({ rule_id })` — the summary it would send RIGHT NOW, with what
  entered, left and changed NAMED rather than counted, plus the in-view total and a
  click-through to the filtered grid. It assembles only: nothing is delivered,
  nothing is recorded, and the watermark does not move.

`QuietHours`, `DigestPreview` and `SubscriptionSpec` are new exported types.
`store.generated.ts` regenerated from live — 520 doors, `check:store-registry` OK.
## 0.26.0 — 2026-09-20

**The queue was filing every refusal as a hiccup, and a real browser found it.**

`isDecided` listed SQLSTATEs. `RecordsClient` hands back its own WORD —
`invalid_argument`, `door`, `refused_by_rule` — with the sqlstate beside it, so
no refusal ever matched: a capture the store had refused BY NAME went back in the
queue and would have been sent again forever, which is the exact silent hole the
queue's fourth decision exists to prevent. The unit suite passed throughout
because it fed the check SQLSTATEs — it was testing the assumption, not the
client. Measured in a headless Chromium at 390 px against the main database.

It is now the other way round, and from the package's own list rather than a
guess: `stale_read`, `timed_out` and `store_limit` are worth repeating (which is
what `results.ts` says in as many words), a failure with NO code at all is the
network and waits, and everything the door named is a decision that stays.
`isDecided` takes the error, not a code, so either spelling works.

## 0.25.0 — 2026-09-20

**A crew out in the field, and a phone with no bars (PRODUCTS row 15, P13).**

* `captureSheetDeclare`, `capturePublish`, `captureSheets`, `captureOpen` and
  `captureSubmit` — the five SIGNED-IN doors of a crew capture sheet. A capture
  sheet is not a second form store: it is the ONE Form object saying
  `audience = 'crew'`, opened by `capture_opened_at` rather than `published_at`,
  and the database carries a CHECK that refuses a row which is both. `captureOpen`
  answers `null` for a sheet below `viewer` AND for a sheet that is not there —
  the same answer the Table gives — and carries an honest `may_capture` with a
  sentence, so a phone never discovers a refusal after the walk to the bin.
* `openCaptureQueue` — the offline queue itself, headless, in IndexedDB, with the
  network calls injected. The capture is on disk before the network is asked; the
  idempotency key is minted when the capture is TAKEN, not when it is sent; items
  go up oldest first, one at a time; a file's bytes are uploaded once and the
  reference written back, so a failed submit never re-sends the photograph; and a
  REFUSAL is not a retry — it stays in the queue carrying the door's own sentence
  until a person fixes it or throws it away. `sync()` answers with per-item
  outcomes, never a boolean.

## 0.24.0 — 2026-09-20

**0.23.0 never reached npm.** Its release run died before any package could be
built: five `node_modules` SYMLINKS, each pointing at an absolute path inside
one machine's checkout, had been committed to the repository, and on a CI runner
a dangling symlink makes `pnpm install --frozen-lockfile` answer ENOENT. Every
package release was blocked, not just this one. The symlinks are untracked and
`.gitignore` now carries both spellings — `node_modules/` with the slash matches
a directory only, which is how a symlink of that name walked past it. This
version is 0.23.0's contents from the repaired tree; a tag is never moved, so
0.23.0 is simply absent from npm.

## 0.23.0

**A column a model owns — seven doors, and the four states one cell can be in.**

`enrichments`, `enrichDeclare`, `enrichDue`, `enrichCells`, `enrichLand`,
`enrichPin` and `enrichRuns` (PRODUCTS row 10 / AGT-6). An Enrichment is not a new
object: it IS the Field, saying `source = agent`, with its instruction, inputs,
model, confidence floor and freshness in `source_config` and
`review_interval_days` — so nothing here is keyed by an enrichment id.

`EnrichCell` is the type a screen draws a badge from, and it is deliberately able
to say four different things, because they are four different facts: a value an
agent wrote (with the model, what it read and how sure it was), a value a person
took back (`pinned`), an answer the model would not commit to (`absent_reason:
"conflicting"`, the guess in `alternates[0]`), and a value past its freshness date
(`stale` — still holding its value; stale marks it, it never erases it).

`enrichCells` answers a WHOLE PAGE in one call. Asked per cell it is one round trip
per badge.

The door catalogue is regenerated from the live store: 478 doors.

## 0.22.0 — 2026-09-20

**BREAKING: a parse is a parse. `importCsv` / `importXlsx` no longer take the Fields.**

Found by looking at the import screen, not by reading it. The plan showed *"every
row in this column is empty, so there is nothing to go on"* for columns plainly
full of values, and every unmapped column's sample list was blank.

The cause was in this package. The parse took the Fields, matched each heading by
key or label, keyed the rows by whatever it matched and **dropped every column it
did not**. That is a mapping decision, made by the one component that must not
make it: the rows handed to `custom.io_import_rows` carried nothing for the
unmatched columns, so the store never saw them and could not offer a single one as
a new column. The whole "never a junk bag" half of SCR-N-7 was unreachable through
that screen — and it shipped in **0.21.0**, so anyone on that version has it.

Fixed in the SHAPE rather than beside it. These functions no longer take `fields`
at all, so a parse cannot decide a mapping even by accident:

```ts
// before
importCsv(fields, text)   // -> { columns: [{header, field}], rows, unmapped }
// after
importCsv(text)           // -> { headers, rows }   rows keyed by heading
```

`ParsedImport` is now `{ headers: string[]; rows: Array<Record<string, string>> }`
— every heading, every row keyed by heading, nothing dropped — with no `field`, no
`unmapped`, and **no safe path left standing beside the unsafe one**. Only the door
decides. `parseCsv`, `exportCsv`, `exportXlsx`, `toAoa` and `cellText` are
unchanged.

**Migrating:** drop the first argument, and send `rows` straight to
`custom.io_import_rows` — the store matches the headings and offers what it could
not match. A caller that genuinely wants the old pre-matched shape can build it
from `headers` in four lines, which is what this package's own red twin does.

## 0.21.0 — 2026-09-20

**The store moved and this package's census caught it — twice.** The release
gate runs the live-door suite against the real record store, and it went red on
main:

- `src/store.generated.ts` regenerated from the live store. The value envelope
  gained `pinned`, the door census gained `conversation_scope` and its bind /
  unbind / context siblings (478 doors, up from 428), and the knob list grew to
  43. Nothing in that file is typed by a person.
- **SQLSTATE `22003` is mapped.** `custom.context_resolve` raises it — a number
  the store could not hold at the size it was handed — and this package did not
  know the class, so it would have read as `internal` in somebody's console. It
  is `invalid_argument`, the same class `22P02`, `42703` and `22007` already
  are: the call named a value the shape does not accept, and the store's own
  sentence names the number and the range.

> **About 0.20.0 on npm.** This lane tagged `0.20.0` from a commit that carried
> the census fixes above but NOT the checklist doors below — the tag was pushed
> while a merge of main was still open, and a tag is never moved. `0.21.0` is
> the first version that carries both. Nobody should sit on 0.20.0.

## 0.20.0

**"Every new hire gets these twelve steps." — the checklist doors, PRODUCTS row 13.**

A checklist template is a record of the store: ordered steps, each with the role
that owns it, how many days in it is due, what it waits for and what it asks for
before it counts as done, plus the thing that starts a run. Starting one makes
ONE work item per step in the one work layer — assigned by role to real people,
dated from the offsets, dependencies carried across — so a step appears in the
person's ordinary inbox beside everything else and there is no
checklist-shaped queue anywhere.

Nine doors, all of them live on the main database:
`checklistRefusal` (pure — a screen may ask it on every keystroke, and the
refusal arrives before the save), `checklistDeclare`, `checklistTemplates`,
`checklistTemplateShape`, `checklistStart`, `checklistRun`, `checklistRuns`,
`checklistStepComplete`, `checklistStepRefusal`.

`ChecklistRunStep` carries `blocked_by`, `refusal`, `may_complete` and
`requires_key`, so a screen can say why a step cannot be finished yet — in the
words a person would use — before anybody clicks, file the evidence under the
key the checklist itself named, and show no control at all where a control would
fail.

THE DEPENDENCY RULE LIVES IN THE STORE, not in these methods: a BEFORE trigger
on `custom.record` refuses a step reaching Done while a step it waits for is
open, so `workSetState` meets the same sentence `checklistStepComplete` does.

## 0.19.0

**"Pull my spreadsheet in" — the import doors, and the address a Table can own.**

`core/io.ts` has parsed CSV and XLSX bytes since W4-IO, and `ImportWizard` then
wrote the rows one at a time through `recordWrite` — one HTTP round trip per
row, no duplicate check, no provenance, and a refusal that carried a row NUMBER
into a file the screen no longer had. Every one of those is a way to lose data
quietly at 5,000 rows.

Eleven client methods arrive. The BYTES are still parsed here, because Postgres
cannot unzip a workbook; **everything after the parse is the store's**, so a file
imported from a screen, from an agent and from a script land identically and
there is no second importer in this package to drift.

**BREAKING, and it is the point: `importCsv` / `importXlsx` no longer take the
Fields.** They took them, matched each heading to a Field by key or label, keyed
the rows by whatever they matched and **dropped every column they did not** —
and that cannot be the input to a door whose whole job is to decide the mapping
and offer what it could not match. Measured by looking at the screen: every
unmapped column's samples came back empty and its proposal read *"every row in
this column is empty, so there is nothing to go on"* for columns plainly full of
values. The store can only offer a column whose values reached it.

`ParsedImport` is now `{ headers: string[]; rows: Array<Record<string, string>> }`
— every heading, every row keyed by heading, nothing dropped — and there is no
`field`, no `unmapped` and no way to ask for one. The fix is the SHAPE rather
than a second field beside the first: a parse that is not given the Fields
cannot decide a mapping even by accident, and no safe path is left standing
beside an unsafe one. `parseCsv`, `exportCsv`, `exportXlsx`, `toAoa` and
`cellText` are unchanged.

- **`importPlan({ table_id, columns })`** — what the store makes of a file's
  columns before anything is written, reading the parity map AND this
  organization: an address that belongs to a member is a `member` column, a few
  repeating words are a `select` with its options, `$1,250.00` is `currency` with
  its unit, a column of names that are all records of another Table is a
  `relation` at it. Every answer carries one sentence saying why, and two columns
  that would make the same key say so.
- **`importBegin(...)`** — opens a run with the file's sha-256 as its IDENTITY.
  The same bytes into the same table a second time answer the FIRST run with
  `already: true` and write nothing, so **a re-import doubles nothing**. Carries
  the duplicate key and the policy the whole run obeys.
- **`importRows({ import_id, rows, mapping })`** — a BATCH. Every row goes
  through the one write door inside its own subtransaction and comes back with
  one of exactly three outcomes: `landed` with the record id, `duplicate` with
  the id of the record already there, or `refused` with the store's own sentence
  **and the source row**, so an outcome table is clickable. Every value that
  lands carries `{"kind":"import", …}` as its source (VAL-1).
- **`importFinish({ import_id, unmapped })`** — closes the run and sends every
  column the file had and the table did not to the **ONE approvals inbox**
  (`propose`, the default and the only choice a non-admin has), or adds them
  outright (`create`, an admin's), or leaves them out. Never a junk bag.
- **`importReport`**, **`imports`**, **`importForget`** — the refused and
  already-here rows with their source, this organization's runs, and forgetting
  a receipt so the same bytes can be imported again deliberately.
- **`inboundDeclare` / `inboundAddresses` / `inboundSet`** (DOOR-19) — the
  address a Table owns. `inboundDeclare` answers with **`mail_route`**: the one
  sentence saying what has to exist in DNS before anything arrives. Show it
  wherever the address is shown. The gateway's own landing door is deliberately
  absent from this client — it is `service_role`'s alone.

New types in `@ai-matrx/records`: `ImportPlan`, `ImportColumnPlan`,
`ImportPolicy`, `ImportOpened`, `ImportOutcome`, `ImportProposal`,
`ImportBatchResult`, `ImportFinished`, `ImportReport`, `ImportRun`,
`InboundAddress`, `InboundDeclared`.

`store.generated.ts` regenerated against the live store: **428 doors**.
## 0.18.0

**"Who changed this price, and can I put it back?" — the doors for PRODUCTS row 4.**

The store has kept every version of every record since W3-HIST: one append-only
chain covering Values AND structure, with the compound verb that wrote each
version stamped on it since MERGE-HISTORY. None of it reached a screen. The only
thing a client could ask for was `revisions()` (`custom.io_revisions`), which
answers a version number, a moment, a RAW UUID for the author and a list of
KEYS — so a timeline could say "something changed" and could not say who, what
it was before, or which Rule produced a derived value.

Seven client methods arrive:

- **`recordHistory({ record_id })`** — one record's versions, newest first, each
  naming the author as `user` / `agent` / `system` **and, for an agent, the
  person it acted for**, the operation in the word somebody used, and every
  field that moved with its `before`, its `after`, the Rule VERSION that
  produced a derived value and its interned source pointer. Viewer.
- **`fieldHistory({ table_id, field_key })`** — one column's history across a
  whole Table ("who changed the price on ANY record"), narrowed to the records
  this person may see by a predicate in the same statement. A column that names
  no Field is refused BY NAME.
- **`restorePreview({ record_id, version, field_key? })`** — what a restore
  would change, BEFORE it does, computed by the body that computes the write.
  Editor: the first half of an act is not shown to somebody who may not do it.
- **`restoreVersion`** and **`valueRestore`** — put the whole record, or ONE
  column (HIS-N-2), back to what it said at a version. Both write a NEW version
  through the record's own write path; no stored version is ever altered.
- **`commentThread`**, **`commentWrite`**, **`commentResolve`** — SCR-18, at the
  store's own `commenter` rung, with everybody NAMED, a comment anchored to one
  Field, and a mention that notifies the person named.

**Consumer action:** none required. `revisions()` is unchanged and every
existing caller keeps working. A screen showing a record's past should move to
`recordHistory` — `revisions()` cannot answer who, and was never able to.

**And two defects in the store this came with**, both fixed in the same session
and both reaching every existing caller: `custom.io_restore` did not restore (it
patched the old document through a merging write, so a key added after the
target version survived its own restore in silence), and it returned NULL to
every caller it has ever had (it read the version out of the read door, which
answers a document, and `version` is a column).

## 0.17.0

**A list is a door, never a grant on the thing behind it.**

`docTemplates`, `docRenders` and `docSignatures` read `custom.doc_template` (a
view), `custom.doc_render` and `custom.doc_signature` (tables) with a direct
PostgREST `.from()`. **None of those three holds a client SELECT**, and none ever
should — so every plural act of document generation returned 403, and the
try-everything page photographed *"permission denied for view doc_template"* on
2026-09-20. A person could save a template they could never list and render a
document they could never find again.

All three now call doors that narrow inside the definer, after the ladder has
spoken: `custom.doc_templates` and `custom.doc_renders` / `custom.doc_signatures`
(viewer on the Table, viewer on the record). Two acts that had no door at all
arrive with them:

- **`docTemplateDelete({ template_id })`** — retire one template. Editor on the
  Table it renders, because a template is that Table's wording. Soft only:
  documents already rendered keep their frozen bytes and their seals, so
  retiring a template can never invalidate a signature (VAL-10).
- **`subscriptionCadences()`** — the cadences a subscription may be written
  with, through the door a PERSON may call. `aggSubscriptionCadences` is the
  notifier's own and holds no client grant, so every screen that asked for it
  was refused and fell back to a word typed into the screen; this one delegates
  to the same array, so a picker can never offer a cadence the digest runner
  does not honour.

`DocTemplateRow` drops `organization_id`, which the door does not answer and no
caller read — the client already holds the organization. The door names its key
`template_id` / `render_id` / `signature_id`; the package keeps `id`, mapped once
here rather than renamed in four screens.

`store.generated.ts` regenerated from the live store: **387 → 401 doors**.

**Consumer action:** none for `docTemplates` / `docRenders` / `docSignatures` —
same names, same row shapes, they simply stop being refused. The five doors they
need are live on the main database (`doorstwo_the_document_and_cadence_doors.sql`
+ its grant file, applied 2026-09-20 13:43Z), so this version requires no
migration of its own.

## 0.16.0

**A column is declared, and a client type can finally say so.**

`NewFieldDeclaration` now names what `custom.field_declare` has actually taken
since 2026-09-20: `type` (the one word every agent, script and import reaches
for — the store resolves the thirteen parity types, the five behaviours and the
plain synonyms, and refuses a word that means nothing by name), `config` (a long
text's `multiline`, a list's existing options table — the door always read it and
no client could send it), `relation_target` and `on_target_delete` (a column that
points at another of this organization's own tables, which no client type could
express at all), and `format` (the one shape `custom.doc_sign` accepts).

`FieldPatch` gains the door's BEHAVIOUR arm — `parity_type`, `plain`, `type`,
`multi` — plus `relation_target`, `on_target_delete`, `promoted`, `unique` and
`depends_on`. It named only the settings, so the one caller that needed to move a
column back onto the behaviour it is written against had no door and reached for
`custom.record_update` instead, writing the document *under* the guards rather
than through them. **`multi` is read only inside that arm: send the behaviour
word with it or nothing happens.**

**The columns come back in the order they were declared.** `custom.applicable_fields`
answers a SET — its order is the plan's and it moves between calls — so every
consumer of `client.fields()` was drawing columns in an arbitrary order. A table
declared Title, Day rate, Due came back Day rate, Title, Due. The Field's own
`sort` is applied once, here, so every screen, export, form and agent inherits it.

**22007 is mapped.** The live-door census caught `custom.dashboard_window_sql`
raising it; it reads as `invalid_argument`, like 42703 before it.

**The live-door suite declared its own fixture the way the store no longer
allows** — `custom.record_write` into the field kernel — so eighteen of its
thirty-three clauses were red on `main` against the live store. It uses
`custom.field_declare`. 33/33.
## 0.15.0

**A client portal has an owner's side, and this package can reach it.**
`custom.portals`, `custom.portal_card`, `custom.portal_declare`,
`custom.portal_invite`, `custom.portal_revoke` and `custom.portal_preview` have
been live on the database and no client could call them, so the six doors that
decide who signs in from outside an organization — and what they see — existed
with nothing able to ask them. `store.generated.ts` was regenerated from the
live store (372 → 387 doors) and the six are now on `client`:

- `client.portals()` — every portal over this organization, with its client
  Table, how many Tables it exposes, how many people are invited and how many
  have actually signed in. The store filters the list on whether you may open
  the client Table, so it never reveals a Table.
- `client.portalCard({ portal_id })` — one portal in full: each exposed Table
  with the relation Field that says whose a record is and the fields a client
  sees and may change, every person with `signed_in` and `is_active` kept
  APART, and `external_lane_open`.
- `client.portalDeclare(...)` — create or RE-STATE one. Re-stating replaces
  what the portal exposes and leaves the people alone.
- `client.portalInvite({ portal_id, client_record_id, email })` — and it takes
  no `user_id` on purpose. From a screen that value is always null: an
  invitation confers NOTHING until the person follows their own sign-in link,
  which is what the door's own `say` sentence states. A client that passed an
  id would be granting access on somebody else's behalf.
- `client.portalRevoke({ portal_id, principal_id })` — the answer's `say` names
  the consequence in the past tense.
- `client.portalPreview({ portal_id, principal_id, table_id })` — "view as this
  client". The door asks `custom.visible_set` for HER user id, the same call
  `custom.read_records` makes when she opens the page, so it cannot disagree
  with what she sees. It REFUSES (`02000`, mapped to `not_found`) when she has
  not signed in, rather than answering an empty list.

New module `portal`, exported from the root: `PortalSummary`,
`PortalTableExposure`, `PortalPrincipal`, `PortalCard`, `PortalInvitation`,
`PortalRevocation`, `PortalPreviewRow`, and `portalPath(slug)` — the one place
`/portal/c/<slug>` is written down.

**Consumer action:** none required; every addition is new surface. A host that
already builds a portal address by hand should use `portalPath` so there is one
spelling of it.

## 0.13.0

**A choice value is the choice's own word, and this package says what that means.**
A list Field's cell used to hold the option RECORD'S ID, so every read, filter,
group-by, export and agent got back a uuid — the seventh independent pass failed
acceptance test T8 on exactly that. The store now holds the option's own stable
key, the read doors hand back the LABEL and name the key beside it in
`_choices`, and a caller may write the label, the key or (so nothing that worked
yesterday breaks) the option's id.

New module `choice`, exported from the root: `optionKey`, `optionLabel`,
`isTheChosen`, `choiceValuesOf`, `choicesOf`, `holdsARetiredChoice`,
`choiceSlug`, `looksLikeAnId`, and the `ChoiceOption` type. `isTheChosen` is the
one a control wants: it recognises a choice by its key, its label or its option
id, which is what lets one picker read a document from any of the three eras.

**Consumer action:** a control that compared a cell to `option.id` must use
`isTheChosen` and write `optionKey(option)` — comparing against the id alone now
shows nothing selected, because the read door hands back the label.

## 0.12.0

**A person can see what they are being told about, and switch one off.**
`custom.subscriptions` and `custom.subscription_mute` have been live on the
database since 20 September and no client could call them, so a form's "tell me
when somebody answers" could be turned ON by an agent and never turned off by
the person it was telling. `client.subscriptions({ table_id })` answers mine
plus anyone's over a Table I hold admin on — narrowed to Tables I can already
open, so the list never reveals one — and `client.subscriptionMute({ rule_id,
muted })` is the switch. `MySubscription` carries the two things a screen needs
and the notifier does not: `muted`, and `i_may_mute`.

`aggSubscriptions` is unchanged and is still the NOTIFIER's reader, which no
client can call. Muting is honoured inside that one reader, so a screen can
never say a subscription is off while it keeps firing.

## 0.11.0

**The publish gate stops red-lighting on other lanes' migrations.** `the
generated door catalogue is the store's` asserted `STORE_DOORS.length === live`,
so every publish failed in which anybody anywhere had shipped a new `custom`
routine — 337 mirrored against 342 live on 2026-09-20, and not one of the five
was this package's. A release that red-lights on somebody else's work teaches
everyone to ignore red.

What the package must be right about is asserted and still gates: **every door
this mirror names exists in the live store**. A name here the store does not
have is this package claiming a door it cannot open, which is the whole reason
`store.generated.ts` is generated rather than typed. Doors the store has and the
mirror has not seen are REPORTED by name, loudly, and gate nothing.

### Consumer action

None. No API changed.

## 0.10.0

**The work layer has client doors, and this package can reach them.** Until lane
WORK-DOORS (2026-09-20) all nineteen `custom.work_*` functions were SECURITY
INVOKER, held no EXECUTE for `authenticated` and carried zero rows in
`platform.client_callable_door`: REC-69 (assignment and action state), REC-70
(template instantiation) and REC-71 (the slot hold) were live in the store and
unreachable from a browser. Thirteen new client methods, all against doors that
ask the one ladder first:

* `workInbox` — **THE ONE QUEUE.** Assignments, approvals and the agent's
  proposals in one ordered list with one row shape (PRODUCTS.md row 6).
* `workList` — "my work", "work I assigned", "waiting on somebody being chosen",
  across every Table in the organization, every row decided by the one ladder.
* `workAssign` — the Assignee value AND editor access, in one transaction. An
  assignment that did not give access put a row in somebody's inbox that they
  were refused when they opened it.
* `workRecordStates` / `workSetState` — the moves the workflow model allows, with
  the sentence for each one it does not, so a screen offers real moves.
* `workApprovalRequest` / `workApprovalMayDecide` / `workApprovalDecide` /
  `workApprovalRead` / `workApprovalApprovers` — asking for a change instead of
  making it, and a decision that **applies** the change in the same transaction.
* `workPerson`, `workTemplates`.

New types in `work.ts`: `WorkInboxItem`, `WorkListItem`, `WorkRecordState`,
`WorkApprover`, `WorkApprovalFiled`, `WorkApprovalDecision`, `WorkAssignment`,
`WorkChange`, `WorkInboxKind`, `WorkOrigin`, `WorkDueState`, `WorkListFlavour`.

**Consumer action:** none required — every addition is new surface. A host that
wants the inbox takes `@ai-matrx/records-ui` 0.15.0 with it.

## 0.7.2

**The door catalogue, regenerated while the store was still growing.** 0.7.1
mirrored 300 doors and the gate, running twelve minutes later, counted 301 —
another lane shipped one in the window. Regenerated: 301 doors. Nothing else
changed.

## 0.7.1

**The live suite opens the switch the way the store resolves it.**

0.7.0's release gate failed four clauses against the live store and none of them
was about this package's code. `platform.knob_resolve` reads an organization's
own override ABOVE the base knob row, and Test's Org gained one
(`custom/system_enabled` = false, written through the ramp screen's own writer)
— so the suite's in-transaction `update platform.feature_knob` stopped opening
anything and three cases met the store's switched-off refusal. Both rungs are
set inside the transaction now, for the member's organization and for the
stranger organization the membership wall case names, and the `rollback` still
puts both back.

The fourth was the door catalogue: 293 mirrored names against 300 live doors,
because other lanes added seven. `src/store.generated.ts` is regenerated from
the live store — 300 doors, 35 knobs, 1020 entity tokens — which is the file's
whole contract: nothing in it is typed by a person.

## 0.7.0

**A screen can ask what this person may do, before it offers it.**

The 19–20 September verdict (sixth pass) watched a colleague who had been shared
a table at Viewer double-click a cell, get a live editor, type a value and press
Enter — and only then be told she may not write. "The permission is right; the
screen told her she could." No screen could have done better: the store answers
"what may this person do to this thing?" in exactly one place,
`custom.effective_level`, which has no grant to `authenticated` and no decision
of its own — it takes ANY principal and ANY subject, so granting it to clients
would be handing out a visibility probe.

- **`client.myLevels({ ids })`** — the new `custom.my_levels` door. What level
  the CALLER holds on the subjects they name, with no principal argument: the
  person is resolved from the session, so you can ask about your own access and
  about nobody else's. It takes a LIST so a page of fifty rows is one call, and
  **every id asked about gets a row** — `null` included, because a missing row
  and "no access" would be the same thing to the screen that asked and one of
  them is a silence. `null` for an id that does not exist is the same answer, so
  it is not an existence oracle.
- **`useMyLevel(id)` / `useMyLevels(ids)`** in `/react`.
- **`level.ts`**: `PermissionLevel`, `PERMISSION_LEVELS`, `atLeast`,
  `asPermissionLevel`, `highestLevel`. The ladder a screen compares with is the
  ladder the door compares with; two orderings would be two answers to one
  question and the second is always wrong.

**What this release deliberately does NOT do.** A `Table.your_level` carried off
the read door looked free and was wrong: `tableList` reads the Table KERNEL, so
`read_records`'s `level` there is the caller's level on the kernel — viewer, for
everybody — and not on the table that row describes. It was written, measured
wrong within the hour (a table's own owner came back `viewer`), and removed; the
reason is in `core/client.ts` where the next person will reach for it. A table
is a record, and its level is asked for like any other record's.

### Consumer action

None required. `custom.my_levels` is live on the main database (lane UI-HONEST,
`uihonest_a_screen_can_ask_what_it_may_offer.sql` +
`uihonest_the_level_door_takes_a_json_list.sql`), so `myLevels` answers as soon
as this version is installed.

## 0.6.2

The generated store contract, regenerated at 279 doors. 0.6.1's gate was red on
nothing but the count: the door catalogue is asserted EQUAL to the live store's,
and other lanes landed a door in the ten minutes between the regeneration and
the publish. No source change and no consumer action.

## 0.6.1

0.6.0's release gate caught four things the live store had changed since the
contract was generated, and this is all four.

- **`54001` is a refusal class this package now knows.** `custom.record_delete`
  stops a cascade that nests past sixty-four levels of containment — a loop
  somebody built rather than a hierarchy — and it is a `store_limit`.
- **The generated contract is regenerated at 278 doors.**
- **The signed-out reach assertion excludes trigger functions**, for the reason
  `platform.reopen_declared_doors` excludes them: they have no direct call
  surface, so a grant on one says nothing about who can reach the store. Two
  store triggers another lane landed turned it red while nothing about the
  store's reach had changed.
- **The read door decides the organization WALL before the switch.** It opens
  with `custom.assert_may_know_table`, so a person outside an organization is
  told they are not a member and is never told whether the store is on for it.
  The adapter suite accepts both of the store's own sentences and no third one.

### Consumer action

None.

## 0.6.0

**A column can be added.** The store grew the three doors a Field always needed
and this package carries them.

- **`fieldDeclare`, `fieldUpdate`, `fieldRetire`** on the client, and
  `useFieldMutation()` in `/react`. Until now a Field was written with
  `recordWrite` into the field kernel, and that could never work: the one write
  door writes `data_class = 'record'` and cannot ALSO add the key to the Table
  record's `fields` array, which the store's field guard requires before it
  accepts any definition at all. Every screen that tried ended on *"the table
  does not declare a field called … — declare it there first"*.
- **`NewFieldDeclaration`** — what a PERSON says when they add a column: a
  parity type, a name, and for a choice list the choices themselves. The store
  turns that into the behaviour, modifiers, format, unit, relation target,
  relation maximum, compute_on and attached Rules its own guards demand, so a
  client states an intention and never re-derives thirteen types of its own.
- **`FieldPatch`** — a field's own settings. What a field HOLDS is a conversion
  and the door refuses it here by name.
- The generated store contract is refreshed to the live 260-door registry.

### Consumer action

Nothing breaks. A host that writes Fields with `recordWrite` should move to
`useFieldMutation().declare` — the old path is the defect, not a deprecation.

## 0.5.2

Automatic changed-only republish (docs/metadata drift since the last tag — see
`git diff npm/records/v0.5.1..npm/records/v0.5.2 -- apps/shared/records`).
No source changes intended and no consumer action required.

## 0.5.1

Refreshes the generated store contract to the live 240-door registry caught by
the blocking release guard after 0.5.0 was tagged.

### Consumer action

None. The two new door entries are additive generated contract data.

## 0.5.0

**Two refusal classes the store really raises, which this package answered with
"something this package does not recognise".** Both were found by walking the
real product on 2026-09-19, and in both the screen showed the store's own
sentence — honest, but the class was nameable and this package had not named it.

- **`57014` — the store cancelled the statement at its own timeout.** Seen on an
  organization whose Table kernel is large enough for `custom.read_records` to
  run out of time. It is now `timed_out`, which is, with `stale_read`, one of
  only two refusals in the vocabulary worth REPEATING: nothing was refused on
  its merits and nothing was written, so a screen can offer "try again" honestly
  instead of showing a failure a person cannot act on.
- **`22P02` — a value that does not fit the type the store holds for it.** Seen
  as `invalid input syntax for type uuid: "open"`, writing the text `open` into
  an organization's copy of a package Table whose `status` Field was uuid-shaped.
  It is `invalid_argument`, which is what it is: the CALL is wrong for the shape
  the store holds, and the store's own message names the value and the type.

Both are proved against the LIVE store in `src/__tests__/real-doors.test.ts`:
the SQLSTATEs are raised by the real database inside the suite's own
rolled-back transaction and read back out of psql's verbose report, and the only
thing asserted about this package is what it makes of them. Each case is RED
without its map entry (`expected 'internal' to be 'timed_out'`).

### Consumer action

`RecordsErrorCode` gains one member, `timed_out`. A host that switches
exhaustively over the union must add an arm for it; every other host needs
nothing beyond taking `latest`. A host that was special-casing `internal` to
recognise a timeout by its message text should branch on `timed_out` instead.

## 0.4.1

Carries the live Supabase adapter proof through the shared npm publishing lane
by supplying its existing main-database HTTP URL and publishable-key secrets to
the verification step.

### Consumer action

None beyond taking `latest`; the 0.4.0 API is unchanged.

## 0.4.0

**The browser can reach the store again — `supabaseDataSource`.** Every screen
of the record store was dead from a browser, for everybody, and the cause was a
single silent drop. `RecordsDataSource.rpc(fn, args, { schema })` says the
schema out loud, because every door is `custom.<name>` and every trust door is
`iam.<name>`. supabase-js's own `rpc()` has NO schema option — its third
argument is `{ head, get, count }` — so a host that bound its client as a bare
structural cast handed the option to a function that dropped it, every door call
went to PostgREST's default profile, and the store answered, correctly and
uselessly, `PGRST202 — Could not find the function public.table_kernel_id in the
schema cache`. Independent verdict, 2026-09-19.

The fix is ONE adapter in the headless core, exported from `/core`, that every
host inherits — never three hosts (web, desktop, extension) each remembering the
same thing. It routes through `client.schema(name).rpc(...)`, which is the one
way supabase-js offers, and it refuses two things by name rather than sending
them: a call with no schema (which would reach `public`), and a client with no
`.schema()` method (which would throw inside a render).

**The test that would have caught it**, `src/__tests__/supabase-adapter.test.ts`:
a real supabase-js client against the MAIN database, signed in as `test@test.com`,
calling a real door. A raw `fetch` to `/rest/v1/rpc/...` would NOT have caught
this — the bug is in supabase-js's option handling, so only a real client
exercises it. The RED half reconstructs the bare cast that shipped and asserts
the store's own PGRST202 naming `public`; the GREEN half is the same call
through the adapter answering the kernel id.

## 0.3.0

**The anonymous lane, wired to the doors that actually exist.** This package
named a door `custom.anon_record_write`, which the store never had — so
`FormRunner` told every person that a public form's link was "pending" for a
lane that had already shipped. W4-ANON landed as SEVEN doors, and they are all
here now: `anonTokenIssue` / `anonTokenVerify` / `anonTokenRevoke` (an embed
token carries ONE decision, read or write, and an exact origin list), `anonWrite`
(the stranger's write — the TOKEN supplies the organization, and what it makes is
a quarantined submission, never a record), `anonCapture` (the SIGNED-IN offline
path, deduplicated on a key the client mints) and `anonPublish`. A screen calling
a live feature pending is the same silent failure as one calling a missing
feature live.

**Work.** `workStates`, `workTemplateRefusal` / `workTemplateDeclare` /
`workTemplateInstantiate` / `workTemplateShape` / `workInstantiationShape`,
`workWhoseTurn`, `workHasAssignment`, `workAssignmentFields`,
`workTakeAssignment`, `workTransitionRefusal`, and the slots:
`workSlotsDeclare`, `workSlotHold`, `workSlotRelease`, `workSlotHolds`,
`workSlotExpire`. One hold per slot is a UNIQUE INDEX in the database, so a
second hold comes back as `already_exists` carrying the index that refused it.

**The trust doors, and they are `iam`'s, not the store's.** `isExternalPrincipal`,
`externalPrincipalCard`, `externalPrincipalReach`, `publishBindingCreate`,
`publishBindingRevoke`, `resolvePublishBinding`, `worldPublishGapNotice`,
`publishToWorld`. They are named by hand in `core/doors.ts` rather than
generated, because `pnpm records:generate` reads `custom` and only `custom` — a
census of it says nothing true about `iam`. `RecordsDataSource.rpc` gained an
optional third argument, `{ schema }`, which a supabase-js host routes to
`client.schema(schema).rpc(...)`; every existing call is unchanged.

New types: `WorkState`, `WorkGraph`, `WorkInstantiation`, `WorkTurn`,
`WorkSlotHold`, `WorkAssignmentField`, `AnonTokenBinding`,
`ExternalPrincipalCard`, `PublishBinding`, `ResolvedPublishBinding`.

**Two vocabularies this package had wrong, found by writing a Field the store
refused.** `FIELD_SENSITIVITIES` read `standard · sensitive · secret`; the store
has always wanted `public · internal · confidential · restricted`, which is also
what the knob `custom/field_sensitivity_levels` keys off — so the two could
never have agreed. And `Field.context_policy` was typed as `Delivery`
(`inline · on_demand · auto`), which is a different question: delivery is how
much of a merge field's value rides into a turn, while FLD-12's context policy
is whether an agent may see a Field's values at all, in the store's own words
`include · summarize · exclude · on_request`. Both are corrected, and
`ContextPolicy` is now its own exported type. Neither was generated, which is
why neither was caught by `pnpm check:store-registry`.

`src/store.generated.ts` regenerated from the live store.

## 0.2.0

**Reads go through the read door.** `recordRead` calls `custom.read_record` and
`list` calls `custom.read_records` instead of selecting `custom.record`, so the
store decides which rows a reader may see and which fields of them: a masked
field comes back present-and-null with the store's own reason in `hidden`
(`_hidden`, unfolded for you). `query` materialises through the same doors, and
its as-of reads now call `custom.query_record_as_of` / `custom.query_table_as_of`
— both clocks — instead of filtering `updated_at` itself and refusing the world
clock by name.

Shapes that changed with it: `list` and `query` answer `ReadRow[]`
(`{ id, document, level, hidden }`) rather than raw store rows, `RecordRead` is
`{ record_id, document, hidden, computed }`, and `useRecords` lost
`includeDeleted` — the read door reads live records only, and there is no door
that answers otherwise.

New refusal class: `stale_read` (`40001`), which is `custom.has_visibility_at`
refusing to answer from a snapshot older than a write the reader has already
observed. It is the one refusal worth retrying.

**A store defect this found and fixed** (`db/migrations/campaign/vis25_the_owner_arm_on_the_read_door.sql`,
applied to the main database): `custom.has_visibility` had no VIS-25 owner arm,
so the record you had just written was yours to `custom.read_records` and not
yours to `custom.read_record` — the list offered the row and the door slammed.

## 0.1.1

First working release: the types root, the headless `/core` client over the
store's doors, the `/react` binding, the generated store registry
(`pnpm records:generate` / `check:store-registry`) and the real-door suite that
runs against the live rehearsal branch inside an always-rolled-back transaction.

Two defects the suite found in the mirror and fixed:

- The document-size prediction measured `JSON.stringify` bytes; the store
  measures the JSONB text, which puts a space after every `:` and every `,`. On
  an 18-key document the two sentences disagreed by 35 bytes. The mirror now
  renders the way Postgres does.
- The mirror predicted refusals for `ver`, `actor`, `on_behalf_of` and `at`.
  The store never raises them: `custom.stamp_value_envelopes` and
  `custom.value_versions` overwrite all four with the truth BEFORE
  `custom.value_envelope_refusal` looks. Those four predictions are gone —
  a sentence the store never says is a defect in the mirror.


