feat(audit): seal which AGENTS.md steered a turn into the chain - #51
Merged
Conversation
rrrodzilla
force-pushed
the
worktree-context-sources
branch
from
August 30, 2026 23:28
091ca27 to
95f25fe
Compare
Contributor
Author
|
Rebased onto green The back-compat question is now answered, and locally rather than by inference. Temporarily swapping acton-ai's
I confirmed |
Bumps acton-ai to 0.37.0 and replaces the interim tracing-only record of project instructions with the real thing: every layer that survives the policy gate is now named in the turn's own audit entry, hash-chained alongside what the turn did. This closes the gap opened deliberately in #29. That work shipped with discovery gated and confined but its provenance only logged, because acton-ai 0.36.0's TurnRecord had nowhere to put it; Govcraft/acton-ai#18 was filed to give it one, and 0.37.0 delivers it. What changed: - agent/src/instructions.rs no longer carries its own LoadedLayer type or hashes content itself. Discovered::layers is now Vec<ContextSource>, acton-ai's own metadata-only fingerprint (scope, path, BLAKE3 of the content, never the content), built with ContextSource::from_instruction_layer and handed straight to the builder. - agent/src/thread.rs calls PromptBuilder::context_sources() with those fingerprints. Set unconditionally, including the empty case: acton-ai skips the field entirely when empty, so a turn no AGENTS.md reached hashes over exactly the bytes it did before this existed. The fingerprints are deliberately built from the layers that survived filter_layers, never from AgentInstructions::context_sources(). That convenience fingerprints every layer discovery *found*, including the ones a `restricted` bundle just refused to load — an entry claiming a turn was steered by a file the gate withheld would be worse than no entry at all. A test pins that distinction rather than leaving it to a comment. The tracing call stays, reworded: it is operational visibility for somebody tailing logs, and no longer pretends to be the record. The sealed entry is what an auditor reads. blake3 moves from a dependency to a dev-dependency. The only remaining use is a test that recomputes a digest independently and checks it against the one acton-ai sealed, which is worth keeping precisely because it does not trust the crate under test to grade its own homework. wire/src/audit.rs's fixture skeleton gains context_sources: Vec::new(), matching the shape acton-ai writes for an entry no instructions steered. docs/compatibility.md records the 0.36 -> 0.37 rationale in the row that requires an exact pin to be a reviewed commit, and the "logged, not sealed" limitation is struck from docs/control-plane.md and docs/garrison-agent-design.md because it is no longer true.
rrrodzilla
force-pushed
the
worktree-context-sources
branch
from
August 30, 2026 23:43
95f25fe to
c45f61d
Compare
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.
Closes the gap deliberately left open by #29, now that Govcraft/acton-ai#18 has shipped in acton-ai 0.37.0.
What this does
#29 landed
AGENTS.mddiscovery gated and confined, but its provenance was only logged, not sealed — acton-ai 0.36.0'sTurnRecordhad nowhere to put it. 0.37.0 addsContextSourceandPromptBuilder::context_sources(), so the record now lives in the same tamper-evident chain that already answers "what did this turn do".Discovered::layersis nowVec<ContextSource>(acton-ai's own metadata-only fingerprint: scope, path, BLAKE3 of the content — never the content). Garrison's interimLoadedLayerand its hand-rolled hashing are gone.drive_turnpasses those toPromptBuilder::context_sources(), unconditionally. acton-ai skips the field when empty, so a turn noAGENTS.mdreached hashes over exactly the bytes it did before.tracingcall stays but is reworded as operational visibility, not the record.One subtlety worth the review: the fingerprints are built from the layers that survived
filter_layers, never fromAgentInstructions::context_sources(). That convenience fingerprints every layer discovery found, including ones arestrictedbundle refused to load — an entry claiming a turn was steered by a withheld file would be worse than none. There's a test pinning that.blake3drops from dependency to dev-dependency: its one remaining use is a test that independently recomputes the digest and checks it against the one acton-ai sealed, which is worth keeping precisely because it doesn't let the crate under test grade its own homework.Verification
cargo check --workspace --all-targets --locked— cleancargo clippy --locked --workspace --all-targets -- -D warnings— zero warningscargo test -p garrison-wire --lib— 38/38garrison-agent's tests hit this machine's known AWS-LC-FIPS abort; CI is the real check here, and specificallyagent/tests/audit_fixture.rs: the frozen 1.0 trail must still verify under 0.37.0. That test existing is why the exact-pin policy indocs/compatibility.mdcalls for a reviewed commit, and that row now records the 0.36 → 0.37 rationale.Noted, not addressed here
Two things surfaced while verifying this; neither is in scope for this PR and I'd rather raise them than fold them in:
argumentsas aserde_json::Value. Withserde_json/preserve_orderthe object's key order round-trips; without it keys are sorted, producing a different hash for identical data.garrison-agentgetspreserve_ordertransitively (viaagent-client-protocol-schema→serde_with), so trails verify today — but dropping an unrelated dependency could silently strand every trail on disk. I hit this building a scratch harness ingarrison-wire, which lacks the feature and could not verify the frozen fixture. Probably worth an upstream issue.admission::admitrefusals returnTurnResult::Refusedand are onlytracing::info!-logged, so a governed install that refuses a turn leaves no tamper-evident trace that it did. acton-ai 0.37.0 also shippedActonAI::record_refused_turn(No public path to seal a turn the host refused before the runtime saw it acton-ai#17), which is exactly the writer for this.