fix(webui): authority-aware ownership + redacted contamination text (#641)
Addresses the two blockers from the PR #898 review ata81db754. B1 - degraded ownership inventory was rendered as affirmative absence. _build_session_rows read the sessions/leases/locks sections without consulting their status, so a session row emitted lease_ids=() and worktree_paths=() whether the session genuinely held nothing or the lease store simply could not be read. The renderer printed both as "none" and "unbound", contradicting the ownership_authority_complete invariant documented on InventorySnapshot. SessionRow now carries lease_authority and worktree_authority. A worktree binding is correlated through lease work numbers, so it is unproven when either the leases or the locks section fails to read -- this covers the narrow variant where locks hold real worktree paths but a degraded leases section leaves work_numbers empty. The renderer emits "unknown (inventory <status>)" with an authority-unproven badge instead of none/unbound, the card names the unreadable sections, and an empty session list from an unreadable sessions section no longer reads as "no sessions recorded". snapshot_to_dict exports ownership_authority_complete, ownership_section_status, and per-row lease_authority / worktree_authority so /api/sessions consumers can distinguish the two cases. B2 - contamination payload strings bypassed redaction. _inspect_contamination copied command_summary, session_id, role and reason_class out of the marker payload with only str(), while every inventory-sourced field on the same page arrives through webui.inventory.scrub(). The write-time redactor stable_branch_push_guard.redact_command is a narrow denylist that leaves absolute $HOME paths, -H 'X-Api-Key: <value>', --password <value>, and PRIVATE_KEY=<value> intact, and this is the first web surface to render command_summary at all. Adds webui.inventory.scrub_text(), which collapses $HOME and redacts credential-shaped tokens and URL userinfo anywhere inside a string rather than only at its start, and routes the marker payload through it. scrub() and every existing caller are untouched. The command_summary field is kept: it is the #630 evidence naming which daemon was killed. The module docstring claiming absolute paths were already collapsed is corrected. Also: removes the locks_by_session_hint dead loop and its discard (N1), adds the missing trailing newline to webui/runtime_views.py (N4), drops an unused dataclasses.field import, and documents both honesty rules in docs/webui-local-dev.md. Tests: tests/test_webui_sessions_view.py grows from 12 to 26 cases, covering degraded and unavailable ownership sections in both the HTML and JSON paths, the locks-readable/leases-degraded variant, a guard against over-correcting clean inventory into "unknown", the previously untested expired-lease flag, HTML escaping of hostile values in clean and degraded renders, and each secret class the write-time denylist misses. The STATUS_UNAVAILABLE import that was present but unused is now exercised. Validation, from the issue worktree with venv/bin/python (Python 3.14.5, pytest 9.1.1): pytest tests/test_webui_sessions_view.py tests/test_webui_*.py -q -> 512 passed, 376 subtests (was 498 / 372; +14 new tests) pytest tests/test_issue_854_semantic_container_exclusion.py -q -> 13 passed, 8 subtests 13-file runtime/health/inventory/restart set -> 1 failed, 223 passed; the single failure is test_runtime_clarity.py::TestRuntimeClarity:: test_activate_profile_succeeds_when_enabled, the identical test and assertion the reviewer recorded on master at7af40fb5, so it is baseline-equivalent and not introduced here. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -307,6 +307,26 @@ reconnect/operator restart docs (`docs/mcp-namespace-eof-recovery.md`,
|
||||
document). The page does **not** restart, kill, or take over sessions; manual
|
||||
`pkill` of MCP daemons is contamination, not recovery.
|
||||
|
||||
Honesty rules specific to this view:
|
||||
|
||||
* **Ownership columns never assert absence they cannot prove.** When the
|
||||
`leases` or `locks` section is degraded or unavailable, the Leases and
|
||||
Worktree-binding cells render `unknown (inventory <status>)` with an
|
||||
*authority unproven* badge instead of `none` / `unbound`, and a caveat names
|
||||
the unreadable sections. A worktree binding is correlated through lease work
|
||||
numbers, so it is unproven when *either* section fails to read.
|
||||
`/api/sessions` carries the same facts as `ownership_authority_complete`,
|
||||
`ownership_section_status`, and per-row `lease_authority` /
|
||||
`worktree_authority`, so a JSON consumer can tell "holds none" from "could
|
||||
not be read".
|
||||
* **Contamination text is redacted at the display boundary.** Marker payloads
|
||||
(`command_summary`, `reason_class`, `session_id`, `role`) are
|
||||
operator-supplied free text that does not arrive through inventory scrubbing,
|
||||
so they pass through `webui.inventory.scrub_text`, which collapses `$HOME` and
|
||||
redacts credential-shaped tokens and URL userinfo *anywhere* in the string.
|
||||
The write-time redactor is a narrow denylist and is not relied on. The field
|
||||
itself is kept — it is the `#630` evidence naming which daemon was killed.
|
||||
|
||||
## System-health dashboard (#639)
|
||||
|
||||
`/system-health` renders the same snapshot the `/api/v1/system/health` API
|
||||
|
||||
Reference in New Issue
Block a user