Repository navigation
feat: give a rule a lifecycle, an owner and a denial that says what would pass - #78
Merged
Merged
Conversation
…ould pass This is package 6 of PLAN.md, section 7 of the paper. - The policy block has `status`, `review_by`, `core` and `signed_off_by`. A rule is proposed, advisory, enforced or retired. A rule is in a decision only when a person verified the version that says so. The policy loads no module from an entry below verified. - An advisory rule reports what it would refuse and refuses nothing. The decision and the decision log have `advisories` and `denials`. - A denial can give a reason code and a remedy. The refusal shows them. - `[gates] max_enforced_rules` is a ceiling on the enforced rules of one domain. A rule over the ceiling is advisory until a person signs it off. - Only an L0 rule in the marked core takes no exception. - The memory tools `check_policy`, `file_exception` and `propose_amendment`, and the commands `amem policy check`, `except`, `amend` and `reset`. The reset retires each rule of a domain that no person wrote, promoted or approved. - The lint reports a rule with no owner, a rule after its review date, a rule over the ceiling and an unused rule. - Act 3 shows the steps, a refusal, an amendment, an exception and a reset. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
- An advisory module could refuse. Its denial could name a different rule, and the decision used that name. The modules of advisory rules now run in an engine of their own, and their denials are only reported. - A second entry with the id of an enforced rule and `status = "advisory"` switched that rule off. The id of a denial no longer selects a status. - A denial with a field that is not a string made each decision fail. A denial now reads such a field as its JSON. - The words of `file_exception` could carry a policy block and a module into the entry that a person verifies. A request with a code fence is refused, the entry must read back as the exception that was asked for, and the entry goes in the domain of the rule. - The ceiling counted by id, so a new rule could push out an older rule or a rule of the L0 core. It now counts from the oldest entry, never holds back the core, and accepts a sign-off only from a person. - `check_policy` and `propose_amendment` gave the text of a rule above the handling of the session. A rule is now read as an entry is. - An entry that nobody reviewed could speak for a rule with the same id. The tools now use the entry in force. - The reset kept a rule when a person proposed it and nobody approved. It now counts a curation or an approval. Its answer names entries. - T0 showed an L0 rule that nobody verified as core. - An exception with `status = "retired"` stayed in force. - One filer has one open amendment for one rule. - The lint, the rates and the filing of findings are in the library. The `held-back` finding is gone, because `inert` says the same. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
- A denial that is not an object, or whose rule is not a string, made each decision fail. The decision now reads each denial and each cited id as text. - All advisory modules ran in one engine, so one module that failed took each other report with it. Each advisory module now has its own engine. - The version of the policy now says which modules refuse and which report. - T0 gave the text of a rule above the handling of the session. The digest now gives only the id of such a rule. - `check_policy` and `propose_amendment` raised the label of the session from entries and tasks that they did not give. The label now rises only to the rules given. - An entry that nobody verified could speak for a rule when no entry was in force. Such a rule is now not found. - The bound of one amendment for each rule and filer now holds while a person works on the first task. - The words of an exception are one line with no markup. - `with_status` now changes the policy block where it is, and checks that the result reads back. - The reset counts an approver only when the entry is verified. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Main has the attempt keys of #77. The plan keeps the rows of both, and the MCP tests of both stand. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…s text in a refusal A third adversarial pass on ba8eeb8 found these defects. Each fix has a test that fails without it. - The policy was compiled from the newest 1000 policy entries. An agent that wrote 1000 raw policy entries put a verified rule out of every decision. The policy is now compiled from every policy entry. `check_policy` and the T0 digest also read every policy entry. - A refusal gave the reason, the code and the remedy of a rule to a session that could not read the rule's entry. The task that the refusal filed held the same text below the rule's label. For such a session, the refusal now gives the rule's id only. The decision log keeps the full denial. - A denial that gave no `policy` was filed as a blast-radius gate and named nothing. It is now filed as a policy gate, and the refusal says that the rule names no policy. - The same exception asked twice wrote two entries with one policy id and two approval tasks. The second request is now refused and names the first entry. - A rule of the L0 core at advisory was in `check_policy` and not in T0. T0 now lists it. - A test now holds that an entry nobody reviewed cannot mark a rule as core. - The comment on `max_enforced_rules` said that rules are counted by id. They are counted oldest entry first. Not fixed, and listed in PLAN.md: a denial that names no policy is not counted for its module in the rates and the lint. `make check` failed once on `scripts/conformance.test.sh` (missing-tool: "list is not newest first"). The script then passed six times. The fake server orders its list by wall-clock time; that is a separate defect. 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 16:41
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 6 of
PLAN.md: the rule lifecycle of section 7 of the paper.The paper gives each rule an owner, a review date and a status. It gives a ceiling on enforced rules, a reset, a denial that says what would pass, and three tools. The code had none of these. A rule was enforced when a person verified it, and that was all.
What changes
status,owner,review_by,coreandsigned_off_by. A rule is proposed, advisory, enforced or retired. A rule is in a decision only when a person verified the version that says so.amemloads no module from an entry below verified.advisories: what those modules would refuse. An advisory module refuses nothing, whatever rule its denial names.codeandremedy. The refusal shows the rule, the reason, the code and what would pass.[gates] max_enforced_rulesis a ceiling on the enforced rules of one domain, counted from the oldest entry. A rule over the ceiling is advisory until a person signs it off. The ceiling never holds back a rule of the L0 core.check_policy,file_exceptionandpropose_amendmentare memory tools and commands (amem policy check,except,amend). A session reads a rule as it reads an entry.amem policy resetretires each rule of a domain that no person wrote, curated or approved. Only the owner runs it.docs/demo-narrative.md,local/README.md,docs/terms.md,README.mdandPLAN.mdsay the same.A decision for the reviewer
A step from advisory to enforced is a new version of the entry. The new version supersedes the old version. Until a person verifies the new version, the rule is in no decision. The alternative is that a verified rule stays in force until a person verifies its next version. That alternative changes what "supersedes" means for a policy entry, so it is not in this change.
README.mdandPLAN.mdlist it as open.Not in this change
README.md("The rule lifecycle, in part") and row 6 ofPLAN.mdlist what is open:check_policydoes not select rules by the action.amemdoes not check that the person who approves an exception is the approver that the answer names.mainhas the same behaviour.Checks
make checkpassed with 990 tests on 9d0d6d6.make corpus-checkpassed. Act 3 ran on a scratch record with the debug binary and printed each step. I did not run the act with a gateway key, so its auditor step and its spend step did not run.Each new assertion for a fix failed when I removed that fix: the code fence check, the withheld text, the entry in force for a rule, the curation and approval rule of the reset, and the exception that the reset must not touch.
Adversarial pass
A first pass ran on bfae3c4. It confirmed 13 findings, and 30d4780 answers them:
file_exceptioncould carry a policy block and a module into the entry that a person verifies. Now a request with a code fence is refused, and the entry must read back as the exception that was asked for.status = "advisory"switched that rule off. Now the id of a denial does not select a status.signed_off_byaccepted any text. Now it must name a person.check_policyandpropose_amendmentgave the text of a rule above the handling of the session. Now the text is withheld, and the amendment is not filed.lint --filefiled a task for a rule that nobody verified. Theheld-backfinding is gone.status = "retired"is no longer in force.amem policythrough the binary.The pass named these as plausible, and they are open: the reset stops at the first write that fails and does not give what it retired, and the reset raises the label of the session when it reads a restricted rule.
A second pass ran on 30d4780. It attacked each fix. It found that five of nine fixes did not hold in full, and 9d0d6d6 answers what it confirmed:
check_policyandpropose_amendmentraised the label of the session from entries and tasks that they did not give. Now the label rises only to the rules given.<.with_statuschanged the first copy of the block text in the body, not the block. Now it changes the block and checks the result.local/README.mddid not agree with the code. The text changed.Open from the second pass:
Enforcedfilter on the policies that an exception can lift, and the date of an entry as the order under the ceiling.lint --filegives each finding the text of a conflict between layers.A third pass ran on ba8eeb8 (9d0d6d6 with
mainmerged in). It confirmed these defects, and a25307a fixes them. Each fix has a test that fails without it.policywas filed as a blast-radius gate and named nothing. It is now a policy gate, and the refusal says that the rule names no policy.The third pass also found assertions that pass when the code is wrong. These are still open, with no test: the label rises only for the rules that
check_policygives; a core rule above the handling is withheld in T0; an amendment on a blocked task counts as not done; a curator below the tier of a promotion does not count as a person; one row for two live entries with one id; the read-back checks offile_exceptionandwith_status; the module's id in the policy version.A denial that names no policy is not counted for its module in the rates and the lint. PLAN.md lists that.
No pass ran on a25307a. Three passes ran, and each found defects in the revision before it.
make checkfailed once onscripts/conformance.test.sh(casemissing-tool, "list is not newest first"), then the script passed six times. The fake server orders its list by wall-clock time. That flaky test is not from this change, and a separate branch fixes it.Posted by Claude on behalf of @sstriker
🤖 Generated with Claude Code