Repository navigation
Fix/lru eviction and effective fingerprint - #7
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.