From 59432cfbefe9293893bb742770f8bf420d8bdec2 Mon Sep 17 00:00:00 2001 From: Kyzcreig <9063726+Kyzcreig@users.noreply.github.com> Date: Fri, 25 Sep 2026 18:49:37 -0700 Subject: [PATCH 1/2] docs(audit): measure and rule the 24 UNRESOLVED rows (t_63023f77) Each row gets the measurement the lead lacked, then a README section 2 verdict: DROP 10, KEEP 8, UPSTREAM 4, SUPERSEDED-BY-UPSTREAM 2. 14 slice cards were filed for the rows whose verdict changed. Verified: probes on clean fork 858ee59722 and upstream c15ebb1435 worktrees with a temp home, read-only state.db/kanban.db queries, log greps, a live CDP repro on Chrome 154, and REST merge-queue job-log counts for #695. Narrow fork tests via test-gate: 175 passed. --- docs/plans/fork-pr-audit/UNRESOLVED.md | 85 ++++++ .../fork-pr-audit/unresolved.verdicts.json | 242 ++++++++++++++++++ 2 files changed, 327 insertions(+) create mode 100644 docs/plans/fork-pr-audit/UNRESOLVED.md create mode 100644 docs/plans/fork-pr-audit/unresolved.verdicts.json diff --git a/docs/plans/fork-pr-audit/UNRESOLVED.md b/docs/plans/fork-pr-audit/UNRESOLVED.md new file mode 100644 index 0000000000000..e3436d5f8af0a --- /dev/null +++ b/docs/plans/fork-pr-audit/UNRESOLVED.md @@ -0,0 +1,85 @@ +# UNRESOLVED rows: measured and ruled (t_63023f77) + +Follow-up to ROLLUP.md §9. The lead (t_03e35f0e) left 24 rows UNRESOLVED because the evidence the protocol needs (README §2) had not been measured. For each row this card took the missing measurement and then ruled with the same protocol. An unmeasured "still needed" is not a KEEP. Every KEEP below has a measured trigger or fire count. + +Trees: fork `origin/main` 858ee59722, upstream `upstream/main` c15ebb1435 (both fetched 2026-09-25). Probes ran against clean worktrees with a temp home. The live `~/.hermes` was only read (sqlite `mode=ro`, log greps, one read-only registry lint). Narrow fork tests went through `~/.hermes/scripts/test-gate` and ran remotely on ACE-MEDIA: 175 passed (kanban pin, live-board write guard, review parking, cli_hint, zero-byte db). + +## Tally + +- DROP: 10 +- KEEP: 8 +- SUPERSEDED-BY-UPSTREAM: 2 +- UPSTREAM: 4 + +14 slice cards were filed (one per changed row; #215+#441 share one because they revert as one unit, and nopr:08fc3aff65 (auto) rides nopr:8dcc69611c). Worker-created cards land in triage. + +| row | tranche | loc | verdict | card | measurement | upstream | +|---|---|---|---|---|---|---| +| #780 | scripts_misc | 1681 | **KEEP** | — | follows #764 (lead: KEEP, 2 bridge-side 'confab notice emitted out-of-band' across 20 bridges). Consumer-side count: messages.display_kind='confab_notice' = 2 rows in root state.db (2026-09-21 07:58 and 08:09 UTC, source cli), 0 in the other 13 profile DBs; 0 after #780 merged (09-21 18:02 UTC) over 58,652 later assistant rows. Low fire, but the rows exist and #780 is the P1 correctness fix to a KEPT consumer | n/a (fleet-only) | +| #356 | gateway | 285 | **SUPERSEDED-BY-UPSTREAM** | t_da2a5730 | live call on upstream c15ebb1435: gateway.run._prepare_resume_pending_message('restart','') returns a non-empty persist text (the recovery note), so the synthesized empty-text resume event (up gateway/run_startup.py:611) no longer yields an empty user row; real text still persisted clean | GREEN: fixed by upstream 0cc26777bb 'fix(gateway): persist resume recovery notes' + c69b6471e6 (#86580), consumed at up gateway/run_turn_runner.py:1647 | +| #907 | hermes_cli | 1149 | **KEEP** | — | trigger measured in the live board: incident card t_e1c2dae5 (2026-09-21 14:51, 'a probe that redirected HOME still wrote to the live board'), and kanban.db task_events shows 21 review_stale_alerted events on 17 cards dated 2026-09-22 UTC, the burst the PR describes. Repro on fork 858ee59722: test_diverged_pin_cannot_create_a_task_or_append_an_event and test_pin_contradicting_explicit_board_arg_REFUSES pass (part of a 175-test narrow run via test-gate on ACE-MEDIA) | RED by read: upstream reads the same board-path pin env but has no KanbanPinDivergenceError / refusal | +| #879 | hermes_cli | 225 | **KEEP** | — | trigger shape is live: 3 of 1,715 repository keys in live kanban.db task_workspace_survivors.bases contain whitespace (card t_f5ebd9db, keys ending '/fresh clone'); a bare `--survivor-pr =...` hint for those splits in the shell. Fork 858ee59722: tests/hermes_cli/test_cli_hint.py passes in the 175-test narrow run (test-gate, ACE-MEDIA) | n/a: survivor/hint path is fork-only (hint_arg, kanban_survivor absent upstream) | +| #858 | hermes_cli | 112 | **KEEP** | — | trigger measured: live card t_880f5d17 (2026-09-22 05:05, argus) records repair_db() PROCEEDING from a pytest context and leaving .init.lock + .bak beside a production board. Fork repro: tests/hermes_cli/test_kanban_live_board_write_guard.py (test_test_context_refused_on_live_board etc.) pass in the same 175-test narrow run | n/a: fork-only kanban path (fork kanban_db.py diverged from upstream split modules) | +| #798 | hermes_cli | 156 | **KEEP** | — | DB signal exists after all: task_events 'blocked' payload carries source_status. Since merge (2026-09-21 18:51 UTC): 76 blocks with source_status='review' on 68 cards, and 84 'unblocked' events restoring status='review' (85 with resume_status='review', 70 cards) | RED by read: up block_task UPDATE@hermes_cli/kanban_db.py:3287 still limits to status IN ('running','ready'), so a review card cannot be parked | +| #620 | hermes_cli | 58 | **DROP** | t_111298c6 | 0/34,120 sessions across 12 profile state.db (2026-05-08..2026-09-26) have model LIKE 'name:%'; 0 config.yaml/cron jobs use a name:/ string; 0 'could not verify name:' lines in ~/.hermes/logs + profiles/*/logs | RED by read: up validate_requested_model@hermes_cli/models_validate.py:780 has no name: strip (relocated from models.py) | +| #579 | hermes_cli | 493 | **KEEP** | — | trigger still occurs: 4 zero-byte .db decoys on the LIVE board tree right now (~/.hermes/kanban/kanban.db 09-22 01:01, boards/'*.db' 09-22 09:41, board.db 09-24 07:35, default.db 09-24 17:21; all created after #579 merged 08-11). Repro on a zero-byte kanban.db in a temp home: fork connect_readonly -> KanbanDbNotABoardError; upstream c15ebb1435 has no read-only reader and its connect() silently turns the decoy into a real empty board (0 tasks, 0 -> 4096 bytes); fork connect() does the same | RED (live call): up connect@hermes_cli/kanban_db_connect.py:668 initialises schema in a zero-byte file | +| #441 | hermes_cli | 474 | **DROP** | t_7c17872a | the gate logs INFO 'session_db_heavy_read queue_wait=... outcome=acquired' whenever a read waits >=1 ms and WARNING '... outcome=shed' on busy; the dashboard process's hermes_cli.* loggers demonstrably reach ~/.hermes/logs/agent.log (8 web_server + 153 dashboard_auth.registry lines, same process). Count: 0 'session_db_heavy_read' lines in root agent.log* (2026-09-25 04:05..18:11) and errors.log* (02:58..18:20), and 0 across every file under ~/.hermes/logs + profiles/*/logs. Not one heavy read queued even 1 ms in the window. (Live counter /api/status.session_db_heavy_reads is auth-gated; not read.) | RED by read (no admission gate; up offloads per router + bounds read connections in hermes_state_readpool.py) | +| #326 | hermes_cli | 44 | **UPSTREAM** | t_4bf92fd0 | live repro in temp HOME: seed auth.json xai-oauth.last_auth_error, call _save_xai_oauth_tokens(new tokens): upstream c15ebb1435 -> last_auth_error present=True (RED); fork 858ee59722 -> False. xai-oauth is live on root auth.json (last_refresh 2026-09-25T21:25Z), so the path runs in production | RED (live): up _save_xai_oauth_tokens@hermes_cli/auth_xai.py:127 does state.update(...) and never pops last_auth_error | +| #291 | hermes_cli | 111 | **SUPERSEDED-BY-UPSTREAM** | t_ee9353f2 | live call on upstream c15ebb1435 with a 4-profile temp home and list_profiles() wrapped with a call counter: web_routers.profiles._profile_targets (serves GET /api/profiles/sessions + /sidebar) and web_server_cron._cron_profile_dicts both return all 4 profiles with 0 list_profiles() calls | GREEN: upstream uses profiles_to_serve() (directory read) for both hot paths — de626c5a36 (_profile_targets) + 4590ef8b58 '#114041 polled profile lists never walk skill trees'; the auditor read of 'keeps full list_profiles()' was wrong | +| #215 | hermes_cli | 473 | **DROP** | t_7c17872a | the gate logs INFO 'session_db_heavy_read queue_wait=... outcome=acquired' whenever a read waits >=1 ms and WARNING '... outcome=shed' on busy; the dashboard process's hermes_cli.* loggers demonstrably reach ~/.hermes/logs/agent.log (8 web_server + 153 dashboard_auth.registry lines, same process). Count: 0 'session_db_heavy_read' lines in root agent.log* (2026-09-25 04:05..18:11) and errors.log* (02:58..18:20), and 0 across every file under ~/.hermes/logs + profiles/*/logs. Not one heavy read queued even 1 ms in the window. (Live counter /api/status.session_db_heavy_reads is auth-gated; not read.) | RED by read (no admission gate; up offloads per router + bounds read connections in hermes_state_readpool.py) | +| #166 | hermes_cli | 173 | **UPSTREAM** | t_db92203d | live trigger shape confirmed: ~/.hermes/plugins/model-providers/claude-apr fallback_models = 'claude-apr/' x5 (namespaced with own slug) while GET 127.0.0.1:18810/anthropic/v1/models returns bare ids. Real provider_model_ids() with that exact profile shape (stubbed fetch): upstream c15ebb1435 -> 10 rows (every model twice); fork -> 5 | RED (live call): up provider_model_ids@hermes_cli/models.py:1683 merges curated-namespaced + live-bare without canonicalizing | +| #135 | hermes_cli | 94 | **UPSTREAM** | t_bc006a95 | live trigger: root ~/.hermes/config.yaml model.default=claude-fable-5-1 (bare), provider claude-bpr whose plugin fallback_models are 'claude-bpr/'. Upstream _finalize_picker_rows(row models=['claude-bpr/claude-fable-5-1','claude-bpr/claude-opus-5-5'], current='claude-fable-5-1') -> ['claude-fable-5-1','claude-bpr/claude-fable-5-1',...] (current model listed twice) | RED (live call): up _finalize_picker_rows@hermes_cli/model_switch_providers.py:1280 uses plain `current_model not in models` | +| #33 | hermes_cli | 25 | **DROP** | t_d3825c87 | live CDP repro, Chrome 154.0.8037.57 headless on temp profiles: with upstream launch args (no --remote-allow-origins) the handshake WITHOUT an Origin header (websockets default = upstream tools/browser_cdp_tool.py:176) -> OK; only a handshake WITH an Origin header -> HTTP 403. The launcher that this PR edits logged 0 'browser debug launch: spawned' lines across ~/.hermes/logs + profiles/*/logs; 0 CDP 403/'Rejected an incoming WebSocket' lines | GREEN for upstream's own CDP client (no Origin sent); 403 only reproduces for Origin-sending clients | +| nopr:8a8b81638c | hermes_cli | 69 | **DROP** | t_08d470c3 | caller count: build_recap (the only public entry of hermes_cli/session_recap.py) has 0 non-test callers on fork 858ee59722 and 0 on upstream c15ebb1435 (grep over *.py/*.ts/*.tsx/*.yaml); the module is not imported by gateway/ or hermes_cli anywhere. The guard's code path cannot run | n/a: upstream session_recap.py:169 is equally unreached; upstream's extract_local_files@gateway/platforms/base.py:3283 is only called from gateway/kanban_watchers.py:162 | +| #18 | hermes_cli | 325 | **KEEP** | — | /compress usage counted from gateway logs: 19 "[Discord] slash '/compress' invoked" lines by Ace's user id in ~/.hermes/logs/gateway.log* (window 2026-09-20..09-25, ~6 days: 10 on 09-20, 4 on 09-21, 5 on 09-23) — each renders this readout | read-only: upstream /compress reply has no measured-context / full-request lines (readout keys fork-only) | +| #17 | hermes_cli | 183 | **KEEP** | — | /compress usage counted from gateway logs: 19 "[Discord] slash '/compress' invoked" lines by Ace's user id in ~/.hermes/logs/gateway.log* (window 2026-09-20..09-25, ~6 days: 10 on 09-20, 4 on 09-21, 5 on 09-23) — each renders this readout | read-only: upstream /compress reply has no measured-context / full-request lines (readout keys fork-only) | +| nopr:7c3d5cdd0f | scripts_misc | 121 | **DROP** | t_c4b3a02c | the in-repo diff is only C-2 (lint_provider_collisions + a doctor line; C-1 normalisation is not in this commit). Ran the fork lint against the LIVE plugin registry (real HOME, read-only): 0 collisions. 'Provider collision lint' warning lines: 0 across ~/.hermes/logs + profiles/*/logs. Callers: doctor.py:1928 only; no cron job or script runs it (the 'cli-parity sweep' caller named in the docstring does not exist in cron/jobs.json or ~/.hermes/scripts) | n/a (fleet copy-paste fN plugin hazard) | +| #894 | scripts_misc | 629 | **DROP** | t_ceb111f9 | trigger = a whitespace in the home / interpreter / skill-category / plugin path pasted into a hint. Measured on both fleet hosts: Mac Studio HOME=/Users/alexgierczyk, ACE-AI HOME=/home/ace; 0 of 18 profile dirs, 0 skill category dirs (skills, skills-shared, fork skills/ + optional-skills/), 0 venv interpreter paths contain whitespace. The printed hints round-trip unquoted here | not applicable as-is: routes through hint_value from fork-only #889 | +| #358 | gateway | 237 | **DROP** | t_28b13602 | 4020 rejections are not logged anywhere (0 hits for 4020 in dashboard.{out,err}.log, dashboard-auth.log); a count needs live instrumentation and ~/.hermes is read-only for this card. Substitute measurement of the path's traffic: root state.db has 8 source='tui' sessions out of 15,265 (all 2026-08-20; 2026-05-08..2026-09-26); 0 empty user rows ever from source tui. The triggering client (fork desktop reconnect loop) is retired by D9 (desktop upstream-owned since 2026-08-08) | RED by read: up prompt.submit@tui_gateway/methods_prompt.py:564 has no empty-text rejection (auditor's 'absorbed' premise falsified) | +| #695 | scripts_misc | 87 | **UPSTREAM** | t_c1c5035d | merge-queue counts via REST job logs (ANSI-stripped, grep 'FAIL ... virtualHistoryOffsetCache.test.ts'): BEFORE the fix (merge_group runs 2026-09-07..09-14 13:52, 201 runs) 10 'JS & TS checks' jobs failed on this exact file (09-07, 09-08, 09-09 x4, 09-10 x2, 09-14 x2). AFTER (2026-09-14 14:00..09-26, 2,297 runs, 120 failed runs) 0 failures of this file (1 JS&TS failure total, a different test) | RED by read: up ui-tui/src/__tests__/virtualHistoryOffsetCache.test.ts lacks the act()/IS_REACT_ACT_ENVIRONMENT settle | +| nopr:8dcc69611c | scripts_misc | 554 | **DROP** | t_cad124fd | G1/G2 refusals return to the model as skill_manage tool results. Across all 14 profile state.db since the commit (2026-07-08..2026-09-26): 3,130 skill_manage tool results, 0 containing 'Refusing to author', 'reserved work-queue-note prefix' or 'Post-write placement check failed' (the 5 LIKE hits are all read_file/terminal/execute_code rows showing the SOURCE template '{name}', incl. this audit). scripts/local-skill-leak-check.py has 0 callers (launchd ai.hermes.skill-collision-check runs the skills-shared copy) | n/a (fleet-only skills-shared layout) | +| nopr:08fc3aff65 | auto | 109 | **DROP** | t_cad124fd | auto (tests-only): tests/test_skill_hygiene_guards_a3.py tests nopr:8dcc69611c; follows it | n/a | + +## Rationale per row + +- **#780** (fix(confab-notice): close the 5 FleetReview P1s on the OOB notice consumer (#764 merged mid-run) (#780)): KEEP. reverting #780 alone would leave #764's consumer with 5 known P1 defects (dropped/duplicated/false notices). Its fate is #764's: if #764 is later dropped, drop both +- **#356** (fix(resume): don't persist an empty user row on auto-resume (the /undo '(no text)' bug) (#356)): SUPERSEDED-BY-UPSTREAM. upstream solved the same empty-row bug (persists the note instead of dropping the row); fork flag+drop machinery is redundant +- **#907** (fix(kanban): refuse destructive writes when pin contradicts requested board (t_e1c2dae5) (#907)): KEEP. guard on a destructive path with two live-board incidents in the last 5 days; silent by design (a refusal raises to the caller) +- **#879** (fix(cli): a printed --flag hint must parse as pasted, for any repo key or path (#879)): KEEP. rare but real; 225 loc, 0 conflicting files; operator-facing remedy that otherwise cannot be pasted +- **#858** (fix(kanban): gate repair_db(), the second rw door to the live board (t_880f5d17) (#858)): KEEP. 112 loc closing a measured second rw door to the live board +- **#798** (fix(kanban): park a review card — block accepts review, unblock restores it, triage-resolve names the verb (#798)): KEEP. fires ~19x/day on the live board; fork review lane depends on it +- **#620** (fix(models): strip relay routing prefix before model-listing lookup (#620)): DROP. warning-text fix for a model-string form nobody uses; never fires +- **#579** (fix(kanban): zero-byte board .db files must not exist, and must not read as empty boards (#579)): KEEP. decoys are being created weekly; the reader half is what stops a script reading them as an empty board. Follow-up: something other than connect() still creates the stubs (a literal '*.db' = an unexpanded shell glob), so the source half does not cover all creators +- **#441** (feat(tui_gateway): bound heavy session reads (#441)): DROP. never contends under fleet load; 473/474 loc across 5 files that conflict in all 3 syncs. #441 is a re-land of #215: revert as one unit incl. the tui_gateway/ws.py import +- **#326** (fix(auth): clear stale xai-oauth last_auth_error on successful token save (#326)): UPSTREAM. generic 1-line fix, reproduced on upstream, provider in live use; effect is a stale error record that misleads triage (07-14 incident) +- **#291** (perf(dashboard): lightweight profile listing for session-list + cron aggregators (10s -> ~1s) (#291)): SUPERSEDED-BY-UPSTREAM. upstream solved the same 10s sidebar cost the same way +- **#215** (feat(tui_gateway): bound heavy session reads (#215)): DROP. never contends under fleet load; 473/474 loc across 5 files that conflict in all 3 syncs. #441 is a re-land of #215: revert as one unit incl. the tui_gateway/ws.py import +- **#166** (fix(models): dedup namespaced curated vs bare live ids in provider_model_ids merge (#166)): UPSTREAM. duplicate picker rows reproduce on upstream with the fleet's live claude-apr catalog shape; fix is generic (own-slug prefix only) +- **#135** (fix(model-picker): don't duplicate the current model when its catalog entry is namespaced (#135)): UPSTREAM. Apollo's own root config hits it every /model picker open; generic namespace-aware presence check +- **#33** (fix(browser): add --remote-allow-origins to CDP debug launch for Chrome 111+ (#33)): DROP. launcher never used in window; upstream client unaffected; --remote-allow-origins=* also widens the debug port to any web origin +- **nopr:8a8b81638c** (fix(recap): backtick-wrap Files touched paths to stop gateway auto-attach): DROP. guard on a dead module; fires 0 by construction +- **#18** (fix(compress): two-line readout — Chat size + Full request size (#18)): KEEP. the operator reads it ~3x/day; #18 layers on #17, keep as one unit. 508 loc over 15 locale files is the cost; candidate for upstreaming as one PR later +- **#17** (fix(compress): show real measured context alongside estimate; estimate over full transcript (#17)): KEEP. the operator reads it ~3x/day; #18 layers on #17, keep as one unit. 508 loc over 15 locale files is the cost; candidate for upstreaming as one PR later +- **nopr:7c3d5cdd0f** (feat(telemetry): fN alias collision lint + stop composite model strings leaking to charts (A4 axis C)): DROP. never fired, verified clean at landing and now; 121 loc in providers/__init__.py which diverges +184/-346 from upstream +- **#894** (fix(cli): the six residual pasteable-hint sites must survive a real shell (#894)): DROP. never fires on this fleet; 629 loc incl. gateway/run.py (conflicts in all 3 syncs). If wanted for Windows users, upstream #889+#894 together as one PR +- **#358** (fix(gateway): reject empty prompt.submit + skip no-op model-switch side effects (#358)): DROP. guard's path carries ~no traffic here and the client that looped is gone; not worth 237 loc on 2 files that conflict every sync. Reopen if a desktop empty-submit loop recurs +- **#695** (test(tui): settle deferred rows before unmount compensation checks (#695)): UPSTREAM. the flake was real (5% of merge-queue runs over a week) and the fix removed it; test-only, generic. Branch already built: audit/scripts_misc/upstream-695 @ 830f68a5dc (hand port off upstream/main) +- **nopr:8dcc69611c** (feat(skills): write-time skill-hygiene guard (A3)): DROP. 0 fires in ~80 days / 3,130 writes; the repo script is dead. 554 loc in tools/skill_manager_tool.py + agent/skill_utils.py (2 files, conflict in 2 syncs) +- **nopr:08fc3aff65** (fix(test): rewrite A3 guard test as a proper pytest module (was a script → CI collection abort)): DROP. rides the nopr:8dcc69611c revert + +## Corrections to earlier audit claims + +- #358: the auditor ruled SUPERSEDED on the claim that upstream rejects empty text. That is false. `prompt.submit` has no empty-text check (methods_prompt.py:564). The lead had already caught this. +- #291: "upstream keeps full list_profiles()" is wrong. Both hot paths use `profiles_to_serve()` (de626c5a36, 4590ef8b58), confirmed with a live call-counter probe. +- #356: upstream already fixed the empty-row bug by persisting the recovery note (0cc26777bb, c69b6471e6/#86580). +- nopr:8a8b81638c: `build_recap` has no production caller on either tree, so its auto-attach question is moot. +- #798: this row is not silent. `blocked` events carry `source_status`, and `unblocked` events carry `status`/`resume_status`. +- #33: modern Chrome (154) still accepts a CDP handshake that sends no Origin header. The 403 only happens when the client sends an Origin. + +## Findings outside these rows + +- The live board tree has 4 zero-byte `.db` decoys right now: `~/.hermes/kanban/kanban.db` (09-22), `boards/*.db` with a literal `*` in the name, i.e. an unexpanded shell glob (09-22), `board.db` (09-24) and `default.db` (09-24). Something other than kanban_db.connect() is creating them, so #579's source half does not cover every creator. No card was filed for this: it is not an audit row. +- `hermes_cli/session_recap.py` has no production caller on either tree. The whole module is dead code. +- Log windows are short. Root `agent.log*` rotation keeps about 14 h (2026-09-25 04:05..18:11) and `gateway.log*` covers 2026-09-20..09-25. Where a row relies on these, the window is stated in its measurement. + +Machine-readable: `unresolved.verdicts.json` (same schema keys as the tranche files plus `card`). diff --git a/docs/plans/fork-pr-audit/unresolved.verdicts.json b/docs/plans/fork-pr-audit/unresolved.verdicts.json new file mode 100644 index 0000000000000..d4841d9a44dd5 --- /dev/null +++ b/docs/plans/fork-pr-audit/unresolved.verdicts.json @@ -0,0 +1,242 @@ +{ + "#620": { + "verdict": "DROP", + "measure": "0/34,120 sessions across 12 profile state.db (2026-05-08..2026-09-26) have model LIKE 'name:%'; 0 config.yaml/cron jobs use a name:/ string; 0 'could not verify name:' lines in ~/.hermes/logs + profiles/*/logs", + "upstream": "RED by read: up validate_requested_model@hermes_cli/models_validate.py:780 has no name: strip (relocated from models.py)", + "why": "warning-text fix for a model-string form nobody uses; never fires", + "card": "t_111298c6", + "tranche": "hermes_cli", + "subject": "fix(models): strip relay routing prefix before model-listing lookup (#620)", + "loc": 58 + }, + "#326": { + "verdict": "UPSTREAM", + "measure": "live repro in temp HOME: seed auth.json xai-oauth.last_auth_error, call _save_xai_oauth_tokens(new tokens): upstream c15ebb1435 -> last_auth_error present=True (RED); fork 858ee59722 -> False. xai-oauth is live on root auth.json (last_refresh 2026-09-25T21:25Z), so the path runs in production", + "upstream": "RED (live): up _save_xai_oauth_tokens@hermes_cli/auth_xai.py:127 does state.update(...) and never pops last_auth_error", + "why": "generic 1-line fix, reproduced on upstream, provider in live use; effect is a stale error record that misleads triage (07-14 incident)", + "card": "t_4bf92fd0", + "tranche": "hermes_cli", + "subject": "fix(auth): clear stale xai-oauth last_auth_error on successful token save (#326)", + "loc": 44 + }, + "#166": { + "verdict": "UPSTREAM", + "measure": "live trigger shape confirmed: ~/.hermes/plugins/model-providers/claude-apr fallback_models = 'claude-apr/' x5 (namespaced with own slug) while GET 127.0.0.1:18810/anthropic/v1/models returns bare ids. Real provider_model_ids() with that exact profile shape (stubbed fetch): upstream c15ebb1435 -> 10 rows (every model twice); fork -> 5", + "upstream": "RED (live call): up provider_model_ids@hermes_cli/models.py:1683 merges curated-namespaced + live-bare without canonicalizing", + "why": "duplicate picker rows reproduce on upstream with the fleet's live claude-apr catalog shape; fix is generic (own-slug prefix only)", + "card": "t_db92203d", + "tranche": "hermes_cli", + "subject": "fix(models): dedup namespaced curated vs bare live ids in provider_model_ids merge (#166)", + "loc": 173 + }, + "#135": { + "verdict": "UPSTREAM", + "measure": "live trigger: root ~/.hermes/config.yaml model.default=claude-fable-5-1 (bare), provider claude-bpr whose plugin fallback_models are 'claude-bpr/'. Upstream _finalize_picker_rows(row models=['claude-bpr/claude-fable-5-1','claude-bpr/claude-opus-5-5'], current='claude-fable-5-1') -> ['claude-fable-5-1','claude-bpr/claude-fable-5-1',...] (current model listed twice)", + "upstream": "RED (live call): up _finalize_picker_rows@hermes_cli/model_switch_providers.py:1280 uses plain `current_model not in models`", + "why": "Apollo's own root config hits it every /model picker open; generic namespace-aware presence check", + "card": "t_bc006a95", + "tranche": "hermes_cli", + "subject": "fix(model-picker): don't duplicate the current model when its catalog entry is namespaced (#135)", + "loc": 94 + }, + "#33": { + "verdict": "DROP", + "measure": "live CDP repro, Chrome 154.0.8037.57 headless on temp profiles: with upstream launch args (no --remote-allow-origins) the handshake WITHOUT an Origin header (websockets default = upstream tools/browser_cdp_tool.py:176) -> OK; only a handshake WITH an Origin header -> HTTP 403. The launcher that this PR edits logged 0 'browser debug launch: spawned' lines across ~/.hermes/logs + profiles/*/logs; 0 CDP 403/'Rejected an incoming WebSocket' lines", + "upstream": "GREEN for upstream's own CDP client (no Origin sent); 403 only reproduces for Origin-sending clients", + "why": "launcher never used in window; upstream client unaffected; --remote-allow-origins=* also widens the debug port to any web origin", + "card": "t_d3825c87", + "tranche": "hermes_cli", + "subject": "fix(browser): add --remote-allow-origins to CDP debug launch for Chrome 111+ (#33)", + "loc": 25 + }, + "#356": { + "verdict": "SUPERSEDED-BY-UPSTREAM", + "measure": "live call on upstream c15ebb1435: gateway.run._prepare_resume_pending_message('restart','') returns a non-empty persist text (the recovery note), so the synthesized empty-text resume event (up gateway/run_startup.py:611) no longer yields an empty user row; real text still persisted clean", + "upstream": "GREEN: fixed by upstream 0cc26777bb 'fix(gateway): persist resume recovery notes' + c69b6471e6 (#86580), consumed at up gateway/run_turn_runner.py:1647", + "why": "upstream solved the same empty-row bug (persists the note instead of dropping the row); fork flag+drop machinery is redundant", + "card": "t_da2a5730", + "tranche": "gateway", + "subject": "fix(resume): don't persist an empty user row on auto-resume (the /undo '(no text)' bug) (#356)", + "loc": 285 + }, + "#358": { + "verdict": "DROP", + "measure": "4020 rejections are not logged anywhere (0 hits for 4020 in dashboard.{out,err}.log, dashboard-auth.log); a count needs live instrumentation and ~/.hermes is read-only for this card. Substitute measurement of the path's traffic: root state.db has 8 source='tui' sessions out of 15,265 (all 2026-08-20; 2026-05-08..2026-09-26); 0 empty user rows ever from source tui. The triggering client (fork desktop reconnect loop) is retired by D9 (desktop upstream-owned since 2026-08-08)", + "upstream": "RED by read: up prompt.submit@tui_gateway/methods_prompt.py:564 has no empty-text rejection (auditor's 'absorbed' premise falsified)", + "why": "guard's path carries ~no traffic here and the client that looped is gone; not worth 237 loc on 2 files that conflict every sync. Reopen if a desktop empty-submit loop recurs", + "card": "t_28b13602", + "tranche": "gateway", + "subject": "fix(gateway): reject empty prompt.submit + skip no-op model-switch side effects (#358)", + "loc": 237 + }, + "#291": { + "verdict": "SUPERSEDED-BY-UPSTREAM", + "measure": "live call on upstream c15ebb1435 with a 4-profile temp home and list_profiles() wrapped with a call counter: web_routers.profiles._profile_targets (serves GET /api/profiles/sessions + /sidebar) and web_server_cron._cron_profile_dicts both return all 4 profiles with 0 list_profiles() calls", + "upstream": "GREEN: upstream uses profiles_to_serve() (directory read) for both hot paths \u2014 de626c5a36 (_profile_targets) + 4590ef8b58 '#114041 polled profile lists never walk skill trees'; the auditor read of 'keeps full list_profiles()' was wrong", + "why": "upstream solved the same 10s sidebar cost the same way", + "card": "t_ee9353f2", + "tranche": "hermes_cli", + "subject": "perf(dashboard): lightweight profile listing for session-list + cron aggregators (10s -> ~1s) (#291)", + "loc": 111 + }, + "#441": { + "verdict": "DROP", + "measure": "the gate logs INFO 'session_db_heavy_read queue_wait=... outcome=acquired' whenever a read waits >=1 ms and WARNING '... outcome=shed' on busy; the dashboard process's hermes_cli.* loggers demonstrably reach ~/.hermes/logs/agent.log (8 web_server + 153 dashboard_auth.registry lines, same process). Count: 0 'session_db_heavy_read' lines in root agent.log* (2026-09-25 04:05..18:11) and errors.log* (02:58..18:20), and 0 across every file under ~/.hermes/logs + profiles/*/logs. Not one heavy read queued even 1 ms in the window. (Live counter /api/status.session_db_heavy_reads is auth-gated; not read.)", + "upstream": "RED by read (no admission gate; up offloads per router + bounds read connections in hermes_state_readpool.py)", + "why": "never contends under fleet load; 473/474 loc across 5 files that conflict in all 3 syncs. #441 is a re-land of #215: revert as one unit incl. the tui_gateway/ws.py import", + "card": "t_7c17872a", + "tranche": "hermes_cli", + "subject": "feat(tui_gateway): bound heavy session reads (#441)", + "loc": 474 + }, + "#215": { + "verdict": "DROP", + "measure": "the gate logs INFO 'session_db_heavy_read queue_wait=... outcome=acquired' whenever a read waits >=1 ms and WARNING '... outcome=shed' on busy; the dashboard process's hermes_cli.* loggers demonstrably reach ~/.hermes/logs/agent.log (8 web_server + 153 dashboard_auth.registry lines, same process). Count: 0 'session_db_heavy_read' lines in root agent.log* (2026-09-25 04:05..18:11) and errors.log* (02:58..18:20), and 0 across every file under ~/.hermes/logs + profiles/*/logs. Not one heavy read queued even 1 ms in the window. (Live counter /api/status.session_db_heavy_reads is auth-gated; not read.)", + "upstream": "RED by read (no admission gate; up offloads per router + bounds read connections in hermes_state_readpool.py)", + "why": "never contends under fleet load; 473/474 loc across 5 files that conflict in all 3 syncs. #441 is a re-land of #215: revert as one unit incl. the tui_gateway/ws.py import", + "card": "t_7c17872a", + "tranche": "hermes_cli", + "subject": "feat(tui_gateway): bound heavy session reads (#215)", + "loc": 473 + }, + "nopr:8a8b81638c": { + "verdict": "DROP", + "measure": "caller count: build_recap (the only public entry of hermes_cli/session_recap.py) has 0 non-test callers on fork 858ee59722 and 0 on upstream c15ebb1435 (grep over *.py/*.ts/*.tsx/*.yaml); the module is not imported by gateway/ or hermes_cli anywhere. The guard's code path cannot run", + "upstream": "n/a: upstream session_recap.py:169 is equally unreached; upstream's extract_local_files@gateway/platforms/base.py:3283 is only called from gateway/kanban_watchers.py:162", + "why": "guard on a dead module; fires 0 by construction", + "card": "t_08d470c3", + "tranche": "hermes_cli", + "subject": "fix(recap): backtick-wrap Files touched paths to stop gateway auto-attach", + "loc": 69 + }, + "#579": { + "verdict": "KEEP", + "measure": "trigger still occurs: 4 zero-byte .db decoys on the LIVE board tree right now (~/.hermes/kanban/kanban.db 09-22 01:01, boards/'*.db' 09-22 09:41, board.db 09-24 07:35, default.db 09-24 17:21; all created after #579 merged 08-11). Repro on a zero-byte kanban.db in a temp home: fork connect_readonly -> KanbanDbNotABoardError; upstream c15ebb1435 has no read-only reader and its connect() silently turns the decoy into a real empty board (0 tasks, 0 -> 4096 bytes); fork connect() does the same", + "upstream": "RED (live call): up connect@hermes_cli/kanban_db_connect.py:668 initialises schema in a zero-byte file", + "why": "decoys are being created weekly; the reader half is what stops a script reading them as an empty board. Follow-up: something other than connect() still creates the stubs (a literal '*.db' = an unexpanded shell glob), so the source half does not cover all creators", + "card": null, + "tranche": "hermes_cli", + "subject": "fix(kanban): zero-byte board .db files must not exist, and must not read as empty boards (#579)", + "loc": 493 + }, + "#879": { + "verdict": "KEEP", + "measure": "trigger shape is live: 3 of 1,715 repository keys in live kanban.db task_workspace_survivors.bases contain whitespace (card t_f5ebd9db, keys ending '/fresh clone'); a bare `--survivor-pr =...` hint for those splits in the shell. Fork 858ee59722: tests/hermes_cli/test_cli_hint.py passes in the 175-test narrow run (test-gate, ACE-MEDIA)", + "upstream": "n/a: survivor/hint path is fork-only (hint_arg, kanban_survivor absent upstream)", + "why": "rare but real; 225 loc, 0 conflicting files; operator-facing remedy that otherwise cannot be pasted", + "card": null, + "tranche": "hermes_cli", + "subject": "fix(cli): a printed --flag hint must parse as pasted, for any repo key or path (#879)", + "loc": 225 + }, + "#18": { + "verdict": "KEEP", + "measure": "/compress usage counted from gateway logs: 19 \"[Discord] slash '/compress' invoked\" lines by Ace's user id in ~/.hermes/logs/gateway.log* (window 2026-09-20..09-25, ~6 days: 10 on 09-20, 4 on 09-21, 5 on 09-23) \u2014 each renders this readout", + "upstream": "read-only: upstream /compress reply has no measured-context / full-request lines (readout keys fork-only)", + "why": "the operator reads it ~3x/day; #18 layers on #17, keep as one unit. 508 loc over 15 locale files is the cost; candidate for upstreaming as one PR later", + "card": null, + "tranche": "hermes_cli", + "subject": "fix(compress): two-line readout \u2014 Chat size + Full request size (#18)", + "loc": 325 + }, + "#17": { + "verdict": "KEEP", + "measure": "/compress usage counted from gateway logs: 19 \"[Discord] slash '/compress' invoked\" lines by Ace's user id in ~/.hermes/logs/gateway.log* (window 2026-09-20..09-25, ~6 days: 10 on 09-20, 4 on 09-21, 5 on 09-23) \u2014 each renders this readout", + "upstream": "read-only: upstream /compress reply has no measured-context / full-request lines (readout keys fork-only)", + "why": "the operator reads it ~3x/day; #18 layers on #17, keep as one unit. 508 loc over 15 locale files is the cost; candidate for upstreaming as one PR later", + "card": null, + "tranche": "hermes_cli", + "subject": "fix(compress): show real measured context alongside estimate; estimate over full transcript (#17)", + "loc": 183 + }, + "#894": { + "verdict": "DROP", + "measure": "trigger = a whitespace in the home / interpreter / skill-category / plugin path pasted into a hint. Measured on both fleet hosts: Mac Studio HOME=/Users/alexgierczyk, ACE-AI HOME=/home/ace; 0 of 18 profile dirs, 0 skill category dirs (skills, skills-shared, fork skills/ + optional-skills/), 0 venv interpreter paths contain whitespace. The printed hints round-trip unquoted here", + "upstream": "not applicable as-is: routes through hint_value from fork-only #889", + "why": "never fires on this fleet; 629 loc incl. gateway/run.py (conflicts in all 3 syncs). If wanted for Windows users, upstream #889+#894 together as one PR", + "card": "t_ceb111f9", + "tranche": "scripts_misc", + "subject": "fix(cli): the six residual pasteable-hint sites must survive a real shell (#894)", + "loc": 629 + }, + "#780": { + "verdict": "KEEP", + "measure": "follows #764 (lead: KEEP, 2 bridge-side 'confab notice emitted out-of-band' across 20 bridges). Consumer-side count: messages.display_kind='confab_notice' = 2 rows in root state.db (2026-09-21 07:58 and 08:09 UTC, source cli), 0 in the other 13 profile DBs; 0 after #780 merged (09-21 18:02 UTC) over 58,652 later assistant rows. Low fire, but the rows exist and #780 is the P1 correctness fix to a KEPT consumer", + "upstream": "n/a (fleet-only)", + "why": "reverting #780 alone would leave #764's consumer with 5 known P1 defects (dropped/duplicated/false notices). Its fate is #764's: if #764 is later dropped, drop both", + "card": null, + "tranche": "scripts_misc", + "subject": "fix(confab-notice): close the 5 FleetReview P1s on the OOB notice consumer (#764 merged mid-run) (#780)", + "loc": 1681 + }, + "nopr:7c3d5cdd0f": { + "verdict": "DROP", + "measure": "the in-repo diff is only C-2 (lint_provider_collisions + a doctor line; C-1 normalisation is not in this commit). Ran the fork lint against the LIVE plugin registry (real HOME, read-only): 0 collisions. 'Provider collision lint' warning lines: 0 across ~/.hermes/logs + profiles/*/logs. Callers: doctor.py:1928 only; no cron job or script runs it (the 'cli-parity sweep' caller named in the docstring does not exist in cron/jobs.json or ~/.hermes/scripts)", + "upstream": "n/a (fleet copy-paste fN plugin hazard)", + "why": "never fired, verified clean at landing and now; 121 loc in providers/__init__.py which diverges +184/-346 from upstream", + "card": "t_c4b3a02c", + "tranche": "scripts_misc", + "subject": "feat(telemetry): fN alias collision lint + stop composite model strings leaking to charts (A4 axis C)", + "loc": 121 + }, + "nopr:8dcc69611c": { + "verdict": "DROP", + "measure": "G1/G2 refusals return to the model as skill_manage tool results. Across all 14 profile state.db since the commit (2026-07-08..2026-09-26): 3,130 skill_manage tool results, 0 containing 'Refusing to author', 'reserved work-queue-note prefix' or 'Post-write placement check failed' (the 5 LIKE hits are all read_file/terminal/execute_code rows showing the SOURCE template '{name}', incl. this audit). scripts/local-skill-leak-check.py has 0 callers (launchd ai.hermes.skill-collision-check runs the skills-shared copy)", + "upstream": "n/a (fleet-only skills-shared layout)", + "why": "0 fires in ~80 days / 3,130 writes; the repo script is dead. 554 loc in tools/skill_manager_tool.py + agent/skill_utils.py (2 files, conflict in 2 syncs)", + "card": "t_cad124fd", + "tranche": "scripts_misc", + "subject": "feat(skills): write-time skill-hygiene guard (A3)", + "loc": 554 + }, + "nopr:08fc3aff65": { + "verdict": "DROP", + "measure": "auto (tests-only): tests/test_skill_hygiene_guards_a3.py tests nopr:8dcc69611c; follows it", + "upstream": "n/a", + "why": "rides the nopr:8dcc69611c revert", + "card": "t_cad124fd", + "tranche": "auto", + "subject": "fix(test): rewrite A3 guard test as a proper pytest module (was a script \u2192 CI collection abort)", + "loc": 109 + }, + "#695": { + "verdict": "UPSTREAM", + "measure": "merge-queue counts via REST job logs (ANSI-stripped, grep 'FAIL ... virtualHistoryOffsetCache.test.ts'): BEFORE the fix (merge_group runs 2026-09-07..09-14 13:52, 201 runs) 10 'JS & TS checks' jobs failed on this exact file (09-07, 09-08, 09-09 x4, 09-10 x2, 09-14 x2). AFTER (2026-09-14 14:00..09-26, 2,297 runs, 120 failed runs) 0 failures of this file (1 JS&TS failure total, a different test)", + "upstream": "RED by read: up ui-tui/src/__tests__/virtualHistoryOffsetCache.test.ts lacks the act()/IS_REACT_ACT_ENVIRONMENT settle", + "why": "the flake was real (5% of merge-queue runs over a week) and the fix removed it; test-only, generic. Branch already built: audit/scripts_misc/upstream-695 @ 830f68a5dc (hand port off upstream/main)", + "card": "t_c1c5035d", + "tranche": "scripts_misc", + "subject": "test(tui): settle deferred rows before unmount compensation checks (#695)", + "loc": 87 + }, + "#907": { + "verdict": "KEEP", + "measure": "trigger measured in the live board: incident card t_e1c2dae5 (2026-09-21 14:51, 'a probe that redirected HOME still wrote to the live board'), and kanban.db task_events shows 21 review_stale_alerted events on 17 cards dated 2026-09-22 UTC, the burst the PR describes. Repro on fork 858ee59722: test_diverged_pin_cannot_create_a_task_or_append_an_event and test_pin_contradicting_explicit_board_arg_REFUSES pass (part of a 175-test narrow run via test-gate on ACE-MEDIA)", + "upstream": "RED by read: upstream reads the same board-path pin env but has no KanbanPinDivergenceError / refusal", + "why": "guard on a destructive path with two live-board incidents in the last 5 days; silent by design (a refusal raises to the caller)", + "card": null, + "tranche": "hermes_cli", + "subject": "fix(kanban): refuse destructive writes when pin contradicts requested board (t_e1c2dae5) (#907)", + "loc": 1149 + }, + "#858": { + "verdict": "KEEP", + "measure": "trigger measured: live card t_880f5d17 (2026-09-22 05:05, argus) records repair_db() PROCEEDING from a pytest context and leaving .init.lock + .bak beside a production board. Fork repro: tests/hermes_cli/test_kanban_live_board_write_guard.py (test_test_context_refused_on_live_board etc.) pass in the same 175-test narrow run", + "upstream": "n/a: fork-only kanban path (fork kanban_db.py diverged from upstream split modules)", + "why": "112 loc closing a measured second rw door to the live board", + "card": null, + "tranche": "hermes_cli", + "subject": "fix(kanban): gate repair_db(), the second rw door to the live board (t_880f5d17) (#858)", + "loc": 112 + }, + "#798": { + "verdict": "KEEP", + "measure": "DB signal exists after all: task_events 'blocked' payload carries source_status. Since merge (2026-09-21 18:51 UTC): 76 blocks with source_status='review' on 68 cards, and 84 'unblocked' events restoring status='review' (85 with resume_status='review', 70 cards)", + "upstream": "RED by read: up block_task UPDATE@hermes_cli/kanban_db.py:3287 still limits to status IN ('running','ready'), so a review card cannot be parked", + "why": "fires ~19x/day on the live board; fork review lane depends on it", + "card": null, + "tranche": "hermes_cli", + "subject": "fix(kanban): park a review card \u2014 block accepts review, unblock restores it, triage-resolve names the verb (#798)", + "loc": 156 + } +} \ No newline at end of file From d1f7df94b06c720de21985cd3138a9c6316dcb6c Mon Sep 17 00:00:00 2001 From: "ang-fleet-workers[bot]" <333956806+ang-fleet-workers[bot]@users.noreply.github.com> Date: Sun, 27 Sep 2026 07:11:00 -0700 Subject: [PATCH 2/2] docs(audit): fold the 24 t_63023f77 UNRESOLVED rulings into rows.json + FINAL.md (t_12b66b00) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1186 (cherry-picked here) added the rulings as UNRESOLVED.md + unresolved.verdicts.json but left lead/rows.json, FINAL.md and ROLLUP.md reading UNRESOLVED for all 24 rows. - rulings.UNRESOLVED_RULINGS loads unresolved.verdicts.json (single source). - final.build() applies it to the 23 code rows, asserting each was UNRESOLVED; the auto row nopr:08fc3aff65 follows nopr:8dcc69611c to DROP in build_all.py. - build_all.py fails if any of the 24 rows ends with a verdict other than its ruling. - render.py: card column takes the ruling cards (and the POST_RULING_2026_09_27 cards that left cards.json in #1296, so those 8 rows keep their committed ids); hand-written tail sections are kept on re-render; the §6 UPSTREAM count and UNRESOLVED sentence are computed; §3 lists the 4 new UPSTREAM rows. - FINAL.md / ROLLUP.md: re-rendered, plus a hand-written fold note. Counts: KEEP 672->680, DROP 153->163, UPSTREAM 93->97, SUPERSEDED-BY-UPSTREAM 174->176, UNRESOLVED 24->0. Verified: build_all.py (t_03e35f0e lead/merged.json) + render.py; rows.json diff touches exactly the 24 ruling keys, fields final+why only; each of the 24 FINAL.md rows' FINAL column starts with its ruling verdict. Rerunning build_all + render reproduces the committed files byte-exact (rows.json sha256 579de903...). Baseline check: the new render.py on HEAD rows.json reproduced HEAD ROLLUP.md exactly and changed FINAL.md only in the card column of the 16 rulings that have a card. ruff clean; gitleaks --no-git findings unchanged vs HEAD (6, pre-existing). --- docs/plans/fork-pr-audit/FINAL.md | 69 ++++++++++------ docs/plans/fork-pr-audit/ROLLUP.md | 33 +++++--- docs/plans/fork-pr-audit/lead/build_all.py | 2 + docs/plans/fork-pr-audit/lead/final.py | 6 ++ docs/plans/fork-pr-audit/lead/render.py | 26 +++++- docs/plans/fork-pr-audit/lead/rows.json | 96 +++++++++++----------- docs/plans/fork-pr-audit/lead/rulings.py | 16 ++++ 7 files changed, 157 insertions(+), 91 deletions(-) diff --git a/docs/plans/fork-pr-audit/FINAL.md b/docs/plans/fork-pr-audit/FINAL.md index 567d29b8794e2..a5d7200dafad8 100644 --- a/docs/plans/fork-pr-audit/FINAL.md +++ b/docs/plans/fork-pr-audit/FINAL.md @@ -112,7 +112,7 @@ Inputs: auditor branches `audit//verdicts` (latest, incl. post-adversar | nopr:2f530dd026 | gateway | runtime-footer provider_model, context_full, reasoning fields — orig: commit body | RED-partial (read-only: upstream runtime_footer.py has reasoning but not the fork's provider_model/context_full/msgs fields; _split_provide… | none | n/a | 299 loc, 3 cf (3) | KEEP | keep-unproven | KEEP — LEAD: footer family; registry entry 22 (upstream PR #80661 outstanding) + display.runtime_footer enabled with fork fields provider_model/reasoning/context_full (~/.hermes/config.yaml:342-350) -> renders on every reply. | — | — | | nopr:11f8a67f01 | gateway | runtime-footer msgs field (raw count vs hygiene hard-limit) — orig: commit body | RED (_resolve_footer_message_stats 0 hits) | none | n/a | 294 loc, 2 cf (3) | KEEP | keep-unproven | KEEP — LEAD: footer family; registry entry 22 (upstream PR #80661 outstanding) + display.runtime_footer enabled with fork fields provider_model/reasoning/context_full (~/.hermes/config.yaml:342-350) -> renders on every reply. | — | — | | nopr:0da8ba5356 | gateway | boot-resume recovery turns interrupted by busy-input — orig: commit body | n/a (fork boot-resume) | n/a | n/a | 289 loc, 1 cf (3) | KEEP | keep-unproven | KEEP — LEAD: KEEP-by-unit: follows a measured restart/resume head (#295 1,190 / #289 1,410 / #761 183 / #790 151 / #904 455 / #937 132 fires). | — | — | -| #356 | gateway | empty user row persisted on auto-resume (the /undo '(no text)' bug) — orig: PR body | read-only: upstream synthesizes an empty-text internal event for resume (run_startup.py:611) and its own transcript path; the /undo '(no te… | n/a | n/a | 285 loc, 5 cf (3) | DROP | needs-lead → lead: test before revert | UNRESOLVED — LEAD: removed from the #49 revert unit. The empty-resume guard is not undo-only: upstream synthesizes an empty-text internal resume event (up gateway/run_startup.py:611) with no empty-row guard. Needs a repro on upstream before either DROP or UPSTREAM; unmeas… | audit/gateway/revert-49-undo | — | +| #356 | gateway | empty user row persisted on auto-resume (the /undo '(no text)' bug) — orig: PR body | read-only: upstream synthesizes an empty-text internal event for resume (run_startup.py:611) and its own transcript path; the /undo '(no te… | n/a | n/a | 285 loc, 5 cf (3) | DROP | needs-lead → lead: test before revert | SUPERSEDED-BY-UPSTREAM — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): upstream solved the same empty-row bug (persists the note instead of dropping the row); fork flag+drop machinery is redundant. Measured: live call on upstream c15ebb1435: gateway.run._prepare_resum… | audit/gateway/revert-49-undo | t_da2a5730 | | #396 | gateway | a message queued during startup-restore got no ack — orig: PR body (re-land of #258) | RED (read-only: _queue_startup_restore_event@up/gateway/run_startup.py:79 logs only; no ack send) | none | 0 (2 literals = failure paths) | 269 loc, 1 cf (3) | KEEP | keep-unproven | DROP — LEAD: KEEP-UNPROVEN with no positive fire count, no executed upstream RED, no live knob/consumer/registry entry found in the row evidence -> DROP per rule (unmeasured "still needed" is not a KEEP). | — | t_c79aa544 | | #306 | gateway | stale resume markers never cleared — orig: PR body | GREEN-equivalent (read-only: upstream _resume_pending_candidates freshness window @run_startup.py:588-590 skips stale markers; clear_resume… | upstream freshness window (_auto_continue_freshness_window) | n/a | 264 loc, 4 cf (3) | SUPERSEDED-BY-UPSTREAM | stands | SUPERSEDED-BY-UPSTREAM | — | — | | #906 | gateway | restart-loop ledger recorded one entry per resume scan instead of per process boot — orig: PR body | RED (read-only: upstream restart_loop_guard.check_and_record is called per _resume_pending_candidates call — run_startup.py:554 — i.e. per … | none | n/a | 263 loc, 3 cf (3) | KEEP | keep-unproven | DROP — LEAD: KEEP-UNPROVEN with no positive fire count, no executed upstream RED, no live knob/consumer/registry entry found in the row evidence -> DROP per rule (unmeasured "still needed" is not a KEEP). | — | t_4a15d14c | @@ -122,7 +122,7 @@ Inputs: auditor branches `audit//verdicts` (latest, incl. post-adversar | #173 | gateway | granular CompactionStats breakdown for manual /compress — orig: PR body | RED (agent/compaction_stats.py fork-only) | none | n/a | 250 loc, 2 cf (3) | KEEP | keep-unproven | KEEP — LEAD: compaction-announce family; registry entry 3 + announce_on_hygiene: true (config.yaml:215); "Context compacted" 217 fires (nopr:2870fd4994). | — | — | | #333 | gateway | /footer command on desktop turns — orig: PR body | RED for the tui_gateway/server.py half (_turn_runtime_footer 0 hits upstream) | none | n/a | 243 loc, 1 cf (3) | KEEP | keep-unproven | KEEP — LEAD: footer family; registry entry 22 (upstream PR #80661 outstanding) + display.runtime_footer enabled with fork fields provider_model/reasoning/context_full (~/.hermes/config.yaml:342-350) -> renders on every reply. | — | — | | #140 | gateway | a wedged worker stranded the gateway after graceful shutdown — orig: PR body | GREEN-partial (read-only: upstream run_shutdown.py:2118-2120 os._exit past drain+grace) — but _executor_drain_timeout/_force_process_exit 0… | upstream shutdown watchdog os._exit (run_shutdown.py:2118) | 0 (2 literals) | 238 loc, 2 cf (3) | KEEP | keep-unproven | KEEP — LEAD: drain-budget unit (#738 SUPERSEDED, #861 UPSTREAM). KEEP until the unit converges onto #861; teardown timings are in gateway-exit-diag.log (23 drain/budget lines), not the audited logs. | — | — | -| #358 | gateway | tui_gateway accepted an empty prompt.submit and ran model-switch side effects on a no-op switch — orig: PR body | read-only: absorb 56% of src lines already in up/tui_gateway (prompt.submit contract models reject undeclared/empty params via extra=forbid… | upstream tui_gateway contracts refactor (dispatcher param v… | n/a (no literal) | 237 loc, 2 cf (3) | SUPERSEDED-BY-UPSTREAM | needs-lead → lead: upstream claim falsified | UNRESOLVED — LEAD: auditor premise falsified (upstream prompt.submit has no empty-text check); fork guard fires unmeasured. Needs a count of 4020 rejections before KEEP/UPSTREAM. | — | — | +| #358 | gateway | tui_gateway accepted an empty prompt.submit and ran model-switch side effects on a no-op switch — orig: PR body | read-only: absorb 56% of src lines already in up/tui_gateway (prompt.submit contract models reject undeclared/empty params via extra=forbid… | upstream tui_gateway contracts refactor (dispatcher param v… | n/a (no literal) | 237 loc, 2 cf (3) | SUPERSEDED-BY-UPSTREAM | needs-lead → lead: upstream claim falsified | DROP — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): guard's path carries ~no traffic here and the client that looped is gone; not worth 237 loc on 2 files that conflict every sync. Reopen if a desktop empty-submit loop recurs. Measured: 4020 rejecti… | — | t_28b13602 | | #184 | gateway | stale-code /model guard fired on non-runtime file changes — orig: PR body | RED (read-only: _is_runtime_python/_runtime_python_changed 0 hits in up/gateway/code_skew.py) | none | n/a | 234 loc, 1 cf (1) | KEEP | keep-unproven | DROP — LEAD: KEEP-UNPROVEN with no positive fire count, no executed upstream RED, no live knob/consumer/registry entry found in the row evidence -> DROP per rule (unmeasured "still needed" is not a KEEP). | — | t_828999bf | | nopr:ba371ef32f | gateway | free-response Discord channels could not quote bot mentions — orig: commit body | read-only: not located upstream (absorb 10%) | none found | n/a | 233 loc, 1 cf (2) | KEEP | keep-unproven | DROP — LEAD: KEEP-UNPROVEN with no positive fire count, no executed upstream RED, no live knob/consumer/registry entry found in the row evidence -> DROP per rule (unmeasured "still needed" is not a KEEP). | — | t_586f8488 | | #403 | gateway | footer ignored per-model reasoning_overrides and fallback per-entry effort — orig: PR body | RED (_footer_reasoning_label 0 hits upstream) | none | n/a | 223 loc, 2 cf (3) | KEEP | keep-unproven | KEEP — LEAD: footer family; registry entry 22 (upstream PR #80661 outstanding) + display.runtime_footer enabled with fork fields provider_model/reasoning/context_full (~/.hermes/config.yaml:342-350) -> renders on every reply. | — | — | @@ -449,7 +449,7 @@ Inputs: auditor branches `audit//verdicts` (latest, incl. post-adversar | #921 | hermes_cli | fix(kanban): fail closed on unprovable worker liveness (#921) — Fail closed on host-local Kanban reclaims when worker PID is missing or a signalled worker survives. A `reclaim_refused` event leaves t… | read-only: _abort_lost_claim_spawn absent from up/hermes_cli/kanban*, _claim_still_held absent from up/hermes_cli/kanban*, _prior_worker_st… | none | kanban.db events reclaim_refused 98 (2026-09-24..2026-09-24); DB/config: 98 reclaim_refused events [window: log files 2026-05-10..2026-09-2… | 1246 loc, 4 cf (2) | KEEP | — | KEEP | — | — | | #848 | hermes_cli | fix(kanban): bind an explicit survivor claim to the card that names it (#848) — `_verified_explicit()` called `verify_pr()` / `verify_ref()` with `mined_for` UNSET, so the one check that ties a PR to… | read-only: RemoteUnavailable absent from up/hermes_cli/kanban*, _claimant absent from up/hermes_cli/kanban*, _reusable absent from up/herme… | none | log literals 'https://github.com/' 696, 'survivor_unavailable:' 578, 'operator override' 297 (315 files); DB/config: 1,375 task_workspace_s… | 1170 loc, 2 cf (2) | KEEP | — | KEEP | — | — | | #655 | hermes_cli | fix(kanban): preserve quota exits and defer exhausted providers (#655) — Fixes: provider quota walls being counted as Kanban worker crashes and tripping the task-failure circuit breaker. Incident mea… | read-only: WorkerExit absent from up/hermes_cli/kanban*, _classify_run_exit absent from up/hermes_cli/kanban*, _counter_repair absent from … | none | kanban.db events deferred 630 (2026-09-24..2026-09-25); DB/config: 630 deferred events; 4,338 rate_limited runs [window: log files 2026-05-… | 1165 loc, 6 cf (2) | KEEP | — | KEEP | — | — | -| #907 | hermes_cli | fix(kanban): refuse destructive writes when pin contradicts requested board (t_e1c2dae5) (#907) — --- /verdicts` (latest, incl. post-adversar | #1057 | hermes_cli | fix(kanban pr-gate): MERGED is not DEPLOYED — hold deploy-premised cards until the merge is in the live tree (t_289e8020) (#1057) — **Bug:** gate_auto_resolved unblocked t_2789cbae on `ANG-Ventures/h… | read-only: _awaiting_deploy_sentence absent from up/hermes_cli/kanban*, _deploy_checks absent from up/hermes_cli/kanban*, _deploy_marker ab… | none | log literals '~/.hermes/runtime/hermes-agent' 83, 'MERGED, awaiting deploy of' 1 (29 files); DB/config: 'MERGED, awaiting deploy of' 1 line… | 512 loc, 0 cf (0) | KEEP | — | KEEP | — | — | | #313 | hermes_cli | fix(plugins): isolate background-review tool whitelist per-task (threading.local -> ContextVar) (#313) — Foreground (and cron) turns intermittently get tools denied with: ``` Background review denied… | GREEN by read: _review_tool_whitelist@agent/background_review.py:1078 is DISPATCH-side and per review agent; upstream has no threading.loca… | c408601937 | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 512 loc, 1 cf (1) | SUPERSEDED-BY-UPSTREAM | needs-lead → KEEP (standalone); SUPERSEDED-BY-UPSTRE… | KEEP — LEAD: fork agent/background_review.py:1590 arms the ContextVar whitelist this row added; standalone revert re-opens the leak. Converges only with a background_review take-upstream. | — | — | | #158 | hermes_cli | feat(desktop): backend runs from runtime deploy tree by default (+ config override) (#158) — Route the desktop app's Python backend at the **runtime deploy tree** (`~/.hermes/runtime/hermes-agent` + … | read-only: upstream web_server emits no HERMES_DASHBOARD_READY; the only readers are apps/desktop/electron (fork and upstream) | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 507 loc, 1 cf (3) | DROP | overturned → SUPERSEDED-BY-UPSTREAM | SUPERSEDED-BY-UPSTREAM — adversary overturned: ../up/hermes_cli/web_server.py:1355 _write_dashboard_ready_file(actual_port); :1359 ready_token HERMES_DASHBOARD_READY; reader ../up/apps/desktop/electron/backend-ready.ts:145 (D9); no non-desktop reader in fork, ~/.hermes/scripts, launc… | — | — | -| #579 | hermes_cli | fix(kanban): zero-byte board .db files must not exist, and must not read as empty boards (#579) — A zero-byte `.db` is worse than a missing one. `sqlite3.connect()` on an **existing** zero-byte file … | RED by read: _validate_sqlite_header@hermes_cli/kanban_db_connect.py:264 lets zero-byte files pass | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 493 loc, 2 cf (2) | UNRESOLVED | — | UNRESOLVED | — | — | +| #579 | hermes_cli | fix(kanban): zero-byte board .db files must not exist, and must not read as empty boards (#579) — A zero-byte `.db` is worse than a missing one. `sqlite3.connect()` on an **existing** zero-byte file … | RED by read: _validate_sqlite_header@hermes_cli/kanban_db_connect.py:264 lets zero-byte files pass | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 493 loc, 2 cf (2) | UNRESOLVED | — | KEEP — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): decoys are being created weekly; the reader half is what stops a script reading them as an empty board. Follow-up: something other than connect() still creates the stubs (a literal '*.db' = an unex… | — | — | | #948 | hermes_cli | fix(kanban): gc uses one cached machine-wide cwd scan, not per-path lsof +D (t_ee808d83) (#948) — **Change**: `_process_cwd_within` ran `lsof -a -d cwd -Fn +D ` once per candidate whenever a live… | read-only: _gc_remove_workspaces absent from up/hermes_cli/kanban*, _kernel_path absent from up/hermes_cli/kanban*, _loose_spelling absent … | none | log literals '/System/Volumes/Data' 761 (10 files) [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-2… | 489 loc, 2 cf (2) | KEEP | — | KEEP | — | — | | #359 | hermes_cli | fix(reasoning): align non-desktop max parity (#359) — Hermes's central reasoning parser already accepts `max`, but several generic non-Desktop consumers still stopped at `xhigh` or older subsets. Thi… | GREEN by read: VALID_REASONING_EFFORTS = (..., 'xhigh', 'max', 'ultra')@hermes_constants.py:991 is the single parser upstream | none (upstream consumers read the constant) | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 478 loc, 14 cf (3) | SUPERSEDED-BY-UPSTREAM | stands | SUPERSEDED-BY-UPSTREAM | — | — | -| #441 | hermes_cli | feat(tui_gateway): bound heavy session reads (#441) — A single Discord voice message posted its 🎙️ transcript echo **twice** — two identical messages 4.3s apart. The gateway log confirms the same `.o… | read-only: same as #215 | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 474 loc, 5 cf (3) | UNRESOLVED | — | UNRESOLVED | — | — | -| #215 | hermes_cli | feat(tui_gateway): bound heavy session reads (#215) — Phase 2 of docs/desktop/2026-07-05-session-list-loop-starvation-PRD.md (D-4/D-5/INV-4/INV-6). Shared per-loop async SessionDB heavy-read gate (de… | read-only: upstream has no SessionDB heavy-read gate; upstream offloads per router (web_routers/sessions.py) | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 473 loc, 5 cf (3) | UNRESOLVED | — | UNRESOLVED | — | — | +| #441 | hermes_cli | feat(tui_gateway): bound heavy session reads (#441) — A single Discord voice message posted its 🎙️ transcript echo **twice** — two identical messages 4.3s apart. The gateway log confirms the same `.o… | read-only: same as #215 | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 474 loc, 5 cf (3) | UNRESOLVED | — | DROP — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): never contends under fleet load; 473/474 loc across 5 files that conflict in all 3 syncs. #441 is a re-land of #215: revert as one unit incl. the tui_gateway/ws.py import. Measured: the gate logs I… | — | t_7c17872a | +| #215 | hermes_cli | feat(tui_gateway): bound heavy session reads (#215) — Phase 2 of docs/desktop/2026-07-05-session-list-loop-starvation-PRD.md (D-4/D-5/INV-4/INV-6). Shared per-loop async SessionDB heavy-read gate (de… | read-only: upstream has no SessionDB heavy-read gate; upstream offloads per router (web_routers/sessions.py) | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 473 loc, 5 cf (3) | UNRESOLVED | — | DROP — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): never contends under fleet load; 473/474 loc across 5 files that conflict in all 3 syncs. #441 is a re-land of #215: revert as one unit incl. the tui_gateway/ws.py import. Measured: the gate logs I… | — | t_7c17872a | | #682 | hermes_cli | fix(gateway): canonicalize BOTH sides of the routing-lane comparison (#682) — A `kanban_notify_subs` row spelled `chat_type='channel'` for a Discord **guild** chat could never match its own chat's li… | read-only: _effective_lane absent from up/hermes_cli/kanban* | none | DB/config: notify subs chat_type group/thread/dm live [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-0… | 460 loc, 2 cf (2) | KEEP | — | KEEP | — | — | | #771 | hermes_cli | fix(kanban): run worker gateways at background CPU priority (#771) — A kanban worker is batch work. It shares a host with RESIDENT gateways that must answer a human in real time, and nothing stopped … | read-only: _apply_background_priority absent from up/hermes_cli/kanban*, _build_worker_priority_preexec absent from up/hermes_cli/kanban*, … | none | log literals 'PHASE=worker_spawn task=' 2163, '; dumping all thread stacks and exiting ' 29, 'so the service supervisor can restart it' 29 … | 457 loc, 2 cf (2) | KEEP | — | KEEP | — | — | | #960 | hermes_cli | fix(kanban): judge goal deliverables before completion; fail open for operator errors (#960) — --- "` — so you can see where the … | read-only: /undo is fork-only (hermes_undo.py absent upstream) | none | log literals 'Triggering message id' 68 (4 files) [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29… | 115 loc, 2 cf (2) | KEEP | — | KEEP | — | — | -| #858 | hermes_cli | fix(kanban): gate repair_db(), the second rw door to the live board (t_880f5d17) (#858) — #853 put `_assert_live_board_write_allowed()` at `connect()`. `connect()` is not the only read-write door to … | read-only: fork-only kanban path (fork kanban_db.py diverged from upstream's split modules) | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 112 loc, 1 cf (2) | UNRESOLVED | — | UNRESOLVED | — | — | -| #291 | hermes_cli | perf(dashboard): lightweight profile listing for session-list + cron aggregators (10s -> ~1s) (#291) — `/api/profiles/sessions?profile=all` (fired on every desktop sidebar refresh) takes **10–12s** w… | read-only: upstream web_routers/profiles.py offloads (13 sites) but keeps full list_profiles() | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 111 loc, 2 cf (3) | UNRESOLVED | — | UNRESOLVED | — | — | +| #858 | hermes_cli | fix(kanban): gate repair_db(), the second rw door to the live board (t_880f5d17) (#858) — #853 put `_assert_live_board_write_allowed()` at `connect()`. `connect()` is not the only read-write door to … | read-only: fork-only kanban path (fork kanban_db.py diverged from upstream's split modules) | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 112 loc, 1 cf (2) | UNRESOLVED | — | KEEP — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): 112 loc closing a measured second rw door to the live board. Measured: trigger measured: live card t_880f5d17 (2026-09-22 05:05, argus) records repair_db() PROCEEDING from a pytest context and leav… | — | — | +| #291 | hermes_cli | perf(dashboard): lightweight profile listing for session-list + cron aggregators (10s -> ~1s) (#291) — `/api/profiles/sessions?profile=all` (fired on every desktop sidebar refresh) takes **10–12s** w… | read-only: upstream web_routers/profiles.py offloads (13 sites) but keeps full list_profiles() | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 111 loc, 2 cf (3) | UNRESOLVED | — | SUPERSEDED-BY-UPSTREAM — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): upstream solved the same 10s sidebar cost the same way. Measured: live call on upstream c15ebb1435 with a 4-profile temp home and list_profiles() wrapped with a call counter: web_routers.profiles._… | — | t_ee9353f2 | | #654 | hermes_cli | fix(kanban): respawn guard honors ALL operator requeue verbs, not three of five (#654) — active_pr guard, but hard-listed only changes_requested / unblocked / review_reopened. Two operator verbs were… | read-only: fork-only kanban path (fork kanban_db.py diverged from upstream's split modules) | none | kanban.db events reopened 165 (2026-08-14..2026-09-25), triage_resolved 569 (2026-08-08..2026-09-25); DB/config: 569 triage_resolved + 165 … | 108 loc, 2 cf (2) | KEEP | — | KEEP | — | — | | #519 | hermes_cli | fix(kanban): ignore provenance links in triage stranding reports (#519) — - restrict `find_stranded_by_triage()` to normalized blocking links - restrict `block_task()` escalation events to children l… | read-only: fork-only kanban path (fork kanban_db.py diverged from upstream's split modules) | none | DB/config: 693 derived-from links (provenance links excluded from stranding) [window: log files 2026-05-10..2026-09-25 (1741 files; agent/g… | 103 loc, 1 cf (2) | KEEP | — | KEEP | — | — | | #391 | hermes_cli | fix(packaging): declare hermes_state_ext + hermes_undo in py-modules (#391) — The running gateway survived only because it runs with cwd on `sys.path`. Any neutral-cwd import — e.g. `find_spec("gatew… | GREEN by read: pyproject.toml:796 'py-modules list here drifted ... Do not add the list back' — upstream derives root py-modules from the t… | 283c4f058c | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 103 loc, 1 cf (2) | SUPERSEDED-BY-UPSTREAM | stands | SUPERSEDED-BY-UPSTREAM | — | — | | #760 | hermes_cli | fix(backup): exclude cache/forensic-*/ and cache/**/*.db from the backup walk (#760) — The FULL tier walks every `*.db` under `$HERMES_HOME` and restore-verifies each one. `cache/forensic-native-snap… | read-only: upstream backup.py has no forensic-cache exclusion | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 97 loc, 2 cf (2) | DROP | stands | DROP | audit/hermes_cli/revert-760 | t_1dcb5196 | | nopr:8f7823887e | hermes_cli | fix(models): name provider + endpoint in anthropic_messages fallback warning + fix provider_label shadowing — Manual re-apply of a02a28941 onto v0.15.1 (validate_requested_model moved to ~3343). Impr… | read-only: upstream validate path has no 'could not verify' wording | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 97 loc, 1 cf (2) | DROP | stands | DROP | — | t_ebe23da0 | -| #135 | hermes_cli | fix(model-picker): don't duplicate the current model when its catalog entry is namespaced (#135) — The `/model` picker (Discord dropdown, screenshot from Ace) listed **`claude-opus-4-8` twice** when … | read-only: no _same_model upstream | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 94 loc, 1 cf (2) | UNRESOLVED | — | UNRESOLVED | — | — | +| #135 | hermes_cli | fix(model-picker): don't duplicate the current model when its catalog entry is namespaced (#135) — The `/model` picker (Discord dropdown, screenshot from Ace) listed **`claude-opus-4-8` twice** when … | read-only: no _same_model upstream | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 94 loc, 1 cf (2) | UNRESOLVED | — | UPSTREAM — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): Apollo's own root config hits it every /model picker open; generic namespace-aware presence check. Measured: live trigger: root ~/.hermes/config.yaml model.default=claude-fable-5-1 (bare), provider… | — | t_bc006a95 | | #239 | hermes_cli | fix(picker): match RENAMED failover-lane slugs (claude-apx-N / claude-bpx-N) (#239) — PR #233 hid the `/model` picker failover lanes with `^claude-(?:api-proxy/bridge)-f\d+$` — but the fleet's failov… | read-only: upstream picker unfiltered | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 92 loc, 1 cf (2) | KEEP | — | KEEP | — | — | | #492 | hermes_cli | fix(models): dots->hyphens only for Anthropic MODELS, not Anthropic-wire relays (#492) — `normalize_model_for_provider()` applies Anthropic's **dots→hyphens** rewrite based purely on the **provider**… | RED by read: provider-only dots->hyphens@hermes_cli/model_normalize.py:225 | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 91 loc, 0 cf (0) | KEEP | keep-unproven | DROP — LEAD: no fleet Anthropic-wire model id contains a dot; guarded rewrite has no live input. | — | t_b7301b61 | | #637 | hermes_cli | fix(auth): defer hermes_cli.config import until after PROVIDER_REGISTRY exists (#637) — Fix: one choke-point import move (below the registry definitions) instead of touching all 37 provider plugins. … | GREEN by read: `from hermes_cli.config import ( # noqa: E402`@hermes_cli/auth.py:259 (import deferred below PROVIDER_REGISTRY) | 30b0958a69 | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 87 loc, 1 cf (2) | SUPERSEDED-BY-UPSTREAM | stands | SUPERSEDED-BY-UPSTREAM | — | — | @@ -591,22 +591,22 @@ Inputs: auditor branches `audit//verdicts` (latest, incl. post-adversar | nopr:a184062e7b | hermes_cli | fix(banner): include local HEAD in update-check cache key — An in-place `git pull`/rebase moves HEAD without changing VERSION or HERMES_REVISION (both None for source installs tracking a fork). The u… | read-only: upstream banner cache keyed without local HEAD | none | log literals 'commits behind' 1111 (51 files) [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..20… | 77 loc, 2 cf (2) | KEEP | — | KEEP | — | — | | nopr:d68a4b0fcc | hermes_cli | fix(model-switch): resolve provider-module plugin providers in switch path — resolve_provider_full() (the /model switch + --provider resolver) consulted only the models.dev catalog, Hermes overlays, … | GREEN by read: _plugin_profile_pdef@hermes_cli/providers.py:222 resolves registered provider-plugin profiles in the switch path | none found by -S (behavior present by read) | log literals 'Unknown provider' 47 (47 files) [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..20… | 72 loc, 1 cf (2) | SUPERSEDED-BY-UPSTREAM | stands | SUPERSEDED-BY-UPSTREAM | — | — | | nopr:7cb39d1319 | hermes_cli | fix(backup): exclude browser-automation profiles + bulk media from full backup — The full `hermes backup` hung AND ballooned on a live fleet host: 1. HANG: it called the SQLite safe-copy on ~/.hermes… | read-only: no chrome-* exclusion upstream | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 70 loc, 1 cf (2) | KEEP | — | KEEP | — | — | -| nopr:8a8b81638c | hermes_cli | fix(recap): backtick-wrap Files touched paths to stop gateway auto-attach — The /status session recap lists touched file paths via _shortened_path, which emits a bare absolute or ~-relative path for … | read-only: upstream session_recap.py:169 joins bare paths, but upstream gateway/ has no bare-path auto-attach detector (rg 'extract_local_m… | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 69 loc, 0 cf (0) | UNRESOLVED | — | UNRESOLVED | — | — | +| nopr:8a8b81638c | hermes_cli | fix(recap): backtick-wrap Files touched paths to stop gateway auto-attach — The /status session recap lists touched file paths via _shortened_path, which emits a bare absolute or ~-relative path for … | read-only: upstream session_recap.py:169 joins bare paths, but upstream gateway/ has no bare-path auto-attach detector (rg 'extract_local_m… | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 69 loc, 0 cf (0) | UNRESOLVED | — | DROP — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): guard on a dead module; fires 0 by construction. Measured: caller count: build_recap (the only public entry of hermes_cli/session_recap.py) has 0 non-test callers on fork 858ee59722 and 0 on upstre… | — | t_08d470c3 | | #856 | hermes_cli | fix(kanban): log git's returncode and stderr when survivor capture fails (#856) — NOT a survivor defect, and NOT a RAM-disk / racy-clean-index race. Root cause is outside this repo: `~/.hermes/fleet/… | read-only: fork-only kanban path (fork kanban_db.py diverged from upstream's split modules) | none | DB/config: 1,375 task_workspace_survivors rows [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2… | 67 loc, 0 cf (0) | KEEP | — | KEEP | — | — | | #781 | hermes_cli | fix(kanban): require an open parent for dependency auto-resume (#781) — Only take the todo/auto-resume dependency path when an unfinished blocking parent exists. Otherwise retain kind=dependency but … | read-only: fork-only kanban path (fork kanban_db.py diverged from upstream's split modules) | none | DB/config: 74 dependency_wait events [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25)… | 65 loc, 1 cf (2) | KEEP | — | KEEP | — | — | -| #620 | hermes_cli | fix(models): strip relay routing prefix before model-listing lookup (#620) — A relay/pool provider takes a model string shaped `name:/` — the `name:/` prefix pins **which upstream… | not probed | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 58 loc, 2 cf (2) | UNRESOLVED | — | UNRESOLVED | — | — | +| #620 | hermes_cli | fix(models): strip relay routing prefix before model-listing lookup (#620) — A relay/pool provider takes a model string shaped `name:/` — the `name:/` prefix pins **which upstream… | not probed | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 58 loc, 2 cf (2) | UNRESOLVED | — | DROP — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): warning-text fix for a model-string form nobody uses; never fires. Measured: 0/34,120 sessions across 12 profile state.db (2026-05-08..2026-09-26) have model LIKE 'name:%'; 0 config.yaml/cron jobs … | — | t_111298c6 | | #490 | hermes_cli | fix(kanban-diagnostics): a clean block breaks the crash streak (recovered card stops paging CRITICAL) (#490) — `_rule_repeated_crashes` counts a trailing run of `crashed` outcomes, and only `complete… | read-only: fork-only kanban path (fork kanban_db.py diverged from upstream's split modules) | none | DB/config: 707 crashed runs across boards [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-0… | 54 loc, 0 cf (0) | KEEP | — | KEEP | — | — | | #35 | hermes_cli | fix(kanban): make --initial-status blocked sticky against recompute_ready (#35) — `hermes kanban create --initial-status blocked` is documented as the way to park a card for human ops (the R3 gate), … | GREEN by read: initial_status=='blocked'@hermes_cli/kanban_db_graph.py:45 + _has_sticky_block@kanban_db.py:2076 (blocked-at-creation event … | 34120a0ae2 | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 53 loc, 2 cf (2) | SUPERSEDED-BY-UPSTREAM | stands | SUPERSEDED-BY-UPSTREAM | — | — | | #794 | hermes_cli | fix(kanban): survivor baseline skips unreadable dirs instead of killing the dispatch tick (#794) — workspace rooted at a real home contains dirs the gateway user cannot read (2026-09-20: ~/.hermes/va… | read-only: fork-only kanban path (fork kanban_db.py diverged from upstream's split modules) | none | DB/config: 1,375 task_workspace_survivors rows [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2… | 52 loc, 0 cf (0) | KEEP | — | KEEP | — | — | | nopr:aa0f725eb8 | hermes_cli | fix(browser): suppress macOS "Keychain Not Found" modal on debug-Chrome auto-launch — The native browser-toolset auto-launch (`/browser connect` → try_launch_chrome_debug, and the macOS `open -a "Goo… | RED by read: debug launch args@hermes_cli/browser_connect.py:765 lack --use-mock-keychain | none | log literals '--password-store=basic' 2 (3 files) [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29… | 48 loc, 0 cf (0) | KEEP | — | KEEP | — | — | -| #326 | hermes_cli | fix(auth): clear stale xai-oauth last_auth_error on successful token save (#326) — **Observed 2026-07-14:** a stale 07-13 `invalid_grant` ("Refresh token has been revoked") record was still sitting i… | read-only: upstream writes last_auth_error@hermes_cli/auth_xai.py:401; clear-on-success not verified | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 44 loc, 1 cf (2) | UNRESOLVED | — | UNRESOLVED | — | — | +| #326 | hermes_cli | fix(auth): clear stale xai-oauth last_auth_error on successful token save (#326) — **Observed 2026-07-14:** a stale 07-13 `invalid_grant` ("Refresh token has been revoked") record was still sitting i… | read-only: upstream writes last_auth_error@hermes_cli/auth_xai.py:401; clear-on-success not verified | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 44 loc, 1 cf (2) | UNRESOLVED | — | UPSTREAM — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): generic 1-line fix, reproduced on upstream, provider in live use; effect is a stale error record that misleads triage (07-14 incident). Measured: live repro in temp HOME: seed auth.json xai-oauth.l… | — | t_4bf92fd0 | | #115 | hermes_cli | fix(backup): exclude swiftui-docs (Apple IP) from backup zip (#115) — Adds `swiftui-docs` to `_EXCLUDED_DIRS` in `hermes_cli/backup.py` so Apple's Xcode `AdditionalDocumentation` can never enter the … | read-only: upstream backup has no swiftui-docs exclusion | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 40 loc, 2 cf (2) | KEEP | — | KEEP | — | — | | #491 | hermes_cli | fix(kanban): respawn guard fail-closed forever on a JSON-quoted PR URL (#491) — A worker posts its handback as JSON: ```json "pr": "https://github.com/o/r/pull/3", ``` `_RESPAWN_GUARD_PR_URL_RE` capt… | read-only: fork-only kanban path (fork kanban_db.py diverged from upstream's split modules) | none | DB/config: 26,889 respawn_guarded events [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09… | 39 loc, 2 cf (2) | KEEP | — | KEEP | — | — | | #593 | hermes_cli | fix(cli): hide numeric failover lanes in `hermes model`, the third picker surface (#593) — `hermes model` (hermes_cli/main.py) was the only provider picker that applied no lane filter. It rendered 69… | read-only: upstream has no failover-lane filter | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 30 loc, 1 cf (1) | KEEP | — | KEEP | — | — | | nopr:36134d8944 | hermes_cli | fix(model): route bare gpt-5.5 to Codex OAuth — Ace expects '/model gpt-5.5' to mean the subscription-backed OpenAI Codex route, not OpenAI API or an aggregator slug. Add a built-in direct alias for … | read-only: upstream has no bare gpt-5.5 alias | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 29 loc, 1 cf (2) | DROP | stands | KEEP — ACE RULING 2026-09-27 (DROP -> KEEP, premise falsified by the slice worker): PR #1200 (the revert) went CI-red: af43fd64ff test_cron_auto_model relies on the gpt-5.5 -> openai-codex alias (4 failures). Close #1200 unmerged; branch audit/hermes_cli/revert-3613… | audit/hermes_cli/revert-36134d8944 | t_b865b149 | | nopr:9c6214b9c5 | hermes_cli | fix: parse openai-codex slash model switches — — orig: PR body via REST (merged direct commit); shas 9c6214b9c5 | GREEN by read: typed provider/model routing@hermes_cli/model_switch.py:926-934 (#45006) | none found by -S (behavior present by read) | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 27 loc, 2 cf (2) | SUPERSEDED-BY-UPSTREAM | overturned → KEEP | KEEP — adversary overturned: parse_model_input("openai-codex/gpt-6-sol", "claude-bpr") on ../up -> ("claude-bpr", "openai-codex/gpt-6-sol"); on fork -> ("openai-codex", "gpt-6-sol"); colon form handled on both; fleet worker spawns pass -m provider/model (#374: 322 l… | — | — | | nopr:ca61765850 | hermes_cli | fix(backup): bound _safe_copy_db so a live 1GB+ DB can't deadlock the full backup — The full `hermes backup -o` deadlocked for 12.5h on a Mac Studio whose live gateway holds a 1.46GB state.db in WAL … | GREEN by read: _safe_copy_db(src, dst, *, timeout_seconds=10.0)@hermes_cli/backup_sqlite.py:31 with busy_deadline progress callback (:57) | none found by -S (deadline present by read) | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 27 loc, 1 cf (2) | SUPERSEDED-BY-UPSTREAM | stands | SUPERSEDED-BY-UPSTREAM | — | — | -| #33 | hermes_cli | fix(browser): add --remote-allow-origins to CDP debug launch for Chrome 111+ (#33) — Chrome/Chromium **111+** rejects CDP WebSocket handshakes with **HTTP 403** unless the connecting origin is explic… | RED by read: launch args@hermes_cli/browser_connect.py:765 lack --remote-allow-origins | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 25 loc, 0 cf (0) | UNRESOLVED | — | UNRESOLVED | — | — | +| #33 | hermes_cli | fix(browser): add --remote-allow-origins to CDP debug launch for Chrome 111+ (#33) — Chrome/Chromium **111+** rejects CDP WebSocket handshakes with **HTTP 403** unless the connecting origin is explic… | RED by read: launch args@hermes_cli/browser_connect.py:765 lack --remote-allow-origins | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 25 loc, 0 cf (0) | UNRESOLVED | — | DROP — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): launcher never used in window; upstream client unaffected; --remote-allow-origins=* also widens the debug port to any web origin. Measured: live CDP repro, Chrome 154.0.8037.57 headless on temp pro… | — | t_d3825c87 | | #941 | hermes_cli | fix(kanban): include git operation and exit code in survivor holds (#941) — - Re-port #859 onto current fork/main by content. `_git()` now persists the Git subcommand and returncode in a survivor hol… | read-only: fork-only kanban path (fork kanban_db.py diverged from upstream's split modules) | none | DB/config: 1,375 task_workspace_survivors rows [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2… | 24 loc, 0 cf (0) | KEEP | — | KEEP | — | — | | #638 | hermes_cli | fix(ci): make 3 tests container-hermetic + stop doctor skipping backend sections in containers (#638) — **Real product bug found**: doctor's in-container branch reset any non-docker `terminal_env` to… | GREEN by read: _check_docker_backend(terminal_env, running_in_container, ...)@hermes_cli/doctor_tools.py:202 keeps backend sections in-cont… | a36221ed91 | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 18 loc, 1 cf (1) | SUPERSEDED-BY-UPSTREAM | stands | SUPERSEDED-BY-UPSTREAM | — | — | | #631 | hermes_cli | test: fail on empty parameter batteries (#631) — From greenhouse card t_47585116. Empty parametrize batteries now fail collection; restores the 22-plugin/44-case platform interface battery. Worker-ve… | read-only: upstream pyproject has no empty_parameter_set_mark | none | 0 distinctive log/event hits [window: log files 2026-05-10..2026-09-25 (1741 files; agent/gateway logs only 2026-07-29..2026-09-25); kanban… | 5 loc, 1 cf (2) | KEEP | — | KEEP | — | — | @@ -811,7 +811,7 @@ Inputs: auditor branches `audit//verdicts` (latest, incl. post-adversar | #747 | scripts+misc | publish-e2e-evidence had no failure policy; blanket continue-on-error would mask missing artifacts — orig: PR body: ## What Owns the evidence-publisher lane that was reverted out of #736 by the scope… | n/a (fleet-only) (publish-e2e-evidence.yml is fork-only) | none | publish-e2e-evidence.yml active: 2044 runs since 08-26, latest 2026-09-25T13:47Z (34 success/65 skipped of last 100) [log window 2026-06-02… | 2477 loc, 0 cf (0) | KEEP | — | KEEP | — | — | | #736 | scripts+misc | ci-review-comment poller exhausted the installation token rate limit (2026-09-19) — orig: PR body: ## Why `.github/workflows/ci-review-comment.yml` starts one poller per CI run, polling every 15 s wi… | read-only: upstream still ships ci-review-comment.yml + scripts/ci/live_comment.py (b9f82ed39f 2026-07-20) | none | 0: ci-review-comment.yml is disabled_manually on the fork since 2026-09-19 (GitHub workflow state); last 100 runs 48 cancelled/48 skipped/4… | 2447 loc, 1 cf (1) | DROP | stands | DROP | audit/scripts_misc/revert-ci-review-comment | t_1cc99380 | | #268 | scripts+misc | cross-surface live session sync phase 1: server session.changes polling + desktop consumer — orig: PR body: ## What Phase 1 of cross-surface live session sync: a session open in the desktop app rende… | n/a: upstream has no session.changes method and upstream desktop has no consumer | none | 0 'session.changes' calls in 2026-06-02..2026-09-25 (86 agent/gateway/errors logs, ~/.hermes/logs + profiles/*/logs); the only client (apps… | 1892 loc, 6 cf (3) | DROP | stands | DROP | audit/scripts_misc/revert-livesync | t_a1649ffc | -| #780 | scripts+misc | 5 FleetReview P1s on the OOB confab-notice consumer (agent/confab_notice.py + TUI/desktop render) — orig: PR body: ## Why this is a new PR (the card said not to open one) The card `t_3c012aba` instru… | n/a (fleet-only) (confab_notice is fork-only; 9 fork files import it) | none | not countable: the consumer emits no log marker; only evidence of use is Ace's 2026-09-21 01:45 Discord message 'Confab-OOB lane live on al… | 1681 loc, 4 cf (3) | UNRESOLVED | — | UNRESOLVED | — | — | +| #780 | scripts+misc | 5 FleetReview P1s on the OOB confab-notice consumer (agent/confab_notice.py + TUI/desktop render) — orig: PR body: ## Why this is a new PR (the card said not to open one) The card `t_3c012aba` instru… | n/a (fleet-only) (confab_notice is fork-only; 9 fork files import it) | none | not countable: the consumer emits no log marker; only evidence of use is Ace's 2026-09-21 01:45 Discord message 'Confab-OOB lane live on al… | 1681 loc, 4 cf (3) | UNRESOLVED | — | KEEP — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): reverting #780 alone would leave #764's consumer with 5 known P1 defects (dropped/duplicated/false notices). Its fate is #764's: if #764 is later dropped, drop both. Measured: follows #764 (lead: K… | — | — | | #612 | scripts+misc | recovered July design docs + loop-starvation certify harness — orig: PR body: Stash-triage recovery (closeout loose end, 2026-08-19). `stash@{5}` from 2026-07-06 held the only copy of the dashboard-p… | n/a (fleet-only) | none | scripts/certify/dashboard_loop_certify.py: 0 callers in repo/cron/launchd/hooks [log window 2026-06-02..2026-09-25 (86 agent/gateway/errors… | 1636 loc, 0 cf (0) | DROP | needs-lead → cross-tranche (gateway #616) | KEEP — LEAD: scripts/certify/dashboard_loop_certify.py is named by dashboard-eventloop-starvation-triage as THE hardened harness and is co-owned by gateway #616. KEEP; revert-oneshot-probes must be rebuilt without it. | audit/scripts_misc/revert-oneshot-probes | — | | #748 | scripts+misc | round-6 poller findings on #736 (status loss, stale artifacts, shutdown budget) — orig: PR body: Follow-up to #736 (squash-merged at 07:46Z). FleetReview's round-6 record on head `111527dd21` escalat… | as #736 | none | as #736 — workflow disabled since 2026-09-19 [log window 2026-06-02..2026-09-25 (86 agent/gateway/errors logs, ~/.hermes/logs + profiles/*/… | 1625 loc, 1 cf (1) | DROP | stands | DROP | audit/scripts_misc/revert-ci-review-comment | t_2820954b | | #932 | scripts+misc | CI overflow Phase-0 read-only capability preflight + baseline receipt — orig: PR body: Implements read-only Phase-0 preflight with named PASS/BLOCK/UNVERIFIABLE checks, fixture contracts, live receip… | n/a (fleet-only) | none | 0 callers: ci_overflow_acceptance.py is referenced only by its own test (fork .github/scripts grep); receipts/preflight.json has no reader;… | 1601 loc, 0 cf (0) | DROP | stands | KEEP — ACE RULING 2026-09-27 (DROP -> KEEP, premise falsified by the slice worker): #954 (merged after the audit) imports ci_overflow_acceptance from ci_overflow_integration.py:30; the rebased revert fails with ModuleNotFoundError (reproduced). Branch audit/scripts_… | audit/scripts_misc/revert-ci-overflow-phase0 | t_7bc9ce6e | @@ -833,10 +833,10 @@ Inputs: auditor branches `audit//verdicts` (latest, incl. post-adversar | #873 | scripts+misc | pre-yield permit check excused conditionally-exiting arms — orig: PR body: **STACKED on #863** (base is `fix/preyield-guard-hardening`, not `main`). Rebase to `main` after #863 merges; the diff is on… | n/a (fleet-only) | none | as #860 [log window 2026-06-02..2026-09-25 (86 agent/gateway/errors logs, ~/.hermes/logs + profiles/*/logs)] | 705 loc, 0 cf (0) | KEEP | — | KEEP | — | — | | nopr:3cd73eb4d4 | scripts+misc | one-shot LCM PRD-6/7/8 QA campaign harness (Arm-A/Arm-B/K=2); campaign concluded June; ships nowhere — orig: commit body: test: add isolated LCM smoke gate Adds the repeatable fork LCM isolated smoke… | n/a (fleet-only) | none | 0 runs: no cron/launchd/hook invokes it; only callers are ~/.hermes/scripts/lcm-*-autofire*.sh + fleet/run-lcm-campaign.sh, themselves unsc… | 680 loc, 0 cf (0) | DROP | overturned → KEEP | KEEP — adversary overturned: ~/.hermes/skills-shared/ang-lifecycle/ang-swarm-plan/references/sibling-branch-topology-and-incremental-integration.md:56 prescribes `python scripts/probe_hermes_lcm_isolated.py --profile-dir ... ` as the integration behavior proof; file… | audit/scripts_misc/revert-lcm-campaign-harness | — | | #863 | scripts+misc | pre-yield permit check missed line-adjacent leaks / partial multi-permit releases — orig: PR body: Follow-up to #860 (merged as `6319e227`); rebased onto `main` after that PR landed mid-run. Closes t… | n/a (fleet-only) | none | as #860 [log window 2026-06-02..2026-09-25 (86 agent/gateway/errors logs, ~/.hermes/logs + profiles/*/logs)] | 655 loc, 0 cf (0) | KEEP | — | KEEP | — | — | -| #894 | scripts+misc | residual pasteable-hint sites must survive a shell (quote HERMES_HOME-derived paths) — orig: PR body: fix(cli): the six residual pasteable-hint sites must survive a real shell Residual of the #889/#8… | not checked here: code sites are in gateway/run.py, hermes_cli/doctor.py, hermes_cli/plugins_cmd.py, plugins/platforms/whatsapp/adapter.py,… | none | not measurable from logs (operator-facing hint text) [log window 2026-06-02..2026-09-25 (86 agent/gateway/errors logs, ~/.hermes/logs + pro… | 629 loc, 1 cf (3) | UNRESOLVED | — | UNRESOLVED | — | — | +| #894 | scripts+misc | residual pasteable-hint sites must survive a shell (quote HERMES_HOME-derived paths) — orig: PR body: fix(cli): the six residual pasteable-hint sites must survive a real shell Residual of the #889/#8… | not checked here: code sites are in gateway/run.py, hermes_cli/doctor.py, hermes_cli/plugins_cmd.py, plugins/platforms/whatsapp/adapter.py,… | none | not measurable from logs (operator-facing hint text) [log window 2026-06-02..2026-09-25 (86 agent/gateway/errors logs, ~/.hermes/logs + pro… | 629 loc, 1 cf (3) | UNRESOLVED | — | DROP — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): never fires on this fleet; 629 loc incl. gateway/run.py (conflicts in all 3 syncs). If wanted for Windows users, upstream #889+#894 together as one PR. Measured: trigger = a whitespace in the home … | — | t_ceb111f9 | | nopr:4b3698271f | scripts+misc | one-shot LCM PRD-6/7/8 QA campaign harness (Arm-A/Arm-B/K=2); campaign concluded June; ships nowhere (Tier-0 QA battery) — orig: commit body: fix(lcm): null-safe lcm_grep query + Tier-0 QA battery (P… | n/a (fleet-only) | none | 0 runs: no cron/launchd/hook invokes it; only callers are ~/.hermes/scripts/lcm-*-autofire*.sh + fleet/run-lcm-campaign.sh, themselves unsc… | 619 loc, 0 cf (0) | DROP | overturned → KEEP | KEEP — adversary overturned: ~/.hermes/skills-shared/general/lcm-context-engine/references/local-addenda-lcm-context-engine.md:96 prescribes `python scripts/lcm_qa_battery.py --tier 0` as THE gate for LCM changes; SKILL.md:48,864 (battery refuses Opus); tier 1/2 dri… | audit/scripts_misc/revert-lcm-campaign-harness | — | | nopr:eb38ffc5b2 | scripts+misc | refactor-equivalence harness (golden replay + mutation gate) backing the god-file extraction goldens — orig: commit body: Add refactor equivalence harness | n/a (fleet-only) | none | imported by tests/golden/{state_ext,tool_gate}/runner.py + tests/scripts/test_refactor_equiv.py, which CI (ci.yaml, 1949 runs since 08-26) … | 568 loc, 0 cf (0) | KEEP | — | KEEP | — | — | -| nopr:8dcc69611c | scripts+misc | write-time skill-hygiene guard: refuse non-local skill creates outside skills-shared/, leak-check script — orig: commit body: feat(skills): write-time skill-hygiene guard (A3) Three guards closing th… | n/a (fleet-only) | none | G1 refusal: 0 log hits for the refusal in window; repo scripts/local-skill-leak-check.py has 0 callers — the launchd job ai.hermes.skill-co… | 554 loc, 2 cf (2) | UNRESOLVED | — | UNRESOLVED | — | — | +| nopr:8dcc69611c | scripts+misc | write-time skill-hygiene guard: refuse non-local skill creates outside skills-shared/, leak-check script — orig: commit body: feat(skills): write-time skill-hygiene guard (A3) Three guards closing th… | n/a (fleet-only) | none | G1 refusal: 0 log hits for the refusal in window; repo scripts/local-skill-leak-check.py has 0 callers — the launchd job ai.hermes.skill-co… | 554 loc, 2 cf (2) | UNRESOLVED | — | DROP — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): 0 fires in ~80 days / 3,130 writes; the repo script is dead. 554 loc in tools/skill_manager_tool.py + agent/skill_utils.py (2 files, conflict in 2 syncs). Measured: G1/G2 refusals return to the mod… | — | t_cad124fd | | #766 | scripts+misc | one CI_RUNNER_LABELS for the whole matrix → jobs queued in waves on a 17-slot pool — orig: PR body: ## What and why The self-hosted pool has **17 slots** (ace-ai 8 + ace-media 8 + studio 1). A PR/mer… | n/a (fleet-only) | none | per-slice runs-on live: CI_SELF_HOSTED_SLOTS=4, CI_ARM_HOSTED_SLICES=3 read by run_tests_parallel --generate-slices every run [log window 2… | 528 loc, 3 cf (3) | KEEP | — | KEEP | — | — | | #752 | scripts+misc | a file SIGKILLed at the per-file timeout was reported as a no-op, not a HANG (main run 35495902532) — orig: PR body: ci(run_tests_parallel): a file killed at the per-file timeout is a HANG, not a no-… | RED by reading: upstream scripts/run_tests_parallel.py has 0 'HANG' classifications (grep -c HANG = 0) | none | scripts/run_tests_parallel.py is invoked by tests.yml (matrix generation + per-slice run via run_tests.sh) on every CI run; fork CI: ci.yam… | 527 loc, 1 cf (3) | KEEP | — | KEEP | — | — | | nopr:d9d2266dfe | scripts+misc | read-only LCM upstream security-drift checker (scripts/check_lcm_upstream_drift.py) — orig: commit body: feat(lcm): add upstream drift metadata checker Adds structured LCM vendoring provenance fields… | n/a (fleet-only) | none | 0 runs: no cron/launchd/hook/workflow invokes check_lcm_upstream_drift.py (live-tree grep, fork .github grep) [log window 2026-06-02..2026-… | 446 loc, 0 cf (0) | DROP | overturned → KEEP | KEEP — adversary overturned: ~/.hermes/skills-shared/coding/hermes-fork-pr-contribution/references/executing-an-upstream-parity-merge.md:92-93 prescribes `scripts/check_lcm_upstream_drift.py --metadata plugins/context_engine/lcm/VENDORED_FROM.txt --offline` as a ste… | audit/scripts_misc/revert-lcm-campaign-harness | — | @@ -866,8 +866,8 @@ Inputs: auditor branches `audit//verdicts` (latest, incl. post-adversar | nopr:595b6e78ae | scripts+misc | parity-sync CLI (hermes_parity) — fleet-only sync tooling, run once per upstream sync — orig: commit/PR body: feat(parity): add fork manifest lint | n/a (fleet-only) | none | used by the 2026-08-29 parity sync: gates.jsonl records through ts 1788136253 (2026-08-31) written by gates.py; ACK-REVIEW-2026-08-30.md; f… | 124 loc, 0 cf (0) | KEEP | — | KEEP | — | — | | nopr:7d33c982d8 | scripts+misc | one-shot LCM PRD-6/7/8 QA campaign harness (Arm-A/Arm-B/K=2); campaign concluded June; ships nowhere — orig: commit body: feat(lcm): multi-sentinel-per-session Arm-B batching (~Kx cheaper) --sentinel… | n/a (fleet-only) | none | 0 runs: no cron/launchd/hook invokes it; only callers are ~/.hermes/scripts/lcm-*-autofire*.sh + fleet/run-lcm-campaign.sh, themselves unsc… | 123 loc, 0 cf (0) | DROP | overturned → KEEP | KEEP — adversary overturned: ~/.hermes/skills-shared/general/lcm-context-engine/SKILL.md:870-874 Tier-2 gate Arm B = scripts/lcm_arm_b_node_recovery.py; SKILL.md:486 (needs_escalation wiring), references/k2-disambiguation-fix-and-ab-loop.md:40; ~/.hermes/scripts/lcm… | audit/scripts_misc/revert-lcm-campaign-harness | — | | #272 | scripts+misc | livesync RC-1 probe + t_silence derivation scripts — orig: PR body: Ship-gate probe fixes learned during the live RC-1 run (PR #268 follow-up): - `session.changes`/`session.resume` take the STORED st… | n/a (fleet-only) | none | 0 callers (repo + live grep); probes for the #268 ship gate only [log window 2026-06-02..2026-09-25 (86 agent/gateway/errors logs, ~/.herme… | 122 loc, 0 cf (0) | DROP | stands | DROP | audit/scripts_misc/revert-livesync | t_09c54ea8 | -| nopr:7c3d5cdd0f | scripts+misc | provider alias collision lint + strip first-class provider prefix from recorded model — orig: commit body: feat(telemetry): fN alias collision lint + stop composite model strings leaking to charts (A… | not checked: providers/__init__.py diverges +184/-346 vs upstream | none | lint_provider_collisions still wired into hermes doctor (doctor.py:1928); recording-boundary normalization unmeasured [log window 2026-06-0… | 121 loc, 0 cf (0) | UNRESOLVED | — | UNRESOLVED | — | — | -| #695 | scripts+misc | virtualHistoryOffsetCache unmount-compensation test flaky in merge queue (act()/deferred rows) — orig: PR body: ## Problem `ui-tui/src/__tests__/virtualHistoryOffsetCache.test.ts` fails inside the Gi… | read-only: ../up/ui-tui/src/__tests__/virtualHistoryOffsetCache.test.ts lacks the act()/IS_REACT_ACT_ENVIRONMENT settle (fork diff +57/-33) | none | test-only; red observed in fork merge queue per PR body; no local vitest run (ui-tui node_modules not installed here) [log window 2026-06-0… | 87 loc, 0 cf (0) | UPSTREAM | keep-unproven | UNRESOLVED — LEAD: merge-queue red NOT reproduced on either tree (17 passed x3 + 6/6 concurrent both); evidence = PR body only. | audit/scripts_misc/upstream-695 | — | +| nopr:7c3d5cdd0f | scripts+misc | provider alias collision lint + strip first-class provider prefix from recorded model — orig: commit body: feat(telemetry): fN alias collision lint + stop composite model strings leaking to charts (A… | not checked: providers/__init__.py diverges +184/-346 vs upstream | none | lint_provider_collisions still wired into hermes doctor (doctor.py:1928); recording-boundary normalization unmeasured [log window 2026-06-0… | 121 loc, 0 cf (0) | UNRESOLVED | — | DROP — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): never fired, verified clean at landing and now; 121 loc in providers/__init__.py which diverges +184/-346 from upstream. Measured: the in-repo diff is only C-2 (lint_provider_collisions + a doctor … | — | t_c4b3a02c | +| #695 | scripts+misc | virtualHistoryOffsetCache unmount-compensation test flaky in merge queue (act()/deferred rows) — orig: PR body: ## Problem `ui-tui/src/__tests__/virtualHistoryOffsetCache.test.ts` fails inside the Gi… | read-only: ../up/ui-tui/src/__tests__/virtualHistoryOffsetCache.test.ts lacks the act()/IS_REACT_ACT_ENVIRONMENT settle (fork diff +57/-33) | none | test-only; red observed in fork merge queue per PR body; no local vitest run (ui-tui node_modules not installed here) [log window 2026-06-0… | 87 loc, 0 cf (0) | UPSTREAM | keep-unproven | UPSTREAM — RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): the flake was real (5% of merge-queue runs over a week) and the fix removed it; test-only, generic. Branch already built: audit/scripts_misc/upstream-695 @ 830f68a5dc (hand port off upstream/main).… | audit/scripts_misc/upstream-695 | t_c1c5035d | | #188 | scripts+misc | web dashboard chat self-declares source='dashboard' — orig: PR body: ## Problem The in-browser **web dashboard** chat (`web/`), the Electron desktop app, and the Ink TUI all drive the same `tui_gatew… | n/a: upstream web client has no source field and no per-client attribution | none | 0: ~/.hermes/blackbox/turns.db last 30 days has platforms discord/subagent/cron/telegram/cli/curator only — no 'dashboard' (or 'tui') turn … | 85 loc, 2 cf (1) | DROP | needs-lead → KEEP or DROP+skill-edit | DROP — LEAD: dashboard platform = 4 turns ever, last 2026-07-04 (83 days dormant). DROP + skill edit (hermes-client-source-attribution SKILL.md:61-83,121 and its reference test). | audit/scripts_misc/revert-web-source | t_5560deb1 | | nopr:9f0c416088 | scripts+misc | one-shot LCM PRD-6/7/8 QA campaign harness (Arm-A/Arm-B/K=2); campaign concluded June; ships nowhere — orig: commit body: feat(lcm-campaign): self-contained tightened-A + semantic-B campaign, venv-pi… | n/a (fleet-only) | none | 0 runs: no cron/launchd/hook invokes it; only callers are ~/.hermes/scripts/lcm-*-autofire*.sh + fleet/run-lcm-campaign.sh, themselves unsc… | 83 loc, 0 cf (0) | DROP | needs-lead → KEEP or DROP+skill-edit | DROP — LEAD: one-shot June N=180 campaign runner, 0 invocations in 60d (state.db tool calls). DROP together with the skill edit: lcm-context-engine SKILL.md:874,902 and ~/.hermes/scripts/lcm-arm-a-autofire-watch.sh:21 must stop naming lcm_armab_campaign.sh. revert-l… | audit/scripts_misc/revert-lcm-campaign-harness | t_99035752 | | nopr:e0cf699f81 | scripts+misc | one-shot LCM PRD-6/7/8 QA campaign harness (Arm-A/Arm-B/K=2); campaign concluded June; ships nowhere — orig: commit body: fix(lcm): Arm-B harness follows session rollover to node-bearing session Live… | n/a (fleet-only) | none | 0 runs: no cron/launchd/hook invokes it; only callers are ~/.hermes/scripts/lcm-*-autofire*.sh + fleet/run-lcm-campaign.sh, themselves unsc… | 73 loc, 0 cf (0) | DROP | overturned → KEEP | KEEP — adversary overturned: ~/.hermes/skills-shared/general/lcm-context-engine/SKILL.md:870-874 Tier-2 gate Arm B = scripts/lcm_arm_b_node_recovery.py; SKILL.md:486 (needs_escalation wiring), references/k2-disambiguation-fix-and-ab-loop.md:40; ~/.hermes/scripts/lcm… | audit/scripts_misc/revert-lcm-campaign-harness | — | @@ -984,7 +984,7 @@ Inputs: auditor branches `audit//verdicts` (latest, incl. post-adversar | #478 | auto | fix(tests): isolate gateway session ContextVars across test files (#478) | | | | 116 loc, 1 cf (2) | (auto) | n/a | KEEP — auto: follows #655 (hermes_cli) by path; same fate. | — | — | | #1058 | auto | test(canary): a parity merge must not bring back import-time plugin discovery (t_39f3a794) (#1058) | | | | 114 loc, 1 cf (1) | (auto) | n/a | SUPERSEDED-BY-UPSTREAM — auto: follows #697 (agent) by path; same fate. | — | — | | #881 | auto | fix(gitignore): make the venv patterns symlink-safe so a deploy tree reads clean (#881) | | | | 112 loc, 0 cf (0) | (auto) | n/a | KEEP — auto: no tranche code row owns these paths (fork test-infra, fork docs/specs, fork-ext goldens); stays with the fork code it serves; zero runtime cost. | — | — | -| nopr:08fc3aff65 | auto | fix(test): rewrite A3 guard test as a proper pytest module (was a script → CI collection abort) | | | | 109 loc, 0 cf (0) | (auto) | n/a | UNRESOLVED — auto: follows nopr:8dcc69611c (scripts_misc) by path; same fate. | — | — | +| nopr:08fc3aff65 | auto | fix(test): rewrite A3 guard test as a proper pytest module (was a script → CI collection abort) | | | | 109 loc, 0 cf (0) | (auto) | n/a | DROP — auto: follows nopr:8dcc69611c (scripts_misc) by path; joins that card. | — | t_cad124fd | | #892 | auto | test(cli): the title-resume-hint gate could not see naive quoting (#892) | | | | 107 loc, 0 cf (0) | (auto) | n/a | KEEP — auto: follows #889 (hermes_cli) by path; same fate. | — | — | | nopr:334ae3a402 | auto | fix(golden): address Greptile P1s — corpus per-model override case used a wrong key+shape (agent.model_reasoning dict → agent.reasoning_overrides effort-string; golden now proves effort=high wins ove… | | | | 107 loc, 0 cf (0) | (auto) | n/a | KEEP — auto: no tranche code row owns these paths (fork test-infra, fork docs/specs, fork-ext goldens); stays with the fork code it serves; zero runtime cost. | — | — | | #696 | auto | test(delegation): quiesce persistence fixture workers before reset (#696) | | | | 105 loc, 0 cf (0) | (auto) | n/a | KEEP — auto: follows #688 (cron_tools) by path; same fate. | — | — | @@ -1180,7 +1180,7 @@ Inputs: auditor branches `audit//verdicts` (latest, incl. post-adversar | #427 | gateway | KEEP | keep-unproven → KEEP-UNPROVEN | DROP | LEAD: KEEP-UNPROVEN with no positive fire count, no executed upstream RED, no live knob/consumer/registry entry found in the row evidence -> DROP per rule (unmeasured "still needed" is not a KEEP). | | #401 | gateway | KEEP | keep-unproven → KEEP-UNPROVEN | DROP | LEAD: KEEP-UNPROVEN with no positive fire count, no executed upstream RED, no live knob/consumer/registry entry found in the row evidence -> DROP per rule (unmeasured "still needed" is not a KEEP). | | #396 | gateway | KEEP | keep-unproven → KEEP-UNPROVEN | DROP | LEAD: KEEP-UNPROVEN with no positive fire count, no executed upstream RED, no live knob/consumer/registry entry found in the row evidence -> DROP per rule (unmeasured "still needed" is not a KEEP). | -| #356 | gateway | DROP | needs-lead → lead: test before revert | UNRESOLVED | LEAD: removed from the #49 revert unit. The empty-resume guard is not undo-only: upstream synthesizes an empty-text internal resume event (up gateway/run_startup.py:611) with no empty-row guard. Needs a repro on upstream before either DROP or UPSTREAM; unmeasured either way. | +| #356 | gateway | DROP | needs-lead → lead: test before revert | SUPERSEDED-BY-UPSTREAM | RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): upstream solved the same empty-row bug (persists the note instead of dropping the row); fork flag+drop machinery is redundant. Measured: live call on upstream c15ebb1435: gateway.run._prepare_resume_pending_message('restart','') returns a non-empty persist text (the recovery note), so the synthesized empty-text resume event (up gateway… | | #353 | gateway | DROP | needs-lead → follows #49 | DROP | LEAD: measurement supports the drop (/undo 2, /redo 0 native invocations 2026-05-10..09-25) but registry entry 14 is lifecycle=fork-permanent (Ace ruling). DROP is ACE-GATED: the slice card must not merge until Ace retires entry 14 (D2b write-back: entry 14 -> retired). Revert unit = audit/gateway/revert-49-undo; must also remove tui_gateway/server.py _undo/_redo_session_core + callers. | | #339 | gateway | DROP | needs-lead → follows #49 | DROP | LEAD: measurement supports the drop (/undo 2, /redo 0 native invocations 2026-05-10..09-25) but registry entry 14 is lifecycle=fork-permanent (Ace ruling). DROP is ACE-GATED: the slice card must not merge until Ace retires entry 14 (D2b write-back: entry 14 -> retired). Revert unit = audit/gateway/revert-49-undo; must also remove tui_gateway/server.py _undo/_redo_session_core + callers. | | #315 | gateway | SUPERSEDED-BY-UPSTREAM | needs-lead → KEEP (route-identity layer) / ruling on sticky re… | KEEP | LEAD: route-identity symbols consumed outside the PR (chat_model_pins = registry 0, route_identity = registry 10, /fast = registry 17 fork-permanent), 0 hits upstream; revert breaks chat-model pins. Sticky-reset knob alone is a trim candidate. | @@ -1226,10 +1226,9 @@ Inputs: auditor branches `audit//verdicts` (latest, incl. post-adversar | #698 | gateway | KEEP | keep-unproven → KEEP-UNPROVEN | DROP | LEAD: KEEP-UNPROVEN with no positive fire count, no executed upstream RED, no live knob/consumer/registry entry found in the row evidence -> DROP per rule (unmeasured "still needed" is not a KEEP). | | #687 | gateway | KEEP | keep-unproven → KEEP-UNPROVEN | DROP | LEAD: follows #683. | | #433 | gateway | KEEP | keep-unproven → KEEP-UNPROVEN | SUPERSEDED-BY-UPSTREAM | LEAD: dedups the fork copy of #198 (SUPERSEDED); dissolves when upstream compute host is taken. | -| #358 | gateway | SUPERSEDED-BY-UPSTREAM | needs-lead → lead: upstream claim falsified | UNRESOLVED | LEAD: auditor premise falsified (upstream prompt.submit has no empty-text check); fork guard fires unmeasured. Needs a count of 4020 rejections before KEEP/UPSTREAM. | +| #358 | gateway | SUPERSEDED-BY-UPSTREAM | needs-lead → lead: upstream claim falsified | DROP | RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): guard's path carries ~no traffic here and the client that looped is gone; not worth 237 loc on 2 files that conflict every sync. Reopen if a desktop empty-submit loop recurs. Measured: 4020 rejections are not logged anywhere (0 hits for 4020 in dashboard.{out,err}.log, dashboard-auth.log); a count needs live instrumentation and ~/.herm… | | #229 | gateway | DROP | needs-lead → lead: flag IS enabled | DROP | LEAD: flag desktop_auto_resume is on (config.yaml:371, aegis:323) but the feature has produced no state: state.db desktop_resume_markers = 0 rows, desktop_resume_breakers = 0 rows (read-only, 2026-09-25). On a retired desktop surface (D9). DROP stands; branch audit/gateway/revert-229 (d96e727829, 22+160 passed); remove the config key with it. | | #49 | gateway | DROP | needs-lead → Ace ruling (fork-permanent) | DROP | LEAD: measurement supports the drop (/undo 2, /redo 0 native invocations 2026-05-10..09-25) but registry entry 14 is lifecycle=fork-permanent (Ace ruling). DROP is ACE-GATED: the slice card must not merge until Ace retires entry 14 (D2b write-back: entry 14 -> retired). Revert unit = audit/gateway/revert-49-undo; must also remove tui_gateway/server.py _undo/_redo_session_core + callers. | -| #695 | scripts+misc | UPSTREAM | keep-unproven | UNRESOLVED | LEAD: merge-queue red NOT reproduced on either tree (17 passed x3 + 6/6 concurrent both); evidence = PR body only. | | #188 | scripts+misc | DROP | needs-lead → KEEP or DROP+skill-edit | DROP | LEAD: dashboard platform = 4 turns ever, last 2026-07-04 (83 days dormant). DROP + skill edit (hermes-client-source-attribution SKILL.md:61-83,121 and its reference test). | | #44 | plugins | DROP | overturned → KEEP | KEEP | adversary overturned: plugins/blackbox/card.py:124-150 _tokens_out_line (introduced by 930d367238) feeds card.py:208 render_card, which /cost uses via commands.py:56-59 _render_card -> card.render; store.py:394 cites _tokens_out_line as a contract; blackbox is fork-identity by ruling | | #509 | plugins | KEEP | keep-unproven → KEEP | DROP | LEAD: no-op compaction class not observed in 201 "compression done" lines; 3-sync conflict on agent/turn_context.py. | @@ -1336,3 +1335,19 @@ plugins 68 → 69 / 5 → 4; scripts+misc 76 → 78 / 32 → 30. ROLLUP.md is re Side effect of the re-render: #562, #589, nopr:4ed79b6dad and nopr:051c2076e1 no longer appear in the Conflicts table. `render.py` lists a KEEP-UNPROVEN row there only when FINAL differs from the auditor verdict, and all four auditors said KEEP. This subsection records the change. + +### UNRESOLVED rows folded (2026-09-27) + +t_63023f77 measured and ruled the 24 rows the lead left UNRESOLVED (`UNRESOLVED.md`, source `unresolved.verdicts.json`). +Card t_12b66b00 folds them into the canonical build: `lead/rulings.py` loads `UNRESOLVED_RULINGS` from that file, +`final.build()` applies it to the 23 code rows (asserting each was UNRESOLVED), the auto row nopr:08fc3aff65 inherits DROP +from nopr:8dcc69611c, and `lead/build_all.py` fails if any of the 24 rows ends with a verdict other than its ruling. +`render.py` takes the card column from the ruling (and from `POST_RULING_2026_09_27`, whose cards left `cards.json`) and keeps +these hand-written sections when it re-renders. + +**Verdicts:** KEEP 8 (#907 #879 #858 #798 #579 #780 #18 #17), DROP 10 (#620 #441 #215 #33 nopr:8a8b81638c #358 #894 +nopr:7c3d5cdd0f nopr:8dcc69611c + auto nopr:08fc3aff65), UPSTREAM 4 (#326 #166 #135 #695), SUPERSEDED-BY-UPSTREAM 2 (#356 #291). +The previous lead notes on #356, #358 and #695 stay in the `why` field after the ruling ("prior lead note"). + +**Counts (by script, `lead/rows.json`):** KEEP 672 → 680, DROP 153 → 163, UPSTREAM 93 → 97, SUPERSEDED-BY-UPSTREAM 174 → 176, +UNRESOLVED 24 → 0; 1,116 rows. ROLLUP.md is re-rendered to match. diff --git a/docs/plans/fork-pr-audit/ROLLUP.md b/docs/plans/fork-pr-audit/ROLLUP.md index 224cf3018d149..84e7a1b507591 100644 --- a/docs/plans/fork-pr-audit/ROLLUP.md +++ b/docs/plans/fork-pr-audit/ROLLUP.md @@ -4,20 +4,20 @@ | tranche | KEEP | UPSTREAM | SUPERSEDED-BY-UPSTREAM | DROP | UNRESOLVED | total | |---|---|---|---|---|---|---| -| gateway | 95 | 19 | 26 | 39 | 2 | 181 | +| gateway | 95 | 19 | 27 | 40 | 0 | 181 | | agent | 118 | 20 | 85 | 17 | 0 | 240 | -| hermes_cli | 134 | 4 | 16 | 7 | 16 | 177 | +| hermes_cli | 141 | 7 | 17 | 12 | 0 | 177 | | plugins | 69 | 7 | 4 | 4 | 0 | 84 | | cron+tools | 44 | 37 | 21 | 9 | 0 | 111 | -| scripts+misc | 78 | 1 | 3 | 30 | 5 | 117 | -| auto | 134 | 5 | 17 | 10 | 1 | 167 | +| scripts+misc | 79 | 2 | 3 | 33 | 0 | 117 | +| auto | 134 | 5 | 17 | 11 | 0 | 167 | | auto-cherry-pick | 0 | 0 | 2 | 0 | 0 | 2 | | auto-desktop-retired | 0 | 0 | 0 | 37 | 0 | 37 | -| **all** | **672** | **93** | **174** | **153** | **24** | **1116** | +| **all** | **680** | **97** | **176** | **163** | **0** | **1116** | ## 2. Fork lines that DROP / SUPERSEDED would delete -Census `loc` (add+del of the original PR, an upper bound — later rows edit the same lines): DROP 50,822, SUPERSEDED-BY-UPSTREAM 88,161, UPSTREAM (deleted once merged upstream) 30,313, KEEP 338,037, UNRESOLVED 7,867; all rows 515,200. +Census `loc` (add+del of the original PR, an upper bound — later rows edit the same lines): DROP 53,571, SUPERSEDED-BY-UPSTREAM 88,557, UPSTREAM (deleted once merged upstream) 30,711, KEEP 342,361, UNRESOLVED 0; all rows 515,200. Real `git diff --shortstat ` of every revert branch on origin (lead run 2026-09-25): @@ -144,6 +144,10 @@ Real `git diff --shortstat ` of every revert branch on orig | t_522e29c9 | #71 | audit/cron_tools/upstream-71 | | t_05cdacb0 | nopr:4d626ac813 | to build | | t_e480bf39 | nopr:9a1db22e58 | to build | +| t_4bf92fd0 | #326 | to build (t_63023f77) | +| t_db92203d | #166 | to build (t_63023f77) | +| t_bc006a95 | #135 | to build (t_63023f77) | +| t_c1c5035d | #695 | to build (t_63023f77) | ## 4. Expected reduction in parity-merge conflicts @@ -152,10 +156,10 @@ Census `conflict_files` is file-level (the PR touches a file named in a sync led | ledger | ledger files touched by fork rows | touched by DROP/SUPERSEDED rows | freed (no surviving row touches it) | |---|---|---|---| | RESOLUTION-LEDGER.md | 47 | 43 | 7 | -| RESOLUTION-LEDGER-20260807.md | 117 | 85 | 13 | -| RESOLUTION-LEDGER-2026-08-29.md | 123 | 84 | 14 | +| RESOLUTION-LEDGER-20260807.md | 117 | 87 | 14 | +| RESOLUTION-LEDGER-2026-08-29.md | 123 | 87 | 16 | -Row view: 233/795 ledger-touching rows are DROP/SUPERSEDED; they hold 545/1,727 (32%) of all row×conflict-file incidences. Freed files (all ledgers, deduped): 31 — apps/desktop/src/lib/reasoning-effort.ts, hermes_cli/tools_config.py, locales/ar.yaml, scripts/install.sh, scripts/iso-certify.py, tests/agent/test_compaction_threshold_reresolve.py, tests/agent/test_fallback_announce.py, tests/agent/test_i18n.py, tests/agent/test_redact.py, tests/cron/test_lifecycle_guard_heredoc_data.py, tests/gateway/test_config_env_bridge_authority.py, tests/gateway/test_cron_session_contextvar.py, tests/gateway/test_no_gateway_session_env_writes.py, tests/hermes_cli/test_model_switch_custom_providers.py, tests/hermes_cli/test_web_server.py, tests/run_agent/test_partial_stream_finish_reason.py, tests/run_agent/test_run_agent.py, tests/run_agent/test_run_agent_conversation.py, tests/test_code_skew.py, tests/test_web_server.py, tests/tools/test_cron_subagent_session.py, tests/tools/test_mcp_tool.py, tests/tui_gateway/test_compute_host_phase1.py, tests/tui_gateway/test_iso_certify_seam.py, tui_gateway/compute_host.py, tui_gateway/host_supervisor.py, tui_gateway/methods_session.py, tui_gateway/synthetic_turn.py, web/src/lib/gatewayClient.test.ts, web/src/lib/gatewayClient.ts, website/docs/user-guide/features/browser.md. The god files (gateway/run.py, agent/*helpers, hermes_state.py, cron/scheduler.py) stay conflicted: KEEP rows still touch them, so the win there is fewer hunks, not fewer files. +Row view: 241/795 ledger-touching rows are DROP/SUPERSEDED; they hold 569/1,727 (33%) of all row×conflict-file incidences. Freed files (all ledgers, deduped): 33 — apps/desktop/src/lib/reasoning-effort.ts, hermes_cli/tools_config.py, locales/ar.yaml, scripts/install.sh, scripts/iso-certify.py, tests/agent/test_compaction_threshold_reresolve.py, tests/agent/test_fallback_announce.py, tests/agent/test_i18n.py, tests/agent/test_redact.py, tests/cron/test_lifecycle_guard_heredoc_data.py, tests/gateway/test_config_env_bridge_authority.py, tests/gateway/test_cron_session_contextvar.py, tests/gateway/test_no_gateway_session_env_writes.py, tests/hermes_cli/test_model_switch_custom_providers.py, tests/hermes_cli/test_web_server.py, tests/hermes_cli/test_web_server_cron_profiles.py, tests/run_agent/test_partial_stream_finish_reason.py, tests/run_agent/test_run_agent.py, tests/run_agent/test_run_agent_conversation.py, tests/test_code_skew.py, tests/test_web_server.py, tests/tools/test_cron_subagent_session.py, tests/tools/test_mcp_tool.py, tests/tui_gateway/test_compute_host_phase1.py, tests/tui_gateway/test_iso_certify_seam.py, tui_gateway/compute_host.py, tui_gateway/host_supervisor.py, tui_gateway/methods_session.py, tui_gateway/synthetic_turn.py, tui_gateway/ws.py, web/src/lib/gatewayClient.test.ts, web/src/lib/gatewayClient.ts, website/docs/user-guide/features/browser.md. The god files (gateway/run.py, agent/*helpers, hermes_state.py, cron/scheduler.py) stay conflicted: KEEP rows still touch them, so the win there is fewer hunks, not fewer files. ## 5. D2b registry write-backs owed (docs/sync/fork-features.json) @@ -170,11 +174,11 @@ Row view: 233/795 ledger-touching rows are DROP/SUPERSEDED; they hold 545/1,727 ## 6. The three answers Ace asked for -**What still provides value.** 672 rows (338,037 loc) KEEP on a measurement: the restart/resume/admission family (boot_resume_scheduled 1,190, dropbox_resume 1,410, turn_slot_acquire 2,208, restart_notice 455 fires), blackbox cost accounting (19k–47k turns/api-calls priced), relay-lane headers/pricing (16,913 bpx/bpr calls/7d), the footer and route/compaction announce families (registry 3/22/23/24, live config), Discord restart backfill (425 re-injected messages), cron fallback/pins (17 opt-in jobs, 521-job stores). Plus 93 UPSTREAM rows that are needed AND generic — value that should stop being ours. +**What still provides value.** 680 rows (342,361 loc) KEEP on a measurement: the restart/resume/admission family (boot_resume_scheduled 1,190, dropbox_resume 1,410, turn_slot_acquire 2,208, restart_notice 455 fires), blackbox cost accounting (19k–47k turns/api-calls priced), relay-lane headers/pricing (16,913 bpx/bpr calls/7d), the footer and route/compaction announce families (registry 3/22/23/24, live config), Discord restart backfill (425 re-injected messages), cron fallback/pins (17 opt-in jobs, 521-job stores). Plus 97 UPSTREAM rows that are needed AND generic — value that should stop being ours. -**What solves problems that no longer exist.** 174 rows are fixed upstream (take theirs at the next sync) and 153 rows DROP: never fired in the window, dormant surfaces (desktop D9 ×37, gemini/yunwu lanes, MoA/send_message tools dormant since July, /undo 2 uses, /merge 0), one-shot June LCM campaign harnesses, and a write-only reaction journal. 24 rows stay UNRESOLVED (silent guards with no log/DB signal — need a canary, not a guess). +**What solves problems that no longer exist.** 176 rows are fixed upstream (take theirs at the next sync) and 163 rows DROP: never fired in the window, dormant surfaces (desktop D9 ×37, gemini/yunwu lanes, MoA/send_message tools dormant since July, /undo 2 uses, /merge 0), one-shot June LCM campaign harnesses, and a write-only reaction journal. 0 rows stay UNRESOLVED: the 24 the lead left were measured and ruled by t_63023f77 (UNRESOLVED.md). -**What the maintenance burden actually is (measured).** 515,200 fork lines across 1,116 rows; 795 rows sit on files named in the sync ledgers and 458 on files that conflicted in all three syncs. DROP+SUPERSEDED rows carry 545 of 1,727 (32%) row×conflict-file incidences and 138,983 census loc; but only 31 ledger files are fully freed because KEEP rows still sit on the god files. The real reduction comes in two steps: (1) this audit's DROP/SUPERSEDED (fewer hunks per sync), (2) landing the UPSTREAM rows + registry 22/23/24, which is what empties gateway/run.py of fork hunks. +**What the maintenance burden actually is (measured).** 515,200 fork lines across 1,116 rows; 795 rows sit on files named in the sync ledgers and 458 on files that conflicted in all three syncs. DROP+SUPERSEDED rows carry 569 of 1,727 (33%) row×conflict-file incidences and 142,128 census loc; but only 33 ledger files are fully freed because KEEP rows still sit on the god files. The real reduction comes in two steps: (1) this audit's DROP/SUPERSEDED (fewer hunks per sync), (2) landing the UPSTREAM rows + registry 22/23/24, which is what empties gateway/run.py of fork hunks. ## 7. Open prerequisites before the sync takes upstream @@ -222,3 +226,8 @@ Post-ruling flips (Ace, 2026-09-27, card t_f29230a1): 8 DROP rows → KEEP (#562 nopr:36134d8944, nopr:051c2076e1, #22). §1/§2/§4/§6 above are re-rendered: KEEP 672, DROP 153. The §9 card arithmetic records what was created at handoff (254 rows); after the flips, 246 rows are DROP/UPSTREAM. The 8 flipped rows' slice cards were closed by the ruling. Details are in FINAL.md "Post-ruling flips (2026-09-27)". + +UNRESOLVED fold (2026-09-27, card t_12b66b00): the 24 rows above were measured and ruled by t_63023f77 (`UNRESOLVED.md`) and are +now applied by `final.build()`. §1/§2/§3/§4/§6 are re-rendered: KEEP 680, DROP 163, UPSTREAM 97, SUPERSEDED-BY-UPSTREAM 176, +UNRESOLVED 0. The 4 new UPSTREAM rows are listed in §3 with their t_63023f77 slice cards. Details are in FINAL.md +"UNRESOLVED rows folded (2026-09-27)". diff --git a/docs/plans/fork-pr-audit/lead/build_all.py b/docs/plans/fork-pr-audit/lead/build_all.py index 38cf83afa8777..e787b5723559a 100644 --- a/docs/plans/fork-pr-audit/lead/build_all.py +++ b/docs/plans/fork-pr-audit/lead/build_all.py @@ -69,6 +69,8 @@ def cand_src(p): keys = [r['key'] for r in rows] assert len(keys) == len(set(keys)) == 1116, len(keys) assert set(ROW) == set(keys) +bad = [(k, ROW[k]['final'], x['verdict']) for k, x in final.rulings.UNRESOLVED_RULINGS.items() if ROW[k]['final'] != x['verdict']] +assert not bad, bad # every t_63023f77 ruling, auto followers included, lands in rows.json open(L + 'rows.json', 'w', encoding='utf-8').write(dump_rows(ROW)) check_cards(ROW, json.load(open(L + 'cards.json', encoding='utf-8'))) if __name__ == '__main__': diff --git a/docs/plans/fork-pr-audit/lead/final.py b/docs/plans/fork-pr-audit/lead/final.py index 20ee22c25fe13..dbbcb6d887de8 100644 --- a/docs/plans/fork-pr-audit/lead/final.py +++ b/docs/plans/fork-pr-audit/lead/final.py @@ -49,4 +49,10 @@ def build(): for k, (card, ev) in rulings.POST_RULING_2026_09_27.items(): assert out[k]['final'] == 'DROP', (k, out[k]['final']) # the ruling flips DROP rows only out[k]['final'], out[k]['why'] = 'KEEP', rulings.POST_RULING_TAG + ev + f' (slice card {card})' + for k, x in rulings.UNRESOLVED_RULINGS.items(): + if k not in out: # auto rows follow their code row in build_all.py + continue + assert out[k]['final'] == 'UNRESOLVED', (k, out[k]['final']) # the ruling resolves UNRESOLVED rows only + assert x['verdict'] in VALID and x['verdict'] != 'UNRESOLVED', (k, x['verdict']) + out[k]['final'], out[k]['why'] = x['verdict'], rulings.unresolved_why(x, str(out[k]['why'] or '')) return out diff --git a/docs/plans/fork-pr-audit/lead/render.py b/docs/plans/fork-pr-audit/lead/render.py index 27bce40fe4864..7bd5e5638cb77 100644 --- a/docs/plans/fork-pr-audit/lead/render.py +++ b/docs/plans/fork-pr-audit/lead/render.py @@ -2,6 +2,7 @@ import sys, os, re, json, collections sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from common import * +import rulings ROW = json.load(open(L + 'rows.json', encoding='utf-8')) CARDS = json.load(open(L + 'cards.json', encoding='utf-8')) @@ -13,6 +14,13 @@ key2card[k] = IDS.get(c['name'], 'card:' + c['name']) SPLIT = json.load(open(L + 'split_ids.json', encoding='utf-8')) if os.path.exists(L + 'split_ids.json') else {} key2card.update(SPLIT) +for k, (card, _ev) in rulings.POST_RULING_2026_09_27.items(): # the ruling's slice cards (off cards.json since t_04cd162a) + key2card.setdefault(k, card) +for k, x in rulings.UNRESOLVED_RULINGS.items(): # t_63023f77 slice cards (KEEP rulings have none) + if x.get('card'): + key2card.setdefault(k, x['card']) +# Hand-written sections appended after the rendered body; render keeps them from the existing file. +TAILS = {'FINAL.md': '\n## Post-handoff lead rulings', 'ROLLUP.md': '\n## 8. Slice cards'} ORDER = ['gateway', 'agent', 'hermes_cli', 'plugins', 'cron+tools', 'scripts+misc', 'auto', 'auto-cherry-pick', 'auto-desktop-retired'] VS = ['KEEP', 'UPSTREAM', 'SUPERSEDED-BY-UPSTREAM', 'DROP', 'UNRESOLVED'] @@ -122,6 +130,9 @@ def rollup_md(): for cd in CARDS: if cd['verdict'] == 'UPSTREAM': out.append(f"| {IDS.get(cd['name'], cd['name'])} | {', '.join(cd['keys'])} | {', '.join(b for b in cd['branches'] if '/revert-' not in b) or 'to build'} |") + for k, x in rulings.UNRESOLVED_RULINGS.items(): + if x['verdict'] == 'UPSTREAM' and ROW[k]['final'] == 'UPSTREAM': + out.append(f"| {x['card']} | {k} | to build (t_63023f77) |") # conflicts led = ledger_sets() out += ['', '## 4. Expected reduction in parity-merge conflicts', '', @@ -162,12 +173,13 @@ def rollup_md(): 'family (boot_resume_scheduled 1,190, dropbox_resume 1,410, turn_slot_acquire 2,208, restart_notice 455 fires), blackbox cost ' 'accounting (19k–47k turns/api-calls priced), relay-lane headers/pricing (16,913 bpx/bpr calls/7d), the footer and route/compaction ' 'announce families (registry 3/22/23/24, live config), Discord restart backfill (425 re-injected messages), cron fallback/pins ' - '(17 opt-in jobs, 521-job stores). Plus 93 UPSTREAM rows that are needed AND generic — value that should stop being ours.', + f'(17 opt-in jobs, 521-job stores). Plus {tot["UPSTREAM"]} UPSTREAM rows that are needed AND generic — value that should stop being ours.', '', f'**What solves problems that no longer exist.** {tot["SUPERSEDED-BY-UPSTREAM"]} rows are fixed upstream (take theirs at the next sync) ' f'and {tot["DROP"]} rows DROP: never fired in the window, dormant surfaces (desktop D9 ×37, gemini/yunwu lanes, MoA/send_message ' 'tools dormant since July, /undo 2 uses, /merge 0), one-shot June LCM campaign harnesses, and a write-only reaction journal. ' - f'{tot["UNRESOLVED"]} rows stay UNRESOLVED (silent guards with no log/DB signal — need a canary, not a guess).', + + (f'{tot["UNRESOLVED"]} rows stay UNRESOLVED (silent guards with no log/DB signal — need a canary, not a guess).' if tot['UNRESOLVED'] else + f'0 rows stay UNRESOLVED: the {len(rulings.UNRESOLVED_RULINGS)} the lead left were measured and ruled by t_63023f77 (UNRESOLVED.md).'), '', f'**What the maintenance burden actually is (measured).** {alloc:,} fork lines across 1,116 rows; 795 rows sit on files named in the ' f'sync ledgers and 458 on files that conflicted in all three syncs. DROP+SUPERSEDED rows carry {cfd:,} of {cft:,} ({100*cfd/cft:.0f}%) ' @@ -186,9 +198,15 @@ def rollup_md(): return '\n'.join(out) + '\n' +def write(name, body): + old = open(D + name, encoding='utf-8').read() if os.path.exists(D + name) else '' + i = old.find(TAILS[name]) + open(D + name, 'w', encoding='utf-8').write(body + (old[i:] if i >= 0 else '')) + + if __name__ == '__main__': - open(D + 'FINAL.md', 'w', encoding='utf-8').write(final_md()) - open(D + 'ROLLUP.md', 'w', encoding='utf-8').write(rollup_md()) + write('FINAL.md', final_md()) + write('ROLLUP.md', rollup_md()) txt = open(D + 'FINAL.md', encoding='utf-8').read() keys = [r['key'] for r in census()] body = txt.split('## Conflicts')[0] diff --git a/docs/plans/fork-pr-audit/lead/rows.json b/docs/plans/fork-pr-audit/lead/rows.json index ac08e175a8e00..4c6b4164f4f3c 100644 --- a/docs/plans/fork-pr-audit/lead/rows.json +++ b/docs/plans/fork-pr-audit/lead/rows.json @@ -9625,8 +9625,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "KEEP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): reverting #780 alone would leave #764's consumer with 5 known P1 defects (dropped/duplicated/false notices). Its fate is #764's: if #764 is later dropped, drop both. Measured: follows #764 (lead: KEEP, 2 bridge-side 'confab notice emitted out-of-band' across 20 bridges). Consumer-side count: messages.display_kind='confab_notice' = 2 rows in root state.db (2026-09-21 07:58 and 08:09 UTC, source cli), 0 in the other 13 profile DBs; 0 after #780 merged (09-21 18:02 UTC) over 58,652 later assistant rows. Low fire, but the rows exist and #780 is the P1 correctness fix to a KEPT consumer. Upstream: n/a (fleet-only)", "x": { "branch": null, "evidence": { @@ -17114,8 +17114,8 @@ "auditor": "DROP", "adv": "needs-lead", "adv_new": "lead: test before revert", - "final": "UNRESOLVED", - "why": "LEAD: removed from the #49 revert unit. The empty-resume guard is not undo-only: upstream synthesizes an empty-text internal resume event (up gateway/run_startup.py:611) with no empty-row guard. Needs a repro on upstream before either DROP or UPSTREAM; unmeasured either way.", + "final": "SUPERSEDED-BY-UPSTREAM", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): upstream solved the same empty-row bug (persists the note instead of dropping the row); fork flag+drop machinery is redundant. Measured: live call on upstream c15ebb1435: gateway.run._prepare_resume_pending_message('restart','') returns a non-empty persist text (the recovery note), so the synthesized empty-text resume event (up gateway/run_startup.py:611) no longer yields an empty user row; real text still persisted clean. Upstream: GREEN: fixed by upstream 0cc26777bb 'fix(gateway): persist resume recovery notes' + c69b6471e6 (#86580), consumed at up gateway/run_turn_runner.py:1647 (slice card t_da2a5730) | prior lead note: LEAD: removed from the #49 revert unit. The empty-resume guard is not undo-only: upstream synthesizes an empty-text internal resume event (up gateway/run_startup.py:611) with no empty-row guard. Needs a repro on upstream before either DROP or UPSTREAM; unmeasured either way.", "x": { "verdict": "DROP", "problem": "empty user row persisted on auto-resume (the /undo '(no text)' bug)", @@ -22270,8 +22270,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "KEEP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): guard on a destructive path with two live-board incidents in the last 5 days; silent by design (a refusal raises to the caller). Measured: trigger measured in the live board: incident card t_e1c2dae5 (2026-09-21 14:51, 'a probe that redirected HOME still wrote to the live board'), and kanban.db task_events shows 21 review_stale_alerted events on 17 cards dated 2026-09-22 UTC, the burst the PR describes. Repro on fork 858ee59722: test_diverged_pin_cannot_create_a_task_or_append_an_event and test_pin_contradicting_explicit_board_arg_REFUSES pass (part of a 175-test narrow run via test-gate on ACE-MEDIA). Upstream: RED by read: upstream reads the same board-path pin env but has no KanbanPinDivergenceError / refusal", "x": { "verdict": "UNRESOLVED", "problem": "fix(kanban): refuse destructive writes when pin contradicts requested board (t_e1c2dae5) (#907) \u2014 --- =...` hint for those splits in the shell. Fork 858ee59722: tests/hermes_cli/test_cli_hint.py passes in the 175-test narrow run (test-gate, ACE-MEDIA). Upstream: n/a: survivor/hint path is fork-only (hint_arg, kanban_survivor absent upstream)", "x": { "verdict": "UNRESOLVED", "problem": "fix(cli): a printed --flag hint must parse as pasted, for any repo key or path (#879) \u2014 `--survivor-pr =owner/repo#N`. For a key beginning with `-` argparse binds the token as an option and refuses (`expected one argument`); measured against the real `hermes kanban complete` parser, 3 of 7 key shapes failed to round-trip. The equals form works but the hint neve", @@ -22896,8 +22896,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "KEEP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): 112 loc closing a measured second rw door to the live board. Measured: trigger measured: live card t_880f5d17 (2026-09-22 05:05, argus) records repair_db() PROCEEDING from a pytest context and leaving .init.lock + .bak beside a production board. Fork repro: tests/hermes_cli/test_kanban_live_board_write_guard.py (test_test_context_refused_on_live_board etc.) pass in the same 175-test narrow run. Upstream: n/a: fork-only kanban path (fork kanban_db.py diverged from upstream split modules)", "x": { "verdict": "UNRESOLVED", "problem": "fix(kanban): gate repair_db(), the second rw door to the live board (t_880f5d17) (#858) \u2014 #853 put `_assert_live_board_write_allowed()` at `connect()`. `connect()` is not the only read-write door to a `kanban_db_path()`-derived board. `repair_db()` (`hermes kanban repair`) resolves the same way and opens the file rw via `_sqlite_connect()` **without calling `connect()", @@ -23883,8 +23883,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "KEEP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): fires ~19x/day on the live board; fork review lane depends on it. Measured: DB signal exists after all: task_events 'blocked' payload carries source_status. Since merge (2026-09-21 18:51 UTC): 76 blocks with source_status='review' on 68 cards, and 84 'unblocked' events restoring status='review' (85 with resume_status='review', 70 cards). Upstream: RED by read: up block_task UPDATE@hermes_cli/kanban_db.py:3287 still limits to status IN ('running','ready'), so a review card cannot be parked", "x": { "verdict": "UNRESOLVED", "problem": "fix(kanban): park a review card \u2014 block accepts review, unblock restores it, triage-resolve names the verb (#798) \u2014 Three one-line-class fixes so a **finished-but-unclosable** card can be parked without a hand DB edit. Measured 2026-09-20 on live cards `t_3fd73eba` / `t_86d437a7`: a card whose implementation was landed and **approved**, but whose `kanban_complete` was structurally refused by t", @@ -25160,8 +25160,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "DROP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): warning-text fix for a model-string form nobody uses; never fires. Measured: 0/34,120 sessions across 12 profile state.db (2026-05-08..2026-09-26) have model LIKE 'name:%'; 0 config.yaml/cron jobs use a name:/ string; 0 'could not verify name:' lines in ~/.hermes/logs + profiles/*/logs. Upstream: RED by read: up validate_requested_model@hermes_cli/models_validate.py:780 has no name: strip (relocated from models.py) (slice card t_111298c6)", "x": { "verdict": "UNRESOLVED", "problem": "fix(models): strip relay routing prefix before model-listing lookup (#620) \u2014 A relay/pool provider takes a model string shaped `name:/` \u2014 the `name:/` prefix pins **which upstream subscription** serves the request; only the final segment is the model id the endpoint's `/v1/models` listing contains. `validate_requested_model()` compared", @@ -25513,8 +25513,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "KEEP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): decoys are being created weekly; the reader half is what stops a script reading them as an empty board. Follow-up: something other than connect() still creates the stubs (a literal '*.db' = an unexpanded shell glob), so the source half does not cover all creators. Measured: trigger still occurs: 4 zero-byte .db decoys on the LIVE board tree right now (~/.hermes/kanban/kanban.db 09-22 01:01, boards/'*.db' 09-22 09:41, board.db 09-24 07:35, default.db 09-24 17:21; all created after #579 merged 08-11). Repro on a zero-byte kanban.db in a temp home: fork connect_readonly -> KanbanDbNotABoardError; upstream c15ebb1435 has no read-only reader and its connect() silently turns the decoy into a real empty board (0 tasks, 0 -> 4096 bytes); fork connect() does the same. Upstream: RED (live call): up connect@hermes_cli/kanban_db_connect.py:668 initialises schema in a zero-byte file", "x": { "verdict": "UNRESOLVED", "problem": "fix(kanban): zero-byte board .db files must not exist, and must not read as empty boards (#579) \u2014 A zero-byte `.db` is worse than a missing one. `sqlite3.connect()` on an **existing** zero-byte file succeeds even with `mode=ro`, and reports an empty `sqlite_master` \u2014 so a reader concludes *\"this board is empty\"* instead of *\"I opened the wrong file\"*. Measured on a live fleet", @@ -26499,8 +26499,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "DROP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): never contends under fleet load; 473/474 loc across 5 files that conflict in all 3 syncs. #441 is a re-land of #215: revert as one unit incl. the tui_gateway/ws.py import. Measured: the gate logs INFO 'session_db_heavy_read queue_wait=... outcome=acquired' whenever a read waits >=1 ms and WARNING '... outcome=shed' on busy; the dashboard process's hermes_cli.* loggers demonstrably reach ~/.hermes/logs/agent.log (8 web_server + 153 dashboard_auth.registry lines, same process). Count: 0 'session_db_heavy_read' lines in root agent.log* (2026-09-25 04:05..18:11) and errors.log* (02:58..18:20), and 0 across every file under ~/.hermes/logs + profiles/*/logs. Not one heavy read queued even 1 ms in the window. (Live counter /api/status.session_db_heavy_reads is auth-gated; not read.). Upstream: RED by read (no admission gate; up offloads per router + bounds read connections in hermes_state_readpool.py) (slice card t_7c17872a)", "x": { "verdict": "UNRESOLVED", "problem": "feat(tui_gateway): bound heavy session reads (#441) \u2014 A single Discord voice message posted its \ud83c\udf99\ufe0f transcript echo **twice** \u2014 two identical messages 4.3s apart. The gateway log confirms the same `.ogg` was transcribed twice: ``` 09:27:37,033 INFO tools.transcription_tools: Transcribed audio_b8e37bff145d.ogg via local whisper (base,", @@ -26898,8 +26898,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "UPSTREAM", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): generic 1-line fix, reproduced on upstream, provider in live use; effect is a stale error record that misleads triage (07-14 incident). Measured: live repro in temp HOME: seed auth.json xai-oauth.last_auth_error, call _save_xai_oauth_tokens(new tokens): upstream c15ebb1435 -> last_auth_error present=True (RED); fork 858ee59722 -> False. xai-oauth is live on root auth.json (last_refresh 2026-09-25T21:25Z), so the path runs in production. Upstream: RED (live): up _save_xai_oauth_tokens@hermes_cli/auth_xai.py:127 does state.update(...) and never pops last_auth_error (slice card t_4bf92fd0)", "x": { "verdict": "UNRESOLVED", "problem": "fix(auth): clear stale xai-oauth last_auth_error on successful token save (#326) \u2014 **Observed 2026-07-14:** a stale 07-13 `invalid_grant` (\"Refresh token has been revoked\") record was still sitting in `auth.json` while grok-4.5 was serving `HTTP 200` and a force-refresh succeeded cleanly. The actual cron fallback that fired was an unrelated transient xAI `429 \"", @@ -27039,8 +27039,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "SUPERSEDED-BY-UPSTREAM", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): upstream solved the same 10s sidebar cost the same way. Measured: live call on upstream c15ebb1435 with a 4-profile temp home and list_profiles() wrapped with a call counter: web_routers.profiles._profile_targets (serves GET /api/profiles/sessions + /sidebar) and web_server_cron._cron_profile_dicts both return all 4 profiles with 0 list_profiles() calls. Upstream: GREEN: upstream uses profiles_to_serve() (directory read) for both hot paths \u2014 de626c5a36 (_profile_targets) + 4590ef8b58 '#114041 polled profile lists never walk skill trees'; the auditor read of 'keeps full list_profiles()' was wrong (slice card t_ee9353f2)", "x": { "verdict": "UNRESOLVED", "problem": "perf(dashboard): lightweight profile listing for session-list + cron aggregators (10s -> ~1s) (#291) \u2014 `/api/profiles/sessions?profile=all` (fired on every desktop sidebar refresh) takes **10\u201312s** with 7 profiles; `/api/cron/jobs` (also a boot-path RPC) stacks on top. py-spy on the live dashboard: **~70% of the wall time is inside `list_profiles()`** \u2014 per profile it yaml-parses ", @@ -27250,8 +27250,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "DROP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): never contends under fleet load; 473/474 loc across 5 files that conflict in all 3 syncs. #441 is a re-land of #215: revert as one unit incl. the tui_gateway/ws.py import. Measured: the gate logs INFO 'session_db_heavy_read queue_wait=... outcome=acquired' whenever a read waits >=1 ms and WARNING '... outcome=shed' on busy; the dashboard process's hermes_cli.* loggers demonstrably reach ~/.hermes/logs/agent.log (8 web_server + 153 dashboard_auth.registry lines, same process). Count: 0 'session_db_heavy_read' lines in root agent.log* (2026-09-25 04:05..18:11) and errors.log* (02:58..18:20), and 0 across every file under ~/.hermes/logs + profiles/*/logs. Not one heavy read queued even 1 ms in the window. (Live counter /api/status.session_db_heavy_reads is auth-gated; not read.). Upstream: RED by read (no admission gate; up offloads per router + bounds read connections in hermes_state_readpool.py) (slice card t_7c17872a)", "x": { "verdict": "UNRESOLVED", "problem": "feat(tui_gateway): bound heavy session reads (#215) \u2014 Phase 2 of docs/desktop/2026-07-05-session-list-loop-starvation-PRD.md (D-4/D-5/INV-4/INV-6). Shared per-loop async SessionDB heavy-read gate (default 2, config dashboard.heavy_read_max_concurrency, lazy/loop-bound NOT import-frozen). REST + WS list_sessions_rich (and siblings) a", @@ -27401,8 +27401,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "UPSTREAM", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): duplicate picker rows reproduce on upstream with the fleet's live claude-apr catalog shape; fix is generic (own-slug prefix only). Measured: live trigger shape confirmed: ~/.hermes/plugins/model-providers/claude-apr fallback_models = 'claude-apr/' x5 (namespaced with own slug) while GET 127.0.0.1:18810/anthropic/v1/models returns bare ids. Real provider_model_ids() with that exact profile shape (stubbed fetch): upstream c15ebb1435 -> 10 rows (every model twice); fork -> 5. Upstream: RED (live call): up provider_model_ids@hermes_cli/models.py:1683 merges curated-namespaced + live-bare without canonicalizing (slice card t_db92203d)", "x": { "verdict": "UNRESOLVED", "problem": "fix(models): dedup namespaced curated vs bare live ids in provider_model_ids merge (#166) \u2014 Every model for `claude-api-proxy`, `claude-api-proxy-f1..f5`, `claude-bridge`, `claude-bridge-f1..f5` (and any plugin provider whose curated catalog is namespaced) shows up **twice** in the desktop Models dialog / model picker \u2014 e.g. Fable 5, Opus 4-8, Sonnet 5, Haiku 4-5 each l", @@ -27443,8 +27443,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "UPSTREAM", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): Apollo's own root config hits it every /model picker open; generic namespace-aware presence check. Measured: live trigger: root ~/.hermes/config.yaml model.default=claude-fable-5-1 (bare), provider claude-bpr whose plugin fallback_models are 'claude-bpr/'. Upstream _finalize_picker_rows(row models=['claude-bpr/claude-fable-5-1','claude-bpr/claude-opus-5-5'], current='claude-fable-5-1') -> ['claude-fable-5-1','claude-bpr/claude-fable-5-1',...] (current model listed twice). Upstream: RED (live call): up _finalize_picker_rows@hermes_cli/model_switch_providers.py:1280 uses plain `current_model not in models` (slice card t_bc006a95)", "x": { "verdict": "UNRESOLVED", "problem": "fix(model-picker): don't duplicate the current model when its catalog entry is namespaced (#135) \u2014 The `/model` picker (Discord dropdown, screenshot from Ace) listed **`claude-opus-4-8` twice** when it was the current model \u2014 once injected at the top, once from the provider's curated catalog. ## Root cause `list_authenticated_providers` (`hermes_cli/model_switch.py`) injects t", @@ -27628,8 +27628,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "DROP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): launcher never used in window; upstream client unaffected; --remote-allow-origins=* also widens the debug port to any web origin. Measured: live CDP repro, Chrome 154.0.8037.57 headless on temp profiles: with upstream launch args (no --remote-allow-origins) the handshake WITHOUT an Origin header (websockets default = upstream tools/browser_cdp_tool.py:176) -> OK; only a handshake WITH an Origin header -> HTTP 403. The launcher that this PR edits logged 0 'browser debug launch: spawned' lines across ~/.hermes/logs + profiles/*/logs; 0 CDP 403/'Rejected an incoming WebSocket' lines. Upstream: GREEN for upstream's own CDP client (no Origin sent); 403 only reproduces for Origin-sending clients (slice card t_d3825c87)", "x": { "verdict": "UNRESOLVED", "problem": "fix(browser): add --remote-allow-origins to CDP debug launch for Chrome 111+ (#33) \u2014 Chrome/Chromium **111+** rejects CDP WebSocket handshakes with **HTTP 403** unless the connecting origin is explicitly allowed (the DevTools remote-origin lockdown, [crrev/c/4106462](https://chromium-review.googlesource.com/c/chromium/src/+/4106462)). Playwright's `connect_over_c", @@ -27795,8 +27795,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "DROP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): guard on a dead module; fires 0 by construction. Measured: caller count: build_recap (the only public entry of hermes_cli/session_recap.py) has 0 non-test callers on fork 858ee59722 and 0 on upstream c15ebb1435 (grep over *.py/*.ts/*.tsx/*.yaml); the module is not imported by gateway/ or hermes_cli anywhere. The guard's code path cannot run. Upstream: n/a: upstream session_recap.py:169 is equally unreached; upstream's extract_local_files@gateway/platforms/base.py:3283 is only called from gateway/kanban_watchers.py:162 (slice card t_08d470c3)", "x": { "verdict": "UNRESOLVED", "problem": "fix(recap): backtick-wrap Files touched paths to stop gateway auto-attach \u2014 The /status session recap lists touched file paths via _shortened_path, which emits a bare absolute or ~-relative path for any file outside the gateway cwd. The gateway scans outbound message text for bare local file paths and auto-uploads matches as native attachments (gateway/p", @@ -30123,8 +30123,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "KEEP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): the operator reads it ~3x/day; #18 layers on #17, keep as one unit. 508 loc over 15 locale files is the cost; candidate for upstreaming as one PR later. Measured: /compress usage counted from gateway logs: 19 \"[Discord] slash '/compress' invoked\" lines by Ace's user id in ~/.hermes/logs/gateway.log* (window 2026-09-20..09-25, ~6 days: 10 on 09-20, 4 on 09-21, 5 on 09-23) \u2014 each renders this readout. Upstream: read-only: upstream /compress reply has no measured-context / full-request lines (readout keys fork-only)", "x": { "verdict": "UNRESOLVED", "problem": "fix(compress): two-line readout \u2014 Chat size + Full request size (#18) \u2014 Reworks the `/compress` token readout per request into **two clearly labelled lines** instead of one ambiguous estimate: ``` \ud83d\udddc\ufe0f Compressed: 181 \u2192 181 messages Chat size: ~29,521 \u2192 ~27,300 tokens (excludes system, tools, tool results) Full request size: 290,310 \u2192 ~265,000 tokens (", @@ -30183,8 +30183,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "KEEP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): the operator reads it ~3x/day; #18 layers on #17, keep as one unit. 508 loc over 15 locale files is the cost; candidate for upstreaming as one PR later. Measured: /compress usage counted from gateway logs: 19 \"[Discord] slash '/compress' invoked\" lines by Ace's user id in ~/.hermes/logs/gateway.log* (window 2026-09-20..09-25, ~6 days: 10 on 09-20, 4 on 09-21, 5 on 09-23) \u2014 each renders this readout. Upstream: read-only: upstream /compress reply has no measured-context / full-request lines (readout keys fork-only)", "x": { "verdict": "UNRESOLVED", "problem": "fix(compress): show real measured context alongside estimate; estimate over full transcript (#17) \u2014 `/compress` reported two token numbers that disagreed by ~10x with no explanation (flagged by diagnosing the difference between `/usage` Context % and `/compress` Approx request size). ### Root cause - **`/usage` Context %** = real provider-returned `prompt_tokens` \u2014 counts syste", @@ -30292,8 +30292,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "DROP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): never fired, verified clean at landing and now; 121 loc in providers/__init__.py which diverges +184/-346 from upstream. Measured: the in-repo diff is only C-2 (lint_provider_collisions + a doctor line; C-1 normalisation is not in this commit). Ran the fork lint against the LIVE plugin registry (real HOME, read-only): 0 collisions. 'Provider collision lint' warning lines: 0 across ~/.hermes/logs + profiles/*/logs. Callers: doctor.py:1928 only; no cron job or script runs it (the 'cli-parity sweep' caller named in the docstring does not exist in cron/jobs.json or ~/.hermes/scripts). Upstream: n/a (fleet copy-paste fN plugin hazard) (slice card t_c4b3a02c)", "x": { "branch": null, "evidence": { @@ -30600,8 +30600,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "DROP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): never fires on this fleet; 629 loc incl. gateway/run.py (conflicts in all 3 syncs). If wanted for Windows users, upstream #889+#894 together as one PR. Measured: trigger = a whitespace in the home / interpreter / skill-category / plugin path pasted into a hint. Measured on both fleet hosts: Mac Studio HOME=/Users/alexgierczyk, ACE-AI HOME=/home/ace; 0 of 18 profile dirs, 0 skill category dirs (skills, skills-shared, fork skills/ + optional-skills/), 0 venv interpreter paths contain whitespace. The printed hints round-trip unquoted here. Upstream: not applicable as-is: routes through hint_value from fork-only #889 (slice card t_ceb111f9)", "x": { "branch": null, "evidence": { @@ -30994,8 +30994,8 @@ "auditor": "SUPERSEDED-BY-UPSTREAM", "adv": "needs-lead", "adv_new": "lead: upstream claim falsified", - "final": "UNRESOLVED", - "why": "LEAD: auditor premise falsified (upstream prompt.submit has no empty-text check); fork guard fires unmeasured. Needs a count of 4020 rejections before KEEP/UPSTREAM.", + "final": "DROP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): guard's path carries ~no traffic here and the client that looped is gone; not worth 237 loc on 2 files that conflict every sync. Reopen if a desktop empty-submit loop recurs. Measured: 4020 rejections are not logged anywhere (0 hits for 4020 in dashboard.{out,err}.log, dashboard-auth.log); a count needs live instrumentation and ~/.hermes is read-only for this card. Substitute measurement of the path's traffic: root state.db has 8 source='tui' sessions out of 15,265 (all 2026-08-20; 2026-05-08..2026-09-26); 0 empty user rows ever from source tui. The triggering client (fork desktop reconnect loop) is retired by D9 (desktop upstream-owned since 2026-08-08). Upstream: RED by read: up prompt.submit@tui_gateway/methods_prompt.py:564 has no empty-text rejection (auditor's 'absorbed' premise falsified) (slice card t_28b13602) | prior lead note: LEAD: auditor premise falsified (upstream prompt.submit has no empty-text check); fork guard fires unmeasured. Needs a count of 4020 rejections before KEEP/UPSTREAM.", "x": { "verdict": "SUPERSEDED-BY-UPSTREAM", "problem": "tui_gateway accepted an empty prompt.submit and ran model-switch side effects on a no-op switch", @@ -31316,8 +31316,8 @@ "auditor": "UPSTREAM", "adv": "keep-unproven", "adv_new": null, - "final": "UNRESOLVED", - "why": "LEAD: merge-queue red NOT reproduced on either tree (17 passed x3 + 6/6 concurrent both); evidence = PR body only.", + "final": "UPSTREAM", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): the flake was real (5% of merge-queue runs over a week) and the fix removed it; test-only, generic. Branch already built: audit/scripts_misc/upstream-695 @ 830f68a5dc (hand port off upstream/main). Measured: merge-queue counts via REST job logs (ANSI-stripped, grep 'FAIL ... virtualHistoryOffsetCache.test.ts'): BEFORE the fix (merge_group runs 2026-09-07..09-14 13:52, 201 runs) 10 'JS & TS checks' jobs failed on this exact file (09-07, 09-08, 09-09 x4, 09-10 x2, 09-14 x2). AFTER (2026-09-14 14:00..09-26, 2,297 runs, 120 failed runs) 0 failures of this file (1 JS&TS failure total, a different test). Upstream: RED by read: up ui-tui/src/__tests__/virtualHistoryOffsetCache.test.ts lacks the act()/IS_REACT_ACT_ENVIRONMENT settle (slice card t_c1c5035d) | prior lead note: LEAD: merge-queue red NOT reproduced on either tree (17 passed x3 + 6/6 concurrent both); evidence = PR body only.", "x": { "branch": "audit/scripts_misc/upstream-695", "evidence": { @@ -38904,8 +38904,8 @@ "auditor": "UNRESOLVED", "adv": "none", "adv_new": null, - "final": "UNRESOLVED", - "why": "unchallenged by adversary (KEEP/UPSTREAM rows are only challenged when flagged)", + "final": "DROP", + "why": "RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): 0 fires in ~80 days / 3,130 writes; the repo script is dead. 554 loc in tools/skill_manager_tool.py + agent/skill_utils.py (2 files, conflict in 2 syncs). Measured: G1/G2 refusals return to the model as skill_manage tool results. Across all 14 profile state.db since the commit (2026-07-08..2026-09-26): 3,130 skill_manage tool results, 0 containing 'Refusing to author', 'reserved work-queue-note prefix' or 'Post-write placement check failed' (the 5 LIKE hits are all read_file/terminal/execute_code rows showing the SOURCE template '{name}', incl. this audit). scripts/local-skill-leak-check.py has 0 callers (launchd ai.hermes.skill-collision-check runs the skills-shared copy). Upstream: n/a (fleet-only skills-shared layout) (slice card t_cad124fd)", "x": { "branch": null, "evidence": { @@ -42202,9 +42202,9 @@ "auditor": "(auto)", "adv": "n/a", "adv_new": null, - "final": "UNRESOLVED", + "final": "DROP", "follows": "nopr:8dcc69611c", - "why": "auto: follows nopr:8dcc69611c (scripts_misc) by path; same fate.", + "why": "auto: follows nopr:8dcc69611c (scripts_misc) by path; joins that card.", "x": { "branch": null }, diff --git a/docs/plans/fork-pr-audit/lead/rulings.py b/docs/plans/fork-pr-audit/lead/rulings.py index 16b30d5e96ce8..a907ee414e720 100644 --- a/docs/plans/fork-pr-audit/lead/rulings.py +++ b/docs/plans/fork-pr-audit/lead/rulings.py @@ -1,4 +1,5 @@ """Lead rulings (t_03e35f0e). Every entry: key -> (FINAL verdict, reason). Recorded verbatim in FINAL.md.""" +import json, os ACE14 = ('DROP', 'LEAD: measurement supports the drop (/undo 2, /redo 0 native invocations 2026-05-10..09-25) but registry ' 'entry 14 is lifecycle=fork-permanent (Ace ruling). DROP is ACE-GATED: the slice card must not merge until Ace ' @@ -185,3 +186,18 @@ 'attribution check passed only via this mapping. The 2 nopr:40a6040932 lines stay DROP on ' 'audit/scripts_misc/revert-author-map-stale @4053ee9081.'), } + +# t_63023f77 measured and ruled the 24 rows the lead left UNRESOLVED (UNRESOLVED.md). The ruling file is the source; +# final.build() applies it to code rows, build_all.py checks that the auto follower (nopr:08fc3aff65) inherits the same verdict. +UNRESOLVED_RULING_TAG = 'RULED 2026-09-27 (t_63023f77, UNRESOLVED.md; was UNRESOLVED): ' +UNRESOLVED_RULINGS = json.load(open(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + 'unresolved.verdicts.json'), encoding='utf-8')) + + +def unresolved_why(x, prior): + why = UNRESOLVED_RULING_TAG + x['why'] + '. Measured: ' + x['measure'] + '. Upstream: ' + x['upstream'] + if x.get('card'): + why += f' (slice card {x["card"]})' + if prior.startswith('LEAD'): + why += ' | prior lead note: ' + prior + return why