Build image / build-and-push (push) Successful in 5s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
54 lines
2.9 KiB
SQL
54 lines
2.9 KiB
SQL
-- Revision history: the undo substrate (#48).
|
|
--
|
|
-- The unit of undo is the OPERATION, not the row. "Empty the garlic bed and
|
|
-- plant cucumbers" touches one object and many plantings; undoing half of that
|
|
-- is worse than useless. So a change_set groups the row-level revisions it
|
|
-- produced, and revert replays their inverses.
|
|
--
|
|
-- Two properties this schema is built around:
|
|
-- * Revert is itself a change set (reverts_id points at its target). History is
|
|
-- append-only and never rewritten, so an undo can be undone. git revert, not
|
|
-- git reset.
|
|
-- * Every revision carries full JSON row snapshots INCLUDING version, which is
|
|
-- what the revert guard compares against so a later edit is reported as a
|
|
-- conflict rather than silently clobbered.
|
|
--
|
|
-- JSON snapshots rather than a shadow table per entity: the rows are small,
|
|
-- household scale makes storage a non-issue, and one code path covers all three
|
|
-- entity types.
|
|
--
|
|
-- Deliberate gap: garden DELETION is not revertible. Dropping a garden cascades
|
|
-- its objects and plantings without the service ever seeing them, and the
|
|
-- ON DELETE CASCADE below would take the history with it. The history UI says so
|
|
-- rather than pretending otherwise.
|
|
CREATE TABLE change_sets (
|
|
id INTEGER PRIMARY KEY,
|
|
garden_id INTEGER NOT NULL REFERENCES gardens (id) ON DELETE CASCADE,
|
|
actor_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
|
source TEXT NOT NULL CHECK (source IN ('ui', 'agent', 'api')),
|
|
summary TEXT NOT NULL DEFAULT '',
|
|
agent_run_id TEXT, -- executus run id when source='agent'
|
|
reverts_id INTEGER REFERENCES change_sets (id), -- set when this change set reverts another
|
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
);
|
|
|
|
-- The history panel's only query: one garden, newest first.
|
|
CREATE INDEX idx_change_sets_garden ON change_sets (garden_id, created_at DESC);
|
|
|
|
-- "Has this change set been reverted?" — the list marks reverted entries, so the
|
|
-- lookup by target must not be a scan.
|
|
CREATE INDEX idx_change_sets_reverts ON change_sets (reverts_id) WHERE reverts_id IS NOT NULL;
|
|
|
|
CREATE TABLE revisions (
|
|
id INTEGER PRIMARY KEY,
|
|
change_set_id INTEGER NOT NULL REFERENCES change_sets (id) ON DELETE CASCADE,
|
|
seq INTEGER NOT NULL, -- order within the change set
|
|
entity_type TEXT NOT NULL CHECK (entity_type IN ('garden', 'object', 'planting')),
|
|
entity_id INTEGER NOT NULL,
|
|
op TEXT NOT NULL CHECK (op IN ('create', 'update', 'delete')),
|
|
before TEXT, -- JSON row snapshot; NULL for create
|
|
after TEXT -- JSON row snapshot; NULL for delete
|
|
);
|
|
|
|
CREATE INDEX idx_revisions_change_set ON revisions (change_set_id, seq);
|