Skip to content

v2.8.3

Choose a tag to compare

@github-actions github-actions released this 08 Sep 04:12
· 761 commits to main since this release
v2.8.3
36a68c8

[2026-09-07] — the blanket-ruling day: eight owner waves on the contested MERGE rows, the watchdog dead-monitor backstop, the fleet refusal sweep (FPLAN-0492 / FPLAN-0499, merged as PR #758, v2.8.3)

Fixed

  • aipass: six refusals exited 0 — feedback with drone missing or hooks timing out (the rc from @hooks was computed and discarded), handoff --cli <bad>, and profile clear on a piped EOF or a wrong confirmation, which reported success while clearing nothing (FPLAN-0492 wave 6 / FPLAN-0506, canary's sweep rows). All exit 1 through raise SystemExit, aipass's own idiom (it never consults cli's resolve_exit); the three green tests that asserted result is True were rewritten in place, handoff.py:152 had no test at all and gained one in the existing platform file, and counterfactual pins keep the success paths at 0. A seventh row (feedback unknown option, ~97) exits 0 still and is aipass's next wave.
  • aipass: --no-chat and a non-TTY install skipped the doctor preflight silently; --non-interactive ignored the cwd and always targeted the default home; a second concurrent install died without a word (FPLAN-0492 wave 6, todos 193/206). The skip is now announced with aipass doctor --fix named; a headless install standing in an AIPass tree targets that tree; an install lock is claimed before the first mutating step and released before the welcome chat (which replaces the process), a second install refuses naming the lock path and holding pid, a stale lock from a dead pid is taken over once and announced. Two existing tests had an unstated dependency on the runner's cwd and now pin it. Caught in passing: the first _pid_alive used os.kill(pid, 0), which terminates the target on Windows — rewritten to tasklist there, unknown counts as alive so a live lock is never stolen.
  • aipass: init_flow._write_local_json was the last hand-rolled atomic writer in the tree (mkstemp + dump + replace) and rode a size bypass row. It goes through prax's write_json now, a False raised back into OSError so a lost init stage cannot look like a saved one. The bypass row stays, and its premise was wrong: it is a file-size bypass on a 1210-line module, measured by control-running both lanes with the row removed (audit 95 %, checklist FAIL); the split is a scoped refactor on aipass's list, not a wave smuggle. --help now names feedback and handoff (handoff was missing from the bare listing too) and init's positional scaffold.
  • hooks: five refusals exited 0 — dismiss <nosuchalertid>, both feedback sentinel arms, hookstatus with no config, and an output-capture status (FPLAN-0492 wave 7 / FPLAN-0507, canary's sweep rows plus one the suite found). Cure is sys.exit(1), the idiom already in wire_verify.py — hooks refuses through yellow Rich prints, never cli's warning(), so the fleet seam carries nothing there. Five tests rewritten in place (one had patched _dismiss_alert with a bare truthy MagicMock and could not fail in either direction); three mutations, each red. Measured and named, not fixed: from a neutral cwd drone @hooks status renders AIPass's config while a direct find_project_config() returns None, so under drone the cwd-walking surfaces resolve the AIPass project and the no-config branch is unreachable — a drone finding. The heredoc-body false positive in the test-write gate is NOT fixed (only the runner half was); it is git_gate's defect wearing a second gate, hooks' todo 25, next wave.
  • commons: every refusal exited 0 — thread bogus, thread 999999, post, comment, room join and a whoami with no detectable identity all printed the red cross and returned success (FPLAN-0492 wave 7 / FPLAN-0505, canary's sweep row). The cure is cli's existing seam, not a commons one: reset_command_state() at the top of main(), error() marking the command failed as it already did, resolve_exit(handled) producing the verdict — a refusal now exits 2 (cli's handled-but-failed code), an unknown command exits 1 and names the whole invocation. A first draft built a parallel exit_status module across 21 routers; withdrawn on the steer, both files parked in .archive/. Four pins in the existing test_cli_and_contracts.py (deleting the seam kills two; the first draft's flag-poking pin killed none and was rewritten); an autouse conftest fixture clears the process-level flag per test because the suite is one process where a real run is one per invocation.
  • ai_mail: monitor_alive answered None on Windows, so the watchdog there could never see a dead monitor — the Windows matrix went red on 6d76498 on the two liveness pins. os.kill(pid, 0) is TerminateProcess on win32, not a probe, so the Windows leg asks OpenProcess + GetExitCodeProcess (the probe prax's instance_lock already uses; kept separate because that one is private and folds "exists but not ours" into dead). ERROR_INVALID_PARAMETER → gone, ERROR_ACCESS_DENIED → alive, anything else → None. Five mapping pins through a fake kernel32 run on every platform, plus a routing pin that os.kill is never reached on win32. Same run: api's two ONE ROOM wiring tests drive open_attach, which refuses on a host with no PTY — marked @pty_required like the file's other attach tests.
  • api: a refused bind exited 0, so Restart=on-failure never fired and the host-api face stayed dark after roughly one boot in three (FPLAN-0492 wave 4, api's incident 3023446f). The tailscaled boot race (six seconds, measured in the journal on two of the last six boots) now exits 1 at all three host_serve.py sites and the unit's sixty-attempt window arms. Carved out of APLAN-0013 as a bug against documented intent; pinned at the foreground and detach seams.
  • api: the phone's git-changes cards each spawned their own drone @git status --json — 31 concurrent reached 60 % of the 30 s budget on an idle box and timed out under boot load (incident a6a71c2d). git_reads.py now coalesces: single-flight plus a 1.5 s TTL keyed on (branch, project, grain), the mechanism in a new apps/handlers/host/read_cache.py (the 1500-line cap forced the split). Four mutations red, including the copy-on-read pin that needed three readers to bite.
  • flow: a Windows-absolute or UNC recorded plan location was restored under FLOW_ROOT silently (FPLAN-0492 wave 4, todo 180). restore_ops.py tested absoluteness with startswith('/'), so C:\plans and \\srv\share\plans read as relative. Now PurePosixPath OR PureWindowsPath is_absolute, and an absolute recorded directory that has vanished is an error naming the path, never a silent re-home. Five pins, mutation-checked.
  • flow: team_dev_plans derived TPLAN on a fresh install and TDPLAN only where the registry already held the row (FPLAN-0492 wave 4, todo 179). The prefix rule is now deterministic: initials of the underscore words minus a trailing plans, plus PLAN; collision widening unchanged. Every shipped directory derives its registered prefix (pinned against the live template tree); the one hypothetical that moves is security_audit_plans, SPLAN → SAPLAN. The README says where a prefix comes from.
  • flow: nine doors accepted an unknown argument and exited 0 — aggregate <bogus> ran the cross-branch write, post <bogus> ran the archival pass (FPLAN-0492 wave 4, the unknown-argument ruling). One gate, apps/handlers/cli/arg_gate.py, decides and raises UnknownArgument with a did-you-mean; each module renders it and exits 1 before any write. registry <bogus> no longer prints a contradictory second refusal plus the help screen. The undocumented aggregate run / scan run sub-verbs are refused. Reported, not changed: register/unregister with a missing argument still exit 0 (arity, not an unknown argument).
  • skills telegram: the secret store held a ten-key bot document of which one key was a secret (FPLAN-0492 wave 6, todo 205; the root of CodeQL #111, a false positive on prax's audit-log side). config.SECRET_FIELDS is now the rule — bot_token plus the Telegram app credential api_id/api_hash — and create_bot hands the secret store {bot_token} only; the other nine keys live in ~/.aipass/telegram_bots/<bot_id>.json, promoted from "staging artifact" to runtime config, with load_bot_config merging the halves so callers keep the dict they had. The config write is fatal where it was a logged non-fatal (a bot whose branch and work_dir never landed cannot start). Legacy ten-key documents still load and warn on every load, naming the stray keys and the split command. A dry run against the live store caught the first draft proposing to move the app credential into a plain file — pinned by test_the_telethon_app_credential_is_left_alone. Migration is an operator door, drone @skills run telegram migrate-config (dry by default; --apply is Patrick's call: 30 keys across 5 bots, token kept in every one). Seven pins in test_multibot_config.py.
  • skills telegram tests wrote sync_test.json into the live ~/.aipass/telegram_bots/ on every run. A conftest autouse fixture redirects config.BOT_CONFIG_DIR per test; the eight _BOT_CONFIG_DIR patch sites moved to the one owner, and a hardcoded /tmp/test_bots is gone.
  • memory: every refusal exited 0 — unknown verb, unknown flag, rollover <bogus> all printed the right cross and returned success (FPLAN-0492 wave 6, the only branch failing all three probes of the 2026-09-07 sweep). One seam: every door in main() returned None and __main__ never called sys.exit. Cured with the fleet idiom (reset_command_state() at the top, resolve_exit(True) on the routed path): unknown verb 0 → 1, unknown flag 0 → 1, bogus sub-argument 0 → 2 (handle_command's True means "handled", not "worked"); --help, --version, bare and working verbs still 0. Four pins in test_contracts.py; the README documents the exit-code contract with the before/after table.
  • memory: the templates bump ledger could never be stamped — push reported 54 stray .trinity/ files across 22 of 22 branches as "NOT push scope", and three readers (spawn, memory, devpulse) took the line as a verdict on the branch. It named the files. The line now says so, and _trinity_strays() no longer counts *.pre_v2_backup / *.pre_v3_backup — accounted-for artifacts of named migrations (exclude by name, not an archive sweep: moving 54 files out of 22 citizens' .trinity/ is a cross-branch write, and a pin keeps the backups surviving a push). First real push: 22 branches, 44 files, 22 receipts, 5 entries archived and pruned, 854 carried, 0 strays; ledger stamped local 3.0.0 / observations 3.0.0; a dry run straight after reports nothing pending. The per-machine ledger templates/.template_version.json is gitignored.
  • memory: _completion_status returns "unknown" for an absent or empty section (hooks' 09-05 finding, trigger's fourth status value), pinned inside the existing crash test. Twelve checklist flags in test_trinity_push, test_lint, test_rollover_pipeline cured (/tmp strings → tempfile.gettempdir(), two test names lowercased); the two symbolic-tier flags are moot, those files were archived with the parked tier. Leaf-module bare pops: measured three, all guarded — nothing to cure. README 1437 functions / 42 files / 1573 collected.
  • ai_mail: a dispatch row stayed outstanding until the two-hour hard timeout after its target had already replied, so the devpulse watchdog announced DEAD @api for a landed wave (FPLAN-0492 wave 5 / FPLAN-0499 phase 2). Close-on-reply now lands in the reply path and matches by THREAD (replier is the row's target, recipient is the row's sender, thread_subject() equality after stripping stacked RE: markers), not by dispatch stamp: api's stamp sat on its report-first plan mail while the real completion carried a different id after a resume, so stamp matching would have closed early on the plan and still missed the completion. Four overdue rows (flow, api, drone, prax) were closed through the rule by append, never by hand; three older rows with no surviving reply evidence and one bare-wake row with no thread stay open by design (a sender-side close door and a 24 h expiry rule are ai_mail's next wave).
  • ai_mail: the admin bridge's discovery half walked projects/.archive/. get_project_tree_branches globbed projects/*/*_REGISTRY.json with no dot filter (the resident half had one), so an archived project surfaced as @archive. The rule is now one reader, _has_dot_component(), shared by both halves; pinned in TestProjectTreeBranches — the first fixture was planted one level too deep and the depth rule, not the dot rule, refused it, which the mutant exposed.
  • trigger: two doors refused an unknown sub-argument and exited 0 (FPLAN-0492 wave 5, row 51 of the 2026-09-07 sweep). errors <bogus> printed the token and returned True; escalation <bogus> printed its HELP screen and returned True, never naming the token. Both now refuse through the one gate at trigger.py:246-256 (Unknown command: errors bogus_xyz, exit 1). Two green tests had pinned the old behaviour — the suite was agreeing, not silent — and were rewritten, plus an end-to-end pin over both modules in test_trigger_entry.py.
  • trigger: the catch-up scan counted one occurrence per distinct error, so a 37-line burst arrived as count=1 and gate 3 (count >= 2) held it as "first occurrence — waiting for pattern" (FPLAN-0492 wave 5, the count-vs-occurrences gap @api's incident exposed). The dedup key is unchanged (one event per distinct error, never 37 dispatches); _scan_single_log_file now counts every matching line and stamps first_seen / last_seen; the live watcher path is untouched; severity stays a documented constant, never derived from the count; MAX_ERRORS_PER_SCAN still measures distinct entries so counting repeats cannot widen the storm guard. Five pins, three mutations red.
  • drone: rm's deletion ledger named the caller from the directory it stood in (FPLAN-0492 wave 3, S2). A devpulse deletion run from the repo root was recorded as caller: aipass — the project, not a citizen. deletion_log now resolves the caller through resolve_caller_identity_signal and accepts only the assigned or passport sources; the project source is refused because it answers "where", not "who". The repo root with no passport now records unknown; standing in drone records drone. Mutation-checked. The 211 earlier records stay untouched, with one record-shaped annotation appended after them (S1, Patrick's option 2).
  • drone: drone @git log not_a_real_count honoured the default and exited 0 (FPLAN-0492 wave 3, S5). Output was byte-identical to a plain log, stderr empty — a caller reading $? was told its count had been honoured. Now refuses by name on stderr, exit 1, and an ok: false document under --json; the three idioms (log 20, log -n 20, log -3) still parse. Three pins.
  • prax: six doors accepted an unknown argument and exited 0 (FPLAN-0492 wave 3, the unknown-argument ruling). drone @prax --definitely-not-a-flag printed the self-map, log-audit/monitor/log-health printed a refusal and still exited 0, dashboard printed help, and status bogus printed the normal status block with no complaint at all. All six now refuse by name on stderr with exit 1 through one shared gate (apps/handlers/cli/arg_gate.py), pinned.
  • seedgo audit: every refusal now exits non-zero (FPLAN-0496, row from the 2026-09-07 fleet sweep). Unknown argument, --artifact with no path, a branch without @, unknown pack, private branch, branch not found — each printed the cross and returned 0. _print_refusal raises CommandRefused (in apps/modules/__init__.py), seedgo.py passes it through route_command and returns its code. Pinned in test_standards_audit.py. The argv grammar moved to apps/handlers/audit/argv.py when standards_audit.py crossed 600 lines (609 → 573).
  • seedgo: a SyntaxWarning from a compiled template during the fleet walk. corpus._walk now prunes dot-directories as pytest's norecursedirs does; the source was an .archive/ file ast.parse could not attribute.
  • seedgo README said "11 core agents" three times; the fleet is 18. json_handler_content.py no longer prints the retired aipass.aipass.shared.json_handler example; json_handler.md no longer says "retiring".
  • hooks: drone @hooks <bogus> refused on stderr, and the version drift its own tests were pinning (FPLAN-0495, hooks todo 16). The refusal printed to stdout with exit 1, so redirecting stdout swallowed it; moved to err_console, exit unchanged, both tests now pin stderr and assert absence from stdout. hooks.py header said 1.2.0 while the banner and --version said 1.1.0, and both tests asserted the literal stale string; now 1.2.1 everywhere and the tests read the # Version: header (mutation-checked: header bumped with the strings left behind → 2 failed).
  • hooks test-write gate: -m pytest / -m unittest paths no longer read as test-file creation (testwrite_gate 1.1.0, bash_writes 1.3.0). write_targets_by_segment() split out so there is still one shell reader; the exemption is per-segment (pytest x && touch tests/test_new.py still refuses; 56 → 67 cases). Known residual, hooks todo 25: _interpreter_targets scans the whole command, so a heredoc plus a pytest path in one call still refuses.
  • daemon: twelve verbs refuse an unknown argument by name (FPLAN-0497). update, queue, rotation, inbox-sweep, run, install-timer, uninstall-timer, schedule, actions, activity, activity-report, activity_report accepted and silently ignored a trailing argument (daemon's own finding after FPLAN-0494). One shared gate, apps/handlers/cli/arg_gate.py, knows boolean flags from value flags (--hours 48 is not a stray positional), lets a help request outrank it, and raises UnknownArgument; every verb refuses on stderr with the usage line and exit 1. Two swallowed refusals surfaced and cured: update.py's broad except turned the refusal into exit 0 (the gate now runs outside the try in every module), and route_command's broad catch fell through to "unknown command" (the refusal is caught first, pinned). Mutation-checked one per verb, all twelve red. Every test that resolves a branch or job name now patches the roster; 594 passed with the registry forced empty.
  • daemon tests: the five branch-health tests resolved the branch name against the machine's registry, which a CI checkout does not have (PR #758 red on b681c08). The roster is now pinned in the tests; the refusal tests keep the real function.
  • spawn: add_to_registry and sync_registry --fix refuse an unreadable registry (FPLAN-0493, todo 188.2). Red-first over a registry truncated mid-file: add_to_registry returned True and left the file holding one branch with metadata.id None; sync_registry(fix=True) rewrote it with freshly minted uppercase entries and new registry ids, orphaning every passport that carried the real credential. registry_is_writable_document() now guards both (missing → creatable, unparseable or not-an-object → refuse by name, file byte-identical); metadata via setdefault so a registry without it cannot KeyError mid-write. Five pins in test_registry_credential.py (30 → 35).
  • spawn update: the passport heal reported "updated" on every run for externally written passports. The "did anything change" test compared serialised text against on-disk text across an ensure_ascii boundary, so a 1.0.0 passport with escapes came back changed with a backup and zero changed fields, forever (@vera). It now compares parsed documents. The "half-migration" premise from the 09-06 vera research was measured false: passports never reach deep_merge, the heal allowlist is three fields that exist in every schema, and migrate_document completes or refuses (pinned). Template-owned lists (.registry_ignore.json ignore_files / ignore_patterns / patterns) now grow additively; permissions deliberately excluded (a union would have re-denied devpulse's publishing lane).
  • daemon branch-health <unknown> refused by name, exit 1. It printed two contradictory "not found" blocks and exited 0 (row 21 of the 2026-09-07 sweep). The name is now resolved once, up front, against the roster the report uses; unknown or missing → one refusal naming the token plus the known-branch list. Resolution is case-insensitive, which closes daemon's standing "expects uppercase" known issue. Pinned in test_activity_report.py (36 → 38).
  • daemon inbox_sweep docstring and introspection panel claimed it discovers from AIPASS_REGISTRY.json; the sweep has read the whole fleet through @memory's fleet_branches() (28 citizens, projects/* included) since FPLAN-0460. Text corrected, scope pinned by TestSweepScopeIsTheWholeFleet. Dead apps/json_templates/ (zero readers) archived out of the tree.
  • devpulse admin_grant: verify, keygen and mint refused in a yellow print and exited 0 once the owner guard had passed, so admin_grant verify && <next> ran the next step on an unverified grant (canary's fleet refusal sweep, 2026-09-07, the three rows directly beneath the 08-28 cure). All three refuse through error(), which marks the command failed; four pins in test_owner_guard_refusals.py (a refused verify marks, a verified one does not, keygen and mint past the guard mark), mutation-checked. The sweep itself, report only: 141 warning()/yellow-print refusal sites across 18 branches, 35 exit 0, 17 of those with a green test pinning the exit-0 outcome; and the structural finding that only ai_mail, devpulse and memory consult resolve_exit(), so in 15 branches error() changes the colour and nothing else. Each owner has its rows and the seam rule for its next wave.
  • devpulse: an unknown command or flag is refused by name. drone @devpulse <bogus> used to exit 1 with empty stdout and stderr (the 2026-09-07 fleet sweep's REFUSES-SILENT). route_command now prints Unknown command: <token>, a did-you-mean from the discovered module names, the known list, and a --help pointer; exit code unchanged. Pinned in tests/test_devpulse.py (FPLAN-0492, Patrick's standing unknown-argument ruling: fail non-zero and name the token, never default).

Added

  • aipass: init stamps .aipass/test_write_policy.json at stage 1 — gate off, empty allow, create-only, never clobbered (FPLAN-0492 wave 6, the shape confirmed by @hooks by mail, never guessed). Hooks corrected the premise: a fresh install was not permissive, the gate fails closed when the file is absent, so this replaces a fail-closed refusal with a readable, flippable state and closes no hole. Stamped from the live bytes at git HEAD (hooks' mailed copy was abridged). Incident in the same wave, reported by aipass itself: a pin called the cwd-resolving path helper unpatched and overwrote the live fleet policy with a 49-byte fixture (gate on, canary allowed) for under two minutes; restored byte-exact from HEAD, no commit carried it, both tests now patch the path.
  • ai_mail: monitor_pid and a tri-state monitor_alive on the dispatch register (FPLAN-0499 phase 2). The pid is recorded as a second append-only outstanding record after the spawn (the original promise is never rewritten); monitor_alive is derived at read time from /proc, never stored — True alive, False gone, None cannot be told (every row written before this change and the systemd-scope path, which never learns a pid; folding those into False would have announced the whole historic backlog dead). Exposed in outstanding() and therefore in outstanding_dispatches() for the devpulse wire, and in dispatch register's human output per row. Readers treat None as "fall back to overdue".
  • devpulse watchdog: dead-monitor backstop (FPLAN-0499, the hole DPLAN-0314 named "outcome M"). A dispatch whose monitor died (host reboot, OOM, kill) can never report; the wire now reads the dispatch register at sign-in and every 5 minutes and pushes one DEAD @branch ... line per dispatch of this seat's that is past expected_by, announced once ever via devpulse_json/wire_dead_cursor.json. No agent is polled and no token is spent until it fires. Found the hard way: the 2026-09-07 12:17 reboot killed two wave-3 agents mid-work and nothing said so for 2.5 h. Phase 2 (wire 2.2.0, same day): once ai_mail served monitor_alive, the wire announces a gone monitor within one cadence ("its monitor (pid N) is gone before the hard timeout") instead of at the two-hour expected_by; None (a row that never learned a pid) keeps the overdue rule, so the historic backlog is not announced dead. Three pins.
  • seedgo json-handler contract: resident citizens are discovered (FPLAN-0496 item 7). Discovery is a roster — the installed 18 as the floor, unioned with @memory's fleet_branches() — measured 18 → 22 on the dev machine; residents load from file with the module cached in sys.modules exactly as import_module does, and test_one_branch_is_one_module_however_many_times_it_is_asked_for pins that identity (an uncached first draft wrote 92 fixture documents into four Vera-Studio trees before it was caught; the files were moved out, nothing pre-existing was touched). The four pre-migration Vera handlers (no shim, open(path, "w")) are skipped BY NAME with the finding in the skip line.
  • seedgo json-handler contract: three pins from the advisory mutation run (ruling 5 of FPLAN-0492). A rewrite keeps the file's permission bits (probe mode 0o640, chosen because 0o664 is what a new document is born with under the default umask and proved nothing); NaN and Infinity are refused with the previous document byte-identical after; log rotation keeps the newest window asserted by content over a seeded 105-entry log. Each pin was run against its mutant on a copy of json_service.py; two first drafts were blind and were rebuilt. Run: 391 mutants, 255 killed, behavioural score 77 % once logger-text survivors are set aside; report in devpulse docs.local/.
  • seedgo audit banner is pack-aware. pack.json declares corpus {noun, detail, measured_by}; discovery.pack_corpus resolves it like a checker; the banner no longer says "apps/ only, tests/ not in the corpus" under a pack whose every rule walks test units.
  • daemon: opt-in catch_up, interval slot, and a MISSED line (FPLAN-0494, ruling 6 of FPLAN-0492). A daily or rotation job with schedule.catch_up: true whose window closed unrun fires late on the next tick, bounded by the already-ran-today guard so it cannot double-fire, stamped caught_up: <iso> in its runstate and cleared on the next ordinary run. Interval jobs take schedule.slot, an ISO instant naming one occurrence of the rhythm; a freshly enabled job with no last_run is seeded so its first fire lands on the slot instead of the next tick (the failure that fired seedgo's weekly audit on a Monday). One MISSED: line per daily job per day, same predicate as the catch-up, so the line and the fire never disagree. Root cause: the vera feedback of 2026-09-06 (a host down across the 30-minute window lost the job day silently).
  • devpulse tools/statusline.sh — the watchdog statusline for Claude Code, previously only at ~/.claude/statusline.sh and tracked nowhere. Byte-identical copy is now the versioned source; the green watchdog:in additionally requires the registered wire's metadata.wrapper == "monitor", so a foreground wire with no listener paints watchdog:OUT.

Changed

  • aipass: the profile-write failure event is profile_write_failed, not file_deleted (FPLAN-0492 wave 6; trigger's alias landed in 41ebc9f). Five fire sites renamed (profile.py fire at 84 / call at 110, init_flow.py 120/125/142), payloads kept, pinned and mutation-checked. 3 MERGE rows: two print_help does-not-raise pairs merged (a raising print_help killed both members identically); one KEPT — TestPrintIntrospection::test_does_not_raise is the only pin that renders through real Rich, its proposed sibling patches the console, and a broken close tag reddens only the candidate. README re-measured: 1052 test functions across 28 files (the old "29" was never right), 1067 → 1081 cases.
  • commons: apps/json_templates/default/{config,data,log}.json archived (FPLAN-0492 wave 7). Measured first: zero references from apps/, tests/ and tools/, one from the README's layout row. Moved to apps/.archive/json_templates/default/, never deleted; .archive/ is gitignored, so the tree records three deletions. README re-measured the same night: every line naming v4 test_quality as a live standard is gone, 477 def test_ across 21 files expanding to 491 cases, 74 bypass rows, and a correction to the FPLAN-0490 figure — 53 live prax import sites, not 75 (that count had swept .archive/).
  • skills: 2 MERGE rows from the 2026-09-05 contested band, second pass (FPLAN-0492 wave 6). test_single_line_sent_as_single_message deleted (_send_batched has no single-line branch; the three-line sibling pins one call and the exact joined payload — mutation "\n".join → " ".join red on the survivor). test_declared_unreadable_sends_nothing deleted: SwitchStateUnreadable subclasses RuntimeError, so the declared test could never go red while its bare sibling was green; the declared path is exercised for real by test_corrupt_state_file_sends_nothing. Sources verified byte-identical to HEAD after each revert.
  • ai_mail: 6 MERGE rows from the 2026-09-05 contested band, second pass (FPLAN-0492 wave 5). Five merged (two kill_process copies and a cleanup_own_lock copy 640 lines apart in test_dispatch_monitor.py, the error-dispatch suppression duplicate, the header pair as a two-value parametrize); one KEPT with the proof in the test — test_batch_close_post_ops_partial_fns is the sole pin that a None purge function does not stop the central update, which the walk had credited to its siblings. The session_pointer Path.home() guard from the brief was already landed and pinned (_claude_home(), three tests on a raising Path.home); reported with citations, nothing added. 18 mutants planted, 18 killed; 1419 → 1447 collected.
  • trigger: the profile write-failure event is profile_write_failed; file_deleted is a deprecated alias for one release (FPLAN-0492 wave 5, trigger todo 192). The event fires on a failed profile write, not a deletion. DEPRECATED_EVENT_ALIASES in core.py resolves the old name in fire(), on() and off() (off too, or a handler registered under the old name could never be detached), logging the deprecation once per name per process. Measured fleet-wide: zero registered handlers on either name; three firers — aipass profile.py, aipass init_flow.py (both queued for aipass's wave), daemon timer_install.py:157 (a different event, queued for daemon to name honestly) — and prax's unified_stream.py:59 colour map keyed on the literal, queued for prax. Six pins, five mutations red.
  • trigger: 7 MERGE rows from the 2026-09-05 contested band, second pass (FPLAN-0492 wave 5). Six merged (1049 → 1043 mid-wave); one KEPT with the reason written into the test — test_suppressed_fingerprint_does_not_dispatch is the only pin between compass #219 and a suppressed error going out the door. The walk's reasoning on three delegation rows was corrected: the named failure sibling proved the handler was called, not called once; a different sibling caught the double call. Two pins for 3.10 module identity live inside the existing test_core.py (a CI canary; the box is 3.12). Dead apps/json_templates/ (zero readers) moved to apps/.archive/, which is gitignored by the 2026-08-18 ruling, so it lands in git as the deletion of four tracked files; the archive copy is local recovery only.
  • api: ONE ROOM step 1 — a branch seated in a room BAUD did not create is attached to, never given a second room underneath it (DPLAN-0327 / DPLAN-0310 P3, FPLAN-0492 wave 4). _room_for honours the snapshot's outside_room when has_room is false and uses a new attach-only door (attach.py attach_only_command, tmux attach-session, no new-session -A), so the create path is structurally absent rather than guarded. An own room still wins when it exists. Step 2 (an empty room must not outrank a live seat) waits on a per-row liveness field from BAUD.
  • api: 8 MERGE rows from the contested band landed, mutation-checked (FPLAN-0492 wave 4). Three in test_api_key.py, five in test_openrouter_client.py; every twin carried exactly one oracle its sibling lacked, the survivor keeps the union. README re-measured (1470 functions / 1569 cases). Left unfolded on purpose: fleet.py still carries its own copy of the single-flight+TTL shape that read_cache.py now names.
  • flow: 4 MERGE rows from the contested band landed, mutation-checked (FPLAN-0492 wave 4). One pair merged (parametrised over one and two modules), one kept as a parametrised pair because the inputs straddle a live branch in print_help, one kept and now asserting where it asserted nothing. All four read the real Rich output instead of an empty capsys. test_flow.py 39 → 40 collected; suite 1023. The dead json_templates handler directory (zero readers fleet-wide, 23 mentions all skip-list entries) leaves the tree; a copy sits in the branch's local .archive/.
  • drone: 3 MERGE rows from the contested band landed, mutation-checked (FPLAN-0492 wave 3, S4). test_path_calls_get_registry_path folded into test_path_prints_registry_location; the four one-line log_operation twins in test_module_registry.py are one parametrised test over list/info/check/bogus — two mutants prove the verb has to stay parametrised. 1265 → 1264 collected. S6 (rm refusal exit 0) could not be reproduced from any door drone can reach; two pins lock the mapping (an outside-roots refusal is False, the CLI maps it to exit 1). S3 (owner/admin rm in siblings) NOT built: the sibling fence is one identity-blind if, and the only real authority in the tree is a plugin welded to the git verb table — lifting it is a change to the git credential path, held for a ruling.
  • prax: 18 MERGE rows from the contested band landed, mutation-checked (FPLAN-0492 wave 3). Seven test files, 1491 → 1487 collected cases (18 test functions gone, 10 cases — a parametrised survivor keeps both inputs where dropping one would change a branch). Every survivor keeps the union of both oracles and eight pin claims nothing pinned before (emit_command_separator's caller/command, SystemLogger level routing, the TTY aliases, doRollover's warning, the template-status truncation). 40 mutations, all red, all reverted. README re-measured: 1404 functions / 1487 cases / 36 files, the per-file table re-collected, 11 handler directories; the branch prompt's "901 tests across 19 files" claim corrected.
  • seedgo: 44 MERGE rows from the 2026-09-05 contested band landed (FPLAN-0491 + FPLAN-0496). 37 pairs in test_content_functions.py, one retired by subject gone (get_test_quality_standards left with the v4 standard), six in test_coverage_audit.py (collection 124 → 118) each mutation-checked with one mutant per moved input, killed by exactly the survivor that inherited it. v4 test_quality residue: two live-voice references cured, .archive/ untouched. README re-measured: 62 files, 3028 test functions, 3897 collected cases.
  • hooks: auto_watchdog retired and the presence fossil parked (FPLAN-0495, devpulse todos 181 and 171). auto_watchdog(disabled).py, enabled: false in both .aipass/hooks.json and project_hooks.json (30 of 32 entries enabled); the hooks.json edit broke the enrolled trust hash by design and Patrick re-enrolled at 11:13. presence.py → presence(disabled).py, presence_gate stays live; archive waits for a session cycle proving nothing was connected. Both suites moved to tests/parked/ behind a conftest.py with collect_ignore_glob = ["*"] — a (disabled) suffix does not stop pytest, and a branch-level ini is never read when CI runs from the repo root. hooks README re-measured (1838 cases / 51 files, 59 live apps files).
  • .aipass/tier1_navmap.md: the four directory roles in Terminology, and the file trimmed back under its ~8,000-char cap (it was 9,035 before hooks' bullet; now under the cap with the same signal, bold removed per PROMPT_STYLE).
  • spawn citizen template: tests/test_json_handler.py retired (ruling 2 of FPLAN-0492). Newborns no longer ship the six shim stamps; seedgo's contract suite discovers a newborn's shim by path and passes it by hash (proved on a throwaway citizen, sha256 = CANONICAL_SHIM_SHA256). Archived at spawn's tests/.archive/, not inside the template (a template .archive/ ships to every newborn). Manifest 50 → 49 files, .registry_ignore.json 1.2.0 → 1.3.0, absence pinned in test_template_hygiene.py. Known gap, queued to seedgo: the contract's discovery glob is the installed package, so resident citizens in other project trees get no coverage.
  • spawn citizen template: the four directory-role README stubs (docs/ tracked public reference · docs.local/ own untracked scratch and research, sub_agent_drops/ inside · dropbox/ inbound-only, consumed once · artifacts/ identity and provenance only) from the 2026-09-07 fleet inventory (devpulse todo 194). Newborns only; existing branches refresh their own by hand under their FPLAN-0492 wave.
  • tests/docker_checklist.md (repo root) gains "The one flow": one image (aipass-test:latest from Dockerfile.test), --rm containers only, post-mortem naming aipass-verify-<date>, the two runner scripts, never docker cp/exec a fix into a container (Patrick's 2026-08-28 ruling: no invented flow per test).