2 Commits
Author SHA1 Message Date
claude@clouddev1 e845a6ee72 docs: correct the stale fmt-gate note in CLAUDE.md; handoff-79
ci / gate (push) Successful in 2m6s
ci / manifests (push) Successful in 4s
CLAUDE.md said "fmt is intentionally not gated yet" — stale; the gate is
now fmt + clippy + test (ADR-ci-002 Amendment 1 / issue #35), run via
`nix develop -c`. Add CI-exact local-verification guidance and flag the
exit-code-vs-piped-output trap that masked the fmt failure this session.
handoff-79 records the blob removal + the CI/fmt lesson.
2026-06-22 21:43:51 +00:00
claude@clouddev1 c9d6660ba6 style: rustfmt the blob-removal code (fix the CI fmt gate)
The blob-removal commit (6b4c4dc) failed CI's fmt gate — long assert! lines
and a doc comment that stock rustfmt wraps. No behaviour change; just
`cargo fmt`. Verified via `nix develop -c cargo fmt --check`.
2026-06-22 21:43:51 +00:00
6 changed files with 66 additions and 27 deletions
+12 -4
View File
@@ -115,15 +115,23 @@ Current decisions at a glance (each backed by an ADR):
a self-hosted a self-hosted
**Gitea Actions** pipeline built on a **nix flake** (pinned Rust **Gitea Actions** pipeline built on a **nix flake** (pinned Rust
`1.95.0` — one source of toolchain for dev *and* CI) plus a `1.95.0` — one source of toolchain for dev *and* CI) plus a
prebuilt CI image. **Gate** (`ci.yaml`): `clippy -D warnings` + prebuilt CI image. **Gate** (`ci.yaml`, every step run via `nix
`cargo test` on every branch push / PR. **Release** on a `v*` tag develop -c` so dev and CI share the pinned toolchain): `cargo fmt
--check` + `clippy -D warnings` + `cargo test --no-fail-fast` on
every branch push / PR. **Verify locally the CI way before pushing**
— `nix develop -c cargo fmt --check`, `nix develop -c cargo clippy
--all-targets -- -D warnings`, `nix develop -c cargo test` — and read
the **exit code**, not piped output (a `cargo fmt --check | tail &&
echo clean` masks the real result; a bare host `cargo` may also differ
from the pinned toolchain). **Release** on a `v*` tag
(`release.yaml`): the four non-macOS **D1** targets cross-built (`release.yaml`): the four non-macOS **D1** targets cross-built
with `cargo-zigbuild` (Linux musl static + standalone Windows with `cargo-zigbuild` (Linux musl static + standalone Windows
`.exe`); the two macOS targets via the **dispatched** `.exe`); the two macOS targets via the **dispatched**
`release-macos.yaml` on a Tart Apple-Silicon runner (de-nix the `release-macos.yaml` on a Tart Apple-Silicon runner (de-nix the
`libiconv` load path + ad-hoc re-sign). All published to a Gitea `libiconv` load path + ad-hoc re-sign). All published to a Gitea
release with `.sha256`s. **`fmt` is intentionally not gated yet** release with `.sha256`s. **`fmt` is gated** (`cargo fmt --check`,
(the tree isn't stock-`rustfmt`-clean). Now that this is on `main`, stock defaults — enabled once the tree was reformatted on `main`,
ADR-ci-002 Amendment 1 / issue #35). Now that this is on `main`,
`release-macos` is dispatchable (`workflow_dispatch` is `release-macos` is dispatchable (`workflow_dispatch` is
Gitea-default-branch-only) — **dispatched and verified working**: the Gitea-default-branch-only) — **dispatched and verified working**: the
macOS build + de-nix/re-sign + upload runs end-to-end and the binaries macOS build + de-nix/re-sign + upload runs end-to-end and the binaries
+32 -14
View File
@@ -7,15 +7,15 @@ Amendment 2), which grew out of a one-line copy-rule fix.
## §1. State ## §1. State
**Branch `main`.** Commits this session so far (all on `main`, **not pushed**): **Branch `main`.** Commits this session (`1a2002d` ADR-0005 Am2 + `6b4c4dc`
`07575da` (#39 gate) · `e88fa79` (handoff-77 + changelog rule) · `3ad4aff` blob removal, plus the earlier `07575da` #39 · `e88fa79` handoff-77 · `3ad4aff`
(#36 help) · `64818c0` (handoff-78) · `1a2002d` (**ADR-0005 Amendment 2** — #36 · `64818c0` handoff-78) — **the user pushed these.** A follow-up
the blob-drop decision record). **`style:` rustfmt commit + this CLAUDE.md/handoff doc commit** land on top
(see §5) and are the only unpushed work at handoff time.
**Uncommitted: the blob-removal implementation** (39 files) — staged for review, **Suite green: 2521 passed / 0 failed / 1 ignored** (1810 lib + 8 e2e_pty + 503
commit proposed at session end (see §4). **Full suite green: 2520 passed / 0 it + 200 typing), `fmt --check` + `clippy -D warnings` clean — **verified via
failed / 1 ignored** (1810 lib + 7 e2e_pty + 503 it + 200 typing). `nix develop -c`** (the CI-exact way; see §5's lesson).
`clippy --all-targets -D warnings` + `fmt --check` clean.
**Closed this session:** #36, #39. **Open:** #37 (clause hints), #38 **Closed this session:** #36, #39. **Open:** #37 (clause hints), #38
(diagnostic route — needs a do/defer/close call), #40 (winget release notes). (diagnostic route — needs a do/defer/close call), #40 (winget release notes).
@@ -92,16 +92,34 @@ is deleted outright.
## §4. Process pins / next ## §4. Process pins / next
- Commits user-confirmed, no AI attribution, append-only, on `main`; **push is - Commits user-confirmed, no AI attribution, append-only, on `main`; **push is
the user's step** (everything this session is unpushed). the user's step.**
- **`/runda` was run on both the ADR (design) and the implementation.** The - **`/runda` was run on both the ADR (design) and the implementation.** The
implementation pass found one real gap — the runtime's migrate-on-open glue implementation pass found one real gap — the runtime's migrate-on-open glue
was untested — closed with the new Tier-4 PTY test. It also verified the was untested — closed with the new Tier-4 PTY test. It also verified the
import path migrates (extract→migrate→rebuild) and the non-migration import path migrates (extract→migrate→rebuild) and the non-migration
`parse_schema` callers fail-safe. `parse_schema` callers fail-safe.
- Proposed commit split for the implementation: (1) `feat(types)!` — code +
tests + CHANGELOG; (2) `docs` — the CLAUDE.md/requirements/website/ADR-x-ref
sweep; (3) `docs(handoff-79)`.
- Consider a `cargo sweep` at this milestone. - Consider a `cargo sweep` at this milestone.
- **Next open issues:** #37 (clause hints, on-mission), #38 (escalate - **Next open issues:** #37 (clause hints, on-mission), #38 (escalate
do/defer/close first), #40 (winget release notes). Plus: blob removal means a do/defer/close first), #40 (winget release notes).
`cargo sweep`-worthy build and a good moment to push.
## §5. CI/fmt lesson (cost a red CI run — don't repeat)
The blob commit `6b4c4dc` **failed the CI `fmt` gate** (run 92, `ci.yaml`).
Two compounding causes, both fixed here:
1. **`fmt` IS gated now** (`cargo fmt --check`, ADR-ci-002 Amendment 1 / issue
#35) — but `CLAUDE.md` still said "fmt is intentionally not gated yet." That
stale note bred complacency. **Corrected `CLAUDE.md`** (the gate is fmt +
clippy + test, run via `nix develop -c`).
2. **A broken local check masked the failure:** `cargo fmt --check 2>&1 | tail
-1 && echo "fmt clean"` tests `tail`'s exit code (always 0), **not** fmt's —
so "fmt clean" printed over real diffs. **Always read the exit code**, never
a piped `… | tail && echo clean`.
**Going forward, verify the CI way:** `nix develop -c cargo fmt --check` /
`nix develop -c cargo clippy --all-targets -- -D warnings` / `nix develop -c
cargo test` — matching the pinned 1.95.0 toolchain *and* the devShell env, and
checking exit codes. (A bare host `cargo` happened to be 1.95.0 this time, so
the divergence was the masking pipe, not the toolchain — but `nix develop -c`
guards both.) The `style:` rustfmt fix + this doc update land on top of
`6b4c4dc`; CI re-runs green on the next push.
+1 -2
View File
@@ -394,8 +394,7 @@ mod tests {
" - \"note <> 'type: blob'\"\n", " - \"note <> 'type: blob'\"\n",
"relationships: []\n", "relationships: []\n",
); );
let outcome = let outcome = migrate_to_latest(body, &MigratorRegistry::production(), tmp.path()).unwrap();
migrate_to_latest(body, &MigratorRegistry::production(), tmp.path()).unwrap();
assert_eq!(outcome.migrated_from, Some(1)); assert_eq!(outcome.migrated_from, Some(1));
assert!(outcome.body.contains("version: 2")); assert!(outcome.body.contains("version: 2"));
// The blob column became text. // The blob column became text.
+2 -1
View File
@@ -180,7 +180,8 @@ pub async fn run(args: Args) -> Result<()> {
// force a `.db` rebuild after the v1→v2 migration converts it to text // force a `.db` rebuild after the v1→v2 migration converts it to text
// (the stale `.db` keeps a `STRICT … BLOB` engine column + `"blob"` // (the stale `.db` keeps a `STRICT … BLOB` engine column + `"blob"`
// metadata that load otherwise uses as-is). // metadata that load otherwise uses as-is).
let had_blob_column = std::fs::read_to_string(project.path().join(crate::project::PROJECT_YAML)) let had_blob_column =
std::fs::read_to_string(project.path().join(crate::project::PROJECT_YAML))
.map(|b| crate::persistence::migrations::body_declares_blob_column(&b)) .map(|b| crate::persistence::migrations::body_declares_blob_column(&b))
.unwrap_or(false); .unwrap_or(false);
let migrate_registry = crate::persistence::migrations::MigratorRegistry::production(); let migrate_registry = crate::persistence::migrations::MigratorRegistry::production();
+5 -1
View File
@@ -437,7 +437,11 @@ fn opens_a_legacy_v1_blob_project_by_migrating_to_text() {
), ),
) )
.expect("write project.yaml"); .expect("write project.yaml");
fs::write(proj.join("data").join("Files.csv"), "id,payload\n1,aGVsbG8=\n").expect("write csv"); fs::write(
proj.join("data").join("Files.csv"),
"id,payload\n1,aGVsbG8=\n",
)
.expect("write csv");
fs::write(root.join("last_project"), format!("{}\n", proj.display())).expect("write resume"); fs::write(root.join("last_project"), format!("{}\n", proj.display())).expect("write resume");
// Open via --resume: the runtime migrates blob→text and rebuilds, so // Open via --resume: the runtime migrates blob→text and rebuilds, so
+12 -3
View File
@@ -55,15 +55,24 @@ fn v1_blob_project_migrates_to_text_and_rebuilds() {
assert_eq!(outcome.migrated_from, Some(1), "v1 project was migrated"); assert_eq!(outcome.migrated_from, Some(1), "v1 project was migrated");
let migrated = fs::read_to_string(root.join("project.yaml")).expect("read migrated"); let migrated = fs::read_to_string(root.join("project.yaml")).expect("read migrated");
assert!(migrated.contains("version: 2"), "version bumped: {migrated}"); assert!(
migrated.contains("version: 2"),
"version bumped: {migrated}"
);
assert!( assert!(
migrated.contains("{ name: data, type: text }"), migrated.contains("{ name: data, type: text }"),
"blob column rewritten to text: {migrated}" "blob column rewritten to text: {migrated}"
); );
assert!(!migrated.contains("type: blob"), "no blob type remains: {migrated}"); assert!(
!migrated.contains("type: blob"),
"no blob type remains: {migrated}"
);
// The pre-migration original is preserved as a .bak. // The pre-migration original is preserved as a .bak.
let bak = fs::read_to_string(root.join("project.yaml.v1.bak")).expect("read bak"); let bak = fs::read_to_string(root.join("project.yaml.v1.bak")).expect("read bak");
assert!(bak.contains("type: blob"), "bak keeps the original blob column"); assert!(
bak.contains("type: blob"),
"bak keeps the original blob column"
);
// 2. Rebuild from the migrated text (as the runtime forces when a blob // 2. Rebuild from the migrated text (as the runtime forces when a blob
// column was converted): the column is now `text` and the row data // column was converted): the column is now `text` and the row data