opbox

The column.* family (6 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.

Addcolumn.add

PropertyValue
Capabilitydata.table.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

Adds a column. A COMPUTED column (type FORMULA/LINK/LOOKUP/ROLLUP) is is_computed (the GENERATED flag) and is NEVER writable (INV-6) - a FORMULA column must carry a config.formula (the DB CHECK + this verb). The key is the STABLE binding for Facts (C7) and is prototype-pollution-fenced; uq_data_column_table_key is a friendly Conflict on a duplicate key.

Input { tableId, key, name?, type, config?, required?, isUnique?, isPii?, displayOrder? } (fields ending in ? are optional)

Call POST /v/column.add with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.

Deletecolumn.delete

PropertyValue
Capabilitydata.table.write
Risk classDestructive - autonomy L3
Min. permissionAdmin
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

Drops a column (its Facts cascade by the FK, migration 0026). A LINK column is BIDIRECTIONAL: it deletes its auto-created reverse-LINK column (named in config.reverseColumnId, the harvested Opbox contract - “deleting one deletes its reverse, in a $transaction”) in the SAME tx, so the relation is never left half-defined. The reverse delete cascades its own Facts + edges (the 0026 entity_edge.source_column_id ON DELETE CASCADE keeps the derived edge index consistent, INV-2/C9).

Input { tableId, key } (fields ending in ? are optional)

Call POST /v/column.delete with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.

Hidecolumn.hide

PropertyValue
Capabilitydata.table.write
Risk classWrite - autonomy L1
Min. permissionAdmin
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

Per-COLUMN visibility, recorded on the column’s own settings (a hiddenForRoles array). This is per-column authz AT THE VERB (INV-11) - the render/read layer reads settings.hiddenForRoles and omits the column for a caller in one of those roles. It is NOT a second role map: the authz table stays the registry; this is a column-scoped render flag the verb owns. hidden=false (or an empty roles) clears the hide. Folding the read here keeps the keyspace in one place (no __hidden__ Fact, no parallel store).

Input { tableId, key, hidden?, roles? } (fields ending in ? are optional)

Call POST /v/column.hide with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool column.hide.

Listcolumn.list

PropertyValue
Capabilitydata.column.read
Risk classRead - autonomy L0
Min. permissionMember
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)
Render schemaYes - drives an inline chat artefact and a GUI panel

Lists the columns (schema) of a data-plane table. RLS-scoped + an explicit workspace predicate. Columns are ordered by display_order, then key. Each column carries its id, key, name, type, displayOrder, required, isUnique, isComputed (INV-6 boundary), and isPii (INV-7 flag). Computed columns (FORMULA/LINK/LOOKUP/ROLLUP) are marked isComputed=true and have no canonical Facts. PII columns are marked isPii=true (values stored encrypted).

Input { tableId } (fields ending in ? are optional)

Call POST /v/column.list with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.

Reordercolumn.reorder

PropertyValue
Capabilitydata.table.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

where order is the array of column KEYS in the desired display order. Sets each named column’s display_order to its index in the array. A key not on the table is an actionable not-found (fail-closed before any write). Keys omitted from order keep their existing display_order (a partial reorder is allowed; the array only re-ranks the keys it names).

Input { tableId, order } (fields ending in ? are optional)

Call POST /v/column.reorder with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.

Retypecolumn.retype

PropertyValue
Capabilitydata.table.write
Risk classDestructive - autonomy L3
Min. permissionAdmin
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

SCHEMA-ALTERING: changes a column’s storage type. Best-effort coercion is the RENDER layer’s job (it reads the new type); this verb NEVER deletes or rewrites a Fact (an un-coercible value is kept VERBATIM in data_fact.value - the canonical datum is never dropped). The one destructive move it WILL refuse: retyping a STORED column to a COMPUTED type (or vice-versa) while Facts exist - a stored->computed flip would orphan every Fact (a computed column has no Fact, INV-6 + the 0027 trigger), so it is a hard reject naming the conflict (the data is preserved, not silently destroyed). A FORMULA target requires config.formula (INV-6, same as column.add).

Input { tableId, key, type, config? } (fields ending in ? are optional)

Call POST /v/column.retype with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.