feat: add read-only Baby Menu quota fallback for stale quota-axi readings - #1175
Closed
19991-cmd wants to merge 17 commits into
Closed
feat: add read-only Baby Menu quota fallback for stale quota-axi readings#117519991-cmd wants to merge 17 commits into
19991-cmd wants to merge 17 commits into
Conversation
Add bin/fm-freellmapi.sh, the single owner of the fleet's vetted FreeLLMAPI install: exact-commit pin (526c8634), risk-acknowledged install, secure ENCRYPTION_KEY generation, loopback-only start with mandatory bind verification, .env-sourced key seeding that keeps secret values out of stdout, logs, and argv, and guarded stop. The lane is opt-in tooling only; docs/freellmapi-lane.md owns the usage policy, known risks, and removal, and nothing routes to the lane automatically. Behavior tests use PATH shims and invented fake keys only.
External review found two overclaims: process-listing immunity (ENCRYPTION_KEY still lives in process env) and status stopping non-loopback binds (status only warned). State the honest secret boundary, document xtrace leak risk, and make header/docs match status-alerts-only behavior; add a regression test.
lisaa-avain.sh writes .env.bak-* copies that hold API keys. Track .gitignore rules for .env.bak and .env.bak-* next to .env, and add a hermetic regression test so a plain git add -A cannot stage them.
…anged-path selection
Replace `VAR=$(cat <<EOF ...)` multi-line captures with statement-level `read -r -d '' VAR <<EOF` so bash 3.2 no longer chokes on apostrophes in Definition-of-done and Herdr declaration bodies (issue kunchenguid#958). Scaffold text is unchanged. Add an explicit /bin/bash parse-and-run regression test.
# Conflicts: # bin/fm-test-run.sh
Integrate fm/lukko-korjauskierros over kunchenguid's kunchenguid#996/kunchenguid#997: - typed lock outcomes (OWNED, LIVE_OTHER, STALE_RECLAIMABLE, IDENTITY_UNAVAILABLE, RECLAIM_BUSY) with LOCK_RESULT= on every path, atomic stale-owner reclaim, and the Codex thread-identity fallback - reclaim mutex records owner pid and started timestamp; a live owner is never overridden and only a provably abandoned mutex is recovered - keep main's shared session-lock lib identity (kimi included), the .lock.acquire acquisition serialization, the state-write probe, the non-regular-file lock refusal, and publish read-back verification - session start and supervision instructions withhold mutation on every non-owned outcome while keeping the atomic reclaim path offered - make the concurrent-winner test bash 3.2 compatible: contenders run as separate bash processes because macOS bash has no BASHPID - update branch test assertions to the current Stop-hook-owned Claude protocol line and the typed non-mutating banners - drop the TILANNE.md scratch note
…th (S4) Move opt-in detail out of the always-loaded AGENTS.md to its canonical owners and leave one-line owner pointers behind: - section 2 config knob annotations -> docs/configuration.md sections and secondmate-provisioning (inheritance contract) - section 2 state artifact annotations (turn-ended, grok token, meta fields, herdr presentation, check-trust, pr-poll records, PR-check migration artifacts, X-mode artifacts, pending-replies, wake-queue format, watcher/guard/sub-supervisor internals) -> producing script headers, docs/configuration.md, docs/architecture.md, docs/herdr-backend.md, and bootstrap-diagnostics, keeping a compact core tree plus a never-touch rule for unlisted state entries - section 3 bootstrap sweep enumeration and liveness sweep detail -> bin/fm-bootstrap.sh header (already the owner) - section 6 secondmate routing/reconciliation duplicates of sections 5 and 7 -> deduplicated to the section 7 and 5 owners - section 14 duplicate fmx-respond trigger and completion-follow-up rule -> section 13 trigger and section 8 supervision rule Add the section 7 light path for bounded work: at most 3 files, local-only, no concurrent task on the project, no new public API, dependency, or abstraction, nothing destructive, irreversible, or security-sensitive; minimal brief, same spawn isolation, supervision, and merge authority. Record the previously AGENTS-only watch-triage.log deletion-safety fact in docs/architecture.md so no moved fact is lost. AGENTS.md: 503 -> 473 lines, 53584 -> 47989 bytes; the section 2 layout block alone: 61 -> 30 lines. Safety boundaries are unchanged: every section 1 hard rule, merge authority, unlanded-work, discard, isolation, lock, ask-user, and X-mode consent boundary is retained verbatim or strengthened in its single owner; tests/fm-instruction-owners.test.sh and the related contract suites pass unchanged except for the section 8 sentence now carrying the exact completion-follow-up phrase previously in section 14.
Prefer a kernel flock(2) on state/.lock.acquire and state/.lock-reclaim, taken through a small perl helper (macOS has flock(2) but no flock(1) CLI; perl is already a soft dependency of bin/fm-wake-lib.sh). The kernel releases the lock when the holding process dies, removing the orphaned reclaim-mutex jam class at its root; a leftover mutex file is unlocked and harmless. A post-flock inode and regular-file recheck closes the classic unlink-and-recreate sharing race. The mkdir/symlink locks remain the complete fallback when the helper is unavailable (FM_LOCK_NO_FLOCK=1 forces this) and the recovery path for a legacy directory-form mutex already on disk, so no new hard dependency is added. The main session lock state/.lock stays a durable on-disk identity and is deliberately untouched. All typed outcomes (OWNED, LIVE_OTHER, STALE_RECLAIMABLE, IDENTITY_UNAVAILABLE, RECLAIM_BUSY) are preserved. Tests: flock mutex is busy while held and auto-released on holder death; crash-leftover mutex files never jam; the mkdir fallback still recovers abandoned mutexes and keeps typed results without the helper; concurrent acquisitions still admit exactly one winner.
…tory Regression from the fm-freellmapi lane merge (dee5ae6): the lane policy doc was added without an audience entry, failing fm-documentation-audiences. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…chenguid#1) * docs(session-close): conditional close instruction with an explicit handoff path Session closing previously had no instruction at all: only the recovery side existed (AGENTS.md sections 3 and 5), so a session ending with work under way had no defined handoff and relied on the model improvising. Add a new user-invocable internal skill .agents/skills/session-close that branches on whether work is under way: an empty fleet closes straight away, while live work requires reconciling durable records, filing unanswered captain decisions as held work items, a /stow sweep, leaving running work and monitoring untouched, and a closing summary that tells the captain what is under way, what awaits them, and how the next session resumes. It also states the lock facts (automatic stale reclaim for numeric owners, captain confirmation for codex-thread owners) and the close-versus-/afk distinction. Owner choice: neither stow nor afk is the right home. stow is a knowledge-routing sweep (and becomes one step of the handoff), and afk is the opposite contract - supervision stays live there, while closing ends it. A dedicated skill keeps one owner, with only a three-line trigger stub in AGENTS.md section 8 beside the away-mode stub it contrasts with, per the inline-stub pattern. No lock, watcher, or supervision machinery behavior changes; this is instruction-surface work only. The audience inventory and README built-in-skill table gain matching entries. Verified: bin/fm-doc-audience-check.sh ok; tests fm-documentation-audiences, fm-instruction-owners, and fm-captain-translation-contract pass. * no-mistakes(review): harden session-close wording, lane pid guard, reclaim fallback * no-mistakes(document): verify session-close doc ownership; no stale facts found * no-mistakes(review): scope close branch to ordinary tasks; handle active away mode * no-mistakes(review): defer away-mode exit at close to the afk return owner * no-mistakes(document): note away-mode return in README session-close row * no-mistakes(review): name pre-upgrade lane pid, gate catch-up promise, /session-close trigger * no-mistakes(review): translate close-summary gate wording, add section 9 pointer
19991-cmd
force-pushed
the
fm/baby-menu-kiintiolahde
branch
from
July 28, 2026 08:31
1197b9d to
19902c7
Compare
Owner
|
Automated reminder: thanks for the PR! This branch currently has a merge conflict with the base branch. When you get a chance, please rebase onto (or merge) the latest base branch, resolve the conflict, and push. After that, checks will re-run and the PR will get looked at again. Noted for firstmate#1175 at |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Add an independent Baby Menu SQLite fallback for provider quota readings when quota-axi reports stale or auth_required. Normal fresh quota-axi behavior must remain unchanged. Read ~/.baby-menu/baby-menu.db strictly read-only and remain optional and failure-silent when Baby Menu, sqlite3, the database, or a usable row is unavailable. Every fallback reading must identify Baby Menu SQLite as its source and report snapshot age. Preserve Baby Menu window identifiers instead of claiming unverified equivalence with quota-axi windows, document that the singleton/current-result tables provide no consumption history, and keep the implementation to one small direct helper with no cache, background process, or extra layer. Cover fresh, fallback, missing/unopenable, and no-write behavior, and avoid exposing account identity from stored payloads.
What Changed
bin/fm-quota-fallback.sh, a single direct helper that reads one aged Baby Menu quota snapshot forclaude,codex,grok, orkimionly when the primaryquota-axistatus isstaleorauth_required. Fresh readings never touch the database. Output is one JSON object markedsource: "baby-menu-sqlite"withsavedAt/ageSeconds, Baby Menu's own window identifiers preserved,historyAvailable: false, and quota-only fields so stored account identity is never printed. A missingsqlite3, database, table, row, or unreadable/future-dated timestamp exits 0 with no output. The database is opened read-only withPRAGMA query_only, and because Baby Menu stores it in WAL mode the open follows the writer: plain-readonlywhen a-wal/-shmsidecar exists,immutable=1when neither does, so a closed-writer database is readable without the read creating sidecars; a sidecar appearing mid-read drops the reading.AGENTS.mdsection 4 now names the helper as the only sanctioned supplementary source, counts its snapshot only when newer than the candidate's own primaryrefreshedAt, and keeps a candidate with neither a fresh primary nor a newer snapshot in the stop-and-report path.docs/configuration.mddocuments the invocation condition, the read-only/optional behavior, the age semantics, the observed Baby Menu 0.1.24 window identifiers, and the singleton/current-result tables' lack of consumption history;docs/architecture.mdanddocs/scripts.mdare aligned.tests/fm-quota-fallback.test.shcovering fresh (no database access), fallback readings, missing/unopenable databases, and no-write behavior against a WAL fixture, and registers the new test plusbin/fm-quota-fallback.shinbin/fm-test-run.sh'spure-contract-unitlane so the file cannot be silently skipped. The pipeline's Test phase passed both the new suite and--check-coverage, alongside live CLI runs against the real~/.baby-menu/baby-menu.db; Review's remaining open note is an informational bound on theimmutable=1path (rollback-journal mode is unreachable today since that database is WAL).Risk Assessment
✅ Low: Every finding from the prior two rounds is resolved and independently verified - the helper reads live, closed-writer, missing, unopenable, and read-only databases correctly on both the Apple and standard SQLite builds without creating or modifying any file, and the remaining note is a documented, non-reachable property rather than a defect.
Testing
Ran the two targeted suites covering the new helper and the dispatch-contract wording (all 18 checks pass) plus the runner's lane-coverage guard, then demonstrated the feature end-to-end as an operator would experience it by invoking bin/fm-quota-fallback.sh against the real ~/.baby-menu/baby-menu.db: a fresh primary status produced no output and never touched the database, stale and auth_required produced single JSON readings tagged source=baby-menu-sqlite with real ageSeconds, historyAvailable=false and Baby Menu's own window identifiers preserved (including Kimi's window-less auth_required case), a missing database and a stripped PATH both stayed silent with exit 0, and byte/mtime/sha comparison of the database plus its -wal/-shm sidecars before and after showed no writes; a payload-key comparison confirmed the stored accountEmail and accountBinding fields never appear in the emitted output. No failures or flakiness; this is a shell CLI change with no rendered UI surface, so the reviewer-visible evidence is CLI transcripts rather than screenshots.
Evidence: Live CLI transcript: fallback against the real Baby Menu database
--- 1. quota-axi reports a FRESH primary: helper stays out of the way --- $ bin/fm-quota-fallback.sh claude fresh (stdout: <empty>) exit=0 --- 2. state of the real database BEFORE any fallback read --- baby-menu.db 24576 bytes mtime=1785205242 sha=776eb4b58a32ccb8 baby-menu.db-wal 57712 bytes mtime=1785226540 sha=ecc1c17a912f89c6 baby-menu.db-shm 32768 bytes mtime=1785205246 sha=d73a3b693b322ffc --- 3. quota-axi reports STALE for claude --- $ bin/fm-quota-fallback.sh claude stale { "schemaVersion": 1, "provider": "claude", "primaryStatus": "stale", "source": "baby-menu-sqlite", "sourceTable": "claude_code_quota_snapshot", "savedAt": "2026-07-22T22:42:23.230Z", "ageSeconds": 466850, "historyAvailable": false, "snapshot": { "plan": "max", "upstreamSource": "oauth", "windows": [ {"id": "five_hour", "label": "session · 5h", "percentUsed": 52}, {"id": "seven_day", "label": "week", "percentUsed": 52} ], "credits": null, "errorCode": null } } --- 4. AUTH_REQUIRED for codex/grok/kimi: Baby Menu window identifiers preserved --- {"source":"baby-menu-sqlite","sourceTable":"codex_quota_snapshot","ageSeconds":453,"historyAvailable":false,"windows":["weekly"],"status":null} {"source":"baby-menu-sqlite","sourceTable":"grok_quota_snapshot","ageSeconds":454,"historyAvailable":false,"windows":["credits","product:Grok Build","product:Voice","product:Imagine"],"status":null} {"source":"baby-menu-sqlite","sourceTable":"kimi_quota_cache","ageSeconds":21863,"historyAvailable":false,"windows":[],"status":"auth_required"} --- 5. state of the real database AFTER four fallback reads --- baby-menu.db 24576 bytes mtime=1785205242 sha=776eb4b58a32ccb8 baby-menu.db-wal 57712 bytes mtime=1785226540 sha=ecc1c17a912f89c6 baby-menu.db-shm 32768 bytes mtime=1785205246 sha=d73a3b693b322ffc --- 6. missing database / absent sqlite3 --- $ bin/fm-quota-fallback.sh claude stale /nonexistent/baby-menu.db (stdout: <empty>) exit=0 $ PATH=<no sqlite3> bin/fm-quota-fallback.sh claude stale ~/.baby-menu/baby-menu.db (stdout: <empty>) exit=0Evidence: Account-identity filtering verified against the real stored payloads
### Real Baby Menu database: keys present in the stored payloads (values redacted) claude_code_quota_snapshot plan, refreshedAt, source, stale, windows codex_quota_snapshot accountEmail, credits, plan, refreshedAt, source, stale, windows grok_quota_snapshot accountBinding, operation, period, refreshedAt, schemaVersion, source, sourceVersion, stale, windows kimi_quota_cache checkedAt, error, source, stale, status ### Keys the helper actually emits under .snapshot for the same rows claude checkedAt, credits, errorCode, plan, refreshedAt, stale, status, upstreamSource, windows codex checkedAt, credits, errorCode, plan, refreshedAt, stale, status, upstreamSource, windows grok checkedAt, credits, errorCode, plan, refreshedAt, stale, status, upstreamSource, windows kimi checkedAt, credits, errorCode, plan, refreshedAt, stale, status, upstreamSource, windows => accountEmail and accountBinding exist in the stored data and never appear in the emitted reading.Evidence: Targeted test run
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
🔧 **Rebase** - 1 issue found → auto-fixed ✅
.agents/skills/afk/SKILL.md- branch carries 12 commit(s) that exist on your local main branch but were never pushed to origin/main; rebasing would bundle this unrelated work (72 file(s)) into the PR:Push main to origin, or rebase your branch onto origin/main, before gating.
🔧 Fix applied.
✅ Re-checked - no issues remain.
AGENTS.md:138- The new clause "then evaluate every configured candidate against the available explicitly sourced output" replaces "against that current output" and weakens the fail-closed rule still stated on line 139 ("if any harness/model/provider relationship, applicable quota data, or interpretation cannot be established, stop and report that candidate instead of omitting it, guessing, falling back, or calling the result quota-informed"). When a candidate's primary status is stale/auth_required AND the fallback yields nothing (no sqlite3, no DB, no row - all silent exit 0), the only remaining evidence is a stale primary; line 138 now reads as "evaluate it anyway" while line 139 demands stop-and-report, and the same paragraph both sanctions and forbids "falling back". Verified reachable: quota-axi currently reports claude=stale, kimi=auth_required on this machine, and the helper is silent whenever Baby Menu is absent. Recommend spelling out in line 138 what happens when the fallback produces nothing, so the two sentences cannot be read against each other.AGENTS.md:138- The contract tells firstmate to evaluate candidates "against the available explicitly sourced output" and "choose the candidate with the most real headroom", but gives no precedence rule between the two aged readings it now permits. Concretely observed here: quota-axi's stale Claude reading has refreshedAt 2026-07-26T01:16:36Z, while bin/fm-quota-fallback.sh returns savedAt 2026-07-22T22:42:23Z with ageSeconds 460744 - the fallback is three days OLDER than the stale primary it supplements, yet carries the same five_hour/seven_day window ids and so looks directly substitutable. Nothing in AGENTS.md or the helper compares the two ages, so headroom can be decided on the older numbers. The helper correctly reports age (intent satisfied); what is missing is the consuming rule, e.g. that a Baby Menu snapshot only adds evidence when it is newer than the primary's refreshedAt.bin/fm-quota-fallback.sh:43- age_seconds is unguarded in two directions. (1) If saved_at is ever not strftime-parseable (format drift in a third-party DB this helper does not own), strftime returns NULL, SQLite's two-argument max(0, NULL) returns NULL, and the helper emits a full reading with "ageSeconds": null - contradicting both this file's header ("or valid snapshot also produces no output") and the requirement that every fallback reading report snapshot age. (2) max(0, ...) clamps negative ages to 0, so a future-dated or non-UTC saved_at (clock skew, local-time storage) presents an arbitrarily old snapshot as brand new - the most dangerous direction for a headroom decision. I confirmed the current Baby Menu 0.1.24 values parse correctly (ISO-8601 with Z for the three snapshot tables, epoch-ms for kimi), so this is drift/skew hardening, not a live break. Suggested fix: filter the outer SELECT to rows where the computed age is a non-negative non-NULL integer, keeping the documented silent-no-output behavior.docs/scripts.md:88- bin/fm-quota-fallback.sh is not listed in the bin/ toolbelt index, even though AGENTS.md section 4 now instructs firstmate to invoke it by path at profile-array intake - exactly the "the first mate drives these" class this table documents. The other unlisted scripts are libs and internal checks (fm-*-lib.sh, fm-lint.sh, fm-doc-audience-check.sh), not agent-invoked entrypoints. Add one row next to fm-harness.sh/fm-spawn.sh.tests/fm-quota-fallback.test.sh:63- Two documented behaviors are uncovered while the harness for them already exists in this file. (1) "sqlite3 optional": the fresh test builds a fakebin to prove sqlite3 is never invoked, but no test runs a stale status with sqlite3 absent from PATH, so thecommand -v sqlite3 || exit 0guard is untested. (2) The fixture DB is created in the default rollback-journal mode, while the deployed Baby Menu DB is WAL (baby-menu.db-wal/-shm exist; pragma journal_mode = wal), so test_database_is_never_written does not exercise the journal mode that actually ships. I checked the WAL case manually and the helper left baby-menu.db, -wal, and -shm mtimes unchanged, so the claim holds - the test just does not guarantee it. AddingPRAGMA journal_mode=WAL;to make_fixture would close the gap.🔧 Fix: harden quota fallback age validation and dispatch contract
3 issues (1 warning, 2 infos) still open:
bin/fm-quota-fallback.sh:113-sqlite3 -readonlycannot open a WAL-mode database whose -shm/-wal sidecars are absent, and ~/.baby-menu/baby-menu.db is WAL mode (verified: pragma journal_mode = wal). Verified on this machine: the default sqlite3 is Apple's /usr/bin/sqlite3 3.51.0apl, and against a WAL database with no sidecars it returnsError: in prepare, unable to open database file (14)(reproduced 4x, fresh dirs); the helper then emits nothing with exit 0, so the whole fallback silently disappears. Also verified: a standard (non-Apple) SQLite deletes -wal/-shm when the last connection closes cleanly (Python libsqlite3 3.50.4 and the brew 3.53.2 CLI both left only the main file), while Apple's build keeps them - and a standard build's-readonlyopen of a sidecar-less WAL database CREATES<db>-shmand a 0-byte<db>-walnext to the database (observed on two copies), which is a filesystem write inside ~/.baby-menu/ and sits awkwardly against the criterion "Read ~/.baby-menu/baby-menu.db strictly read-only" and the no-write test. Not verified (I did not quit the app): whether Baby Menu, which bundles its own standard SQLite, leaves the database sidecar-less after a clean quit - if it does, the fallback is dead on macOS exactly when Baby Menu is closed, which is when a cached snapshot matters most. Right now the sidecars exist (49KB -wal from 05:55), so the feature works today. One cheap remedy I verified: when neither -wal nor -shm exists (i.e. no active writer), retry asfile:<db>?immutable=1, which read the same sidecar-less database successfully with Apple's sqlite3 and created no files; note immutable must NOT be used while a writer is active. Documenting the limitation instead is also a legitimate choice.tests/fm-quota-fallback.test.sh:161- The new WAL guard is environment-dependent rather than a guarantee.wal_before/wal_afterare only populated when$DB-walexists after make_fixture: on Apple's SQLite the sidecars persist so the comparison is real, but on a standard build the writer's clean close deletes them, leaving both values '' so[ "$wal_before" = "$wal_after" ]can never fail - the test reads as a WAL guarantee on CI while asserting nothing there. Separately, file_signature is size:mtime:sha256, which cannot see metadata changes: I observed that afterchmod 444 dba read-only open propagated the database's mode to the -wal file (644 -> 444), so a permission-level side effect on the sidecar would pass this check silently. Asserting on sidecar existence/absence around the read (e.g. assert_absent "$DB-wal" / "$DB-shm" when they did not exist before) would test the property that actually matters and behave the same on both builds.AGENTS.md:139- Noting the operational consequence of the added rule, since it is a real behavior change rather than only a clarification: "treat a candidate left with neither a fresh primary nor a newer snapshot as one whose quota data cannot be established" combined with line 140's stop-and-report means array dispatch now halts in cases it previously would have resolved. Concretely, with today's state on this machine (quota-axi claude=stale with refreshedAt 2026-07-26T01:16:36Z, Baby Menu claude snapshot savedAt 2026-07-22T22:42:23Z - older, so it adds no evidence), a profile array containing a claude candidate must stop and report instead of dispatching. That is a coherent fail-closed reading and matches what was asked for in review; flagging only so the stricter halting is a deliberate choice rather than a surprise.🔧 Fix: read closed-writer WAL quota database without creating sidecars
1 info still open:
bin/fm-quota-fallback.sh:140- Noting two bounded properties of the new immutable path, both consistent with the header's documented scope - no action needed unless Baby Menu's storage changes. (1)immutable=1disables SQLite's locking and hot-journal recovery, andhas_writer_sidecardeliberately tests only-wal/-shm, so a database in rollback-journal mode would always take the immutable path and ignore a hot-journal. I built that case (killed a writer mid-BEGIN IMMEDIATE, leavingh.db-journal) and the helper still returned the correct committed row, matching a plain read-only open, so this is a property rather than a demonstrated defect; it is also unreachable today since ~/.baby-menu/baby-menu.db is WAL (verified). If that ever changes, adding[ -e "$database-journal" ]to has_writer_sidecar keeps the guard true to its own "the open follows the writer" description. (2) The post-read sidecar re-check catches a writer that arrives and stays, but not one that opens, commits, checkpoints and cleans up entirely inside the few-millisecond read window; Baby Menu writes seconds apart (observed refreshes 274 s apart during this review), so the window is not material.✅ **Test** - passed
✅ No issues found.
./bin/fm-test-run.sh tests/fm-quota-fallback.test.sh tests/fm-instruction-owners.test.sh— 7 fallback behavior tests plus the dispatch-contract owner test, all passing./bin/fm-test-run.sh --check-coverage— confirms the new tests/fm-quota-fallback.test.sh is claimed by a CI lane and cannot be silently skippedLive CLI run:bin/fm-quota-fallback.sh claude freshagainst the real ~/.baby-menu/baby-menu.db — empty stdout, exit 0, no database accessLive CLI run:bin/fm-quota-fallback.sh claude staleandbin/fm-quota-fallback.sh {codex,grok,kimi} auth_requiredagainst the real database — sourced, dated readings with preserved Baby Menu window ids (five_hour/seven_day, weekly, credits + product:*, and Kimi's window-less auth_required status)Manual immutability check: size/mtime/sha256 of ~/.baby-menu/baby-menu.db, -wal and -shm captured before and after four fallback reads — identical, no sidecars created or removedLive CLI run:bin/fm-quota-fallback.sh claude stale /nonexistent/baby-menu.dband the same command with sqlite3 removed from PATH — empty stdout, exit 0Manual identity-leak check: comparedjson_eachkey sets of the stored payloads in the real database against the keys the helper emits under .snapshot for all four providersgit status --porcelain— no transient artifacts left in the worktree✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.