OXYGENHUB
# cd4eb55e3b3d oxygen / docs / backlog-overnight-2.md
105.3 KiB · Markdown · last changed Backlog 3: not worse than Git 1 month ago renderedsource blame history raw
1
# Overnight backlog 2 (2026-09-28 to 2026-09-30)
2
3
Andy's brief: usability and features on Oxygenhub, as many features as
4
possible in the VS Code extensions, and a stable, mature `oxy` client.
5
Work top to bottom, but interleave the three tracks: after each hub or
6
extension item, take the next oxy stability item if one is open. Each
7
item is done when its tests pass, the gate is clean, it is committed with
8
`oxy` as Andy and pushed to Earth. Log each iteration at the bottom.
9
10
## Hub (oxygenhub): usability and features
11
12
- [x] H1 Dashboard for signed-in users: my repositories, proposals needing me, issues assigned to me, recent activity across repositories I can see (pushes, proposals, issues, releases).
13
- [x] H2 Empty-repository quickstart: exact commands for this repository (remote add, token, CA download, push) and for starting from nothing.
14
- [x] H3 Theme switch (dark, light, auto) in the header and under Settings, remembered in the cookie; check every page in light mode.
15
- [x] H4 Profile and organisation pages: generated identicon avatar, display name, bio, linked keys, repositories, recent activity, organisation members and teams.
16
- [x] H5 Diff view: split (side by side) toggle remembered per user, collapse per file, hide whitespace changes, intra-line word highlighting.
17
- [x] H6 Forks: fork button, fork creation (store copy, forked_from), fork list, "propose to upstream" from a fork.
18
- [x] H7 Stars and watch counts, explore sorting (activity, stars, name) and filtering, repository topics.
19
- [x] H8 Proposal and issue lists: filters (author, label, target, assignee), sort orders, pagination, counts in tabs.
20
- [x] H9 File view: file tree sidebar, `y` for a permalink to the state, line range copy, blame age shading and "blame the state before this change".
21
- [x] H10 Compare page with bookmark and tag pickers and a "propose this" button.
22
- [x] H11 Help pages: getting started (install oxy, clone, push, tokens, CA, delegating to the hub), keyboard shortcuts, glossary for people coming from GitHub.
23
- [x] H12 Password reset and email verification (email when SMTP is set; admin-issued reset link otherwise).
24
- [x] H13b Uploaded profile pictures and organisation logos (Andy, oxygenhub#11): upload, validate by content, re-encode and crop, identicon fallback, remove.
25
- [x] H13 Notifications: per-thread mark read, filter by kind and repository, "participating" versus "watching".
26
- [x] H14 Accessibility and mobile pass: focus styles, skip link, landmarks, responsive tables and tabs, contrast in both themes.
27
- [x] H15 Organisation webhooks page; admin storage and repository maintenance page (sizes, oxy gc).
28
- [x] H16 Performance: cache rendered Markdown and highlighted files by object hash; measure the heaviest pages on Earth before and after.
29
- [x] H17 Issue API: labels, assignees and milestone on create/edit; create labels (oxygenhub#3).
30
- [x] H18 `refs #N` in change messages appears on the issue's timeline (oxygenhub#4).
31
- [x] H19 Projects: boards of issues and proposals across an owner's repositories (oxygenhub#5).
32
- [x] H21 Projects API: list, create, board, cards and columns over /api/v1 (oxygenhub#15).
33
- [x] H20 Issue Markdown resolves against the default bookmark, not `main` (oxygenhub#6).
34
35
## VS Code (oxygen-vscode, oxygenhub-vscode)
36
37
- [x] V1 oxygenhub-vscode: proposal files in native diff editors (target state against projected state through a content provider), with review threads in them.
38
- [x] V2 oxygenhub-vscode: notifications count in the status bar and an inbox quick pick that opens the item.
39
- [x] V3 oxygenhub-vscode: completion of `#N` and `@user` in the Oxygen commit box and in comments; hover on `#N` shows the title.
40
- [x] V4 oxygenhub-vscode: create an issue from the editor selection or a TODO line, with a permalink.
41
- [x] V5 oxygenhub-vscode: releases view; draft a release from a tag.
42
- [x] V6 oxygen-vscode: history graph actions for split, squash and rebase (oxy history commands), with confirmation and undo hint.
43
- [x] V7 oxygen-vscode: commit box polish: amend toggle, message history, signed/unsigned indicator, warning on conflict markers.
44
- [x] V8 oxygen-vscode: Open on Remote in editor gutter context (line permalinks), copy permalink.
45
- [x] V9 Both: README screenshots-free walkthroughs, settings documentation, CHANGELOG per release.
46
47
## oxy: stability and maturity
48
49
- [x] O1 Property tests (proptest) that applying the same changes in any order gives the same state, over random edit sequences on several files.
50
- [x] O2 `oxy fsck`: verify every object's hash, every change's references and signatures, the op log chain, and report precisely.
51
- [x] O3 Crash safety audit: every write goes through write_atomic or the store's batch; a test that interrupts commit and push at each step and proves the repository still opens and fscks.
52
- [x] O4 Benchmarks with thresholds: 10 000 files, 5 000 changes; status, commit, log, blame, push, clone; record numbers in docs.
53
- [x] O10a Heads at scale (first part of O10): `State::maximal` compared every pair of heads (quadratic; about 1.5 s per call at 4 500 heads, called three times per commit); now one walk. Commit reuses the snapshot's base state instead of loading it again.
54
- [x] O10b Parallel snapshot work: rendering the base state's files, building file graphs from a checkpoint, and reading working-copy files now run on every core (rayon).
55
- [x] O12 `log_for_path_in` and `blame_in` rebuild and re-render the whole state on every call, and walk every change: take an already loaded state, stop at the first change touching the path when only the newest is wanted, and benchmark it (found in H16).
56
- [x] O10c Snapshot cost in the in-memory state: faster graph hashing, no unused predecessor map, inline successor sets, lines moved not copied; `OXY_TIMINGS=1` phase timings.
57
- [x] O10d Skip reading text files whose stat matches a manifest that recorded them equal to the base (racy entries, those modified at or after the manifest was written, are still read); test that no edit is ever missed.
58
- [x] F1a **Priority (Andy, 2026-09-28): dogfood OxygenFlow.** Every push to oxygen, oxygenhub, oxygenflow and both extensions lints, tests and builds through OxygenFlow on Earth, with results as checks on OxygenHub. F1a: ephemeral scratch mounts in jobs (`ephemeral://tmpfs`, `ephemeral://disk`) so builds have room; `make deploy-runner`.
59
- [x] F1b A build image for the runner: a rootfs with the Rust toolchain (stable, rustfmt, clippy), Node, e2fsprogs-free build basics, and every crate of the oxygen, oxygenhub and oxygenflow lock files vendored with cargo pointed at them (jobs have no network); a script that rebuilds it when lock files change; imported on Earth and measured on a real cargo test of oxygen in a microVM.
60
- [x] F2 oxygen pipeline (.flow/pipeline.kdl): fmt, clippy -D warnings, tests, release build; green on a real push.
61
- [x] F3 oxygenhub pipeline: needs oxygen alongside (path dependencies), so the job checks out oxygen at a pinned change; fmt, clippy, tests, build.
62
- [x] F4a oxygenflow's pipeline gains fmt, clippy and tests, with oxygen as its pinned subrepo; oxygenhub builds against the oxygen that oxygenflow pins (one oxy-core), and claims follow subrepos of subrepos.
63
- [x] F4b Extension pipelines (oxygen-vscode, oxygenhub-vscode): typecheck, bundle, tests and package, offline. Needs the npm packages in the build image, and `oxy` for oxygen-vscode's tests (built in the job from a pinned oxygen). The hub client tests skip without a hub binary.
64
- [x] F6a Runner runs several jobs at once within a budget (Earth: 3 jobs, 24 GiB, 8 vCPUs); the hub hands out only jobs that fit the room the runner reports.
65
- [x] F6b1 Faster Rust jobs, first part: CI builds without debug info or incremental state, set once in each pipeline's rust sandbox env.
66
- [x] F6b2 A warm dependency cache: the dependencies of each lock file prebuilt in the build image and copied onto the job's scratch disk; measured on oxygenhub.
67
- [x] F6b3 The same prebuilt dependencies for oxygen and oxygenflow (one image rebuild, all pipelines re-pinned).
68
- [x] F5 Required checks: each repository's default bookmark requires its pipeline's checks to land, and the work loop pushes and waits for green instead of gating only locally.
69
- [x] O10e Checkpoint payload version 2: large file graphs as shared blobs named from an index, read and decoded in parallel; readers accept both versions (spec §11). Agreed by Andy on 2026-09-28.
70
- [x] M1 (metalgear feedback 1, reported by Andy) Bookmarks with `/` in their names could not be pushed: attestation and checkpoint files are now flat, escaped names (oxygen#12). The hub is pinned to the fix and deployed.
71
- [x] M2 Flow on/off per repository in the hub (Andy's request; feedback 10): a switch in repository settings for site admins, with hub.toml's `[flow] repos` as the default; no restart.
72
- [x] M11 (feedback 12, Andy: urgent) Flow runs numbered per repository, not by the hub-wide id; existing runs renumbered and their check links rewritten by migration 0019 (oxygenhub#23).
73
- [x] M12 (Andy; feedback 14a) Delete a bookmark on a remote: `oxy push REMOTE --delete NAME` (a signed, attested deletion the server checks like any update; never the default bookmark), and a delete button on the hub's bookmarks page for writers.
74
- [x] M13 (feedback 3a) `oxy bookmark switch --carry`: take uncommitted work to the other bookmark when it applies cleanly there (and `bookmark create --switch` moving the working copy onto a new bookmark at its base, changes and all).
75
- [x] M14 (feedback 7a) `oxy show`'s summary lists every path the change touches with its kind (added, modified, removed, renamed, mode), not only additions.
76
- [x] M15 (feedback 17a) A queued run says where it stands: its jobs' position in the runners' queue, what runs now and what fits the free room (the runner already runs several jobs within its budget; Earth's 5 vCPUs are deliberate after F6a), on the Flow tab, the run page and the API.
77
- [x] M3 Start a run without moving a bookmark (feedback 9): "Run pipeline" on a state and "Re-run" on a finished run, in the web and the API.
78
- [x] M4 Guest `/dev/fd`, `/dev/stdin|stdout|stderr` links in flow-init (feedback 15), and job variables `FLOW_STATE`, `FLOW_HEADS`, `FLOW_BOOKMARK`, `FLOW_RUN`, `FLOW_REPO` (feedback 17).
79
- [x] M5 Sandbox-level `env` in pipeline.kdl, and the image's environment kept through `flow rootfs import` (feedback 16).
80
- [x] M6 Flow API: `GET /repos/{o}/{n}/flow/runs`, `/flow/runs/{n}` with jobs, a job's log; checks by head hash as well as state (feedback 11). JSON 404s under `/api/v1` (feedback 13). Per-repository run numbers, or a note (feedback 12).
81
- [x] M7 Server push errors name the operation and bookmark and log the error chain (feedback 2).
82
- [x] M8 `merge` updates a working copy that is clean against the pre-merge base; `checkout` tells "removed by you" from "not yet written"; `bookmark switch --help` says what happens to uncommitted work (feedback 3).
83
- [x] M9a `oxy describe` naming (tag, else nearest tag + distance + change, else bookmark + change, `-dirty`, `--json`), tags accepted where a change is and `tag list` showing heads (feedback 4); `commit -F` (6).
84
- [x] M9b `submodule status` shows added-but-unpinned and has `--json` (5, 7); doctor before the first commit (8).
85
- [x] M10 Hub: say "signed by an unlinked key" on changes and in `/api/v1/user`, pointing at Settings (feedback 14).
86
- [x] P1a Text conflicts resolved region by region on OxygenHub (first part of P1, below).
87
- [x] P1c Name conflicts on the resolver: keep one candidate, which takes the name as a move.
88
- [x] P1d "Resolve in VS Code": the resolver hands a conflict to oxygen-vscode (a vscode:// URI the extension handles: clone or open, switch to the bookmark, open the file).
89
- [x] P1e Replace conflicts of binary files: oxy-core cannot yet resolve one by keeping an existing candidate (writing a candidate's bytes counts as no change); make that a resolution, then offer it on the resolver.
90
- [x] P1 **Conflict resolution on OxygenHub** (Andy's list, item 1). A resolver page for a conflicted state:
91
  - Per conflict region: pick a side, several sides, or none; edit freely; tree conflicts too (keep a candidate).
92
  - Commit the result as a `kind = resolution` change on the bookmark, through the web-edit path. Signing: sealed when the author next pushes, or "resolve in VS Code" hands off to oxygen-vscode.
93
  - Shareable link to a conflict.
94
  - First single-user, then P1b.
95
- [x] P1b Real-time collaboration on the resolver: presence, live edits merged between viewers (SSE or WebSocket), and whoever is viewing sees the resolution land.
96
- [x] P2a Function history in oxy-core and the CLI: `Repo::line_history`, `oxy log -L START,END:PATH` (first part of P2, below).
97
- [x] P2b Function history on the hub: tree-sitter function boundaries (Rust, TypeScript, Python, shell), and a function's history as a list and a small graph from the file page.
98
- [x] P2 **Function history** (item 5; needs no semantic-deps):
99
  - oxy-core: a history query over a set of atoms (the changes that inserted or deleted any of them, with deleted atoms included).
100
  - Hub: function boundaries by tree-sitter for the languages we host (Rust, TypeScript, Python, shell).
101
  - Clicking a function on a file page shows every ChangeId that touched its lines, as a list and a small graph; exact because atoms keep identity across edits and moves.
102
  - The cross-file semantic graph stays out; noted as future work.
103
- [x] P3a The swimlane board on the hub: lanes of independent changes, dependency edges, no merge nodes (first part of P3, below).
104
- [x] P3b Drag and drop on the board, proposal bookmarks only: dropping a change onto another proposes a squash, dragging a dependent chain proposes a rebase; the hub writes the plan, the author's oxy or oxygen-vscode applies and signs it.
105
- [x] P3 **Swimlanes** (item 3):
106
  - Hub: a bookmark's changes drawn as lanes of independent changes (oxy-core `lanes`), with dependencies as edges and no merge nodes.
107
  - P3b: drag and drop on proposal bookmarks only. Dropping a change onto another proposes a squash, dragging a dependent chain proposes a rebase. The hub produces a plan and the author's oxy or oxygen-vscode applies and signs it (V6 has split, squash and rebase). Reordering independent changes means nothing and is not offered.
108
- [x] P4a The `receipt` object and attestation in the Oxygen spec and oxy-core: a signed receipt verifies on its own; the repository keeps receipts by state; `oxy receipt list|show|export|verify|import` (first part of P4, below).
109
- [x] P4c The runner signs each receipt with its own identity; the hub verifies it against its known runner keys, keeps it in the repository, and shows it on the run, the check and the change.
110
- [x] P4d1 Flow `output` and artifacts: a job's declared outputs become repository blobs named by its signed receipt, listed and downloadable on the run page.
111
- [x] P4d2 A release asset links to the receipt that built it (by content), and an artifact can be attached to a release from its run.
112
- [x] P4 **Signed Flow receipts**:
113
  - A `receipt` object type in the Oxygen spec.
114
  - A runner identity whose key (composite Ed25519 + ML-DSA-65) signs each receipt.
115
  - Receipts stored in the repository and verified by the hub.
116
  - Flow `output` and artifacts, so a release asset links to the receipt that built it.
117
- [x] P4b **Trust dashboard** (item 4): per release and asset, a graph of authors' identities and signatures, the runner and its pinned environment, inputs (state, scope hash), and receipts, with a one-click audit view and export.
118
- [x] H11 Run pipeline without a push, made easy (Andy): a primary button on the Flow tab, reasons shown when it is missing, and Run pipeline beside each bookmark and on change pages (runs the change's own state); `POST /flow/runs` takes `{"change"}`.
119
- [x] V1 **Versions, visible** (Andy): every component reports its version and build.
120
  - The build is `oxy describe` passed as `OXY_BUILD` by `make install`/`make deploy`/`make build`, a Flow build's heads, or "local build".
121
  - Where it shows: `oxy --version` (and `version --json`), `flow --version`, the version a runner reports, `oh --version`, and the hub's footer, `/about` (with runners' versions for admins), `/api/v1/version` and `/health`.
122
- [ ] V2 **Changelogs and 0.2.0**: CHANGELOG.md in oxygen, oxygenhub and oxygenflow, like the extensions' (0.x: a feature bumps the minor number, a fix the patch). Cut 0.2.0 of oxy, OxygenHub (+ oh) and OxygenFlow summing up the work so far.
123
- [ ] V3 **Releases** (Andy: tag plus hub release, each component on its own version): `make release VERSION=x.y.z` per repository.
124
  - It checks the changelog has an entry, bumps the version, commits and pushes, and waits for a green Flow run.
125
  - It signs an oxy tag `vx.y.z` and pushes it, then creates the OxygenHub release with notes from the changelog.
126
  - It attaches the Flow-built artifact (the oxy and flow binaries, and the extensions' .vsix once their pipelines keep it), so each release's Trust page shows a signed receipt.
127
- [ ] H12 **Runner management** (Andy; oxygenhub#41), in parts:
128
  - [x] H12a Runners in the database: registration from the UI (`flow runner register`) with the signing key bound at registration, replacing hub.toml tokens. A Runners page (status, key, architecture, tiers, labels, capacity, current jobs) with each runner's job history, and drain, disable and remove.
129
  - H12b Scopes: site, organisation or repository runners. Claims only return in-scope jobs, and repositories and organisations get Settings → Runners.
130
  - H12c Labels and architecture: `runs-on` in pipelines (oxygenflow), with runners reporting labels and architecture in claims.
131
  - H12d Queue view (why each job waits) and the runners API.
132
  - H12e Bring up Plex (x86_64) as a second microVM runner, and a VisionFive 2 Lite (riscv64). The JH7110 has no hypervisor extension, so it needs a non-microVM tier first. Andy will say how to reach them.
133
- [x] H10 `oh`, a command-line client for OxygenHub like GitHub's `gh`: a crate `crates/oh` in the oxygenhub workspace on hub-api. First slice: `oh auth login`, `oh issue list|view|create`, `oh proposal list|view|create|land`, `oh run list|view|watch`, `oh release list|create|upload`, with `--json`.
134
- [x] L1 **Large inserts as blobs** (oxygen#34): change format 2 stores inserts of 4 KiB or more as text blobs (c8a26be3, spec Draft 0.8q); hub db750ff1 and runner 6cbadaca were deployed first. The Linux first commit now succeeds: 55 s, 9.9 GB, 442 MB store.
135
- [ ] L1b **Memory for a whole tree**: a clean `status` of the Linux tree is killed at a 14 GB cap, and the commit peaks at 9.9 GB (Git: 42 MB). Every line is held as its own `Vec` in the change and again in the file graph. Next: find where status spends memory, then load graphs on demand and keep line bytes shared, not copied.
136
- [ ] L2 **Linux, stage 1** (Andy): Git vs oxy on the Linux tip, with `~/bench/linux-bench.sh` on Earth (Andy runs it). Needs L1. Results go in docs/benchmarks.md.
137
- [ ] L3 **Linux, stage 2**: import slices of recent history (2k, 10k, 50k commits) with verify. Needs L1, oxygen#30 and memory-bounded import (drop untouched graphs, reload on demand).
138
- [ ] L4 **Linux, stage 3**: the full 1.3M-commit import, overnight on Earth.
139
- [ ] P5 **Web workspaces** (item 2), in parts:
140
  - P5a: flow-casfs, a read-only FUSE mount of a state from CAS chunks, measured.
141
  - P5b: "Open in Workspace" on a change. openvscode-server (MIT, unmodified) with oxygen-vscode inside a Firecracker microVM per workspace, the state mounted read-only through casfs with an ephemeral overlay for edits, and commits back through oxy. Boot time measured, aiming under 100 ms for the mount.
142
  - A fork ("oxycode") only if an extension cannot do what we need; decided with Andy.
143
- [ ] P5c Import microsoft/vscode's full Git history with `oxy git import` into a local scratch repository (not published): about 130 000 commits as a stress test of import, log, blame and status at scale, recorded in docs/benchmarks.md.
144
- [x] O10f Load file graphs lazily from a version 2 checkpoint: a state keeps unread graphs by reference and reads one when first needed.
145
- [x] O10g Render only what a snapshot needs, first part: the manifest records, per file, the key of the base graph it matched, so an unchanged file whose graph is untouched is neither read nor rendered; used by status, log, show, tag and bookmark.
146
- [x] O10h The same for commit, diff, hunks, discard and showing a change: only the graphs the change touches are built (caching materialized states turned out unnecessary).
147
- [x] O5 Concurrency: two processes committing, snapshotting and pushing on one repository; locks hold, nothing is lost.
148
- [x] O6 Error messages audit: every user-facing error says what to do next; `oxy doctor` checks identity, config, remotes, CA files, hub reachability.
149
- [x] O7 Shell completions (`oxy completions bash|zsh|fish`) and man pages generated from clap.
150
- [x] O8 Git round trip on real history: import a scratch mirror of tetriblox (never the live repo), export it back, compare trees at every commit.
151
- [x] O11 Line placement found by O8: in 5 of 539 tetriblox commits (all non-merge; e.g. 43de61fac554 src/banked/code_bank_07.c, 021ad78791a2 code_bank_03.c, 2721670b49af code_bank_02.c, 435fbe1d5fac scripts/x16_unchanged.py, d17c19b22748 test/test_memory_guards.py) one line lands elsewhere than in Git with no conflict reported, so an insertion is anchored between atoms that are not adjacent in the base. Reproduce with `oxy git verify` on a scratch mirror; find which edit of the importer's diff (build_from_entries/text_edits) anchors non-adjacently, add a debug assertion for anchor adjacency, fix, re-verify.
152
- [x] O9 Portability review for macOS and Windows: path separators, executable bits, case-insensitive file systems, CRLF; tests where it can be exercised on Linux.
153
154
## Log
155
156
- 2026-09-28 H1 done: signed-in dashboard at `/` (review queue, own proposals, assigned issues, repositories, activity feed filtered to visible repositories); test `dashboard_shows_what_needs_the_viewer`; deployed to Earth.
157
- 2026-09-28 O1 done: `crates/oxy-core/tests/commutativity.rs` (proptest, 32 cases): diverging clones with random edits, rebuilt in shuffled and random dependency orders, checkpoint round trip, and push in random order to a fresh clone; all give identical checkpoints, heads and trees. About 80% of generated cases conflict. No defects found. Workspace tests all green (oxy-core 129 + new).
158
- 2026-09-28 H2 done: empty repositories show a quickstart (remote named after the host, e.g. `earth`; CA download and `remote.X.ca` when self-signed; token placeholder; push; start from nothing; `oxy git import`); readers get a plain notice. Tests: `empty_repository_shows_a_quickstart` and a remote-naming unit test; hub spec §6 row updated; deployed.
159
- 2026-09-28 O2 done: `oxy fsck [--json]` (objects, op log through all parents, references and change closure with identities/blobs/chunks/signatures, bookmark states, checkpoints re-derived, ref and tag attestations verified); spec Draft 0.8h §21.1, guide updated. Test `fsck_passes_a_sound_repository_and_names_each_fault` (corrupt object, missing change, missing blob, broken op head). Clean on oxygen, oxygenhub, oxygen-vscode, oxygenhub-vscode, hellooxy. Release binary rebuilt.
160
- 2026-09-28 V1 done: hub API `GET /proposals/{n}/files` and `/proposals/{n}/content/{base|head}/{path}`, and anchored comments now carry their current `line` (deployed). oxygenhub-vscode 0.2.0: proposals expand to files, native diff editor per file, View Changes multi-diff, review threads on the projected side; installed in Default and Rust profiles (Developer: Reload Window to pick it up). Tests: hub api test extended; extension pass 6 fail 0 against a real hub.
161
- 2026-09-28 O3 done: new `oxy_core::durable` (unique temp, fsync, rename, directory fsync) for every durable write; **real bug fixed**: inside a write batch the op head moved before the batch (including the new op object) was on disk, so a crash there left an unopenable repository; `Store::flush` now runs before the head moves. Test stops commit (20 steps) and push (32 steps) at every step: both repos open, fsck clean, command retries. Spec §13.6. Hub rebuilt and redeployed with the fix. Not covered: working-copy file writes during checkout are still plain writes (user files, recoverable by snapshot).
162
- 2026-09-28 H3 done: header theme switch (dark/light/auto) and Settings → Appearance, theme cookie; auto follows the system live. Light-mode pass by headless Chrome screenshots of dashboard, overview, file, proposal, issue, settings, changes: found and fixed highlighted code drawing boxes in both themes (syntect scope `comment` colliding with the page `.comment` class; classes now `sy-` prefixed) and the band ADMIN link unreadable in light. Test `theme_is_chosen_in_settings_and_remembered_in_a_cookie`; hub spec §6.1; deployed. The code editor picks its theme at load, not live.
163
- 2026-09-28 O4 done: harness `crates/oxy-core/tests/bench.rs` (ignored; run by hand) and `docs/benchmarks.md`. Full run on disk took 5 h 14 min. **Andy: status and commit are slow at scale**: at 10 000 files / 5 000 changes a commit averages 3.8 s, a 10-file commit 9.1 s, a clean status 3.0 s, blame 2.9 s, peak memory 1.4 GiB; push 0.8 s, clone 3.4 s, log 0.4 s are fine. Cause: every snapshot loads and materializes the whole base state. New item O10 (moved ahead of O5) fixes it.
164
- 2026-09-28 V2 done: hub API `GET /notifications`, `POST /notifications/{id}/read`, `POST /notifications/read` (scope user:read; deployed). oxygenhub-vscode 0.3.0: status-bar bell with unread count across hubs, new-notification messages, Open Inbox quick pick that marks read and opens proposals/issues in the editor. Installed in both profiles (Reload Window). Tests: hub api test extended; extension pass 7 fail 0.
165
- 2026-09-28 O10a done: `State::maximal` was quadratic in heads (4 500 heads in the benchmark); now one walk, with a property test against the old definition. Commit reuses the snapshot's base. At 10 000 files / 5 000 changes: commit 3.8 s to 0.86 s, 10-file commit 9.1 s to 0.86 s, status 3.0 s to 0.80 s, blame 2.9 s to 0.81 s, memory 1.4 to 1.1 GiB; benchmark run 5 h 14 min to 1 h 12 min. Hub redeployed with it. O10b (manifest fast path, lazy base) remains for the per-file cost.
166
- 2026-09-28 V3 done: oxygenhub-vscode 0.4.0: `#` and `@` completion in the Oxygen commit box and comment editors, hover on `#N`; repository from the SCM root. Parsing tested in Node (pass 10, fail 0). Installed in both profiles.
167
- 2026-09-28 O10b done: rayon for materialize, checkpoint file graphs and working-copy reads. At 10 000 files: clean status about 800 to 460-580 ms, commit 861 to 550-605 ms. Remaining cost is loading the whole base (O10c, needs a checkpoint format change in the spec, so left for Andy to agree). Hub redeployed.
168
- 2026-09-28 H4 done: `/{owner}` profile (identicon, display name, bio, location, website, keys, organisations, visible repositories, activity by that person; never the email) and organisation page (description, members and roles, teams with sizes, repositories, activity); `/avatar/{name}` identicons also in the header and feeds; migration 0008 (bio, website, location) and Settings fields (http(s) websites only); `avatar` and `help` reserved. Test `profile_and_organisation_pages`; screenshot checked; deployed (andymccall profile 200 on Earth).
169
- 2026-09-28 O5 done. **Real bug fixed**: concurrent commands overwrote `.oxy/ops/head`, so the loser's operation (and its commit on the bookmark) vanished; a two-workspace test lost about half its commits every run. The head now moves by compare-and-swap under a short `.oxy/ops/lock`, and a mover that lost the race records a merge op (two parents, three-way merged view; bookmarks keep both sides' heads), as spec §12.1 described; undo refuses on a moved head. Tests: concurrent two-workspace commits with pushes lose nothing (5/5 runs), deterministic merge of two ops from one parent; two real oxy processes x 25 commits keep all 50 (39 merges). Spec §12.1 updated. Hub redeployed.
170
- 2026-09-28 H5 done: word-level marks within paired changed lines, `?w=1` hides whitespace-only changes (change, compare, proposal files), collapsible files (over 400 rows start closed), unified/split toggle remembered in a `diff` cookie, collapse/expand all. Tests: two renderer unit tests and `diffs_mark_words_hide_whitespace_and_collapse`; split view checked by screenshot; hub spec §6.8.1; deployed.
171
- 2026-09-28 O6 done: `oxy doctor [--offline] [--json]` (op log and stale locks, identity object/secret/expiry, author defaults, each remote: plain HTTP, token, CA file, reachability and repository id) with a next step per problem; every failed remote request now carries a hint (untrusted certificate, 401, 403, 404, refused, DNS, timeout) and transport errors no longer repeat the URL; clearer messages for different-repository remotes, pull-first pushes (still contains "pull first", which oxygen-vscode matches), unauthorized keys, and no repository. Tests: hint table and doctor scenarios. Clean on oxygen and oxygenhub checkouts. Spec §21.2, guide. Hub redeployed.
172
- 2026-09-28 H6 done: fork button and form, `Repos::fork` (new id, hard-linked objects, bookmarks copied, no policy), migration 0009 `forked_from`, forks list, "forked from" and fork count on the overview, "propose to upstream" link. Test `fork_and_propose_upstream` (fork, fsck, clone the fork, push a bookmark, propose upstream, files tab shows it). Hub spec §6.3 row; deployed.
173
- 2026-09-28 O7 done: `oxy completions SHELL` (clap_complete) and `oxy man [--dir]` (clap_mangen 0.3, 104 pages); help text written for 112 bare commands and arguments with a test that every one has help; oh-my-bash plugin loads bash completions; guide section. Tests `cli_docs` (every shell, pages, argument help on the page). Andy: `oxy completions bash > ~/.local/share/bash-completion/completions/oxy` to enable.
174
- 2026-09-28 O7 note: the O7 commit went in before clippy finished (two lints in the new help test); fixed in the next commit. The gate now runs to completion before every commit.
175
- 2026-09-28 H7 done: stars (toggle, counts), watcher counts on the overview, repository topics in settings (validated) shown as chips, Explore filter by text and topic and sort by activity/stars/name with per-row stars and topics; migration 0010. Tests: topic parsing unit test and `stars_topics_and_explore`. Gate fully green before commit; deployed.
176
- 2026-09-28 O8 done. `oxy git verify GIT_DIR` checks an import commit by commit (content, exec bits, symlinks, pins; conflicts named with their changes; first differing line). On a scratch mirror of tetriblox (539 commits, 208 merges, 880 gitlinks; the live repo was only read by `git clone --mirror`): import 36 s, tip tree round-trips exactly, and verification found **real core bugs that also affect ordinary commits**, now fixed: (1) insert after a line, then delete that line, left a delete/insert conflict (deletions now depend on the inserters of the lines right after them); (2) a binary or submodule reverted to an earlier blob lost it, and replacements had no dependency (now depend on the change that set their old blob; causal supersession, legacy records keep the old rule); (3) merges of independently replaced binaries/submodules stayed conflicted (importer resolves to the merge's blob). Mismatching commits went from 50+ (verification stopped early) to 5, now O11. Tests: 3 regressions (sequential insert/delete, binary revert, Git binary merge + verify). Spec Draft 0.8i §8.1, §9.3, §23. Hub redeployed.
177
- 2026-09-28 H8 done: open counts on the Proposals and Issues tabs (from the repository lookup), issue `author:` filter and sorts (newest/oldest/updated/comments), proposal filter (`author:`, `label:`, `target:`, words) with labels shown and sorts (updated/newest/oldest), 25-per-page pager keeping filter and sort. Test `lists_filter_sort_page_and_count`; spec §6.3 rows; deployed.
178
- 2026-09-28 O9 done: `portable::Platform` (detected, overridable in tests); no exec bits: snapshot keeps the recorded bit (it was recording a mode change on every Windows snapshot); no symlinks: a plain file holding the target stays the symlink (spec §7.7 required it, the code converted it); checkout lists case collisions and Windows-invalid names on affected platforms and `oxy doctor` does everywhere; CRLF already handled by the eol attribute; separators verified (components joined with /). Tests simulate Windows and macOS. Follow-up: normalise `\` in paths typed on Windows command lines. Spec §7.7. Hub redeployed.
179
- 2026-09-28 V4 done: oxygenhub-vscode 0.5.0 "New Issue from Selection" (context menu): quotes the selection under a permalink (head-pinned when single-headed and saved), TODO/FIXME lines suggest the title and can be annotated `TODO(#N)`, then the issue opens; line-range links. Tests pass 13 fail 0. Installed in both profiles.
180
- 2026-09-28 O11 done: root cause in `text_edits`: similar's capture_diff_slices runs a Compact hook that slides deletions along equal lines and can leave an insertion with an earlier old index, anchoring a block one line early (a brace on the wrong side). Now uses the diff with only the Replace hook. Randomized replay test (2 000 repetitive files; fails on the old code). tetriblox scratch import verifies all 539 commits, 95 585 files. Note: that commit also picked up Andy's new docs/xenon_spec.md and fix.py, fix2.py, format_xenon.py from the working copy; left for Andy to decide.
181
- 2026-09-28 UI request from Andy (between items): repository page centred with the sidebar, README as wide as the file table, clone command in the top bar; repository, organisation and account settings get a sticky list of sections on the left and equal-width boxes. Screenshot-checked, deployed.
182
- 2026-09-28 V5 done: oxygenhub-vscode 0.6.0 Releases view (releases per repository with latest/pre-release, assets with size and downloads, source archives) and Draft Release…: pick an unreleased tag, title from the tag message, notes generated from the changes since the newest full release's tag, pre-release suggested from the tag name. End-to-end test tags with oxy and releases through the client. Tests pass 15 fail 0; extension spec §8.2. Installed in both profiles.
183
- 2026-09-28 H9 done: file page tree sidebar (path expanded, 200 per directory then "N more", hidden under 900 px); state addresses `h1+h2` (sorted full hashes, at most 32) so any state has a permalink; `y` on file and tree pages switches to it; a line-range bar with Copy lines and Copy permalink; blame shaded in ten age steps and each run links "blame before this change" (the state of its dependencies). Tests: view unit tests (sidebar order and limit, state addresses) and browse end to end. Hub spec §6.3, §6.6, §6.13. Deployed; health ok.
184
- 2026-09-28 Requests from Andy (between items):
185
  - **OxygenHub naming.** The name is written OxygenHub everywhere people read it (hub, both extensions: oxygenhub-vscode 0.6.1, oxygen-vscode 0.7.1).
186
  - **OxygenFlow M0, built and running on Earth.** Xenon was renamed to OxygenFlow, in the separate oxygenflow repository. It has:
187
    - flow-core: KDL pipeline, validation with lines, canonical CBOR, receipts, runner API types
188
    - flow-init, as the guest's PID 1
189
    - the Firecracker microVM tier
190
    - `flow check/run/rootfs/runner`
191
    - a microVM conformance test
192
  - **Hub side.** Pushes start runs, a runner API with repo-scoped read-only job tokens, jobs mirrored as `flow/<job>` checks, and a Flow tab with the SVG job graph and live logs. Hub spec §13.2; migration 0011; tests/flow.rs.
193
  - **Earth.** Firecracker v1.17.0, kernel 6.1.155 and the Alpine 3.24.2 rootfs, pinned in ~claude/flow. `flow-runner.service` is hardened. `hub.toml` gained `[flow]` limited to `andymccall/*` (backed up as hub.toml.before-flow).
194
  - **Result.** oxygenflow's own pipeline passes (runs 1 and 2). Andy's spec is committed, renamed to OxygenFlow, with a README.
195
  - **oxy-core.** `tls_client_config` is public.
196
- 2026-09-28 Flow state stamps instead of CI badges (oxygenhub#1, closed by the change):
197
  - `/flow/stamp/{ref}` is an SVG of the newest run of exactly that state, with receipt hashes in its metadata. For bookmarks and tags it redirects to the stamp of the current state.
198
  - In READMEs, `[![flow](.flow/stamp)](.flow/run)` resolves against the state being viewed.
199
  - Fixed oxygenhub#2 on the way: README images pointed at the HTML file page. There is now a `/raw/{ref}/{path}` route.
200
  - The oxygenflow README shows its stamp (oxygenflow#1).
201
  - From now on every item gets an issue on Earth and its commits say closes/refs #N.
202
  - Gaps the workflow exposed became H17 to H20 (oxygenhub#3 to #6).
203
- 2026-09-28 H10 done (oxygenhub#7, closed by the change):
204
  - The compare page has base and head pickers (bookmarks and tags in a datalist; any change or state can be typed), a swap link, `/compare?base=&head=` as the form target, and `/compare` alone comparing the newest other bookmark against the default.
205
  - **Propose this** shows for writers when both sides are bookmarks and head has changes; it becomes "proposal #N is open" when one exists.
206
  - Tests in browse and proposals. Hub spec §6.3. Deployed.
207
- 2026-09-28 H11 done (oxygenhub#8, closed by the change):
208
  - `/help` is a sectioned page with getting started, identity and key linking, pushing, cloning, proposals, delegating to the hub, pipelines and state stamps, the keyboard, and a GitHub glossary.
209
  - Every command carries this hub's address, remote name and certificate step.
210
  - It is linked from the band, the footer and the `?` sheet.
211
  - Browse test; hub spec §6.3. Deployed.
212
- 2026-09-28 H12 done (oxygenhub#9, closed by the change). Spec §5.1.1 first.
213
  - **Account links.** 32 random bytes, stored as a BLAKE3 hash, single use and expiring. Migration 0012.
214
  - **Verification.** Sent on registration and on every address change (48 h). It only verifies the address it was sent to. Settings shows the status and can resend: one every 2 minutes per address, 10 an hour per account.
215
  - **Reset by email.** By username or verified email (1 h, one every 2 minutes). The answer is identical whether or not anything matched, and mail goes out in the background so timing leaks nothing.
216
  - **Reset by an administrator.** From /admin (24 h, shown once, audited).
217
  - **Using a reset link.** It rotates the session salt (every session ends) and voids other links. TOTP stays on; banned accounts are refused.
218
  - **Mail.** A shared `mail` module with `file://` for development. Notification mail only goes to verified addresses.
219
  - **Tests.** tests/recovery.rs covers the whole flow through a file mailbox.
220
  - **Earth.** Earth has no SMTP, so resets there go through the admin link. Existing addresses count as unverified until verified.
221
- 2026-09-28 H13 done (oxygenhub#10, closed by the change):
222
  - Notifications record their thread, repository and reason (participating or watching) and can be marked done. Migration 0013 backfills threads and repositories from the URLs.
223
  - The inbox shows one row per thread, with updates and new counts.
224
  - Filters: inbox, unread and done views, reason, kind family and repository, with counts.
225
  - Per-thread read, unread and done. Done threads return when a new event arrives.
226
  - Opening a row marks its thread read.
227
  - `notify::add` takes a struct.
228
  - Tests extended in tests/issues.rs. Hub spec §10.
229
  - The API keeps per-item read; per-thread actions in the API are a follow-up for the VS Code inbox.
230
- 2026-09-28 Andy asked for uploaded profile pictures and organisation logos: added as H13b (oxygenhub#11), taken next.
231
- 2026-09-28 H13b done (oxygenhub#11, closed by the change):
232
  - Upload a picture under Settings › Profile › Picture, or an organisation logo under its settings (owners only).
233
  - PNG, JPEG or WebP, recognised by content; at most 5 MiB and 8 000 px a side.
234
  - Every upload is decoded, centre-cropped, scaled to 400×400 and re-encoded (PNG if it has transparency, JPEG otherwise), so uploaded bytes are never served.
235
  - `/avatar/{name}` serves the picture with an ETag, falling back to the identicon; Remove restores the identicon.
236
  - Avatar CSS no longer pixelates photos.
237
  - Unit tests for processing and refusals; end-to-end test in tests/accounts.rs. Hub spec §6.3. Deployed.
238
  - This machine's disk filled up (0 bytes free) during the build. I removed the regenerable `target/debug/incremental` caches of oxygenhub and oxygen (33 GB) and build with CARGO_INCREMENTAL=0 from now on.
239
- 2026-09-28 H14 done (oxygenhub#12, closed by the change):
240
  - Keyboard and screen readers: a focus ring on everything focusable; a skip link to `<main id="main">`; named landmarks (Account, Site, Repository and Settings navigation); `aria-current` on the current tab.
241
  - Contrast: WCAG AA in both themes, enforced by tests/contrast.rs over the CSS variables.
242
    - Light theme: mute, acc, ok, now and the string, function and comment syntax colours adjusted.
243
    - Dark theme: comment colour adjusted.
244
    - Text no longer uses `--dim`.
245
  - Under 760 px: tab bars scroll on one line, the tree drops its message column, wide content scrolls inside its panel, sectioned pages stack, and the header search hides.
246
  - Checked at 390 px on Earth. Hub spec §6.13.
247
- 2026-09-28 H15 done (oxygenhub#13, closed by the change):
248
  - **Organisation webhooks.** A Webhooks section in organisation settings: add, ping, enable or disable, delete, deliveries and redeliver.
249
    - Repository and organisation webhooks now share one module (`web/webhooks.rs`) and one template macro.
250
    - Every action checks that the hook belongs to the scope it is managed from.
251
  - **Storage.** `/admin/storage` lists each repository's size, loose objects and packs. Maintenance runs `oxy gc` (grace period kept, write lock held) on one repository or all, and is audited.
252
  - Tested in tests/accounts.rs. Hub spec §6.3, §12.2. Deployed.
253
  - Earth's repositories can be maintained from /admin/storage now; I have not run it on Andy's data.
254
- 2026-09-28 H16 done (oxygenhub#14, closed by the change). Measured on Earth first (oxygenhub/docs/performance.md).
255
  - The raw bytes of heavy files took 5 ms, but their pages took 196 to 428 ms: syntax highlighting, Markdown rendering, and above all the file's "last change", found by `log_for_path_in` rebuilding the state and reading every change on each view.
256
  - Now cached, in memory-bounded LRUs keyed by content or by (state, path): rendered Markdown, highlighted files, last change and blame. Labels are still computed fresh.
257
  - `?plain=1` takes the lean path.
258
  - Warm views: spec.md 196 → 8 ms, repo/mod.rs 400 → 18 ms, state.rs 317 → 14 ms, blame 428 → 23 ms.
259
  - Unit test for the LRU; browse test checks the second view hits the cache. Hub spec §16.3.
260
  - Follow-up for oxy-core: `log_for_path_in` should reuse a loaded state instead of rebuilding it.
261
- 2026-09-28 O12 done (oxygen#1, closed by the change; the first issue in the oxygen repository):
262
  - oxy-core has `log_of(state)`, `log_for_path_of(state, path, limit)` and `blame_of(state, tree, path)`. They find the path in the tree view alone, with no full render, and the walk stops at `limit`.
263
  - `log_for_path_in` and `blame_in` wrap them and load once instead of twice.
264
  - Test `history_and_blame_of_a_loaded_state_match_the_full_walk`; gate green; release binary rebuilt.
265
  - The hub uses them for last change, blame and tree rows.
266
  - Cold file pages on Earth: 196/400/317/428 ms (before any caching) → 146/256/121/88 ms; warm 8–24 ms (oxygenhub/docs/performance.md).
267
- 2026-09-28 H17 done (oxygenhub#3, closed by the change):
268
  - `POST /issues` takes labels, assignees and a milestone (triage). Unknown names are refused before anything is created.
269
  - New `PATCH /issues/{n}`: title and body for the author or triage; the whole label set, assignees and milestone for triage; each change goes on the timeline.
270
  - `POST /labels` creates or changes a label and `DELETE /labels/{name}` removes it (triage).
271
  - hub-api types `IssueEdit` and `NewLabel`; TS regenerated (the extension's copy follows with its next change). Test `api_issue_triage_and_labels`; hub spec §12.1.
272
  - Dogfooding:
273
    - Each Earth repository now has the labels bug, feature, performance, accessibility, security and gap.
274
    - Every issue so far is labelled.
275
    - The work log's issue helper sets labels from now on.
276
- 2026-09-28 H18 done (oxygenhub#4, closed by the change):
277
  - Any bare `#N` in a pushed change's message, on any bookmark, adds "referenced by change <hash> on <bookmark>" to that issue's or proposal's timeline, with the hash linked.
278
  - `closes #N` still records only "closed by change"; `owner/name#N` from other repositories is left alone.
279
  - `refs::references` has a unit test; the issues end-to-end test covers issue and proposal timelines. Hub spec §8.
280
  - From now on, the `refs #N` lines in work commits show on the Earth issues.
281
- 2026-09-28 H19 done (oxygenhub#5, closed by the change). Spec §8.2 first.
282
  - Projects belong to a person or organisation: `/projects/{owner}` and `/projects/{owner}/{n}` ("projects" is now a reserved name).
283
  - Columns start as Backlog, Doing and Done; they can be added, renamed, moved and removed (their cards go to the first column; the last column stays).
284
  - Cards are issues or proposals of the owner's repositories (by `repo#N`, or `#N` with a repository picked) or notes. Each can be on a project only once. They move, reorder and are removed without JavaScript.
285
  - Visibility: public or private (owner, organisation members and admins). Cards from repositories the viewer cannot read are not shown at all.
286
  - A closed project is read-only; deleting one leaves the issues.
287
  - Issue and proposal pages list their projects; profiles and organisation pages link to them.
288
  - Migration 0015. Test `project_boards` plus reference parsing. Screenshot-checked on a scratch hub.
289
  - The screenshot tool now disables the browser cache (an earlier shot had served a stale style.css).
290
  - Gap found: no projects API → H21 (oxygenhub#15).
291
- 2026-09-28 H20 done (oxygenhub#6, closed by the change): relative links and images in issue, comment and proposal Markdown resolve against the repository's default bookmark; release notes resolve against the release's tag. Test `issue_markdown_follows_the_default_bookmark`.
292
- 2026-09-28 Webhooks, from Andy's report (oxygenhub#16 and #17, closed by the change):
293
  - **Why Discord got nothing.** Every delivery, ping included, was the OxygenHub envelope, which Discord rejects ("Cannot send an empty message").
294
  - **Formats.** Hooks now have a format: `oxygenhub`, `discord` (an embed with the event's summary; mentions disabled so user text cannot ping) or `slack`. It is picked from the URL; migration 0016 switched existing Discord and Slack hooks.
295
  - **Settings.**
296
    - The URL is masked (long path segments and query cut short, since chat webhooks keep a token there; the audit log records the same masked form) and wraps inside the panel.
297
    - The table shows the last delivery result.
298
    - Each hook has an edit form; a blank URL or secret keeps the current one.
299
  - **Also found.** Issues opened, commented on, closed or reopened in the web interface emitted no webhook events at all; now they do.
300
  - **Tests.** Unit tests for the formats and masking; end-to-end test `webhook_formats_editing_and_web_issue_events`. Hub spec §12.2.
301
- 2026-09-28 H21 done (oxygenhub#15, closed by the change):
302
  - `/api/v1/projects/{owner}`: list and create.
303
  - `/api/v1/projects/{owner}/{n}`: the board as the caller may see it, and `PATCH`.
304
  - `/cards`: add a reference or note; columns by id or name.
305
  - `/cards/{id}`: move or remove.
306
  - The pages and the API share `rights_of`, `board_columns` and `add_card`. hub-api types added; TS regenerated. Test `api_projects`. Hub spec §12.1.
307
- 2026-09-28 Repository overview, from Andy's review (oxygenhub#18, closed by the change):
308
  - The heads and conflicts strips sit inside the main column.
309
  - Clone is a button beside search: a popup with a selectable command, copy, and the bare URL.
310
  - Contributors box: authors counted from the state's changes, linked to hub accounts through linked keys; unsigned changes with no author show as "Unattributed changes".
311
  - Languages box: bytes per language (an extension table plus syntect), with Linguist's colours. Prose, lock files and vendored paths are left out; under 1% folds into Other.
312
  - A public, internal or private badge by the repository name.
313
  - Tests in browse.rs and a unit test for detection. Screenshot-checked on Earth.
314
  - Andy's key is not linked to his account yet (Settings › Keys), so his contributor entry is not a link.
315
- 2026-09-28 V6 done (oxygen-vscode#1, closed by the change). oxygen-vscode 0.8.0:
316
  - The History graph's change picker, and a right-click menu on its rows (webview context menus), offer:
317
    - Split for heads with several files: pick files; they become the first change.
318
    - Squash into a dependency, picked when there are several.
319
    - Rebase onto a bookmark, or a change or tag typed in.
320
  - Each asks first, then shows oxy's summary with an Undo button (`oxy undo`).
321
  - Client methods `split`, `squash` and `rebase`, tested against the real oxy with undo. check green (9 tests); packaged and installed into the Rust profile.
322
- 2026-09-28 V7 done (oxygen-vscode#2, closed by the change). oxygen-vscode 0.9.0:
323
  - **Amend mode** (pencil or close icon in the Source Control title bar): Ctrl+Enter amends; the box starts from the last change's message.
324
  - **Previous Commit Messages**: remembered per repository in workspace state, 30 kept.
325
  - **Signing indicator**: the placeholder says signed, sealed at push, or unsigned, read from `oxy identity show` at most every 30 s.
326
  - **Conflict marker warning** on commit, commit-selected and amend: files oxy reports with markers left, plus any changed text file with lines opening or closing a conflict (Oxygen's or Git's). The dialog can open the first one.
327
  - Pure logic in `src/commitbox.ts` with unit tests; `identity()` tested against the real oxy. check green (14 tests); installed into the Rust profile.
328
- 2026-09-28 V8 done (oxygen-vscode#3, closed by the change). oxygen-vscode 0.10.0 and oxygenhub-vscode 0.6.2:
329
  - The line-number gutter (`editor/lineNumber/context`) and the editor context menu offer Open on Remote, Copy Remote Link and Copy Permalink.
330
  - The link covers the clicked line or the selection (`#L10-L20`).
331
  - A permalink uses OxygenHub's state address: the bookmark's heads, sorted and joined with `+`. Checked live: an 11-head address for oxygen/README.md opens with 200 on Earth.
332
  - A note appears when uncommitted edits may shift the lines, or when remote links point past unpushed changes.
333
  - `stateSpec` and `linkedLines` are unit-tested; the hub extension keeps `+` readable (tested) and its API types are brought up to date (issue editing, labels, projects). Both checks green; both installed into the Rust profile.
334
- 2026-09-28 V9 done (oxygen-vscode#4, closed by the change):
335
  - Both READMEs rewritten as walkthroughs by task, each claim checked against the code:
336
    - Oxygen: commit, conflicts, sync, history rewriting, bisect, line links, trust.
337
    - OxygenHub: propose, review, land, issues, releases, notifications.
338
  - Each README has a Settings table.
339
  - Every CHANGELOG entry is dated from the history.
340
  - `test/docs.test.mjs` in both fails if a setting is missing from the README or the newest CHANGELOG entry is not the package version. Both checks green (18 tests each).
341
- 2026-09-28 O10c done (oxygen#2, closed by the change), after splitting it.
342
  - **Measured first.** New `OXY_TIMINGS=1` phase timings, documented in the guide. At 10 000 files and 200 changes, loading the base state was about 790 of 1 230 ms. A checkpoint did not help: decoding it took 680 ms, like replaying the history, since both build the same in-memory graphs.
343
  - **Fixed without a format change:**
344
    - A seeded fast hasher for the graph maps (`fasthash.rs`, tested).
345
    - The predecessor map was written for every line and read nowhere; it is removed.
346
    - An inline edge set replaces a `BTreeSet` per vertex.
347
    - Lines are moved in with one lookup each.
348
  - **Result.** Both builds run alternately on the same load, medians: clean status 0.96 s to 0.52 s, one-file commit 0.99 s to 0.51 s, peak memory 657 MB to 239 MB (docs/benchmarks.md).
349
  - Gates green for oxygen and oxygenhub; release binary rebuilt; hub redeployed.
350
  - The rest is split into:
351
    - O10d: skip reading files that are clean by the manifest; next.
352
    - O10e: per-file checkpoints, a spec §25 format change. **Andy: this one waits for your agreement.**
353
  - Also this iteration: V6 to V8 had been installed into the Rust profile only; now into the default profile too, as the loop asks.
354
- 2026-09-28 O10d done (oxygen#3, closed by the change):
355
  - A text file whose size and mtime match the manifest (outside the 2 s racy window), and whose recorded hash equals the base's rendering of it, takes the base's bytes instead of being read.
356
  - At 10 000 files, a clean status opens none of the files (checked with strace).
357
  - With a warm cache the walk falls only from about 75 to 56 ms, since traversal and stat remain. The gain is larger on a cold cache or a slow disk.
358
  - Test `stat_cache_takes_unchanged_text_from_the_base_and_misses_no_edit`, using unreadable files to prove what is and is not read:
359
    - an older uncommitted edit is always read, never replaced by the base;
360
    - a same-size edit made just now is caught by the racy window;
361
    - after a commit the edits become the base.
362
  - Spec §12.4. Gate green; release binary rebuilt. The hub (bare repositories) is unaffected.
363
  - Next is O10e, which waits for Andy's agreement to change the checkpoint format.
364
- 2026-09-28 O10e done (oxygen#4, closed by the change). Spec §11, Draft 0.8j.
365
  - **Format.** Payload version 2: a file graph encoded in 2 KiB or more is a blob of its own, named from an index; smaller ones stay inline. Blobs are content addressed, so unchanged files are shared between checkpoints.
366
  - **Compatibility.** Readers accept both versions; writers produce version 2. gc keeps the graph blobs, fsck checks them against a full load in the payload's own version, and shallow-clone bundles carry them.
367
  - **Speed.** The first cut was slower than version 1, because it read 10 000 blobs one at a time. So the store gained `get_many`, which reads loose objects in parallel and packed ones in parallel after one index pass, and the graphs are decoded in parallel.
368
  - **Result at 10 000 files, 216 changes** (the same binary reading both versions, alternated, medians): load through a checkpoint 615 ms (v1) to 342 ms (v2). The index is 2.4 MB against a 62 MB v1 payload. A second checkpoint after a one-file edit took 0.63 s against 1.08 s.
369
  - **Tests.** Unit tests for the v2 round trip, determinism, missing blobs and unknown versions, and `get_many` over loose, packed and missing objects. A repository test covers a large file's graph blob through gc, fsck, a shared second checkpoint, the shallow bundle, an old v1 checkpoint still loading, and a lost blob failing loudly.
370
  - Gates green for oxygen and oxygenhub; release binary rebuilt; hub redeployed.
371
  - **Andy:** oxy older than this cannot read checkpoints written from now on. Rebuild every copy, as ~/.local/bin/oxy already is.
372
  - **New priority from Andy:** dogfood OxygenFlow on every push (F1–F5, ahead of O10f and O10g).
373
    - The runner is live on Earth, but only oxygenflow has a pipeline, of light shell jobs in a 256 MiB, one-CPU Alpine microVM.
374
    - No Rust or Node sandbox exists yet.
375
- 2026-09-28 F1a done (oxygenflow#2, closed by the change). Andy said go ahead with the Earth work for F1.
376
  - **Mounts.** Jobs may now declare up to 4 ephemeral scratch mounts:
377
    - `ephemeral://tmpfs` uses guest memory; `ephemeral://disk` is a fresh sparse ext4 disk per job, attached as vdc onward.
378
    - Targets are clean paths strictly under /src or /tmp and may not nest; sizes run from 16 MiB to 64 GiB. Other sources and settings are refused as not in M0.
379
    - Mounts are part of the canonical job (field 6, only when present, so existing pipeline hashes are unchanged) and of the receipt environment.
380
    - `flow-init` creates mount points owned by the job's user.
381
  - **Deployment.** `make deploy-runner` builds `flow` and the static `flow-init`, installs them under /home/claude/flow keeping `.prev` copies, re-pins `flow-init` in flow.toml and restarts flow-runner.
382
  - **Checked on Earth** with `flow run` in a real microVM (no hub involved): the job (uid 1001) owns the mounts, a 2 GiB disk took a 300 MiB write, a 64 MiB tmpfs refused 100 MiB, and scratch was cleaned. The next real push of oxygenflow passed on the new runner.
383
  - Tests: 2 new flow-core tests (parsing, refusals, hash stability) and a runner test for guest device numbering. Gates green for oxygenflow and oxygenhub; hub redeployed (it validates pipelines with flow-core).
384
  - Andy also confirmed that only he and I use oxy, so there is no need for backward compatibility before the release.
385
- 2026-09-28 F1b done (oxygenflow#3 and oxygen#6, closed by the changes).
386
  - **The build image** (`oxygenflow/images/rust`):
387
    - Rust 1.97.1 with rustfmt and clippy, and Node 24.21.0 (SHA-256 checked).
388
    - Every crate of the oxygen, oxygenhub and oxygenflow lock files, vendored, with cargo pointed at them via /.cargo/config.toml and `offline = true`.
389
    - Toolchain binaries are linked into /usr/local/bin, so no rustup is needed at run time.
390
    - `build.sh` builds it; Docker is only a file-system builder on the workstation (Andy agreed). Imported on Earth as `cas://blob/3ba0779a55b1d66fce10adf5a01585624c9aab089f590d228368c38fd3e65a46` (2.2 GiB).
391
    - Rebuild it when a Cargo.lock changes.
392
  - **Running oxygen's gate in it found three real problems:**
393
    - `flow-init` called glibc's `execvpe`, which searches PID 1's own (empty) PATH rather than the job's, so programs outside /bin and /usr/bin were not found. It now resolves them itself (tested).
394
    - Tests serving on 127.0.0.1 need a loopback interface. `network "loopback"` (already in the spec) is now in M0: `flow-init` brings up `lo`, with still no device leaving the guest (tested).
395
    - oxygen's `git_import_reproduces_trees` only passed where git has a user identity, because one merge lacked `-c user.name`. Fixed; it now also asserts the merge started.
396
  - **Result:** oxygen's full gate (fmt, clippy -D warnings, every test) passes in a microVM on Earth in 135 s cold, with 8 GiB, 4 CPUs and a 24 GiB disk mount.
397
  - Runner and hub redeployed; Earth scratch cleaned.
398
- 2026-09-28 Andy agreed the order for his five ideas: F2–F5 first, then conflict resolution (P1, P1b), function history (P2), swimlanes (P3), signed receipts and the trust dashboard (P4, P4b), then web workspaces (P5, P5c).
399
  - Decisions from the discussion:
400
    - Web workspaces start from openvscode-server unmodified, not a VS Code fork.
401
    - Hub-side history rewrites are plans applied and signed by the author's client, only on proposal bookmarks.
402
    - Function history uses atom identity rather than the semantic-deps hook.
403
  - Andy asked the loop to carry straight through all of it.
404
- 2026-09-28 F2 done (oxygen#6, closed by the change).
405
  - **The pipeline** (`.flow/pipeline.kdl`) runs on every push to oxygen:
406
    - `fmt` in a small sandbox (1 GiB, 1 CPU, isolated);
407
    - then `clippy -D warnings`, `test --workspace` and a release `build`, each with `--locked` in the build image (8 GiB, 4 CPU, loopback) with a 24 GiB scratch disk for target/.
408
  - **First run** (#6, passed): fmt 13 s, clippy 46 s, test 1 min 37 s, build 2 min 7 s; 4 min 44 s end to end. The runner runs one job at a time.
409
  - The jobs show as checks on the change. Required checks come with F5.
410
  - The run shows "site token" as its pusher: Andy asked why pushes are not attributed to him. They use the site push token from hub.toml, set up before accounts existed.
411
    - Waiting on Andy for a personal access token (Settings › Tokens, repo:write) to put in every repository's `earth` remote.
412
    - Then, with his OK, remove the site token from hub.toml.
413
- 2026-09-28 Pushes now use a personal access token for Andy's account in every repository's `earth` remote, not the site push token, so the hub records them as andymccall. The site token stays in hub.toml until Andy agrees to remove it.
414
- 2026-09-28 F3 done (oxygenhub#19, oxygenflow#4, oxygen#8, closed by the changes). Andy chose pinned subrepos.
415
  - **Subrepos.** oxygenhub pins oxygen and oxygenflow as subrepos in deps/ and builds against them.
416
    - `make pins` pulls them to main on Earth; the next commit records the new pins.
417
    - A Cargo `paths` override that kept local builds on the sibling checkouts warned that it will become a hard error, so there is none: local, CI and deploy all build the pins.
418
  - **OxygenFlow.**
419
    - Claims list the state's pinned Oxygen subrepos that the pusher can read, and the job token may read those (and nothing else).
420
    - The runner fetches and exports each at its path, inside the scope hash.
421
    - Hub test `claims_carry_pinned_subrepos_and_the_job_token_reads_only_those`.
422
  - **oxy fix.** `submodule add` probed URLs without OXY_TOKEN and OXY_CA, so a hub with its own CA, or a private repository, fell back to git. It now reports the Oxygen error too.
423
  - **Formatting.** `cargo fmt --all` in oxygenhub also formats the path dependencies in deps/, with the hub's style, since oxygen has no rustfmt.toml. The pipeline and the loop's hub gate use `cargo fmt -p oxygenhub -p hub-api`.
424
  - **First run** (#12, pushed as andymccall, passed): fmt 5 s, clippy 1 min 49 s, test 4 min 1 s, release build 8 min 26 s. The runner runs one job at a time → F6.
425
  - Also this iteration: removed fix.py, fix2.py and format_xenon.py from oxygen (Andy's request, oxygen#7). Answered Andy's VS Code question: the extension is installed in both profiles and only shows in an Oxygen repository.
426
- 2026-09-28 F4a done (oxygenflow#5, oxygenhub#19, closed or referenced by the changes).
427
  - **Pinning.** oxygenflow pins oxygen at deps/oxygen, with `make pins` and a README note.
428
  - **oxygenflow's pipeline** adds fmt (this workspace only), clippy -D warnings and tests on the build image, before its summary. First run #14: all 8 jobs passed.
429
  - **The hub's layout had to change.** oxygenflow's flow-core now points at its own deps/oxygen, so the hub keeping a second oxygen pin would give Cargo two different oxy-core packages. The hub now pins only oxygenflow and builds against deps/oxygenflow/deps/oxygen.
430
    - The chain for newer code: push oxygen, then `make pins` in oxygenflow and push it, then `make pins` in oxygenhub.
431
  - **Nested subrepos.** Hub claims follow subrepos of subrepos, parents first, joined paths, up to 3 deep, with the same read check. The hub test covers a pin inside a pin.
432
  - **Result.** oxygenhub run #15 fetched deps/oxygenflow and deps/oxygenflow/deps/oxygen and passed: fmt 2 s, clippy 1 min 37 s, test 3 min 44 s, build 6 min 25 s.
433
  - **Also fixed** for Andy: `oxy identity export` failed in a new repository, because identity lookups ignored the key directory. It now falls back to it, checked against the key, and the help names the file it writes (oxygen#9).
434
- 2026-09-28 F4b done (oxygen-vscode#5, closed by the change; oxygenhub-vscode references it).
435
  - **Image.** The build image (now `85c744e5…`) also carries an npm cache of both extensions' lock files. `npm ci --offline --cache /opt/npm-cache` installs from it read-only; checked locally before building.
436
    - oxygen, oxygenhub and oxygenflow still pin the previous image `3ba0779a…`; they move on their next re-pin.
437
    - Rebuild the image when a package-lock.json or Cargo.lock changes.
438
  - **oxygen-vscode** pins oxygen as deps/oxygen (kept out of the .vsix). Its `check` job builds oxy from the pin on a scratch disk and runs typecheck, bundle and the 18 tests against it; then `package`.
439
  - **oxygenhub-vscode:** check and package. Its hub-client tests skip in the VM, since there is no hub binary there.
440
  - **First runs:** `package` failed in both, because a job starts from a fresh workspace and had no dist/. It now builds first.
441
  - **Green:** oxygen-vscode #21 (check 1 min 8 s, package 6 s), oxygenhub-vscode #22 (check 7 s, package 5 s). oxygenflow #20 passed all 8 jobs.
442
  - All five repositories are now linted, tested and built by OxygenFlow on every push.
443
- 2026-09-28 F5 done (oxygenhub#20, referenced by the changes).
444
  - **Required checks over the API.** They could only be set from the settings page, so the hub gained `GET` and `PUT /api/v1/repos/{o}/{n}/checks/required` (admins, the whole list, audited). Tested; TS regenerated; hub deployed.
445
  - **Required checks set on every repository:**
446
    - oxygen and oxygenhub: flow/fmt, clippy, test, build;
447
    - oxygenflow: flow/fmt, clippy, test, summary;
448
    - both extensions: flow/check, package.
449
    - Proposals cannot land on main without them.
450
  - **Waiting for green.**
451
    - `oxy push --json` now reports the state each bookmark was pushed to (tested).
452
    - New `flow wait <repo URL> <state>` follows that state's checks on the hub against the required ones: exit 0 passed, 1 failed (a failed check, or a skipped required one), 2 at the time limit. Unit-tested.
453
    - First real use: it followed oxygenflow's own run for the change that adds it, 8 checks from queued to passed, and exited 0.
454
  - **From now on the loop pushes and waits** with `flow wait` after the local gate, and a red run is fixed before an item counts as done. Recorded in my working notes; Andy's loop prompt file is unchanged.
455
  - F1 to F5 (dogfooding) are done. F6 (faster runs) is next in the F group; then P1 and the rest.
456
- 2026-09-28 O10f done (oxygen#10, closed by the change; OxygenFlow run green, followed with `flow wait`).
457
  - **History-only states.** `Repo::load_state_history` keeps changes and tree but builds no file graph.
458
    - From a version 2 checkpoint, graphs stay as pending blob refs or inline values.
459
    - By replay, each change's edits wait per file.
460
    - `load_graphs` builds the graphs asked for, in one blob batch, identical to a full load (tested against full payloads, by replay and from a checkpoint with later changes).
461
    - An unloaded graph panics with a clear message rather than reading as empty, and `materialize` refuses a history-only state (both tested).
462
  - **Used by** `log`, `log_state`, `log_for_path_in` and `blame_in` (one graph).
463
  - **At 10 000 files:** the log's state load 270 → 37 ms, `oxy log` 0.89 → about 0.7–0.8 s. The rest is the working-copy snapshot the log takes first (O10g). CLI `blame` works from the snapshot, so it is unchanged (0.6 s).
464
  - **Andy asked:**
465
    - Why his pushes show "site token": his repositories metalgear, rstar-api and snake-oxy use hub.toml's site push token (c085…). Told him to make his own personal token and set it with `oxy config remote.earth.token`. Removing the site token waits for his OK.
466
    - The oxy equivalent of git-flow: bookmarks and `oxy merge` (a union, no merge change), proposals, tags. Answered.
467
- 2026-09-28 F6a done (oxygenflow#6, oxygenhub#21, closed by the changes).
468
  - **The runner** runs up to `max_jobs` jobs at once within a memory and vCPU budget (flow.toml).
469
    - It reports its free room when it claims, and the hub hands out only a job whose sandbox fits; a lone job may use the whole budget.
470
    - Preparing workspaces stays one at a time: shared repository caches, and the first clone's token in the environment.
471
    - Unit-tested budget; the hub was deployed before the runner.
472
  - **Measured honestly: parallel Rust jobs are slower on Earth's 4 cores.** Oxygen's run took 345 s with clippy beside test and build (clippy 46 s → 1 min 53 s, build 2 min 7 s → 4 min 49 s), against 284 s one at a time.
473
    - Earth's budget is therefore max_jobs 3, 24 GiB and **5 vCPUs**: one 4-vCPU Rust job at a time, with 1-vCPU and short jobs beside it.
474
    - The real speed-up is F6b (prebuilt dependencies, release without debug info).
475
  - **Found on the way:** OxygenFlow log appends failed with HTTP 500 "database is locked", losing log lines.
476
    - Cause: the hub's write transactions were deferred and read before writing, which SQLite fails at once whatever the busy timeout.
477
    - All 9 now begin IMMEDIATE (`db::begin_write`). A test of 16 concurrent appends fails with the old code and passes now.
478
    - Deployed; the next oxygenhub run passed with no new lock errors in the runner's log.
479
  - **Andy asked** whether an `oh` command, like GitHub's `gh`, should be its own repository. Recommended a crate in the oxygenhub workspace, since it shares hub-api and can test against a real hub. Asked where in the backlog he wants it; no answer yet.
480
- 2026-09-28 O10g, first part, done (oxygen#11, closed by the change; OxygenFlow run green, followed with `flow wait`). Spec §12.4, Draft 0.8k.
481
  - **Graph keys.** `State::graph_key` hashes a file's node, its checkpoint graph (blob, inline value or none) and the set of later changes that edited it. Equal keys mean equal graphs, and so equal renderings.
482
    - A test caught my first version, which left the node out: two files edited only by the same change got the same key.
483
  - **What the manifest records.** Per text file found equal to the base's unconflicted rendering, the key it matched.
484
    - When a file's stat and key both still match, the snapshot neither reads nor renders it.
485
    - `.oxyattributes` files are always read. When any of them is added, removed, edited or changed in the base, nothing is skipped.
486
  - **`Repo::snapshot_status`** gives a partial snapshot: a history-only base with only the other graphs built, and a `Snapshot.unrendered` list.
487
    - Used by status, log, show, tag, bookmark create/set, the default head, and `pull --json`.
488
    - `snapshot` (commit, diff, amend…) still builds the whole base. Its graphs now load in parallel.
489
  - **Tests:** 3 new; 157 core tests pass, clippy is clean.
490
  - **At 10 000 files:** a clean status takes 0.22 s instead of 0.55 s, and 51 MB instead of 259 MB. Log takes 0.38 s instead of about 0.75 s. Diff and commit are unchanged at about 0.57 s.
491
  - **Split:** O10h (a lazy base for commit and diff, and cached materialized states) is added to the backlog.
492
- 2026-09-28 **Metalgear feedback.** Andy's other project reports what it finds in `~/oxygen_feedback.md`; I watch it and act on it. Its items are M1 to M10, placed ahead of P.
493
  - **M1 done** (oxygen#12). Pushing a bookmark with `/` in its name failed with "No such file or directory".
494
    - Cause: reference attestations and checkpoint pointers are files named after the bookmark, and nothing created the subdirectory.
495
    - They are now one flat file each, with `/` escaped (`%2F`), so `feature` and `feature/x` coexist.
496
    - A test pushes and checkpoints both.
497
    - Pinned through oxygenflow into oxygenhub and deployed. All three OxygenFlow runs are green.
498
  - **M2 done** (oxygenhub#22, Andy's request). Settings → OxygenFlow has on, off or site default (hub.toml's `[flow] repos`), and needs no restart.
499
    - Site administrators change it; repository admins only see it.
500
    - Audited as `repo.flow`; migration 0018.
501
    - Two tests; hub gate green; deployed; run green.
502
  - **Andy asked urgently** to enable Flow for doomsdayone/metalgear.
503
    - My edit of hub.toml on Earth was refused by the permission check, so I did not make it.
504
    - The line `"doomsdayone/metalgear"` is now in `[flow] repos`, added outside this loop, and run #37 there passed.
505
    - From now on the new setting does this without editing hub.toml.
506
- 2026-09-28 **M11 done** (oxygenhub#23; Andy asked for it urgently). Flow runs are numbered per repository.
507
  - Run list, run page (`/flow/{number}`), live view, stamp and check links all use the number.
508
  - Migration 0019 numbered the existing runs per repository in creation order and rewrote the links of existing flow checks.
509
  - I tried it first on a read-only copy of Earth's runs and checks: 160 of 185 links changed, and every link names a run of its own repository.
510
  - Live: metalgear's run is #1, oxygen's #1 to #19. Hub run green.
511
- 2026-09-29 **M12 done** (oxygen#13, oxygenhub#24; Andy asked, and feedback 14a).
512
  - **`oxy push REMOTE --delete NAME`** sends an update to the zero state.
513
    - Checked like any update: the bookmark must exist at `old_state`, and a policy needs an authorized key.
514
    - The deletion's attestation stays the bookmark's latest, so an old one cannot be replayed to bring it back.
515
    - So a bookmark new to a remote starts its serial at the clock in milliseconds.
516
    - The local bookmark is kept.
517
  - **The default bookmark is never deleted.**
518
    - A non-bare repository protects its checked-out bookmark.
519
    - For a bare one, the host says which is the default through `Backend::default_bookmark`: `oxy serve` uses the configured bookmark, the hub its setting.
520
  - **Hub:** a DELETE button (with a confirmation) for writers on every bookmark but the default. It goes through the same checks, is logged on the pushes page and sent to webhooks as a push.
521
  - **Tests:** core, HTTP and hub, one each. Spec §16.4 (Draft 0.8l) and hub spec updated. Deployed; all three OxygenFlow runs green.
522
  - metalgear's `flow-first-run` can now be deleted; I left that to its project.
523
  - **Feedback file:** new items 3a and 7a became M13 (`bookmark switch --carry`) and M14 (`oxy show` summary lists every kind).
524
- 2026-09-29 **M14 done** (oxygen#14). `oxy show`'s summary names every file the change touches: an `M` line for each edited file without a tree operation, and renames with their full new path. Tests updated; run green.
525
- 2026-09-29 **M13 done** (oxygen#15).
526
  - `oxy bookmark switch NAME --carry` takes uncommitted work along (edits, additions, removals, renames).
527
    - It needs every touched path to be the same in NAME's state as in the working copy's base, so the same edits apply there.
528
    - Otherwise it refuses, changes nothing, and names the paths.
529
  - `bookmark create feat && bookmark switch feat --carry` moves work onto a new bookmark.
530
  - The switch help and spec §10.1 say what happens to uncommitted work, which also answers part of feedback 3.
531
  - One test; run green.
532
- 2026-09-29 **M3 done** (oxygenhub#25; feedback 9).
533
  - **Web:** writers get RUN PIPELINE on the Flow tab (a bookmark's current state) and RE-RUN on a finished run's page.
534
  - **API:** `POST /api/v1/repos/{o}/{n}/flow/runs` and `…/runs/{n}/rerun`, with `repo:write`. They answer 201 with the number and url, or 409 with the reason.
535
  - A new run's checks replace the old ones for that state.
536
  - Runs record what started them (migration 0020), and the run page says "pushed by", "started by" or "re-run of #N by".
537
  - Pushes and both new paths share `flow::start_run`.
538
  - One hub test; gate green; deployed; run green.
539
- 2026-09-29 **M4 done** (oxygenflow#7; feedback 15 and 17).
540
  - **/dev links.** flow-init adds `/dev/fd`, `/dev/stdin`, `/dev/stdout` and `/dev/stderr` into `/proc/self/fd`.
541
  - **Job variables.** Claims carry the run's state hash, bookmark and number. The runner adds `FLOW_REPO`, `FLOW_STATE`, `FLOW_HEADS`, `FLOW_BOOKMARK`, `FLOW_RUN` and `FLOW_JOB` to the job's environment, so they are in its receipt too.
542
    - `flow run` sets `FLOW_JOB` and `FLOW_RUN=local`.
543
    - A pipeline's own value wins; unknown values are left out.
544
  - **Tests:** unit tests for both, and the hub test checks the claim's new fields. Hub and runner deployed (runner with `.prev` copies).
545
  - **Proved in a real microVM on Earth:** `< <(echo …)` works, the links are there, and the variables arrive.
546
- 2026-09-29 **"database is locked" again** (oxygenflow#8).
547
  - At 00:40 and 01:01, during oxygenhub runs, log appends and a claim got HTTP 500 after the 5 s busy timeout, and the runner dropped those log lines.
548
  - It was not the hub restarting (up since 00:51) nor the WAL (4 MB). Most likely a commit stalled while the release build saturated Earth's disk. Not proven.
549
  - **Runner:** tries 5xx answers again after 1, 2 and 4 s; a 4xx is final. Tested against a fake hub.
550
  - **Hub:** busy timeout 30 s.
551
  - No lock errors in the runs since, including a full oxygenhub run.
552
- 2026-09-29 **M5 done** (oxygenflow#9; feedback 16).
553
  - **Sandbox env.** A sandbox takes an `env` block that every job in it starts from; a job's own `env` overrides it.
554
    - It is canonical field 6, only when set, so existing pipelines keep their hashes.
555
  - **Image environment.** `flow rootfs import --env NAME=value` writes the image's own environment to `/etc/flow/env`: what a Dockerfile's `ENV` sets and `docker export` loses. It is covered by the image's hash.
556
  - **Layers in flow-init:** `PATH` and `HOME`, then the image's environment, then the job's. The program is found in the resulting `PATH`.
557
  - **Tests** for parsing, precedence, hashing, layering and `--env` checks.
558
  - Hub pinned and deployed, since it parses pipelines. Runner deployed.
559
  - **Proved in a microVM on Earth:** a program found only through the sandbox's `PATH` ran, with the job's value winning. The image layer is unit-tested; nothing was re-imported on Earth.
560
- 2026-09-29 **M6 done** (oxygenhub#26; feedback 11 and 13; run numbering was M11).
561
  - `GET /api/v1/repos/{o}/{n}/flow/runs`, `/flow/runs/{n}` (with its jobs) and `/flow/runs/{n}/jobs/{job}/log?after=seq`, typed in hub-api.
562
  - `/checks/{state}` also takes heads joined with `+`, a single head, or a bookmark.
563
  - Unknown `/api/v1` routes answer with a JSON problem.
564
  - **Found on the way:** two flow tests raced the checks a run reports just after it is recorded, and one failed in a full run. They now wait for the checks.
565
  - Deployed and tried live: oxygen's runs #28 and #27 listed, and the JSON 404. Hub run green.
566
- 2026-09-29 **M7 done** (oxygen#16; feedback 2).
567
  - A refused update's reason names the reference and the step: reading or storing its attestation. One helper now checks and stores attestations for updates and deletions.
568
  - The protocol server prefixes failures with the operation and repository (`refs of lib: …`) and logs them, along with every refused reference. The hub's journal gets the same lines.
569
  - The server's own storage failing answers 500, not 400.
570
  - HTTP test with an attestation store that cannot be written. Spec §16.4.
571
  - Pinned through oxygenflow into the hub and deployed. All three runs green.
572
- 2026-09-29 **M8 done** (oxygen#17; feedback 3).
573
  - **Not reproduced exactly:** a switch then merge, and an emptied directory, both behaved. So I fixed the two gaps the report points at.
574
  - **Merge:** a working copy without changes now follows it, whichever state it sat on. It previously also had to sit exactly on the bookmark's old heads, the likely cause of the lagging working copy.
575
  - **Checkout refusals** name each changed path and how ("removed b.txt" means in the base but not on disk), and offer `--carry`. It shares `Repo::checkout_carry` with `bookmark switch --carry`.
576
  - One test; spec §10.1. Run green.
577
- 2026-09-29 **M9a done** (oxygen#18; feedback 4 and 6). M9 was split into M9a and M9b.
578
  - **`oxy describe` without `-m`** names the working copy as `git describe` does: `v0.1.0`, `v0.1.0-2-8c5ea12a`, or `develop-8c5ea12a`, with `-dirty` for uncommitted work and `--json` for the parts. With `-m` it still changes a head's message, so the VS Code extension is unaffected.
579
  - Tags with one head name their change (`oxy show v0.1.0`), and `tag list` shows heads rather than the state hash.
580
  - `oxy commit -F FILE|-`.
581
  - CLI test; spec §10.1 and §12.3. Run green.
582
- 2026-09-29 **M9b done** (oxygen#19; feedback 5, 7 and 8).
583
  - `submodule status` lists a subrepo registered but not committed yet as added, with the pin the next commit records (`committed: false` in `--json`, which already existed).
584
  - Doctor is OK with a user identity a new repository has not stored yet while its secret key is there: "stored with your first signed change".
585
  - One test; spec §7.8. Run green.
586
- 2026-09-29 **M10 done** (oxygenhub#27; feedback 14).
587
  - A change signed by a key no account has linked is tagged "key not linked" on its page, with a link to Settings → Keys for signed-in viewers.
588
  - The API's change author carries a `note` saying so.
589
  - One hub test; spec updated; deployed; run green.
590
  - **Every item from the metalgear feedback is now done** (M1 to M14; item 18 needed nothing). I keep watching the file.
591
- 2026-09-29 **M15 done** (oxygenhub#28; feedback 17a).
592
  - A waiting run shows its place in line on the Flow tab. Its page adds the jobs running now and those queued ahead, and the API gives `queue_position`.
593
  - Other repositories' jobs are named only when public; otherwise they are counted.
594
  - A unit test for the order, privacy and ordinals, and an end-to-end test.
595
  - Deployed; live, the hub's run #17 reported `queue_position` 4. Run green.
596
  - The runner's parallelism stays within Earth's 5-vCPU budget, deliberate after F6a.
597
- 2026-09-29 **F6b1 done** (oxygenhub#29). F6b was split into F6b1 and F6b2.
598
  - The rust sandbox's `env` (M5) turns off incremental builds and debug info for every job in all three pipelines.
599
  - The release profile keeps line tables for the binary Earth runs, which is built outside CI.
600
  - **Measured on Earth:**
601
602
    | Job | Before | After |
603
    |---|---:|---:|
604
    | oxygenhub build | 390–495 s | 347 s |
605
    | oxygenhub test | about 235 s | 182 s |
606
    | oxygenhub clippy | 99 s | 97 s |
607
    | oxygen test | 98 s | 84 s |
608
    | oxygenflow test | 55–66 s | 45 s |
609
610
    The best oxygenhub run was 722 s from push to done.
611
  - **Found on the way:** the runner's retry test (M4) failed once in a loaded full run. Its fake hub answered after one read and closed while the client still sent. It now reads whole requests; three full runs passed.
612
  - **Next is F6b2:** dependencies prebuilt in the build image.
613
- 2026-09-29 **O10h done** (oxygen#20).
614
  - Commit, working-copy diff, hunks and discard use the partial snapshot. Showing a committed change builds only the graphs of the files it edits (`Repo::change_base`).
615
  - Discarding hunks builds the rest before rendering its target.
616
  - **At 10 000 files:** diff 0.57 → 0.22 s and commit 0.58 → 0.23 s, each 244 → 52 MB; show 0.53 s.
617
  - Caching materialized states turned out unnecessary.
618
  - One test: works with an unreadable unchanged file, and shows a change exactly as a full base renders it. Run green.
619
- 2026-09-29 **F6b2 done** (oxygenhub#29 refs; oxygenflow image recipe).
620
  - **Recipe.** `images/rust/recipe.py` writes a workspace's dependency recipe: manifests, the workspace roots path dependencies inherit from, the lock file, real build scripts, and stub targets. Checked to resolve offline with the lock unchanged.
621
  - **Image.** It builds oxygenhub's recipe at `/src` with the pipeline's environment (release, tests, clippy) and keeps `/opt/warm/oxygenhub` (2.1 GB, image 5.2 GB).
622
    - It was exported straight to Earth over ssh, since the local scratch quota refused the tarball.
623
    - Imported as `cas://blob/47b614a6…`.
624
  - **Jobs.** `.flow/warm-target.sh` copies the warm target onto the job's empty disk (8–12 s), then runs cargo. A missing cache or a failed copy falls back to a full build.
625
  - **First run found a bug:** the fresh ext4 disk holds `lost+found`, so the "empty" check skipped the copy. Fixed.
626
  - **Measured on Earth:**
627
628
    | oxygenhub | F6b1 | F6b2 |
629
    |---|---:|---:|
630
    | push to green | 722 s | 320 s |
631
    | build | 347 s | 147 s |
632
    | test | 182 s | 114 s |
633
    | clippy | 97 s | 52 s |
634
635
    Only the five workspace crates compile, and clippy compiles none.
636
  - **Andy:** rebuild the image when a Cargo.lock moves (`images/rust/build.sh`). Until then only the changed crates compile.
637
  - I ran `git --version` once by mistake while checking a pipeline. It touched no repository.
638
- 2026-09-29 **F6b3 done.**
639
  - The build image prebuilds all three workspaces, imported on Earth as `cas://blob/169de040…`: 14 min to build, 5.7 GB.
640
  - All three pipelines pin it and start their cargo jobs from `/opt/warm/NAME`.
641
  - **Measured on Earth:**
642
643
    | Job | Before | After |
644
    |---|---:|---:|
645
    | oxygen build | 128 s | 46 s |
646
    | oxygen test | 84 s | 42 s |
647
    | oxygen clippy | 45 s | 13 s |
648
    | oxygen, push to done | about 285 s | 139 s |
649
    | oxygenflow test | 45 s | 15 s |
650
    | oxygenflow clippy | 34–54 s | 9 s |
651
    | oxygenflow, push to done | | 37 s |
652
653
    oxygenhub's jobs are as in F6b2 (build 139 s, test 109 s, clippy 48 s).
654
  - **The F group is done.**
655
  - **Andy:** the workstation had 19 GB free after the image build; Docker's build cache grew. I removed only my own tarball, not the months-old images there.
656
- 2026-09-29 **P1a done** (oxygenhub#30; Andy's item 1, first part). P1 was split into P1a, P1c and P1b.
657
  - `/{owner}/{name}/resolve/{bookmark}/{path}` parses a conflicted text file's markers into plain text and regions, with each side's change.
658
  - Per region, keep one side, every side, none, or your own text (a deletion side keeps the deletion); regions are linkable (`#region-N`).
659
  - It commits through the delegate as a web edit, a `resolution` change depending on every contributor.
660
  - It refuses if the bookmark moved since the page loaded; malformed markers go to the plain editor, which stays offered.
661
  - The change page tags resolutions. The web edit's follow-ups are shared (`after_web_commit`).
662
  - Parser and assembly unit tests; the edits test resolves a real conflict through the page.
663
  - Deployed; hub run green.
664
  - **Next:** P1c (tree conflicts, VS Code hand-off), then P1b (live collaboration).
665
- 2026-09-29 **P1c done** (oxygenhub#31).
666
  - **Name conflicts** (several files want one name, shown as `NAME.oxy-conflict-N`) resolve on the resolver by keeping one candidate.
667
    - Its content goes to the name and every candidate is removed, which the snapshot records as a move of the kept file: identity kept, and the test checks the change's file kind is `renamed`.
668
    - It commits as a resolution, refused if the bookmark moved.
669
  - The conflicts page links name conflicts and gives their full path (it gave the bare name).
670
  - Directory and link name conflicts stay with clients.
671
  - Deployed; hub run green.
672
  - **Added:** P1d (resolve in VS Code) and P1e (binary replace conflicts need a core change first: writing a candidate's bytes counts as no change today).
673
- 2026-09-29 **P1d done** (oxygenhub#32; oxygen-vscode 0.11.0).
674
  - The resolver pages link to `vscode://oxygen.oxygen-vscode/resolve?url=…&bookmark=…&path=…` (encoded).
675
  - The extension finds the open checkout with that remote, or offers to clone it, asks before switching bookmark, pulls, and opens the file with its conflict lenses. Bad URLs, bookmarks and paths are refused.
676
  - Parsing and matching are in `src/handoff.ts`, with tests; `npm run check` passes (`ℹ fail 0`).
677
  - Installed in both profiles; hub deployed; both runs green.
678
  - **Fixed on the way:** P1c's name-conflict test found the kept candidate by the first occurrence of a word anywhere on the page, which failed now and then. It now reads the candidates' previews; six passes in a row.
679
- 2026-09-29 **P1e done** (oxygen#21, hub refs; Draft 0.8m).
680
  - **Core.** A change that writes an opaque file with a replace conflict settles it when the caller names the path.
681
    - One `Replace` goes from each live blob to the chosen one, depending on each change that made a candidate live, so all are superseded (§9.3).
682
    - Hosted edits name the paths the user writes. A plain snapshot never settles one, since a checkout writes a candidate at the path.
683
  - **Hub.** The resolver offers a binary file's versions, each labelled with its change. Keeping one commits the resolution, and the conflicts page links them.
684
  - **Tests.** A core test (conflict via merge, snapshot leaves it, keeping a candidate or new bytes settles it) and a hub end-to-end test (five passes in a row).
685
  - Pinned and deployed; all three runs green.
686
  - **P1 is complete apart from P1b** (live collaboration).
687
- 2026-09-29 **P1b done** (oxygenhub#33).
688
  - Everyone on the same resolver page shares an in-memory room per repository, bookmark and path, over server-sent events at `/…/resolve-live/…`.
689
    - Presence: who else is resolving.
690
    - Choices are shared as they are made, the last one for a region winning; text is shared a moment after typing stops. A newcomer gets them so far.
691
    - When someone commits, the others see it land, with a link, and their form is disabled.
692
  - Writers only, choices checked, CSRF on shared choices; a room ends with its last viewer.
693
  - **Tests:** a unit test for rooms, and the edits test follows a real event stream (snapshot, another tab's choice, a refused bad choice, the landing); three passes in a row. The browser script is checked only for syntax.
694
  - Deployed; hub run green.
695
  - **P1 (conflict resolution on OxygenHub) is complete.**
696
- 2026-09-29 **P2a done** (oxygen#22; Andy's item 5, first part; Draft 0.8n). P2 was split into P2a and P2b.
697
  - `FileGraph::between` gives the atoms on any path from a range's first line to its last, live or deleted.
698
  - `touching` gives the changes that inserted or deleted them, and `Repo::line_history` lists those newest first.
699
  - `oxy log -L START,END:PATH` shows it, lines as in the working copy's base, `--json` included.
700
  - Core and CLI tests: edits outside the range are excluded, and edits and deletions inside are included. Spec §27.
701
  - Run green.
702
  - **Next:** P2b (hub, tree-sitter).
703
- 2026-09-29 **P2b done** (oxygenhub#34; Andy's item 5). **P2 is complete.**
704
  - tree-sitter (Rust, TypeScript, Python, shell) finds functions and methods, qualified by impl, trait or class (`Repo::log`). The file page's sidebar lists them.
705
  - Each links to `/{owner}/{name}/function/{ref}/{path}?start=…&end=…&name=…`: exactly the changes that wrote or deleted its lines (oxy-core's line history), newest first, with a dependency graph.
706
  - **Tests:** a unit test per language, and an end-to-end test (a method's two changes, not the import edit above it).
707
  - The new crates meant rebuilding the build image so CI vendors them. It is imported on Earth as `cas://blob/9d76e1ae…`, and all three pipelines are re-pinned.
708
  - Deployed; all three runs green on the new image.
709
  - **Andy:**
710
    - The workstation disk is 99% full (16 GB free). I removed my own local build image, which is kept on Earth and rebuilt by `build.sh`. I did not touch the older images or Docker's build cache (176 GB, 10 GB of it reclaimable).
711
    - The permission check stopped answering for a while mid-iteration; the loop resumed from where it stopped.
712
  - The cross-file semantic graph stays future work.
713
- 2026-09-29 **P3a done** (oxygenhub#35; Andy's item 3, first part). P3 was split into P3a and P3b.
714
  - `/{owner}/{name}/lanes/{ref}` draws up to 60 of a bookmark's newest changes as swimlanes: lanes of the shared layout as rows, time left to right, cards linking to changes, dependency edges, no merge nodes.
715
  - The history page links to it.
716
  - **Tests:** a layout unit test and an end-to-end test (two independent files: 3 cards, 2 lanes, 1 edge).
717
  - Deployed; live, oxygen's board shows the newest 60 of 242 changes in 10 lanes. Hub run green.
718
  - **Andy:** your exact wording for item 3 was lost from my notes, so this follows the backlog description. Say if you meant something else.
719
- 2026-09-29 **P3b done** (oxygenhub#36, oxygen-vscode#6; Andy's item 3, second part).
720
  - **Hub:** on an open or draft proposal's board (author, or Write role), dropping a change on another asks `POST …/lanes/{ref}/plan`. It offers a squash when the change needs nothing the target lacks (the same rule as `oxy squash`), and a rebase unless the change already depends on the target. It refuses when the target depends on the change, or when either change is already on the proposal's target. Each option gives the oxy commands and a VS Code link. The hub never rewrites the changes itself, because they must be signed by their author. Hub spec §6.7 describes it.
721
  - **oxygen-vscode 0.12.0:** the `/plan` link finds the checkout, confirms, switches to the bookmark and pulls it, squashes or rebases, then offers a force push. Installed in both profiles.
722
  - **Tests:** one end-to-end hub test (squash, both options, refused, already on main) and one test of the extension's link parsing. Deployed. Hub and extension runs are green.
723
- 2026-09-29 **P4a done** (oxygen#23; Draft 0.8o). P4 was split into P4a, P4c and P4d.
724
  - OxygenFlow's receipt is now the Oxygen `receipt` object (§14.6), with the same encoding and hash as flow-core's.
725
  - An attestation of kind `receipt` binds a receipt to its state and run, and the runner signs it.
726
  - A signed receipt is `[receipt, attest, sigs, identity]` and verifies with nothing else.
727
  - The repository indexes receipts by state. gc keeps them, their runners and their outputs while the state's changes live, and pruning a state drops its index.
728
  - The CLI has `oxy receipt list|show|export|verify|import`.
729
  - **Tests:** core round trip and tamper tests, a repo test (keep, refuse, gc, fsck) and a CLI end-to-end test. The oxygen run is green.
730
- 2026-09-29 **P4c done** (oxygenflow#10, oxygenhub#37).
731
  - **Runner:** the runner makes a composite key on first start (`runner.key` 0600, `runner.identity`). It signs each receipt for the state of the heads it fetched and for the run URL, and sends the result with `finish` as `signed_receipt`. Earth's runner key is 9006c60d4ecb.
732
  - **Hub checks:** a receipt must verify, be the job's receipt, and be for the run's state and repository. It must also use the key the token first used; `flow_runner_keys` binds a token to its key on first use. If a check fails, the result still stands and the run page says why the receipt was not kept.
733
  - **Storage and pages:** kept receipts go into the repository through `add_receipt`, under the repository's write lock. Run pages link them with the signer, change pages list their state's receipts, and `/{o}/{n}/receipts/{attest}` downloads one.
734
  - **Tests:** a hub end-to-end test (kept, wrong key, wrong state, download, pages) and a runner key test. flow-core's receipt and the Oxygen object hash the same.
735
  - **Deployed:** hub and runner. Live, hub run 30's four jobs all have signed receipts, and a downloaded one passes `oxy receipt verify`. Flow and hub runs are green.
736
- 2026-09-29 **P4d1 done** (oxygenflow#11, oxygenhub#38, oxygen#24). P4d was split into P4d1 and P4d2.
737
  - **Pipeline:** `output { artifact "name" { path "/src/…" } }` allows up to 16 artifacts, sorted and part of the job hash only when present. Paths on tmpfs mounts are refused.
738
  - **Runner:** after a passing job, the runner reads each artifact from the job's disk with `debugfs` (no root; at most 512 MiB). It uploads it, checks that the hub's blob matches `Repo::blob_ref`, and names the blobs in the receipt's `outputs`. A missing artifact fails the job, which then gets no receipt.
739
  - **Hub:** uploads go through `POST /api/v1/flow/jobs/{id}/artifacts/{name}` and must be declared. The hub keeps each one as a repository blob and records it in `flow_artifacts` (path, size, BLAKE3). A receipt must name exactly the uploaded blobs. Run pages list artifacts and link `/flow/{n}/artifacts/{job}/{name}`.
740
  - **Dogfood:** oxygen's build job keeps `target/release/oxy`. Live, run 67's 16 MB binary downloads and runs, and the build job's signed receipt names it as its output.
741
  - **Tests:** parsing, debugfs read-back from an ext4 image, and a hub end-to-end test. All runs are green.
742
- 2026-09-29 **P4d2 done** (oxygenhub#39). P4 is complete (P4a, P4c, P4d1, P4d2).
743
  - **Built by:** a release asset links to the passed job whose artifact has the same BLAKE3 (`flow_artifacts.content`, indexed). A job with a kept signed receipt comes first, then the newest run. The release page shows the run, job, signed receipt and signer, and the API gives `built_by` on each asset (hub-api `AssetBuild`).
744
  - **Attach:** on the run page, writers can attach an artifact to a release. It becomes an asset named after the artifact's path.
745
  - **Tests:** the hub artifacts end-to-end test now also signs in, attaches through the form, and checks the release page and the API. The hub run is green.
746
  - **Not tried live:** oxygen has no releases on Earth yet, and I did not create one on Andy's repositories.
747
- 2026-09-29 **Andy's answers:**
748
  - **`oh`:** it goes in a crate in oxygenhub (H10 added).
749
  - **Deadline:** extended by a week to 2026-10-07 23:59; the loop prompt was updated.
750
  - **Swimlanes:** confirmed that P3 as built is what he meant.
751
  - **Site push token:** remove it from hub.toml. This was not done. The permission check refused my reading even masked token prefixes to confirm our checkouts and runners don't push with it, so Andy is removing it himself.
752
- 2026-09-29 **Site push token removed** at Andy's request, after he confirmed no checkout uses it. hub.toml on Earth was backed up to `hub.toml.bak-20260929`. `push_tokens` is now empty and the hub restarted with /health ok. Pushes from oxygen, oxygenhub, oxygenflow and oxygen-vscode still authenticate with their account tokens.
753
- 2026-09-29 **H11 done** (oxygenhub#40; Andy's request).
754
  - **Andy asked** whether a run without a check-in breaks the history. It does not. Runs only take committed states already in the repository, write no history, and add signed receipts about a state; running it again adds another statement. Only running uncommitted work would be unverifiable, and that stays impossible.
755
  - **Run pipeline:** now a primary button on the Flow tab. Viewers who cannot use it are told why: Flow is off, they are not signed in, or they lack write access.
756
  - **Bookmarks and changes:** each bookmark has Run pipeline beside it. On a change page it runs the state whose only head is that change; such runs carry no bookmark and are labelled "change". `POST /flow/runs` accepts `{"change"}`, and the empty-state text mentions manual runs.
757
  - **Tests and deploy:** the manual-run test covers a change run, an unknown change, the reasons text and the missing buttons. Deployed; the hub run is green.
758
- 2026-09-29 **P4b done** (oxygenhub#42; Andy's item 4).
759
  - **Page:** `/{o}/{n}/releases/trust/{tag}`, linked from each release as "Trust and audit".
760
  - **Contents:** the tag's signer and signature; the authors of the changes since the previous release, with signed, sealed, unsigned, invalid and revoked counts and linked accounts; and each asset's build: run, job, runner key (and whether one of this hub's runner tokens signs with it), tier, rootfs and environment hashes, inputs (state, pipeline, scope), and the signed receipt, verified again.
761
  - **Graph and audit:** an SVG graph (authors → tag → jobs → assets, dashed for a build from another state), and a pass/warn/fail audit of each claim.
762
  - **Export:** `?format=json` and `GET /api/v1/repos/{o}/{n}/releases/trust/{tag}` carry the signed receipts as hex for offline `oxy receipt verify`.
763
  - **Tests and deploy:** a hub end-to-end test with a signed tag, a signed-receipt build and a hand upload. Deployed; the hub run is green.
764
- 2026-09-29 **Andy asked for runner management** (see, assign per site, organisation or repository). Filed as oxygenhub#41 and added as H12a–e. Plex and VisionFive 2 Lites are available for remote runner tests.
765
- 2026-09-29 **P5c in progress.** The importer fix (oxygen#25) is committed. The full 166 412-commit import runs locally on Sarturn under disk and 16 GB RSS watchdogs; Andy said to move heavy jobs to Earth if they cause trouble.
766
- 2026-09-29 **H10 done** (oxygenhub#43). `oh`, OxygenHub from the command line, is a new crate `crates/oh` on hub-api.
767
  - **Target:** in a checkout it takes the hub, repository, token and CA from the oxy remote. Elsewhere it uses `--repo` with a hub saved by `oh auth login` (owner-only `~/.config/oh/config.toml`), or `OH_HUB`/`OH_TOKEN`.
768
  - **Commands:** issues, proposals (not landing yet: that needs an API route), Flow runs (`start --watch`, `watch` and `log`), releases (`upload` and `trust`), and `oh api`. Output has colour, `--plain` and `--json`. Exit 1 when a watched run fails or an audit fails.
769
  - **Tests and install:** a test runs the binary against an in-process hub. The CI fmt job covers the crate, and `docs/oh.md` describes it. Installed as `~/.local/bin/oh`; it works live in the oxygen checkout. The hub run is green.
770
- 2026-09-29 **Proposal landing in the API** (oxygenhub#44). `POST /api/v1/repos/{o}/{n}/proposals/{n}/land` returns 409 with the blockers when a proposal cannot land. The page and the API share `land_as`, and `oh proposal land N` uses it. Deployed; the hub run is green.
771
- 2026-09-29 **Metalgear's dogfooding summary, re-checked** (Andy passed it on). Every point was re-run against today's oxy and is fixed:
772
  - `/` bookmarks push;
773
  - `switch --carry`;
774
  - merge updates the working copy;
775
  - `describe` and `commit -F`;
776
  - `show` lists modified files;
777
  - `submodule status` shows a subrepo before its first commit;
778
  - `doctor` before the first commit.
779
780
  Two gaps were left and are now fixed (oxygen run green):
781
  - **oxygen#26:** durable writes and renames name the file and the step in their errors.
782
  - **oxygen#27:** the guide shows the develop/feature flow the oxy way.
783
- 2026-09-29 **H12a done** (oxygenflow#12, oxygenhub#45). Runners are now in the database.
784
  - **Registration:** one-time tokens on Admin → Runners; `flow runner register` binds the runner's key and writes `runner.toml`.
785
  - **Old configuration:** hub.toml runners are tracked, and their bound keys moved to `flow_runners`.
786
  - **Claims** record the runner and its description.
787
  - **Pages:** the Runners page has drain, disable, resume and remove, and each runner's page shows its history.
788
  - **Tests and deploy:** a lifecycle test (register, reuse refused, claim, history, drain, disable, remove). Deployed. The hub and flow runs were still pending at the time of writing. Earth's runner was not redeployed (it still sends no `info`, which is fine).
789
- 2026-09-29 **Out-of-memory incident on Sarturn at 12:28.** The full vscode import (14.2 GB RSS at about 3 850 s) plus a hub gate build exhausted Andy's workstation. The kernel killed the import, and VS Code and Chrome crashed. The 16 GB watchdog did not allow for concurrent builds and the desktop.
790
  - **Next:** fix the importer's memory growth first (candidates: at most 8 kept state clones, the `new_changes` vector holding every change, and full file graphs for every file). Measure on slices on **Earth only**, inside a hard cgroup memory limit (for example `systemd-run --user --scope -p MemoryMax=…`) that leaves room for the hub and the runner.
791
  - **Saturn:** no heavy jobs there. The partial import was removed; `~/scratch-vscode/vscode.git` (1.4 GB) remains and can be deleted once Earth has its own clone.
792
- 2026-09-29 **P5c progress: importer memory and determinism** (oxygen#28). All measurements were on Earth under `systemd-run --user --scope -p MemoryMax=8G`, in `~/bench`, with its own vscode clone (166 425 commits).
793
  - **Where the memory went:** deep clones of the kept states, not the closure memo (a few thousand entries). With 8 kept states, 1 000 commits took 3.0 GB; with 1, 1.2 GB but slower.
794
  - **The fix:** a state's file graphs are `Arc`-shared and copied on edit, and heads are tracked incrementally. 1 000 commits: 0.8 GB, 91 s. 5 000: 2.25 GB, 410 s.
795
  - **Determinism:** random NodeIds made imports non-deterministic, and not only in their hashes: the same binary gave 370 and 366 heads. New NodeIds now derive from the commit and path (spec §23, Draft 0.8p), and two imports of 5 000 commits give identical heads.
796
  - **Next:**
797
    - `oxy git verify` of the 5 000 slice (running on Earth);
798
    - memory still grows with history (tombstoned atoms in every graph), so try dropping the graphs of long-untouched files and rebuilding them on demand from the store, as history-only states already do;
799
    - then the full import on Earth.
800
- 2026-09-29 **Andy's metalgear VS Code problems, fixed.**
801
  - **Flashing Source Control:** a refresh loop through the subrepo's `.oxy`. Status no longer rewrites an unchanged manifest (oxygen#28 commit), and the extension ignores every `.oxy` directory (oxygen-vscode#7, 0.13.0).
802
  - **"OxygenHub: fetch failed":** plain fetch against Earth's self-signed certificate. The OxygenHub extension now uses the remote's CA file, or the `oxygenhub.caFiles` setting (oxygenhub-vscode#1, 0.7.0), and `oxy remote list --json` reports the CA.
803
  - Both extensions are installed in both profiles.
804
- 2026-09-29 **V1 done** (oxygen#31, oxygenflow#13, oxygenhub#55; Andy asked for semantic versions visible everywhere, each component on its own version, and a signed tag plus hub release per version).
805
  - **Deployed:** the hub reports `0.1.0 (main-0282f310)`.
806
  - **Also for Andy:** the repository bar no longer wraps long names needlessly, and the page keeps room for the scroll bar.
807
  - **2FA sign-in fixed** (oxygenhub#54: the second step sent `password` twice).
808
  - **Next:** V2, then V3.
809
- 2026-09-29 **VS Code made more "Oxygen"** (Andy's pick of four).
810
  - **OxygenHub 0.8.0** (oxygenhub-vscode#2): a Flow view (runs and jobs coloured by status and turning while they run; job logs streamed into an editor tab; Run Pipeline for a bookmark or change; Re-run; notifications when a run finishes) and a live `Flow #N` status bar item. The hub-api TypeScript now has the Flow types.
811
  - **Oxygen 0.14.0** (oxygen-vscode#8): themeable Oxygen colours (a lane palette from the mark, glowing heads, a breathing working copy, signed and sealed marks; file letters on `oxygen.*` colours defaulting to Git's), a Get started walkthrough, and Oxygen: Create Identity.
812
  - **OxygenHub 0.9.0** (oxygenhub-vscode#3): a Get started with OxygenHub walkthrough.
813
  - Installed in both profiles.
814
- 2026-09-29 **Linux stage 1, first run** (Andy ran `~/bench/linux-bench.sh` on Earth).
815
  - **Git:** the first commit of the tip (95 943 files, 1.8 GB) took 33.7 s and 42 MB; clean `status` 0.26–0.31 s warm.
816
  - **oxy:** the first commit made one 1.8 GB change object (every line inline), peaked at 10.9 GB, and was packed despite the 1 GiB object limit, leaving a repository that would not open. The refusal is now fixed (oxygen#33); the design limit is oxygen#34 (L1).
817
  - **Git's `fsck` errors:** Git's own automatic gc was running concurrently; the script now turns it off.
818
  - Re-run after L1.
819
- 2026-09-29 **L1 done**, change format 2 (oxygen#34, c8a26be3). On Earth, the Linux tip (95 943 files, 1.8 GB):
820
  - **First commit:** 55.3 s, 9.9 GB peak, 442 MB store. Before this, the commit failed and left a repository that would not open.
821
  - **Clean `status`:** killed by the 14 GB cap. That is L1b.
822
- 2026-09-29 **Moved to backlog 3.** At Andy's request, the Linux and scale work (L1b to L4, P5c) moved to `docs/backlog-overnight-3.md`: not worse than Git, with Git's numbers on the Linux tip as targets. Overnight loop 3 works only on that; the rest of this backlog waits.