[2026-11-06] compile | Roster repair and a refresh: value the linter did not know

  • system-facts.md (migrated 5 Nov) declared refresh: on-change, which is outside the OKF vocabulary (hourly/daily/weekly/monthly/on-write), so wiki-lint.py treated it as hand-authored and the root index read stale every morning. Changed to on-write, added the page to the root index under System & ops, and refreshed the compile date.
  • wiki-lint.py now names the pages that made an index stale instead of only the index, so the next occurrence is diagnosable from the finding itself. Verified both branches on a two-file fixture (plain newer page → finding naming it; refresh: daily → silent).

[2026-11-03] compile | Domain routing report metadata

  • Added required Report frontmatter to the already-indexed domain routing repair report. Its repair evidence and rollback text are unchanged; refreshed the root index compile date.

[2026-11-01] document | Whole-system architecture map

  • Added system-map.md: machines, assistant/model routes, memory, home automation, voice, documents, finance, health, networking, standby and backup dependencies. Inventory appendices cover 45 VM containers, 101 primary job definitions and 27 VM tunnel rules, with private bridge paths redacted.
  • Live inventories correct older notes about Frigate placement, host Compose sources, Tailscale and systemd-hosted dashboards. Configuration evidence is distinguished from end-to-end verification. No service, provider, schedule or device setting was changed.

[2026-10-30] refine | Readable health logs without duplicate sources

  • Joe chose .md for both. anxiety-log-tracker.md is now the sole anxiety record, using dated entries with the original words; the empty anxiety CSV was moved to trashed/. blood-pressure-tracker.md is a generated read-only view of all seven original CSV readings, retaining the estimated-time labels. The CSV remains BP’s sole measurement source.
  • The existing minute-by-minute capture job writes anxiety entries directly to Markdown, appends BP to the original CSV and refreshes the view, and refreshes the view after manual CSV edits. Fixture tests cover writing, replays, invalid notes, provenance and CSV-to-view regeneration; no synthetic live health entries were made.

[2026-10-30] repair | Index health capture and BP source reconciled

  • Restored health/blood-pressure-log.csv as the sole BP record (seven pre-existing readings retained); added empty health/anxiety-log.csv. Retired two prototype Markdown trackers containing fabricated example rows to trashed/, not into the real logs.
  • health-capture.py reads explicit Index Ring transcriptions from ringbearer’s existing archive, validates measurements, preserves verbatim anxiety notes, records UK capture times, and deduplicates replay. The index-health-capture script-only job scans every minute; typed entries use the same CLI. Tests use isolated temporary files; no fake live capture was sent.
  • Retired the unused digest and unsafe callback draft; removed reminder handlers that marked items checked or verified without checking. Fixed the monthly-26th Sunday calculation. The next genuine ring press is the remaining physical end-to-end acceptance check.

[2026-10-28] audit | CouchDB admin rotated and per-app accounts provisioned

  • At 17:26 UTC, created and verified Vaultwarden entries: Joe-host CouchDB admin, Fossil Log CouchDB sync, and Tennis Log CouchDB sync. Secret values were read back only in-process and never printed.
  • Provisioned fossil-log-sync and tennis-log-sync, each restricted to its own DB. Permission probes: own DB 200; other app DB and /_users 403; /_all_dbs 401. Temporary test docs were deleted; both remote DBs remain at doc_count 0.
  • Before rotation, saved /home/joe/.hermes/backups/homelab/couchdb_couchdb_data-pre-admin-rotation-20261028-171408.tar.gz, mode 0600; all 12 archived files matched the live volume hashes. The running project is host Compose at /home/joe/stacks/28 (container labels confirm it; no stack 28 compose copy exists in the Portainer volume). Updated the owner-only .env and couchdb-admin.txt, then recreated the single service with the unchanged local CouchDB 3.5.3 image using --pull never; no image pull or B2 write.
  • The first immediate post-config-API auth check returned 401 for both credentials; a subsequent probe verified new admin 200 and old admin 401. Final read-back confirms the state file, Compose .env, and running container environment match the new credential. The stale credential remains in historical copies but is invalid.
  • Health-app v4.5 and health-export.py continue to use the wellness-scoped account; same-origin API POST 200, wrong-origin POST 403. No route, Access policy, or 2FA change. Joe reports no unsynced Fossil/Tennis data; he still needs to update those phone sync settings from the Vaultwarden entries to restore syncing.

[2026-10-28] audit | Health-export moved to least-privilege account

  • At 16:52 UTC, updated ~/.hermes/scripts/health-export.py to read /home/joe/.hermes/state/health-app-couchdb.json (mode 0600), require loopback URL + wellness DB, and use the non-admin health-app-proxy account instead of couchdb-admin.txt.
  • Read-only validation: the exporter fetched 0 mood docs; account access statuses were /wellness 200, /fossil-log 403, /tennis-log 403, /_users 403, and /_all_dbs 401. No data or secret values were printed. Syntax check passed. The export main() was not run, so no health-data output file was changed.
  • Global admin remains valid; docker inspect confirmed the live COUCHDB_USER/COUCHDB_PASSWORD environment still matches the current admin file. Rotation awaits mapping fossil/tennis phone localStorage and updating the Portainer bootstrap source. No app settings, database data, route, or password were changed in this step.

[2026-10-28] audit | F-27 corrected scope and live containment

  • Corrected secret-safe filesystem scan: six local files, 12 exact matches (temporary needle excluded), across ~/.hermes, workspace, and .mnemobrain: current/backup health-app config, admin file and its backup, state.db, and the 22 Oct state snapshot. The initial 322-file extension filter missed .bak-ip-*. Git object scan found the active and backup config blobs in commit ff1d555ce5cd (16 Oct); workspace repo has no remote; home/scripts repos had no match. The old global admin is treated as compromised. The backup config URL returned 200 before the fix.
  • Deployed health-app v4.5: local PouchDB remains; browser sync now uses same-origin /api/moods/sync; no CouchDB credentials in active JS; .bak URLs are denied. Provisioned non-admin health-app-proxy, role-scoped to wellness; config is /home/joe/.hermes/state/health-app-couchdb.json, mode 0600 and Git-ignored. Live checks: service active, root/config 200, backup 404, same-origin API 200, wrong-origin 403, wellness doc_count 0.
  • A disposable CouchDB 3.5.3 + host-browser test verified target DB read/write/delete, denial to other DBs, _users and _all_dbs, local offline save/reconnect/delete, and removal of legacy client credential settings. Container, profile and generated test credentials were cleaned up.
  • Joe’s route/no-2FA choices remain unchanged. Exact Access audience is unverified. The old global admin is still valid; map health-export.py, fossil/tennis device storage and bootstrap config before rotating it. Phone localStorage was not inspected; no Git history rewrite was made.

[2026-10-28] audit | Access API alternative checked

  • Synced Vaultwarden and searched its entries; no separate Cloudflare Access/API-token item was found. The saved dashboard login reached the 2FA setup interstitial. Joe chose not to set up 2FA, so the Access policy audience remains unverified; no Cloudflare setting changed.

[2026-10-28] audit | Open health-app storage check

  • Inspected only already-open tabs; did not navigate into fossil-log or tennis-log. Three health-app tabs had two localStorage keys each; neither the current admin password nor its Basic-auth form matched any value. No localStorage values were printed.
  • Access policy audience remains unknown because the Vaultwarden login reached Cloudflare’s 2FA setup interstitial and Joe chose not to enroll. No 2FA, dashboard, route, code, or credential change was made.

[2026-10-28] audit | F-27 source scope and Access limit

  • Hash-only exact-match scan across 322 selected app/script/stack source files found the current CouchDB administrator credential only in health-app/js/config.js; it found no base64 user:password copy. Browser localStorage, Git history, and archived backups were not included.
  • Vaultwarden supplied the saved dash.cloudflare.com login; it reached Cloudflare’s two-factor setup interstitial. Joe chose not to set up 2FA, so dashboard policy audience remains unverified. No 2FA, Access policy, route, code, or credential change was made.

[2026-10-28] audit | Health-app admin credential design verified

  • F-27: health.00000041.xyz served the current CouchDB admin credential in its client config. The first browser test had pre-existing Cloudflare Access cookies, so it was authenticated. After clearing cookies in a disposable session, the hostname redirected away from the app to Cloudflare login without returning app content, consistent with the existing wildcard Access gate; its exact policy audience remains unverified. Access-authorized viewers can still fetch the config; no route or credential change occurred.
  • Live tunnel routes the hostname to 127.0.0.1:8654; the app listens on 0.0.0.0:8654. CouchDB port 5984 is published on 0.0.0.0 and ::; outside-LAN reachability was not tested. The current credential is distinct from F-18’s historical value; neither value was printed.
  • Added F-27 to the findings register and verification sheet. Joe chose to keep the route and pursue Access; the clean-cookie browser redirected away from the app to Cloudflare login, consistent with the existing wildcard gate, but its exact policy audience remains unverified. No new Access app or route change was made. Next secure step: move DB access server-side with least privilege, then rotate the admin credential. No route, code, or credential changed.

[2026-10-28] compile | Audit continuation verified

Evidence-first note, in Joan Didion’s reportage style: measurements before conclusions.

  • Isolated full tests/cron run verified by result JSON and JUnit: 1,482 passed, 0 failed, 2 skipped, 1 RuntimeWarning; 594.47s, 1,484 cases. The two child-process failures were fixed by preserving PYTHONPATH, HERMES_RUNTIME_DIR, and HERMES_DISABLE_LAZY_INSTALLS in child environments.
  • The single warning was attributed to test_shutdown_interrupt.py::TestCombinedCancelEvent::test_run_one_job_forwards_external_cancel_event; it passes alone with RuntimeWarning promoted to error. A warning-promoted two-file diagnostic timed out at 240s; no warning suppression or product-code change was made.
  • Tracker/knowledge checks: wiki-lint.py and system-validate.py exit 0. tracker-normalise.py --check --brief exits 0 but reports four machine-classified format changes in General Todo (2 bold, 2 spacing) and one spacing change in Investment Gains; both files were left untouched. The schema calls General Todo human while the normalizer classifies it machine (F-25). One double space in a Life Admin email-summary cell was normalized; 57 table lines and all task-row content were preserved.
  • Mnemosyne native diagnostics: 44 checks, 0 failures, quick-check ok, complete vector coverage, 0 recall fallback across 100 calls. Detached gist/embedding/vector references remain unexplained; none were deleted.
  • Host probe could not reach Roomba 192.168.0.91; image update check found searxng, couchdb, and cloudflared updates; nothing was pulled.
  • Removed only uniquely tagged audit test artifacts from Trash after the full run; left all pre-existing Trash items alone. Current Proxmox: pve/data 83.85%, vm-101-disk-1 90.70%, /data 61 GiB free. The latest direct LVM read was 14:18; the 14:15 watcher run succeeded, with pool warn retained until below 80%, guest LV ok.
  • CouchDB update preflight: running 3.5.3; _users and _replicator files are present. The 26 Oct volume tar matches all 12 live files and the B2 crypt read-back. No upgrade rehearsal on a copy was run; Joe’s choice to allow the managed update stands.
  • Joe chose to keep both backups running, leave F-16/F-06 uncommitted and inactive, allow SearXNG/CouchDB’s scheduled update, leave Cloudflared unchanged, rely on Google Photos for the Motion Photo clips, and defer a full restore rehearsal. No service, backup, image, B2, or gateway state was changed in response to those choices. F-25 remains undecided, so no schema or normalizer change was made.
  • Updated mnemosyne-run.sh compatibility comment and memory-provider verification reference. Changes remain uncommitted; no gateway restart, B2 write, image pull, or HA change.

[2026-10-28] compile | Thin-pool reclaimed and verified

  • Removed 7.42 GB of inactive BuildKit cache and 471 database-managed encoded_video outputs (18.89 GiB) whose originals were verified under /data/upload/. Preserved all originals, thumbnails, 730 Motion Photo source clips, unlinked -MP.mp4 files, and one test transcode.
  • A representative HEVC output regenerated from its original via Immich’s per-asset transcode-video job while its DB row remained. The 471 removed outputs can be regenerated individually before playback; their originals remain present.
  • Verified: pve/data 90.51% → 83.86%, vm-101-disk-1 98.46% → 90.71%, /data 264 GB used / 36 GB free → 240 GB / 61 GB free. immich_server remains healthy. The final fstrim reported 27.2 GiB, but LVM stayed at 83.86%; use LVM, not the trim’s byte count, as the reclaim proof.
  • The B2 filter excludes encoded-video, including the 730 Motion Photo source clips. Those remain in Google Photos; no B2 write or VM disk resize was made.

[2026-10-28] compile | Proxy guard and source archive outcome

  • Deployed and browser-verified F-19: the public Pages proxy returns 401 without Access, the Access route works, and an unapproved destination is rejected. No live WebDAV write was tested.
  • Joe’s host-path source choice is protected by the encrypted source-code-current.tar.gz B2 object; exact read-back and 372-file scratch restore passed. The archive builder is now inline in system-b2-backup.py. The temporary standalone helper was trashed after system-validate.py correctly flagged it as an orphan; the inline builder passed an isolated archive test. No full system sync was run manually.
  • The scheduled 28 Oct state snapshot and system-B2 mirror both succeeded; the post-leak state archive is in encrypted B2. F-18 remains unrotated/unpurged. HA and tracker follow-ups were read-only; no tracker or HA state changed.
  • Focused Hermes tests: 58 passed. The broader tests/cron rerun is still in progress; no commit or gateway restart.

[2026-10-28] compile | Stack source backup and restore verified

  • Joe chose the live host-path source. The review found 10 current .env files and two stale .env.bak files. Values were not read; all twelve paths are ignored and untracked, and their modes are now 0600. The encrypted archive excludes the stale .env.bak files, node_modules, and dist.
  • Added stack-source-backup.py and a source-code source to the nightly system-B2 job. The one-object archive immich-crypt:system-backups/source-code/source-code-current.tar.gz is 3,224,244 bytes; streamed read-back SHA-256 matched. A scratch restore matched all 372 files and was trashed.
  • RESTORE.md now records the archive and the limited restore proof. No full system-B2 sync was run manually.

[2026-10-28] compile | Whole-stack audit handover recorded

  • Added the audit report and nine-criterion verification page, linked both from index.md, and updated the compiled date.
  • Added findings F-20–F-24. RESTORE.md now describes the live /home/joe/stacks source gap and the Portainer-volume comparison.
  • Repaired the bills table structure while retaining the £679 Klarna row as unverified; corrected the subscriptions receipt marker and Life Admin update marker.
  • Added /stacks/**/.env ignore rules to /home/joe/.gitignore; all 12 current .env paths are ignored and none is in Git path history. Values were not read, staged, or committed.
  • Final checks: system-validate.py exit 0; tracker-normalise.py --check exit 0 with total_issues=0 (machine-owned preview suggestions remain excluded); wiki-lint.py exit 0 with 0 findings across 123 notes. Regenerated the graph: 120 notes, 165 links, 4 tags.
  • Trashed the two passed chat audit harnesses from scratch. No B2 sync, service change, public-route edit, image pull, or commit was made.

[2026-10-27] compile | Timed reminder added

  • Added the timed task “Clean out the spare room drawers” for 28 Oct at 22:00. Its no-agent reminder is scheduled to the Telegram home chat, and the Cron Reminders table is synced to the live job list.

[2026-10-27] compile | Remaining audit work reconciled

  • Refreshed the Gate 2 pickup list after the approved OWUI catch-up and local-first skill split. Marked the 18:05 whole-stack refresh and pre-split F-07/F-12 note as historical snapshots so their older statuses are not mistaken for the current state.
  • The next 03:35 system-B2 sync remains enabled. Its OWUI mirror would prune three B2-only objects; no full sync or remote deletion was run.

[2026-10-27] compile | OWUI catch-up and local-first split verified

  • Split four sections from local-first-web-apps into loader-visible references. The main file is 37,761 bytes, down from 51,518; the duplicate opening sentence was removed. The active skill-file scan found zero matches; separate retained curator rollback copies remain documented under F-18. Pre-split backups are mode 0600.
  • The OWUI producer left four local archives; the newer owui-20261027-213542.tar.gz was copied to the exact B2 target and verified by SHA-256 read-back. The other new archive remains local. The freshness watcher exits 0. No full system-B2 sync or remote deletion was run.
  • The next full sync will mirror the local OWUI folder and remove remote-only objects, including a pre-repair DB backup, unless retained in the local source.

[2026-10-27] compile | P1 findings register updated

  • Updated the findings register with the isolated rerun of the 45 previously failing cron test nodes (37 passed; 8 remain blocked by the real-manifest test guard or missing child pytest path). The full suite is still not green.
  • The read-only CouchDB check returned HTTP 401 for the current account only; status elsewhere and rotation remain unknown. The 02:45 state backup writes backups/hermes/state.tar.gz containing state.db; the client-side-encrypted 03:35 B2 sync includes that directory. The current archive predates this transcript leak. If both jobs succeed, the 28 Oct sync will upload a fresh archive containing the database. Neither job was paused; the full system B2 sync was not run.

[2026-10-27] compile | Drive prune report catalogued

  • drive-prune-2026-10-26.md (the 26 Oct Drive audit) was an orphan with no frontmatter, so wiki-graph-refresh flagged it three ways every morning. Added OKF v0.2 frontmatter (Report) and listed it in the root index under Logs & retrospectives.

[2026-10-26] compile | Tracker and reminder housekeeping

Removed stale Royal Mail task and its spent reminder script; removed three completed one-shot reminder jobs, synced the cron summary, and corrected Home Connect / Dunelm / Jo task wording after checking source email and cron state.

Workspace Log

Append-only record of structural changes to this bundle — what was compiled, ingested, linted or migrated, and when. Newest first (OKF v0.2 §9).

Entry types: ingest · compile · lint · migrate · retire

Parseable: grep "^## \[" log.md | head -5

[2026-10-18] compile | Four recovery notes catalogued, frontmatter added

  • Root index (index.md): added the four notes written during the 16-17 Oct pool incident to System & ops — incident-20261017-io-crashloop.md, runbook-joehost-pool-fix.md, runbooks/2026-10-16-hands-on-runbook.md and handoff/standby-seed/STANDBY-BRIEFING-20261017.md. They were orphans with no type: frontmatter, so wiki-lint flagged them every morning from 17 Oct.
  • Added OKF v0.2 frontmatter to all four: Report, Guide, Guide, Reference.
  • Recompiled the index date line. Lint now 0 findings across 109 notes.

[2026-10-12] retire | Envelopes bought, line removed

  • shopping-register.md: Envelope deleted from 📥 Dump here. Joe confirmed bought, and the register’s rule is done means delete. It never needed tidying into a section, so nothing is left behind of it. Chewing gum, added an hour earlier, stays.

[2026-10-12] compile | Chewing gum filed, stale leak-sensor row removed

  • shopping-register.md: Chewing gum added to the 📥 Dump here block from a ring note, same treatment as Envelope the day before. The block is the staging area, so no section and no “For” is invented for it.
  • Same file, 🔥 Trip-worthy: Leak sensors (×2) deleted. It was the last surviving trace of the “Install leak sensors” job, which Joe had me remove from general-todo-tracker.md on 11 Oct as done. A shopping row pointing at a task that no longer exists is worse than no row. The register’s own rule is “Done = delete the row”.
  • index.md Last compiled bumped to 12 Oct, which wiki-lint.py requires before it will stop reporting the member files as newer than the index.

[2026-10-12] compile | Ice cream reminder filed

  • Ring note: “Had a reminder for 8 o’clock this evening to make ice cream”. Read as “ADD a reminder”, which is what the surrounding words require. Ran ring-reminder.py --at 20:00 --todo --action "Get the ice cream maker going", which files the tracker row Make ice cream (⏰ 12 Oct, 20:00) and writes reminder-make-ice-cream.sh. Cron reminder-make-ice-cream created one-shot, next_run_at 2026-10-12T20:00:00+01:00, no_agent, delivered to Telegram.
  • Note for the next caller passing a bare hour: the helper reads 1-7 as evening and 8-11 as morning, so “8 o’clock this evening” must be passed as --at 20:00. --at 8 fires at 08:00 and still looks correct in the dry run.

[2026-10-11] retire | Install leak sensors removed as done

  • general-todo-tracker.md 🏡 DIY: Install leak sensors deleted, not ticked. Done items leave the table. It had read “Research + buy + install” since May and Joe has now fitted seven (five original, plus Bath Leak 1 and 2 today), so the row was actively misleading about what is left to do.
  • instinct-briefing.md §4.3 DIY list updated in the same pass: “installing leak sensors” was the only completed entry in that sentence, so it is replaced by “sealing a floor”, the item added to the tracker minutes earlier. Left alone: memory/2026-05-18.md, which is a source-layer note and is never edited.

[2026-10-11] compile | Seal the whole floor added to DIY

  • general-todo-tracker.md 🏡 DIY / HOME PROJECTS gains Seal the whole floor, no date, status ❄️ like its neighbours. Filed under the ring note’s own correction rather than its opening words: he said “seal kitchen floor”, then “no, kitchen, sorry, add whole floor to my to-do list to seal”. The retraction is the signal, so the row names no room. The Details cell carries the verbatim fragments so the ambiguity survives to whoever reads the row next, and the reply to Joe asks which floor.

[2026-10-11] compile | Two dated rows added to the tracker

  • general-todo-tracker.md Open Tasks gains Return blind to Dunelm, dated Due 14 October 2026 (Wed). The wording matters: parse_date_from_text needs a cue word or a four-digit year, so a bare “Wed 14 Oct” parses as nothing and the row would never surface as a deadline. Verified against tracker_lib before committing: parses to 2026-10-14 and deadline_marker returns (True, ' 🔴 2d'), so the daily tick treats it as a live deadline from now until it is done.
  • Same table already carried Get stuff ready for the electrics tomorrow from the 22:00 ring reminder earlier today, filed by ring-reminder.py --todo with its ⏰ 11 Oct, 22:00 marker. Records it here because that helper writes the row directly and leaves no log entry of its own.

[2026-10-11] compile | Envelope added to shopping register

  • “Envelope” dumped into the 📥 Dump here block of shopping-register.md (voice note, no detail given). Not yet tidied into a section, which is the normal path for a dump line.
  • index.md Last compiled bumped to 11 Oct 2026.

[2026-10-10] compile | Breach section removed, kidney date settled, mortgage note trimmed

  • Deleted life-admin-tracker.md §5 (Moores data breach) at Joe’s request. Nothing actionable in it: no compensation route, no open correspondence. The notification itself stays archived in Paperless as doc 200, so the section was a pointer to it, not the record.
  • Kidney donation §3: the day/date mismatch is resolved. Joe confirms the appointment is Mon 19 Oct 2026, 14:45, York Hospital. Pipeline row moved Waiting -> Booked, Current Focus rewritten, chaser-log row added, frontmatter description updated. Nothing to chase.
  • pension-cashflow-tracker.md: the 25 Sep £947.96 first-payment detail is gone from the Monthly Expenditure note and from the mortgage paragraph, so it stops throwing a 25 Oct action on every sweep. It is a normal monthly Direct Debit now. The primary record in documents/principality-mortgage-completion-2026-07-30.md is untouched.
  • Followed through: index.md tracker line updated, and instinct-briefing.md lost a stale “Cifas protective registration is outstanding” claim.
  • No cron or case-watch change. The ips portal entry still awaits a case code from Interpath and stays disabled.

[2026-10-10] compile | Case actions closed out

  • Eight rows deleted from the trackers at Joe’s request. Done rows are removed, not ticked: FOS password reset, tribunal record check, Pixel phone repair, Toyota breakdown cover and the Lake District date change from general-todo-tracker.md; Cifas registration, credit-file alerts and reused @moores.co.uk passwords from life-admin-tracker.md §5.
  • Lake District removed entirely, including the trip row itself.
  • Rail Ombudsman R175958 closed: LNER compensated Joe. The rail portal was removed from case-watch.py and its state entry dropped, so it is no longer polled. fetch_rail is kept in case a future rail case needs the same shape.
  • Tribunal: the 29 Sep “record moved” anomaly is explained. It was Joe’s own login, not a case event, so §2 is updated and the case stays parked. The portal’s “Date last modified” now reads 6 October 2026, which is the same story.
  • FOS: the portal password was reset but the vault item still holds the old value. One sign-in attempt on 10 Oct was rejected, so there must be no further attempts until the vault is updated. No fetch_fos written yet; flow steps 1-2 (email, password) verified read-only, step 3 (one-time passcode) not yet seen live.
  • case-watch.sh now forwards its arguments to the Python script, so --dry-run and --portal actually work through the cron entry point.

[2026-09-29] fix | Vault blocker: bw can never work here, switched to rbw

  • Joe hit: “insecure URLs are not allowed and must use HTTPS” on bw login.
  • Root cause, and it was my error: I pointed bw at http://localhost:8383. The official CLI refuses any non-HTTPS URL in its fetch path: if (!request.url.startsWith("https://") && !platformUtilsService.isDev()) throw InsecureUrlNotAllowedError(), and isDev() compiles to return "production" === "development", a build-time constant that is always false. No flag, no env var, no escape.
  • The HTTPS routes are closed as well. The public name is behind Cloudflare Access SSO (302 to a login a CLI cannot complete). NPM’s existing vaultwarden.local proxy host listens on port 80 only and the name does not resolve from the host. Nothing on the box terminates TLS for the vault: socat, caddy, nginx and stunnel are all absent.
  • Fix: switched the broker’s backend to rbw, an unofficial MIT-licensed client that speaks the same API over plain HTTP. It covers TOTP (rbw code) and, better than the design it replaces, keeps unlocked keys in a memory-resident agent instead of a session token file, which removes the residual risk flagged in section 8 of the write-up.
  • Verified rather than assumed: stood up a throwaway Vaultwarden on a spare port with a disposable account and drove rbw against it. It authenticated over plain HTTP and reached Vaultwarden’s own credential check. The sync then stopped on the fixture account’s missing RSA key, an artefact of hand-registering it rather than of rbw or the transport. Container, account and every test file removed afterwards, and no residue in the real rbw config.
  • Cleanup: the official bw CLI was uninstalled (221 packages). It can never work against this vault and leaving a permanently broken tool on the box is worse than not having it.
  • Config: rbw set to base_url http://localhost:8383, Joe’s address, lock_timeout 604800s (7 days).
  • Blocker now on Joe: rbw register && rbw login && rbw unlock in a real terminal, since pinentry needs a TTY.
  • Docs corrected: browser-vault-capability.md sections 4, 6 and 8, and the web-automation-scripts skill, all of which described the wrong backend.

[2026-09-29] compile | Vault decisions made and wired

  • Decision 1: built-in secrets.command, not the plugin. The hansipie/hermes-vaultwarden plugin is competent but unlicensed, untouched since the day it was created, and would hold vault access plus the session token. The in-tree source runs a 60-line helper that can be read in one sitting. Adding a third-party dependency at the exact point we are removing credential dependencies is the wrong trade.
  • Decision 2: the vault is authoritative for everything re-fetchable. .env keeps only what lets the system boot and talk. OPENCODE_GO_API_KEY (the model) and TELEGRAM_BOT_TOKEN (the channel) never move, because a BW_SESSION token can be invalidated and losing either leaves an agent that cannot reason or cannot be heard. Everything else can degrade a feature safely and move once unlocked: OPENROUTER_API_KEY, MEALIE_API_TOKEN, MCP_GBRAIN_API_KEY, API_SERVER_KEY.
  • Wired: secrets.command enabled with command = vault-cred.py env Hermes, helper_timeout_seconds: 20 (the 3s default is too tight for bw), override_existing: true so the vault wins when reachable and .env catches the fallback, meaning the migration needs no .env surgery and no window where a key is absent.
  • Verified fail-soft: with the vault locked, hermes status prints one structured warning plus a remediation line and continues, exit 0. Startup is never blocked. The apply runs once per process (_APPLIED_HOMES in hermes_cli/env_loader.py), so it costs one bw call per Hermes start rather than one per message turn.
  • Safety rail added: vault-cred.py env refuses to emit OPENCODE_GO_API_KEY or TELEGRAM_BOT_TOKEN from the vault, so a mistyped field name can never override a bootstrap key under override_existing: true. Unit-tested: protected keys blocked and reported on stderr, junk and empty field names dropped, no value ever reaching stderr.
  • Safety rails proved by running them: world-readable session file refused (exit 4), clean failure when bw is not logged in (exit 6), unknown command (exit 2), bad argument count (exit 2).
  • Deferred deliberately: the vault-session watchdog. It would watch nothing until the keys actually move, and a cron job plus a Healthchecks check for an empty vault is decorative. It gets wired as the last step of the migration.
  • Blocked on Joe: bw login (master password), then creating the Hermes item and one item per site.

[2026-09-29] compile | Instinct parity: browser persistence + vault credential broker

  • Trigger: Joe is using Instinct and finds it much better at navigating websites and using account credentials. Asked for a dig into how to do the same locally.
  • Research: Instinct’s credential mechanism is a broker (1Password partnership, 15 Sep 2026), not a store. Its navigation advantage is a persistent computer with a browser that is already logged in. Both are replicable.
  • Audit found four gaps. Vaultwarden was running with zero clients installed, so nothing could read it at all. No TOTP. Provider keys sit in a 24 KB plaintext .env. And the real one: browser.camofox.managed_persistence was false, so every task got a random userId and therefore a fresh, logged-out browser profile. The Camofox volume was already persistent, it was simply never reused.
  • Built: bw CLI installed (npm install -g @bitwarden/cli, 2026.9.0) and pointed at the local Vaultwarden. New ~/.hermes/scripts/vault-cred.py credential broker: status, list, fields, get, env. Refuses to print a secret to a terminal, never passes a value as an argument or into a child environment, stderr carries structured errors only.
  • Changed: browser.camofox.managed_persistence set to true. Identity is now a deterministic profile-scoped uuid5, so one persistent browser profile survives restarts. Trade-off recorded: live cookies land on disk and a prompt-injected page would operate inside an authenticated browser.
  • Vaultwarden caveat established: secrets.bitwarden uses the bws CLI against Bitwarden Secrets Manager, which Vaultwarden does not implement. Verified: POST /api/secrets and POST /api/projects both 404. Upstream issue #33126 closed not-planned. Routes that do work: the built-in secrets.command source, or the unlicensed/unmaintained hansipie/hermes-vaultwarden plugin.
  • Written up: browser-vault-capability.md, indexed, plus a credentials section in the web-automation-scripts skill.
  • Blocked on Joe: bw login needs his master password. Everything downstream waits on that.

[2026-09-29] migrate | skill split, finance refresh rescheduled

  • cron-script-watchdogs split. SKILL.md was 99,991 characters against a 100,000 limit, so every edit was being rejected. Four of the largest self-contained sections moved out verbatim into references/: pitfalls.md (22.6 KB), mnemosyne-ingestion-hygiene.md, freshness-vs-integrity.md and reminders-json-pattern.md. SKILL.md is now 60,615 characters with 39 KB of headroom, each moved section replaced by a pointer. Backup at SKILL.md.bak-20260929-pre-split, content verified present byte for byte in the new files.
  • New reference added: references/inter-job-ordering.md, plus a pointer to it in the skill’s intro. That is the lesson from the investment-history date shift.
  • Daily finance refresh rescheduled from 10 6,18 * * * to 40 18 * * * (single evening run). It reads the same T212 sensors through the same API-snapshot override as the investment logger, so the 18:10 evening run was reading the previous day’s close. The 06:10 run was a no-op, it could only ever read the same snapshot the 18:40 run had already consumed, so it is gone rather than duplicated into a second job.
  • Verified live: ran the refresh after the change. Snapshot dated 2026-09-29, and the trackers moved from the stale 28 Sep close (ISA £87,007, GIA £15,674.03) to the correct 29 Sep values (£87,430 and £15,749.51). Day-to-day NW £112,856, liquid £103,180, total £149,355. Silent, exit 0, schema validation clean.
  • Observation, not fixed: the GIA note in pension-cashflow says “includes £1,666 deposit 25 Aug”. There was a £1,666 deposit on 25 Aug and another on 25 Sep, so the note is accurate but incomplete.

[2026-09-29] compile | investment-history.csv was one trading day out, now fixed

  • Reported symptom: finance/investment-history.csv ISA/GIA values frozen at the 26 Sep figures for three days. The job was running fine and reporting silent success, so the fault was upstream.
  • Root cause: investment-history-log.py ran at 18:20 and t212-true-return.py at 18:25. tracker_lib.ha_get_entity_state serves the T212 sensor family from the API snapshot that the 18:25 job writes, so the logger always read the PREVIOUS day’s snapshot and stamped it with today’s date. The override shipped 19 Sep, so the shift covers 19 to 28 Sep.
  • Why it looked fine: the override covers only account_value, invested, result, result_percent. Largest position, in-loss count and best/worst names came live from HA, so every row was a mix of fresh and stale values. Only the weekend exposed it, because Sat and Sun snapshots are also Friday’s close.
  • Fix 1: logger moved 18:20 to 18:35, after the API refresh.
  • Fix 2: the logger now refuses to write unless the snapshot is dated today, and self-heals by invoking t212-true-return.py first. Verified by planting a 48h-old snapshot: it refreshed and wrote correctly, exit 0, silent.
  • Repair: 7 rows corrected (18 to 28 Sep). Each row took its six API-served fields from the following row, which holds the snapshot for that row’s own date; 28 Sep (no following row) was set from HA’s recorded 28 Sep close. Nothing invented. Backup at finance/investment-history.csv.bak-20260929-pre-repair. All 12 rows from 18 to 29 Sep now reconcile exactly to the API series.
  • Note: the basis join now sits between 17 and 18 Sep (pre-override rows carry HA’s last poll, from 18 Sep the series is the API close). That boundary is unavoidable, the API series starts 18 Sep.
  • Skill: cron-script-watchdogs gained an “inter-job ordering: the silent date shift” section.

[2026-09-29] compile | S75 claim marked escalated to the Financial Ombudsman

  • Evidence found in email: FOS complaint PNX-6154478-P3L2, received 24 Sep 2026 against Homeserve Finance Limited, with Ombudsman Connect registration confirmed the same afternoon. HomeServe’s Final Response followed on 25 Sep 2026 (external flue remedial work complete, BOXT’s £250 offer stands against Joe’s £450 request, no further movement, complaint closed).
  • life-admin-tracker.md §4: the separate “Boxt” and “Section 75 Claim” entries merged into one, since they are the same dispute. Now records the FOS reference, the final response, the FOS timescales (investigator in 6 to 8 weeks, outcome 6 to 8 weeks after that) and the ~25 Mar 2027 referral deadline. Status moved from red “pursuing” to amber “with FOS”.
  • Stale reminder retired: s75-fos-escalation removed from ~/.hermes/scripts/reminders.json. It had already fired and still pointed at the superseded 15 Sep escalation window.
  • Verified: reminders.json parses (8 entries left), reminder-checker.py runs clean, and system-validate.py reports no drift.

[2026-09-29] compile | Warwick trip booked, T212 daily ping retired

  • Warwick weekend booked. Airbnb, Holloway House (4 bedroom townhouse, historic Warwick town centre), Fri 23 Oct 15:00 to Sun 25 Oct 11:00. Confirmation HM29P843B8, £860 total, paid 25 Sep.
  • Calendar: the bare Gmail auto-event (“Stay at 4 bedroom townhouse…”, all-day, no times) was replaced with a single detailed event carrying the times, address, host contact, parking options (West Rock CV34 4DZ / Cape Road free 5pm Fri to 8am Mon), lockbox note, house rules and listing URL. A timed update to the all-day event failed with “Invalid start time”, so it was deleted and recreated.
  • Tracker: the “Arrange Warwick weekend trip” row removed from general-todo-tracker.md. The Instinct briefing’s Warwick references were corrected (it said nothing was booked).
  • T212 daily ping retired. t212-true-return.py is now capture-only: it still writes finance/true-return-history.csv and state/t212-api-values.json, but prints nothing on success and exits 0. Failure is now loud (exit 1) since a failed capture would otherwise silently stop the CSV growing. --verbose gives the read on demand. t212-record-watch is untouched and still pings on a new record high.

[2026-09-28] compile | 62 stale entity ids renamed to their devices

  • Root complaint: after the bedroom/spare-room swap and several device repurposings, entity ids still carried the name of whatever the hardware used to be (or a bare IEEE address, or the old mqtt_ discovery prefix).
  • Convention now applied: <domain>.<device_slug>_<z2m_attribute>, derived from the device’s Z2M friendly_name and the entity’s own original_name.
  • 62 renames, verified in the entity registry: 62/62 applied, all new ids present, zero old ids left. Worst offenders fixed: porridge_* on the unnamed router plug, dehumidifier_* on Spare Room Lamp, power_test_linkquality on Microwave, bathroom_aqara_lux_linkquality on Lounge Aqara Motion, smartthings_toilet_linkquality on the Samjin leak sensor, washing_machine_leak_* on the Aqara leak sensor, plus 24 mqtt_-prefixed and ~28 IEEE-prefixed entity ids on named devices.
  • Checked before writing: no references in automations/scripts/scenes/configuration/Lovelace, no references in ~/.hermes/scripts, no id collisions, and none of the 62 carry a label, so no Hubitat (matter) impact.
  • Deferred: 6 landing_motion_* entities on the device Z2M calls “Bedroom Motion” but the HA registry calls “Landing Motion Xiaomi”. Renaming them would also require rewriting the binary_sensor.landing_motion group, and it is not clear which room the sensor is actually in.
  • Water-leak and smoke alarm trigger entities were already device-named and are untouched; all four leak triggers returned HTTP 200 afterwards and the estate is still 43/43 healthy.

[2026-09-28] compile | Fridge sensor removed, estate re-verified

  • Joe removed the Fridge contact sensor from Zigbee2MQTT (no longer used). Z2M is now 43 devices.
  • Cleanup: "Fridge": "contact" removed from QUIET_DEVICES in zigbee-health-watch.py, budgets recalibrated (43 entries), health ledger refreshed (43, no Fridge). HA automations, scripts, scenes, configuration and the Lovelace dashboard were already clean of fridge references.
  • Final whole-estate verdict: 43/43 in the liveness report (exact set match, healthy), function audit clean, 40/43 reachable from an automation. The three that are not (the unnamed eWeLink plug kept for router stability, Microwave, Monitor) are all deliberately un-automated.
  • Safety alarms both on: water leak last triggered 28 Sep by Joe’s physical test, smoke 22 Sep.

[2026-09-28] retire | Device Offline Alert (24h) automation deleted

  • automation.device_offline_alert_24h removed (config id 1789323813937) at Joe’s request. It had been disabled since 17 Sep 2026 and was fully superseded by the daily zigbee-health-watch.py, which builds its device list straight from zigbee2mqtt/bridge/devices and therefore covers all 44 devices, against the retired automation’s hand-maintained 41-entity list covering 35.
  • Deleted via ha_config_remove_automation. Verified: entity returns 404, zero occurrences left in automations.yaml, 49 automations remain.
  • Config archived to trashed/device-offline-alert-24h-retired-20260928.json (the pruned 41-entity version, with the retirement reason), so it is recoverable without a backup restore.
  • Estate verified after the change: all 44 Z2M devices appear in the liveness report (exact set match, no orphans either way), the function audit is clean, and 40/44 drive at least one automation. The four that do not are listed in the session notes.

[2026-09-28] compile | Water leak sensors replaced and rewired

  • Three Tuya TS0207 leak sensors removed from Zigbee2MQTT (Toilet Leak, Toilet Sink, Washing Machine Leak) plus a dead Mi temp/humidity sensor. Replaced by Round Leak (0xa4c1389abd759042) and Square Leak (0xa4c13809faa4ffff), both TS0207.
  • automation.water_leak_detected rewired: the trigger now covers Round Leak, Square Leak and the two still-unnamed 21 Sep sensors (0x00158d0004010a56, 0x286d970001084f4b). The three dead entity references are gone. Fired once manually to verify — notification delivered to notify.joe.
  • zigbee-health-watch.py watchlist updated: Round/Square added as leak class, the three dead keys removed. zigbee-budget-calibrate.py re-run (44 → 43 budget entries).
  • Found: automation.device_offline_alert_24h (disabled by Joe, superseded by zigbee-health-watch.py) still carried all three dead sensors in its watched: list plus three non-existent *_last_seen sensors in its silent: list. Pruned on Joe’s instruction the same day: every one of its 44 watched entries and 3 silent entries was validated against the live state machine first, exactly 6 were dead and were removed (41 and 0 remain). Still off, untouched otherwise.

[2026-09-26] lint | OKF frontmatter added to Instinct pages

  • wiki-lint.py finding cleared (2 → 0) — instinct-ai-research.md (type: Research) and instinct-briefing.md (type: Reference) were written on 25 Sep without required OKF frontmatter, so the daily wiki-graph-refresh cron alerted on drift.
  • Content unchanged: frontmatter only, no claims edited. Both pages were new compiled pages, not raw sources, so retro-fitting is correct here.
  • index.md date line bumped and re-verified.

[2026-09-26] compile | Instinct AI research + briefing pack added

  • instinct-ai-research.md — research brief on Instinct (Spear Street Technology, Noah Shinn), covering what it is, the funding path to a reported $10bn, the four documented security incidents, the ToS clauses that matter (disconnect ≠ delete, binding-agent authority, perpetual training licence), and a verdict: burner use only, errands not the life.
  • instinct-briefing.md — two-part pack. Part 1 is a shareable briefing on everything Joe is working on (money, legal, home, health, projects, errands) written to be handed to a third-party assistant. Part 2 is the private annex of figures, case references, credentials and constraints that stay on this machine.
  • Both added to index.md under Reference.

[2026-09-15] compile | Cash buffer deleted, SIPP corrected to VALL, Widows rebased, solar day-count fixed

  • Cash buffer apparatus deleted (Joe: “delete the whole cash buffer situation, we don’t need it any more”). Removed from pension-cashflow-tracker.md (target table, retired section, £10k Overview row), general-todo-tracker.md (Monmouthshire overflow task), cross-tracker-intelligence.md, stoozing-summary.md and index.md. Net worth now reports actual net cash £9,674.90 (Tembo + Monmouthshire + RBS + current, less stoozing £17,840.15 and Aqua £4.79) instead of a round target: £109,103 day-to-day, £145,603 total.
  • SIPP holding corrected to VALL (Vanguard FTSE Global All-Cap ETF, 0.07% OCF) — the tracker and decision log both said VWRP at 0.22%. Recorded in decisions.md.
  • Scottish Widows rebased to £3,000 in the retirement projection: +£13,403 grown at the table’s stated 5% over 30 years, so the row moves £486,372 → £499,775 and the projection total is now the true sum of its rows (£1,302,822).
  • October seasonal items deleted from annual-checklist-tracker.md (gutter clearance, flu jab) — Oct–Dec now intentionally empty, as at 7 Sep.
  • general-todo-tracker.md: four ✅ “Resolved” transaction rows and the Monmouthshire task deleted (done items are removed, not ticked).
  • Root cause fixed in ~/.hermes/scripts/solar-track.py: the headline counted only days with usable PV data but labelled the figure “Online since”. It now reports both — 152 days online, 140 with data — and the monthly “Days” column is labelled “Days w/ data”.

[2026-09-15] compile | Contradiction sweep — kidney pipeline re-synced to primary source, money snapshots re-dated

  • life-admin-tracker.md: Consultant appointment was recorded as ”⏳ Waiting — Jo to arrange” but was booked on 8 Sep. Gmail proves Jo offered Tue 19 Oct 2026, 14:45, York Hospital and Joe accepted the same day. Stage flipped to ✅ Done; chaser log corrected (the old row claimed “appt 04 Sep 2026” — that was the date of Joe’s email, scraped from the quoted reply header). Current Focus + description + stamp updated.
  • Root cause fixed in ~/.hermes/scripts/kidney-chase.py (commit a61f9dd): strip quoted reply history before date parsing, only accept a date beside appointment wording, infer a missing year from the email’s own date, match natural booking phrasing, and never leave stale blocker text on a Done row. Verified end-to-end with --sim-inbound on a scratch copy.
  • pension-cashflow-tracker.md: ISA/GIA rows were dated “15 Sep” and “26 Aug” — both are the 14 Sep close (the 26 Aug GIA was actually £13,993, so that parenthetical was stale). 30-year projection start rebased £85,494 → £85,603 and the ISA/Combined columns recomputed at 8.4%. Malformed || row prefixes in the Overview table repaired.
  • general-todo-tracker.md: stamp corrected to the actual last edit date.

[2026-09-15] compile | Shopping Register gets a free-form dump section; Warwick trip added

  • shopping-register.md gains ## 📥 Dump here — one line per item, no format required. Housekeeping tidies entries into the existing Trip-worthy / Gift cards / Everything else sections and deletes bought rows.
  • general-todo-tracker.md gains Arrange Warwick weekend trip (Sat 24 Oct 2026, Fri 23 – Sun 25, 8 people) as 🔴 — accommodation researched in September but never booked, ~5 weeks out.

[2026-09-14] compile | Solar Tracker register created from HA statistics

  • New register solar-tracker.md: EcoFlow Stream Ultra performance, import avoided and payback progress against the £1,000 capex. Written by ~/.hermes/scripts/solar-track.py (refresh: on-write), run monthly by cron solar-track (1st, 08:00).
  • Source layer is HA statistics, read-only — no hand-edited rows; the script is the authority. Registered in tracker-schema.json and the index.
  • Method note: PV is integrated from hourly statistics because the sensor’s daily sum is corrupt (froze at ~502 W on 27-29 Apr 2026).

[2026-09-10] migrate | Adopted OKF v0.2 metadata across the bundle

  • Added type, title and description frontmatter to every in-bundle note in the workspace. Controlled type vocabulary declared in KNOWLEDGE-GRAPH; enforced by scripts/wiki-lint.py.
  • Converted index and documents/index to OKF §8 shape (* [Title](path) - description), replacing the old [[wiki-link]] bullets.
  • Taught wiki-graph.py, wiki-backlinks.py and wiki-lint.py to read standard markdown links, so the §8 index conversion does not cost graph edges. Code fences and inline spans are now stripped before link scanning, which also kills phantom edges from documented link syntax.
  • Fixed three templates whose {{date}} placeholders made their frontmatter unparseable YAML (latent since before this migration).
  • Declared the carve-outs — see OKF for what sits outside the bundle and why. Raw layers keep their immutability; new source notes are born with frontmatter instead of being retro-edited.

[2026-09-10] lint | wiki-lint.py wired into the daily cron

  • The existing daily wiki-graph-refresh job now runs wiki-lint.py as well: silent when clean, exit 1 (alert) when the wiki has drifted.

[2026-09-10] compile | First workspace catalog

  • Created index — one line per note, grouped by purpose.
  • Created documents/index — sources vs derived notes.

[2026-09-08] ingest | Mailie weekly row

  • mealie-weekly cron row added (2 vegan recipes/week).

[2026-09-07] ingest | Mnemosyne backup

  • mnemosyne-backup cron at 03:10.

[2026-09-06] retire | Seasonal and audit reminders

  • Nine annual seasonal/audit items deleted at Joe’s request. The Oct–Dec checklist sections are intentionally empty.

[2026-09-03] ingest | HA backup to B2

  • ha-b2-backup cron added; the Google Drive addon path retired.

[2026-08-27] lint | Tracker schema

  • system-validate.py reported four schema/reminder issues; all fixed.