The canvas tracer's cleanup puts the admin's row back as it found it #226

Merged
Grandiras merged 1 commit from trace-unseed-restores-admin into main 2026-09-23 11:29:53 +00:00
Owner

design/trace/seed.py draws the admin as initials by overwriting their member row (DisplayName → plaintext 'Grandiras', AvatarUrl → null, UsesLocalProfile → true), and unseed.sql only reset UsesLocalProfile to false. After a trace the row kept the seeded name and no picture until some profile refresh overwrote them, and a member who really was on a local profile would have been switched off it.

The fix follows the pattern seed.py already uses for the default board's grants (zz_canvas_saved):

  • seed.py saves Did, DisplayName, AvatarUrl and UsesLocalProfile into zz_canvas_admin before it overwrites them. It uses create table if not exists, so a second seed without an unseed in between keeps the real row and doesn't save the demo one.
  • unseed.sql restores the three columns from that table and drops it. This replaces the lone UsesLocalProfile = false.

The saved copy is the stored ciphertext (enc:v1:…), and that's what goes back into the column: the value converter decrypts it on read.

Verified against the test stack by running README steps 2 and 5 (seed, restart the app, unseed, restart the app) and comparing snapshots of the admin row:

DisplayName AvatarUrl UsesLocalProfile
before enc:v1:IpSCMs…pg== enc:v1:qyq7V/…KRI= false
seeded, after restart enc:v1:2F09xP…6w== (the migrator sealed 'Grandiras') null true
after unseed identical to before (cmp) identical false
after unseed + restart identical to before (cmp) identical false

The member, board and grant counts and the default board's visibility also came back unchanged, and no zz_ tables were left. No canvas retrace is needed. There's no changelog entry because only a maintainer would notice this.

🤖 Generated with Claude Code

`design/trace/seed.py` draws the admin as initials by overwriting their member row (`DisplayName` → plaintext `'Grandiras'`, `AvatarUrl` → null, `UsesLocalProfile` → true), and `unseed.sql` only reset `UsesLocalProfile` to false. After a trace the row kept the seeded name and no picture until some profile refresh overwrote them, and a member who really was on a local profile would have been switched off it. **The fix** follows the pattern seed.py already uses for the default board's grants (`zz_canvas_saved`): - `seed.py` saves `Did`, `DisplayName`, `AvatarUrl` and `UsesLocalProfile` into `zz_canvas_admin` before it overwrites them. It uses `create table if not exists`, so a second seed without an unseed in between keeps the real row and doesn't save the demo one. - `unseed.sql` restores the three columns from that table and drops it. This replaces the lone `UsesLocalProfile = false`. The saved copy is the stored ciphertext (`enc:v1:…`), and that's what goes back into the column: the value converter decrypts it on read. **Verified against the test stack** by running README steps 2 and 5 (seed, restart the app, unseed, restart the app) and comparing snapshots of the admin row: | | `DisplayName` | `AvatarUrl` | `UsesLocalProfile` | |---|---|---|---| | before | `enc:v1:IpSCMs…pg==` | `enc:v1:qyq7V/…KRI=` | false | | seeded, after restart | `enc:v1:2F09xP…6w==` (the migrator sealed `'Grandiras'`) | null | true | | after unseed | identical to *before* (`cmp`) | identical | false | | after unseed + restart | identical to *before* (`cmp`) | identical | false | The member, board and grant counts and the default board's visibility also came back unchanged, and no `zz_` tables were left. No canvas retrace is needed. There's no changelog entry because only a maintainer would notice this. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The canvas tracer's cleanup puts the admin's row back as it found it
All checks were successful
CI / build (pull_request) Successful in 41s
1b7bfe954b
seed.py draws the admin as initials by overwriting their member row —
DisplayName to a plaintext 'Grandiras', AvatarUrl to null, UsesLocalProfile
to true — and unseed.sql only reset UsesLocalProfile to false. So after a
trace the row kept the seeded name (sealed into fresh ciphertext by the
migrator on the restart the README asks for) and no picture, until a
profile refresh happened to overwrite them; and a member who really was on
a local profile would have been switched off it.

seed.py now saves the three columns first, into zz_canvas_admin, the same
way it already saves the default board's grants into zz_canvas_saved, and
unseed.sql restores them from it and drops the table. The saved copy is the
stored ciphertext, which is what belongs back in the column: the converter
decrypts it on read.

Checked against the test stack by running README steps 2 and 5 (seed,
restart, unseed, restart): the admin's DisplayName, AvatarUrl and
UsesLocalProfile are byte-identical to the snapshot taken before, and the
member, board and grant counts and the default board's visibility are back
too, with no zz_ tables left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Grandiras force-pushed trace-unseed-restores-admin from 1b7bfe954b
All checks were successful
CI / build (pull_request) Successful in 41s
to a4dde34f87
All checks were successful
CI / build (pull_request) Successful in 42s
2026-09-23 11:23:49 +00:00
Compare
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review -->
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Clean fix that mirrors the existing zz_canvas_saved pattern. The create table if not exists … as select correctly preserves the real admin row across a re-seed without an intervening unseed, and restoring the stored ciphertext is right: Members.DisplayName and Members.AvatarUrl are both in the EncryptionMigrator allowlist (idempotent on the enc:v1: prefix), and the value converter decrypts on read. The UPDATE … FROM join touches at most the one admin row, and since unseed.sql already requires a prior seed (it reads zz_canvas_saved), the new table dependency adds no new precondition. Verified against the test stack per the description; no changelog entry is reasonable for maintainer-only tooling.



⏱ 1m 35s · 0 steps · $0.0000 · tokens: 0 in / 0 out (+0 cache-read) · Go: 5h 11% · wk 78% · mo 72%

✅ **Verdict: approve** — no blocking issues found. Clean fix that mirrors the existing `zz_canvas_saved` pattern. The `create table if not exists … as select` correctly preserves the real admin row across a re-seed without an intervening unseed, and restoring the stored ciphertext is right: `Members.DisplayName` and `Members.AvatarUrl` are both in the `EncryptionMigrator` allowlist (idempotent on the `enc:v1:` prefix), and the value converter decrypts on read. The `UPDATE … FROM` join touches at most the one admin row, and since unseed.sql already requires a prior seed (it reads `zz_canvas_saved`), the new table dependency adds no new precondition. Verified against the test stack per the description; no changelog entry is reasonable for maintainer-only tooling. --- --- ⏱ 1m 35s · 0 steps · $0.0000 · tokens: 0 in / 0 out (+0 cache-read) · Go: 5h 11% · wk 78% · mo 72% <!-- opencode-reviewed-head:a4dde34f87a7353b2b358fa3c75727e642cca52b --> <!-- opencode-verdict:approve -->
Grandiras deleted branch trace-unseed-restores-admin 2026-09-23 11:29:53 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/Ponente!226
No description provided.