The table.* family (5 verbs). Every verb enters through the one front door: it is capability-checked, permission-checked, scope-checked and audited before it runs (see Security & permissions). Each entry below lists its capability, risk class (which fixes the minimum autonomy level), the minimum caller permission, its availability tier, whether it is idempotent, and any outbound egress.
Archivetable.archive
| Property | Value |
|---|---|
| Capability | data.table.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Admin |
| Availability | On demand (MCP tier 2) |
| Idempotent | Yes |
| Egress | None - in-box (pure Postgres, no outbound call) |
Sets the table’s archived flag (the soft-hide lane migration 0026 already
carries - line 93). REVERSIBLE: archived=true (default) hides it from the default table.list
(which filters archived = false unless includeArchived); archived=false un-archives it. NO data
is touched - every column / row / Fact is RETAINED (this is the non-destructive twin of table.delete).
RLS-scoped with an EXPLICIT workspace_id = $ predicate; a table in another workspace reads back
not-found (anti-enumeration). IDEMPOTENT: re-archiving an already-archived table (or un-archiving an
active one) is a no-op-shaped success (the UPDATE simply re-asserts the same flag).
Input { id, archived? } (fields ending in ? are optional)
Call POST /v/table.archive with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool table.archive.
Createtable.create
| Property | Value |
|---|---|
| Capability | data.table.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | Admin only (CLI/HTTP; never advertised over MCP) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
Authors a typed Table. category defaults
to USER (the only hand-built category). A SYSTEM table MUST name its systemType and there may be
AT MOST ONE per (workspace, systemType) - a second create of the same SYSTEM type is a friendly
Conflict (the raw uq_data_table_workspace_system_type violation mapped, never a raw 23505 leak).
Input { name, category?, systemType?, description?, addonKey? } (fields ending in ? are optional)
Call POST /v/table.create with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.
Deletetable.delete
| Property | Value |
|---|---|
| Capability | data.table.write |
| Risk class | Destructive - autonomy L3 |
| Min. permission | Owner |
| Availability | On demand (MCP tier 2) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
The HARD teardown of a table: the data_table row is deleted and its
data_column / data_row / data_fact / data_formula children CASCADE by the FK (migration 0026
ON DELETE CASCADE). This is the DESTRUCTIVE/OWNER twin of the reversible table.archive - it does NOT
retain data, so it sits at the highest tier (OWNER) the catalogue gives a teardown.
ENTITY-EDGE CASCADE (the verb’s load-bearing extra work): entity_edge endpoints are POLYMORPHIC -
there is NO DB FK from an edge to a data_row / data_table (migration 0026 §7, the regen job owns
integrity), so the table-row delete does NOT cascade the edges that point at this table’s rows. A
LINK-column-minted edge cleans up via source_column_id ON DELETE CASCADE (the columns drop), but a
VERB-minted edge (matter.attach of a TABLE_ROW, NULL source_column_id) would be left DANGLING. So this
verb EXPLICITLY deletes every entity_edge whose TABLE_ROW endpoint names this table (src_table_id
OR dst_table_id = the table) in the SAME tx, so the derived edge index is never left referencing a
torn-down table (INV-2/C9 - the index is rebuilt off canonical rows, never left pointing at the void).
REGULATED-PII EXTRA-GATE: a table that carries ANY is_pii column holds (encrypted) regulated cells.
Tearing it down is irreversible destruction of regulated data, so it requires an EXPLICIT
confirmRegulated=true acknowledgement (the gateOnConfirm convention) - fail-closed otherwise, so a
caller cannot destroy a regulated table by a bare one-arg call. A table with no PII column needs no
extra flag (the OWNER tier gate alone governs it).
Input { id, confirmRegulated? } (fields ending in ? are optional)
Call POST /v/table.delete with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool table.delete.
Gettable.get
| Property | Value |
|---|---|
| Capability | data.table.read |
| Risk class | Read - autonomy L0 |
| Min. permission | Member |
| Availability | Admin only (CLI/HTTP; never advertised over MCP) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - drives an inline chat artefact and a GUI panel |
s one table + its columns (the grid schema). RLS-scoped + an explicit workspace predicate.
Input
| Field | Type | Required | Description |
|---|---|---|---|
tableId | string | yes |
Call POST /v/table.get with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.
Listtable.list
| Property | Value |
|---|---|
| Capability | data.table.read |
| Risk class | Read - autonomy L0 |
| Min. permission | Member |
| Availability | Admin only (CLI/HTTP; never advertised over MCP) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - drives an inline chat artefact and a GUI panel |
Lists
tables in the workspace (metadata only; columns are the per-table table.get). All filters optional
and inert when absent - empty input keeps the historical full list. ids / names / systemType
accept an array (the frontend’s { in: [...] } reads); systemType is case-folded to match the stored
enum text. q is a case-insensitive name substring (ILIKE, wildcard-escaped - never a SQL error on
user text). Filter + projection parity for the frontend’s prisma.table.findMany reads (the data_table
SPINE; opbox-prod/PARITY-LEDGER.md track C, disposed by [2026] CC-OPBOX 24/25/34 - reuse/extend the
existing reader, no new verb/store/permission-model). RLS-scoped + an explicit workspace predicate (a
cross-workspace table is unreachable). Computes nothing authoritative.
Input
| Field | Type | Required | Description |
|---|---|---|---|
category | string | no | filter by table category (case-insensitive) |
includeArchived | boolean | no | include archived tables (default false) |
ids | array | no | filter to these table ids |
names | array | no | filter to these exact table names |
systemType | array | no | filter by SystemTableType (case-insensitive) |
addonKey | string | no | filter by owning addon key |
q | string | no | case-insensitive name substring search |
limit | integer | no | page size (1..500, default 50) |
offset | integer | no | page offset (default 0) |
Call POST /v/table.list with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.