I3110 — A blocking rule reads proven only when one of its cases shows its violation refused
Extend the rule proof so that a blocking rule is satisfied only when one of its named cases declares the rule whose violation it refuses with a '# majordomus-refutes: <rule-id>' header and has a passing recorded run. '# majordomus-negative:' keeps its command vocabulary and is never read as rule proof. majordomus-cli rules report prints the blocking rules that lack such a case, and scripts/ci/rule-proof-check ratchets that list.
READY wave 0 · p1 · implementation profile · parallel safe
Ready. Every dependency is done, so majordomus plan start I3110 will be accepted.
Objective
Extend the rule proof so that a blocking rule is satisfied only when one of its named cases declares the rule whose violation it refuses with a '# majordomus-refutes: <rule-id>' header and has a passing recorded run. '# majordomus-negative:' keeps its command vocabulary and is never read as rule proof. majordomus-cli rules report prints the blocking rules that lack such a case, and scripts/ci/rule-proof-check ratchets that list.
Why
The owner asked eleven times between 2026-09-11 and 2026-10-14 for rules to be enforced \"positive, negative and wiring tested\" (c139#1, c259#6). On 2026-10-08 the independent audit showed Majordomus treating wired as enforced. Rule proof today accepts any named, present, passing case, so a rule backed only by a happy-path case reads proven. The refusal gets its own header because '# majordomus-negative:' already names a failure mode of a command, gate, script, workflow or capability (lib/commands.sh:216-229, scripts/ci/command-furnished:71), mj_covers_resolve refuses any other prefix (lib/commands.sh:186-188), and only the first such line of a file is read. It extends milestone architecture-is-enforced. Proposed in ADR 0188 (A rule's proof of effect is a recorded refusal of its violation, not the presence of its wiring) (proposed; it carries no authority until the owner accepts it). Sources: chatgpt-majordomus:c115#1, chatgpt-majordomus:c115#4, chatgpt-majordomus:c129#3, chatgpt-majordomus:c139#1, chatgpt-majordomus:c193#1, chatgpt-majordomus:c207#6, chatgpt-majordomus:c214#2, chatgpt-majordomus:c259#6.
Current state
apps/majordomus-cli/src/evidence/subject.rs reads the first '# majordomus-covers:' and the first '# majordomus-negative:' line of every case (module doc at :21, NEGATIVE const at :126). apps/majordomus-cli/src/rules/mod.rs builds rule proof from named, present, recorded cases, and RuleState has no notion of a refusal. No case carries a refutes header. .ai/repo/rule-proof-baseline.txt holds only its header. The number of blocking rules is read with majordomus-cli rules report --format json, not quoted here.
Desired state
RuleProof carries refusal_shown: bool for a blocking rule. The report and its MCP and HTTP projections name the rules without a refusal case. A new baseline, written by --write-baseline, freezes today's list and may only shrink.
Scope
- apps/majordomus-cli/src/rules
- scripts/ci/rule-proof-check
- test/cases/125_rule_proof.sh
- .ai/repo/rules/project/rule-is-a-doctrine.v1.md
- apps/majordomus-cli/src/evidence/subject.rs
- lib/commands.sh
- scripts/ci/command-furnished
- scripts/generate-site-data
- test/cases/31_command_coverage.sh
- .ai/repo/project/intents
- .ai/repo/rule-proof-baseline.txt
- .ai/repo/adrs/0188-a-rule-s-proof-of-effect-is-a-recorded-refusal-of-its-violation.md
- apps/majordomus-cli/src/capability/builtin/rules.rs
- apps/majordomus-cli/tests/rules.rs
Out of scope
- Writing the missing negative cases themselves (follow-up issues per rule family)
- Advisory rules
- Adding a rule: vocabulary to '# majordomus-negative:' or to mj_covers_resolve
Dependencies
None. This issue is a root of the graph.
What waits on this
- I3111BLOCKED Every dispatched blocking validator is run against a declared violation, and one that accepts it fails the case
- I3112BLOCKED rule-proof-check prints the blocking rules no recorded run proves, and that count only falls
Acceptance criteria
- A blocking rule none of whose named cases carries a '# majordomus-refutes:' header naming it is listed by majordomus-cli rules report as refusal_not_shown
- Adding a blocking rule with no refusal case fails rule-proof-check; removing one from the baseline after adding a case passes
- majordomus-cli rules report --format json carries refusal_shown per rule, and the MCP and HTTP projections carry the same field
- A case declares the rule whose violation it refuses with its own header line '# majordomus-refutes: <rule-id>' (one or more per file, every line read); '# majordomus-negative:' keeps its command vocabulary and is never read as rule proof
- refusal_shown is true for a blocking rule only when a named case carries a refutes header naming that rule id and has a passing recorded run; case 125 ('negative: rules') and case 99 ('negative: adr doctor') show no rule refused
- doctor, test/cases/31_command_coverage.sh and scripts/ci/command-furnished pass unchanged over a tree that carries refutes headers
- A new case carrying evidence case_rule_refusal_ratchet (its number from scripts/case-numbers next, announced on the board when the issue starts) fails on master 707a04c and passes with this issue; the same commit rebinds intent criterion blocking-rule-shows-refusal to it
Validation
- cargo test --manifest-path apps/majordomus-cli/Cargo.toml --lib rules
- cargo test --manifest-path apps/majordomus-cli/Cargo.toml --test rules
- MJ_TEST_JOBS=4 bash test/run.sh 125_rule_proof 31_command_coverage
- scripts/ci/rule-proof-check
Evidence required
- case_rule_refusal_ratchet
Evidence
None recorded. Every token above needs a command or an artifact behind it before this issue can be completed; narrative is refused.
Timeline
- started
- —
- verified
- —
- completed
- —
Those three fields, the evidence above and the state of the dependencies are all the status is made of. There is no status field to disagree with them.
Canonical record: .ai/repo/project/issues/I3110.yaml. Read it back with majordomus plan show I3110.