Compare commits

...

2 Commits

Author SHA1 Message Date
Julien Calixte
4153cae9c4 docs(postmortems): record milestone #2A verified, FAT read-only-delete finding
Increment A (persistent-clone publish cycle) is hardware-verified: clone +
persistent open + fast-forward push. Mark the follow-up checklist: git module
fold is in progress (A done; B = divergence + AM_RDO unlink shim; C = editor
wiring), tech-doc revision done, PAT-in-flash tracked as open ADR-011. Note
the FAT read-only-delete limitation and the real-notes-repo sizing caveat.
2026-07-07 08:35:11 +02:00
Julien Calixte
afa61deaa6 fix(firmware): recover git_sync from a leftover clone dir
859c478 aborted if /spiflash/repo already existed — libgit2 refuses to
clone into a non-empty dir (code=Exists). Remove a writable stale/partial
clone and re-clone.

Milestone #2A now hardware-verified end to end: clone (boot 1) + persistent
open + fast-forward push (boot 2) against origin/main, on the 96 KB git
thread, min heap 6.8 MB — clone/checkout fit the stack, no bump needed.

Known limit: the remove only works on a *writable* leftover. libgit2 marks
objects/packs read-only and FATFS won't f_unlink a read-only file (EACCES);
POSIX chmod does not clear AM_RDO on esp-idf's FATFS VFS (verified). So
re-cloning over a run that wrote objects needs an AM_RDO-clearing unlink
shim in esp_stubs.c (increment B, also needed for fetch/repack); until then
the fallback is a one-time `espflash erase-region 0x310000 0x400000`.
2026-07-07 08:34:09 +02:00
2 changed files with 38 additions and 8 deletions

View File

@@ -207,16 +207,26 @@ up.
HTTPS (needs a non-fast-forward against a real remote to trigger it).
- [x] On-device `init → commit → push` over mbedTLS HTTPS — **DONE +
hardware-verified 2026-07-06** (see "On-device push COMPLETE").
- [ ] Revise the `git` module section of the technical doc (it still describes
gix crates/transport) now the device path is confirmed.
- [x] Revise the `git` module section of the technical doc **DONE 2026-07-07**
(commit `2f2f122`): gix → libgit2/git2, transport settled, 96 KB stack,
persistent-clone flow.
- [x] Real cert trust-store (drop the `certificate_check` bypass) — **DONE +
hardware-verified 2026-07-06** (commit `2519ed8`; see "Shortcuts — status").
- [x] Settle the product sync transport — **DECIDED 2026-07-06: HTTPS + PAT**
(on-device libgit2 is HTTPS-only; no libssh2 port).
- [ ] Retire the last shortcut: no PAT-in-flash (ADR-005) — source the token at
provisioning / from secure storage instead of `env!()` into the image.
- [ ] Fold the push into the editor's `git` module (persistent clone +
fast-forward, not a fresh per-boot branch) over the HTTPS+PAT remote.
- [~] Retire the last shortcut: no PAT-in-flash **decision documented, deferred**
as [ADR-011](../adr.md#adr-011-credential-provisioning--how-the-pat-reaches-the-device-and-is-protected-at-rest)
(open): on-device paste → eFuse-encrypted NVS + a per-device fine-grained PAT.
Gates the first non-dev distribution; nothing to implement until then.
- [~] Fold the push into the editor's `git` module (persistent clone +
fast-forward) over HTTPS+PAT — **increment A DONE + hardware-verified
2026-07-07** (`git_sync.rs`, commit `afa61de`): `clone` + persistent `open`
+ fast-forward push proven on device. Remaining: **B** = divergence/merge
path + the AM_RDO-clearing unlink shim (fetch/repack need read-only delete
on FAT — POSIX chmod can't clear it, verified); **C** = lift the logic into
a reusable `git` module wired to the editor's `Ctrl-G`. Storage caveat: the
real notes repo is 3.9 GB / 562 MiB pack — needs shallow+sparse or a
dedicated small repo (ADR-007), can't clone whole.
- [x] Move git to a dedicated large-stack task so the shared main-task stack (and
the editor build) can drop back — **DONE + hardware-verified 2026-07-06**.
`git_publish` now runs on its own `std::thread` (`GIT_STACK = 96 KB` via

View File

@@ -241,13 +241,33 @@ fn open_or_clone() -> Result<(Repository, bool)> {
Ok((repo, false))
}
Err(_) => {
log::info!("no repo at {REPO_DIR} — cloning {REMOTE_URL}");
// A leftover at REPO_DIR is a partial clone (libgit2 refuses to clone
// into a non-empty dir, code=Exists). Remove it and re-clone. NOTE:
// this only succeeds if the leftover is writable — libgit2 marks
// objects/packs read-only, FATFS won't f_unlink a read-only file
// (EACCES), and POSIX chmod does NOT clear the attribute on esp-idf's
// FATFS VFS (verified on hardware). So a clone that got far enough to
// write objects can't be cleared from here yet; the proper fix is an
// AM_RDO-clearing unlink shim in esp_stubs.c (increment B, needed for
// fetch/repack too). Until then the fallback is a one-time wipe:
// `espflash erase-region 0x310000 0x400000`.
if Path::new(REPO_DIR).exists() {
log::warn!("{REPO_DIR} exists but is not a valid repo — removing stale clone");
fs::remove_dir_all(REPO_DIR).with_context(|| {
format!(
"removing stale {REPO_DIR} — if this is EACCES (read-only files on \
FATFS), erase the storage partition once: \
`espflash erase-region 0x310000 0x400000`"
)
})?;
}
log::info!("cloning {REMOTE_URL} → {REPO_DIR}");
let mut fo = FetchOptions::new();
fo.remote_callbacks(auth_callbacks());
let repo = RepoBuilder::new()
.fetch_options(fo)
.clone(REMOTE_URL, Path::new(REPO_DIR))
.context("clone (is REPO_DIR a leftover partial clone? it must not exist)")?;
.context("clone")?;
Ok((repo, true))
}
}