Repository navigation
item 8 (part): the ACS control hook - #14
Merged
Merged
Conversation
SPEC-v0.2 §9 amended, acceptance tests T51-T55. §9 said "no code, no adapter, no claim" on the reading that ACS had no stable interface to build against. Reading the v0.1.0 schemas settled that differently, so the adapter ships and the no-claim rule stays exactly where it was. Read against GenAI-Security-Project/agent-control-standard at commit c7ad162 (2026-08-11), spec v0.1.0: request-envelope.json, response-envelope.json, hooks/tool-call-request.json, hooks/tool-call-result.json, ask-details.json, CONTRIBUTING.md. docs/ACS.md records what was read, file by file. ACS is advisory and CTRLRun is executing: a Guardian returns a decision and the platform runs the tool, which is the opposite way round from @Protect. So one action splits across two hooks and the reservation is held between them — exactly the shape Suspended and Control.resume were built for in §6.9. The adapter reuses them rather than reaching for the store, so Control stays the only module that composes the others and there is still one implementation of v0.1 §5.5's asymmetry. exit_status is success|failure|timeout|blocked, and ACS says nothing about what any of them means for a side effect. The fail-closed reading is the same one §6.8 applies to MCP: success committed, blocked failed (a control refused before dispatch), failure and timeout unknown — because a tool that failed after acting and one that failed before send the same string. Of ACS's 22 hooks the adapter answers two and declines twenty. The rest are checkpoints about what the model is thinking, remembering or composed of; the product rule is that CTRLRun decides actions that affect the real world and nothing else, so an unanswered method returns a JSON-RPC error rather than an opinion. No compliance claim, and that is not a formality: at that commit ACS has no reference implementation and no conformance suite, so there is nothing to be conformant with. "ACS-compatible" appears nowhere. examples/acs/ runs the double-refund scenario through the hooks with no network. ACS has no examples/ directory of its own, so it follows CTRLRun's convention and says so. The build list and ROADMAP.md move with §9: the do-not-build line narrows from "an ACS adapter or any ACS claim" to "any ACS compliance claim", and ROADMAP's v0.3 standards line records that the adapter landed early.
Mutation-testing §9 found seven, of which five were untested rather than subsumed: - not_executed_on_error reaching across to `timeout`. The operator asserted something about *errors*; a timeout is not an error the tool reported, it is the absence of any report, and no per-tool claim can speak for it. - a toolCallResult with no request_id_ref at all — the earlier test passed a UUID that matched nothing, which is a different case. - an argument that is a mapping with provenance but no `value`, which is the shape a producer gets wrong; the earlier test sent a bare string, which the isinstance check catches either way. - an envelope whose only fault is `jsonrpc: "1.0"`; every other malformed case was also caught by a later guard. - the operation in the action name, asserted on the receipt rather than on an event merely existing. Two are subsumed and stay for their messages: a missing agent_id would be refused by Principal too, but this one names the ACS field a producer has to fix; the tests now assert which refusal they got.
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.
The ACS half of item 8. This amends SPEC-v0.2 §9, which said "no code, no adapter, no claim" — flagged up front because it also narrows a line in the do-not-build list.
Why the amendment
§9's ruling rested on ACS having no stable interface to build against. Reading the v0.1.0 schemas settled that differently: the envelope, the hook payloads and the five decisions are specified precisely enough to write against, and
steps/toolCallResultgives a post-execution checkpoint the earlier draft assumed did not exist.The no-claim rule is untouched, and it is why the adapter is safe to ship. At the commit read there is no ACS reference implementation and no conformance suite, so there is nothing to be conformant with. "ACS-compatible" appears nowhere in the README, docstrings or CLI output. The do-not-build line narrows from "an ACS adapter or any ACS claim" to "any ACS compliance claim"; ROADMAP's v0.3 standards line records that the adapter landed early.
What was read
GenAI-Security-Project/agent-control-standardat commitc7ad162f69386daac94b89073e3b751e8cdf28b2(2026-08-11), release v0.1.1, spec version v0.1.0 (decoupled at1af1f92, same day). Apache-2.0 code, CC BY-SA 4.0 docs.specification/ACS/acs_schema.json·v0.1.0/request-envelope.json·response-envelope.json·hooks/tool-call-request.json·hooks/tool-call-result.json·ask-details.json·CONTRIBUTING.md. All recorded file-by-file indocs/ACS.md.Two absences shaped the work: no reference implementation (the README puts a Guardian sample and FastMCP instrumentation at v1), and no
examples/directory, so there is no house format for a community example —examples/acs/follows CTRLRun's own convention and says so.The design
ACS is advisory and CTRLRun is executing: a Guardian returns a decision and the platform runs the tool, the opposite way round from
@protect. So one action splits across two hooks with the reservation held between them — exactly the shapeSuspendedandControl.resumewere built for in §6.9.Controlstays the only module that composes the others, and there is still one implementation of v0.1 §5.5's asymmetry.exit_statusissuccess | failure | timeout | blocked, and ACS says nothing about what any of them means for a side effect. The fail-closed reading is §6.8's:successcommitted,blockedfailed (a control refused before dispatch),failureandtimeoutunknown — because a tool that failed after acting and one that failed before send the same string.Of ACS's 22 hooks the adapter answers two and declines twenty. The rest are checkpoints about what the model is thinking, remembering or composed of; the product rule is that CTRLRun decides actions that affect the real world, so an unanswered method returns a JSON-RPC error in ACS's reserved range rather than an opinion.
ask_details.intent_extensionis not used: it grants capabilities for a session, which is an authority model CTRLRun does not have until v0.3, and a grant it cannot represent is one it must not claim to honour.Where ACS is silent
docs/ACS.mdsets this out at length; the short version is five gaps — no effect identity,exit_statusdescribing the call rather than the effect, no binding of an approval to a canonical action, no terminal unknown state, and a Guardian that cannot make reserve-and-execute atomic because it does not execute.Mutation table — 30 mutations, all red
exit_statusmapping, incl. the inversion, andnot_executed_on_errorask_details, approval re-presentation, mutation refusalallowSeven started green, five of them genuinely untested paths — the most useful being that
not_executed_on_errorreached across totimeout. That flag is an operator's assertion about errors; a timeout is not an error the tool reported, it is the absence of any report, and no per-tool claim can speak for it. Nothing tested that boundary.The other two are the familiar subsumed-guard shape and stay for their messages: a missing
agent_idwould be refused byPrincipaltoo, but this refusal names the ACS field a producer has to fix.Not in this PR
The OTel sink (T29) and T30's named test — item 8's other half.
docs/ACS.mdexplains why the OTel attribute alignment is deliberately not written: it would be a compliance claim with nothing behind it, and receipts already written carry the current names.Drafted, not posted
Three documents in
internal/owasp/(git-excluded, asinternal/always is):acs-issue.md— the execution-outcome gap in ACS's own terms, with the concrete duplicate-refund trace and a proposed extension shape (optionaleffect_id, a three-stateeffect_outcome, anaction_digestonask_details). CTRLRun is named once as one reference implementation alongside Stripe/Adyen idempotency keys and Temporal.CONTRIBUTING.mdsays schema changes open a Discussion first, not an issue, so it is written to be pasted there.slack-intro.md— four sentences.top10-contribution.md— mitigation paragraphs for ASI02 Tool Misuse, ASI03 Identity & Privilege Abuse and ASI09 Human-Agent Trust Exploitation, in the Top 10's register, each with a reference scenario from the demos. It flags that section numbers and file paths must be checked against the live document before filing, and that ASI08 is a plausible fourth home that would weaken both if duplicated.Nothing has been posted anywhere.
Verification
1241 tests pass (1207 → 1241),
mypy --strict src/,ruff checkandruff format --checkclean.import ctrlrunimports neitherctrlrun.acs,httpx,sslnorctrlrun.gateway.examples/acs/main.pyruns with no network.