[2026-11-06] compile | Roster repair and a refresh: value the linter did not know
system-facts.md(migrated 5 Nov) declaredrefresh: on-change, which is outside the OKF vocabulary (hourly/daily/weekly/monthly/on-write), sowiki-lint.pytreated it as hand-authored and the root index read stale every morning. Changed toon-write, added the page to the root index under System & ops, and refreshed the compile date.wiki-lint.pynow 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
.mdfor both.anxiety-log-tracker.mdis now the sole anxiety record, using dated entries with the original words; the empty anxiety CSV was moved totrashed/.blood-pressure-tracker.mdis 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.csvas the sole BP record (seven pre-existing readings retained); added emptyhealth/anxiety-log.csv. Retired two prototype Markdown trackers containing fabricated example rows totrashed/, not into the real logs. health-capture.pyreads explicit Index Ring transcriptions from ringbearer’s existing archive, validates measurements, preserves verbatim anxiety notes, records UK capture times, and deduplicates replay. Theindex-health-capturescript-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, andTennis Log CouchDB sync. Secret values were read back only in-process and never printed. - Provisioned
fossil-log-syncandtennis-log-sync, each restricted to its own DB. Permission probes: own DB 200; other app DB and/_users403;/_all_dbs401. 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.envandcouchdb-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.pycontinue to use thewellness-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.pyto read/home/joe/.hermes/state/health-app-couchdb.json(mode 0600), require loopback URL +wellnessDB, and use the non-adminhealth-app-proxyaccount instead ofcouchdb-admin.txt. - Read-only validation: the exporter fetched 0 mood docs; account access statuses were
/wellness200,/fossil-log403,/tennis-log403,/_users403, and/_all_dbs401. No data or secret values were printed. Syntax check passed. The exportmain()was not run, so no health-data output file was changed. - Global admin remains valid;
docker inspectconfirmed the liveCOUCHDB_USER/COUCHDB_PASSWORDenvironment 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 commitff1d555ce5cd(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;.bakURLs are denied. Provisioned non-adminhealth-app-proxy, role-scoped towellness; 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,wellnessdoc_count 0. - A disposable CouchDB 3.5.3 + host-browser test verified target DB read/write/delete, denial to other DBs,
_usersand_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 base64user:passwordcopy. Browser localStorage, Git history, and archived backups were not included. - Vaultwarden supplied the saved
dash.cloudflare.comlogin; 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.xyzserved 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 on0.0.0.0:8654. CouchDB port 5984 is published on0.0.0.0and::; 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/cronrun 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 preservingPYTHONPATH,HERMES_RUNTIME_DIR, andHERMES_DISABLE_LAZY_INSTALLSin 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.pyandsystem-validate.pyexit 0.tracker-normalise.py --check --briefexits 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 foundsearxng,couchdb, andcloudflaredupdates; 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/data83.85%,vm-101-disk-190.70%,/data61 GiB free. The latest direct LVM read was 14:18; the 14:15 watcher run succeeded, with poolwarnretained until below 80%, guest LVok. - CouchDB update preflight: running 3.5.3;
_usersand_replicatorfiles 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.shcompatibility 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_videooutputs (18.89 GiB) whose originals were verified under/data/upload/. Preserved all originals, thumbnails, 730 Motion Photo source clips, unlinked-MP.mp4files, and one test transcode. - A representative HEVC output regenerated from its original via Immich’s per-asset
transcode-videojob while its DB row remained. The 471 removed outputs can be regenerated individually before playback; their originals remain present. - Verified:
pve/data90.51% → 83.86%,vm-101-disk-198.46% → 90.71%,/data264 GB used / 36 GB free → 240 GB / 61 GB free.immich_serverremains healthy. The finalfstrimreported 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.gzB2 object; exact read-back and 372-file scratch restore passed. The archive builder is now inline insystem-b2-backup.py. The temporary standalone helper was trashed aftersystem-validate.pycorrectly 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/cronrerun 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
.envfiles and two stale.env.bakfiles. Values were not read; all twelve paths are ignored and untracked, and their modes are now 0600. The encrypted archive excludes the stale.env.bakfiles,node_modules, anddist. - Added
stack-source-backup.pyand asource-codesource to the nightly system-B2 job. The one-object archiveimmich-crypt:system-backups/source-code/source-code-current.tar.gzis 3,224,244 bytes; streamed read-back SHA-256 matched. A scratch restore matched all 372 files and was trashed. RESTORE.mdnow 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.mdnow describes the live/home/joe/stackssource 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/**/.envignore rules to/home/joe/.gitignore; all 12 current.envpaths are ignored and none is in Git path history. Values were not read, staged, or committed. - Final checks:
system-validate.pyexit 0;tracker-normalise.py --checkexit 0 withtotal_issues=0(machine-owned preview suggestions remain excluded);wiki-lint.pyexit 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-appsinto 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.gzwas 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.gzcontainingstate.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, sowiki-graph-refreshflagged 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.mdandhandoff/standby-seed/STANDBY-BRIEFING-20261017.md. They were orphans with notype:frontmatter, sowiki-lintflagged 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 hereblock 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 fromgeneral-todo-tracker.mdon 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.mdLast compiled bumped to 12 Oct, whichwiki-lint.pyrequires 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 writesreminder-make-ice-cream.sh. Cronreminder-make-ice-creamcreated 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 8fires 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.mdOpen Tasks gains Return blind to Dunelm, datedDue 14 October 2026(Wed). The wording matters:parse_date_from_textneeds 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 againsttracker_libbefore committing: parses to 2026-10-14 anddeadline_markerreturns(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 --todowith its⏰ 11 Oct, 22:00marker. 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 hereblock ofshopping-register.md(voice note, no detail given). Not yet tidied into a section, which is the normal path for a dump line. index.mdLast 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 indocuments/principality-mortgage-completion-2026-07-30.mdis untouched.- Followed through:
index.mdtracker line updated, andinstinct-briefing.mdlost a stale “Cifas protective registration is outstanding” claim. - No cron or case-watch change. The
ipsportal 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.ukpasswords fromlife-admin-tracker.md§5. - Lake District removed entirely, including the trip row itself.
- Rail Ombudsman R175958 closed: LNER compensated Joe. The
railportal was removed fromcase-watch.pyand its state entry dropped, so it is no longer polled.fetch_railis 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_foswritten yet; flow steps 1-2 (email, password) verified read-only, step 3 (one-time passcode) not yet seen live. case-watch.shnow forwards its arguments to the Python script, so--dry-runand--portalactually 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
bwathttp://localhost:8383. The official CLI refuses any non-HTTPS URL in its fetch path:if (!request.url.startsWith("https://") && !platformUtilsService.isDev()) throw InsecureUrlNotAllowedError(), andisDev()compiles toreturn "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.localproxy 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
bwCLI 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 unlockin a real terminal, since pinentry needs a TTY. - Docs corrected:
browser-vault-capability.mdsections 4, 6 and 8, and theweb-automation-scriptsskill, 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. Thehansipie/hermes-vaultwardenplugin 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.
.envkeeps only what lets the system boot and talk.OPENCODE_GO_API_KEY(the model) andTELEGRAM_BOT_TOKEN(the channel) never move, because aBW_SESSIONtoken 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.commandenabled withcommand = vault-cred.py env Hermes,helper_timeout_seconds: 20(the 3s default is too tight forbw),override_existing: trueso the vault wins when reachable and.envcatches the fallback, meaning the migration needs no.envsurgery and no window where a key is absent. - Verified fail-soft: with the vault locked,
hermes statusprints one structured warning plus a remediation line and continues, exit 0. Startup is never blocked. The apply runs once per process (_APPLIED_HOMESinhermes_cli/env_loader.py), so it costs onebwcall per Hermes start rather than one per message turn. - Safety rail added:
vault-cred.py envrefuses to emitOPENCODE_GO_API_KEYorTELEGRAM_BOT_TOKENfrom the vault, so a mistyped field name can never override a bootstrap key underoverride_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
bwis 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 theHermesitem 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_persistencewasfalse, so every task got a randomuserIdand therefore a fresh, logged-out browser profile. The Camofox volume was already persistent, it was simply never reused. - Built:
bwCLI installed (npm install -g @bitwarden/cli, 2026.9.0) and pointed at the local Vaultwarden. New~/.hermes/scripts/vault-cred.pycredential 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_persistenceset totrue. Identity is now a deterministic profile-scopeduuid5, 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.bitwardenuses thebwsCLI against Bitwarden Secrets Manager, which Vaultwarden does not implement. Verified:POST /api/secretsandPOST /api/projectsboth 404. Upstream issue #33126 closednot-planned. Routes that do work: the built-insecrets.commandsource, or the unlicensed/unmaintainedhansipie/hermes-vaultwardenplugin. - Written up: browser-vault-capability.md, indexed, plus a
credentials section in the
web-automation-scriptsskill. - Blocked on Joe:
bw loginneeds his master password. Everything downstream waits on that.
[2026-09-29] migrate | skill split, finance refresh rescheduled
cron-script-watchdogssplit. 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 intoreferences/:pitfalls.md(22.6 KB),mnemosyne-ingestion-hygiene.md,freshness-vs-integrity.mdandreminders-json-pattern.md. SKILL.md is now 60,615 characters with 39 KB of headroom, each moved section replaced by a pointer. Backup atSKILL.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 * * *to40 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.csvISA/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.pyran at 18:20 andt212-true-return.pyat 18:25.tracker_lib.ha_get_entity_stateserves 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.pyfirst. 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-watchdogsgained 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-escalationremoved 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.pyruns clean, andsystem-validate.pyreports 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.pyis now capture-only: it still writesfinance/true-return-history.csvandstate/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.--verbosegives the read on demand.t212-record-watchis 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 ownoriginal_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_linkqualityon Microwave,bathroom_aqara_lux_linkqualityon Lounge Aqara Motion,smartthings_toilet_linkqualityon the Samjin leak sensor,washing_machine_leak_*on the Aqara leak sensor, plus 24mqtt_-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 thebinary_sensor.landing_motiongroup, 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 fromQUIET_DEVICESinzigbee-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_24hremoved (config id 1789323813937) at Joe’s request. It had been disabled since 17 Sep 2026 and was fully superseded by the dailyzigbee-health-watch.py, which builds its device list straight fromzigbee2mqtt/bridge/devicesand 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 inautomations.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 tonotify.joe. zigbee-health-watch.pywatchlist updated: Round/Square added asleakclass, the three dead keys removed.zigbee-budget-calibrate.pyre-run (44 → 43 budget entries).- Found:
automation.device_offline_alert_24h(disabled by Joe, superseded byzigbee-health-watch.py) still carried all three dead sensors in itswatched:list plus three non-existent*_last_seensensors in itssilent:list. Pruned on Joe’s instruction the same day: every one of its 44watchedentries and 3silententries was validated against the live state machine first, exactly 6 were dead and were removed (41 and 0 remain). Stilloff, 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) andinstinct-briefing.md(type: Reference) were written on 25 Sep without required OKF frontmatter, so the dailywiki-graph-refreshcron 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(commita61f9dd): 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-inboundon 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 cronsolar-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,titleanddescriptionfrontmatter to every in-bundle note in the workspace. Controlledtypevocabulary declared in KNOWLEDGE-GRAPH; enforced byscripts/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.pyandwiki-lint.pyto 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-refreshjob now runswiki-lint.pyas 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-weeklycron row added (2 vegan recipes/week).
[2026-09-07] ingest | Mnemosyne backup
mnemosyne-backupcron 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-backupcron added; the Google Drive addon path retired.
[2026-08-27] lint | Tracker schema
system-validate.pyreported four schema/reminder issues; all fixed.