Sprint 46 — the button that was there all along


First, the cliffhanger. Sprint 45 ended with a chore called “Ranger les tamias” refusing to render on my phone, then the test being declared invalid because the phone wasn’t running the build everyone thought it was. Monday morning, after one honest relaunch: the chipmunks are on the calendar. Day view, correct time, full title, in a screenshot — observed, not inferred. The recurrence pipeline works end to end, and the one real code change to come out of the scare (a pull page that carries an occurrence set now always carries its parent event in the same payload) went to production the same day. On to the actual sprint.

Forty-nine PRs over six days (August 18–23), two migrations, five production promotes ending at v1.0.177 — about eight PRs a day. August build cost so far: $5,606.62 across 20 active days. But the number that matters this week is 24 — the number of days a feature we had declared missing had been sitting on production, in plain sight, where nobody could see it.

Every merge now answers to a second model

The structural change of the sprint: every pull request in the main repo is now reviewed by an independent model — Codex, running OpenAI’s gpt-5.6-terra — whose verdict gates the merge. It runs offline, in a read-only sandbox, and the workflow that invokes it is the only thing that can post its reviews. That posture went through the threat model before the workflow existed, because a reviewer that reads untrusted diffs is an injection surface and I’d rather write T-61 down first.

Here’s the discipline part. Before the gate was armed, we built a smoke set: five planted defects — a table without row-level security, a mutation that skips the audit log, a broken test seed, a fix that patches the mechanism instead of the outcome, and one clean control that must be approved. The reviewer scored 5/5, and that number is now a floor: any future change to its prompt, model, or effort level has to re-run the gauntlet and match it.

And then it started catching things. The tally for its first week on the job:

  • On the demo-reseed endpoint: five rounds of changes-requested, starting with a genuine race — two CI runs reseeding the same demo household could interleave the wipe-and-recreate. The fix ended up with an advisory lock plus a row lock, each round pinned by a new regression test.
  • On a routine metrics snapshot — a docs-only diff I’d have merged without a second look — it caught silent data loss: the fresh capture was about to replace a fully-recorded day with the post-pruning remainder of itself, quietly deleting $571.68 of July history from a file whose own README promises it preserves history. The merge is now monotonic: a smaller capture never overwrites a stored day.
  • On the weather migration: Open-Meteo’s “current” precipitation is a preceding-15-minutes value, and our mobile contract promises next-hour semantics. An off-by-one-hour in the rain forecast, on a strip whose whole job is “do I need a coat now.” Round four of five. I would not have found that one.
  • On the new CI-eyes package: ten rounds, twelve findings, all accepted, zero noise.
  • And exactly one false positive all week — on a help-docs PR — which we refuted in writing and merged over the red, per protocol. The reviewer gates; a human disposition on the record beats a wrong finding.

My favorite moment, though, is the recursive one. A PR tweaking the reviewer’s own configuration tried to slip in a behavior change — scale the model’s effort down on big diffs — with only workflow-shape tests. The reviewer sent it back, citing the repo rule: no eval, no merge. Applied to itself. It was right; the change now waits on the smoke-set eval it demanded.

It wasn’t a flawless week for the machinery — Saturday at 7:25 in the morning the reviewer died mid-review because the OpenAI org ran out of credits, and the uncomfortable footnote is that the “dev” key CI was holding was still secretly the production key, so the drained balance was the one production draws on. That’s why this sprint also finished the key isolation work across all three LLM providers: CI now structurally holds no production key for any of them, enforced by a test that sweeps every workflow file and goes red if anyone re-introduces one.

The app got its face, and the weather changed twice

The design system arrived as a full implementation spec, and all seven steps shipped: the Cabinet and Foyer palettes, the typefaces, a Theme × Mode control in Settings that syncs across devices through your profile, retired accent colors chased out of the transactional emails, a rendering rule for event colors, proper per-page browser-tab titles. The brand descriptor went through the ledger the way brand decisions should: “AI Family Office” was proposed, challenged, and withdrawn — the withdrawal recorded with reasons — and Domi is now a Household Office (“Bureau familial” in French, which frankly sounds better).

Weather shipped twice. Built the backend on MET Norway’s API on Saturday morning — client, geocoder, a frozen contract for the mobile app, a cache keyed on rounded coordinates. Same day, for licensing-fit reasons, I ruled a provider migration, and by Sunday it was Open-Meteo end to end. Here’s where the frozen-contract habit from the sync work paid for itself: the swap touched four contract fields, mobile acked all four in one round — and suggested an additive isDay boolean that was adopted within the hour. My favorite test of the sprint pins a daylight-saving transition inside a single forecast series: minus-four and minus-five offsets in the same payload, which no fixed-offset implementation can produce. And for the person who next registers a GeoNames account: after registering, you must separately enable “free web services” via a plain text link on the account page that looks nothing like a button. Two grown professionals and one language model failed to find it. The weather strip is on my phone, on production, on real data.

The funnel day, and the button that was there all along

Saturday was beta-funnel day: can a stranger find, believe, and enter this product? We audited every claim on gailleur.com/product against the exact commit production runs — four claims fully true, four partial, each with a named copy fix (the most important: “write actions require confirmation” is true in the app’s chat and not true over MCP, where protection is consent-scoping and rate limits, not per-action confirmation — the copy now says so). The site was then repositioned on that audit rather than on aspiration.

And then the humbling one. The funnel walk discovered that the request-an-invitation flow — which our own earlier audit had declared missing — had been live on production for 24 days, as a low-contrast text link on the sign-in page that two informed people and one audit all failed to see. The audit finding was retracted in writing, and the real fix shipped in an hour: it’s a proper button now. A live-but-invisible feature is indistinguishable from an absent one, and no amount of reading the code tells you which you have. You have to walk in the user’s shoes, on the real site, pretending you know nothing.

The rest, honestly

CI grew eyes this sprint — a Playwright suite that signs into staging as a demo account after every deploy, twenty visual baselines captured in one consistent run, first real comparison 24-for-24 green, and a promote gate that wants end-to-end evidence for the exact SHA being promoted (advisory for now; the flip is next sprint). The deterministic reseed behind it can only ever touch demo households by construction — the route resolves its target from the demo allowlist and re-checks before wiping, so even a leaked token can’t reach a real family’s data.

And the incident log, because that’s the deal with these posts. My merge-watcher merged a PR with two red checks early in the sprint — the code was fine, the reds belonged to someone else’s breakage, and that is still the wrong process; the rule now is zero failures on the pinned head, with no interpretation clauses. The GitHub Actions spending cap bit again Saturday night — recognized in minutes this time because sprint 41 wrote down the signature (instant zero-step failures), raised, moved on. And re-running everything at once after the cap lifted taught us that a blanket rerun cancels itself through its own concurrency groups. Every incident this sprint was caught by a written signature, a reviewer, or a test. None by a user. That’s the metric I actually watch.

Next sprint: the landing page gets its redesign — design rules it, implementation follows — the MCP surface finally gets its real-client hour against production, and the App Store submission is on me.