Repository navigation
feat: stop a session when its grants change, and keep its policy current - #79
Merged
Merged
Conversation
This is package 10 of PLAN.md: access freshness, section 6 and Appendix E of the paper. - A session records the digest of the grants file that it started with. Before each read, each decision and each spend check, it reads the file again. If the file changed or cannot be read, the session stops, and each call gives `Error::Stale`. - `state/policy.stamp` changes when a session changes a policy entry. A session that sees a different stamp, or a different root policy, loads the policy again. The read filter has the version of the policy that computed it, and is computed again for a new version. - The policy compares the expiry of an exception with the clock at each decision. It was compared only when the policy loaded. - `recall` with a deputy withholds its brief when the read filter changed while the deputy wrote the brief. - The board opens a new session when its session stops. - Act 3 has a step for a grant withdrawn while a session is open. The step and the step after it now count the hits of an agent's search. They printed the length of the answer object, which was always 1. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Both sides keep their fields of the policy: the data and the expiries of this branch, and the engine for advisory modules of the other. The lint test of the other branch opens its agent session after it changes the grants, because a session now stops when its grants change. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The policy keeps the data and the expiries of this branch, and one engine for each advisory module of the other branch. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
- A stopped session still made model calls. The egress read an error from the spend check as "not over the limit". An error is now a refusal, and the egress checks the session before a model call, a tunnel and a tool request. - A stopped session got a stored source from a hosted tool, and could import a bundle. Both now check the session first. - An import, and an entry of any type that replaced a policy entry, left the policy stamp as it was, so open sessions kept the old policy. Both now load the policy again and write the stamp. - A root policy directory that could not be read gave the built-in policy. It is now an error, and the session is refused. - After the policy narrowed and widened again, the handling of the session stayed narrow. It now follows the filter. - The reload of the policy opened the index with its schema set-up, and waited five seconds on a writer. It now opens the index to read, and it refuses when the index cannot be read. - The board carries its claims to its new session, and answers 403 when its session stays stopped. - Act 3 puts the grants back when its step fails. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D5DyJRpu5ned5y3d92U7Ts
…cy after a reindex A second adversarial pass on 5a9a3ce found these defects. Each fix has a test that fails without it, except the change to the demo script, which a run under three signals showed. - A tunnel that was open when the grants changed kept carrying bytes. The egress now asks each second if the session is valid and closes the tunnel when it is not. - `reindex` and `materialize` changed the entries that the policy is compiled from, and wrote no stamp. Open sessions, and the session that ran the command, kept the old policy. Both now reload the policy. - A board whose session stayed stopped answered 500, gave the domain list, and served the files of a bundle that it exported before. It now answers 403 to each of these. - The trap of act 3 did not put the grants back when a signal ended the act, and a second run then overwrote the copy with lowered grants. The trap now covers INT, TERM and HUP, and a copy that is left is put back first. Not fixed, and listed in PLAN.md and local/README.md: the expiry of an exception does not reach a read filter that a root policy computes from the exceptions, or the advisory rules. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D5DyJRpu5ned5y3d92U7Ts
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D5DyJRpu5ned5y3d92U7Ts
sstriker
marked this pull request as ready for review
November 14, 2026 21:26
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.
This is package 10 of
PLAN.md: access freshness, section 6 and Appendix E of the paper.This branch is on top of #78 (the rule lifecycle). Until #78 merges, the diff of this pull request also shows #78. Review commits 71fbc51 and 8441f88 for this package.
The paper says that a request made after a revocation cannot use the old grant. The code kept the grants, the policy and the read filter of a session until the session ended.
What changes
grants.tomlwhen it starts. Before each read, each decision, each spend check, each call of a hosted tool, each import and each request through the egress, it reads the file again. If the file changed or cannot be read, the session stops: each of those calls givesstopped: .... A new session starts under the grants as they are.state/policy.stampchanges when a session changes what is in force. The stamp also has the digest of the root'spolicy/*.rego. A session that sees a different stamp loads the policy again, and computes its read filter and its handling again. If the policy does not load, the call is refused.recallwith a deputy withholds its brief when the read filter changed while the deputy wrote the brief.A defect on
mainthat this branch correctsThe last step of act 3 printed the length of the answer object of an agent's search. That length is always 1. The step now counts the hits. On a record that I seeded from
examples/decisionsanddocs, the agent gets 0 hits under the narrow policy, anddocs/demo-narrative.mdnow says so.Not in this change
README.md("Access freshness, in part") and row 10 ofPLAN.mdlist what is open:Checks
make checkpassed with 1011 tests on 5a9a3ce.make corpus-checkpassed on 71fbc51. Act 3 ran on a scratch record with the debug binary: the open session got 5 hits, then twostopped:refusals, and the grants were put back. I did not run the act with a gateway key.The new assertions failed when I removed the mechanism: the check in
fresh()(four tests), the expiry at the decision, the reopen of the board, and the refusal in the egress.Adversarial pass
One pass ran on b7a80c5. It found that each task operation on the ledger does the check. It confirmed these, and 8441f88 answers them:
use_toolchecks first.importchecks first.getand the tasks of the session stayed narrow. Now the handling follows the filter.Open from the pass:
use_tool, or the read-only open of the index.A second pass ran on 5a9a3ce (8441f88 with #78 and
mainmerged in). It confirmed these defects, and 4eb0aca fixes them. Each fix has a test that fails without it, except the script, which a run under three signals showed.reindexandmaterializechanged the entries that the policy is compiled from, and open sessions kept the old policy. Both now reload the policy.The pass also found fixes of 8441f88 that no test holds. These are still open, with no test: the refusal of
over_budgeton an error; the check inpostof the egress; the check inuse_tool; the reload after a release from quarantine; the index check inload_policy_again; the reset of the pack cache;adopt_claimson the board.Two more items are open. The expiry of an exception does not reach a read filter that a root policy computes from the exceptions, or the advisory rules;
local/README.mdand PLAN.md say so. A demo root with a quote in its path breaks therunitem of act 3.No pass ran on 4eb0aca. This pull request stays a draft until #78 merges.
Posted by Claude on behalf of @sstriker
🤖 Generated with Claude Code