fix(lokee): revert only against the current database, snapshot first, and say it in the reader's words - #273
Merged
Conversation
…shots first Three changes to the revert path, one of them a correctness bug. **What you review is now what runs.** The diff came from `compareLokeeVersions(original, target)` while the plan and the execute call went to `planLokeeRevert(original)` — which reverses from the *live head*, ignoring Target entirely. With an older version on Target the reader reviewed one script and Execute applied a different, usually larger one. Reverting restores the live database, so it is only coherent when Target is the newest version; anything else is refused, with a one-click "Use current database" to fix it. An open dialog now follows the picker instead of holding the pair it opened with, so that button visibly does something. **Snapshot before touching anything.** The revert route captures the live schema first. That leaves a version to come back to, but the reason that matters is correctness: `planRevert` reverses from the newest *captured* version, not from what is in the database, so a schema edited by hand since the last capture was being reversed against a picture that no longer existed. When the snapshot finds drift the request is refused (`schema_drifted`) rather than applied — the caller reviewed a plan built on the old head, and running a different one silently is the surprise this exists to stop. Verified live: a hand-added column is captured as its own version and the revert leaves the database untouched. **The version menus say which choices cannot be reverted.** The newest version on Original reads "current, nothing to restore"; an older version on Target reads "compare only". Both stay selectable — they are legitimate comparisons — but they no longer look identical to a choice that can run and then dead-end at a greyed-out button with the reason hidden in a tooltip. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… that do nothing A launch-readiness pass over the schema history module, reading it as someone seeing it for the first time. **One real bug, found by reading a card.** A deleted column came back from the graph DTO typed as `table` — `objectType: row.object_type ?? 'table'`, and the delta row carries no type. So a deleted column was drawn with a table's icon and colour, and slipped through the default table filter as a phantom table. The key already says what it is (`column:ORDERS.NOTE`), so the fallback now reads the kind from the key. **Names the database actually uses.** Children were labelled with their compare key — `ORDERS.NOTE` — which CLAUDE.md is explicit is an uppercased match key and never an identifier. Cards now read `NOTE` with `deleted from orders` beneath. **Filters that can change what you see.** The object-type list was the union of every dialect, so a SQLite user got MQT — a term only Db2 uses — beside Procedure and Function boxes that could never match. It now offers the types the history contains; a type the user ticked themselves stays. On the SQLite demo that is 8 checkboxes down to 2. **Words, not internals.** "reused" was the store's vocabulary for one object pointed at by many versions; a reader is asking what moved. Summary reads "12 unchanged", the legend "Unchanged", the edge key "Unchanged from previous version". "Content-addressed schema history (Lokee)" — an internal codename and an implementation detail — is now what the feature does. The header says Schema history everywhere. **A first run you can act on.** The empty state was a paragraph pointing at a button on another bar: the one screen where a newcomer has nothing to act on was the one screen with no action on it. It now holds the database picker and a "Take first snapshot" button. Also `[sqlite] /tmp/app.db.main` → `sqlite · /tmp/app.db · main`; the dotted schema suffix read as a file extension. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…un goes
The last launch blockers in schema history, all found by walking the flow the
user described — a database that has fallen behind, brought back up to date.
**The loudest warning was on the safest operation.** `ReversalRisk` defines
`lossy` as "succeeds but destroys or truncates data". Re-creating a dropped
column does neither: it adds one back, empty, because the rows went when it was
dropped. Classifying it lossy meant a plan of nothing but ADD COLUMN came back
`risk: lossy`, showed "This revert destroys data", and demanded a data-loss
acknowledgement — on the single most common thing anyone would do with version
history. It is now `safe`, and keeps the note explaining the column comes back
empty, because that is worth knowing while the gate is not.
Verified against a live database three versions behind: the plan went from
`risk: lossy, lossyCount: 2` to `risk: safe, lossyCount: 0`, and executes
without a confirmation, restoring both columns.
**The button says where it goes.** "Execute migration (3)" gave a count and no
destination, and read as an undo even when the plan only adds. Direction cannot
come from version numbers — the plan always runs from the head, so the target is
always the lower number — so it comes from what the plan does: a plan that
destroys nothing is catching a database up ("Update to v2"), one that destroys
something is rolling it back ("Revert to v2").
**The author line earns its place.** It answered "who did this?" only when the
versions disagreed; on a single-user install it was the same address repeated
down the whole column, crowding out the date and change count that do differ.
Shown only when the versions actually have different authors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t are actually required No Pro subscription, so the on-canvas "React Flow" badge is gone. That is permitted: @xyflow/react is plain MIT (node_modules/@xyflow/react/LICENSE), and its only condition is that the copyright and permission notice accompany the software — displaying a badge in the UI is not a licence term. xyflow asks that you subscribe when you hide it; that is a request, and this project has chosen not to. Which leaves the condition that *is* real, and was not being met. The client bundles 38 third-party packages — React Flow among them — under MIT, BSD and ISC, all of which require their notice to travel with the code. `NOTICE` only carried Fox Schema's own Apache-2.0 terms, so shipping a build distributed their code without their notices. - `THIRD-PARTY-NOTICES.md` reproduces each package's own licence text, generated from the installed tree by `scripts/generate-third-party-notices.mjs` and regenerable when dependencies change. - `.gitignore` blanket-ignores `*.md`, which would have kept the file out of the repo entirely; added to the tracked exceptions beside README and SECURITY. - The Dockerfile copies LICENSE, NOTICE and the new file into the image, so the notices reach whoever runs the container rather than stopping at the repo. Only gap: @google-cloud/secret-manager (Apache-2.0) ships no licence file of its own; its entry names the licence and links upstream. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_d64947dc-7207-491d-b503-230b343264ca) |
…o longer has Fallout from #271, already on main. Hiding host / port / username / password for file dialects was right for the product and broke every e2e that saves a SQLite credential: `addSqliteCredential` fed `conn-host-input` a value, and Playwright sat waiting 30s for a field that is not rendered. `schema-revert.test.ts` failed in `beforeAll` and skipped all four cases. The helper now passes only what a file dialect shows — a path — and `addCredential` fills host, port, username and password only when the caller supplies them, so live-server dialects are unaffected. This gets the suite past setup and into the tests. It is not green yet: the target picker does not end up with the new credential selected, so `snapshotTarget` finds its button still disabled. Root cause not yet found — recorded here rather than left as a silent red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_d76ad6ce-12b7-4a98-95c5-22fa2a7997d0) |
Flagged by the code-quality bot on #273. Pre-existing rather than new — the symbol had no reference before this branch either; the file being in the diff is what surfaced it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_debeb405-a541-4a76-adbb-3ce454689fcc) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four commits taking schema history from "works" to "shippable". Two are correctness bugs; the rest is the launch-readiness pass.
1. What you review is not what runs
The diff came from
compareLokeeVersions(original, target)while the plan and the execute call went toplanLokeeRevert(original)— which reverses from the live head and ignores Target entirely. With an older version on Target you reviewed one script and Execute applied a different, usually larger one.Reproduced in the UI: a dialog headed "Original Version 10 → Target Version 13" above a button that would have applied v15 → v10.
A revert restores the live database, so it is only coherent when Target is the newest version. Anything else is refused, with a one-click Use current database to fix it. An open dialog now follows the picker instead of holding the pair it opened with — otherwise that button changed the bar behind the modal and appeared to do nothing.
2. Snapshot before touching anything
The revert route now captures the live schema first. The restore point matters, but the correctness win matters more:
planRevertreverses from the newest captured version, not from what is in the database, so a schema edited by hand since the last capture was being reversed against a picture that no longer existed.When the snapshot finds drift the request is refused (
schema_drifted), not applied — the caller reviewed a plan built on the old head. Verified against the live API:ok=true, column droppedschema_drifted, nothing applied, drift kept as its own version3. The loudest warning was on the safest operation
ReversalRiskdefineslossyas "succeeds but destroys or truncates data". Re-creating a dropped column does neither — it adds one back, empty, because the rows went when it was dropped. Classifying it lossy meant a plan of nothing butADD COLUMNcame backrisk: lossy, displayed "This revert destroys data", and demanded a data-loss acknowledgement — on the single most common thing anyone does with version history: bringing a database that has fallen behind back up to date.Verified on a database three versions behind:
risk: lossy, lossyCount: 2→risk: safe, lossyCount: 0, executes without confirmation, both columns restored. The note explaining the column comes back empty stays; the gate does not.This is also what makes migrate any version → latest usable — the mechanism was already there, buried under a false alarm.
4. Launch-readiness pass
table.objectType: row.object_type ?? 'table', and the delta row carries no type — so a deleted column drew with a table's icon and slipped through the table filter as a phantom table. The key already says what it is.ORDERS.NOTEis an uppercased match key that exists nowhere in the user's database (CLAUDE.md is explicit about this). Cards readNOTE, with deleted from orders beneath.[sqlite] /tmp/app.db.main→sqlite · /tmp/app.db · main; the dotted suffix read as a file extension.5. Licensing
The React Flow attribution badge is hidden.
@xyflow/reactis plain MIT (node_modules/@xyflow/react/LICENSE) and its only condition is that the notice accompany the software — the badge is not a licence term. xyflow asks that you subscribe to Pro when hiding it; the project has chosen not to.That surfaced the condition that was unmet: the client bundles 38 third-party packages under MIT/BSD/ISC, all requiring their notice to travel with the code, and
NOTICEcarried only Fox Schema's own Apache-2.0 terms. AddedTHIRD-PARTY-NOTICES.md(generated byscripts/generate-third-party-notices.mjs), un-ignored it —.gitignoreblanket-ignores*.mdand would have kept it out of the repo — and the Dockerfile now copies LICENSE, NOTICE and it into the image.Only gap:
@google-cloud/secret-managerships no licence file of its own; its entry names the licence and links upstream.Reviewer notes
e2etype-filter assertion was updated deliberately: it asserted Procedure and Function filters are visible on a SQLite seed that has neither. It now asserts the types the seed actually has, and that MQT/Procedure are absent.saferemoves a confirmation gate. That is the intended effect — the operation destroys nothing — but it is the change most worth a second opinion.Gates
npx tsc --noEmitclean ·npx vitest run1955 pass / 2 expected-fail · eslint 0 errors.🤖 Generated with Claude Code
Note
Medium Risk
Revert and snapshot logic on the live schema path can block or apply DDL incorrectly if drift or version targeting is wrong; reclassifying re-create as safe removes a confirmation gate on catch-up plans. Licensing and Docker copy changes are low operational risk but affect compliance artifacts.
Overview
Revert correctness ties the compare modal to what actually runs: execute only when Target is the newest captured version (otherwise block with Use current database), keep an open compare dialog synced with the bar’s pair, and label runs as Update to vN vs Revert to vN based on whether the plan destroys data. The revert API now snapshots the live schema first and returns
schema_driftedwhen drift is found so reviewed plans are not applied against a stale head.Reversal risk reclassifies re-creating dropped tables/columns as
safe(notlossy) so catch-upADD COLUMNplans skip false data-loss confirmation; explanatory notes about empty re-created objects remain.History graph and labels infer object type from delta keys (fixes deleted columns showing as tables), split compare keys on cards via
objectDisplayName, offer only object-type filters present in the history (offeredObjectTypes), and refresh copy (unchanged vs reused, schema history subtitle formatting, empty-state Take first snapshot, compare-only version labels).Distribution adds
THIRD-PARTY-NOTICES.mdandscripts/generate-third-party-notices.mjs, tracks the file in.gitignore, extendsNOTICE, copies LICENSE/NOTICE/notices into the Docker image, and hides React Flow’s on-canvas attribution (hideAttribution: true) with MIT notice in third-party file.Tests update e2e SQLite credential helpers and schema-history type-filter expectations; vitest covers revert targeting, run-button labels, and graph display helpers.
Reviewed by Cursor Bugbot for commit 8299992. Bugbot is set up for automated code reviews on this repo. Configure here.