You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor(tables): make lib/table/orchestration the single implementation
Column update was implemented four times — the UI route, v1, v2, and the
copilot table tool — each calling the same column services but owning its own
guards, error mapping, and audit. The copies had drifted, and the drift was the
bug: v2 was missing both guards, only the copilot copy minted stable option
ids, and only v1/v2 audited.
performUpdateTableColumn, performDeleteTable, and performDeleteTableRow now own
that logic; all ten call sites reduce to auth, parse, call, render. The guards
are asserted once in lib/table/orchestration rather than four times against
four routes.
Behavior this consolidates, previously true on only some paths:
- The typeChanging guard. updateColumnType early-returns on an unchanged type
and drops any options sent with it, so restating the current type alongside
new options silently discarded them. v2 had no guard at all and, since its
contract shares v1's body schema, accepted options and ignored them.
- The select-unique guard. Each write is its own locked transaction, so a
rename or type change paired with a constraint write that is going to fail
commits first and then throws, half-applying the schema change.
- Stable select-option ids. Cells reference the option id, so an edit that
re-sends an option by name has to reuse it or every cell holding it is
orphaned. Only the copilot path did this; normalizeSelectOptionsInput moves to
lib/table/select-options and now covers every caller. It preserves a supplied
id, so it is a no-op for the fully-formed options the HTTP contracts accept.
- required forwarded into the type and options writes, so a conversion
validates against the constraint the same request is setting.
- An audit on every successful update. The UI route and the copilot tool
emitted none.
- Single-row delete through the row service. v2 did a raw db.delete, skipping
assertRowDelete and deleteOrderedRow, so a delete-locked table returned 200
and the row-count bookkeeping never ran.
- The delete actor handed to deleteTable, which audits only when a row was
actually archived. v1 and v2 omitted it and audited themselves outside that
check, emitting TABLE_DELETED for a no-op delete of an archived table.
Failure classes come back as OrchestrationErrorCode; v2 renders them through a
new v2ErrorForOrchestration, mirroring statusForOrchestrationError on the v1
and UI surfaces, so a given failure maps to the same status everywhere.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0 commit comments