Skip to content

Fix/lru eviction and effective fingerprint - #7

Merged
nayfly merged 7 commits into
mainfrom
fix/lru-eviction-and-effective-fingerprint
Aug 1, 2026
Merged

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

Conversation

@nayfly

@nayfly nayfly commented Aug 1, 2026

Copy link
Copy Markdown
Owner

No description provided.

Robert and others added 7 commits July 30, 2026 10:21
Clicking Approve in the containerised console returned "confirmation admin
routes are localhost-only". The check requires client_ip == 127.0.0.1, and in a
container every client arrives as the bridge gateway — so the approval queue
shipped into a deployment where answering was structurally impossible. Three
things were missing at once: that check, the admin key, and the signing key.

In a container the boundary is the port publication, not the client IP the
process observes. Both services now publish on 127.0.0.1:PORT:PORT so the host
is the real limit, and the decision service is told it may stop enforcing
locality itself. Delegating locality does not delegate identity: the admin key
is still required, asserted by a test that a wrong key from the bridge address
is refused.

Signed proofs are on in the container profiles and stay off in the repo default.
The container ships an approval queue, and an approval not bound to the exact
action is a token anyone can type; the repo default keeps working with no key
management. The drift contract now pins both intentional differences rather
than one, so anything else diverging still fails.

Verified in the running stack, not a fixture: held action refused, parked,
approved, retried, allowed — and the audit chain across both containers
verifies clean afterwards.

100% coverage, mypy strict, green under CI simulation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Compose gives the invoking shell precedence over `.env` when a value arrives
through `${VAR}` interpolation. A console key exported days earlier therefore
kept winning over the rotated one in the file, and the console asked for a key,
rejected the one the file advertised, and asked again — with nothing anywhere
indicating that two different keys were in play.

Secrets now arrive via `env_file`, which reads the file directly and ignores the
shell. Verified the distinction rather than assuming it: with a conflicting
value exported, interpolation yields the shell's and env_file yields the file's.

Provider credentials move to `.env.provider`, read only by the gateway, so the
decision service no longer receives a key it has no use for. Asserted against
the running container.

This is the third time this variable has caused a failure that looked like
something else — a reverted rotation, a test suite that passed or failed by
shell, and now a console locked out of itself. The file is authoritative now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two problems stacked. The container inherited the image healthcheck, which
probes 8080 and asserts fields only the decision service returns, so the gateway
was permanently unhealthy for a reason unrelated to its actual state. And its
own /health answered `ok: true` with no upstream credential — a gateway that
returns 502 on every completion, reported as ready.

The second is the one that matters: a readiness probe believes that answer and
leaves the container in rotation serving nothing. `upstream_key_present` is now
part of the contract and `ok` depends on it, so the condition is visible before
the first real request rather than discovered by it. Compose gets a gateway
healthcheck that probes the right port and asserts the right thing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…an upstream

With a valid API key in place, every completion returned 502 `anthropic is not
installed`. The gateway runs from the same image as the decision service, and
that image installed only `[redis]` — so the service added to compose could not
do the one thing it exists for, and the failure reads like a network fault
rather than a missing dependency.

Optional extras for a library consumer, mandatory for this image.

Four deployment-wiring contracts added, each pinning a gap that shipped and had
to be found by running the thing: the image installs the SDKs, the gateway has
its own healthcheck on its own port, secrets arrive by env_file rather than
interpolation, and both ports publish on loopback.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by the agent now running behind this stack, reading its own deployment.
Every other port was moved to loopback and redis was left on all interfaces —
verified reachable from a non-loopback address with an unauthenticated PING.

It is not just a cache. Redis holds the confirmation replay store, so network
access means deleting replay records and reusing an approval proof: the
single-use guarantee lives there, not only in the signature. An unauthenticated
port on the LAN undoes the approval flow committed two days ago.

Pinned twice — once for redis specifically, once as a rule that no service in
the stack publishes on anything but 127.0.0.1, so the next port added does not
have to remember.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Switching the gateway to Haiku 4.5 to cut cost turned every request into
`adaptive thinking is not supported on this model`. The Anthropic path sent
`thinking: {"type": "adaptive"}` and `output_config.effort` unconditionally;
both exist on the 4.6+ families and are rejected by anything older, so choosing
a cheaper model broke the gateway completely rather than producing shallower
answers.

Gated by an allowlist of families known to accept them, not a denylist of those
that do not. An unrecognised model then answers with less depth — a worse
answer — instead of 400-ing every request, which is no answer at all. A model
released after this list needs adding, and until then it still works.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`context.action` was never written, so every real decision carried `tool: null`
and the console's tool column read empty for the entire deployment. Recovering
which tool an agent used meant regexing `raw_input` prose.

That matters now rather than cosmetically: the next step is two weeks of phase-1
observation to learn what vocabulary the agent actually speaks, and the data
being collected could not answer it. Measure first, discover the measurement is
unusable second.

The console test asserted a `tool` field and passed throughout, because it
seeded `context={"action": {"tool": "shell"}}` by hand — a test asserting a
field the production path never wrote.

Parameter *names* only. Values carry commands, file contents, and for a
confirmed action `confirm_proof`, which is a single-use credential; the trail is
exported, mirrored and archived, so anything written there cannot be taken back
out. Asserted by a test that a proof value never reaches the file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nayfly
nayfly merged commit c8f64a3 into main Aug 1, 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