Skip to content

Fix/lru eviction and effective fingerprint - #5

Merged
nayfly merged 8 commits into
mainfrom
fix/lru-eviction-and-effective-fingerprint
Jul 29, 2026
Merged

nayfly merged 8 commits into
mainfrom
fix/lru-eviction-and-effective-fingerprint

Conversation

@nayfly

@nayfly nayfly commented Jul 29, 2026

Copy link
Copy Markdown
Owner

No description provided.

Robert and others added 8 commits July 28, 2026 18:15
Recording a hard-deny review requires AETHERYA_CONSOLE_API_KEY, so without it
the review queue in the containerised console answers 503 and the phase gate
stays unreachable — the same dead end the queue was built to remove.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adding AETHERYA_CONSOLE_API_KEY to the compose stack gated the reads as well,
and the page only attached the header when recording a review. Against any
deployment with a key configured — the normal one — every panel 401'd and the
console rendered empty while /health reported ok.

The key now travels from one place, so the read and write paths cannot drift
apart again, and a 401 prompts once and retries instead of failing silently.
A refresh that still cannot authenticate says so in the panels: a page stuck on
"loading…" sends you to the server logs for nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The page ships inside the engine, so its markup and its JS change with every
upgrade while the URL stays the same. Served without cache headers, a browser
keeps the previous version and runs it against the new API — which is how an
upgraded, healthy deployment renders a console stuck on "loading…" with no
indication that anything is stale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`alert("...\n\n" + x)` in console.py: the escape was written for JavaScript but
Python consumed it and emitted a real newline, splitting the string literal and
breaking the whole script. Nothing on the page ran — every panel sat on
"loading…" while /health reported ok, which reads as a server or cache problem
and is neither.

Every existing console test passed throughout. They assert that substrings are
present in the HTML, and all of them still were; none asserted that the script
parses. That gap is the actual defect here.

Two regression tests close it, both verified to fail against the original bug:
one rejects Python escape sequences in the template, where the class of bug
originates; one checks the rendered output for a string literal left open at
end of line, so it holds through a refactor of the template.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hit for real. The console asks for the key, then asks for a reviewer name in an
identical prompt() — so the key gets pasted into the name, and the reviewer
field is written verbatim into the review store. That store is part of the
audit surface: exported, mirrored, archived. A secret that lands there cannot
be taken back out, and the operator has no reason to suspect it happened.

The server now refuses any reviewer or note containing one of this process's
secrets (console key, approvals key, confirmation HMAC, attestation key). The
guarantee belongs there rather than in the page, because the page is not the
only thing that can POST to the endpoint. Secrets shorter than 8 characters are
ignored: below a real key's length the check would reject ordinary prose, which
is worse than the mistake it prevents.

The console stops the round trip too, and the two prompts no longer look alike.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`get()` asked for the key whenever it saw a 401, and the page refreshes every
ten seconds. A wrong key therefore produced a dialog every few seconds with no
way out — the console became unusable by the operator it was locking out, which
is worse than the blank page it replaced.

Only refresh() decides when to ask now, and it pauses polling on failure rather
than retrying into the same prompt. The locked panel says whether the key was
rejected or the engine was unreachable, and carries a button to try again: a
dead end with no control is indistinguishable from a broken page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two things that both read as "the console is broken" when it is not.

Secrets came from whatever shell ran `docker compose`, so rotating a key
reverted to the compose default the next time anyone rebuilt without it
exported — which is exactly what happened after a rotation here. They now come
from .env, which compose reads on its own and which is gitignored; .env.example
documents the shape without carrying values.

And a fresh volume has no audit file, so the rollout report raised and the
console showed an error on a healthy first boot. The decision feed already
treated this as empty rather than broken; the report now agrees. The CLI still
fails on a missing path, because there it is a bad argument rather than a
deployment that has not decided anything yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`chain_intact` reported "audit file not found — investigate before advancing"
on a install that had decided nothing, sending an operator to look for damage
that cannot exist: a chain of zero events is vacuously intact. Same class as
the report itself raising on a missing file, one layer down.

A genuine chain break is still reported — asserted with a tampered event, so
the relaxation cannot swallow the finding it exists to surface.

The empty feed now says the engine is running and waiting, and shows a command
that puts something in it. An empty state that only says "empty" leaves you
guessing whether the service is broken or idle, which is the question the whole
page exists to answer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nayfly
nayfly merged commit 6e86563 into main Jul 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant