Skip to content

CES-70 — lock replace mode, resolve remaining decisions, mark READY - #24

Merged
JMNofziger merged 2 commits into
mainfrom
cursor/ces-70-replace-lock-40e4
Aug 21, 2026
Merged

CES-70 — lock replace mode, resolve remaining decisions, mark READY#24
JMNofziger merged 2 commits into
mainfrom
cursor/ces-70-replace-lock-40e4

Conversation

@JMNofziger

Copy link
Copy Markdown
Owner

Follow-up to PR #22 (already merged). Product locked replace over merge and confirmed cross-account imports stay a warning, so the spec flips from Draft to Complete (v1) and the execution prompt from BLOCKED to READY.

Docs only. No application code, no schema migration, no CES-71 rename.

Mode: replace

Merge is deferred to v1.x — not built, not stubbed, not selectable. The decisive argument: export deliberately omits soft-deleted rows, so a merge sees no tombstones and can never remove anything. Every row deleted on the source device would survive on the destination forever and be re-affirmed by every subsequent import. Row-level LWW would also spend the v1.x field-level merge decision ADR 002 deliberately deferred, on a design no spec defines.

Replace has one deterministic post-state, which makes the golden round-trip test a complete correctness proof rather than a set of encoded guesses.

Revisit gate: merge needs v1.x merge rules and tombstones in export. Both are prerequisites, not nice-to-haves.

Follow-on decisions replace forced

Two of these corrected wrong assumptions in the first draft, found by reading the schema rather than trusting the earlier note:

Table Disposition Why
vehicles, fill_ups, maintenance_rules, maintenance_events Hard DELETE then insert Soft-delete would leave rows colliding with incoming ids on the primary key
settings UPDATE in place, never deleted A DELETE would destroy settings.id and violate the identity invariant
outbox Cleared Its table CHECK admits only the four replaced tables, so every queued mutation describes a row being destroyed
drafts Preserved when the draft's vehicle survives; discarded otherwise Drafts are looked up by vehicle (openDraftForVehicle), so a draft whose vehicle is gone is unreachable by construction — and would resurface if that vehicle id ever returned
photo_refs + JPEGs Follow drafts Correction: photo_refs.draft_id is an FK to drafts, not fill_ups, and ttl_expires_at is an absolute stamp — so replacing fill-ups orphans no photos and changes no TTL

Photo files are deleted only after the transaction commits. An interruption then leaves files with no row, which PhotoService.sweep already collects as orphanFilesDeleted; deleting files first would leave rows pointing at missing files on rollback.

Also added

  • Technical rationale for replace (5 numbered arguments) in the spec and on the issue.
  • User-facing explanation — plain-language copy ready to drop into install-*.md, a help sheet, or the confirm dialog, plus copy notes for whoever wires the UI.
  • Cross-account revisit gate — differing user_key_hash stays a warning, with re-open triggers named: CES-46 landing a real telemetry user key, or M3 (CES-42/43) introducing server accounts.
  • Confirmation rules — typed REPLACE when local history is non-empty with an inline export-first offer; plain confirm on an empty database, since that is the new-phone path and friction there buys no safety.

Error table 13 → 17 codes; test list 15 → 17 items.

Verification

flutter analyze clean · python3 ci/telemetry-gate.py PASS · docs-only (three files) · all relative links and internal anchors resolve. Rebased onto current main so CES-41's merged export code is intact — the earlier branch was on a stale base.

Open in Web Open in Cursor 

cursoragent and others added 2 commits August 16, 2026 12:22
Product locked replace over merge and confirmed cross-account imports stay
a warning. Spec flips from Draft to Complete (v1); the execution prompt
flips from BLOCKED to READY with MODE = replace filled in.

Replace forced four follow-on decisions, two of which corrected wrong
assumptions in the first draft:

- settings is UPDATEd in place, never deleted -- a DELETE would destroy
  settings.id and violate the identity invariant.
- outbox is cleared. Its table CHECK only admits the four replaced
  tables, so every queued mutation describes a row being destroyed;
  keeping them would later push replaced data.
- photo_refs.draft_id is an FK to drafts, NOT fill_ups, and ttl_expires_at
  is an absolute stamp -- so replacing fill-ups orphans no photos and
  changes no TTL. The first draft claimed otherwise.
- drafts are keyed by vehicle, so a draft whose vehicle is destroyed is
  unreachable by construction and would resurface if that vehicle id
  returned in a later import. Preserve drafts whose vehicle survives;
  discard the rest with their photos. Photo files are deleted only after
  commit, which the existing CES-40 orphan-file sweep already covers.

Adds the technical rationale for replace (merge cannot express deletions
because export omits tombstones; LWW merge would spend the v1.x merge
decision ADR 002 deferred), user-facing copy for docs/UI, and a
revisit gate on cross-account imports triggered by CES-46 or M3 auth.

Error table 13 -> 17 codes, test list 15 -> 17 items.

Co-authored-by: JMNofziger <JMNofziger@users.noreply.github.com>
Export code is on main, so client/lib/export/ is available to share from
and the prompt has no outstanding prerequisites.

Co-authored-by: JMNofziger <JMNofziger@users.noreply.github.com>
@JMNofziger
JMNofziger marked this pull request as ready for review August 21, 2026 10:54
@JMNofziger
JMNofziger merged commit b4f58b7 into main Aug 21, 2026
2 checks passed
@JMNofziger
JMNofziger deleted the cursor/ces-70-replace-lock-40e4 branch August 21, 2026 11:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants