import { U as Uuid, g as Timestamp, n as ReadRow, l as RecordDocument, T as TableType, a as TableDisplay, S as StorageMode, aw as TableOrigin, as as StoredRecord, p as ParityFieldType, j as RecordsError, R as RecordsResult, ad as RecordChange, ae as RecordChangeResult, af as RecordGraphWritten, ai as RecordRead, m as RecordPageSort, ah as RecordPage, ag as RecordKeysetPage, aj as RecordRevision, r as ReadValue, aE as WriteConflict } from './record-C3thPhpR.cjs'; import { G as StoreFilter, p as DashboardSummary, I as InterfaceRead, w as InterfaceRows, n as DashboardBlockResult, m as DashboardBlock, R as RecordFilter, e as AggregateCompare, A as AggregateBucket, o as DashboardRunResult, H as StuckRow, c as AggregateMeasure, k as AggregateRow, J as Subscription, M as MySubscription, L as SubscriptionSpec, v as DigestPreview, d as SavedViewRow, E as SavedViewSpecIn, y as RowTemplateRow } from './subscription-BRwuydeh.cjs'; import { f as RelationCoordinate, j as RelationWordsManyArgs, g as RelationIcons, k as ReverseColumn, o as ReverseLinksManyArgs, m as ReverseLinks, n as ReverseLinksFieldsArgs, S as SourceArchived } from './relation-DuY2Eis-.cjs'; import { F as Field, r as FieldPatch, u as NewFieldDeclaration, z as RelationNamesAnswer, o as FieldDuplicated, s as FieldProposal, T as TableProposal, h as ButtonPressed } from './field-BJBjyD8y.cjs'; import { S as ScopeDoors } from './doors-CBiSnfzU.cjs'; import { P as PickListDoors } from './pickListDoors-cMy0JEig.cjs'; import { R as RuleExpression, F as FormSummary, b as BookingSummary, B as BookingAvailability, a as BookingDeclared, e as FormRoute, d as FormResults } from './rule-Ch4aZVWV.cjs'; import { P as PermissionLevel, R as RecordsConfig, M as MyLevel } from './ports-BSd-1p1l.cjs'; import { F as FormLookOptions, t as PortalSummary, d as PortalArchiveFilter, c as PortalArchiveAnswer, v as PortalTableRef, e as PortalCard, g as PortalConfigInput, l as PortalFormSpec, i as PortalFormAnswer, m as PortalInvitation, p as PortalRevocation, n as PortalPreviewRow } from './portal-Cd8TMBAQ.cjs'; import { TableKindFacts } from './table-kind/index.cjs'; /** * THE TWO LANES THE ARCHIVED-ITEMS LAW NAMES (Arman, 2026-09-09) — the same * split every other list in the platform makes between what is mine and what * is the organization's. * * · `mine` — what I archived. * · `org` — what anybody in this organization archived. * * `mine` is by construction a SUBSET of `org`: the store applies it as a * narrowing conjunct beside the read-access ladder, so a lane can never show a * record the ladder would have refused. */ declare const ARCHIVE_LANES: readonly ["mine", "org"]; type ArchiveLane = (typeof ARCHIVE_LANES)[number]; /** THE DEFAULT LANE. The organization's archive, because "where did that job go?" is almost never a question about yourself. */ declare const DEFAULT_ARCHIVE_LANE: ArchiveLane; /** What each lane is called on a screen, and what it actually answers. */ declare const ARCHIVE_LANE_LABEL: Record; declare const ARCHIVE_LANE_MEANS: Record; /** A word that is not a lane is not silently answered as one. */ declare function isArchiveLane(word: unknown): word is ArchiveLane; /** * One archived record, as `custom.read_records_archived` answers it: an * ordinary `ReadRow` — same id, same masked document, same level, same hidden * notices as it had while it was live — plus the two facts that only an archive * needs. * * `archivedBy` is null when the archiver is not a member of this organization * any more (their name is not this screen's to learn) or when the history * partition that held the SOFT_DELETE has rolled away. Null is an ANSWER and * gets its own sentence; it is never printed as an empty space. */ interface ArchivedRow extends ReadRow { archivedAt: Timestamp; archivedBy: Uuid | null; archivedByName: string | null; } /** The row shape the door returns over the wire, before it is folded. */ interface ArchivedDoorRow { id: Uuid; document: unknown; level: string; archived_at: Timestamp; archived_by: Uuid | null; archived_by_name: string | null; } /** * WHEN IT WAS ARCHIVED, THE WAY A PERSON SAYS IT — "22 September 2026 at 01:53". * Not an ISO string, and never a bare relative word like "yesterday" on its * own: somebody deciding whether to bring a record back is deciding about a * particular day's work. */ declare function archivedWhen(at: Timestamp, locale?: string): string; /** * THE WHOLE SENTENCE A ROW IN THE ARCHIVE CARRIES. Both halves of the question * the archived-items law asks — who, and when — and an honest sentence when the * store could not answer the first half, rather than a blank where a name goes. */ declare function archivedByLine(row: Pick, locale?: string): string; /** * WHAT AN EMPTY ARCHIVE SAYS, PER LANE. An empty list is an answer and it names * the lane it is empty for, so "nothing here" can never be read as "nothing was * ever archived" while the other lane holds twenty records. */ declare function emptyArchiveLine(lane: ArchiveLane): string; /** A document that survived the round trip is the same document — used by the guard. */ declare function sameDocument(a: RecordDocument, b: RecordDocument): boolean; /** The two levels a custom table can hold. */ type TableLevel = "organization" | "confidential"; /** One readers rule: `field` is one of the table's own fields that names a person or a team. */ interface TableReaderRule { field: string; level?: "viewer" | "commenter" | "editor"; } /** The last approval an organization's admin gave this table (`null` when none ever did). */ interface TableLevelApproval { level: TableLevel; reason: string; approver_role: "owner" | "admin"; approver_user_id: string; approved_on: string; } /** `custom.table_level_facts` verbatim. */ interface TableLevelFacts { table_id: string; organization_id: string; level: TableLevel; readers: TableReaderRule[]; maker_is_reader: boolean; /** Who approves a change of level: the table's organization admins, or the platform (Arman). */ set_by: "org_admin" | "platform"; /** True only for an owner/admin of the table's organization on an ordinary custom table. */ can_set: boolean; last_approval: TableLevelApproval | null; /** * True when THIS person is told the table's name and nothing about its columns (a Confidential * table, knob `access/confidential_hides_structure`, `custom.may_see_table_structure`). Its * column list is then empty by decision, never "no columns yet". Absent from a store that * predates it: read as false. */ structure_hidden?: boolean; } /** What `setTableConfidential` / `setTableOrganization` answer. */ interface TableLevelSet { token: string; table_id: string; level: TableLevel; approval_id: number; approver_role: "owner" | "admin"; from: TableLevel; readers?: TableReaderRule[]; } /** * REC-1. A Table declares its fields, exactly one Home, and the properties * `display`, `ordered`, `heavy` | `light`, `retention` and `detail`. */ interface Table { id: Uuid; organization_id: Uuid; /** * The access ladder's Table "Level": organization · public · confidential · private. Absent is * the organization's own. Confidential and Private records have no "Shown to". */ level?: string | null; /** The token this Table is addressed by. */ slug: string; name: string; label_singular: string | null; label_plural: string | null; icon: string | null; color: string | null; /** REC-32. One of the seven, closed. */ type: TableType; /** REC-11. A detail Table inherits only: no direct shares, never a Home. */ detail: boolean; parent_token: string | null; /** AGT-1 / AGT-2. On by default for a custom table; an organization turns it off. */ agent_writable: boolean; display: TableDisplay | null; ordered: boolean; /** REC-4. `heavy` is indexed with Rules on write; `light` is the whole document with Rules on read. */ weight: StorageMode; /** REC-23. How long a soft delete stays reversible. */ retention_days: number | null; /** REC-2. Every Table has one; without it a record cannot be a chip. */ title_field: string | null; /** * T8 / CD-CORE-15. The Field KEY whose value is each record's type (a Shape's Kind); which columns a record * has follows it (`applies_to_types` on a Field). Absent or null: every record has every column. */ type_field?: string | null; /** The field ids this Table declares, in declaration order. */ fields: Uuid[]; default_sort: string | null; row_order: string | null; /** REC-14. Where this Table lives. Exactly one primary Home. */ home_id: Uuid | null; /** The feature that keeps this Table out of the person's lists (`snapshot`, `copying`, …); absent for a person's own table. */ kept_for?: string | null; /** REC-27. One of the nine, defined in code and not editable by Migration. */ is_kernel: boolean; created_by: Uuid | null; updated_by: Uuid | null; created_at: Timestamp; updated_at: Timestamp; version: number; metadata: Record; /** The old lane word; read `shown_to` and `published_to_web` (access ladder), which the store answers now. */ visibility: string; /** Access ladder "Shown to" (which lists show it; never a lock). Absent on a store that does not answer it. */ shown_to?: string | null; /** Access ladder "Published to the web". Absent on a store that does not answer it. */ published_to_web?: boolean; /** * Lane 10 FD. Part of the business's day-one data (Patients, Therapists, Services) — a mark, not * storage. Absent is false. Only admin on the Table changes it (custom._table_shape_guard). */ foundation?: boolean; /** * Present only on what `tableFind({ …, keptFor })` answers for an ARCHIVED table of that slug and * kept_for (the store's `archived_at`); such an answer carries only `id`, `organization_id` and this. */ archived_at?: Timestamp | null; } /** * REC-33. `origin` is `standard` or `custom`, and it is orthogonal to `type`. * A Table we ship in schema `custom` is `standard` — the schema name is not the * origin. */ interface RegisteredTable { token: string; type: TableType; origin: TableOrigin; /** REC-34. Carried per table type: true for Entity and Detail, false for the other five. */ custom_fields_enabled: boolean; } /** * REC-3 / REC-26. A Home is where a Table appears. The declared organization * Home is the tree's root; every additional Home is a referenced carrying * relation from the Home record to the Table record, which is why this shape * names a relation id rather than a second placement column. */ interface Home { table_id: Uuid; home_record_id: Uuid; organization_id: Uuid; /** `declared` for the Table's own Home, `carried` for an additional one. */ kind: string; relation_id: Uuid | null; } /** What `tablePropose` / `tableList` hands back about how full a Table is (REC-N-5). */ interface TableCapacity { records: number; ceiling: number; full: boolean; /** The knob that decides the ceiling, named so a person can act on it. */ knob: string; } /** * WHAT ONE PASS OF `custom.table_archive` ANSWERS WITH — the numbers a screen * needs to draw a bar and write a sentence, instead of a spinner and an * SQLSTATE (VERIFIER-10 F3, VERIFIER-11 F3). * * Archiving a table is a JOB, NOT A STATEMENT. `custom.record_delete` on a * Table recurses through everything the Table contains inside one transaction, * which is unbounded by construction: at 1,430 records it dies on `57014`, * "canceling statement due to statement timeout", and nothing is changed. The * remedy for a thing that cannot finish in one statement is more statements, * never a longer one — so the caller loops, passing its own `chunk`, until * `done`. * * IT IS RESUMABLE BECAUSE IT IS STATELESS. There is no job row to lose: * `remaining` is read off the records themselves on every call, so a browser * closed halfway, a failed pass or a deploy mid-run all leave a table that is * half archived and a next call that picks up exactly where the last stopped. * Which is why a screen driving this may NEVER say "nothing was changed" after * a pass that moved rows — `archived_total` is what really landed. * * NOTHING IS DELETED. Every row goes through `custom.record_delete`, so every * guard, history capture and outbox row still happens and `recordRestore` * still brings any of them back. */ interface TableArchiveProgress { table_id: Uuid; /** The table's own name, so a sentence can use it rather than an id. */ table_name: string; /** What THIS pass archived. */ archived: number; /** Records of this table still live. Zero is the end of the row work. */ remaining: number; /** Records this table has ever held: `remaining` + `archived_total`. */ total: number; /** Records of this table already archived, across every pass ever made. */ archived_total: number; /** Whether the Table record itself has been archived. */ table_archived: boolean; /** Nothing live is left to do. The caller's loop ends here. */ done: boolean; /** The chunk the door actually took on — it clamps what it is handed. */ chunk: number; /** The store's own sentence about this pass, in the product's voice. */ message: string; /** * An earlier run of this archive was started and has not finished (an open archive event). A * screen reading this after a reload offers to carry on, never a fresh button (BREAKER-1 B-F13). * Absent from a store that predates it. */ in_progress?: boolean; /** * THE REVERSIBLE ACTION'S SIZE LINE (knob `custom/archive_confirm_over`, organization-overridable): * a table with at most this many live records is archived at once and announced with Undo; one * with more is asked about first, with the count. Absent from a store that predates it — a screen * then asks first, as it always did. */ confirm_over?: number; } /** * BRINGING A WHOLE TABLE BACK — ONE PASS OF IT (`custom.table_restore`, lane TABLE-ACTIONS). * * The mirror of {@link TableArchiveProgress}: the Table and its structure come back in the first * pass, then its records in the order they went, then what was built on it, all from the one * archive event. The CALLER LOOPS until `done` (`client.tableRestoreWhole` does it). */ interface TableRestoreProgress { table_id: Uuid; table_name: string; /** Records THIS pass brought back. */ restored: number; /** The Table, its Fields, rules and views THIS pass brought back (the first pass). */ structure_restored: number; /** Forms, views, portals, child Tables… built on the Table THIS pass brought back. */ built_on_restored: number; /** Rows the archive took that are still waiting. */ remaining: number; built_on_remaining: number; /** Rows refused on their own now (a value another row took since): left archived and named. */ left: number; left_ids?: Uuid[]; /** Each row left archived, with the store's own reason for it. */ left_reasons?: Record; /** Restoring events of this table still open (a re-archive mid-restore leaves two). */ events_open?: number; /** Whether the Table record itself is live. */ table_restored: boolean; /** Nothing the archive took is still waiting. The caller's loop ends here. */ done: boolean; chunk: number; archive_event: Uuid | null; message: string; } /** * WHAT A STORED COLUMN DIFFERS FROM THE SPEC THAT ASKED FOR IT IN (lane PLATFORM-APP-DATA). * * `custom.table_ensure` never retypes, relabels or drops a column a table already has — a retype is * a conversion a person decides. It used to ignore the difference without a word; now it names * each one, aspect by aspect, and still changes nothing. This shape is the contract a release-time * drift check will read, so it stays this small: * * - `type` the column's kind: its parity type when it has one, else its behavior (`text`, `range`, …) * - `format` how it is shown (`long`, `phone`, `currency`, …); `null` when it has none * - `label` the column's name * - `multi` / `required` booleans (absent = `false`) * - `unique` whether it holds a `unique` rule * - `spec` the spec for that column could not be read at all; `declared` is the store's sentence * * The declared side is the spec as the store WOULD store it, so `long_text` compares as the * `text` + `long` it is stored as — never a false difference. */ /** `extra`: a column the table holds that the spec no longer declares (kept, never removed). */ type FieldDriftAspect = "type" | "format" | "label" | "multi" | "required" | "unique" | "spec" | "extra"; interface FieldDrift { /** The column's key. */ field: string; aspect: FieldDriftAspect; /** What the column holds now. */ stored: string | boolean | null; /** What the spec asked for (for `spec`, the store's sentence about why it could not be read). */ declared: string | boolean | null; } /** * The store's `drift` answer, typed. `null` when the store did not send one — a `table_ensure` * body older than `lane12_c_an_ensured_table_says_what_differs_from_its_spec.sql` — which means * "not compared", never "no difference". An entry of an aspect this package does not know is kept * as `spec`, so a newer store is never silenced. */ declare function parseFieldDrift(raw: unknown): FieldDrift[] | null; /** One option, as `_choices` names it and as `optionsOf` returns it. */ interface ChoiceOption { /** The stable key the cell actually holds. */ key: string; /** What a person reads. */ label: string; /** The option RECORD's id, for a caller that needs the row itself. */ id?: string; /** True when the option was retired: still readable, no longer pickable. */ retired?: boolean; /** Why it is no longer offered, in a sentence, when it is retired. */ reason?: string | null; } /** * The stable key of an option RECORD as `custom.field_options` returns it. * * It lives in `metadata.option_key`, not in the document: FLD-5 keeps a category to one title * field, and the key is the store's own bookkeeping rather than a column anybody declared. * The fallback is the same slug the store derives, so an option written by something that has * not caught up still answers. */ declare function optionKey(option: StoredRecord): string; /** What a person reads on an option record. */ declare function optionLabel(option: StoredRecord): string; /** * The store's own slug, mirrored so a client can predict a key without a round trip. * `custom.choice_slug` is the original; a title with nothing a-z0-9 in it gets a short hash of * itself there, and this returns the empty-ish fallback rather than guessing a hash — a caller * that needs the real key for such a title reads it off the option. */ declare function choiceSlug(word: string): string; /** * Does this stored (or typed) value name this option? The key, the label and the option's own * id all name the same choice, and a control has to recognize every one of them: what the read * door hands it is the LABEL, what the store holds is the KEY, and what a screen written before * this contract sends is the ID. */ declare function isTheChosen(option: StoredRecord, value: unknown): boolean; /** Every value of one cell, as strings, whether the Field is multi or not. */ declare function choiceValuesOf(value: unknown): string[]; /** * What the read door said about one column's choices: the key behind the label, and whether the * option was retired and why. Returns an empty array when the document carries no `_choices` * entry for that column, which is also what a document written before this contract looks like. */ declare function choicesOf(document: RecordDocument | undefined, fieldKeyOrId: string): ChoiceOption[]; /** True when this cell's value points at a choice that has since been retired. */ declare function holdsARetiredChoice(document: RecordDocument | undefined, fieldKeyOrId: string): boolean; /** True when a token looks like a uuid — which a choice value is no longer allowed to be. */ declare function looksLikeAnId(value: unknown): boolean; /** The Feature Knob `custom/choice_nudge`: ask (the default), always add, or never add. */ type ChoiceNudge = "ask" | "always_add" | "never_add"; declare const CHOICE_NUDGES: readonly ChoiceNudge[]; /** Any answer the knob door gives, as one of the three; anything unknown is the default, `ask`. */ declare function asChoiceNudge(value: unknown): ChoiceNudge; /** What `decideTypedChoice` answers. */ type TypedChoiceDecision = /** Nothing was typed. */ { decision: "empty"; } /** The words name a live choice (by its words or its key, any case): write `key`. */ | { decision: "chosen"; key: string; label: string; } /** Ask the person. `keepOffered` is true only when the column takes other values. */ | { decision: "ask"; words: string; keepOffered: boolean; } /** Add the words to the choices and the cell takes them — without asking (`always_add`). */ | { decision: "add"; words: string; } /** Keep the words as typed, an "other" value (`never_add` on a column that takes other values). */ | { decision: "keep_as_typed"; words: string; } /** The words are not one of the choices and nothing may add them (`never_add`, closed column). */ | { decision: "not_a_choice"; words: string; }; /** A choice as either door hands it: an option record (`fieldOptions`) or a `_choices` entry. */ type ChoiceLike = StoredRecord | ChoiceOption; /** * THE ONE DECISION for words typed into a choice cell. * * `options` are the column's choices; a retired one is not pickable, so words naming only a * retired choice are treated as new (adding them brings the retired choice back, which is what * the store does). `nudge` absent or unknown is `ask`. */ declare function decideTypedChoice(args: { words: string; options: readonly ChoiceLike[]; allowOther: boolean; nudge?: ChoiceNudge | string | null | undefined; }): TypedChoiceDecision; /** How many records hold one choice (`custom.field_choice_usage`), keyed by the option's id. */ interface ChoiceUsage { key: string; words: string; retired: boolean; /** Live records the caller may see whose cell holds the choice. */ records: number; } /** Where the records of a removed choice go. */ type ChoiceRehome = { then: "move"; to: string; } | { then: "keep"; } | { then: "clear"; }; /** One cell as it was before a rehoming save — what its undo writes back. */ interface ChoiceCellBack { record_id: string; value: unknown; } /** What `fieldUpdateRehomingChoices` answers. */ interface ChoicesRehomed { field_id: string; rehomed: Array<{ choice_id: string; words: string; then: ChoiceRehome["then"]; to: string | null; records: number; }>; cells_back: number; /** Send `{ patch: { options: undo.options }, cells_back: undo.cells_back }` to put it all back. */ undo: { options: Array<{ id: string; words: string; }>; cells_back: ChoiceCellBack[]; }; } /** * THE CHOICES A LIST NO LONGER NAMES THAT RECORDS STILL HOLD — the ones the editor must ask about. * `beforeIds` is the list as the store answered it, `afterIds` the list as the person has it now. */ declare function removedChoicesInUse(beforeIds: readonly string[], afterIds: readonly string[], usage: Readonly> | null | undefined): Array<{ id: string; words: string; records: number; }>; /** “1 record uses “X-ray”.” · “3 records use “X-ray”.” — the question's first line. */ declare function recordsUseSentence(records: number, words: string): string; /** What the answer will do, said before Save: “3 records move to “Exam”.” */ declare function rehomeSentence(records: number, words: string, rehome: ChoiceRehome, toWords?: string): string; /** The store's behavior words a column can be changed to. */ type RetypeBehaviour = "text" | "range" | "boolean"; /** `custom.migrate_retype`'s answer for a Field. */ interface FieldRetyped { verb: "retype"; field_id: string; was: string | null; now: string; changed: boolean; migration_id?: string; /** Records that hold a value for this column after the change. */ records_still_holding_a_value?: number; /** Values of this column now kept aside in `_retired`, with their reason. */ values_in_retired_for_this_field?: number; at?: string; } /** * `custom.field_retype_preview`'s answer: what a change of a column's kind WILL do, counted by the store * running the change itself in a subtransaction it rolls back (lane F2, D11). It cannot disagree with the * change, because it is the change. */ interface FieldRetypePreview { /** Records holding a value before the change. */ held_before: number; /** Records still holding a value after it (converted, or already fitting). Null when `limited`. */ converts: number | null; /** Values the change keeps aside in `_retired`, with their reason. Null when `limited`. */ kept_aside: number | null; /** * The column holds too many values to run the change ahead of time without holding the table (ADV-1 F8), * or another editor held it. No counts then; `held_before` is still the number of values held. */ limited?: boolean; /** Why it is limited: "large" (too many values) or "busy" (another editor holds the table). */ why?: string; /** Up to five of the values kept aside, as words. */ examples: string[]; /** Choices the change makes from the column's words (a change to Choice or Multi-choice). */ choices_added: number; } /** * `custom.migrate_merge`'s answer (T5 / REC-21): the other record merged into the survivor. Its * values the survivor did not hold moved across; the ones that differed are kept as ranked * alternates with their source (or in `_retired` when the key is not a declared field); every * link and child pointing at it now points at the survivor; it is archived and its id resolves to * the survivor. `migration_id` is what `migrateUndo` takes. */ interface RecordsMerged { verb: "merge"; winner: string; loser: string; migration_id: string; values_taken: number; values_copied: number; alternates_added: number; values_retired: number; relations_retargeted: number; relations_detached_as_duplicates: number; resolves_to?: string | null; at?: string; } /** `custom.migrate_split`'s answer. The original keeps its id; the moved values live on `new_record`. */ interface RecordSplit { verb: "split"; kept_the_id: string; new_record: string; migration_id: string; moved: string[]; old_id_resolves_to?: string | null; at?: string; } /** `custom.migrate_extract_parent`'s answer. The named values were COPIED up into `parent`, and the record now sits under it. */ interface ParentExtracted { verb: "extract_parent"; record_id: string; parent: string; was_under: string | null; migration_id: string; at?: string; } /** * `custom.migrate_retype`'s answer for a RECORD (test 9 / REC-N-18): the record moved to another table of * the same organization and KEPT ITS ID, so every link to it still resolves. The values whose column the * new table lacks are `misfits`: taken off the record and kept in History on `migration_id`, with the * reason — neither coerced nor deleted. `migrateUndo(migration_id)` puts the record back on its table * with every value it held. */ interface RecordMoved { verb: "retype"; record_id: string; kept_the_id: boolean; from_table: string; to_table: string; migration_id: string; /** The values that did not fit, by key, exactly as they were. */ misfits: Record; misfits_are?: string; at?: string; } /** One row of `custom.migrations`: a recorded Migration on one target, and whether it was put back. */ /** `custom.history_retention`'s answer: the organization's floor and (when asked) one table's days. */ interface HistoryRetention { organization_id: string; table_id: string | null; floor_days: number; table_days: number | null; /** The store's own sentence. */ says: string; } interface MigrationEntry { id: string; verb: string; target_kind: string; target_id: string; note: string | null; applied_at: string; applied_by: string | null; undone_at: string | null; } /** `custom.migrate_undo`'s answer: the Migration it put back. */ interface MigrationUndone { undone: string; verb: string; kind: string; record_id: string | null; version_after?: number | null; also_restored?: number; ids_unaliased?: number; /** A split or an extracted parent: the record the Migration made, archived by the undo. */ removed?: string | null; at?: string; } /** * THE MIRROR OF `custom.field_value_convert`: does `value` survive the change to `to` (the * behavior word, and for `range` the kind — `number`, `date`, `datetime`)? An absence always * survives; a list survives only when every member does. */ declare function valueConverts(to: { type: string; kind?: string | null; }, value: unknown): boolean; /** What a retype will do to the values already written, counted before anything is changed. */ interface RetypePreview { /** Records holding a value that will be converted and stay in the column. */ converts: number; /** Records holding a value that will be kept aside (in `_retired`, with the reason). */ keptAside: number; /** Up to five of the values that will be kept aside, to show the person. */ examples: string[]; /** The count covers only the groups the store answered (a ceiling was reached). */ partial: boolean; } /** Count a column's distinct values (value → how many records hold it) against the change. */ declare function previewRetype(to: { type: string; kind?: string | null; }, groups: ReadonlyArray<{ value: unknown; count: number; }>, partial?: boolean): RetypePreview; /** The checks the mirror can judge on one value. */ type JudgedRule = { kind: string; value?: string | number | undefined; min?: number | undefined; }; /** The first check `value` fails, or null when every check keeps it. An absence always passes. */ declare function ruleThatRefuses(rules: ReadonlyArray, value: unknown): JudgedRule | null; /** A rating's checks with its top star as the `max` (the store writes the top star as that Rule). */ declare function rulesWithTopStar(rules: ReadonlyArray, topStar: number): Array; /** What new checks will do to the values already written, counted before anything is saved. */ declare function previewRuleChange(rules: ReadonlyArray, groups: ReadonlyArray<{ value: unknown; count: number; }>, partial?: boolean): RetypePreview; /** The seven table types. Only these two carry custom fields (REC-34, DD-011). */ type CustomFieldTableType = "entity" | "detail"; /** What `custom.entity_table` answers about a registry token. */ interface EntityTableFacts { token: string; schema_name: string; table_name: string; type: CustomFieldTableType; title_column: string | null; /** The name a person reads. Never the type word. */ label: string; /** * 57 of the 643 have no `organization_id`. They carry the column like every * other one, but a VALUE on them would belong to nobody — the guard finds an * organization's Fields through the row's own `organization_id` — so the * value doors refuse them BY NAME rather than writing something unowned. */ has_organization: boolean; has_deleted_at: boolean; } /** One custom field of a standard row, as the read door hands it back. */ interface EntityFieldValue { id: Uuid; key: string; label: string; type: Field["type"]; parity_type?: ParityFieldType; format?: string; unit?: string; multi?: boolean; required?: boolean; sort?: number; sensitivity?: Field["sensitivity"]; options_table_id?: Uuid; relation_target?: Uuid; /** The value itself, or undefined when this row has none. */ value?: unknown; /** VAL-1…VAL-8: who wrote it, when, on whose behalf, in which version. */ written?: { ver?: number; actor?: string; on_behalf_of?: string | null; at?: string; src?: unknown; absent?: unknown; alternates?: unknown; dated?: unknown; }; } /** * REC-53 IN ONE ANSWER: a standard row is its real columns, plus the base * contract, plus its organization's custom fields — three named blocks, never * one flat bag, so nothing can quietly shadow anything. */ interface EntityRecordRead { token: string; label: string; type: CustomFieldTableType; id: Uuid; organization_id: Uuid; /** The registry's `title_column` of this row, when it has one. */ title: string | null; /** The row's own columns, `custom_fields` removed. */ columns: Record; /** The custom values, by key. */ custom: Record; /** Their envelopes, by key. */ custom_written: Record; /** The definitions, in the organization's own sort order, each with its value. */ fields: EntityFieldValue[]; live: boolean; } /** AP-4: one custom row that links to a platform record, and which of its fields does the linking. */ interface EntityBackLinkItem { record: { token: "record"; id: Uuid; label: string | null; }; table_id: Uuid; table_label: string | null; field_id: Uuid; field_key: string; field_label: string | null; organization_id: Uuid; linked_at: string | null; } /** What `custom.entity_back_links` answers: the target, one page of linking rows, and where the next page starts. */ interface EntityBackLinks { target: { token: string; id: Uuid; label: string | null; }; items: EntityBackLinkItem[]; next_cursor: string | null; } /** What `custom.entity_row_write` answers: which row, whose, and whether anything changed. */ interface EntityRowWritten { id: Uuid; token: string; organization_id: Uuid; /** True when every value already matched: nothing was written and the version did not move. */ unchanged?: boolean; /** True when the call had no record id and the door added this row (a token whose create_via is "insert"). */ created?: boolean; } /** One row the filter matched. */ interface EntityRecordMatch { id: Uuid; title: string | null; value: unknown; } interface EntityRecordsFound { token: string; label: string; key: string; value: unknown; rows: EntityRecordMatch[]; count: number; } /** * What a person knows when they add a column. The SAME spec `fieldDeclare` * takes for a custom table — the store turns it into the document its guards * demand, so a standard table gets the whole type vocabulary rather than a * second, poorer one. */ type NewEntityFieldDeclaration = Record & { label: string; type?: string; options?: string[]; }; /** * SCR-12. May this person add a column to this standard table, and if not, the * sentence saying why — so the control is ABSENT with a reason rather than dead * when pressed. Filling in the fields that already exist is not admin work. */ /** * LANE 7 W2. One standard table's Fields across every organization a list spans, each with its * organization and — for a Choice — its options keyed by the option's stable key. An * organization the reader may not reach is NAMED under `unavailable` with the store's sentence. */ interface EntityFieldsAcross { fields: Array | null; }>; unavailable: Array<{ organization_id: Uuid; reason: string; }>; } interface EntityFieldRights { token: string; label: string; may_declare: boolean; reason: string | null; may_fill_in: boolean; } /** One stage of a board: the KEY the store keeps, and the LABEL a person reads. */ interface PipelineStage { /** The option's stable key — what a cell holds, and what a move is written with. */ key: string; /** What the column heading says. Renaming a stage rewrites no record. */ label: string; /** A retired choice can still be READ on a record that holds it; it cannot be picked anew. */ retired: boolean; } /** What each Rule of a pipeline is about. Closed, and the store is the authority. */ declare const PIPELINE_RULE_KINDS: readonly ["moves", "requires", "who", "limit", "entry"]; /** * A GATE is the general form under `requires`, `who` and `limit`, and the store spells it * `gate:1`, `gate:2` … so several gates can sit on one stage without one overwriting the * next. It is matched as a prefix rather than listed, because the number is open. */ type PipelineRuleKind = (typeof PIPELINE_RULE_KINDS)[number] | `gate:${number}`; /** * WHAT A GATE DOES WHEN IT SAYS NO. The closed list `custom.stage_rule_on_fail_kinds()` * answers — a screen offering a third answer would be offering something no Rule can carry. */ declare const STAGE_RULE_ON_FAIL: readonly ["refuse", "require_approval"]; type StageRuleOnFail = (typeof STAGE_RULE_ON_FAIL)[number]; /** * THE ORGANIZATION'S ONE KNOB (`custom/stage_rule_enforcement`). `refuse` is the default and * what a gate is for; `warn` lets the move through and hands the same sentence back. It never * softens `require_approval` — "ask somebody" and "carry on with a note" are different answers. */ declare const STAGE_RULE_ENFORCEMENT: readonly ["refuse", "warn"]; type StageRuleEnforcement = (typeof STAGE_RULE_ENFORCEMENT)[number]; /** * One transition Rule, as `custom.pipeline_read` hands it back — WITH the sentence it says * when it refuses, because a screen that knows a rule exists but not what it refuses in * cannot warn anybody before they drag. */ interface PipelineRule { id: Uuid; name: string; /** The exact words a person is shown when this Rule stops a move. */ message: string; kind: PipelineRuleKind | null; /** The stage this Rule is about; `null` on the one Rule about the moves themselves. */ stage: string | null; uses: string[]; /** What happens when a card ARRIVES in this stage — assign, notify, start a checklist. */ on_entry: Record | null; /** * What this Rule does when it STOPS a move. `refuse` is the default and what every * pipeline Rule did before gates existed; `require_approval` files the change in the * approvals queue with this Rule's sentence as the reason and leaves the record alone. */ on_fail?: StageRuleOnFail; /** * THE GATE AS IT WAS WRITTEN, which is the only thing that makes it EDITABLE. * * A gate is saved as one Rule whose expression is an `or` of four arms — not this * stage, OR not a move, OR the gate is not about this card, OR the demand holds. That * compiled form used to be the only copy, so a settings screen could read a gate's name * and its sentence and nothing else: it would open every existing rule with a blank * condition and overwrite it the first time somebody pressed Save. * * Absent on the four sugar rules (`moves`, `requires`, `who`, `limit`), which nobody * writes in a condition builder, and on any gate declared before the store started * keeping them. Absent means "this one was not written here" — never "it has no * condition", which is a different and dangerous thing to draw. */ when?: Record | null; /** @see {@link PipelineRule.when} — the condition that has to hold, as it was written. */ demands?: Record | null; version: number; } /** * WHAT A GATE WOULD DO TO THIS BOARD TODAY, from `custom.pipeline_gate_preview`. * * The one question somebody writing a rule that will refuse their colleagues' work * actually has, answered while they are still typing it — the gate is judged as an * argument, nothing is saved, and nothing is written. The store compiles it with the * SAME compiler `pipeline_declare` uses, so this count and the refusal at the door are * the same rule. */ interface PipelineGatePreview { is_pipeline: boolean; /** Present, and it says what would make a board exist, when `is_pipeline` is false. */ why?: string; /** The stage key the gate was tried against. */ stage?: string; /** Live cards of this board this person may see that are not already in that stage. */ considered: number; /** How many of them this gate would turn away. */ refused: number; /** * Up to three of them, BY NAME. A count with no names is a number nobody can check, and * checking it is the whole reason to show it before saving. */ examples: { record_id: Uuid; title: string; }[]; /** True when the board is larger than the preview will walk, so the count is a floor. */ truncated?: boolean; on_fail?: StageRuleOnFail; /** The word this board uses for one of its cards — "quote", "deal" — for the sentence. */ noun?: string; } /** The whole board's definition, from `custom.pipeline_read`. */ interface Pipeline { is_pipeline: boolean; /** Present, and it says what would make a board exist, when `is_pipeline` is false. */ why?: string; table_id?: Uuid; /** The Field KEY that holds the stage — the twin of a Table's `type_field`. */ stage_field?: string; field_id?: Uuid; /** What that column is called, e.g. "Stage". */ stage_label?: string; /** In the order they were declared, which is the order the board draws them. */ stages?: PipelineStage[]; rules?: PipelineRule[]; } /** One column of the board, with the store's own numbers over what this person may see. */ interface PipelineColumn { stage_key: string; stage_label: string; /** 1-based, the declared order. */ stage_position: number; cards: number; /** The total of the chosen measure, or null when no measure was asked for. */ total: number | null; /** How many cards this column takes at once, or null when it takes any number. */ wip_limit: number | null; /** True when the column already holds MORE than its limit — a state a board must show. */ over_limit: boolean; } /** One column this stage demands before a card may land in it. */ interface PipelineMissingField { field_id: Uuid; key: string; /** What the column is called, so a screen can ASK for it rather than only refuse. */ label: string; } /** * Whether a card may go to a column, answered BEFORE the finger lets go — by the same * Rules, through the same evaluator, on the document the move WOULD produce. * * A board grays a column, and snaps a card back, from this. It never decides for itself. */ interface PipelineVerdict { allowed: boolean; is_pipeline: boolean; /** * The THREE answers a board has to draw differently, and the reason `allowed` alone is * not enough: a card that is going for approval has not been refused, and a card that * moved under `warn` has not been waved through silently. */ outcome?: "allowed" | "refused" | "needs_approval" | "warned"; /** What this organization's one knob says right now. */ enforcement?: StageRuleEnforcement; /** Set on `needs_approval`: what pressing on will actually do, in plain words. */ what_happens?: string; /** Set on `warned`: the gates this organization asked to be told about, not stopped by. */ warnings?: StageRuleWarning[]; /** Approvals already filed against this card's own stage column that nobody has decided. */ pending?: StagePending[]; /** The stage key the move was asked about. */ to?: string; /** The store's own sentence — shown verbatim, never paraphrased by a screen. */ why: string; rule?: string; rule_id?: Uuid; rule_version?: number; kind?: PipelineRuleKind; /** What this Rule does when it stops a move. */ on_fail?: StageRuleOnFail; /** * When a stage demands columns that are empty, exactly which ones — so the screen can * prompt for them on entry instead of sending the person away to find them. */ missing?: PipelineMissingField[]; } /** What `custom.pipeline_declare` says it made or changed. */ interface PipelineDeclared { table_id: Uuid; stage_field: string; field_id: Uuid; stages: string[]; rules: Record; /** Plain sentences naming each thing this declaration wrote. */ said: string[]; } /** The declaration a person (or an agent) writes. Everything is optional but the stages. */ interface PipelineSpec { stage_field: { key: string; label?: string; options?: string[]; sort?: number; }; transitions?: { from: string; to: string; }[]; /** Stage name → the column keys that must be filled in to land there. */ requires?: Record; /** Stage name → the rung somebody needs on the record to move a card INTO it. */ who?: Record; /** Stage name → how many cards may sit there at once. The mover is not counted. */ limits?: Record; /** Stage name → what happens when a card arrives: assign, notify, start a checklist. */ on_entry?: Record>; /** * Stage name → the gates a card has to get past to land there. The general form under * `requires`, `who` and `limits`, and the only one that can say `require_approval`. */ gates?: Record; moves_message?: string; requires_messages?: Record; } /** * A gate the organization's own knob overruled. NOTHING FAILS SILENTLY: the move happened * and the sentence comes back WITH it, together with what to do about it. */ interface StageRuleWarning { rule_id: Uuid; rule: string; /** The gate's own sentence, shown verbatim. */ why: string; what_to_do: string; } /** An approval filed against this card's stage column that nobody has decided yet. */ interface StagePending { approval_id: Uuid; /** The stage key the card is waiting to go to. */ to: string; why: string | null; requested_at: string; state: string; } /** * The same wait, asked for a WHOLE BOARD in one question (`custom.pipeline_pending`), so a * board can badge every card that is waiting without a round trip per card — and therefore * actually does it, instead of drawing a filed card as if nobody had ever dragged it. */ interface StagePendingOnBoard extends StagePending { record_id: Uuid; } /** What `custom.pipeline_move` answers — an OBJECT on every path, never a bare number. */ interface PipelineMoveResult { applied: boolean; moved: boolean; outcome: "moved" | "moved_with_warnings" | "needs_approval"; to: string; version: number; /** Set on `moved_with_warnings`. */ warnings?: StageRuleWarning[]; /** Set on `needs_approval`: the gate's sentence, the wait that was filed, and who has it. */ why?: string; rule?: string; rule_id?: Uuid; approval_id?: Uuid; approvers?: { user_id: Uuid; name: string | null; why: string; }[]; what_happens?: string; } /** One card of a batch move (`custom.pipeline_move_many`). The version is REQUIRED: it is the * version the board loaded, and a card that changed since answers `changed_since_loaded`. */ interface PipelineMoveManyItem { record_id: Uuid; stage: string; expected_version: number; /** Columns the stage asks for, filled in on the way in. */ also?: Record; } /** What one card of a batch move came to. The sentence is always the store's own. */ type PipelineMoveVerdict = "moved" | "needs_approval" | "needs_fields" | "changed_since_loaded" | "no_right" | "refused"; interface PipelineMoveManyResult { index: number; record_id: Uuid; stage: string; verdict: PipelineMoveVerdict; sentence: string; /** Set on `needs_fields`: the empty columns the stage asks for. */ needs?: PipelineMissingField[]; /** Set on `moved` / `needs_approval`: the card's version now. */ version?: number; /** Set on `changed_since_loaded`: the version that won. */ current_version?: number; expected_version?: number; approval_id?: Uuid; approvers?: { user_id: Uuid; name: string | null; why: string; }[]; warnings?: StageRuleWarning[]; rule?: string; code?: string; } /** What `custom.pipeline_move_many` answers: the counts, then one verdict per card in order. */ interface PipelineMoveMany { asked: number; moved: number; needs_approval: number; not_moved: number; results: PipelineMoveManyResult[]; } /** * ONE GATE ON A STAGE, as the condition builder on the stage settings screen writes it. * `requires`, `who` and `limits` are sugar over this shape; this is the general form, and * it speaks the SAME condition language as filters and formulas — never a second one. */ interface PipelineGate { /** What the gate is called in the settings list. */ name?: string; /** The sentence a person reads when it stops them. Required: a refusal with no words is a dead end. */ message: string; /** WHEN this gate is about this record at all. Absent means always. */ when?: Record; /** What has to hold. One condition node of the one Rule language. */ demands: Record; /** Default `refuse`. */ on_fail?: StageRuleOnFail; } /** * THE ONE PLACE A SCREEN DECIDES WHETHER A CARD LOOKS DRAGGABLE — and it decides on the * PERSON, not on the rules. Whether a particular move is allowed is * `pipelineTransitionRefusal`, which is the store's answer; whether this person may move * anything at all is their level on the record, which the read door already told us. * * Absent rather than dead: a viewer's card carries no drag handle at all, instead of one * that looks live and snaps back every time. */ declare function mayDrag(level: string | null | undefined): boolean; /** What the store thinks one column of a file is. */ interface ImportColumnPlan { /** The heading exactly as the file spells it. */ header: string; /** Set when this table already has the column; then `type` is that Field's. */ field_id: Uuid | null; /** The key this column would become, or the key it matched. */ field_key: string; label: string; /** * A parity type, or one of the three plain words (`text`, `long_text`, * `number`). It is exactly what `custom.field_declare` takes, so a plan can be * handed on with nothing in between to reinterpret it. */ type: ParityFieldType | "text" | "long_text" | "number" | "relation"; matched: boolean; /** One sentence, in words a person reads, saying why the store said that. */ why: string; /** Present on a money column. */ unit?: string; /** Present when the store read the column as a dropdown: its words. */ options?: string[]; /** Present when the column points at another Table. */ relation_target?: Uuid; /** A slashed date whose day/month order cannot be read off the file. */ ambiguous?: boolean; /** Another column of the same file would make the same key. */ collides_with?: string; samples?: unknown[]; } interface ImportPlan { table_id: Uuid; columns: ImportColumnPlan[]; matched: number; unmatched: number; fields: Array<{ field_id: Uuid; key: string; label: string; type: string | null; }>; } /** What a run does when it meets something. Both defaults cannot lose anything. */ interface ImportPolicy { /** `skip` leaves the record that is already there; `update` brings it up to date. */ on_duplicate?: "skip" | "update"; /** `propose` sends a new column to the ONE approvals inbox. `create` is an admin's. */ unmapped?: "propose" | "create" | "ignore"; /** How a slashed date is read. Chosen once for the run, never guessed per row. */ date_order?: "mdy" | "dmy"; } interface ImportOpened { import_id: Uuid; state: "open" | "written" | "finished"; /** * TRUE means these exact bytes already landed on this Table and NOTHING was * written again. The numbers are the FIRST run's. */ already: boolean; rows_seen: number; rows_written: number; rows_duplicate: number; source_name: string | null; opened_at?: Timestamp; /** * IMPORT-2. How many rows to send in the FIRST call. The store owns this * number rather than a screen, because nothing has been measured yet when a * run opens and a batch size written into a wizard is wrong on a busy * afternoon and stale the day the write path gets cheaper. Every call after * the first uses `ImportBatchResult.rows_per_call`, which is measured. */ rows_per_call?: number; message: string; } /** One row of the file, and what became of it. */ type ImportOutcome = { row: number; outcome: "landed"; record_id: Uuid; } | { row: number; outcome: "duplicate"; record_id: Uuid; updated: boolean; reason: string; } | { row: number; outcome: "refused"; reason: string; source: Record; }; interface ImportProposal { column: string; field_key: string; label: string; type: string; why: string; samples: unknown[]; state: "proposed" | "waiting" | "accepted" | "rejected" | "ignored" | "refused"; /** Set once the column is waiting in the approvals inbox. */ approval_id?: Uuid; /** Set once an admin added it outright. */ field_id?: Uuid; reason?: string; options?: string[]; unit?: string; relation_target?: Uuid; } interface ImportBatchResult { import_id: Uuid; rows_seen: number; rows_written: number; rows_duplicate: number; rows_refused: number; outcomes: ImportOutcome[]; proposals: ImportProposal[]; /** * IMPORT-2. The columns this call had to declare because the run never took * the `importDeclareColumns` step. Normally empty: declaring is its own step. */ columns_added?: ImportProposal[]; /** * IMPORT-2. FALSE means the batch could not be written as one statement — one * row of it was refused by a guard that refuses the whole statement — so every * row was written on its own instead and the refusals below name the rows that * could not be. The answers are identical either way; this says what it cost. */ one_statement?: boolean; /** How long THIS call's writing phase took, and what that was a row. */ write_ms?: number; ms_per_row?: number; /** * IMPORT-2. How many rows the NEXT call should carry, measured from this one: * the store aims at two seconds of writing, which leaves a database that gets * 50% busier between two calls still inside the three seconds a person should * wait. Send this many next. Ignoring it costs correctness nothing. */ rows_per_call?: number; } /** IMPORT-2. What `importDeclareColumns` made, as its own step of the wizard. */ interface ImportColumnsDeclared { table_id: Uuid; columns_added: ImportProposal[]; /** Header -> Field key, including the headers that already named a column. */ mapping: Record; message: string; } interface ImportFinished { import_id: Uuid; state: "finished"; rows_seen: number; rows_written: number; rows_duplicate: number; rows_refused: number; columns_offered: number; columns_added: number; columns_ignored: number; proposals: ImportProposal[]; message: string; } /** * The rows a person clicks through to. `capped` and `note` are the store saying * out loud which part of the total it can still show — a number with nothing * behind it is the thing this shape exists to prevent. */ interface ImportReport { import_id: Uuid; table_id: Uuid; state: "refused" | "duplicate"; rows: Array>; kept: number; total: number; capped: boolean; note: string | null; } interface ImportRun { import_id: Uuid; table_id: Uuid; format: "csv" | "xlsx"; source_name: string | null; file_hash: string | null; dedupe_key: string | null; policy: ImportPolicy; state: string; rows_seen: number; rows_written: number; rows_duplicate: number; rows_refused: number; columns_offered: number; opened_at: Timestamp; finished_at: Timestamp | null; created_by: Uuid | null; } /** DOOR-19. An address a Table owns, and what has arrived at it. */ interface InboundAddress { inbound_id: Uuid; table_id: Uuid | null; channel: string; address: string; source: string | null; enabled: boolean; last_received_at: Timestamp | null; created_at: Timestamp; received: number; } interface InboundDeclared { inbound_id: Uuid; table_id: Uuid; address: string; channel: string; enabled: boolean; /** * THE ONE THING THAT COULD FAIL SILENTLY, SAID OUT LOUD. The address is real * and stored the moment this comes back; whether mail REACHES it is a fact * about DNS that no door can make true. Show this sentence wherever the * address is shown. */ mail_route: string; message: string; } /** The four values of "Shown to", in the ladder's order. */ declare const SHOWN_TO_VALUES: readonly ["only_me", "my_team", "everyone", "everyone_on_ai_matrx"]; type ShownTo = (typeof SHOWN_TO_VALUES)[number]; /** THE WORDS (access ladder § Words, row "Shown to"). One label per value, used by every screen. */ declare const SHOWN_TO_LABEL: Record; declare function isShownTo(word: unknown): word is ShownTo; /** * Does a Table's Level give its records a "Shown to"? (Access ladder § Words: "Private and * Confidential records have no 'Shown to'".) Absent or unknown level = yes, as before. The ONE test * every screen asks before it draws the control, so a locked table never shows a control that does * not exist for it. */ declare function levelHasShownTo(level: unknown): boolean; /** What `custom.table_row_defaults` answers: the table's own choices, what applies, and what may change. */ interface TableRowDefaults { /** The table's own choice; null = the organization's default. */ shown_to: ShownTo | null; published_to_web: boolean | null; indexed: boolean | null; effective: { shown_to: ShownTo; published_to_web: boolean; indexed: boolean; }; allowed_shown_to: ShownTo[]; available: { shown_to: boolean; published_to_web: boolean; indexed: boolean; }; can_change: boolean; } /** What `custom.table_row_defaults_set` takes. A key left out is left alone; null = back to the default. */ interface TableRowDefaultsPatch { shown_to?: ShownTo | null; published_to_web?: boolean | null; indexed?: boolean | null; } /** One pass of `custom.table_row_defaults_apply`. `done: false` → call again with `next_after`. */ interface RowDefaultsApplied { table_id: Uuid; seen: number; changed: number; kept_by_hand: number; done: boolean; next_after: Uuid | null; /** The store's own sentence. Show it verbatim. */ sentence: string; } /** What `custom.record_row_controls` answers for one row. */ interface RecordRowControls { shown_to: { value: ShownTo | null; effective: ShownTo; type_default: ShownTo | null; table_default: ShownTo | null; allowed: ShownTo[]; is_child: boolean; can_change: boolean; legacy_only_me?: boolean; }; published_to_web: { available: boolean; value: boolean; table_default: boolean | null; can_change: boolean; }; indexed: { available: boolean; value: boolean | null; table_default: boolean | null; effective: boolean; can_change: boolean; }; } /** * WHO MAY CHANGE A ROW'S "Shown to" (NOT its cells: a cell needs Editor on the table), in one sentence — the store's rung (`platform.shown_to_state`: * the person who made it, or someone with full access to it). Said where the control would be. */ declare const SHOWN_TO_WHO_MAY_CHANGE = "Only who added this row, or an admin, can change who sees it."; /** The sentence beside the two web switches while the store does not carry them yet. */ declare const ROW_WEB_NOT_READY = "Not available for table rows yet."; /** What `custom.form_archive` and `custom.form_restore` answer with. */ interface FormArchiveAnswer { archived: boolean; changed: boolean; form_id: Uuid; kind: "form" | "booking"; title: string | null; archived_at?: Timestamp | null; /** The store's own sentence. Show it verbatim. */ sentence: string; } /** One row of `custom.forms_archived` — a form or a booking page that was put away. */ interface ArchivedForm { form_id: Uuid; table_id: Uuid; table_name: string | null; kind: "form" | "booking"; title: string | null; slug: string; archived_at: Timestamp; archived_by: Uuid | null; table_archived: boolean; can_restore: boolean; } /** One row of `custom.checklist_templates_archived`. */ interface ArchivedChecklistTemplate { template_id: Uuid; name: string | null; about_table_id: Uuid | null; about_table: string | null; steps: number; archived_at: Timestamp; archived_by: Uuid | null; table_archived: boolean; } /** What an archived form, booking page or checklist is called in a list. */ declare const BUILT_KIND_WORD: Record<"form" | "booking" | "checklist", string>; /** What `tableRowLabel` / `tableRowLabelSet` answer. */ interface TableRowLabel { /** The formula as it was typed, or null when the table names its records by its title column. */ formula: string | null; /** The single-column label the table falls back to (its `title_field` key). */ title_field: string | null; /** Which of the two names records now: the formula, or the title column. */ effective: "formula" | "column"; /** The type the formula answers (text, number, ...), when it still reads. */ result_type: string | null; /** False when the stored formula text no longer parses against the table's columns (a column was renamed or retired). */ valid: boolean; /** The parser's sentence when `valid` is false. */ error: string | null; /** Whether the caller may set or clear it (the table's maker, or someone with full access). */ can_change: boolean; } /** The longest formula the store keeps. */ declare const ROW_LABEL_FORMULA_MAX_LENGTH = 2000; /** True when a value handed to `tableRowLabelSet` clears the formula (null, empty, or only spaces). */ declare function clearsRowLabel(formula: string | null | undefined): boolean; /** Where a question is asked. */ type DrillSource = { kind: "table"; id: Uuid; } | { kind: "entity"; token: string; }; /** Who a question is asked for (the access lanes, applied to counting). */ type DrillLane = "mine" | "organization" | "platform"; type DrillGrain = "hour" | "day" | "week" | "month" | "quarter" | "year"; type DrillMeasureOp = "count" | "count_distinct" | "sum" | "avg" | "min" | "max" | "median" | "percentile" | "rate" | "filled" | "empty" | "ratio" | "sum_of" | "span"; /** * The operations a question may ask ad hoc (`{op, of}`); a ratio, a percentile and a run rate are * declared, never ad hoc (each needs more than a column: its parts, its p, its per). */ type DrillAdHocOp = Exclude; /** * A color a choice keeps on every chart and share bar (lane DRILL-PARITY-LAST): one of the package's * semantic chart tokens (light and dark), never a raw color — the old usage page's origin colors. */ type DrillChartToken = "--matrx-chart-1" | "--matrx-chart-2" | "--matrx-chart-3" | "--matrx-chart-4" | "--matrx-chart-5" | "--matrx-chart-6" | "--matrx-chart-7" | "--matrx-chart-8" | "--matrx-chart-9" | "--matrx-chart-10" | "--matrx-chart-other"; /** Every chart token a choice may name (the validator's list). */ declare const DRILL_CHART_TOKENS: readonly DrillChartToken[]; /** A value a Measure's own `where` narrows by: a value, null ("not set") or a list of them. */ type DrillMeasureWhereValue = string | number | boolean | null | Array; /** An axis a question can cut by. */ interface DrillDimension { key: string; label: string; /** The column (fact) or `join.column` it reads. */ from: string; kind: "choice" | "relation" | "boolean" | "text" | "time"; cardinality?: "low" | "medium" | "high"; /** A code's words; `color` keeps the value one chart token on every chart and share bar. */ choices?: Array<{ value: string; label: string; color?: DrillChartToken; }>; /** A relation's target: a registry token (standard) or a Table id (custom). */ relation?: { token?: string; table_id?: Uuid; person?: boolean; }; /** A time's grains, coarsest first. */ grains?: DrillGrain[]; /** The words the empty group reads ("No embedding (nothing new to embed)"); a screen's own default otherwise. */ empty_label?: string; /** * What the Dimension groups by, at most 140 characters (lane DRILL-FLIP-FIXES, the Spend cut tables' * explanations): a screen shows it as the column header's tooltip, never as text on the page. */ description?: string; /** * WHAT A LEVEL OF IT LOOKS LIKE (lane DRILL-LEVELS) — the same shape as the design-system's * `MatrxDrillLevel`; judged by platform.drill_definition_problems and handed to a screen by * drill_describe. Absent = the screen infers it. */ level?: DrillLevel; } /** * A DIMENSION'S LEVEL PROFILE (lane DRILL-LEVELS): what a drilled value of it offers and says. * breakouts — Dimension references (`at:day` for a time) offered as one-click "By …" chips, in order; * `:` names a SIBLING definition's Dimension (`ai_usage_executions:conversation`): * the screen switches the source there carrying the trail (lane DRILL-PRIMITIVE-2) * show — the Measures shown while grouping by it * attributes — Dimension keys each value holds ONE of (a conversation's agent, a model's provider): * columns while grouping by it, and the drilled value's header * records — the columns "See these records" shows at this level (a subset of `records.columns`) */ interface DrillLevel { breakouts?: string[]; show?: string[]; attributes?: string[]; records?: string[]; } /** The longest a Dimension's or a Measure's `description` may be (a tooltip's budget). */ declare const DRILL_DESCRIPTION_MAX = 140; /** A number a question can show. */ interface DrillMeasure { key: string; label: string; op: DrillMeasureOp; /** * The column it reads (absent for `count`, `ratio` and `sum_of`). A `max` / `min` of a time column * is a MOMENT ("Last active"): it declares `unit: "time"`, is never additive, and no threshold reads it. */ of?: string; /** * `ratio` only: (the sum of these Measures) / (the sum of `den`), recomputed for every group, Other * and the total from their parts (never averaged). Each part is a `sum` or `count` Measure of the * same definition (optionally narrowed by its own `where`), or another ratio — e.g. cost per request * = `{op: "ratio", num: ["cost"], den: "requests"}`; cache-hit rate = `{num: ["hits"], den: ["hits", * "calls"]}`; the enrichment multiplier = `{num: ["avg_enriched"], den: "avg_plain"}` (two ratios). */ num?: string[]; /** * `sum_of` only: these Measures added up, recomputed for every group, Other and the total from * their own rows — total tokens = `{op: "sum_of", parts: ["tokens_in", "tokens_cached", "tokens_out"]}`. * Each part is a `sum` or `count` that adds up, or another `sum_of`; at least two. */ parts?: string[]; /** `ratio` only: the Measure (or the list of Measures, added up) it is divided by; null where that is zero. */ den?: string | string[]; /** * `ratio` or `rate`: a multiplier, bound by the door — `1000` makes cost per character cost per * 1,000 characters; `10` makes a monthly projection "at ten times today's load". */ scale?: number; /** `percentile` only: the fraction, above 0 and below 1 (`0.9` = p90). Recomputed for Other and the total from their own rows. */ p?: number; /** * `rate` only: the run rate's period. A run rate is the window's sum of `of` ÷ the window's length * × one `per` ("day", or "month" = 30 days — the old KG projection's rule). A question showing one * must have a window with a start; a window reaching past now counts only the time that has passed. */ per?: "day" | "month"; /** * This Measure only, narrowed to the rows whose Dimensions hold these values (Dimension key → a * value, null or a list; never a time): "cost of successful enriched runs". Bound by the door. */ where?: Record; /** * How a screen reads the number: usd, count, tokens, ms, share, times, …; `time` = a moment (ISO); * `duration` = a length of time in milliseconds — what a `span` (`of` a time column: the latest * minus the earliest moment, never additive, recomputed for Other and the total) declares. */ unit?: string; /** Counted once per this column's value (a measure on the "one" side of a join). */ at_grain?: string; /** May it be summed across groups? */ additive?: boolean; /** * What the number counts, at most 140 characters (lane DRILL-FLIP-FIXES): a screen shows it as the * column header's tooltip — "Requests a person or an API caller made; automated runs count as Calls". */ description?: string; /** * (lane DRILL-ENGINE, 2026-10-18) How a summary-backed distinct count that is NOT exact over groups or hours is * answered exactly, definer definitions only. `per_hour`: registry token of a per-hour total (the time column, * `everyone`, this Measure's `of` column, each lane rule's column) read for an ungrouped question — exact to the * hour. `raw_of`: the records column counted distinct, the whole question read from the records relation when the * window is at most `raw_within_hours` long. Any other question showing it says "not exact". */ exact?: { per_hour?: string; raw_of?: string; raw_within_hours?: number; }; } /** A named drill path: the levels a click walks, e.g. ["created_at:year", "created_at:month"]. */ interface DrillPath { key: string; label: string; levels: string[]; } /** * A threshold on the groups of an answer (`having`). Judged on the current window, and applied * BEFORE the cut into Other: a group that misses any threshold is added into Other with the groups * past the limit, so the answer still adds up to its total; `distinct_groups` counts the groups that * met every threshold and the total row says how many did ("3 of 41 groups meet the rule …"). * Exactly one of `value` / `share_of_total` / `times_median`. */ interface DrillHaving { /** A Measure the question shows (a screen shows the number it filters on). */ measure: string; /** At least / more than; at most / less than (lane DRILL-FLIP-FIXES: "groups where cost ≤ 5"). */ op: ">=" | ">" | "<=" | "<"; /** The group's value against a number. */ value?: number; /** The group's value against this percent (0–100] of the window's total; additive Measures only. */ share_of_total?: number; /** The group's value against this multiple of the median group's value. */ times_median?: number; /** With `times_median`: the median of the groups above zero only. */ median_nonzero?: boolean; /** * The address of the drill setting that holds the line (organizations and people decide): * `drill.finding...` inside a finding. When present the setting's value * is the threshold and the number written beside it is its default. Resolved by the door through * platform.drill_knob; clients read the same value from platform.knob_snapshot. */ knob?: string; } /** A question — what a Saved view stores and the explorer's URL encodes. */ interface DrillQuestion { /** Dimension keys, outermost first; a time as `key:grain` ("created_at:week"). At most four. */ by?: string[]; /** One Dimension drawn as the columns of a pivot. */ across?: string; /** Measure keys, or an ad hoc `{op, of}` over a column the definition reads. */ show?: Array; /** * The store's own filter shape: a scalar is an equality, `null` is "not set", a list is any of * them, `{from, to}` is a half-open range; `key:grain` = one period by its label. */ where?: Record; /** * A custom Table only: the view's Rule expression, which must hold beside `where` * (custom.record_drill ANDs the two). A standard source refuses it. */ rule?: RuleExpression; window?: { key?: string; preset?: string; from?: string; to?: string; }; compare?: "previous_period" | "same_period_last_year" | { against: "previous_period" | "same_period_last_year" | "range"; period?: DrillGrain; from?: string; to?: string; baseline?: { from: string; to: string; }; to_date?: boolean; }; sort?: { key: string; direction?: "asc" | "desc"; }; /** Groups per level before Other; the knob's value when absent, never above the page ceiling. */ limit?: number; lane?: DrillLane; /** The drill path a screen's default click follows. */ path?: string; /** Thresholds on the groups, applied before the cut into Other (see DrillHaving). */ having?: DrillHaving[]; /** Records only: the page's start. */ offset?: number; /** Records only: which columns a record shows (a definer definition's records: of its declared columns). */ columns?: string[]; } /** A definition's built-in Saved view: read-only; "Save a copy" makes a person's own. */ interface DrillView { key: string; label: string; question: DrillQuestion; /** * AT A GLANCE (lane DRILL-FLIP-FIXES, the Spend Explorer's costliest requests): Dimensions each group * of the view's outer grouping has ONE value of — a request's person, agent, model — shown as text * columns beside its numbers, no click required. A screen reads them by asking the same door with * the group and these Dimensions (the group's values listed), so they obey the same lane and filters. * A group holding several values of one says so. */ attributes?: string[]; /** * How the view's chart draws (lane DRILL-FLIP-FIXES, the CX tab's input vs output tokens): `stack` * names two or more Measures that add up, drawn as one stacked bar per period (no split). */ chart?: { stack?: string[]; }; } /** * A finding ("dig here"): a question whose `having` thresholds are the rule. Its lines are settings * at `drill.finding...`, seeded from `knobs` by the drill sync; a * threshold that reads one writes the setting's default beside it. A finding with no group says so. */ interface DrillFinding { key: string; label: string; question: DrillQuestion; knobs: Record; } /** * The records behind a DEFINER definition's numbers: a registry relation carrying the fact's column * names (e.g. `bucket`), read by the door definer with the SAME filter compiler and the SAME lane * rule as the number, for a window (required), and never past the number's `as_of` (rows created * after it are not listed). The number and its records are the SAME counted rows, equal as of the * count (`as_of`); a value that changes afterwards reaches both at the next count. * * An INVOKER definition may declare records too (lane DRILL-GAPS): they are its own rows (`fact` is * the definition's own fact), read as the person through the table's row security, among the columns * the definition already reads — so a screen shows "the recent runs" without a host link. */ interface DrillRecords { /** The registry token of the records relation (e.g. "ai_usage_executions"). */ fact: string; /** The columns a record shows, in order. */ columns: string[]; /** The words a person reads for a column, where its name would mislead ("Request's top model"). */ labels?: Record; /** * What ONE record is, singular ("execution", "log line"): the noun "See these records" and their * count read in. REQUIRED when the records are another relation's rows (`fact` is not the * definition's own fact: an hourly summary's records are its executions, never "hours"); optional * otherwise, where the definition's own grain ("one row per deal") already names it. */ noun?: string; } /** The address of a finding's setting: `drill.finding...`. */ declare function drillFindingKnob(definition: string, finding: string, knob: string): string; interface DrillDefinition { key: string; label: string; source: DrillSource; /** One sentence: "one row per agent". */ grain: string; lanes: DrillLane[]; dimensions: DrillDimension[]; measures: DrillMeasure[]; paths: DrillPath[]; detail: { columns: string[]; open?: string; }; default: DrillQuestion; /** Keys that came from inference, so a screen can say so. */ inferred?: string[]; mode?: "invoker" | "definer"; /** Honest notes about what inference could not offer, in words. */ says?: string[]; /** Built-in Saved views, listed first and read-only. */ views?: DrillView[]; /** Findings ("dig here"), answered by the same door. */ findings?: DrillFinding[]; /** The records relation (what "See these records" reads): a definer definition's declared relation, or an invoker definition's own rows. */ records?: DrillRecords; /** * The drill setting (minutes) after which the definition's `as_of` is old enough for a screen to say * "the schedule is behind" — null when the definition is counted live. */ stale_after_knob: string | null; } /** One row of an answer. */ interface DrillAnswerRow { /** `group` a shown group · `other` every group past the cap · `total` the whole answer (or a pivot column's total). */ kind: "group" | "other" | "total"; /** Each asked Dimension -> its value (a period by its label). A pivot cell also carries the `across` key. */ groups: Record | null; measures: Record | null; row_count: number; prior_groups: Record | null; prior_measures: Record | null; prior_row_count: number | null; delta: Record | null; compare: Record | null; /** How many groups there were before the cap (current window). */ distinct_groups: number | null; /** Relation Dimensions' labels, read as the person through the target's own row security. */ labels: Record; /** The answer's honest sentences (the cut, a column past its cap, a name that could not be read). */ says: string | null; /** The moment a summary fact counted through (ISO); null when counted live from the table. */ as_of: string | null; } interface DrillAnswer { rows: DrillAnswerRow[]; /** Every sentence the rows carry, once. */ says: string[]; /** The total row (kind total, no group). */ total: DrillAnswerRow | null; /** The moment the answer counted through (the rows' `as_of`); null when counted live. */ as_of: string | null; } /** The records behind a number ("see these records"): the same filter, read as the person. */ interface DrillRowsPage { total: number; limit: number; offset: number; rows: Array>; next_offset?: number; columns?: string[]; says?: string; /** The moment these records are cut at — the number's `as_of`; null when read live. */ as_of: string | null; /** * A definer definition's records, first page only: every Measure that adds up (sum, count, ratio) * summed over the SAME filter — what these records add up to, which is the number they open. */ measures?: Record; } /** * A DECLARED drill definition (a `*.drill.ts` file): an override of one standard table (its key * IS that table's registry token) or a fact over several tables (a key of its own). The one sync * script (scripts/drill-sync.ts) compiles each into `platform.drill_def__()`; the store's * validator, platform.drill_definition_problems, judges it at sync, at census and on every ask. */ interface DrillDeclaration { key: string; label: string; grain: string; /** The fact's registry token. */ fact: string; /** invoker (default): the fact's own row security decides. definer: the lane rules below do. */ mode?: "invoker" | "definer"; lanes: DrillLane[]; /** invoker: which columns narrow the organization and mine lanes. */ lane_columns?: { organization?: string; mine?: string; }; /** definer ONLY, and required for every lane: a closed vocabulary, never SQL. */ lane_rules?: { mine?: { column: string; }; organization?: { column: string; rule: "member" | "admin"; }; platform?: { rule: "super_admin"; }; }; /** Star joins along a real foreign key, many-to-one only. `from` is a fact column or `join.column`. */ joins?: Array<{ as: string; token: string; from: string; }>; dimensions: DrillDimension[]; measures: DrillMeasure[]; paths?: DrillPath[]; detail?: { columns: string[]; open?: string; }; default?: DrillQuestion; /** An override may hide inferred keys. */ hide?: string[]; /** Built-in Saved views (every question judged against this definition by the validator). */ views?: DrillView[]; /** Findings: questions with thresholds whose lines are settings. */ findings?: DrillFinding[]; /** * definer: the relation its records are read from (the same filter and lane rule); invoker: its own * fact, read as the person (lane DRILL-GAPS). */ records?: DrillRecords; /** definer ONLY, and only over a fact that keeps a `_watermark`: the stale line's setting. */ stale_after_knob?: string; } /** Identity with a type: the one way a `*.drill.ts` file declares a definition. */ declare function declareDrillDefinition(declaration: T): T; /** Decode the door's rows into an answer: the total row found, every sentence once. */ declare function drillAnswerOf(rows: DrillAnswerRow[]): DrillAnswer; /** The door's name for each call (schema `platform`). */ declare const DRILL_SCHEMA = "platform"; declare const DRILL_DOORS: { readonly describe: "drill_describe"; readonly ask: "drill_ask"; readonly rows: "drill_rows"; }; /** `custom.decoration_colors()` — the palette, in the order an uncolored choice takes. */ type DecorationColor = "slate" | "green" | "amber" | "red" | "blue" | "violet" | "teal" | "orange" | "yellow" | "pink" | "brown" | "gray"; /** `custom.decoration_rule_ops()` — the tests a color rule may ask. */ type DecorationRuleOp = "is" | "is_not" | "contains" | "is_empty" | "not_empty" | "is_true" | "is_false" | "gt" | "gte" | "lt" | "lte"; type DecorationTarget = "row" | "cell"; interface DecorationRule { /** Given by the store when omitted; keep it to edit the rule in place. */ id?: string; /** The Field id the rule reads. */ field: Uuid; op: DecorationRuleOp; /** Compared as text for is / is_not / contains, as a number for gt / gte / lt / lte. */ value?: string | number; color: DecorationColor; target: DecorationTarget; } interface TableDecorations { version: 1; colors: DecorationColor[]; rule_ops: Array<{ op: DecorationRuleOp; takes_value: boolean; label: string; }>; color_by?: { field: Uuid; target: DecorationTarget; }; rules: DecorationRule[]; /** Hand highlights: record id → color. */ rows: Record; /** Hand highlights: Field id → color. */ columns: Record; /** Hand highlights: record id → Field id → color. */ cells: Record>; /** What stopped painting because its column is gone — said, never silently dropped. */ stale: Array<{ part: string; field?: Uuid; rule_id?: string; record_id?: Uuid; says: string; }>; } /** * ONE LEAF of a Table's colors — the older door's own path shape * (`public.udt_set_table_style`), so two people highlighting two rows never overwrite each other. */ type DecorationPath = ["color_by"] | ["rules"] | ["rows", Uuid] | ["columns", Uuid] | ["cells", Uuid, Uuid]; type GridLayoutMode = "auto" | "fit" | "scroll"; /** * Airtable's four heights are short (1 line), medium (2), tall (4), extra_tall (6). `compact` and `normal` * are the older words and still read: both are a one-line row. */ type GridRowHeight = "compact" | "short" | "normal" | "medium" | "tall" | "extra_tall"; /** A saved view's own grid choices, kept at `definition.grid` (custom.view_declare judges them). `definition.layout` is the view's KIND (grid / kanban / calendar / gallery), never these. */ interface ViewLayout { mode?: GridLayoutMode; row_height?: GridRowHeight; freeze_first_column?: boolean; wrap?: boolean; /** Field id → pixels, 60–1200. */ widths?: Record; } interface GridLayout { layout: Required> & { fit_max_columns: number; widths?: Record; }; /** Where each choice came from. */ source: Record; view_id: Uuid | null; /** A stored choice the grid cannot honor, named. */ refused: Array<{ from: "organization" | "view"; choice: string; value: unknown; says: string; }>; } type RowActionStep = { field: Uuid; set: "value"; value: unknown; } | { field: Uuid; set: "clear"; } /** Worked out by the store on each record: the older grid's formula text, or an expression. */ | { field: Uuid; set: "compute"; formula_text?: string; expr?: Record; }; interface RowAction { /** Given by the store when omitted. */ id?: Uuid; name: string; kind?: "update" | "agent"; color?: DecorationColor | null; icon?: string; /** Ask before running. */ confirm?: boolean; steps?: RowActionStep[]; /** `agent` only: what the record chat opens with. */ prompt?: string; /** On read: whether custom.action_run can run it. */ runnable?: boolean; } interface ActionRunResult { action_id: Uuid; action: string; table_id: Uuid; ran: number; records: Array<{ record_id: Uuid; version: number; }>; } /** One record (and, when it can be named, one field) that refused a row action. */ interface ActionRefusal { record_id: Uuid; record: string | null; field_id: Uuid | null; field: string | null; code?: string; says: string; } /** * Every refusal of an action run, read off the store's refusal. A run that any record refused * changed NOTHING; this is the list a screen shows so the person can fix those and run again. */ declare function actionRefusals(error: RecordsError): ActionRefusal[]; type FormulaParse = { ok: true; text: string; /** The store's expression shape — what `config.expr` holds. */ expr: Record; references: Array<{ name: string; field_id: Uuid; }>; result_type: "number" | "text" | "boolean" | "date" | "unknown"; } | { ok: false; text: string; error: string; position: number; }; /** NOTION-PROPS: `formulaPreview` — the parse, and what it works out to on one record. */ type FormulaPreview = FormulaParse & { /** The answer on `record_id`, as the formula column would show it; null when nothing was sampled. */ value?: unknown; record_id?: Uuid | null; /** It parses, and could not be worked out on that record (a division by zero, a bad date …). */ error?: string; }; type RecordEvent = "record.created" | "record.updated" | "record.archived" | "record.restored" | "record.purged"; interface TableWebhookDeclared { webhook_id: Uuid; table_id: Uuid; target_url: string; events: RecordEvent[]; /** Shown this once: X-Matrx-Signature is sha256=HMAC-SHA256(secret, body). */ secret: string; says: string; } interface TableWebhook { webhook_id: Uuid; target_url: string; description: string | null; events: RecordEvent[] | null; is_active: boolean; last_attempt_at: string | null; last_success_at: string | null; consecutive_failures: number; owner_id: Uuid; created_at: string; } /** One page of `custom.io_export`: exactly what the read door serves this person. */ interface IoExportPage { table_id: Uuid; token: string; columns: string[]; rows: Array>; /** A column the store withheld from this reader, with the store's reason. */ withheld: Record; page: { requested: number; returned: number; ceiling: number; next: number | null; }; choices: unknown; } /** * THE .XLSX OF AN EXPORT PAGE. Numbers stay numbers and booleans stay booleans in the sheet; a * column the store withheld is NAMED on a second sheet with the store's reason, so a person * never mistakes a withheld column for an empty one. `labels` maps a column key to its header * (the Field's label); a key with no label is its own header. */ declare function exportPageXlsx(page: IoExportPage, labels?: Record, sheetLabel?: string): Promise; /** * THE EVENT-SOURCE KEY of a record-store table: `record:
` — what its changes carry on the * activity spine, what a webhook's `resource_types` names and what a schedule's `event` trigger * listens for (`custom.record_source_key` in the store). The store's token is `record`; the older * key `custom_record:
` named the retired tier-2 table and is never written. */ declare function recordSourceKey(tableId: Uuid): string; /** * The table a stored event-source key names, or `null` when the key is not a record-store key. * Reads the older `custom_record:
` too, for rows saved before the key moved (the store * reads both for one release: `custom.record_source_table`). */ declare function recordSourceTable(key: string | null | undefined): Uuid | null; /** A record-store row event, as the activity spine carries it (and a schedule matches it). */ type RecordChangeAction = RecordEvent; /** * THE SCHEDULE TRIGGER for "when a row of this record-store table changes, run …" — the same * `event` trigger the older store's tables use, pointed at this store's events: * `entity_type` `record:
` ({@link recordSourceKey}), actions `record.*`, `changed_fields` * by column KEY. * `scheduler.sch_match_event` matches it against the event * `custom._record_events_to_activity` writes (GRID-PRIMITIVES G8), so one matcher serves both * stores. Omit `actions` to hear every change; omit `changedFields` to hear every column. */ declare function recordChangeTrigger(tableId: Uuid, options?: { actions?: RecordChangeAction[]; changedFields?: string[]; }): { type: "event"; entity_type: string; table_id: Uuid; actions?: RecordChangeAction[]; changed_fields?: string[]; }; /** * One entry of `custom.table_list_everywhere` — `public.get_user_tables`' own shape plus * `store`, so a picker swaps one call and keeps its code. `store` says which home opens it. */ interface TableListEntry { id: Uuid; table_name: string; description: string | null; version: number | null; user_id: Uuid | null; is_public: boolean | null; visibility: string | null; organization_id: Uuid; created_at: string; updated_at: string | null; last_activity_at: string | null; row_count: number; field_count: number; store: "older" | "records"; } /** * One row of `custom.data_home_tables` — every table the person reaches, with the store's own * answers for the organization's name, who made it, and whether the app keeps it. `system` is a * system organization's table (reached on the admin lane only). */ interface DataHomeTable { table_id: Uuid; table_name: string; organization_id: Uuid; organization_name: string; member: boolean; visibility: string | null; updated_at: string | null; mine: boolean; shared_with_me: boolean; platform_owned: boolean; kind: string | null; team?: boolean; system?: boolean; created_by?: Uuid | null; } /** * One row of `custom.admin_all_tables` — a table in ANY organization, for the admin section only * (the store's walls admit a platform admin to system organizations alone, so `DataHomeTable` cannot * list the rest). Answered only on the admin lane for a platform admin; any other caller is refused. */ interface AdminTable { table_id: Uuid; table_name: string; organization_id: Uuid; organization_name: string | null; updated_at: string | null; platform_owned: boolean; system: boolean; } /** Where a synced table comes from. `external_id` is the source's own id (a spreadsheet id). */ interface TableSyncSource { provider: string; external_id: string; tab_id?: string | null; synced_at?: string | null; } /** What `custom.table_sync` takes: the source, its columns, and every row it has NOW. */ interface TableSyncSpec { source: TableSyncSource; table_name: string; columns: string[]; rows: { ref: string; values: Record; }[]; } interface TableSyncResult { table_id: Uuid; created: boolean; fields_added: number; rows_inserted: number; rows_updated: number; rows_archived: number; source_system: string; } /** * A sheet tab's cells (header row first) → the spec `custom.table_sync` takes. Every row is * named by its sheet row number (`row-2` is the first row under the header) unless * `refColumn` names a column holding a stable id; a blank or repeated ref is refused here, so * the store never sees two rows claiming to be the same source row. Wholly blank rows are * skipped. Pure. */ declare function sheetSyncSpec(args: { source: TableSyncSource; tableName: string; cells: readonly (readonly (string | number | boolean | null | undefined)[])[]; refColumn?: string; }): TableSyncSpec; /** * What `custom.table_copy_sync` takes (lane SYNC-AIRTABLE): a table synced from another table's * view — read as the caller, so the copy holds only what that view shows them. Lands through * `custom.table_sync` in the caller's active organization (`home_id` defaults to its data home). */ interface TableCopySpec { source_organization_id: Uuid; source_table_id: Uuid; view_id?: Uuid | null; table_name?: string | null; home_id?: Uuid | null; /** `on_change` (default): the store's time tick refreshes the copy when the source changes. */ auto_refresh?: "on_change" | "off"; } /** Every `custom.table_sync` answer also says when the sync happened and what it could not do. */ interface TableSyncAnswer extends TableSyncResult { rows_restored?: number; rows_kept_archived?: number; rows_total?: number; warnings?: string[]; synced_at?: string; } /** How often a synced table refreshes on its own (`sync_source.auto_refresh`). */ type SyncAutoRefresh = "off" | "hourly" | "daily" | "on_change"; /** What `custom.table_sync_settings_set` changes on one synced table. */ interface TableSyncSettings { auto_refresh?: SyncAutoRefresh; /** Google Sheets through a connected account only: edits go back to the sheet. */ write_back?: boolean; } /** The `sync_source` a synced Table carries (what the store wrote, read back). */ interface TableSyncSourceDocument extends TableSyncSource { label?: string | null; url?: string | null; access?: "link" | null; header_row?: number | null; set_by?: Uuid | null; auto_refresh?: SyncAutoRefresh | null; write_back?: boolean | null; source_organization_id?: Uuid | null; due_at?: string | null; } /** One row of `custom.table_row_counts`: the rows of that Table this reader may see. */ interface TableRowCount { table_id: Uuid; visible_rows: number; } /** * One row of `custom.record_headers`: the row's own columns the document door does not answer. * `created_by` is the caller's own id on rows the caller made and null on every other; `mine` * says which. */ interface RecordHeader { id: Uuid; table_id: Uuid; created_at: string; updated_at: string; version: number; deleted_at: string | null; mine: boolean; created_by: Uuid | null; } /** * Fold `table_row_counts` onto a table list: `row_count` set on every Table the counts name. * A Table the counts do not name keeps what it had — the store answers no row for a Table this * reader may not know, and that is never turned into a zero. Pure. */ declare function withRowCounts(tables: readonly T[], counts: readonly TableRowCount[] | null | undefined): T[]; /** What `custom.view_record_order_set` answers. */ interface ViewOrderSet { view_id: Uuid; table_id: Uuid; order: "manual"; named: number; positioned: number; } /** * The order to send after a drag: `order` with `moved` taken out and put at `toIndex` (clamped). * The store keeps every row the call does not name in its old place after the named ones, so a * screen may send only the rows it shows. Pure. */ declare function moveInOrder(order: readonly Uuid[], moved: Uuid, toIndex: number): Uuid[]; /** * What `custom.table_share_outside_grant` answers. `person` says which path ran: * `outside_account` (granted at once and notified — never made a member), `member` (a plain * share), `no_account` (the invitation door's own answer, with its link and delivery). */ interface OutsideShareAnswer { granted: boolean; invited: boolean; joined: boolean; person: "outside_account" | "member" | "no_account"; email: string; table: string; level: string; level_label: string; say: string; user_id?: Uuid; permission_id?: Uuid; invitation_id?: Uuid | null; accept_path?: string | null; delivery_say?: string | null; } /** A value an action writes: a literal, or a reference the store resolves at run time. */ type AutomationValue = string | number | boolean | null | { from: Uuid; } | { now: true; } | { in_days: number; } | { me: true; } | { clear: true; } | unknown[] | Record; /** `{ [field id]: value }` — every key is a live Field of the table the step writes to. */ type AutomationValues = Record; /** A filter in the store's one rule shape; inside an `edit_rows.where`, `{ trigger: }` means that value on the triggering row. */ type AutomationFilter = Record; /** Days of the week as the store counts them: 1 Monday … 7 Sunday. */ type AutomationWeekday = 1 | 2 | 3 | 4 | 5 | 6 | 7; type AutomationTrigger = { on: "row_added"; } | { on: "property_edited"; field: Uuid; to?: unknown; } | { on: "form_answered"; } /** * AUTOMATIONS-AIRTABLE: Airtable's "When record updated" — any of `fields` edited (an empty list or none = any * property). */ | { on: "record_updated"; fields?: Uuid[]; } /** * AUTOMATIONS-AIRTABLE: Airtable's "When record matches conditions" (the spec's `condition`, any depth) or, with * `view_id`, "When record enters view" (that saved view's own filter, read at run time). Fires when a row COMES TO * match — added matching, or edited from not matching to matching — never again while it stays matching. */ | { on: "record_matches"; view_id?: Uuid; } /** AUTOMATIONS-AIRTABLE: a press of this button column (`custom.button_press`) on a row runs it on that row. */ | { on: "button_pressed"; field: Uuid; } /** * AUTOMATION-TIME: a date or date-time property reaching now, `offset_days` before (negative) or after * (positive), at `at` (HH:MM, default 09:00) in the person's own zone, else the organization's. Fires once per row * per date value; the date changing re-arms it. */ | { on: "date_arrives"; field: Uuid; offset_days?: number; at?: string; } /** * AUTOMATION-TIME: the table acts with no row, every day / week (`weekday`) / month (`day`, 1-31, a short month * runs on its last day) at `at`. `set`, a person property, `{from}` and `{trigger}` have no row to read and are * refused; `add_row` is the repeating row and `edit_rows` the scheduled bulk edit. */ | { on: "schedule"; every: "day" | "week" | "month"; at?: string; weekday?: AutomationWeekday; day?: number; } /** * AT-AUTO2: every N minutes (5, 10, 15, 30) or every N hours (1, 2, 3, 4, 6, 8, 12) at `minute` past. Saved switched * OFF; the person switches it on (no blind paid automations). */ | { on: "schedule"; every: "minutes"; minutes: 5 | 10 | 15 | 30; } | { on: "schedule"; every: "hour"; hours?: number; minute?: number; } /** * AT-AUTO2: an inbound webhook. The store makes the automation's `webhook_secret`; the URL is * `automationWebhookUrl(serverBase, automation)`. The JSON body is `{{trigger.body.…}}` to every step. */ | { on: "webhook"; } /** * W2-E: a new email in the OWNER's own connected Gmail matching `query` (Gmail search syntax; none = every new * email). Like every connected-app trigger it has no row, is saved switched OFF, and once switched on the server * checks the account on the automation's own personal schedule (`automationAppTriggerTerms`); each new item starts one * run with the item as `{{trigger.body.…}}` (`subject`, `from`, `to`, `snippet`, `date`, `link`, `id`). */ | { on: "gmail_received"; query?: string; } /** * W2-E: an event on the owner's primary Google Calendar starts (`minutes_before` its start, 0-1440) or is created. * `{{trigger.body.title}}`, `starts_at`, `ends_at`, `location`, `attendees`, `link`, `id`. */ | { on: "calendar_event"; when?: "starts" | "created"; minutes_before?: number; } /** * W2-E: a message posted in a Slack `channel` (optionally containing `contains`). The platform has no Slack sign-in * yet, so the automation says so (`app_says`) until one exists. */ | { on: "slack_message"; channel: string; contains?: string; } /** * AT-MSFT: a new email in the owner's Outlook inbox, read through their own Microsoft connection (`Mail.ReadBasic`: * envelopes only, never the body). `query` keeps it to `from:` / `subject:` terms and plain words (every term must * match; none = every new email). `{{trigger.body.subject}}`, `from`, `from_name`, `to`, `received_at`, `link`, `id`. */ | { on: "outlook_received"; query?: string; }; /** W2-E: the triggers that read the owner's connected account on a schedule (saved off; metered per check). */ declare const AUTOMATION_APP_TRIGGERS: readonly ["gmail_received", "calendar_event", "slack_message", "outlook_received"]; type AutomationAppTrigger = (typeof AUTOMATION_APP_TRIGGERS)[number]; declare function isAutomationAppTrigger(on: string): on is AutomationAppTrigger; /** W2-E: what one connected-app automation costs, read before switching it on (`GET /automations/app-triggers/terms`). */ interface AutomationAppTriggerTerms { every_minutes: number; points_per_check: number; checks_per_month: number; points_per_month: number; points_per_action: number; } /** * AT-AUTO2: a token a step's text or value may carry, resolved when the run reaches that step: * `{{trigger.}}`, `{{trigger.record_id}}`, `{{trigger.body.}}`, `{{stepN.}}` * (a find's `records` / `first` / `count` / `ids`, a script's `output.`, an added row's `record_id`), * `{{item.}}` / `{{index}}` inside a repeating group. A value that IS one token keeps its type (a list stays a list). */ type AutomationToken = `{{${string}}}`; /** AT-AUTO2: one comparison of a conditional group's path. `value` / `to` take tokens. */ interface AutomationComparison { value: unknown; is: "equals" | "not_equals" | "contains" | "not_contains" | "gt" | "gte" | "lt" | "lte" | "empty" | "not_empty" | "is_true"; to?: unknown; } type AutomationWhen = { all: AutomationComparison[]; } | { any: AutomationComparison[]; }; type AutomationAction = { do: "set"; values: AutomationValues; } | { do: "add_row"; table_id: Uuid; values: AutomationValues; } | { do: "edit_rows"; table_id: Uuid; where: AutomationFilter; values: AutomationValues; limit?: number; } | { do: "notify"; to: { person: Uuid; } | { field: Uuid; } | { author: true; }; text: string; subject?: string; link?: string; } | { do: "webhook"; url: string; } /** * AUTOMATIONS-AIRTABLE: an email through the platform's notification sender (event `custom.automation.email`) to an * address, a member, the address (or person) in a property of the row, or the automation's author. `subject` and * `body` take field tokens: `{Field name}` or `{{}}`. Nothing queued = the step fails and says why. */ | { do: "email"; to: { address: string; } | { person: Uuid; } | { field: Uuid; } | { author: true; }; subject: string; body: string; } /** AUTOMATIONS-AIRTABLE: a Slack incoming webhook (`https://hooks.slack.com/...`), message with field tokens. */ | { do: "slack"; url: string; text: string; } | { do: "agent"; prompt: string; } /** AT-AUTO2: records of a table matching a filter (tokens allowed in `const`s); later steps read `{{stepN.records}}`. */ | { do: "find"; table_id: Uuid; where?: AutomationFilter; limit?: number; } /** AT-AUTO2: change the named records (one id, a list, `{{stepN.records}}`, `{{item.id}}`). */ | { do: "update"; table_id: Uuid; record: string | string[]; values: AutomationValues; } /** AT-AUTO2: Airtable's "delete record" — we archive (restorable), never hard delete. */ | { do: "archive"; record: string | string[]; table_id?: Uuid; } /** * AT-AUTO2: JavaScript run by the server in the owner's own sandbox (never in the API process): `input` is * `inputs` resolved, `output.set(key, value)` answers `{{stepN.output.key}}`, console lines land in the run. * Metered to points. On a time trigger it is saved switched off. */ | { do: "script"; code: string; inputs?: Record; timeout_ms?: number; memory_mb?: number; } /** AT-AUTO2: if / else if / else — the first path whose `when` holds runs its steps; else `else`. */ | { do: "if"; branches: { label?: string; when: AutomationWhen; actions: AutomationAction[]; }[]; else?: AutomationAction[]; } /** * W2-E: open an issue in `repo` (owner/name) through the owner's own GitHub connection; `labels` comma-separated. * Answers `{{stepN.output.number}}` / `.url`. A retry answers the same issue. Metered per issue. */ | { do: "github_issue"; repo: string; title: string; body?: string; labels?: string; } /** * MS-WRITE: send one Outlook email as the owner (`Mail.Send`, approved once under Settings -> Integrations -> Microsoft). * `outlook_calendar_event` creates / changes / cancels an event on the owner's calendar (`Calendars.ReadWrite`; a retried * create is the same event); `teams_message` posts in the channel named by a Teams "Get link to channel" link * (`ChannelMessage.Send`) or in the chat named by a chat message's "Copy link" (`ChatMessage.Send`, AT-MSFT); work or * school accounts. Each is metered per action at `automation.app_action_points`. */ | { do: "outlook_mail"; to: string; subject: string; body: string; } | { do: "outlook_calendar_event"; action?: "create" | "update" | "cancel"; event_id?: string; title?: string; starts_at?: string; ends_at?: string; duration_minutes?: number; timezone?: string; attendees?: string; description?: string; notify?: boolean; } | { do: "teams_message"; channel_link: string; message: string; } /** * AT-SLACK: post one message in a Slack channel (name like `general` or an ID like `C0123ABC`) AS the owner, through * their own Slack sign-in (Settings -> Integrations -> Slack, user scope `chat:write`). Sent once; answers * `{{stepN.output.ts}}` / `.channel`. Metered per message at `automation.app_action_points`. The `slack` step * (an incoming-webhook URL) is separate and unchanged. */ | { do: "slack_post"; channel: string; message: string; } /** AT-AUTO2: repeat `actions` for each item of a list token (at most 200 items). */ | { do: "for_each"; list: string; actions: AutomationAction[]; }; interface AutomationSpec { name: string; trigger: AutomationTrigger; /** An optional filter on the triggering row. */ condition?: AutomationFilter | null; actions: AutomationAction[]; } /** `custom.automation_declare`: stored, or refused with one sentence per path (`trigger`, `actions.0.values.` …). */ type AutomationDeclared = { ok: true; automation_id: Uuid; table_id: Uuid; enabled: boolean; spec: AutomationSpec; /** * AUTOMATIONS-AIRTABLE: true when the trigger is a time and a step is an agent — AI on a schedule is saved * switched OFF and turned on only by `automationSetEnabled`; `says` is the store's sentence. */ ai_on_schedule?: boolean; says?: string; reused?: boolean; } | { ok: false; _errors: Record; }; type AutomationRunStatus = "ran" | "failed" | "stopped" | "waiting"; interface AutomationLastRun { run_id: Uuid; status: AutomationRunStatus; at: string; says: string | null; } interface AutomationItem { id: Uuid; /** AT-AUTO2: a webhook automation's secret, part of its URL. */ webhook_secret?: string | null; /** AT-AUTO2: the last payload its webhook received (its test run uses it). */ webhook_sample?: { body: unknown; at: string; } | null; /** W2-E: a connected-app trigger's last check, and why it could not read the account (null = it could). */ app_checked_at?: string | null; app_says?: string | null; /** W2-E: the last item a connected-app trigger handed to a run. */ app_sample?: { body: unknown; at: string; } | null; name: string; spec: AutomationSpec; enabled: boolean; archived_at?: string | null; /** AUTOMATIONS-AIRTABLE: why the store switched it off (a watched view was removed); cleared when switched on. */ disabled_reason?: string | null; created_by?: Uuid | null; created_at?: string; updated_at?: string; last_run: AutomationLastRun | null; } interface AutomationList { table_id: Uuid; automations: AutomationItem[]; } /** archive / restore / set_enabled. */ interface AutomationFlipped { ok: true; automation_id: Uuid; table_id: Uuid; enabled: boolean; archived_at: string | null; } interface AutomationStep { n: number; do: AutomationAction["do"]; /** AT-AUTO2: the repeat round (0-based) a step inside a repeating group ran in. */ index?: number; /** AT-AUTO2: a script's console lines. */ logs?: string[]; /** AT-AUTO2: a server step's answer, as text. */ output_text?: string; found?: number; status: "done" | "failed" | "waiting"; /** The store's own words. */ says: string; hint?: string | null; record_id?: Uuid; edited?: number; prompt?: string; } interface AutomationRun { run_id: Uuid; status: AutomationRunStatus; says: string | null; record_id: Uuid | null; trigger: { /** `test` for a test run (`of` is the automation's own trigger), `button_pressed` for a press. */ on: AutomationTrigger["on"] | "test"; /** AT-AUTO2: a webhook's payload. */ body?: unknown; of?: AutomationTrigger["on"]; /** A test run: whether the row met the condition (null = it could not be worked out). */ condition_met?: boolean | null; operation: string; via: string | null; changed_field_ids: Uuid[]; /** A time trigger's own facts: the moment it was due, and (date arrives) the date it read. */ fires_at?: string; date_value?: string; every?: "day" | "week" | "month"; } | null; steps: AutomationStep[]; started_at: string | null; finished_at: string | null; ran_by: Uuid | null; at: string; } interface AutomationRuns { automation_id: Uuid; runs: AutomationRun[]; /** Pass back as `cursor` for the next, older page; null at the end. */ next_cursor: string | null; } /** AUTOMATIONS-AIRTABLE: `custom.automation_test` — one run now on a picked row (none for a schedule), on or off. */ interface AutomationTested { ok: true; run_id: Uuid; automation_id: Uuid; record_id: Uuid | null; status: AutomationRunStatus; says: string | null; steps: AutomationStep[]; condition_met: boolean | null; } /** AT-AUTO2: `custom.automation_revisions` — every save of one automation, newest first. */ interface AutomationRevision { revision_id: Uuid; at: string; by: Uuid | null; note: "created" | "edited"; enabled: boolean; spec: AutomationSpec; } interface AutomationRevisions { automation_id: Uuid; table_id: Uuid; revisions: AutomationRevision[]; } /** AT-AUTO2: the public URL of a webhook automation (null until the store made its secret). */ declare function automationWebhookUrl(serverBase: string, item: Pick): string | null; /** The placement facts, as the Table document or `custom.table` carries them. */ interface TablePlacement { platform_owned?: boolean | null; kept_for?: string | null; offered_as_context?: boolean | null; foundation?: boolean | null; } /** * One row of `custom.table_facts` since SC-1 — the two lane facts it always answered * (`visibility`, `mine`) and the placement, with the sentence a person reads and where the * table is used. A store without the SC-1 door answers the first three columns only; every * placement column is then `undefined`, and the readers below treat the table as the * organization's own — exactly what that store's older screens did. */ interface TablePlacementFact extends TablePlacement { table_id: Uuid; visibility: string; mine: boolean; /** The heading a kept table is listed under: "The context system", "The choices behind your columns", … */ keeper_group?: string | null; /** One plain sentence: who keeps it and where it opens from. Null for the organization's own. */ keeper_says?: string | null; /** Where the table is used: `table` (another Table, or itself) or `scope` (a context scope). */ used_in_kind?: "table" | "scope" | null; used_in_id?: Uuid | null; /** The Table that page belongs to, when `used_in_kind` is `table`. */ used_in_table_id?: Uuid | null; } /** Whether a person's list of tables keeps this one out of sight until "Show everything". */ declare function keptOutOfSight(placement: TablePlacement | null | undefined): boolean; /** Whether the Table is part of the business's day-one data (lane 10 FD). Absent is no. */ declare function isFoundation(placement: TablePlacement | null | undefined): boolean; /** Whether the context picker may offer this Table. Absent is no. */ declare function offeredAsContext(placement: TablePlacement | null | undefined): boolean; /** * The placement facts folded onto a table list, by id. Pure — returns new objects and leaves * the list it was given alone. A table the facts do not name is returned unchanged. */ declare function withTablePlacement(tables: readonly T[], facts: readonly TablePlacementFact[] | null | undefined): Array>; /** * THE KEPT TABLES, GROUPED UNDER THEIR KEEPER, in the order each group first appears. A kept * table the store gave no heading goes under "Platform tables" — never dropped. */ declare function keptGroups>(tables: readonly T[]): Array<{ group: string; tables: T[]; }>; /** * WHERE A KEPT TABLE OPENS FROM (THE DOOR LAW): the host's own addresses for a table and for a * context scope, chosen by what the store said the table is used in. With nothing said, the * table's own page — a kept table is never a row with nowhere to go. */ declare function keptTableHref(fact: { table_id: Uuid; } & Partial, hrefs: { table: (tableId: Uuid) => string; scope: (scopeId: Uuid) => string; }): string; /** One of the person's other organizations, and whether the Table can go there. */ interface TableHomeDestination { id: Uuid; name: string; /** * The organization's web address (UI-FIX-19). A screen draws it beside a name another * destination also carries, so two organizations with one name can be told apart. `null` when * the store's answer predates it. */ slug: string | null; /** The person's role there: owner · admin · member. */ role: string; /** False when the store says the Table cannot go there; `why` says why, in its words. */ ok: boolean; why: string | null; } /** `custom.table_home`'s answer, read. */ interface TableHome { table: { id: Uuid; name: string; version: number; }; /** Where it lives, from the Table itself. */ organization: { id: Uuid; name: string; }; /** The Table's maker, or an owner/admin of its organization. */ mayMove: boolean; /** When `mayMove` is false: the sentence that says who can. */ whyNot: string | null; /** Sentences for what holds it where it is. Empty when nothing does. */ heldBy: string[]; /** Every other organization this person belongs to. */ destinations: TableHomeDestination[]; carries: { fields: number; records: number; inTrash: number; choiceLists: number; withIt: number; /** Tables that ride along because they live inside it or inside its rows. */ tablesInside: number; /** Links between its rows and rows that stay, which will reach back across the wall. */ linksAcross: number; }; } /** What `custom.table_move` answered when the move landed. */ interface TableMoved { to: { id: Uuid; name: string; }; from: { id: Uuid; name: string; }; tableName: string; } /** Read the store's answer strictly: an answer this cannot read is `null`, never a guess. */ declare function parseTableHome(raw: unknown): TableHome | null; /** Read `custom.table_move`'s answer; `null` when it is not a landed move. */ declare function parseTableMoved(raw: unknown): TableMoved | null; /** * What `custom.table_duplicate` answered when the copy landed (lane TABLE-ACTIONS). The copy is a * NEW Table in `organization`; `copied` counts what came with it and `notCopied` names, in the * store's own words, what a copy never carries (history, forms, portals, dashboards, rules, …). */ interface TableDuplicated { table: { id: Uuid; name: string; }; from: { id: Uuid; name: string; organizationId: Uuid; }; /** * Where the copy lives. `null` only for a copy carried on by its id (`tableDuplicateContinue` * after a reload): the carry-on door's answer names the copy, not its organization, and the * first answer that did is gone. `tableDuplicate` always fills it from its first answer. */ organization: { id: Uuid; name: string; } | null; copied: { fields: number; choiceLists: number; choices: number; views: number; records: number; withRecords: boolean; }; /** Archived columns of the source, which stay behind with it. */ archivedFieldsLeft: number; /** Archived (or quarantined) records left behind; `null` when records were not copied. */ archivedRecordsLeft: number | null; /** Records this person is not handed (the ladder, then "Shown to"); `null` when records were not copied. */ recordsNotYoursLeft: number | null; /** Columns this person may not read: copied as columns, values left empty — column name → values left behind. */ hiddenColumns: Record; /** Another person's "only me" views of the source, which stay theirs. */ privateViewsLeft: number; /** Where a reference to a row that was not copied was taken out ("view …", "column …", the table's look). */ rowReferencesDropped: string[]; /** Views whose source was "only me": copied as new views are made, still "Shown to: only me". */ viewsSharedAsNewViews: string[]; /** Views the house door for a new view refused, each with its sentence. */ viewsNotCopied: string[]; /** What moved in the source while it was copied (the copy keeps what was fixed when it started). */ changedDuringCopy: { columnsArchivedSince: string[]; rowsChangedAfterCopied: number; rowsArchivedSince: number | null; rowsAddedSince: number | null; }; notCopied: string[]; path: string; } /** One pass of a paged copy (`custom.table_duplicate` / `custom.table_duplicate_continue`). */ interface TableDuplicateProgress { tableId: Uuid; tableName: string; /** "busy": another pass of this copy is running (wait, then carry on). "failed": the source was * archived while it was copied — not handed over; discard it. "discarded": its half table was archived. */ status: "copying" | "done" | "busy" | "failed" | "discarded"; /** What the pass stopped on: "choices" | "fields" | "views" | "records" | "links" | "done". */ step: string; /** The store's sentence for busy / failed / discarded. */ sentence: string | null; /** This pass: how long before its first unit, how many units, how long in all (ms). */ pass: { setupMs: number; units: number; ms: number; } | null; choiceLists: { done: number; total: number; }; fields: { done: number; total: number; }; views: { done: number; total: number; }; /** `null` when records are not being copied. */ records: { done: number; total: number; } | null; } /** Read one pass's answer strictly; `null` when the shape is not a copy's progress. */ declare function parseTableDuplicateProgress(raw: unknown): TableDuplicateProgress | null; /** One of the caller's unfinished copies (`custom.table_copies_in_progress`). */ interface TableCopyInProgress { tableId: Uuid; tableName: string; organization: { id: Uuid; name: string; }; from: { id: Uuid; name: string; }; /** "copying" (carry it on) or "failed" (the source was archived meanwhile — discard it). */ status: "copying" | "failed"; sentence: string | null; startedAt: string | null; records: { done: number; total: number; } | null; } declare function parseTableCopiesInProgress(raw: unknown): TableCopyInProgress[]; /** Read `custom.table_duplicate`'s answer strictly; `null` when it is not a landed copy. */ declare function parseTableDuplicated(raw: unknown): TableDuplicated | null; /** The consequence of a move, said before it happens (destructive-and-expensive-actions law). */ declare function moveConsequence(home: TableHome, to: Pick): string; /** Where one agent-written value came from. Interned once per save (VAL-1). */ interface EnrichSource { kind?: string; field_id?: Uuid; model?: string; model_version?: string | null; instruction?: string; /** What was actually read, with addresses. Never invented by the model. */ read?: Array<{ url?: string; title?: string; at?: string; }>; note?: string; confidence?: number; at?: string; } /** One candidate the model did not commit to, with its rank and its own source. */ interface EnrichAlternate { value: unknown; rank: number; source?: EnrichSource | null; } /** One cell, as `custom.enrich_cells` answers it. The badge is drawn from exactly this. */ interface EnrichCell { record_id: Uuid; field_key: string; /** Whether a model owns this column at all. False means: draw no badge. */ agent_owned: boolean; value: unknown; value_version: number | null; /** `user`, `agent` or `system` — VAL-8's closed vocabulary. */ actor: string | null; /** The person an agent acted for. Always set when `actor` is `agent`. */ on_behalf_of: string | null; written_at: string | null; source: EnrichSource | null; /** One of: "never asked", "none", "refused", "conflicting" (VAL-2). */ absent_reason: string | null; alternates: EnrichAlternate[] | null; /** A person holds this cell against the model until they hand it back. */ pinned: boolean; /** Past its freshness date. The value is still there — stale marks it, never erases it. */ stale: boolean; due_at: string | null; } /** The enrichment itself, as it is stored on the Field. */ interface Enrichment { instruction: string; inputs: string[]; web_search?: boolean; connected_source?: string | null; model: string; confidence_floor: number; write_policy: "auto" | "ask"; cost_cap_cents?: number; review_interval_days?: number | null; /** A person's opt-in: values past their date are filled in again without anybody pressing Fill. */ auto_refill?: boolean; /** Who turned refilling on; the refill runs as them. */ auto_refill_by?: string | null; enabled: boolean; enabled_by?: string | null; enabled_at?: string | null; declared_by?: string | null; declared_at?: string | null; } /** One agent-owned column, with its freshness census and its money. */ interface EnrichmentSummary { field_id: Uuid; table_id: Uuid; field_key: string; label: string; enrichment: Enrichment; enabled: boolean; review_interval_days: number | null; rows_total: number; rows_filled: number; rows_stale: number; rows_pinned: number; rows_absent: number; runs: number; cost_cents: number; cost_per_row_cents: number | null; last_run_at: string | null; last_run: Record | null; } /** One record that needs this column filled in, with what the instruction may read. */ interface EnrichDueRow { record_id: Uuid; title: string; inputs: Record; current_value: unknown; written_at: string | null; /** "never filled in", or "past its freshness date — written N days ago…". */ reason: string; /** Set when the ask was larger than the organization's batch ceiling. */ trimmed_to: number | null; } /** What one run did, and what it cost. */ interface EnrichRun { run_id: Uuid; field_id: Uuid | null; field_key: string | null; model: string | null; trigger_word: string | null; rows_seen: number; rows_written: number; rows_absent: number; rows_below_floor: number; rows_pinned: number; rows_refused: number; cost_cents: number; cost_per_row_cents: number | null; ran_at: string; } /** What `custom.enrich_declare` answers. `says` is a sentence for a person. */ interface EnrichDeclared { field_id: Uuid; table_id: Uuid; field_key: string; label: string; enrichment: Enrichment; says: string; } /** One question, as the sheet words it. */ interface CaptureQuestion { field: string; ask: string; help?: string | null; required: boolean; } /** One Field, as the phone needs it to draw a control and validate offline. */ interface CaptureField { id: Uuid; key: string; label?: string | null; type?: string | null; unit?: string | null; format?: string | null; multi?: boolean | null; required: boolean; /** `attachment` is what makes the phone open a camera instead of a text box. */ parity_type?: string | null; [k: string]: unknown; } /** * Everything the phone loads ONCE and then runs with no network. * * `state` is one of `open`, `not open yet`, `closed`, `read only`. `may_capture` is the * honest label for the screen and never the access decision — `custom.capture_submit` * asks `custom.has_visibility` for `editor` itself, before it admits the sheet exists. */ interface CaptureSheetFace { sheet_id: Uuid; organization_id: Uuid; table_id: Uuid; title: string; presentation: { intro?: string | null; flow?: string | null; thank_you?: { title?: string; body?: string; } | null; questions?: CaptureQuestion[]; [k: string]: unknown; }; fields: CaptureField[]; state: string; may_capture: boolean; message: string | null; } /** A sheet in the foreman's list, with what has come in through it. */ interface CaptureSheetSummary { sheet_id: Uuid; table_id: Uuid; title: string; slug: string; state: string; opened_at: string | null; captures: number; replays: number; last_capture_at: string | null; presentation: Record; } /** A file reference the app has already put in the platform's one byte store. */ interface CaptureFileRef { field: string; name: string; mime_type?: string | null; size_bytes?: number | null; duration_ms?: number | null; /** The `files.files` id. The File record made from this points at it. */ file_id?: string | null; url?: string | null; } /** What one capture became. `replay: true` means it had already arrived. */ interface CaptureOutcome { record_id: Uuid | null; state: string; message: string | null; replay: boolean; file_ids: Uuid[] | null; } /** VAL-8's whole vocabulary. There is no fourth word. */ type HistoryActorKind = "user" | "agent" | "system"; /** * WHO WROTE A VERSION. * * `kind` is the store's own word for the author. `name` is a person as a person * reads them — never a uuid; SCR-N-5 forbids printing one at a customer. * `on_behalf_of` is present only for an agent, and for an agent it is ALWAYS * present: an agent carries the exact authority of the person operating it * (AGT-N-4), so a screen that showed the machine without the person would be * hiding who is answerable. */ interface HistoryActor { kind: HistoryActorKind; user_id: Uuid | null; name: string | null; on_behalf_of: { user_id: Uuid; name: string | null; } | null; /** * The person who said yes, when an agent's change waited for approval and was * applied (`custom.work_approval_decide`, VISION-REACH W4 b). Absent on every * other version: a direct write has no approver. */ approved_by?: { user_id: Uuid; name: string | null; } | null; } /** * ONE FIELD THAT MOVED, with what it said before and what it says after. * * `label` is the Field's own label, so a timeline reads "Price" and not * `day_rate`. `rule` is FLD-9's computed provenance AT THAT VERSION — which * Rule, and which VERSION of the Rule, produced a derived value. `source` is * VAL-1's provenance, resolved from the pointer the document interns it under, * so a screen never prints `s3` at anybody. */ interface HistoryChange { key: string; field_id: Uuid | null; label: string; before: unknown; after: unknown; rule: { rule_id: Uuid; rule_version: number; computed_at: Timestamp; } | null; source: Record | null; /** VAL-2: why a value is absent — never asked, none, refused, conflicting. */ absent_reason: string | null; /** VAL-3: the other candidates the store kept, in rank order. */ alternates: unknown[] | null; /** * CLOSE-LINK (CD-LINK-07/12): the value in words when no column can say it — a move's record it sat under * (`before_words`) and now sits under (`after_words`), named by the store with the access question asked first. * Absent or null: say the value through its column. */ before_words?: string | null; after_words?: string | null; } /** One version of one record, as `custom.record_history` answers it, newest first. */ interface RecordHistoryEntry { version: number; occurred_at: Timestamp; /** The machine word: `edit`, `merge`, `undo of merge`, `insert`… */ operation: string; /** The same thing in the word a person used: "edited", "merge", "created". */ operation_label: string; actor: HistoryActor; changes: HistoryChange[]; /** Set when a compound verb wrote this version (HIS-8). */ migration_id: Uuid | null; /** * HIS-8: this version was written by a compound operation whose Migration log * row is still there, so the WHOLE operation can be undone through * `custom.migrate_undo`. An ordinary edit is never "undoable" in that sense — * it is RESTORABLE, which is a different verb and a different button. */ undoable: boolean; } /** * ONE CHANGE TO ONE COLUMN, ANYWHERE IN A TABLE — the half of the question no * revision panel answers. Airtable and Notion both make you open each record * and look. Only records this person may see contribute, decided as a predicate * in the same statement rather than a filter afterwards. */ interface FieldHistoryEntry { record_id: Uuid; /** The Table's own title field, so a row names a record and not a uuid. */ record_title: string; version: number; occurred_at: Timestamp; operation_label: string; actor: HistoryActor; before: unknown; after: unknown; } /** * ONE MOMENT OF A WORKED-OUT COLUMN'S HISTORY (`custom.formula_history`, CD-FORMULA-08; VISION Rule 8: "History knows * which one produced a Value"). A formula, rollup or lookup keeps no value of its own, so its history is worked out: at * each moment its answer could have changed — the record was written, or the formula itself was changed — the answer * from the record's values and the formula AS THEY WERE, and which version of the formula produced it. A formula's * version counts changes to what it works out; renaming the column is not a new version. Newest first. */ interface FormulaHistoryEntry { occurred_at: Timestamp; /** What moved: "record created", "record edited" or "formula changed". */ cause: string; /** The record's own version at that moment. */ record_version: number; /** 1 for the formula as first written, +1 each time what it works out changed. */ formula_version: number; /** The formula's words as that version had them (empty for a rollup or lookup). */ formula_text: string; /** The answer then. */ value: unknown; /** The store's own sentence when that version could not be worked out then; `null` otherwise. */ refused: string | null; /** Who wrote the record (or changed the formula) at that moment. */ actor: HistoryActor; } /** * WHAT A RESTORE WOULD DO, BEFORE IT DOES IT. * * `before` on each change is what the record says NOW and `after` is what it * would say. A key the target version did not have comes back with `after` of * `null` — the screen says "cleared", because that is what the write does to * it. Computed by the same body that computes the write, so the sentence and * the act cannot drift. */ interface RestorePreview { record_id: Uuid; from_version: number; to_version: number; saved_at: Timestamp; /** Present when the preview is for one column rather than the whole record. */ field_key: string | null; changes: HistoryChange[]; count: number; } /** What a restore actually did. A NEW version, always. */ interface RestoreResult { record_id: Uuid; restored_from_version: number; /** The version the restore WROTE. */ version: number; changed: HistoryChange[]; count: number; /** Always `false`, and said out loud because it is what people fear. */ history_rewritten: boolean; /** Present on a single-value restore. */ field_key?: string; } /** One comment, with the people on it named. */ interface RecordComment { id: Uuid; body: string; anchor: Record; /** * The column this comment is about, when it is about one. Notion anchors a * comment to a block; here the block is a Field, so "this price looks wrong" * sits on the price instead of at the bottom of the record. */ field_key: string | null; parent_comment_id: Uuid | null; created_by: Uuid; created_by_name: string; created_at: Timestamp; resolved_at: Timestamp | null; resolved_by: Uuid | null; resolved_by_name: string | null; mentions: Array<{ user_id: Uuid; name: string; }>; /** Mine, so a screen offers Resolve on my own and Reply on everybody's. */ mine: boolean; } /** * A RECORD'S WHOLE THREAD, AND WHAT THIS PERSON MAY DO IN IT. * * The two booleans come from the STORE, in the same call that answers the * thread, because a composer drawn for somebody the write door will refuse is * the defect lane UI-HONEST spent a night removing. `commenter` is the second * rung: a viewer reads the conversation and cannot join it, which is exactly * what the two rungs mean. */ interface CommentThreadRead { record_id: Uuid; comments: RecordComment[]; may_comment: boolean; may_resolve: boolean; } /** * Who follows ONE record (`custom.record_followers`) — the follower list the record shows, and whether THIS person is * on it. A person who cannot open the record gets an empty list, never a refusal that confirms it exists. */ interface RecordFollowers { following: boolean; followers: Array<{ user_id: Uuid; name: string; mine: boolean; }>; } /** What `custom.record_follow_set` answers: the caller's own state and how many follow now. */ interface RecordFollowSet { following: boolean; followers: number; } /** What `custom.comment_write` answers. */ interface CommentWritten { comment_id: Uuid; mentioned: Uuid[]; /** * How many people were actually TOLD. "I mentioned three people" and "three * people were told" are different facts and a screen that conflates them is * lying quietly. */ notified: number; } /** One token found in a body, in the order it appears. */ interface DocToken { ordinal: number; /** Exactly as it is written in the body. */ raw: string; field_id: Uuid | null; } /** A token this Table cannot answer, with the store's own reason. */ interface DocUnresolvedToken { raw: string; field_id: Uuid | null; /** The store's sentence. Shown verbatim — a template is not published over it. */ why: string; } /** What the store says about a signature when it is asked whether it still holds. */ interface DocSignatureCheck { intact: boolean; /** The store's own sentence when it is not. */ reason?: string; signed_at?: Timestamp; } /** `custom.doc_template` — a body with tokens, and the Table it renders. */ interface DocTemplateRow { id: Uuid; renders_table_id: Uuid; name: string; body: string; /** REC-68 / VAL-10: a seal is over a document VERSION, so the version is first-class. */ template_version: number; token_count: number; updated_at: Timestamp; } /** * `custom.doc_render` — a rendered document, FROZEN. The bytes and their * SHA-256 are what a signature seals, which is why a render is never edited: * a further agreement is a further version with its own seal. */ interface DocRenderRow { id: Uuid; template_id: Uuid; record_id: Uuid; table_id: Uuid; template_version: number; body: string; content_hash: string; rendered_at: Timestamp; } /** `custom.doc_signature` — one seal, immutable once made. */ interface DocSignatureRow { id: Uuid; render_id: Uuid; record_id: Uuid; field_key: string; signer_name: string; signer_user_id: Uuid | null; signed_at: Timestamp; document_hash: string; document_version: number; } /** * One of the five states, as `custom.work_states()` lists them — with what it * may become next and whether it is the end. A screen that hardcoded these * would be a second source of truth for the one question the store answers. */ interface WorkState { sort: number; name: string; terminal: boolean; next: string[]; } /** One node of a template graph: a record-to-be, in a named Table, under a ref. */ interface WorkTemplateNode { /** The name the graph refers to this node by, and the key an override is filed under. */ ref: string; /** The Table the record lands in. */ table: Uuid; /** The document it starts life with. An override merges on top at instantiation. */ data?: Record; } /** One edge. `owned` is what `custom.relation_own` makes; W1-REL owns the rest. */ interface WorkTemplateRelation { from: string; to: string; kind?: string; } /** * THE WHOLE TEMPLATE, DECLARATIVELY. REC-70: the graph is created in ONE act * and judged before any of it is written, so an agent writes a twelve-step * checklist in a single call and a half-made checklist is not a state the store * can be in. */ interface WorkGraph { nodes: WorkTemplateNode[]; relations?: WorkTemplateRelation[]; } /** What `custom.work_template_instantiate` answers: what it made, and how much. */ interface WorkInstantiation { template_id: Uuid; /** The log row. Until W3-HIST lands, this row IS the log of the act, and says so. */ instantiation_id: Uuid; records_created: number; relations_created: number; relation_kind: string; /** ref → the record it became, which is how a step finds its own record. */ refs: Record; records: Uuid[]; relations: Uuid[]; ms?: number; } /** One row of `custom.work_whose_turn`: a record, whose turn it is, and where it is up to. */ interface WorkTurn { record_id: Uuid; title: string | null; assignee_id: Uuid | null; turn: string | null; due_on: Timestamp | null; state: string | null; status: string | null; terminal: boolean | null; } /** A Field assignment is made of, as the store itself describes it. */ interface WorkAssignmentField { key: string; label: string; spec: Record; } /** * One hold on one slot. `expired` is the STORE's answer, not a clock comparison * a screen ran: a hold whose time is up is named as lapsed rather than quietly * disappearing, so a person can see why the slot came back. */ interface WorkSlotHold { hold_id: Uuid; slot_key: string; holder: string; expires_at: Timestamp | null; expired: boolean; } /** * What an embed token is good for, from `custom.anon_token_verify`. A token * carries ONE decision: `read` or `write`, never both. */ interface AnonTokenBinding { token_id: Uuid; organization_id: Uuid; form_id: Uuid | null; saved_view_id: Uuid | null; record_id: Uuid | null; mode: "read" | "write"; } /** The three things that land in the one inbox. Closed, one word each. */ type WorkInboxKind = "assignment" | "approval" | "proposal"; /** Who filed it: a colleague, or an agent acting for one. */ type WorkOrigin = "person" | "agent"; /** Where a due date stands, in the store's own words. */ type WorkDueState = "overdue" | "due_today" | "scheduled" | "undated" | "finished"; /** One row of `custom.work_inbox` — the SAME shape whichever of the three it is. */ interface WorkInboxItem { item_id: Uuid; kind: WorkInboxKind; origin: WorkOrigin; title: string; subject_id: Uuid | null; subject_kind: string; summary: string | null; state: string; due_on: Timestamp | null; due_state: WorkDueState | null; /** False for a decided row a person asked to see. A screen shows it, inert. */ actionable: boolean; requested_by: Uuid | null; requested_by_name: string | null; at: Timestamp; /** * The table it is about (lane S5-PRIME): the table itself for a new column, new records or a * template; the record's table for a change to a record; null for a new table (it waits on a * home). An assignment carries its record's table. What an inbox groups by. */ table_id?: Uuid | null; table_name?: string | null; /** * A CLOSED ITEM SAYS WHO AND WHEN (lane S5-PRIME). Null while it is waiting. For `withdrawn` * — nobody decided it; the thing it would change was archived — `decided_by` is whoever * archived it (null when the store's own backlog sweep closed it). */ decided_by?: Uuid | null; decided_by_name?: string | null; decided_at?: Timestamp | null; /** The store's own sentence: what was applied, what was left alone, or why it was withdrawn. */ outcome?: string | null; /** * THIS PERSON'S OWN STATE (lane S5-PRIME-2). Set only in the view that shows it: `snoozed_until` * in the snoozed view, `cleared_at` in the done view. Nobody else's snooze or clear is ever read. */ snoozed_until?: Timestamp | null; cleared_at?: Timestamp | null; /** How many of this person's items are snoozed / cleared right now — the same on every row. */ snoozed_count?: number; cleared_count?: number; /** * HOW LONG A DECISION ON THIS ROW IS HELD WITH UNDO before it is made (VERIFIER-20), in seconds: * the organization's `custom/decision_undo_seconds` (default 5) for a pending approval or * proposal; 0 when something already waits on the answer; null for work and closed rows. */ undo_seconds?: number | null; /** Why a decision here cannot be held (it is made at once) — the store's sentence, or null. */ undo_refusal?: string | null; } /** * Which of a person's three inbox views `custom.work_inbox` answers (lane S5-PRIME-2): what is * waiting now, what they snoozed (soonest back first), what they cleared (latest first). */ type WorkInboxView = "inbox" | "snoozed" | "done"; /** What `custom.inbox_snooze` / `inbox_unsnooze` / `inbox_clear` / `inbox_unclear` answer. */ interface InboxStateChange { item_id: Uuid; /** Where the item stands for this person now. */ state: "snoozed" | "cleared" | "waiting"; /** False when an un-snooze / un-clear found nothing to undo (it says so in `sentence`). */ changed?: boolean; snoozed_until?: Timestamp; cleared_at?: Timestamp; /** The store's own sentence, for the person. */ sentence: string; } /** * One organization's line of `custom.inbox_counts` — what waits on this person there, counted by * the same predicate the inbox lists with. The shell badge's number. */ interface InboxCounts { organization_id: Uuid; organization_name: string | null; waiting: number; snoozed: number; cleared: number; overdue: number; oldest_waiting_at: Timestamp | null; } /** * The states an approval moves through. `withdrawn` (lane S5-PRIME) is closed by the store, not * decided by a person: the record or table it would change was archived. */ type WorkApprovalState = "pending" | "approved" | "declined" | "withdrawn"; /** Which of the three lists `custom.work_list` answers. */ type WorkListFlavour = "mine" | "assigned" | "unassigned"; /** One row of `custom.work_list`: a record, whose it is, and where it is up to. */ interface WorkListItem { record_id: Uuid; table_id: Uuid; table_name: string | null; title: string | null; assignee_id: Uuid | null; assignee_name: string | null; assignee_user_id: Uuid | null; due_on: Timestamp | null; due_state: WorkDueState; status: string | null; terminal: boolean; assigned_by: Uuid | null; updated_at: Timestamp; } /** * One state a record may be moved to, with the model's own answer about whether * it may — and, when it may not, the sentence saying why. A screen offers what * is allowed and explains what is not; it never renders a dead control. */ interface WorkRecordState { state_id: Uuid; name: string; sort: number; terminal: boolean; is_current: boolean; allowed: boolean; refusal: string | null; } /** The change a pending approval carries. Two kinds, because two can be applied. */ type WorkChange = { kind: "record_patch"; patch: Record; } | { kind: "field_add"; field: Record; }; /** Somebody who may decide a particular request, and the reason they may. */ interface WorkApprover { user_id: Uuid; name: string; why: string; } /** What `custom.work_approval_request` answers: it is filed, and who it waits on. */ interface WorkApprovalFiled { approval_id: Uuid; state: "pending"; subject_id: Uuid; subject_kind: string; origin: WorkOrigin; approvers: WorkApprover[]; message: string; } /** What a decision answers — including whether the change was actually made. */ interface WorkApprovalDecision { approval_id: Uuid; state: "approved" | "declined"; subject_id: Uuid; /** The whole point: approving APPLIES the change, in the same transaction. */ applied: boolean; field_id: Uuid | null; version: number | null; message: string; } /** One inbox decision of a batch (`custom.work_decide_many`). */ interface WorkDecideManyItem { /** The inbox item — a `work_approval` record's id (`WorkInboxItem.item_id`). */ item: Uuid; decision: "approve" | "decline"; note?: string | null; } /** * `archived` (lane S5-PRIME): the thing it would change is archived, so it cannot be decided; the * sentence names it. A withdrawn approval answers `already_decided` with "That was withdrawn on …". */ type WorkDecideVerdict = "approved" | "declined" | "already_decided" | "no_right" | "archived" | "refused"; interface WorkDecideManyResult { index: number; item: Uuid; decision: "approve" | "decline"; verdict: WorkDecideVerdict; /** The store's own sentence: what was applied, or why it was not. */ sentence: string; subject_id?: Uuid; version?: number | null; record_ids?: Uuid[]; code?: string; } /** What `custom.work_decide_many` answers: the counts, then one verdict per item in order. */ interface WorkDecideMany { asked: number; approved: number; declined: number; not_decided: number; results: WorkDecideManyResult[]; } /** What `custom.work_assign` answers: the value, the access, and who holds it now. */ interface WorkAssignment { record_id: Uuid; assigned: boolean; assignee: Uuid | null; assignee_user_id: Uuid | null; assignee_name: string | null; was: Uuid | null; due_date: Timestamp | null; version: number | null; /** The share sentence, or why nothing was taken away when un-assigning. */ access: string; message: string; } /** What a step asks for before it counts as done. Every one of these is CHECKED. */ type ChecklistRequirementKind = /** A person says it is done. */ "none" /** A sentence of evidence, under `note`. */ | "note" /** A named value filled in on the step itself. */ | "answer" /** A named Field on the record the run is about must hold a value. */ | "record_field" /** The step IS a form (SCR-19): finishing it needs a response record. */ | "form" /** The step IS a document from a doc template (REC-68): finishing it needs a render. */ | "document"; interface ChecklistRequirement { kind: ChecklistRequirementKind; /** For `answer` and `record_field`: which value. */ key?: string; /** What a person is asked for, in their words. */ label?: string; /** For `form`: the published form this step is. */ form_id?: Uuid; /** For `document`: the doc template this step renders. */ template_id?: Uuid; } /** One step of a checklist, as it is WRITTEN — not as it is run. */ interface ChecklistStepSpec { /** The name the checklist refers to this step by; lower-case, unique. */ ref: string; /** What a person reads in their work. */ title: string; /** Which role owns it. A role the checklist never declares is refused at save. */ role?: string; /** How many days after the run starts it is due. */ due_days?: number; /** Steps it waits for. A step waits only for steps listed above it. */ depends_on?: string[]; requires?: ChecklistRequirement; } /** A role, and the person who stands in it by default. */ interface ChecklistRoleSpec { role: string; label?: string; user_id?: Uuid | null; } /** What starts a run. */ type ChecklistTriggerKind = "manual" | "record_created" | "status_reached"; interface ChecklistTrigger { kind: ChecklistTriggerKind; /** The Table being watched, for the two kinds that watch one. */ table_id?: Uuid; /** The state, for `status_reached`. */ status?: string; } /** THE COMPLETE DECLARATIVE CHECKLIST — one object, one call, a whole process. */ interface ChecklistSpec { name: string; /** The Table a run is ABOUT: People, Hires, Jobs, Matters. */ about_table_id: Uuid; roles?: ChecklistRoleSpec[]; trigger?: ChecklistTrigger; steps: ChecklistStepSpec[]; } /** What `custom.checklist_declare` answers. */ interface ChecklistDeclared { template_id: Uuid; name: string; about_table_id: Uuid; steps_table_id: Uuid; steps: number; roles: number; trigger: ChecklistTrigger; created: boolean; message: string; ms: number; } /** One row of `custom.checklist_templates`. */ interface ChecklistTemplateSummary { template_id: Uuid; name: string; about_table_id: Uuid | null; about_table: string | null; steps: number; roles: number; trigger_kind: ChecklistTriggerKind; trigger_status: string | null; open_runs: number; total_runs: number; updated_at: Timestamp; } /** `custom.checklist_template_shape` — the spec, plus what the editor needs. */ type ChecklistTemplateShape = ChecklistSpec & { template_id: Uuid; steps_table_id: Uuid | null; may_change: boolean; }; /** What `custom.checklist_start` answers. */ interface ChecklistRunStarted { run_id: Uuid; template_id: Uuid; template: string; about_record_id: Uuid | null; about: string | null; steps_table_id: Uuid; steps_created: number; assigned: number; unassigned: number; origin: string; steps: Array<{ ref: string; step_id: Uuid; title: string; role: string | null; }>; /** Roles nobody is named for, so the screen can say so rather than show blanks. */ waiting_on_a_name: string[]; message: string; ms: number; } /** One step of a RUN, as `custom.checklist_run` answers it. */ interface ChecklistRunStep { step_id: Uuid; step_order: number; ref: string; title: string; role: string | null; assignee_name: string | null; assignee_user_id: Uuid | null; due_on: Timestamp | null; due_state: WorkDueState; status: string | null; finished: boolean; requires: ChecklistRequirementKind; /** * The key the answer is filed under, as the checklist named it. A screen files * evidence under THIS, never under a name it invented — the store looks for * exactly this key and nothing else. */ requires_key: string | null; requires_label: string | null; /** The form or the document template this step is, when it is one. */ requires_id: Uuid | null; evidence: Record; /** The titles of the steps it is still waiting for. */ blocked_by: string[]; may_complete: boolean; /** Why it cannot be finished right now, in the words a person would use. */ refusal: string | null; } /** One row of `custom.checklist_runs`. */ interface ChecklistRunSummary { run_id: Uuid; name: string; template_id: Uuid | null; template: string | null; about_record_id: Uuid | null; about: string | null; about_table_id: Uuid | null; started_at: Timestamp | null; started_by: Uuid | null; origin: string; step_count: number; done: number; overdue: number; next_step: string | null; next_due: Timestamp | null; closed_at: Timestamp | null; } /** What `custom.checklist_step_complete` answers. */ interface ChecklistStepCompleted { step_id: Uuid; run_id?: Uuid; completed: boolean; evidence?: Record; steps_left?: number; run_closed?: boolean; message: string; } /** * VIS-31. A signed-in OUTSIDER, with the reason in plain English. * * "No organization" cannot mean "no row in the membership table": signup mints * every account a PERSONAL organization, so under that reading nobody would * ever be external. It means no membership of a NON-personal organization, and * `iam.is_external_principal` says that once so no surface can invent a second * answer. */ interface ExternalPrincipalCard { user_id: Uuid | null; /** The answer itself. */ external: boolean; signed_in: boolean; /** * WHY, in the platform's own words — the sentence a portal shows instead of * an empty screen. `iam` writes it, not us, so the portal and the API and the * admin screen all say the same thing about the same person. */ explanation: string; /** How many NON-personal organizations they belong to. Zero is what external means. */ organization_memberships: number; [key: string]: unknown; } /** VIS-32. A record or a view bound to a public address, with a declared render mode. */ interface PublishBinding { id: Uuid; slug: string; resource_type: string; resource_id: Uuid; organization_id: Uuid; render_mode: string; is_active: boolean; created_at?: Timestamp; [key: string]: unknown; } /** * What a SIGNED-OUT visitor resolves. An invented address, a revoked one and an * unpublished one all resolve to NOTHING — the same 404 — because an address * that answered differently for a revoked binding would be an existence oracle. * * `notice` is D-15's admission, carried on every read: nothing was scanned * before this was listed, and nothing here pretends otherwise. */ interface ResolvedPublishBinding { slug: string; resource_type: string; resource_id: Uuid; organization_id: Uuid; render_mode: string; namespace: string | null; notice: string; } /** What a public address holds, as `iam.resolve_publish_binding_kind` names it. */ type PublishBindingKind = "table" | "record" | "dashboard" | "page" | "view"; /** * `iam.resolve_publish_binding_kind` — the same resolution, plus WHAT the address holds. Tables, * records and dashboards all carry the entity token `record`, so the token alone cannot say which; * this door can. `table_id` is the table a list or a record belongs to (a view's subject, a * dashboard's subject table); `title` is its name. Anon may call it. */ interface ResolvedPublishBindingKind extends ResolvedPublishBinding { kind: PublishBindingKind | string; table_id: Uuid | null; title: string | null; } /** One value the agent was given, with the triple that makes it citable. */ interface RecordScopeField { key: string; label: string; type: string; value: unknown; /** DYN: a merge field resolves to a TRIPLE, not a string. */ cite: { record_id: Uuid; field_id: Uuid; value_version: number; written_at: string | null; }; } /** A Field the agent was NOT given, and which of the two reasons it is. */ interface RecordScopeWithheld { key: string; label: string; because: "masked" | "policy"; reason: string; needs?: string; /** The whole sentence, already written — a screen prints it rather than composing one. */ says: string; } interface RecordScopeRelation { via: string; target_id: Uuid; foreign: boolean; masked: boolean; your_level: PermissionLevel | null; card: Record | null; why: string | null; } interface RecordScopeHistoryEntry { version: number; at: string; what: string; who: string | null; changes: Array>; } interface RecordScopeComment { id: Uuid; body: string; created_at: string; created_by_name: string; field_key: string | null; } interface RecordScopeContext { record: { id: Uuid; title: string; table_id: Uuid; table_name: string | null; scope_type: string; plural: string; version: number; updated_at: string; your_level: PermissionLevel; }; fields: RecordScopeField[]; withheld: RecordScopeWithheld[]; relations: RecordScopeRelation[]; history: RecordScopeHistoryEntry[]; comments: RecordScopeComment[]; may_comment: boolean; siblings: { shown: Array<{ id: Uuid; title: string; }>; more_than_shown: boolean; says: string; }; /** Questions derived from THIS record's shape — never a fixed list. */ suggested: string[]; /** One sentence naming what is not in the conversation. Printed verbatim. */ says: string; assembled_at: string; } /** What a conversation is about. `readable` false means the person lost access to it. */ interface ConversationScope { bound: boolean; readable?: boolean; record_id?: Uuid; table_id?: Uuid; scope_type?: string; title?: string; bound_at?: string; bound_by?: Uuid; because?: string; } interface ConversationScopeContext { scope: ConversationScope; context: RecordScopeContext | null; } /** `About: Acme Industrial` — the chip a bound chat wears. */ declare function scopeChip(scope: ConversationScope): string; /** * DOOR-8. As-of on EITHER clock, and they are different questions: * · `record` — the record clock: what this record looked like at version time. * · `world` — the world clock: which values were true of the world then * (the Field's `dated` modifier). */ interface AsOf { clock: "record" | "world"; at: Timestamp; } /** A predicate over a PROMOTED field (REC-5: promotion light -> heavy). */ interface PromotedPredicate { field_key: string; /** The value as text, exactly as `custom.promoted_read` takes it. */ equals: string; } interface RecordQuery { table_id: Uuid; /** DOOR-6. Any combination, any subset. An empty list means "no relation constraint". */ relations?: RelationCoordinate[]; /** Predicates the store can answer from an index. */ promoted?: PromotedPredicate[]; /** DOOR-8. */ asOf?: AsOf; /** DOOR-9. Ask across EVERY Home of this Table in one answer. */ acrossHomes?: boolean; /** REC-23. Soft-deleted rows are out unless you say otherwise. */ includeDeleted?: boolean; /** FLD-10. */ recordType?: string; orderBy?: { column: "created_at" | "updated_at"; ascending?: boolean; }; limit?: number; offset?: number; } /** * What a query decomposes into: a set of door calls whose answers INTERSECT, * then one filtered read. Kept as data rather than hidden inside the client so * the suite can assert the plan as well as the answer — a query that quietly * stopped applying one of its coordinates is otherwise invisible. */ interface QueryPlan { /** One `relation_targets` call per coordinate; the id sets are intersected. */ relationCalls: Array<{ door: "relation_targets"; record_id: Uuid; via_key: string | null; }>; /** One `promoted_read` call per predicate; intersected with the above. */ promotedCalls: Array<{ door: "promoted_read"; field_key: string; value: string; }>; /** The filtered read over `custom.record`, which the store's policies gate. */ read: { table_id: Uuid; includeDeleted: boolean; recordType: string | null; asOf: AsOf | null; acrossHomes: boolean; orderBy: { column: string; ascending: boolean; }; limit: number; offset: number; }; /** * Stated so nobody has to guess: when this is true the answer is the * intersection of the id sets above, and when it is false the read stands * alone. */ intersects: boolean; } declare const DEFAULT_PAGE_SIZE = 50; declare function planQuery(query: RecordQuery): QueryPlan; /** The intersection of the id sets a plan's calls produced. Order is preserved from the first set. */ declare function intersectIds(sets: Uuid[][]): Uuid[]; /** One organization the client could not use, with the sentence that says why. */ interface OrganizationUnavailable { organization_id: Uuid; reason: string; } /** `organizationsOpen`: the organizations whose store answers, and those that do not (with why). */ interface OrganizationsOpenAnswer { open: Uuid[]; unavailable: OrganizationUnavailable[]; } /** `metadataSearch`: every match, each carrying the organization it came from. */ interface MetadataSearchAnswer { matches: Array & { organization_id: Uuid | null; }>; /** Organizations asked in order (the ones that answered). */ organizations_covered: Uuid[]; unavailable: OrganizationUnavailable[]; } /** AT-SHARE-B. What `custom.field_notify_on_assign` answers: whether the column tells people, and the automations doing it. */ interface FieldNotifyOnAssign { field_id: Uuid; notify_on_assign: boolean; changed: boolean; automation_ids: string[]; says?: string; } /** What `custom.base_move` answered when the whole base landed. */ interface BaseMoved { name: string; toOrganizationId: Uuid; /** How many tables went with the Home. */ tables: number; } interface RecordsClient extends PickListDoors { /** The config this client was built with, so a screen can show who it is acting as. */ readonly config: RecordsConfig; /** * THE SCOPE DOORS (`custom.context_*`): scope types are Tables kept for context, scopes Records, * context fields Fields, values cells. Built from this client's own door path. `@ai-matrx/records/scopes`. */ readonly scopes: ScopeDoors; recordWrite(args: { table_id: Uuid; data: RecordDocument; }): Promise>; /** * WRITE THE RECORD THIS KEY NAMES, OR UPDATE IT WHEN IT IS ALREADY THERE — one call, race-safe * (`custom.record_upsert`). `key` is the column key (or keys) that say which record this is, * and every one must carry a `unique` rule: the store refuses any other column by its name. * Two concurrent calls with the same key leave ONE record. An update goes through * `record_update` (with `expectedVersion`, its stale-write refusal); a new record through * `record_write`. Answers the record's id either way. */ recordUpsert(args: { table_id: Uuid; key: string | string[]; data: RecordDocument; expectedVersion?: number; }): Promise>; /** * Write MANY new records of one Table in one call. * * `custom.record_write_many` asks the switch, the organization and the Table * ONCE for the whole batch and inserts the rows in one statement — which is * why a paste of 120 lines is one round trip per chunk instead of 120. It * CREATES only: there is no bulk update door, because an update carries a * per-row optimistic version and a batch would have to either drop that or * fail the whole batch for one stale row. * * It returns 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. Hand in * `ids` to choose them yourself; hand in none and the door mints them. */ recordWriteMany(args: { table_id: Uuid; rows: RecordDocument[]; ids?: Uuid[]; }): Promise>; /** * DOOR-SPEED. MANY CHANGES TO ONE TABLE IN ONE TRANSACTION, through * `custom.record_change_many`: inserts, updates (each with an optional `expected_version`) * and archives. Every update lands in ONE statement, so the store's change notice, history and * activity fire once for the batch while every row trigger still judges every row. ONE REFUSED * CHANGE REFUSES THE BATCH and nothing is saved; the store's own sentence comes back with the * change's position in the hint. The answer is one result per change, in order. */ recordChangeMany(args: { table_id: Uuid; changes: readonly RecordChange[]; }): Promise>; /** * A PARENT, THE EDGES IT STANDS ON AND THE ROWS THAT BELONG TO IT — ALL OR NOTHING. * * `recordWrite` and `recordWriteMany` are each ONE statement. A screen or a * server path whose unit of work is a GRAPH — an AI artifact and the lines * it produced, an order and its items, a research note and its citations — * could therefore not save it as one thing: a loop is N transactions, and a * failure on row nineteen leaves a parent standing without the lines it * promised. * * `custom.record_write_graph` is one statement, so it is one transaction. It * is NOT a second write path: it calls `custom.record_write_many` for the * rows, `public.assoc_add` for an edge that points OUT of the store (the * message a record was produced by), and `platform.relation_set` for the * parent's own declared relation to its children. Same store switch, same * editor rung on the Table, same value envelope, same undeclared-key guard, * same history rows, same realtime notice. * * ONE REFUSAL REFUSES THE WHOLE GRAPH, BY NAME — the answer says which member * failed and carries the store's own sentence about it, and nothing is * written. * * Every child MUST name a `role`: it is the key of a declared RELATION field * on the parent's Table, and an edge out of a record that names no field is * refused by the store (REL-10 / T7) because without it nothing can read the * relation's delete rule, its cardinality or its organization wall. The order * children are handed in IS the order of the relation. */ recordWriteGraph(args: { table_id: Uuid; parent: RecordDocument; /** Each child: its document, the parent FIELD it fills, and optionally its own Table. */ children?: Array<{ data: RecordDocument; role: string; table_id?: Uuid; position?: number; }>; /** Edges to things OUTSIDE the store. `direction` defaults to `"in"` — that thing produced this record. */ edges?: Array<{ entity: string; id: Uuid; direction?: "in" | "out"; label?: string; role?: string; position?: number; metadata?: Record; }>; }): Promise>; recordUpdate(args: { record_id: Uuid; patch: Record; /** Leave it out and the last writer wins; supply it and a loser is TOLD. */ expectedVersion?: number; }): Promise>; /** * A CELL THAT TAKES WORDS WHICH ARE NOT YET A CHOICE — the words become a choice of that * column everywhere and the cell takes it, in ONE transaction (lane CHOICE-COLUMN-EDIT, * `custom.record_update_adding_choices`). `add` is `{ "": ["words", ...] }`; the * cell's value in `patch` is the words, and the store turns them into the new choice's key. * Adding a choice changes the table's columns, so it needs that right; a refusal names it. * Answers the new version, like `recordUpdate`, and announces the record changed. */ recordUpdateAddingChoices(args: { record_id: Uuid; patch: Record; add: Record; expectedVersion?: number; }): Promise>; /** * The Feature Knob `custom/choice_nudge` for the signed-in person in this client's * organization: what a choice cell does with typed words that are not a choice * (`decideTypedChoice` in `choice.ts` is the one place that acts on it). */ choiceNudge(): Promise>; /** * REC-23. Soft, and reversible within `retention`. * * 🚨 NOT FOR A TABLE. A Table is a record, so this door accepts one — and * then recurses through every record the Table contains inside a single * transaction, which is unbounded by construction and dies on `57014` at a * size the product's own import reaches in half a minute. Use * `tableArchive` below, which does the same work in resumable passes. */ recordDelete(args: { record_id: Uuid; }): Promise>; /** * MOVE A RECORD (a Table included) INTO ANOTHER HOME — `custom.record_reparent`, the one door that changes where a * record is contained. `parent_id` is the Home's record id (null takes it out). Admin on the record, viewer on the Home. */ recordReparent(args: { record_id: Uuid; parent_id: Uuid | null; }): Promise>; recordRestore(args: { record_id: Uuid; }): Promise>; /** * CLOSE-LINK (CD-LINK-10) — WHERE A TABLE LIVES: every Home it lives in (`custom.table_homes_named`), each named for * this reader, the Home it was declared in first (`declared`). A Home this reader may not open is named as withheld. */ tableHomes(args: { table_id: Uuid; }): Promise>>; /** * CLOSE-LINK (CD-LINK-10) — "ALSO LIVES IN…": the Table also lives in `home_record_id` (`custom.home_add`, admin on the * Table). A person shared on that Home alone then knows the Table and sees the records inside that Home, never another's. */ homeAdd(args: { table_id: Uuid; home_record_id: Uuid; }): Promise>; /** * ARCHIVING A WHOLE TABLE — ONE PASS OF IT (VERIFIER-11 F3). * * One call archives at most `chunk` of the table's live records through * `custom.record_delete` (so every guard, history capture and outbox row * still happens; nothing is hard-deleted) and comes back with the numbers a * screen draws progress from. The CALLER LOOPS until `done` — that loop is * the fix, because the one thing a single statement cannot be made to do is * finish. * * `chunk: 0` changes nothing and only reports, so a screen can say "1,430 * records will be archived" BEFORE a person commits to it. `includeTable: * false` empties the table and leaves it standing. * * RESUMABLE, WITHOUT A JOB ROW: what remains is read off the records * themselves every call. A pass that fails, a closed tab or a deploy mid-run * leaves a half-archived table and a next call that continues from exactly * there. A caller must therefore never report "nothing happened" after a * failed pass — `archived_total` says what really landed. */ tableArchive(args: { table_id: Uuid; /** * How many records this pass takes on. The door clamps it; it answers with what it * took. Default 50 — measured: ~35ms a record against an 8s statement timeout, so * 200 dies on `57014` and is the original defect one level up. */ chunk?: number; /** Archive the Table record itself once nothing live is left in it. Default true. */ includeTable?: boolean; }): Promise>; /** * ONE PASS OF ARCHIVING, ON PURPOSE, A TABLE THE APP'S CODE DEPENDS ON * (`custom.table_archive_deliberately`, lane 12 P5). Such a table (`code_depends: true`) refuses * `tableArchive`, `recordDelete`, a slug / kept_for rename, `tableMove` and removal of the mark. * This door takes the table's slug TYPED by the person and a reason, refuses an agent, and then * archives through `custom.table_archive` (same progress answer; the caller loops until `done`). * Use `archiveTypedTableDeliberately` (`@ai-matrx/records/typed-table`), which loops. */ tableArchiveDeliberately(args: { table_id: Uuid; typed_slug: string; reason: string; }): Promise>; /** * BRINGING A WHOLE TABLE BACK — ONE PASS OF IT (`custom.table_restore`, lane TABLE-ACTIONS). * * 🚨 NOT `recordRestore` FOR A TABLE: that door brings the Table, its structure, every record * and everything built on it back in one statement and dies on `57014` (the clone's "Referral * Intake Queue 2026-Q4 (copy 7)"). This pass brings back the Table and its structure first, then * at most `chunk` records, then what was built on it — all from the one archive event — and * answers `done` when nothing the archive took is still waiting. Use {@link tableRestoreWhole}. */ tableRestore(args: { table_id: Uuid; chunk?: number; }): Promise>; /** * THE WHOLE RESTORE OF A TABLE — the loop every caller needs: `tableRestore` until `done`, * halving the pass on a statement timeout (a pass that timed out changed nothing). `onPass` * hears every pass's numbers. Answers the last pass, or the refusal that stopped it (what the * passes before it brought back stays back; the next call carries on from there). */ tableRestoreWhole(args: { table_id: Uuid; onPass?: (progress: TableRestoreProgress) => void; }): Promise>; /** * DUPLICATE A TABLE — `custom.table_duplicate` (lane TABLE-ACTIONS). * * Makes a NEW Table from one the caller may open: its 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, every id inside them pointing at the copy), and with `withRecords` * every live record (a link to another row of the same table follows to the copy's row; a link * to another table stays). `name` defaults to " (copy)", "(copy 2)", …; * `organizationId` defaults to the source's organization and must be one the caller belongs to. * The source is never written. The copy is made in passes of a few seconds (any size; the * store answers "copying" until "done"), and this call loops them. Refused before anything is * made, the refusal carries the store's one sentence (not given / archived / not a member there * / a column linking across organizations). */ tableDuplicate(args: { tableId: Uuid; withRecords?: boolean; name?: string | null; organizationId?: Uuid | null; /** Called after every pass with what is copied so far (a progress bar is made of it). */ onProgress?: (progress: TableDuplicateProgress) => void; }): Promise>; /** * FINISH A BASE COPY — `custom.base_copy_finish` (lane AT-BASE). After every table of a base was copied by `tableDuplicate`, * points the copies at each other: `pairs` is {source table id: its copy's id}. Re-points each link column's target and each * link cell whose row was copied too; links to a table outside the base stay. Only tables this person copied are touched. */ baseCopyFinish(args: { organizationId: Uuid; pairs: Record; }): Promise>; /** * CARRY A COPY'S EXTRAS — `custom.copy_table_extras` (lane COPY-EXTRAS). `pairs` is {source table id: its copy's id}. Every automation of a * source is declared on its copy SWITCHED OFF and every page too, each column and view id pointed at the copy's own. The step's own * refusals (one sentence each) come back in `extras_refused`; the base-move job in aidream calls the same function. */ copyTableExtras(args: { organizationId: Uuid; pairs: Record; }): Promise>; /** * CARRY ON A COPY — `custom.table_duplicate_continue` (lane TABLE-ACTIONS). The copy is made in * passes of a few seconds each; `tableDuplicate` loops them, and this resumes one that a closed * tab or a lost connection left unfinished (the copy is kept out of every table list until it * is done). Only the person who started it; it answers the finished copy, or refuses. */ tableDuplicateContinue(args: { copyId: Uuid; onProgress?: (progress: TableDuplicateProgress) => void; }): Promise>; /** * THE CALLER'S UNFINISHED COPIES — `custom.table_copies_in_progress` (lane TABLE-ACTIONS). Each * is still being made (carry it on with `tableDuplicateContinue`) or stopped because its source * was archived meanwhile (discard it: `tableArchive` on the copy). Nobody else can see them. */ tableCopiesInProgress(): Promise>; recordRead(args: { record_id: Uuid; withComputed?: boolean; /** Also answer `version` (read in the same round, so the read takes as long as the slower of the two). */ withVersion?: boolean; organization_id?: Uuid | null; }): Promise>; /** * DOOR-1. A page of one Table's records THROUGH `custom.read_records`: the * store decides which rows this reader may see and which fields of them, and * a field it masked comes back present-and-null with its reason in `hidden`. * `total` is null because the door counts nothing it has not read — a count * of rows a person may not see is a leak with a number on it. */ list(args: { table_id: Uuid; /** * THE STORE'S ONE FILTER SHAPE (`RecordFilter`). Left out, this is the * whole page. Supplied, the store narrows INSIDE its own query — never the * browser, which can only ever filter the rows it was already allowed to * see and would silently drop the rest of the answer. * * It is the same object a saved view stores as `filters` and the same one * `recordAggregate` counts with, so the rows a drill-through opens are the * rows the number on the dashboard counted. */ filter?: StoreFilter; limit?: number; offset?: number; }): Promise>; /** * DOOR-SPEED. ONE PAGE THE STORE SORTED, SEARCHED, FILTERED AND COUNTED, through * `custom.read_records_page` — the same wall, ladder, field mask and filter as `list`, with the * order (`sort`, or a hand-ordered view's positions via `view_id`), a text `search` over the * columns this reader may see (and the words of choice options), and the `total` of the * matching visible set. It is what a grid pages with: nothing reads the whole table to draw one * page. A sort on a column this reader cannot see (gone, or masked) is SKIPPED and named in * `sort_ignored` — the page still loads; one worked out on every read is refused by name. */ listPage(args: { table_id: Uuid; filter?: StoreFilter; search?: string | null; sort?: readonly RecordPageSort[]; view_id?: Uuid | null; limit?: number; offset?: number; }): Promise>; /** * AP3-KEYSET-DOOR. THE SAME PAGE AS `listPage`, PAGED BY A CURSOR, through * `custom.read_records_keyset`: the same wall, ladder, field mask, filter, search and sort (no * hand-ordered view), the same rows in the same order — but the next page starts after the last * row's sort values and id, so a write landing mid-read never skips or repeats a row. Pass back * `cursor` as `after` (it is opaque; `null` on the last page). A cursor handed to a different * sort, filter or search is refused by name ("start from the first page"), never reinterpreted. * * Every row carries the `version` and `organization_id` it was read at, in the same statement as * its values. With `ids`, it re-reads exactly those rows among the ones the reader may see (in the * page's order; `after` ignored, `cursor` and `total` null) — get-by-id, the re-read after a write, * and the changed ids of a realtime notice. */ listKeyset(args: { table_id: Uuid; filter?: StoreFilter; search?: string | null; sort?: readonly RecordPageSort[]; after?: string | null; ids?: readonly Uuid[]; limit?: number; }): Promise>; /** * DOOR-1 FOR AN ID SET. Exactly these records of this Table, through * `custom.read_records_by_ids` — the same ladder, the same level, the same * field masking and the same page ceiling as `list`, addressed by id instead * of by page. An id the reader may not see simply does not come back, which * is the same answer `list` gives by omission. * * It exists because a realtime notice has always named the exact ids that * moved and no door could take them: three cells changing re-read a fifty-row * page in every watching browser. */ listByIds(args: { table_id: Uuid; ids: readonly Uuid[]; }): Promise>; /** * DOOR-1 FOR THE ROWS THAT LEFT: a page of one Table's ARCHIVED records, * through `custom.read_records_archived` — the same ladder, the same level, * the same field masking and the same page ceiling as `list`, with the sign * of the `deleted_at` test flipped, plus who archived each one and when. * * 🚨 IT EXISTS BECAUSE ARCHIVING WAS A ONE-WAY DOOR (VERIFIER-10 F4). The * store has always had `record_restore`, and it has always done exactly the * right thing; what it did not have was any door that could LIST the records * it would act on, because every read door answers live rows only. So an * archived record was unreachable from every screen in the platform. * * `lane` is the archived-items law's two lanes — "mine" (what I archived) and * "org" (what anybody in this organization archived), which is the default. */ listArchived(args: { table_id: Uuid; lane?: ArchiveLane; limit?: number; offset?: number; }): Promise>; query(query: RecordQuery): Promise>; /** * Every Table the person can see across ALL their organizations * (`config.organizationIds`); `organization_id` is the page's optional filter * (absent = all). Each Table carries its own `organization_id`. */ tableList(args?: { organization_id?: Uuid | null; }): Promise>; /** * ONE Table's declaration by id — `null` when this reader may not see it or it is not a Table. * The same kernel record `tableList` lists, read through `read_records_by_ids` for exactly * that id, so a screen that opens one table never lists every table to find it (a list capped * at 500 answered "no table" past the 500th). */ tableRead(args: { table_id: Uuid; organization_id?: Uuid | null; }): Promise>; /** * ONE Table of THIS organization found by its slug — or, with no slug, by its name — in ONE * request (`custom.table_find`). `null` when there is none or this reader may not see it: the * store reads the candidates through the same read door `tableRead` uses, so a table she may * not see answers exactly like one nobody made. Where several share a slug, the oldest she may * see answers, every time (the same pick `ensureTable` makes). Never list every table to find one. * * `keptFor` (an app's table): the slug AND what the table is kept for must match — a person's own * table of the same slug is a different table and never answers. With `keptFor` and no live * match, an ARCHIVED match is answered as `{ id, organization_id, archived_at }` (no document), so * a caller says "archived — restore it", never "missing". */ tableFind(args: ({ slug: string; name?: never; } | { name: string; slug?: never; }) & { keptFor?: string; }): Promise>; /** * `tableFind` BY SLUG FOR MANY ORGANIZATIONS IN ONE REQUEST (`custom.table_find_many`). Each * organization meets the same walls `tableFind` does and is answered through `custom.table_find` * itself. `tables` holds one live Table per organization that has one this reader may see (an * organization with none is absent); `refused` holds each organization the store refused, with * its own refusal, and never stops the others. Never loop `tableFind` over organizations. */ tableFindMany(args: { organizationIds: Uuid[]; slug: string; keptFor?: string; }): Promise; }>>; /** * IS THIS TABLE IN THE ARCHIVE? (lane HUB-FIX, 2026-09-23.) The archived row for one * Table of this organization, or `null` when the archive does not hold it — which * is an ANSWER, never "it may have been deleted". A table page that cannot find a * Table asks this before saying anything: an archived Table's own address used to * read "…or it may have been deleted" when it was archived and one click from back. */ tableArchived(args: { table_id: Uuid; }): Promise>; /** * WHO CAN SEE EACH TABLE, AND WHETHER THE CALLER MADE IT — `custom.table_facts` * (lane HUB-FIX). `visibility` and `created_by` are record COLUMNS the Table document * never carried, so a screen that sorts Tables into lanes asks this and folds the * answer onto `tableList()`'s Tables with `withTableFacts`. * * 🚨 IT IS ITS OWN CALL, NOT PART OF `tableList()` (0.57.3). 0.57.2 folded it into * every table list, so every screen that merely LISTS tables (the field editor's * relation target, the import wizard, settings) paid a second round trip it never * used — and every test double that answers a table list had to answer it too * (records-ui 0.84.5's gate: 47 tests in 15 files red). Only the lane-sorting * screens ask. * * The door-presence pattern: when the store's catalog does not carry the door the * answer is `null` — nothing asked, nothing invented. */ tableFacts(args?: { organization_id?: Uuid | null; }): Promise>; /** * WHICH STORE CURRENTLY WRITES each table — `custom.where_tables_live`. * * This is a batch cutover decision, not a Table's home address: during COPY * mode an older table and its record-store copy share an id, and only this * door knows which one the organization is still using. It is intentionally * unbound from this client's organization because the door derives the answer * from each supplied table and the caller's authority. */ tablesLiveWhere(args: { table_ids: Uuid[]; }): Promise>; /** * WHICH ORGANIZATION A TABLE LIVES IN, BY NAME, AND WHERE IT MAY GO — `custom.table_home` * (SC-1' / SC-1-TAILS). Read FROM THE TABLE, never from the organization the client is bound * to, so it answers for a table shared from outside too. `null` = not given to this person, * or not there (the store says the same for both, on purpose). A store without the door * refuses `door_absent` — never a guessed organization. */ tableHome(args: { table_id: Uuid; }): Promise>; /** * MOVE A TABLE into another organization the caller belongs to — `custom.table_move`. It * carries every Table inside it or its rows and writes the change events; refused, NOTHING * moved and the refusal carries the store's one sentence. `expected_version` is the Table's * version the caller decided on (a concurrent change refuses with "look again"). */ tableMove(args: { table_id: Uuid; to_organization_id: Uuid; expected_version?: number | null; }): Promise>; /** * MOVE A WHOLE BASE into another workspace (organization) the caller belongs to — `custom.base_move` (AT-NOTIFY-MOVE). * Admin on the base's Home. Every table of the Home goes through the one table door (with its pages and automations), the * Home and its base row follow, and the tables stay under that Home over there. All or nothing: a refusal moved nothing and * carries the store's sentence (which table it stopped at, or that the caller is not in that workspace). */ baseMove(args: { home_id: Uuid; to_organization_id: Uuid; }): Promise>; /** * The Fields that apply. NOTION-PROPS: a two-way link's reverse Field (`isReverseField`) is left out * unless `withReverse` — every screen draws it as the table's reverse column (`reverseColumns`, with * `read_only: false`), which reads and writes its links; listed as a Field too it would draw twice. */ fields(args: { table_id: Uuid; recordType?: string; withReverse?: boolean; }): Promise>; /** * FLD-5 / FLD-6: a list field stores the id of an option RECORD, because * every pick-list is already a Table. These are that Table's records — the * store resolves which Table from the Field's own config, so no caller has to * know it. */ fieldOptions(args: { field_id: Uuid; }): Promise>; /** * How many records hold each choice of one choice column, keyed by the option's id * (`custom.field_choice_usage`; counts only records the caller may see). What a column editor * says before a choice that records still hold is removed: "3 records use “X-ray”." */ fieldChoiceUsage(args: { field_id: Uuid; }): Promise>>; /** * A column's choices saved AND the records of each removed choice moved to another choice, * kept as other values, or cleared — ONE transaction (`custom.field_update_rehoming_choices`). * `rehome` is keyed by the removed option's id. The answer carries `undo`; sending * `{ patch: { options: undo.options }, cells_back: undo.cells_back }` back through this same * call puts the list and every cell back, again in one transaction. */ fieldUpdateRehomingChoices(args: { field_id: Uuid; patch: FieldPatch; rehome?: Record | undefined; cells_back?: readonly ChoiceCellBack[] | undefined; }): Promise>; tableCapacity(args: { table_id: Uuid; }): Promise>; /** * REC-1: a Table has to live somewhere, so a Home comes first — and a Home is * an ordinary record in the person kernel. `homeId` is therefore required and * `personKernelId()` is how a caller gets a Table to write one into. */ tableDeclare(args: { spec: Record; homeId: Uuid; }): Promise>; /** * FIND THIS TABLE BY ITS SLUG, OR MAKE IT — ONE call, ONE transaction (`custom.table_ensure`). * The store holds a lock on (organization, slug) across the find and the make, so two tabs * saving their first record at once get the SAME table; a second call adds any column the spec * names that the table lacks and never retypes or drops one. `spec.fields` are full * `custom.field_declare` specs. Prefer `ensureTable(client, NewTableSpec)`, which builds them. * * `drift` names every column the table already has whose stored shape differs from the spec * (type, format, label, multi, required, unique) — reported, never applied. `[]` when nothing * differs; `null` only from a store too old to compare (see `FieldDrift`). */ tableEnsure(args: { spec: Record; }): Promise>; /** * ADD A FIELD TO A TABLE. One call, and the store does the three things no * client can do for itself: it turns a parity type into the behavior, * modifiers, format, unit, relation target and Rules its own guards demand; * it tells the TABLE about the field (REC-1 / FLD-8 — a definition for a * field the table never declared is refused, which is the sentence every * "Add field" used to end on); and it stores the definition as a FIELD * rather than as a plain record, so the delete rules and the * formula-dependency check can see it. * * A choice list carries its `options` as words. The store keeps them the only * way FLD-5 / FLD-6 allows — as the records of a Table it declares for them — * so nobody is made to build a table before they can have a dropdown. */ fieldDeclare(args: { table_id: Uuid; spec: NewFieldDeclaration; }): Promise>; /** Several columns of one Table in ONE call and one transaction (`custom.field_declare_many`): all or none. */ fieldDeclareMany(args: { table_id: Uuid; specs: NewFieldDeclaration[]; }): Promise>; /** * A NEW TABLE IN ONE CALL (`custom.table_create`): its Home, the Table under it and its columns, in * one transaction, so a refusal anywhere leaves nothing behind. Prefer `declareTable(client, spec)`. */ tableCreate(args: { spec: Record; fields: NewFieldDeclaration[]; }): Promise>; /** One field's own settings. What it HOLDS is a conversion and is refused here by name. */ fieldUpdate(args: { field_id: Uuid; patch: FieldPatch; }): Promise>; /** * WHAT A CHANGE OF KIND WILL DO, before it is made (`custom.field_retype_preview`): the store runs the * same `field_update` patch inside a subtransaction, counts what converts, what is kept aside and the * choices it makes, and rolls every row back. Nothing is written. */ fieldRetypePreview(args: { field_id: Uuid; patch: FieldPatch; }): Promise>; /** * CHANGE WHAT A COLUMN HOLDS, through `custom.migrate_retype` (FLD-4 / T12). `to` is the store's * behavior word — `text`, `range` (numbers and dates), `boolean`, `list`, `relation`. Every value * that converts is converted; every value that does not is kept in that record's `_retired` * with the reason, never coerced and never deleted. The answer counts both. */ migrateRetype(args: { field_id: Uuid; to: string; note?: string; }): Promise>; /** * MERGE ONE RECORD INTO ANOTHER, through `custom.migrate_merge` (T5 / REC-21). The loser's values * the winner did not hold move across, the ones that differ are kept as ranked alternates, every * link to the loser now points at the winner, and the loser is archived with its id resolving to * the winner. Editor on BOTH records is decided first. The answer's `migration_id` is what * `migrateUndo` takes. */ migrateMerge(args: { winner_id: Uuid; loser_id: Uuid; note?: string | null; }): Promise>; /** Put a recorded Migration back (`custom.migrate_undo`): a retype or a merge, by its id. */ migrateUndo(args: { migration_id: Uuid; }): Promise>; /** * SPLIT ONE RECORD IN TWO, through `custom.migrate_split` (Rule 11 / REC-22). The named values * MOVE to a new record of the same Table; the original keeps its id, so every link to it still * resolves. Editor on the record is decided first. `migrateUndo(migration_id)` puts it back. */ migrateSplit(args: { record_id: Uuid; moved_keys: string[]; note?: string | null; }): Promise>; /** * EXTRACT A PARENT, through `custom.migrate_extract_parent` (Rule 11 / test 5). The named values * are COPIED up into a new record of `parent_table_id`, and this record is put under it. Its old * parent, if any, becomes the new parent's parent. `migrateUndo(migration_id)` puts it back. */ migrateExtractParent(args: { record_id: Uuid; parent_table_id: Uuid; moved_keys: string[]; note?: string | null; }): Promise>; /** * MOVE A RECORD TO ANOTHER TABLE, through `custom.migrate_retype`'s record arm (test 9 / REC-N-18). The * record keeps its id — every link to it still resolves — and its values whose column `to_table_id` lacks * are taken off and kept in History with the reason (`misfits`). Full (admin) on the record is decided * first; the table must be in the same organization. `migrateUndo(migration_id)` puts it back whole. */ migrateMoveRecord(args: { record_id: Uuid; to_table_id: Uuid; note?: string | null; }): Promise>; /** The Migration log for one record or Table (`custom.migrations`), newest first. */ migrations(args: { target_id?: Uuid | null; limit?: number; }): Promise>; /** * HOW LONG HISTORY IS KEPT (Rule 10 / test 14), through `custom.history_retention`. `floor_days` is the * organization's floor (knob `extensibility.user_tables.history_retention_floor_days`: 30 by default, an * organization may raise it, never lower it); `table_days` is the table's own, never below the floor. */ historyRetention(args?: { table_id?: Uuid | null; }): Promise>; /** Set a table's value-history retention (`custom.history_retention_set`); below the floor is refused in the store's words. */ historyRetentionSet(args: { table_id: Uuid; days: number; }): Promise>; /** Raise this organization's retention floor (`custom.history_retention_floor_raise`); a lower number is refused. */ historyRetentionFloorRaise(args: { days: number; }): Promise>; /** * ONE RECORD ON EITHER CLOCK, through `custom.query_record_as_of` (Rule 10 / test 6). * `recorded_at` answers what the store SAID at that moment; `world_on` answers what was TRUE of * the world on that date, read from `dated` values. Both may be given. `null` data means the * record is not visible to this person (or did not exist then) — an absence, not an empty record. */ recordAsOf(args: { record_id: Uuid; recorded_at?: string | null; world_on?: string | null; }): Promise | null>>; /** * REL-DISP. THE WORDS ONE RELATION CELL SHOWS, resolved by the store through * that column's own display spec — one column or several, joined by its * separator. Never resolve a relation label any other way: the store's * `custom._words_for` is the one body that decides what a record is called, it * asks the read-access ladder BEFORE it reads the name, and a record this reader * may not open comes back as the withheld sentence rather than its name or id. * A value that is not an id is handed straight back — it is already words. */ relationWords(args: { field_id: Uuid; value: string; }): Promise>; /** * The same answer for a WHOLE PAGE of cells in one round trip. Prefer this * wherever a grid, board, list or picker resolves more than one chip: the * per-id lane is what made a page of relation cells a page of requests. */ relationWordsMany(args: RelationWordsManyArgs): Promise> & { archived?: readonly string[]; icons?: Record; }>; /** * VISION-REACH G1. Which records of `table_id` each name names — its name column, case and * spacing ignored, only records this reader may see. Hand the answer to `relationNameIndexOf` * and pass that to the coercion as `relationNames`. Up to 5,000 names a call; more are sent in * groups by this method. */ relationNamesMatch(args: { table_id: Uuid; names: string[]; }): Promise>; /** * VIEWS-AND-FIELDS BL (REL-9). The REVERSE COLUMNS of a Table: one per relation column of another * table (or this one) that links here, read-only, virtual — never a Field of this Table, so * `applicableFields` does not list them. A linking table this reader may not know is left out. */ reverseColumns(args: { table_id: Uuid; }): Promise>; /** * A page of records' links through ONE reverse column, in one round trip per 200 records: * `{ [record id]: { total, links: [{ id, words }] } }`, only linking records this reader may see. * `limit` (1–50) and `offset` page one record's links — a record with thousands of links costs * one count and one page. A record with no links answers `{ total: 0, links: [] }`. */ reverseLinksMany(args: ReverseLinksManyArgs): Promise>>; /** * The same, through SEVERAL reverse columns in ONE call (`custom.reverse_links_many_fields`): * `{ [source field id]: { [record id]: { total, links } } }`. A store without the door answers * `door_absent` — ask `reverseLinksMany` per column then. */ reverseLinksFields(args: ReverseLinksFieldsArgs): Promise>>>; /** * NOTION-PROPS. A TWO-WAY LINK: makes (or returns) the reverse Field of relation `field_id` on its * target table (`custom.relation_reverse_field`), so the link is a column there that edits the same * links. Its key is the reverse column's key; `label` defaults to the linking table's name. */ relationReverseField(args: { field_id: Uuid; label?: string; }): Promise>; /** * NOTION-PROPS. Adds and removes links from the reverse side of a two-way link * (`custom.relation_reverse_set`): each id becomes a write of THAT record's forward Field. * Answers how many records changed. */ relationReverseSet(args: { record_id: Uuid; field_id: Uuid; add?: readonly Uuid[]; remove?: readonly Uuid[]; }): Promise>; /** Take a field off a table. Refuses the title field, the last field, and one another field reads through. */ fieldRetire(args: { field_id: Uuid; }): Promise>; /** * Bring a removed column back (`custom.field_restore`): the set that was retired with it, the * Table's declaration of those columns, and the links they made. The values never left. */ fieldRestore(args: { field_id: Uuid; }): Promise>; /** * DUPLICATE PROPERTY (Notion N5) — `custom.field_duplicate`: a new column right after this one, named * " (1)", the same kind (choices with their colors, unit, link, formula, rules, sensitivity), and * every record's value copied into it in one statement. A link column copies its definition only (its * values are links); a formula, lookup or roll-up works its answer out itself. Needs the table's * add-a-column rung, the same as `fieldDeclare`. */ fieldDuplicate(args: { field_id: Uuid; }): Promise>; /** * CHAIR-ROLLUP-HONEST. Is a column this lookup or roll-up reads archived — the link it reads * through, a back-link's linking column, or the far column it totals (`custom.field_source_archived`)? * `null` while every source is live. Bring it back with `fieldRestore({ field_id })`. */ fieldSourceArchived(args: { field_id: Uuid; }): Promise>; /** * A TABLE AS A KIND (KINDS-GLUE wave 3, N1): `custom.table_kind_facts` verbatim — the Table's * live Fields, choices and stamp, as the asking person. The door finds the Table's organization * itself, so this works for a Table in any organization the person may open; anything else is * refused (`42501`, the store's sentence). */ tableKindFacts(args: { table_id: Uuid; }): Promise>; /** * A TABLE'S ACCESS LEVEL (CONFIDENTIAL-BY-ORG-ADMIN): `custom.table_level_facts` verbatim - its level, * readers, who approves a change (`set_by`) and whether THIS person may (`can_set`). Draw the control only * when `can_set` is true; anyone who may know the table may ask. */ tableLevelFacts(args: { table_id: Uuid; }): Promise>; /** * MAKE A CUSTOM TABLE CONFIDENTIAL, or change whom it names (`custom.set_table_confidential`). Succeeds * only for an owner/admin of the table's own organization on an ordinary custom table; the approver, seat, * reason and date are recorded. `readers` is the list of `{field, level}` rules (a person field of the * table); `reason` is required. */ setTableConfidential(args: { table_id: Uuid; readers: TableReaderRule[]; reason: string; }): Promise>; /** THE WAY BACK: the same caller rule, the table is Organization again (`custom.set_table_organization`). */ setTableOrganization(args: { table_id: Uuid; reason: string; }): Promise>; /** * THE COLUMNS THIS TABLE HAD AND NO LONGER HAS (grids review 3): its retired Field records, * newest first, through the same archived door the Table archive uses — addressed at the Field * kernel (every Field is a record in it) and paged until the door answers short. */ fieldsRetired(args: { table_id: Uuid; }): Promise>; /** What the registry says about a token, and whether it can hold a value at all. */ entityTable(args: { token: string; }): Promise>; /** * The fields THIS organization added to that standard table. `include_retired: true` also answers the * ones taken away (`entityFieldRetire`), live first, each with `archived_at` set — the archived view a * screen offers, with `fieldRestore({ field_id })` to bring one back. Live fields carry `archived_at: null`. */ entityFields(args: { token: string; include_retired?: boolean; }): Promise>; /** The fields EVERY named organization added to that standard table, with their choices, in one read. */ entityFieldsAcross(args: { token: string; organization_ids: Uuid[]; }): Promise>; /** Add a column to a standard table. An organization admin's right - it changes the table for everybody. */ entityFieldDeclare(args: { token: string; spec: NewEntityFieldDeclaration; }): Promise>; /** One field's own settings. A TYPE change is refused here by name, as it is on a custom table. */ entityFieldUpdate(args: { field_id: Uuid; patch: Record; }): Promise>; /** Take a custom field off a standard table. The values already written stay in each row. */ entityFieldRetire(args: { field_id: Uuid; }): Promise>; /** May this person add a column here - and if not, the store's own sentence saying why. */ entityFieldRights(args: { token: string; }): Promise>; /** REC-53 in one answer: the row's real columns AND its custom values AND the definitions. */ entityRecordRead(args: { token: string; record_id: Uuid; }): Promise>; /** * AP-4: the custom rows that link to one platform record, by which field and in which table. * Keyed by the ROW's organization (`organization_id`), never the client's active one. Paged: * pass the previous answer's `next_cursor` as `cursor`. */ entityBackLinks(args: { token: string; record_id: Uuid; organization_id: Uuid; limit?: number; cursor?: string | null; }): Promise>; /** * THE one write door for a standard row (`custom.entity_row_write`, as the person): custom values * as a patch (a key left out is left alone, a key set to null is cleared with its envelope), * archive / restore, and real columns only where the store lists them. The row's own organization * governs; `expected_version` refuses a stale write; a change that changes nothing writes nothing. */ entityRowWrite(args: { token: string; /** * null ADDS a row (chairblk3_a): only for a token whose API facts say `create_via: "insert"` * (`drill_describe(...).api.create_via` — task and project), as one insert AS THE PERSON in * `organization_id` (default: this client's organization); the answer carries `created: true` * and the new `id`. Every other token refuses in its own words. */ record_id: Uuid | null; /** A change by id: only to say which organization the row must belong to. A new row: where it goes. */ organization_id?: Uuid | null; custom?: Record; columns?: Record; expected_version?: number | null; archive?: boolean | null; }): Promise>; /** Rows of that standard table with this custom value. Refuses a field nobody declared. */ entityRecordsFind(args: { token: string; key: string; value?: unknown; limit?: number; offset?: number; }): Promise>; /** The kernel Tables a caller writes a Home, a Field or a Rule into. */ personKernelId(): Promise>; fieldKernelId(): Promise>; ruleKernelId(): Promise>; /** Every record the Rule says is a member. A saved view's list is this, not a filter a screen ran. */ /** PRODUCTS row 7 — the whole board: its stage column, its stages in the declared * order, and every Rule WITH the sentence it says when it refuses. */ pipelineRead(args: { table_id: Uuid; }): Promise>; /** The store's own count and total per column, over what this person may see. */ pipelineBoard(args: { table_id: Uuid; measure?: string | null; /** * S2-PRIME — the view's question, either shape (`StoreFilter`). Given, the headings count and * total exactly the cards the same filter puts on the board (`custom.pipeline_board`'s * four-argument door); left out, the whole table this person may see, as before. */ filter?: StoreFilter | null; }): Promise>; /** Whether this card may go to that column — asked BEFORE the finger lets go. */ pipelineTransitionRefusal(args: { record_id: Uuid; to: string; }): Promise>; /** * Move a record to a stage. An ORDINARY record write, judged by the same Rules. * * It answers an OBJECT on every path. A gate whose `on_fail` is `require_approval` * comes back `applied: false` with the wait it filed — the record has NOT moved, and a * screen that treated the call's success as the move's success would show the card in * the wrong column while somebody is still deciding. */ pipelineMove(args: { record_id: Uuid; to: string; also?: Record; expected_version?: number | null; }): Promise>; /** * MOVE MANY CARDS IN ONE CALL (lane S4, `custom.pipeline_move_many`). Each card is judged by * `pipeline_move` exactly as one is, in its own savepoint: a refused card writes nothing and the * others still land. `expected_version` is required per card — a card that changed since the * board loaded answers `changed_since_loaded` and is never overwritten. A refusal of the WHOLE * call (a stranger, an empty batch, more than the `custom/batch_items_max` knob) is the error. */ pipelineMoveMany(args: { moves: PipelineMoveManyItem[]; }): Promise>; /** Declare (or re-state) a pipeline: the stage column and its transition Rules. */ pipelineDeclare(args: { table_id: Uuid; spec: PipelineSpec; }): Promise>; /** Which column of this table holds its stage, or null when it is not a board. */ tableStageField(args: { table_id: Uuid; }): Promise>; /** The closed list of what a gate does when it says no — the store is the authority. */ stageRuleOnFailKinds(): Promise>; /** Whether this organization's gates refuse or warn. One opinion, asked once. */ stageRuleEnforcement(): Promise>; /** What is waiting on this card: approvals filed against its own stage column. */ recordStagePending(args: { record_id: Uuid; }): Promise>; /** The same question for a whole board, in ONE call — what a Kanban badges cards from. */ pipelinePending(args: { table_id: Uuid; }): Promise>; /** * WHAT THIS GATE WOULD DO TO THIS BOARD TODAY — for a gate that has not been saved. * * The settings screen calls it on every edit of the condition, which is why it takes * the gate rather than a rule id: no saved-rule door can answer about a rule that does * not exist yet. The store compiles it with the same compiler `pipelineDeclare` uses, * so the number here and the refusal at the door are the same rule — a preview with its * own compiler would be wrong exactly at the edge of the condition, which is the only * place anybody looks. * * A gate that does not say what it demands is REFUSED, not answered zero: "no cards * would be stopped" beside a half-written rule is the sentence that gets it saved. */ pipelineGatePreview(args: { table_id: Uuid; stage: string; gate: PipelineGate | Record; }): Promise>; /** * SERVER LANE ONLY. `custom.rule_members` walks the whole scope table through the evaluator and * returns STORED rows — no read-access ladder, no masking — so no browser role holds EXECUTE on it * and it answers 42501 from a signed-in seat. A screen reads a Rule's members through * `ruleMembersVisible`. */ ruleMembers(args: { rule_id: Uuid; }): Promise>; /** * S2-PRIME — THE MEMBERS OF A MEMBERSHIP RULE THIS PERSON MAY SEE, a page at a time * (`custom.rule_members_visible`): the list door asked the Rule's own test, so the rows are the * read door's — the same ladder, masked documents, the same page ceiling. */ ruleMembersVisible(args: { rule_id: Uuid; limit?: number; offset?: number; }): Promise>; /** Is this one record a member, and WHY — the store's own answer. */ ruleMembership(args: { rule_id: Uuid; record_id: Uuid; }): Promise>; /** Run a stored Rule against a document. */ ruleRun(args: { rule_id: Uuid; values: Record; context?: Record; }): Promise>; /** Evaluate a Rule expression that has not been stored — the conditional logic a form runner asks about. */ ruleEval(args: { expr: unknown; values: Record; context?: Record; }): Promise>; /** * Write ONE Rule as a real Rule. `recordWrite` into the Rule kernel makes a * valid Rule document that carries `data_class = 'record'`, and every reader * that looks for a Rule looks for the class — `custom.agg_subscriptions` * selects `where data_class = 'rule'` — so a subscription written that way * fires at nobody. This is the door that closes it. */ /** This organization's forms, with how many answers arrived and how many are in the table. */ forms(args?: { table_id?: Uuid | null; }): Promise>; /** * THIS ORGANIZATION'S BOOKING PAGES (PRODUCTS row 14), with the numbers that make * the list worth opening: how many are booked, canceled, still ahead, and how many * slots somebody is holding right now. A booking page is an `anon_form`, so it is * opened and closed with `anonPublish` exactly as a form is. */ bookings(args?: { table_id?: Uuid | null; }): Promise>; /** * CREATE OR RE-STATE ONE BOOKING PAGE over one Table. `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. * * `slot`, `status` and `booked_with` are the store's to fill in: a question * naming any of the three is refused BY NAME, so a page cannot be built that * lets a visitor choose their own status. It does NOT publish — `anonPublish` * is the one act that opens a public surface, for a booking page exactly as * for a form. * * Until 2026-09-21 this door had no client method, so the ONLY way to a * booking page was to ask an agent for one. The agent path is the faster one, * never the only one. */ bookingDeclare(args: { table_id: Uuid; title: string; questions: unknown[]; availability?: Partial | null; presentation?: Record | null; submission_cap?: number | null; quarantine_rule_id?: Uuid | null; notify_rule_id?: Uuid | null; form_id?: Uuid | null; slug?: string | null; home_id?: Uuid | null; }): Promise>; /** Create or re-state one form. A question naming no Field of that Table is refused BY NAME. */ formDeclare(args: { table_id: Uuid; title: string; questions: unknown[]; presentation?: Record | null; submission_cap?: number | null; quarantine_rule_id?: Uuid | null; notify_rule_id?: Uuid | null; form_id?: Uuid | null; slug?: string | null; }): Promise>; /** * WHICH OF A FORM'S QUESTIONS ARE ASKED, given the answers so far — for the OWNER's preview of * a draft (`custom.form_preview_asks`). One row per question in order; `asked` is false only * when the store's own Rule said so. Undecided and unworkable conditions are asked, the second * with the store's sentence in `said`. */ formPreviewAsks(args: { table_id: Uuid; questions: unknown[]; values: Record; }): Promise>>; /** * WHAT A FORM'S LOOK MAY BE, AND WHAT IT WOULD LOOK LIKE (`custom.form_look_options`): the * organization's public pictures (the same list a portal's look offers), the colors, and the * look a stranger would see for the draft `presentation` — the builder's preview draws that. */ formLookOptions(args?: { presentation?: Record | null; }): Promise>; /** * THE ROUTE THROUGH A DRAFT FORM (`custom.form_preview_route`, lane TYPEFORM-DUP): which questions * are asked given the answers so far (showIf AND logic jumps), the ending reached and the score — * the store's walk, the one the stranger's page and `custom.form_submit` read too. */ formPreviewRoute(args: { table_id: Uuid; questions: unknown[]; endings?: unknown[] | null; values: Record; }): Promise>; /** A form's results (`custom.form_results`): views, starts, completions, drop-off, answers. */ formResults(args: { form_id: Uuid; }): Promise>; /** TYPEFORM-2: the organization's form knobs (`custom.form_knobs`): owner header, choice auto-advance. */ formKnobs(): Promise>; /** * CD-PAGE-08: a public form's password (`custom.form_set_password`). A string of 4 to 128 characters sets it, * `""` clears it, `null` changes nothing; every call answers whether the form has one now. */ formSetPassword(args: { form_id: Uuid; password: string | null; }): Promise>; /** * What the store makes of a file's columns, before anything is written. * `columns` is `[{header, samples}]` — the first page of the file is plenty. */ importPlan(args: { table_id: Uuid; columns: Array<{ header: string; samples: unknown[]; }>; }): Promise>; /** * Open a run. `file_hash` is the run's 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. `force` re-opens deliberately. */ importBegin(args: { table_id: Uuid; format?: "csv" | "xlsx"; source_name?: string | null; source_columns?: unknown[]; file_hash?: string | null; policy?: ImportPolicy; /** A Field key. A row whose value for it is already here is a duplicate. */ dedupe_key?: string | null; file_bytes?: number | null; force?: boolean; }): Promise>; /** * IMPORT-2. ADD THE COLUMNS THIS FILE HAS AND THIS TABLE HAS NOT — a step of * its own, BEFORE the run is opened, so the screen can show "8 columns added" * and the person can then name one of them as the file's duplicate key * (`importBegin` refuses a key naming a column that does not exist yet). * Answers the mapping the file should be sent with. Admin on the Table. */ importDeclareColumns(args: { table_id: Uuid; rows: Array>; /** Header -> Field key the person already mapped by hand; never declared. */ mapping?: Record; }): Promise>; /** * One BATCH of rows. The batch is PLANNED and then written as ONE statement * through the batched write door, so 250 round trips became one; a batch a * guard refuses falls back to one write a row, and the answer says which it * was. Send `ImportOpened.rows_per_call` the first time and * `ImportBatchResult.rows_per_call` — measured — every time after that. */ importRows(args: { import_id: Uuid; rows: Array>; /** Header -> Field key. A header that already spells a key needs no entry. */ mapping?: Record; }): Promise>; /** * Close the run and decide the columns the file had and the table did not: * `propose` (the ONE approvals inbox, and the only choice a non-admin has), * `create` (an admin adds them outright) or `ignore`. */ importFinish(args: { import_id: Uuid; unmapped?: "propose" | "create" | "ignore"; }): Promise>; /** The refused rows, or the ones already here, WITH the source row on each. */ importReport(args: { import_id: Uuid; state?: "refused" | "duplicate"; limit?: number; offset?: number; }): Promise>; /** This organization's import runs, newest first. How you find yesterday's. */ imports(args?: { table_id?: Uuid | null; limit?: number; }): Promise>; /** * Forget the RECEIPT for a file so the same bytes can be imported again * deliberately. Never touches the records that import wrote. */ importForget(args: { import_id: Uuid; }): Promise>; /** * Give a table its own address. The answer carries `mail_route`: the one * sentence saying what has to exist in DNS before anything arrives. Show it * wherever the address is shown — an address that silently swallows mail is * the failure this whole door is against. */ inboundDeclare(args: { table_id: Uuid; label?: string | null; }): Promise>; inboundAddresses(args?: { table_id?: Uuid | null; }): Promise>; inboundSet(args: { inbound_id: Uuid; enabled: boolean; }): Promise>; /** Every portal over this organization the caller may know about, with its numbers. */ portals(): Promise>; /** * The SAME list, archive-aware. `custom.portals` keeps its signature and hides * archived portals, because the archived-items law's default is to hide; * this is the one door that also answers the other lane, so a rail gets the * law's one-click reveal without inventing a second control or a second door. * A fourth word is refused `22023` — there is no fourth answer on any surface. */ listPortals(args?: { archived?: PortalArchiveFilter; }): Promise>; /** * PUT ONE PORTAL AWAY. Nothing is deleted and there is no purge: every Table * it exposes and every person invited to it is kept exactly as it is, and * `portalRestore` hands it all back. `confirm_title` is the portal's own title * typed back — ten portals can carry one title, so the title is not what picks * this one and the door refuses `23514` when it does not match. */ portalArchive(args: { portal_id: Uuid; confirm_title: string; reason?: string | null; }): Promise>; /** The way back. Everyone invited signs in exactly as before, and it shows the same Tables. */ portalRestore(args: { portal_id: Uuid; confirm_title: string; }): Promise>; /** * PUT ONE FORM OR BOOKING PAGE AWAY (`custom.form_archive`, admin on its table). Its address stops * answering; everything it collected is kept. `formRestore` brings it back. */ formArchive(args: { form_id: Uuid; }): Promise>; formRestore(args: { form_id: Uuid; }): Promise>; /** The archived forms and booking pages, newest first; `table_id` narrows to one table. */ formsArchived(args?: { table_id?: Uuid | null; limit?: number; }): Promise>; /** * The archived checklist templates. A template is a record: `recordDelete` archives it and * `recordRestore` brings it back. */ checklistTemplatesArchived(args?: { about_table_id?: Uuid | null; limit?: number; }): Promise>; /** * How a table names its records: the row-label FORMULA it keeps (`{First} & " " & {Last}`), the title * column it falls back to, which is in force, and whether the caller may change it. Writes nothing. */ tableRowLabel(args: { table_id: Uuid; }): Promise>; /** * Set the table's row-label formula, or clear it (null / blank). The store parses it against the table's * own columns and refuses an unknown column in a sentence, writing nothing. Answers the new state. */ tableRowLabelSet(args: { table_id: Uuid; formula: string | null; }): Promise>; /** What a table's NEW rows start with (Shown to, and the web half once it lands). */ tableRowDefaults(args: { table_id: Uuid; }): Promise>; /** Set them (the table's maker or an admin of it). A key left out is left alone; null = the default. */ tableRowDefaultsSet(args: { table_id: Uuid; defaults: TableRowDefaultsPatch; }): Promise>; /** * ONE PASS of "apply to existing rows": rows nobody set by hand follow the table's defaults. * `done: false` → call again with `after: next_after`. `rowDefaultsApplyAll` walks every pass. */ tableRowDefaultsApply(args: { table_id: Uuid; after?: Uuid | null; }): Promise>; /** Every pass of `tableRowDefaultsApply`, summed; the last pass's sentence when it took one pass. */ rowDefaultsApplyAll(args: { table_id: Uuid; maxPasses?: number; }): Promise>; /** One row's Shown to (and web half), with who may change it. */ recordRowControls(args: { record_id: Uuid; }): Promise>; recordRowControlsSet(args: { record_id: Uuid; controls: { shown_to?: string | null; published_to_web?: boolean; indexed?: boolean | null; }; }): Promise>; /** * Which Tables each portal of this organization exposes. * * The one question a TABLE-scoped Portals rail has to ask: "is the table I * am standing on in any portal?" Same wall and same read-access gate as * `portals`, so a pair here is always a portal `portals` also returned. */ portalTables(): Promise>; /** ONE portal in full: what it shows, and who it was opened to. */ portalCard(args: { portal_id: Uuid; }): Promise>; /** * Create or RE-STATE one portal. Re-stating REPLACES what it exposes — a * Table left out of `tables` stops being exposed in the same act — and leaves * the people alone: who is invited is not part of what the portal shows. */ portalDeclare(args: { title: string; client_table_id: Uuid; tables: Array<{ table_id: Uuid; names_via: string; visible_fields?: string[]; editable_fields?: string[]; comments?: boolean; }>; portal_id?: Uuid | null; slug?: string | null; sign_in_method?: string | null; /** * S6 — the portal's own look and its forms. A key given REPLACES that key; a key not given * is KEPT (leave `config` out and both stay exactly as they are). The store judges it and * refuses by name: a logo that is not a public picture of this organization's Files, an * accent that is not a design-system color name, a form whose Table the portal does not show. */ config?: PortalConfigInput | null; }): Promise>; /** * S6 — THE CLIENT'S SIDE: one form of HER portal (`custom.portal_form`). Refused unless the * caller is a live principal of that live portal and the form is on its list; the Field that * names the client is never one of the questions — the portal fills it. */ portalForm(args: { portal_id: Uuid; form_id: Uuid; }): Promise>; /** * S6 — send one form of her portal (`custom.portal_form_submit`). The store fills the Field * that names the client with her own record, and the answer reaches her own list at once * (`accepted`) or waits for the office (`held`, with the store's sentence). `client_key` * makes a retry the same submission. */ portalFormSubmit(args: { portal_id: Uuid; form_id: Uuid; answers: Record; client_key?: string | null; }): Promise>; /** * Invite one person, as one record of the portal's client Table. * * `user_id` is deliberately NOT a parameter here. From a screen it is always * null: an invitation confers NOTHING until the person follows their sign-in * link and the platform gives them an identity, and the door's own `say` * sentence states exactly that. A screen that passed an id would be granting * access on somebody else's behalf. */ portalInvite(args: { portal_id: Uuid; client_record_id: Uuid; email: string; }): Promise>; /** Take one person's way in away. The answer's `say` names the consequence. */ portalRevoke(args: { portal_id: Uuid; principal_id: Uuid; }): Promise>; /** * 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 this * cannot disagree with what she sees. It REFUSES with `02000` and a sentence * when she has not signed in yet; show the sentence, never an empty list. */ portalPreview(args: { portal_id: Uuid; principal_id: Uuid; table_id: Uuid; }): Promise>; /** This organization's dashboards, optionally only those whose subject is one Table. */ dashboards(args?: { table_id?: Uuid | null; }): Promise>; /** * AT-INTERFACE — one Interface as this viewer may see it (no organization needed: a share of the * interface record is the way in). `null` = not there, archived, or not shared with her. */ interfaceRead(args: { interface_id: Uuid; }): Promise>; /** The rows one PUBLISHED element of an Interface exposes (a builder may name a draft one). */ interfaceRows(args: { interface_id: Uuid; element_id: string; record_id?: Uuid | null; limit?: number; offset?: number; }): Promise>; /** * AT-FIX-G1b — one PUBLISHED chart of an Interface as its reader may see it, in the shape one block of * `dashboardRun` answers (the same chart draws from it). The groups and measures are worked out by the store * over the rows her interface access shows and only through the columns the interface door exposes; a chart * that reads any other column answers `refused` (naming none). `null` = not there, archived, or not shared with her. */ interfaceChart(args: { interface_id: Uuid; chart_id: string; }): Promise>; /** * Create or re-state one dashboard. Every block is judged on the way IN — * a block naming a field this Table does not have is refused BY NAME, with * the table's own field list in the hint — so a canvas cannot be saved broken. */ dashboardDeclare(args: { table_id: Uuid; name: string; blocks?: DashboardBlock[]; presentation?: Record | null; dashboard_id?: Uuid | null; }): Promise>; /** * Run every block and answer the numbers. `filter` is the ONE filter bar * across the canvas: it is merged UNDER each block's own filter, so a block * deliberately scoped to one stage stays scoped while the bar moves the rest. * A block that refuses carries the store's sentence and the others still answer. */ dashboardRun(args: { dashboard_id: Uuid; filter?: RecordFilter; /** * S3 — compare every block that does not carry its own comparison (the * block's own wins, as its own filter does). A block with no date to * compare along answers alone and says so in `compare_refused`. */ compare?: AggregateCompare | null; /** S3 — re-cut every bucketed block to one date grain (the canvas's picker). */ grain?: AggregateBucket | null; }): Promise>; /** The seventh block kind on its own: records that have sat on the same value too long. */ dashboardStuck(args: { table_id: Uuid; state_key: string; days?: number; filter?: RecordFilter; limit?: number; }): Promise>; /** * Declare or re-state a crew capture sheet over a Table. Admin on the Table. * Closed to the crew until `capturePublish`, like every publishing act here. */ captureSheetDeclare(args: { table_id: Uuid; title: string; questions: Array>; presentation?: Record | null; sheet_id?: Uuid | null; notify_rule_id?: Uuid | null; }): Promise>; /** Open the sheet to the crew, or close it. Admin on the Table. */ capturePublish(args: { sheet_id: Uuid; open?: boolean; }): Promise>; /** The crew sheets of this organization, with what has come in through each. */ captureSheets(args?: { table_id?: Uuid | null; }): Promise>; /** * Everything a phone needs to run one sheet OFFLINE — the questions, the Fields * with their ids and parity types, and an honest `may_capture` with a sentence. * Load it once on a signal; it is the last network call before the walk. */ captureOpen(args: { sheet_id: Uuid; }): Promise>; /** * One capture: the answers, the file references, the device, the moment the phone * took it and the place. Idempotent on `client_key`, which the PHONE mints when * the capture is taken, offline, before its first attempt — send the same key on * every retry and the answer comes back `replay: true` with the first record's id. */ captureSubmit(args: { sheet_id: Uuid; client_key: string; answers: Record; files?: CaptureFileRef[]; device?: string | null; captured_at?: string | null; location?: Record | null; }): Promise>; /** Every agent-owned column, with how many cells are filled, stale or pinned, and what it has cost. */ enrichments(args?: { table_id?: Uuid | null; }): Promise>; /** * Define, change or TURN ON an Enrichment on one Field. Admin on the Table. * `{enabled: true}` on its own turns an existing one on without resetting it — * and turning it on is what authorises every value it writes afterwards. */ enrichDeclare(args: { field_id: Uuid; spec: Record; }): Promise>; /** * The rows that need this column filled in, each with what the instruction may * read. A cell somebody pinned is never returned. This is what a PREVIEW shows * before anybody enables anything. */ enrichDue(args: { field_id: Uuid; limit?: number; include_fresh?: boolean; }): Promise>; /** * THE BADGE DATA FOR A WHOLE PAGE, in one call. Who wrote each value, when, out * of what source, its candidates, whether a person pinned it and whether it is * past its freshness date. Asked per cell this would be one round trip per badge. */ enrichCells(args: { table_id: Uuid; field_keys?: string[] | null; record_ids?: Uuid[] | null; }): Promise>; /** Hold one cell against the model that fills its column, or hand it back. */ enrichPin(args: { record_id: Uuid; field_key: string; pinned?: boolean; }): Promise>; /** What this column has cost, run by run. */ enrichRuns(args?: { field_id?: Uuid | null; limit?: number; }): Promise>; /** Take a dashboard away. The records it counted are untouched — a canvas owns nothing. */ dashboardDelete(args: { dashboard_id: Uuid; }): Promise>; /** * Group, bucket, filter and measure INSIDE `custom.record_aggregate`. The * aggregate node sits above the read-access join, so what a chart draws is * exactly what this person may see — the rest is never fetched, let alone * summed and then hidden. */ recordAggregate(args: { table_id: Uuid; /** * OPTIONAL FILTER (active-org law): with `config.organizationIds` set, the table is looked * up across all the person's organizations and the aggregate runs in the one that OWNS it * (each row then carries that `organization_id`); this narrows the lookup to one. Absent and * no `organizationIds` ⇒ this client's own organization, exactly as before. */ organization_id?: Uuid | null; /** Field keys whose values become the groups. */ groupBy?: string[]; /** `count` needs no key; every other operation reads one Field. */ measures?: AggregateMeasure[]; /** A date Field truncated to a period — a group whose expression is a date_trunc. */ bucket?: { key: string; by: AggregateBucket; } | null; /** * A scalar is an equality and an object is a half-open moment window, and * either way it lands in the SAME WHERE as read access. */ filter?: StoreFilter; limit?: number; /** * S3 — answer the same question over a prior window too (previous period, * same period last year, or a fixed baseline). Each row then carries * `prior_measures`, `delta` and the two `compare` windows. Buckets are cut * in the organization's calendar (knobs `custom/time_zone`, `custom/week_start`). */ compare?: AggregateCompare | null; /** * The grid's search, narrowing the answer to the rows `listPage`'s search would show (the * same predicate, `custom.record_search_sql`). Blank or absent: nothing changes. */ search?: string | null; /** The reader's time zone for the search's date matches (absent = the organization's calendar). */ time_zone?: string | null; /** * A CHART counts each part of a many-value column under its own group (a row tagged A and B counts once under A and once * under B; a multi-link counts per linked record id); a grid group-by (absent / false) keeps the exact combination. */ splitMulti?: boolean; }): Promise>; /** Historical aggregate computed by the store at its recorded-time read door. */ recordAggregateAsOf(args: { table_id: Uuid; recorded_at: string; measure?: string; field_key?: string | null; group_by?: string | null; match?: Record; }): Promise>>; /** The measures the store itself offers. Asked, never hardcoded in a picker. */ aggOperations(): Promise>; /** The periods the store itself buckets by. */ aggBuckets(): Promise>; /** * The organization's calendar (knobs `custom/time_zone`, `custom/week_start`), cut by the SAME door the * relative date filters and the chart buckets use. A calendar or timeline reads its first day of the week * from here, never from a constant of its own (lane NOTION-SMALL-4). */ aggCalendar(): Promise>; /** * A source's drill definition — its Dimensions, Measures, drill paths and first screen. * `{kind: "table", id}` is a custom Table; `{kind: "entity", token}` a standard table by its * registry token or a declared definition by its key. */ drillDescribe(args: { source: DrillSource; }): Promise>; /** * Ask a source a question: grouped, bucketed (day…year in the organization's calendar), * compared, pivoted. A standard table is counted AS THE PERSON (its own row security decides * every row); groups past the cap fold into one Other row and the total row says so. */ drillAsk(args: { source: DrillSource; question?: DrillQuestion; }): Promise>; /** The records behind a number, through the same filter, read as the person. */ drillRows(args: { source: DrillSource; where?: DrillQuestion["where"]; window?: DrillQuestion["window"]; sort?: DrillQuestion["sort"]; lane?: DrillQuestion["lane"]; columns?: string[]; limit?: number; offset?: number; /** The viewer's search term, asked of the door (`platform._drill_compile` reads `question.search`). */ search?: string; }): Promise>; /** * Every subscription in this organization, optionally narrowed to one saved * view or one cadence. There is no subscription table: each one is a Rule * record carrying a `subscription` block, so a subscription is readable, * historied and agent-writable like anything else. */ aggSubscriptions(args?: { saved_view_id?: Uuid | null; cadence?: string | null; }): Promise>; /** The cadences the store itself offers. Asked, never hardcoded in a picker. */ aggSubscriptionCadences(): Promise>; /** * The same cadences, through the door a PERSON may call. * `aggSubscriptionCadences` is the notifier's own and holds no client grant, * so a screen that asked for it was refused and fell back to a list typed * into the picker — which drifts from what the digest runner honors. This * one delegates to the same array, so there is exactly one list. */ subscriptionCadences(): Promise>; /** * WHAT THIS PERSON IS BEING TOLD ABOUT, and what they may switch off. * * Mine, plus anyone's over a Table I hold admin on — narrowed to Tables I can * already open, so the list never reveals one. `aggSubscriptions` above is the * NOTIFIER's reader and no client can call it; this is the owner's. */ subscriptions(args?: { table_id?: Uuid | null; }): Promise>; /** * Switch one on or off. Muting is honored in ONE place — the reader every * consumer goes through — so a screen can never say a subscription is off * while it keeps firing. */ subscriptionMute(args: { rule_id: Uuid; muted: boolean; }): Promise>; /** * 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 — by name — then * declares the Rule through `rule_declare`, because a Rule written as a record * carries `data_class = 'record'` and every reader that goes looking for a Rule * steps straight past it (REC-72). */ subscriptionDeclare(args: { table_id: Uuid; spec: SubscriptionSpec; }): Promise>; /** * SHOW ME ONE NOW. The summary this subscription would send at this moment, with * what entered, left and changed NAMED. It assembles only: nothing is delivered, * nothing is recorded and the watermark does not move. */ subscriptionPreview(args: { rule_id: Uuid; }): Promise>; /** * THE SAVED VIEWS OF A TABLE — what a subscription may be written over. Narrowed * in SQL as the definer to Tables this caller can already open, so the list can * never reveal a Table. */ views(args?: { table_id?: Uuid | null; }): Promise>; /** * SAVE ONE VIEW. VIEWER on the Table, not editor: writing down a question about a * Table is not changing it, and requiring editor would mean the people who read a * Table could not subscribe to anything in it. */ viewDeclare(args: { table_id: Uuid; spec: SavedViewSpecIn; }): Promise>; /** A Table's row templates ("New from template"). VIEWER on the Table. */ rowTemplates(args: { table_id: Uuid; }): Promise>; /** * SAVE ONE ROW TEMPLATE — `{name, values}` by Field key; the same name again changes it, and * `{template_id, archived: true}` archives it. EDITOR on the Table: it is how everyone's new rows start. */ rowTemplateDeclare(args: { table_id: Uuid; spec: { name?: string; values?: Record; template_id?: Uuid; archived?: boolean; }; }): Promise>; /** * DESIGNATE HOW A TABLE OPENS — "Make this the default". EDITOR on the Table: the named view * becomes the table's only default and, when `layout` is given, that is the layout everyone opens * it in. A layout press never designates (`viewDeclare` refuses the default view's layout and * is_default by name — VIEW-SWITCH-NOT-DESIGNATION, VERIFIER-21). */ viewDesignate(args: { table_id: Uuid; view_id: Uuid; layout?: string | null; }): Promise>; /** * ARCHIVE ONE SAVED VIEW (lane TABLE-ACTIONS, T3.5). The soft delete of the `platform.saved_view` * row behind it — `platform.saved_view_archive('custom/records', id)`, which asks its own ladder * (the view's creator, or admin on the view) and clears a default it held. The records are * untouched. `null` when the view was already gone. `viewRestore` (or Trash) brings it back. */ viewArchive(args: { view_id: Uuid; }): Promise | null>>; /** PUT AN ARCHIVED VIEW BACK — the platform's one restore door, `platform.entity_undelete('platform_saved_view', id)`. */ viewRestore(args: { view_id: Uuid; }): Promise>; /** * THE CALLER'S OWN LOOK AT EACH SAVED VIEW OF A TABLE — `{ "": { look, updated_at } }`. * A look is the view settings one person chose while looking (a board's grouping, a calendar's * date field, a sort, a filter, hidden columns, widths, the layout), kept for her alone until an * editor saves it onto the view with `viewDeclare` ("Save to view"). Viewer on the Table. */ viewLookRead(args: { table_id: Uuid; }): Promise; updated_at: string; }>>>; /** * KEEP THE CALLER'S OWN LOOK AT ONE VIEW — the whole look, as definition keys (`group_field`, * `date_field`, `sorts`, `where`, `presentation`, `layout`, …; a key set to null hides the view's * own). `look: null` is "Reset to view". The shared view row is never written. Answers the look * as kept (null after a reset). */ viewLookSet(args: { table_id: Uuid; view_id: Uuid; look: Record | null; }): Promise | null>>; docTemplateSave(args: { table_id: Uuid; name: string; body: string; template_id?: Uuid | null; }): Promise>; /** The body with every token resolved against one record. Read-only, no render row. */ docRenderBody(args: { template_id: Uuid; record_id: Uuid; }): Promise>; /** Write the render — frozen bytes a signature can be checked against. */ docRenderDocument(args: { template_id: Uuid; record_id: Uuid; }): Promise>; /** Every token in a body, in order, with the Field id each one points at. */ docTokens(args: { body: string; }): Promise>; /** The tokens this Table cannot answer, each with the store's own reason. */ docUnresolvedTokens(args: { table_id: Uuid; body: string; }): Promise>; docSign(args: { render_id: Uuid; field_key: string; signer_name: string; signer_user_id?: Uuid | null; }): Promise>; /** * One signature: the seal AND whether what was signed is still what is there. * The store answers both, in one call, never the screen. Viewer on the record. */ docSignatureRead(args: { signature_id: Uuid; }): Promise>; /** Every template that renders one Table. Viewer on the Table. */ docTemplates(args: { table_id: Uuid; }): Promise>; /** * Retire one template. EDITOR on the Table it renders, because a template is * that Table's wording. Documents already rendered keep their frozen bytes * and their seals, so this can never invalidate a signature. */ docTemplateDelete(args: { template_id: Uuid; }): Promise>; /** Bring back a removed template (the undo of docTemplateDelete, same rung). */ docTemplateRestore(args: { template_id: Uuid; }): Promise>; /** * Remove one saved document (soft). EDITOR on its record. Its bytes, hash and * any signature stay exactly as they were; docRenderRestore brings it back. */ docRenderDelete(args: { render_id: Uuid; }): Promise>; /** The undo of docRenderDelete, same rung. */ docRenderRestore(args: { render_id: Uuid; }): Promise>; /** Every frozen render of one record, newest first. Viewer on the record. */ docRenders(args: { record_id: Uuid; }): Promise>; /** Every signature on one record. Viewer on the record. */ docSignatures(args: { record_id: Uuid; }): Promise>; /** * DOOR-11. One record's revisions, newest first. The read door answers * documents and deliberately not versions, so a screen that wants to save * optimistically reads the version of the record it LOADED from here — for * the one record being edited, never for a list. */ revisions(args: { record_id: Uuid; }): Promise>; /** * PRODUCTS row 4. One record's version list, newest first: who (a person, or * an agent AND the person it acted for), when, 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 source pointer. Viewer * on the record — reading how something got to be the way it is is reading. */ recordHistory(args: { record_id: Uuid; limit?: number; offset?: number; }): Promise>; /** * AGT-N-9 — EVERYTHING A CHAT OPENED ON THIS RECORD MAY KNOW, IN ONE ROUND TRIP. * * The Fields with their `(record, field, value version)` triples, the relations one hop * out, the history, the comments and the Table's other records — assembled INSIDE the * database, every piece through the read door, under this person's own field-level * security. A Field the store masks comes back in `withheld` with the store's own * sentence and no value; a Field the organization keeps out of conversations comes back * there too, saying which of the two it is. */ recordScopeContext(args: { record_id: Uuid; relations?: number; history?: number; siblings?: number; }): Promise>; /** What is this conversation about? */ conversationScope(args: { conversation_id: Uuid; }): Promise>; /** Point a conversation at one record. Refused unless this person may open it. */ conversationScopeBind(args: { conversation_id: Uuid; record_id: Uuid; }): Promise>; /** This conversation is no longer about a particular record. */ conversationScopeUnbind(args: { conversation_id: Uuid; }): Promise>; /** The binding AND the context of a bound conversation, in one round trip. */ conversationScopeContext(args: { conversation_id: Uuid; relations?: number; history?: number; siblings?: number; }): Promise>; /** * "Who changed the price on ANY record?" One column's history across a whole * Table, 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, never * answered empty. */ fieldHistory(args: { table_id: Uuid; field_key: string; limit?: number; offset?: number; record_id?: Uuid; }): Promise>; /** * A FORMULA, ROLLUP OR LOOKUP COLUMN'S HISTORY ON ONE RECORD (CD-FORMULA-08). Its answer at every moment it changed, * worked out from the values and the formula as they were, with the formula version that produced it. A column * people type into is refused by name (its history is `fieldHistory`). */ formulaHistory(args: { record_id: Uuid; field_key: string; limit?: number; }): Promise>; /** * WHAT A RESTORE WOULD CHANGE, BEFORE IT DOES. Editor on the record: this is * the first half of an act, and somebody who may not perform it is not shown * its preview. Pass `field_key` for one column. */ restorePreview(args: { record_id: Uuid; version: number; field_key?: string; }): Promise>; /** Put the whole record back to a version, as a NEW version. Editor. */ restoreVersion(args: { record_id: Uuid; version: number; }): Promise>; /** Put ONE column back to what it said at a version, as a NEW version (HIS-N-2). Editor. */ valueRestore(args: { record_id: Uuid; field_key: string; version: number; }): Promise>; /** * SCR-18. The record's comment thread with everybody named, plus what THIS * person may do in it — decided by the store, in the same call, so a screen * never draws a composer the write door will refuse. */ commentThread(args: { record_id: Uuid; include_resolved?: boolean; }): Promise>; /** * How many OPEN comments each named row has (up to 500 rows; rows with none are absent), read * through the same reader as the thread — one call for a whole card of rows. */ commentCounts(args: { record_ids: readonly Uuid[]; }): Promise>>; /** * Write a comment at the `commenter` rung, optionally naming people. A * mention is a reference and not a regex over the body: the ids come from the * picker and the store refuses one who is not in the organization, or who * cannot open the record — because that notification would be a dead end. */ commentWrite(args: { record_id: Uuid; body: string; field_key?: string; parent_comment_id?: Uuid; mentions?: Uuid[]; }): Promise>; /** Resolve (or re-open) a thread. `commenter` — resolution is not a delete. */ commentResolve(args: { comment_id: Uuid; resolved?: boolean; }): Promise>; /** * Follow ONE record (AT-FIX-G2; Airtable's "follow a record"). While followed, a change or a comment by SOMEBODY ELSE * puts a notice in this person's bell, written in the same transaction as the change; the notice opens the record. * `follow: false` stops it. Anybody who may read the record may follow it; the row is always the caller's own. */ recordFollowSet(args: { record_id: Uuid; follow?: boolean; }): Promise>; /** Who follows this record, and whether this person does. Empty for somebody who cannot open the record. */ recordFollowers(args: { record_id: Uuid; }): Promise>; /** * Every value of one record WITH its envelope — its own version, who wrote * it, who they were acting for and when. The timeline IS the record (REC-19); * there is no second history table. */ recordValuesVersioned(args: { record_id: Uuid; organization_id?: Uuid | null; }): Promise>; /** * Metadata search across EVERY organization the person belongs to (`config.organizationIds`; * `organization_id` narrows to one): the Tables and Fields whose name, key, label or description * holds `text` (case-insensitive; structure, never rows). Each match is `{id, kind: "table" | * "field", name, description, organization_id}` (a Field adds `key`, `table_id`, `field_type`). * An organization whose read door refuses is listed in `unavailable` WITH its reason and * never hides the rest. Only when NO organization answers is the whole search a failure (the * first refusal). */ metadataSearch(args: { text: string; organization_id?: Uuid | null; }): Promise>; /** * WHICH of the person's organizations can this client use right now? Asks every organization * in scope (`organization_id` narrows to one) whether its record store is open, so a host can * say "organization X is unavailable: " instead of silently dropping it. Organization * order is the person's own, never completion order. */ organizationsOpen(args?: { organization_id?: Uuid | null; }): Promise>; /** * The organization that OWNS a table or record — the one a write to it must run in. A * `table_id` is looked up across all the person's organizations; a `record_id` is opened in its * own. `null` = found in none of them. Never a permission: the store's doors still decide. */ ownerOrganization(args: { table_id?: Uuid | null; record_id?: Uuid | null; organization_id?: Uuid | null; }): Promise>; /** * Mint an embed token. `mode` is `read` OR `write` and never both — the store * refuses a token carrying two decisions. The secret comes back ONCE, because * only its digest is stored: a database read cannot produce a working token. * `allowedOrigins` is required and matched EXACTLY, so `example.com.evil.test` * fails a check that a suffix match would pass. */ anonTokenIssue(args: { mode: "read" | "write"; allowedOrigins: string[]; formId?: Uuid | null; savedViewId?: Uuid | null; recordId?: Uuid | null; expiresAt?: Timestamp | null; }): Promise>; /** What a secret is good for, from an origin. The store's sentence when it is not. */ anonTokenVerify(args: { secret: string; origin: string; requiredMode?: "read" | "write" | null; }): Promise>; anonTokenRevoke(args: { token_id: Uuid; }): Promise>; /** * A STRANGER'S WRITE. The token supplies the organization, so this call names * none — and what it produces is a QUARANTINED SUBMISSION, not a record: * `custom.record` is never touched here. `clientKey` makes a replay free and * idempotent, which is what lets a flaky phone retry safely. */ anonWrite(args: { secret: string; origin: string; payload: Record; clientKey?: string | null; rawPayload?: Record; }): Promise>; /** * SCR-30, and it is the SIGNED-IN offline path, not the stranger's. The client * mints `clientKey` BEFORE the first attempt, offline, and the store's own * replay ledger dedupes on it — so a reconnect that fires the same capture * thirty times writes one record and returns the same id every time. */ anonCapture(args: { table_id: Uuid; clientKey: string; payload: Record; device?: string | null; capturedAt?: Timestamp | null; }): Promise>; /** Open or close a form to strangers. An admin act on the Table, never an editor one. */ anonPublish(args: { form_id: Uuid; published?: boolean; }): Promise>; /** * What is wrong with a checklist somebody is writing, in the words they would * use. `null` means it is fine. Pure — it reads no row and writes nothing — * so an editor may ask it on every keystroke and the refusal arrives BEFORE * the save rather than after it. */ checklistRefusal(args: { spec: ChecklistSpec; }): Promise>; /** * Store a checklist, whole: ordered steps, who each belongs to by role, how * many days in each is due, what each waits for, what each asks for, and what * starts a run. Pass `template_id` to change the checklist that already * exists rather than making a second one. */ checklistDeclare(args: { spec: ChecklistSpec; template_id?: Uuid | null; }): Promise>; checklistTemplates(args?: { about_table_id?: Uuid | null; limit?: number; }): Promise>; checklistTemplateShape(args: { template_id: Uuid; }): Promise>; /** * Run a checklist for one record: one work item per step in the ONE work * layer, assigned by role to real people, dated from the offsets, * dependencies carried across. `roles` names who stands in each role for THIS * run, over whatever the checklist says by default. */ checklistStart(args: { template_id: Uuid; about_record_id?: Uuid | null; roles?: Record; startingAt?: Timestamp | null; }): Promise>; checklistRun(args: { run_id: Uuid; }): Promise>; checklistRuns(args?: { about_table_id?: Uuid | null; about_record_id?: Uuid | null; includeClosed?: boolean; limit?: number; }): Promise>; /** * Tick a step off with whatever it asked for. Refused by name while a step it * waits for is open, or while what it asks for is missing — and the same * refusal meets `workSetState`, because the rule lives in the store and not * in this door. */ checklistStepComplete(args: { step_id: Uuid; evidence?: Record; }): Promise>; /** Why this step cannot be finished right now. `null` means it can. */ checklistStepRefusal(args: { step_id: Uuid; }): Promise>; /** The five states and what each one may become. Asked, never hardcoded in a picker. */ workStates(): Promise>; /** Why this graph would be refused — asked BEFORE it is written. `null` means it is fine. */ workTemplateRefusal(args: { graph: WorkGraph; }): Promise>; workTemplateDeclare(args: { name: string; graph: WorkGraph; }): Promise>; /** * REC-70: the WHOLE graph is created in one act, judged before any of it is * written, so a half-made checklist is not a state this store can be in. */ workTemplateInstantiate(args: { template_id: Uuid; overrides?: Record>; }): Promise>; workTemplateShape(args: { template_id: Uuid; }): Promise>; workInstantiationShape(args: { instantiation_id: Uuid; }): Promise>; /** Whose turn each record is, with its state and whether that state is terminal. */ workWhoseTurn(args: { table_id: Uuid; includeFinished?: boolean; }): Promise>; /** Does this Table carry assignment at all? A screen asks before it offers one. */ workHasAssignment(args: { table_id: Uuid; }): Promise>; /** The Fields assignment is made of, so nothing here invents a column name. */ workAssignmentFields(args?: { options_table_id?: Uuid | null; }): Promise>; /** Add assignment to a Table. */ workTakeAssignment(args: { table_id: Uuid; }): Promise>; /** Why this move would be refused. `null` means it is allowed. */ workTransitionRefusal(args: { from_state_id: Uuid; to_state_id: Uuid; }): Promise>; /** Declare the Table slots are held in. Idempotent — a second call finds the first. */ workSlotsDeclare(args: { name: string; slug: string; homeId?: Uuid | null; }): Promise>; /** * SCR-29. Take a slot for a while. THE STORE decides, under a unique index — * two people racing for the same slot do not both get it, whatever either * screen believed a moment earlier. */ workSlotHold(args: { table_id: Uuid; slotKey: string; holder: string; ttl?: string; }): Promise>; workSlotRelease(args: { hold_id: Uuid; }): Promise>; /** Every hold on this Table, with whether it has already lapsed. */ workSlotHolds(args: { table_id: Uuid; }): Promise>; workSlotExpire(args: { table_id: Uuid; }): Promise>; /** * THE ONE INBOX. Assignments, approvals and the agent's proposals, in one * ordered list with ONE row shape — PRODUCTS.md row 6. A second queue for the * agent's suggestions is the thing this replaces. */ workInbox(args?: { limit?: number; offset?: number; includeDecided?: boolean; /** Lane S5-PRIME-2: "inbox" (default), "snoozed" or "done" — this person's own views. */ view?: WorkInboxView; }): Promise>; /** * SNOOZE ONE ITEM OF MY INBOX UNTIL A TIME (lane S5-PRIME-2, `custom.inbox_snooze`). Only my * inbox changes; the approval or record is never written. A time already past, a snooze past a * year, and a decided item are refused by name. */ inboxSnooze(args: { item_id: Uuid; until: Timestamp; }): Promise>; /** Take my snooze off: it is back in my inbox now. */ inboxUnsnooze(args: { item_id: Uuid; }): Promise>; /** * CLEAR (mark done) ONE ITEM OF MY INBOX. A decision still owed is refused ("This one needs a * decision"). It comes back if somebody else changes it. */ inboxClear(args: { item_id: Uuid; }): Promise>; /** Put a cleared item back in my inbox. */ inboxUnclear(args: { item_id: Uuid; }): Promise>; /** * WHAT WAITS ON ME, COUNTED (`custom.inbox_counts`) — the same predicate the inbox lists with. * `everyOrganization` counts every organization I belong to (the shell badge: what waits on a * person is theirs, not the selected organization's). */ inboxCounts(args?: { everyOrganization?: boolean; }): Promise>; /** "My work", "work I assigned", "waiting on somebody being chosen". */ workList(args?: { flavour?: WorkListFlavour; includeFinished?: boolean; limit?: number; offset?: number; }): Promise>; /** * Give one record to one person: the Assignee value AND editor access, in one * transaction. Passing no `user_id` un-assigns and says plainly that nobody's * access was taken away. */ workAssign(args: { record_id: Uuid; user_id?: Uuid | null; dueDate?: string | null; clearDue?: boolean; }): Promise>; /** The states this record may be moved to, and the sentence for each it may not. */ workRecordStates(args: { record_id: Uuid; }): Promise>; /** Move it. The store refuses a move the model forbids, in its own words. */ workSetState(args: { record_id: Uuid; state_id: Uuid | null; }): Promise>; /** The person-kernel record standing for a signed-in person here. */ workPerson(args: { user_id: Uuid; create?: boolean; }): Promise>; /** The templates this person may run, so a picker has ids to offer. */ workTemplates(args?: { limit?: number; }): Promise>>; /** Who may decide a change about this subject, and why each of them may. */ workApprovalApprovers(args: { subject_id: Uuid | null; approver_id?: Uuid | null; }): Promise>; /** Ask for a change instead of making it. Viewer on the subject is enough to ASK. */ workApprovalRequest(args: { subject_id: Uuid; change: WorkChange; note?: string | null; approver_id?: Uuid | null; origin?: "person" | "agent"; conversation_id?: Uuid | null; }): Promise>; /** Whether THIS person may decide THIS one — so a screen never draws a dead button. */ workApprovalMayDecide(args: { approval_id: Uuid; }): Promise>; /** Approve or decline. Approving APPLIES the change, as the person approving. */ workApprovalDecide(args: { approval_id: Uuid; approve: boolean; note?: string | null; }): Promise>; /** * DECIDE MANY INBOX ITEMS IN ONE CALL (lane S4, `custom.work_decide_many`). Each is judged by * `work_approval_decide` exactly as one is, in its own savepoint: a refused decision leaves that * item waiting and the others still stand. Verdicts: approved, declined, already_decided, * no_right, refused — each with the store's own sentence. */ workDecideMany(args: { decisions: WorkDecideManyItem[]; }): Promise>; /** One waiting change in full, for the person deciding it or the one who asked. */ workApprovalRead(args: { approval_id: Uuid; }): Promise>>; /** VIS-31. Is this account a signed-in outsider — no NON-personal organization? */ isExternalPrincipal(args?: { user_id?: Uuid | null; }): Promise>; /** The same answer WITH the plain-English reason a screen shows. */ externalPrincipalCard(args?: { user_id?: Uuid | null; }): Promise>; /** Exactly the resource ids Visibility gave that outsider. Never an organization's list. */ externalPrincipalReach(args: { resourceType: string; user_id?: Uuid | null; }): Promise>>; /** VIS-32. Bind a record or a view to a public address with a declared render mode. */ publishBindingCreate(args: { slug: string; resourceType: string; resource_id: Uuid; renderMode?: string; }): Promise>; publishBindingRevoke(args: { slug: string; }): Promise>; /** What a SIGNED-OUT visitor resolves. Empty for an invented, revoked or unpublished address. */ resolvePublishBinding(args: { slug: string; }): Promise>; /** The same resolution plus what the address holds (table | record | dashboard | page | view). */ resolvePublishBindingKind(args: { slug: string; }): Promise>; /** D-15. The ONE sentence admitting nothing was scanned, so four surfaces cannot drift. */ worldPublishGapNotice(): Promise>; /** Put a resource in the world lane. Refuses by name while the lane is closed. */ publishToWorld(args: { resourceType: string; resource_id: Uuid; discoverable?: boolean; }): Promise>; /** * Declare a Rule. It is NOT `recordWrite` into the rule kernel, and that is * the whole point: `custom.record_write` inserts `data_class = 'record'`, so a * document written that way is not a Rule and the store's own rule doors * (`custom.rule_members`, `custom.agg_subscriptions`) never see it. Writing * one through the record door and calling it a Rule would be a silent * failure — a screen that looked like it had subscribed you to something. */ ruleDeclare(args: { spec: Record; rule_id?: Uuid | null; }): Promise>; fieldPropose(proposal: FieldProposal): Promise>; tablePropose(proposal: TableProposal): Promise>; promoteTable(args: { table_id: Uuid; }): Promise>; promoteField(args: { table_id: Uuid; field_id: Uuid; }): Promise>; /** * @deprecated The record store is never off (owner ruling, 2026-10-03: everything is on by * default). This answers `true` whenever the door answers; a refusal means the store could not * be reached, never that it is off. Do not gate a screen on it. */ storeIsOpen(): Promise>; /** * WHAT THIS PERSON MAY DO TO THESE THINGS — asked once, for a whole page. * * There is no principal argument, on purpose: the door resolves the caller * from the session, so a person can ask about their OWN access and about * nobody else's. A subject they hold nothing on answers `level: null`, which * is an answer; the row is never omitted, because a screen that could not * tell "no access" from "not asked yet" would draw one of them as the other. */ myLevels(args: { ids: Uuid[]; subject?: "record"; }): Promise>; /** G1. Write ONE leaf of a Table's colors, or clear it with `null`. Editor on the Table. */ tableDecorate(args: { table_id: Uuid; path: DecorationPath; value: unknown; }): Promise>; /** G1. A Table's colors as a screen paints them, with what went stale named. */ tableDecorations(args: { table_id: Uuid; }): Promise>; /** G1. The layout a grid opens with: platform → organization knob → the view's own. */ gridLayout(args: { table_id: Uuid; view_id?: Uuid | null; }): Promise>; /** G3. The older grid's formula text → the store's expression. Never refuses a typo: answers ok:false. */ formulaParse(args: { table_id: Uuid; text: string; }): Promise>; /** * NOTION-PROPS. The formula being typed, parsed AND worked out for one record of the table * (`custom.formula_preview`): the parse, plus `value` — what a saved formula column would show on * that record — or `error` when it parses and cannot be worked out there. Writes nothing. */ formulaPreview(args: { table_id: Uuid; record_id: Uuid | null; text: string; }): Promise>; /** G2. Declare a Table's row actions — the whole list. Editor on the Table. */ actionDeclare(args: { table_id: Uuid; actions: RowAction[]; }): Promise>; /** G2. A Table's row actions, each saying whether it can run here. */ rowActions(args: { table_id: Uuid; }): Promise>; /** * G2. Run one action on a selection, in ONE transaction. A refusal changed NOTHING; read * every record and field that refused with `actionRefusals(result.error)`. */ actionRun(args: { action_id: Uuid; record_ids: Uuid[]; }): Promise>; /** * AUTOMATION-DOOR. Declare (or, with `automation_id`, replace) one automation on a Table — editor on the Table. * A spec the store refuses comes back as `data.ok === false` with `_errors`, one sentence per path. */ automationDeclare(args: { table_id: Uuid; spec: AutomationSpec; automation_id?: Uuid | null; }): Promise>; /** AUTOMATION-DOOR. A Table's automations with their last run; archived ones only when asked. */ automations(args: { table_id: Uuid; include_archived?: boolean; }): Promise>; /** AUTOMATION-DOOR. Archive one (it switches off and keeps its history). Editor on the Table. */ automationArchive(args: { automation_id: Uuid; }): Promise>; /** AUTOMATION-DOOR. Bring an archived one back (it stays off until `automationSetEnabled`). */ automationRestore(args: { automation_id: Uuid; }): Promise>; /** AUTOMATION-DOOR. Switch one on or off. */ automationSetEnabled(args: { automation_id: Uuid; enabled: boolean; }): Promise>; /** AUTOMATION-DOOR. Run history, newest first: status, the store's own sentence, every step. */ automationRuns(args: { automation_id: Uuid; limit?: number; cursor?: string | null; }): Promise>; /** * AUTOMATIONS-AIRTABLE. Test an automation now (Airtable's "Run test"): its steps run once on `record_id` (none for * a schedule), switched on or not, and the run lands in its history as `trigger.on = "test"`. Editor on the Table. */ automationTest(args: { automation_id: Uuid; record_id?: Uuid | null; }): Promise>; /** AT-AUTO2. Every saved version of an automation, newest first. */ automationRevisions(args: { automation_id: Uuid; }): Promise>; /** AT-AUTO2. Restore one version: one more save through automation_declare (admin on the table). */ automationRevisionRestore(args: { automation_id: Uuid; revision_id: Uuid; }): Promise>; /** G4. Send every change to this Table's records to an https webhook. Admin. The secret is shown once. */ tableWebhookDeclare(args: { table_id: Uuid; target_url: string; events?: RecordEvent[] | null; description?: string | null; }): Promise>; /** G4. The webhooks a Table sends to, with their delivery health — never the secret. */ tableWebhooks(args: { table_id: Uuid; }): Promise>; /** G4. Switch one off. It is never deleted. */ tableWebhookArchive(args: { webhook_id: Uuid; }): Promise>; /** G5. Number the records an autonumber column found already there, in creation order. */ autonumberBackfill(args: { field_id: Uuid; }): Promise>; /** * AT-SHARE-B. A Person column's "Notify people when they're added" switch (`custom.field_notify_on_assign`). * `on: null` only READS whether it is on; `true` declares the column's two notify automations (edited, new * record), `false` archives them. Turning it on or off asks to change the table's structure. */ fieldNotifyOnAssign(args: { field_id: Uuid; on: boolean | null; }): Promise>; /** * NOTION-PROPS-3. PRESS A BUTTON COLUMN ON ONE ROW (`custom.button_press`): a link comes back filled from * the row for the screen to open; a row action or an automation runs in the store and its result comes * back. A button with nothing set up is refused by name (22023). */ buttonPress(args: { record_id: Uuid; field_id: Uuid; }): Promise>; /** * NOTION-PROPS-3. The knob `data/timeline_dependency_shift` for the signed-in person in this organization: * whether moving a record on a timeline moves the records waiting on it (default true). */ timelineDependencyShift(): Promise>; /** * FB5 (ADV-5 F4/F10): the record store's screen limits for the signed-in person in this organization, each a * knob (`custom.records_limits`): the AI column fill's try size, one fill's row cap and the Fill box's start, and * a table copy's stall bound. Asked once per client; a store without the door answers the knobs' defaults. */ recordsLimits(): Promise; /** G5. One page of the export door — exactly the rows and columns the read door serves this person. */ ioExport(args: { table_id: Uuid; columns?: string[] | null; limit?: number; }): Promise>; /** G5. That page as an .xlsx; a withheld column is named on its own sheet. */ /** * G9. Every table a picker may offer here, from BOTH stores, each saying which store opens it. * A table the app keeps out of default lists (`custom.table_kept_out_of_lists`: an agent's * outputs) is left out unless `include_platform_tables` is true — the "Show platform tables" switch. */ tableListEverywhere(args?: { include_platform_tables?: boolean; }): Promise>; /** * Every table the person reaches (`custom.data_home_tables`), as the data home lists them. * `organization_id` names one organization; `null` asks for every organization the person * reaches — never the client's bound organization by default. A table the app keeps out of * default lists (an agent's outputs) is answered only with `include_platform_tables: true`. */ dataHomeTables(args: { organization_id: Uuid | null; include_platform_tables?: boolean; }): Promise>; /** * Every table of EVERY organization (`custom.admin_all_tables`) — the admin section's list. The * store's walls admit a platform admin to system organizations only, so this is a separate door that * answers only on the admin lane for a platform admin; anyone else gets a refusal, not an empty list. */ adminAllTables(): Promise>; /** * G10. Refresh a synced source (a sheet tab) into ONE Table under `home_id`: new rows added, * changed rows updated, rows the source dropped archived — never destroyed; columns the * person added in Matrx survive. Build the spec with `sheetSyncSpec`. */ tableSync(args: { home_id: Uuid; spec: TableSyncSpec; }): Promise>; /** Lane SYNC-AIRTABLE: a table synced from another table's view, in the active organization. */ tableCopySync(spec: TableCopySpec): Promise>; /** Lane SYNC-AIRTABLE: auto-refresh / write-back of one synced table (editor of it). */ tableSyncSettingsSet(args: { table_id: Uuid; settings: TableSyncSettings; }): Promise>; /** * G11. The record-store Table a context scope's template-bound item provisions — one per item * and scope. A scope provisioned before the move answers with its moved Table (same id); the * Home defaults to the organization's own. */ scopeTableProvision(args: { item_id: Uuid; scope_id: Uuid; home_id?: Uuid | null; }): Promise>; /** G12. The rows of each Table this reader may see, in one call (fold with `withRowCounts`). A Table she may not know gets no row. */ tableRowCounts(args: { table_ids: Uuid[]; }): Promise>; /** G12. Each live record's own header (created, updated, version) that the read door does not answer. */ recordHeaders(args: { ids: Uuid[]; }): Promise>; /** * G14. Share a Table with a person by email or account id: an existing account outside the * organization is granted at once and notified (never made a member); an address with no * account is invited. The organization's outside-sharing setting decides first. */ tableShareOutsideGrant(args: { table_id: Uuid; person: string; level?: "viewer" | "commenter" | "editor" | "admin"; }): Promise>; /** G13. Keep a hand-set order on a saved view: the named rows first, in this order (build it with `moveInOrder`). */ viewRecordOrderSet(args: { view_id: Uuid; record_ids: Uuid[]; }): Promise>; /** * G13 + ORDER-FILTER. A page of a hand-ordered view: placed rows by position, then the rest by * created time. `filter` is the list door's question (`StoreFilter`: a flat map or a nested * Rule — the view's own `where`, or a number clicked on a dashboard); given, the store reads * exactly the rows that match, in their places, and `limit`/`offset` page THAT order. Each row * answers the position stored for it; rows the filter hides keep theirs. */ readInViewOrder(args: { view_id: Uuid; filter?: StoreFilter | null | undefined; limit?: number; offset?: number; }): Promise>>; exportXlsx(args: { table_id: Uuid; columns?: string[] | null; limit?: number; labels?: Record; sheetLabel?: string; }): Promise>; } /** One Table's two lane facts, as `custom.table_facts` answers them. */ interface TableFact { table_id: Uuid; /** * The old lane word (personal · internal · public), now worked out by the store from `shown_to` * and `published_to_web` (access ladder). Read those two; this stays for older stores. */ visibility: string; /** Whether the person signed in MADE this Table. Never who did. */ mine: boolean; /** Access ladder "Shown to": only_me · my_team · everyone · everyone_on_ai_matrx; null = the default. */ shown_to?: string | null; /** Access ladder "Published to the web": the only way anyone outside the organization opens it. */ published_to_web?: boolean; } /** * WHERE A TABLE IS CURRENTLY READ AND WRITTEN during the older-table cutover. * `tableHome` is an address read from one Table record; this applies the * organization's migration switch to a batch of ids, including same-id copies. */ interface TableLiveLocation { table_id: Uuid; lives_in: "record" | "older"; why: string | null; } declare function withTableFacts(tables: readonly T[], facts: readonly TableFact[] | null | undefined, me: Uuid | null | undefined): T[]; /** The record store's screen limits (`recordsLimits`), each a knob in `platform.feature_knob`. */ interface RecordsLimits { /** A fill of at most this many rows runs at once; a bigger one meets the confirm step (knob custom/ai_fill_try_max). */ aiFillTryMax: number; /** The most rows one fill covers (knob custom/ai_fill_rows_max). */ aiFillRowsMax: number; /** The number the "Fill N rows" box starts at (knob custom/ai_fill_default_rows). */ aiFillDefaultRows: number; /** Passes a table copy may make without moving before it stops (knob custom/copy_stall_passes). */ copyStallPasses: number; /** * The highest sensitivity an AI fill may read (knob custom/enrichment_sensitivity_ceiling): public, internal, * restricted or confidential. custom.enrich_due drops anything above it; screens default an AI column's inputs by it. */ enrichmentSensitivityCeiling: string; } /** * The knobs' own defaults (the values their rows were seeded with), answered only by a store that has no * `records_limits` door (the in-memory sample, an older store) or refuses it — never in place of a knob the door read. */ declare const RECORDS_LIMITS_DEFAULTS: Readonly; /** The door's answer as limits: each a whole number, else the knob's default for that one. */ declare function recordsLimitsFrom(raw: unknown): RecordsLimits; declare function createRecordsClient(given: RecordsConfig): RecordsClient; /** * Turn a refusal into the conflict a screen can SHOW: which version you wrote * against, which one won, and every field you touched as it stands now. Returns * null when the refusal was not a stale write. */ declare function asWriteConflict(error: RecordsError, recordId: Uuid): WriteConflict | null; export { ARCHIVE_LANES as A, BUILT_KIND_WORD as N, CHOICE_NUDGES as O, ARCHIVE_LANE_LABEL as a, DEFAULT_ARCHIVE_LANE as al, DEFAULT_PAGE_SIZE as am, DRILL_CHART_TOKENS as an, DRILL_DESCRIPTION_MAX as ao, DRILL_DOORS as ap, DRILL_SCHEMA as aq, ARCHIVE_LANE_MEANS as b, PIPELINE_RULE_KINDS as bQ, AUTOMATION_APP_TRIGGERS as c, SHOWN_TO_LABEL as cG, SHOWN_TO_VALUES as cH, SHOWN_TO_WHO_MAY_CHANGE as cI, STAGE_RULE_ENFORCEMENT as cJ, STAGE_RULE_ON_FAIL as cK, RECORDS_LIMITS_DEFAULTS as ca, ROW_LABEL_FORMULA_MAX_LENGTH as cb, ROW_WEB_NOT_READY as cc, createRecordsClient as d$, actionRefusals as dR, archivedByLine as dS, archivedWhen as dT, asChoiceNudge as dU, asWriteConflict as dV, automationWebhookUrl as dW, choiceSlug as dX, choiceValuesOf as dY, choicesOf as dZ, clearsRowLabel as d_, decideTypedChoice as e0, declareDrillDefinition as e1, drillAnswerOf as e2, drillFindingKnob as e3, emptyArchiveLine as e4, exportPageXlsx as e5, holdsARetiredChoice as e6, intersectIds as e7, isArchiveLane as e8, isAutomationAppTrigger as e9, recordsLimitsFrom as eA, recordsUseSentence as eB, rehomeSentence as eC, removedChoicesInUse as eD, ruleThatRefuses as eE, rulesWithTopStar as eF, sameDocument as eG, scopeChip as eH, sheetSyncSpec as eI, valueConverts as eJ, withRowCounts as eK, withTableFacts as eL, withTablePlacement as eM, isFoundation as ea, isShownTo as eb, isTheChosen as ec, keptGroups as ed, keptOutOfSight as ee, keptTableHref as ef, levelHasShownTo as eg, looksLikeAnId as eh, mayDrag as ei, moveConsequence as ej, moveInOrder as ek, offeredAsContext as el, optionKey as em, optionLabel as en, parseFieldDrift as eo, parseTableCopiesInProgress as ep, parseTableDuplicateProgress as eq, parseTableDuplicated as er, parseTableHome as es, parseTableMoved as et, planQuery as eu, previewRetype as ev, previewRuleChange as ew, recordChangeTrigger as ex, recordSourceKey as ey, recordSourceTable as ez }; export type { ChecklistRunStarted as $, AutomationRunStatus as B, AutomationRuns as C, AutomationSpec as D, AutomationStep as E, FieldDrift as F, AutomationTested as G, AutomationToken as H, AutomationTrigger as I, AutomationValue as J, AutomationValues as K, AutomationWeekday as L, AutomationWhen as M, CaptureField as P, CaptureFileRef as Q, RecordsClient as R, CaptureOutcome as S, TableArchiveProgress as T, CaptureQuestion as U, CaptureSheetFace as V, CaptureSheetSummary as W, ChecklistDeclared as X, ChecklistRequirement as Y, ChecklistRequirementKind as Z, ChecklistRoleSpec as _, EnrichDueRow as a$, ChecklistRunStep as a0, ChecklistRunSummary as a1, ChecklistSpec as a2, ChecklistStepCompleted as a3, ChecklistStepSpec as a4, ChecklistTemplateShape as a5, ChecklistTemplateSummary as a6, ChecklistTrigger as a7, ChecklistTriggerKind as a8, ChoiceCellBack as a9, DocTemplateRow as aA, DocToken as aB, DocUnresolvedToken as aC, DrillAdHocOp as aD, DrillAnswer as aE, DrillAnswerRow as aF, DrillChartToken as aG, DrillDeclaration as aH, DrillDefinition as aI, DrillDimension as aJ, DrillFinding as aK, DrillGrain as aL, DrillHaving as aM, DrillLane as aN, DrillLevel as aO, DrillMeasure as aP, DrillMeasureOp as aQ, DrillMeasureWhereValue as aR, DrillPath as aS, DrillQuestion as aT, DrillRecords as aU, DrillRowsPage as aV, DrillSource as aW, DrillView as aX, EnrichAlternate as aY, EnrichCell as aZ, EnrichDeclared as a_, ChoiceLike as aa, ChoiceNudge as ab, ChoiceOption as ac, ChoiceRehome as ad, ChoiceUsage as ae, ChoicesRehomed as af, CommentThreadRead as ag, CommentWritten as ah, ConversationScope as ai, ConversationScopeContext as aj, CustomFieldTableType as ak, DataHomeTable as ar, DecorationColor as as, DecorationPath as at, DecorationRule as au, DecorationRuleOp as av, DecorationTarget as aw, DocRenderRow as ax, DocSignatureCheck as ay, DocSignatureRow as az, PipelineMoveResult as b$, EnrichRun as b0, EnrichSource as b1, Enrichment as b2, EnrichmentSummary as b3, EntityBackLinkItem as b4, EntityBackLinks as b5, EntityFieldRights as b6, EntityFieldValue as b7, EntityFieldsAcross as b8, EntityRecordMatch as b9, ImportOpened as bA, ImportOutcome as bB, ImportPlan as bC, ImportPolicy as bD, ImportProposal as bE, ImportReport as bF, ImportRun as bG, InboundAddress as bH, InboundDeclared as bI, InboxCounts as bJ, InboxStateChange as bK, IoExportPage as bL, MigrationEntry as bM, MigrationUndone as bN, NewEntityFieldDeclaration as bO, OutsideShareAnswer as bP, ParentExtracted as bR, Pipeline as bS, PipelineColumn as bT, PipelineDeclared as bU, PipelineGate as bV, PipelineGatePreview as bW, PipelineMissingField as bX, PipelineMoveMany as bY, PipelineMoveManyItem as bZ, PipelineMoveManyResult as b_, EntityRecordRead as ba, EntityRecordsFound as bb, EntityRowWritten as bc, EntityTableFacts as bd, ExternalPrincipalCard as be, FieldDriftAspect as bf, FieldHistoryEntry as bg, FieldNotifyOnAssign as bh, FieldRetypePreview as bi, FieldRetyped as bj, FormArchiveAnswer as bk, FormulaHistoryEntry as bl, FormulaParse as bm, FormulaPreview as bn, GridLayout as bo, GridLayoutMode as bp, GridRowHeight as bq, HistoryActor as br, HistoryActorKind as bs, HistoryChange as bt, HistoryRetention as bu, Home as bv, ImportBatchResult as bw, ImportColumnPlan as bx, ImportColumnsDeclared as by, ImportFinished as bz, TableHomeDestination as c$, PipelineMoveVerdict as c0, PipelineRule as c1, PipelineRuleKind as c2, PipelineSpec as c3, PipelineStage as c4, PipelineVerdict as c5, PromotedPredicate as c6, PublishBinding as c7, PublishBindingKind as c8, QueryPlan as c9, RestoreResult as cA, RetypeBehaviour as cB, RetypePreview as cC, RowAction as cD, RowActionStep as cE, RowDefaultsApplied as cF, ShownTo as cL, StagePending as cM, StagePendingOnBoard as cN, StageRuleEnforcement as cO, StageRuleOnFail as cP, StageRuleWarning as cQ, SyncAutoRefresh as cR, Table as cS, TableCapacity as cT, TableCopyInProgress as cU, TableCopySpec as cV, TableDecorations as cW, TableDuplicateProgress as cX, TableDuplicated as cY, TableFact as cZ, TableHome as c_, RecordChangeAction as cd, RecordComment as ce, RecordEvent as cf, RecordFollowSet as cg, RecordFollowers as ch, RecordHeader as ci, RecordHistoryEntry as cj, RecordMoved as ck, RecordQuery as cl, RecordRowControls as cm, RecordScopeComment as cn, RecordScopeContext as co, RecordScopeField as cp, RecordScopeHistoryEntry as cq, RecordScopeRelation as cr, RecordScopeWithheld as cs, RecordSplit as ct, RecordsLimits as cu, RecordsMerged as cv, RegisteredTable as cw, ResolvedPublishBinding as cx, ResolvedPublishBindingKind as cy, RestorePreview as cz, ActionRefusal as d, TableLevel as d0, TableLevelApproval as d1, TableLevelFacts as d2, TableLevelSet as d3, TableListEntry as d4, TableLiveLocation as d5, TableMoved as d6, TablePlacement as d7, TablePlacementFact as d8, TableReaderRule as d9, WorkDecideManyResult as dA, WorkDecideVerdict as dB, WorkDueState as dC, WorkGraph as dD, WorkInboxItem as dE, WorkInboxKind as dF, WorkInboxView as dG, WorkInstantiation as dH, WorkListFlavour as dI, WorkListItem as dJ, WorkOrigin as dK, WorkRecordState as dL, WorkSlotHold as dM, WorkState as dN, WorkTemplateNode as dO, WorkTemplateRelation as dP, WorkTurn as dQ, TableRestoreProgress as da, TableRowCount as db, TableRowDefaults as dc, TableRowDefaultsPatch as dd, TableRowLabel as de, TableSyncAnswer as df, TableSyncResult as dg, TableSyncSettings as dh, TableSyncSource as di, TableSyncSourceDocument as dj, TableSyncSpec as dk, TableWebhook as dl, TableWebhookDeclared as dm, TypedChoiceDecision as dn, ViewLayout as dp, ViewOrderSet as dq, WorkApprovalDecision as dr, WorkApprovalFiled as ds, WorkApprovalState as dt, WorkApprover as du, WorkAssignment as dv, WorkAssignmentField as dw, WorkChange as dx, WorkDecideMany as dy, WorkDecideManyItem as dz, ActionRunResult as e, AdminTable as f, AnonTokenBinding as g, ArchiveLane as h, ArchivedChecklistTemplate as i, ArchivedDoorRow as j, ArchivedForm as k, ArchivedRow as l, AsOf as m, AutomationAction as n, AutomationAppTrigger as o, AutomationAppTriggerTerms as p, AutomationComparison as q, AutomationDeclared as r, AutomationFilter as s, AutomationFlipped as t, AutomationItem as u, AutomationLastRun as v, AutomationList as w, AutomationRevision as x, AutomationRevisions as y, AutomationRun as z };