Skip to content

Backup Drive truth, spawn retire-project with receipts, drone caller stamp for in-process modules; 15 commits since #799 - #800

Merged
AIOSAI merged 45 commits into
mainfrom
dev
Oct 23, 2026
Merged

AIOSAI merged 45 commits into
mainfrom
dev

Conversation

@AIOSAI

@AIOSAI AIOSAI commented Oct 22, 2026

Copy link
Copy Markdown
Owner

No description provided.

AIOSAI and others added 30 commits October 21, 2026 16:33
…ough the door

restore is the owner-tier door to git restore --staged --worktree: explicit
paths or a directory, never . or a glob, never on main, no force flag of any
kind, and never a deletion (a file absent at the source is refused; deletion
stays drone rm's). Untracked files are left and listed; one ledger row per
call in .ai_central/git_restores.jsonl. show joins the sixteen external-repo
verbs with a 60 s timeout. Built for RPLAN-0007's replay runs. 14 + 1 pins
in existing files, suite 1725 to 1740, 11 mutants red. @drone, ce356209.
…po root

PR 799 run 2, macOS: a parallel worker's ruff call minted .ruff_cache at the
repo root mid-module and the litter detector convicted it. The children now
run in a tmp_path_factory room the module owns; the guard diffs that room and
a mutant relative write still errors the module by name. @seedgo, 3195de1d.
…arts at pre-flight

The owner's ruling on the v2.8.24 train: CI runs once on the final state. The
bump and the dated CHANGELOG header move into section 2, pre-flight starts the
local audit gate in the background first, and section 7 only verifies.
…behind PR #799

The fake upload in TestADriveRunStops checked len(sent) == 60 unlocked across
upload_batch's three threads. When two appends landed before either thread read
the length, no thread saw 60, SIGTERM never fired, all 120 uploads went and the
run exited 0 instead of 130: the CI line on PR #799 run 2, Python 3.12 lane.
@backup reproduced it with sys.setswitchinterval(1e-6), 7 of 15 red, and cured
the test alone: the count and the SIGTERM under one threading.Lock, a real
threading.local on the fake client (a MagicMock grew children unlocked across
threads and broke its __bool__), the exit code asserted after the 60..62 seam so
a missed patch fails by name, and the worker asserted dead after the join.
Probe 0 of 60 red; a stop-ignoring mutant red; product untouched. 21 green from
devpulse's seat. CHANGELOG Fixed line under Unreleased.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…job's in-window retry documented

@backup's nightly fired twice on 10-18 and 10-19 (about 04:46 and 05:12, 04:47
and 05:07). @daemon traced it in logs/run.log: not a double fire, a catch-up or
two schedule entries. A windowed job is due from its last SUCCESS, and the
failure backoff holds it ten minutes after a failed run, so while the 04:45 to
05:15 window is still open the job is due again: both second fires landed one
backoff after the first DONE. Both first runs failed on a host network outage in
the 05:00 hour (daemon's own rounds wakes bounced on DNS the same mornings),
which matches backup's oauth2 errors. By design; the docs now say so outright,
and that a command job's non-zero exit or timeout counts as failed.

The defect fixed: after two real runs the MISSED line read 'window closed with
no run'. The predicate is no SUCCESSFUL run, which is right for catch-up and the
miss count; the words were wrong. runstate.failed_today_at reads the day's
last_failure_at as text (no parse, no fallback branch); the MISSED line says
'closed with no successful run (last attempt failed at HH:MM)' when the job
failed today, else 'closed with no run'. Two pins in test_run_module.py (the
10-19 shape red on the old code; a failure from the day before still reads 'no
run', red on the mutant that drops the today check); the bypass rows for
runstate's logged_fallback holds re-pointed by +10. 789 green, audit 100, ruff
clean; 94 green in the file from devpulse's seat. CHANGELOG Fixed line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…3600 s

The nightly AIPass cycle started on 10-20 and 10-21 and never finished: the
Drive upload inside nightly-all hit the 1800 s timeout and lanes.drive_sync.last
stayed null. @backup measured the lane by hand on 10-21: it runs long and does
not hang, about 58 files a minute at any size (API round trips, not bytes), so
a full catch-up of 18,714 files is five hours and a normal night is the files
the snapshot changed (108 to 6,742 over 10-18..21). Inside a 30-minute job that
can never complete.

The split: nightly-all stays at 05:00 / 1800 s (snapshot + versioned measured
8.5 to 14 min for AIPass, under a minute for baud; twice the longest is under
the floor) and no longer uploads AIPass (its drive_sync setting is off); a new
command job nightly-drive-aipass runs drone @backup drive_sync @aipass at
05:45 with a 3600 s timeout. Deliberately not twice the catch-up: a command
job holds the daemon tick lock for its whole run, and ten hours of that stalls
every other branch's fires. One hour covers about 3,500 changed files; a
timeout saves the tracker and exits 130, and the next night sends only what is
left. It ends past its window, so the daemon does not retry it the same
morning. nightly-all's note also stops claiming catch-up is on by default: the
contract says opt-in and the tick confirmed it. CHANGELOG Changed line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
The 2026-10-21 catch-up hit a DNS outage at 14,745 of 16,764 files. With no
network every upload fails in milliseconds, so upload_batch walked the rest
of the queue at 2,666 log lines a minute until @backup stopped it by hand.

Failures are now counted in the worker threads (the three workers start the
next file before the main thread sees the last one fail). The 20th failure
in a row with no success between trips BREAKER_FAILURES: no worker starts
another file, in-flight uploads finish, one warning names the client's last
error, the trail row carries aborted, the tracker keeps what went. drive_sync
treats it as a failed run, not a stop: the sentence is the last stdout line,
no changelog entry, mark_command_failed, exit 2. Red-first in test_stop.py
(through the command, and 19 failures then a success never trips); the
every-50 tracker-save test in test_drive_pipeline now sees 20 failures.
522 green serial and with -n 3.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
update_status, update_source_fix_status, clear_resolved and purge_stale
read, changed and saved the registry with no json_file_lock; only report()
held it, so a status change racing a report could drop one side's write.
Each writer now runs under the lock. Red-first: TestRegistryWritersHoldTheLock
holds the lock, starts the writer in a thread, asserts it waits, writes a
concurrent entry, releases, and checks both changes survive. Four
line-scoped bypass rows re-pointed. Suite 1154 to 1158, audit 100.
CHANGELOG carries this and the backup breaker under Unreleased / Fixed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
The owner's ruling, 2026-10-21 22:59: a branch's dropbox holds inbound
hand-offs between branches, relays rather than records, so neither the
local snapshot nor the Drive copy keeps them. Measured on the Drive plan
for AIPass with an empty tracker: 426 files, 8.8 MB, gone from 23,052.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
… against a listing at run start

The only hand that deletes on Drive is the owner's, and the lane never
knew. The tracker said a file was there, so a file deleted on Drive was
skipped forever, an edited one failed its update, and a cleared tracker
uploaded the whole store a second time beside the first. Tonight the
owner emptied the Drive; the run he pressed planned 3,693 of 23,052
files and would have skipped the other 14,745 without a look.

Now drive_sync lists every live file the app can see at run start (the
drive.file scope sees only what this app created; one paged query, about
17 calls for 23,000 files). Every tracked id Drive no longer has is
dropped and uploads again. A create looks the name up in its folder first
and updates the file already there (trail action adopt). A listing with no
answer fails the run: 'Drive listing unavailable: <reason>. Nothing
uploaded.', exit 2. The late tracker reset on a new root is removed,
superseded.

Every run that uploaded a file records itself, stopped and aborted too,
with stopped/aborted/failed on the entry; status --json's drive_sync lane
carries complete. The Backups now panel reads the project's own lanes,
not the branch clock, which printed AIPass's ages on a new project.

Live on the drivetest rig: (a) folder deleted on Drive, sync: '11 tracked
files are gone from Drive; they upload again', 11 created. (b) tracker
cleared, sync: 11 adopt, Drive holds 11, no twins. (c) stopped at 3 of 10:
status shows last, files 3, complete false. Suite 528 serial and -n 3;
5 mutants red; audit 100. DPLAN-0420.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
The registry is hook-sealed to the admin seat, so a project could be
registered but never forgotten; retiring tonight's Drive test rig needed a
door. --remove drops the entry and leaves the project's .backup/ store in
place; an unknown name fails on stderr and writes nothing. Two red-first
pins in test_caller_path.py, two mutants red, suite 530.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…ches' receipts

The owner's rule, 2026-10-22: rm is never an option, every removal flows
through the system and leaves a receipt. @Spawn's retire door (RPLAN-0008)
must write one row per removed thing into .ai_central/deletions.jsonl, and
record_deletion lived in drone's handlers with no modules door.

The door is a pure re-export; the validation sits in a new handler,
record_foreign_deletion: drone rm's row shape plus target, through the same
bounded writer, and a half row (empty lane, outcome, caller, resolved or
target, or a measurement not shaped like measure()'s) is refused with
nothing written. Red first in test_deletion_log.py; 5 mutants red; suite
1740 to 1757; audit 100.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…routed children

@Spawn, @git, @seedgo and @cli are declared in routing_config.json and run inside drone's own process. The router stamped AIPASS_CALLER_CWD, AIPASS_CALLER_BRANCH and AIPASS_CALLER_IDENTITY_SOURCE only onto a child's environment, so a gate inside a module read nothing: drone @Spawn retire-project --dry-run from the admin seat answered 'Admin seat: no' while the verified-caller rail passed the same seat when stamped by hand. The gh door had already solved this for itself with a private stamp block.

route_module_command now wraps every module call in caller_stamp(): the gh door's block moved into router_handler (1.4.0) next to the resolver both lanes share, module_registry_handler (2.1.0) uses it, repo_door (1.9.1) imports it and stamps again around its own rail call so a direct caller of the door gets the same shape. Always fresh, since an inherited stamp proves nothing about this process; every prior value is put back after the call. Red-first test in tests/test_module_registry.py (1.1.0); two line-scoped bypass anchors re-derived after the import shifted them. Docs: caller_identity.md, external_repo_door.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…receipt row per removal

The owner's rule tonight: a branch or project is never removed by hand, it flows through the system and leaves a receipt. RPLAN-0008 found spawn's delete took only a lowercased name from the cwd's registry and wrote no row to the central deletion log; retiring the drivetest rig from the admin seat failed three ways.

delete <path> (modules/delete.py 1.3.0, handlers/delete_ops.py 1.3.0) keeps case and resolves the branch from the registry that seals the path. New retire-project <root> --yes (modules/retire_project.py 1.0.0) runs from the verified admin seat only, refuses the AIPass root and unsealed directories, archives the whole tree into AIPass .archive/deleted_branches/<project>_<ts>/ (the fleet's existing store, never ignored by backup), flips archived passports to registered false, revokes the hooks trust entry and removes the tree last. handlers/deletion_receipts.py 1.0.0 measures and validates one row per removed thing before anything moves and writes them through drone's door_deletion_log afterwards, lanes spawn_delete and spawn_retire; a row the door would refuse stops the removal with the tree intact. --dry-run reads from any seat and writes nothing.

Built by @Spawn (FPLAN-0654): 11 tests red first in tests/test_lifecycle.py 1.2.0, 11 mutants killed, conftest 1.3.0 redirects the live trail and trust file to tmp, suite 1071 serial and -n 3, audit 100. Run live on ~/Projects/drivetest from the devpulse seat: four spawn_retire rows, archive drivetest_20261022_010920. CHANGELOG carries this and the drone stamp fix that let the admin verdict pass through drone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…, the CI exit 128

PR #800's first CI run erred at setup in all 13 TestRestore cases on 3.10 through 3.13: dev_tree commits in a repo built by aipass_github, which empties git's global config (GIT_CONFIG_GLOBAL, GIT_CONFIG_NOSYSTEM) but sets no author. A GitHub runner has no identity git can auto-detect, so git commit exited 128; a developer machine passed because git derived one from the OS user and hostname. The gh-door fixture in the same suite already sets GIT_AUTHOR_NAME, GIT_COMMITTER_NAME, GIT_AUTHOR_EMAIL and GIT_COMMITTER_EMAIL; aipass_github now sets the same four. conftest 1.3.1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…g names a commit email

From PR #800's first red: drone's aipass_github fixture set GIT_CONFIG_GLOBAL to an empty file and GIT_CONFIG_NOSYSTEM with no author email, green on a developer machine where git auto-detects one from the OS user, exit 128 on a GitHub runner. No arm of host_portability read git's environment; it judged /proc, binaries, skip gates, Path subclasses and path spellings.

Arm F: a file holding GIT_CONFIG_GLOBAL or GIT_CONFIG_NOSYSTEM must also hold GIT_AUTHOR_EMAIL and GIT_COMMITTER_EMAIL (git resolves the two separately, a half cure still exits 128) or a user.email string. One row per file at the first emptying line. File-local on purpose: the fixture that empties the config and the fixture that commits can live in different files, so the file that takes the host's identity away answers. Fires on the 8efd77c conftest, quiet on 5d2052a. 4 pins red first, 8 mutants red, fleet sweep 2,140 files 0 rows, suite 6157 serial and -n 3, audit 100. Two bypass anchors moved +1 by a wrapped docstring line, confirmed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…t the cured verb --help trap

Commons' 10-22 rounds verified live that prompt --help and room --help print help and exit 0 (cured 10-05); the prompt line still described the old trap. One line, no code touched; committed by devpulse from commons' rounds report.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
… is missing

Both early refusals in _drive_sync_run set result['error'], logged a warning and returned: exit 0, nothing on stdout. The daemon mailed the 05:51 nightly-drive-aipass retry as 'passed, 0.7s, no output' while DNS was down (oauth2.googleapis.com unresolvable), and the host-api job record would read the same. They now print their sentence as the last stdout line and mark the command failed, the shape the listing and headroom refusals already have. Red-first test in test_drive_snapshot_lane.py; backup suite 531, api host door + daemon 848 green; checklist 41/41. docs/drive_sync.md states both refusals.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…number

The daemon killed the nightly Drive upload at 3600 s; a full AIPass rebuild is about 21,000 files at 30 to 58 a minute. The owner, 2026-10-22 11:02, on the host-api cutting his rebuild at 30 minutes: a Drive sync takes hours, a cap that cuts it has to go, eight hours is fine. timeout_seconds 28800; the job's _note carries the ruling, the rates and the tick-lock consequence he accepted. nightly-all stays at 1800. drone @daemon run --dry-run discovers the job. CHANGELOG: Changed line, and last night's arm F line moved under the one Added heading.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…mited, a limit ends through backup's stop

Built by @api on devpulse's dispatch and the owner's ruling (2026-10-22 11:01: a Drive sync takes hours, a cap that cuts it has to go). RUN_TIMEOUT_SECONDS 1800 for every lane cut the owner's 09:45 AIPass Drive rebuild from BAUD at 30 minutes and left backup's running file stale, since drone's hang guard kills one second after SIGTERM. Now RUN_LIMIT_SECONDS per lane: snapshot and versioned 1800, all 86400, drive_sync none (drone's router has no no-deadline mode, so an unlimited lane runs under a ten-year timeout, marked shortcut). A lane at its limit is ended with drone @backup stop on a daemon timer, the cancel route's verb, so it exits 130 and clears its running file; drone's guard stays as the backstop at limit plus 120 s. JobTable(after=) seam. tests/test_host_backup.py 1.2.0: lane limits parametrised, stop at limit, cancel on finish, unroutable stop logged; 7 mutants killed. api suite 1982 green. docs/host_surfaces.md present tense.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…n seat included; drone @api is the one door

Built by @hooks on devpulse's FPLAN-0655 (DPLAN-0421 phase 1). The owner's rulings: the admin seat changes any file except .secrets (2026-09-02) and agents get actions, never values (2026-10-22). Measured that morning: a python -c from the devpulse seat listed ~/.secrets/aipass where cat is denied by the owner's settings, a deny list of four command names. Reads, R6 lane: any argv word naming the store as a path (.secrets with a slash or tilde beside it, backslash read as slash) for any program, and interpreter program text naming the store, SECRETS_BASE, the api secrets modules or get_secret/set_secret/list_secrets/get_api_key calls; drone is the one exemption; a bare .secrets word is a pattern and passes. Writes, protected lane: everything under ~/.secrets, redirects and the Edit/Write tools, with no admin opening. 25 pins red first in test_git_gate.py, six mutants killed, suite 2674 serial and -n 3, checklist green, docs/git_gate.md R7 section. Ships to every project aipass init creates.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…s-file

Built by @hooks on devpulse's earlier dispatch. Cadence 2.10.0: DEFAULTS give tier0 and navmap beat false, so a fresh clone fires them at session start, /clear and post-compact only, as this checkout's config already did; identity, branch, alert and mail keep their beat; a config file's beat true restores the kernel's; eight tests now state their beat. diagnostics_state 2.2.0: pyright reports a pin reaching a not-yet-defined name on an imported module as 'is not a known attribute of module' (or for class ModuleType); missing from the cross-file signatures, that error made the set not all cross-file and the gate refused the edit to the module that cures it; both signatures are cross-file now, a local typo or an attribute on a class or None still shuts the fence. Known limit: text match, so a typo on a real module attribute no longer blocks. Nine mutants killed across both; suite 2674 serial and -n 3; bypass anchors for diagnostics_state moved two lines. CHANGELOG carries the hooks R7, cadence and fence lines.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
Built by @seedgo on devpulse's FPLAN-0656 (DPLAN-0421 phase 2). The owner's ruling 2026-10-22: agents get actions, never values. A production file outside src/aipass/api that imports aipass.api.apps.modules.secrets or handlers.auth secrets/keys/env (import, from-import, import_module by name), builds a path into ~/.secrets (a / with the '.secrets' literal, or a store literal handed to Path/open/expanduser/join), or calls get_secret/set_secret/list_secrets/get_api_key bound from those modules is one row at its first reach, the line quoted, the cure 'ask @api for the action through a drone door, never the value'. A literal is judged only as an argument, so backup's never-leaves pattern and doctor's advice text acquit. New gateway_boundary.md page, docs row, content item 3. 13 pins red first in test_gateway_boundary.py, seedgo suite 6170 green -n 3. Fleet sweep 1,670 files, five rows, all retired Telegram: prax's relay loader is anchored in prax/.seedgo/bypass.json with the retirement reason (devpulse); the four under skills/lib sit outside the audit corpus and wait for the Telegram archive.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
… keyring

The R6 clause in git_gate._credential_read read 'keyring in args and -m in args', so any python -m command with the word keyring anywhere in its argv was refused as the keyring command: python -m pip install keyring, pip show keyring. @api hit it installing the package the keyring-backed secrets store needs and stopped, routing around nothing. The module after -m must now be keyring (the existing _next helper); the keyring program, secret-tool, gh's files and shell_reader's interpreter-text shapes are untouched. One pin red first in TestGitHubWritesWithoutGh; suite 2675 passed serial and -n 3; checklist 41/41; promoted live as .live-20261022T195024Z. Reported to @hooks by mail.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…eds the files

DPLAN-0421 phase 3, built by @api on FPLAN-0657. The store was tidy plaintext under ~/.secrets/aipass that any process under the owner's user could read; hooks R7 and seedgo's gateway_boundary arm (today) made @api the only reader, and this makes the values rest where the OS guards them: one keyring entry per secret through the python keyring package, service aipass, username provider/slug, SecretService on this machine. get_secret, set_secret and list_secrets go through it; keys.py reads provider/api_key; google/auth.py reads google/creds and google/client_secret as JSON documents and saves a refresh back; host-api token, pairing and config rows are JSON entries, so write_private_json and store_path are gone, while the write lock and the pairing receipts stay as files under host_api/. A backend that cannot hold a secret raises SecretsUnavailable naming the backend class, never a value or a path, and nothing falls back to a file. drone @api secrets migrate [--dry-run] imports every store-shaped file, reads the entry back, zeroes, fsyncs and unlinks, one secret_migrated row per entry; what is not a store shape is left and named. get-secret --out is removed: drone is the hooks' one exempt program, so a value written to a file was a value a seat could read. keyring>=25.0 joins the dependencies. Red first in existing test files only, a dict-backed KeyringBackend in conftest so no test touches the real keyring; 5 mutants killed; api 2004 green serial and -n 3; backup 531 green; audit 100. The real migrate run is the owner's.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…luesky drivers

@api moved the two private integration drivers (apps/integrations, gitignored) off dotenv onto the module door: devto reads devto/api_key, bluesky reads bluesky/handle and bluesky/app_password; an absent or empty entry raises naming the entry and the import verb. The tracked change is the table in docs/SECURITY.md that tells the owner which store file to write by hand for each .env line before running secrets migrate, since only lines ending in _API_KEY are imported from the .env itself. The drivers' tests are private too (api/.gitignore); 2005 green locally, four mutants killed by hand.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
… boot

The Macbook rebooted at 16:58 for a kernel update. Lingering started
aipass-host-api.service at 16:59 before any login; the server now reads its
tokens, pairings and config from the login keyring (2ecbcc7), which is locked
until the owner logs in, so every start exited 2 with KeyringLocked.
Restart=on-failure retried every 5 s: 60 failures in 7 minutes, start limit at
17:06:10, unit dead. The owner logged in at 17:58 and the unit stayed dead;
BAUD had no server from 16:59 until the unit was reinstalled by hand at 18:30.

The unit's real dependency is the session, not boot: After= and
PartOf=graphical-session.target, WantedBy=graphical-session.target in place of
default.target. The loginctl enable-linger step, linger_enabled and the
lingering report leave autostart and host_serve; lingering is the daemon
timers' concern. Four pins red first, 5 mutants killed, api suite 2005.

Installing over the old unit needs systemctl --user disable first: enable adds
the session symlink and leaves the old default.target.wants one, which would
start the unit at boot again. Done by hand this once; the CHANGELOG says so.

Built by @api on FPLAN-0658.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…pped

A JSON document or a URL returned by a contract came back through rich with
markup read and lines wrapped at the terminal width, so a caller parsing stdout
got broken JSON unless it forced COLUMNS. Built by @api on FPLAN-0659 beside the
private search_bluesky contract (the driver itself is gitignored).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118phkGE4tVYEdYtE44cUsa
…abled

The owner's ruling 2026-10-22 20:15: Telegram is out of skills, a plain dump
to projects/telegram, no citizen, no repo, a future rewire project. The skill
had been switched off since 08-18 and retired since 09-14 behind name-based
ignores that hid 1,410 items from pytest, coverage, CodeQL and seedgo.

The move: skills/lib/telegram (52 modules, 27 test files), its doc and the two
standards json files went whole to projects/telegram, which is gitignored and
off pytest's testpaths. In the tree, every Telegram handler is renamed
(disabled): skills' door_notifier (the one import of the lib from outside it),
daemon's schedule/telegram_notifier, hooks' notification/telegram_response
with its four tests parked. daemon's run.py 1.7.0 loses the notifier imports
and every lifecycle ping; the mail notices stay and a job's notify key is
still accepted. The root .aipass/hooks.json loses UserPromptSubmit
.user_message_relay (it named a module no longer in the tree and erred on
every prompt after the move, trigger e837fbeb) and Stop.telegram_response;
the provider manifest and seedgo's snapshot fixture lose the relay bridge
line. The telegram optional dependency group is gone. pyrightconfig excludes
*(disabled)* so a parked file's dead import is no diagnostic. Tests that
imported the lib are cut or retargeted to screen_lock; switch_state drops the
telegram row; 42 dead bypass rules go. prax's monitor relay stays: self
contained, opt-in, off.

The whole map, every wire before and after and the steps back, is
projects/telegram/RETIREMENT_MAP.md beside the code. Suites: skills 476,
daemon 780, full run 25,659 passed with the fixture regenerated. FPLAN-0662.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
log_watcher's main registry path fires error_detected on every occurrence
and lets the handler own the count, backoff and breaker gates. The lazy
import fallback path, taken when the registry import failed at load or
registry_report raised, returned early unless the error was new or its
count was 2, so occurrence 3 and later never reached the handler: escalation
undercounted and an elapsed backoff could never re-dispatch. The early
return is gone and both paths follow one rule. @trigger, log_watcher.py
2.8.1, test_log_watcher.py 1.5.1 red first; suite 1158 to 1159, audit 100.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
AIOSAI and others added 15 commits October 22, 2026 21:07
The entry for 824ea64, which landed without its changelog line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
The [ancestor] case of test_the_aipass_root_and_any_directory_holding_it_are_refused
grepped the two words AIPass root in the captured output. Rich wraps the refusal
at the console width, and an xdist worker directory (popen-gw1) lengthens the
tmp path past the wrap point, so the output carried the words across a newline:
green serial, red under -n 2 on every run, one tmp-path length from red on CI.
The product refused correctly. The oracle and its two two-word siblings
(registry owner, admin seat) now read both channels with whitespace collapsed;
a mutant that changes the refusal text turns them red. test_lifecycle 1.2.1,
61 passed under -n 2 from two seats. The branch prompt's key commands gain
retire-project.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
…, read probe

Twelve findings from the ponytail audit of this branch, fixed in one pass.

release-notify matched the CHANGELOG section by substring, so v2.8.2 mailed
the v2.8.24 notes: the version is matched whole now. feedback send with a
multi-token body read the subject as the sender and lost the reply path: the
sender is the drone rail only. The feedback inbox was written unlocked and in
place: the watch registry's lock moved to a shared FileLock, every
load-modify-save holds it, saves go through tmp + os.replace. Replies wrote
the sender's ai_mail inbox by hand, no lock, no feed event: they go through
ai_mail's new post_to_inbox_file door. A command module that failed to import
read as Unknown command: it is named on stderr. Compass created and dropped an
FTS5 probe table on every connect, so every recall-hook read wrote the DB: one
read probe. The owner guard walked the process cwd with no caller rail, which
names the routed branch: it uses admin_grant's env-only resolver. Scheduled
commands had no timeout (600s now) and the help said via shell. watchdog
status listed watches and read the register twice: once each, overdue()
deleted. Feed cursor messages said replaying where a fresh cursor seeds. The
dead cross-branch warning and _handle_command indirection are gone.

devpulse suite 545 to 549, ai_mail public-surface 69 green, seedgo audit
shows only the two pre-existing logged_fallback notes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
…dit 100

The seedgo logged_fallback notes left by the audit pass, resolved.

owner_address returned the same None for an unsealed project and for a
lookup that raised, so a caller could not tell. A raising lookup now answers
an empty string, named in the docstring; both stay falsy, so the three
refusal builders and seat_email behave as before and no refusal becomes a
traceback (the existing watchdog pin still holds; one new pin in
test_owner_guard).

route_command's True after a module raised is the CLI handler contract:
the bool is the module's claim on the command, error() marks the exit 2.
Recorded in .seedgo/bypass.json beside compass's identical entry.

The compass read-only pin compares file bytes, not mtime: SQLite's change
counter moves on any write, a coarse clock could hide one.

Suite 550 green, seedgo audit 100 on every standard.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
By @hooks on devpulse's dispatch, after the CI audit step went red on dev
(run 39899340094: hooks 99, logged_fallback cadence.py:133).

Cause: d8310ff added two lines above the nine logged_fallback lines the
leg-5 bypass pins by number, so all nine stopped matching; the audit prints
the first hit per file.

Cure at the source for :133: _get_turn_token answers -1 when there is no
transcript to measure (no path, or the stat fails), which no size can be,
named in the docstring; before it answered 0, also the size of an empty
transcript. _should_increment's guard reads token > 0 instead of != 0, the
same answer for every non-negative token, and two unmeasurable turns in a
row (-1 == -1) still count where the old guard would have frozen the
counter. Two pins in test_cadence.py, each red on its own mutant. The other
eight lines are re-pointed under the unchanged leg-5 reason.

pytest.ini: the branch-local ini overrides the root pyproject, so a run from
the hooks branch collected the parked telegram tests and errored; it now
ignores *telegram*. Suite 2547 green, promoted, audit 100.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
…-0422 legs

Two build legs are out in parallel: @trigger builds the CI red lane (FPLAN-0663) and @drone builds the ci-cure git verb (FPLAN-0664). Both need new test files under their own tests/ directories. The gate opens per leg by the owner's 2026-10-06 ruling: devpulse adds the branch at the send of the brief and removes it at the landing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
…e on dev and pushes

Built by @drone on devpulse's dispatch (FPLAN-0664) for the CI red lane (DPLAN-0422): @trigger wakes the owner of a red run, gates the cure through @seedgo, and pushes it through this verb, so a red never waits on devpulse. The tier is granted by one registry fact, ci_cure: true, on exactly one entry; two holders refuse both, warn-mode never lifts it, and outside AIPass's registry it is a capability refusal. The verb commits only the named paths under src/aipass/<owner>/ or CHANGELOG.md, only on dev, with a subject that starts fix(<owner>): and names the run id; pathspecs are literal, so a path is never a glob. Every call is one ledger row in .ai_central/git_ci_cures.jsonl, refused or pushed. The owner tier's tenancy, entry and path-binding checks are factored into one registry-fact gate that both tiers use; owner's texts are unchanged. 31 pins run against a real repo with a bare origin, 13 of 14 mutants red (the survivor is --only, equivalent). Suite 1757 to 1787, audit 100. The fact is not sealed yet: @Spawn adds it to trigger's entry through its own registry code.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
…, cure push; spawn grant-ci-cure; ai_mail feed door

Built on devpulse's dispatches for DPLAN-0422. A red CI run on dev is a system event now, not a devpulse interrupt. @trigger's ci poll (daemon, every 10 minutes) reads drone's run list, keeps a ledger of runs and cures, considers only the newest run per workflow and marks older reds superseded; an infrastructure failure gets a feed event and no wake. The failed log names the owner (a seedgo-audit score line or a failing test path), ci_red fires on the bus, and the handler walks medic's gates and the two-awake cap before dispatching the owner with the failing lines and the reply shape. On the owner's wake-back the lane takes the diff and dispatches @seedgo once to judge cause against symptom; VERDICT OK appends the owner's bullet under Unreleased and pushes through drone's ci-cure door, HELD mails devpulse with the diff. Every state is a feed event for the phone and the right mail. Patrick's rulings tonight: trigger owns it, devpulse pushes as normal and never polls, seedgo reviews the cure before the push, devpulse keeps merges. Spawn adds the grant-ci-cure ceremony that seals ci_cure: true on the trigger entry alone (sealed live; sync-registry --check reports two holders). ai_mail gains send_notification as its fifth package door, the call the lane was built against. The schedule ships enabled: the first real red proves the lane end to end; the hand-fire was held because the only red on record is already superseded. Trigger suite 1159 to 1204, spawn 70 green, ai_mail public surface 27 green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
@trigger's lane and @drone's door are committed with their new test files; the per-leg opening closes at the landing, as the 2026-10-06 ruling says.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
… is written only on request

Patrick's ruling 2026-10-23 00:29: the TTS section was rarely used and cost tokens on every plan. @flow removed it from all 11 templates across the seven plan types and left one line in its place, a spoken summary only when the user asks, in plain sentences with no markup. No flow code ever read the section, so close and vectorise are unchanged and existing plans are untouched. Two pins: a fresh plan from every shipped template has no Listen heading, and a plan rendered from the FPLAN template closes. Suite 1080 to 1092, audit 100.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
…-entry cap; caps and meta lines unchanged

Patrick's ruling 2026-10-23 00:29: devpulse missed a 100 or 200 cap by one to four characters on most memory edits, because a model estimates length and cannot count it, and there is no way to make it land under 100 by aiming. The cure is a band, not a new target: entry_limits.tolerance_percent (10) is stamped by the one resolver behind the gate, lint, the push, the renderer and the todo restore; an entry is refused only past cap times (100 plus pct) floor-divided by 100; the refusal and over_by still read against the plain cap; the meta lines print the plain cap and say nothing about the band, so the writer keeps aiming at it. hooks' edit gate measures through memory's door, so it follows with no change. Band pins red first, 7 of 7 mutants killed, suite 1992, audit 100. Held for the owner: budget.py's keep-count arithmetic still measures the plain cap, so local.json's worst case at default counts crosses its 25,000 budget by the band. @seedgo's trinity check measures the cap on its own and is dispatched to follow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
…dget keeps the plain cap

The .trinity write gate (eee328d) accepts up to entry_limits.tolerance_percent over each per-entry cap, so seedgo's trinity check measured the plain cap and would have flagged entries the gate had accepted. trinity_groups.py 1.4.0 reads the same key (int 0-100, seed 10) and the Char caps group refuses only past max_chars * (100 + pct) // 100, naming the plain cap in the message. Built by seedgo on @memory's dispatch, reviewed by memory against its contract, tests run here: 300 green across the four trinity files, memory 453 green, checklist 42/42.

memory's budget.py 1.0.2: the owner ruled the file budgets stay (local.json 25,000) and the keep-count arithmetic keeps measuring the plain cap; the shortcut comment now states the ruling instead of a pending question. local.json's worst case at default counts is 23,973.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
…gram out of the tree

The merge train's README truth check (PPLAN-0075): two claims went stale on this train. Secret values now rest in the login keyring under the service aipass (api 2026-10-22), so the erase list names the keyring entries and what ~/.secrets/aipass/ still holds (the host API's write lock and pairing receipts); the host API's bind, token and pairings are keyring entries, not files beside a config.json. Telegram is out of the tree (2026-10-22), so there is nothing to turn on; the line keeps the state an earlier version may have left.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
PATCH bump on the merge train (the standing default): pyproject.toml and src/aipass/__init__.py move together, the Unreleased header becomes the dated v2.8.25 section so the GitHub Release notes extract it. Bumped before the push so CI runs once on the final state (PPLAN-0074's ruling).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
…heir commit

CI standards step red on drone at 99 (stale_header_date 94): test_git_ci_cure.py and test_git_access.py said Modified 2026-10-22 and were committed in 4488926 at 00:05 on 2026-10-23. Modified moves to the commit's date. The CI red lane caught and triaged this run to @drone at 01:57 but the dispatch was gated by drone's build-session medic mute, and a gated cure does not retry; devpulse cured it by hand for the merge train and handed the retry gap to @trigger.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGrdhwME9JrkTZdaiZFDLF
@AIOSAI
AIOSAI merged commit f705f4e into main Oct 23, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant