> Markdown rendition of https://typeglish.dev/blog/guard-scope-ablation ("We took the scope off every rule. Three came off quietly. - Tidelines"). Canonical page: https://typeglish.dev/blog/guard-scope-ablation · All pages: https://typeglish.dev/llms.txt

[← Tidelines](https://typeglish.dev/blog)/ Deep dives Aug 27, 2026

# We took the scope off every rule. Three came off quietly.

A leave-one-out over the guards rather than the rules. Nine `WHEN` and `UNLESS` clauses came off a clean care prompt one at a time. Six are blocking. Three leave the grade exactly where it was, and one of those three is a data-protection incident.

by **TypeGlish team** 9 min read #deep-dives

Six flinched. Three did not.

**TL;DR** A guard is defended by the prover only when it is holding two rules apart: stripping the scope off 9 guarded rules in a clean `A (92/100)` care prompt is a blocking error 6 times and completely silent 3 times, and the silent three keep `0 error, 0 warning, 0 info` and the identical grade while the artifact turns *WHEN a caller is an account holder THEN you MUST read a balance aloud* into *You MUST read a balance aloud*. The guards that protect a customer rather than a rule have no witness in the file, so gate them on the built artifact instead.

Updated for typeglish 0.10.0

The first version of this post wrote each default flat and scoped only its exception: `You MUST NOT offer a credit.` beside `WHEN an outage lasted more than 24 hours THEN you MUST offer a credit.`, and called the pair clean. That was wrong. A flat rule applies on the long outage too, so both rules fire there and the pair is a contradiction. typeglish 0.9.0 accepted it only because it proved `IF` guards and no others; 0.10.0 proves `WHEN`, `WHENEVER`, `WHILE`, `UNLESS` and `ELSE IF`, and reports the old shape as `logic/contradiction`. The baseline below now carves each default out with the complementary `UNLESS`, and every figure on this page is re-run against 0.10.0. The six-and-three split survived. The sentence in §4 that said a deontic pair only needs one guard did not.

Every ablation on this blog so far has deleted something: [a whole rule](https://typeglish.dev/blog/silent-deletions), [a numeric bound](https://typeglish.dev/blog/bounds-ablation), [a modal](https://typeglish.dev/blog/deontic-force-ablation), a declaration, a test. This one deletes nothing. Every rule stays, every word of policy stays, and the only thing removed is the six or seven words at the front that say *when*. That edit has a name in every contact centre: somebody was told a rule was not firing often enough.

## §1 Thirteen guards, one clean file

The subject is a care prompt for a broadband provider, written the way this blog now recommends: every exception carved out of its default with complementary guards, `UNLESS` the case on the default and `WHEN` the case on the exception, so the two can never both apply. All thirteen of its statements carry a guard. Eight of them are four deontic pairs, a prohibition and its exception holding each other off; two split a length bound two ways; and three are scopes with no opposite number at all.

care.tg - the baseline ✓ A (92/100)

```typeglish
<role>
  You are a care agent for a broadband provider.
</role>

<constraints>
  @@ credit: goodwill is a supervisor decision except on a long outage
  - UNLESS an outage lasted more than 24 hours THEN you MUST NOT offer a credit.
  - WHEN an outage lasted more than 24 hours THEN you MUST offer a credit.
  @@ callout: the customer pays unless the fault is ours
  - UNLESS a fault is in a network THEN you MUST charge a callout fee.
  - WHEN a fault is in a network THEN you MUST NOT charge a callout fee.
  @@ eta: never guess a restoration time, quote the published one
  - UNLESS an incident has a published estimate THEN you MUST NOT state a restoration time.
  - WHEN an incident has a published estimate THEN you MUST state a restoration time.
  @@ transfer: containment first, but two asks is a hand off
  - UNLESS a customer asks twice for a person THEN you MUST NOT transfer a customer to a human agent.
  - WHEN a customer asks twice for a person THEN you MUST transfer a customer to a human agent.
  @@ brevity: short replies, except when somebody wants the whole history
  - UNLESS a customer asks for a fault history THEN you MUST keep every reply to at most 3 sentences.
  - WHEN a customer asks for a fault history THEN you MUST write at least 8 sentences.
  @@ line_test: a diagnostic costs the customer four minutes of hold
  - WHEN a customer reports a total loss of service THEN you MUST run a line test.
  @@ billing: only the account holder hears the balance
  - WHEN a caller is an account holder THEN you MUST read a balance aloud.
  @@ survey: one prompt for feedback, at the end
  - WHEN you resolve a contact THEN you MUST offer a survey.
</constraints>
```

Thirteen statements, thirteen guards. Nothing here is clever and nothing here is wrong: this is what a reviewed contact-centre prompt looks like after somebody has already been through it once. The `UNLESS` on each default is the part the first version of this post left out.

tg check + tg score care.tg - output

```
$ npx typeglish check care.tg
✓ 1 file — 0 error, 0 warning, 0 info

$ npx typeglish score care.tg
care.tg — A (92/100)  proven errors: none  tiers: base+z3
  planes  runtime 96 (what the model reads) · hygiene 79 (source only)
  facets  enforceability 91 x.21 · hardness 100 x.12 · directness 90 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 57 x.12 (hygiene) · style 100 x.08 · security 100 x.08
  lever   annotation 57/100 (up to +5 overall) — 6 statements without a @@ note (L2 L8 L11 L14 L17 L20) — put a "@@ why" line directly above each
  rules   13 of 13 rule-shaped lines read · 4 not provable
    L10  ~ unprovable    UNLESS a fault is in a network THEN you MUST charge a callout fee.
                         "charge" is not a verb the checker knows → use a verb from the catalog (redirect, escalate, ask, cite …) or add a numeric bound
    L11  ~ unprovable    WHEN a fault is in a network THEN you MUST NOT charge a callout fee.
                         "charge" is not a verb the checker knows → use a verb from the catalog (redirect, escalate, ask, cite …) or add a numeric bound
    L22  ~ unprovable    WHEN a customer reports a total loss of service THEN you MUST run a line test.
                         the checker knows "run" but cannot prove this shape: "run" is a light verb with nothing bound to it → name the tool or object it acts on (call @[tool], make a note)
    L24  ~ unprovable    WHEN a caller is an account holder THEN you MUST read a balance aloud.
                         "read" is not a verb the checker knows → use a verb from the catalog (redirect, escalate, ask, cite …) or add a numeric bound
```

Consistency 100 is the number to hold on to. Five pairs of rules in this file require and forbid the same act, or bound the same unit from both ends, and the prover is satisfied, because each pair's guards are complements and can never hold at once. That is exactly what a guard is for. The `rules` table lists four lines the prover cannot use: *charge* and *read* are not verbs it knows, and *run* has nothing bound to it. Keep an eye on L24.

## §2 Leave one guard out

Nine variants in `strip/`. Each is `care.tg` with the guard removed from exactly one statement and the rest of the line untouched, so `WHEN a caller is an account holder THEN you MUST read a balance aloud.` becomes `You MUST read a balance aloud.` and nothing else in the file moves. The nine are the `WHEN` side of each of the four deontic pairs, each side of the brevity pair, and the three lone guards. The four `UNLESS` defaults are not cut separately: each is the other half of a pair whose `WHEN` half already is, and taking the `UNLESS` off instead draws the same two errors.

tg check strip/ - one guard missing each ✗ 12 error, 1 warning

```
$ npx typeglish check strip
strip/brevity.tg:19:3  error  logic/numeric  Conflicts with line 20. Numeric conflict — "at most 3 sentences" and "at least 8 sentences" can't both hold.
                  ↳ line 20: - WHEN a customer asks for a fault history THEN you MUST write at least 8 sentences.
strip/brevity.tg:20:3  error  logic/numeric  Conflicts with line 19. Numeric conflict — "at most 3 sentences" and "at least 8 sentences" can't both hold.
                  ↳ line 19: - You MUST keep every reply to at most 3 sentences.
strip/callout.tg:10:3  error  logic/contradiction  Conflicts with line 11. Logical conflict — "charge a callout fee" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).
                  ↳ line 11: - You MUST NOT charge a callout fee.
strip/callout.tg:11:3  error  logic/contradiction  Conflicts with line 10. Logical conflict — "charge a callout fee" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).
                  ↳ line 10: - UNLESS a fault is in a network THEN you MUST charge a callout fee.
strip/credit.tg:7:3  error  logic/contradiction  Conflicts with line 8. Logical conflict — "offer a credit" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).
                 ↳ line 8: - You MUST offer a credit.
strip/credit.tg:8:3  error  logic/contradiction  Conflicts with line 7. Logical conflict — "offer a credit" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).
                 ↳ line 7: - UNLESS an outage lasted more than 24 hours THEN you MUST NOT offer a credit.
strip/eta.tg:13:3  error  logic/contradiction  Conflicts with line 14. Logical conflict — "state a restoration time" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).
              ↳ line 14: - You MUST state a restoration time.
strip/eta.tg:14:3  error  logic/contradiction  Conflicts with line 13. Logical conflict — "state a restoration time" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).
              ↳ line 13: - UNLESS an incident has a published estimate THEN you MUST NOT state a restoration time.
strip/history.tg:19:3  error  logic/numeric  Conflicts with line 20. Numeric conflict — "at most 3 sentences" and "at least 8 sentences" can't both hold.
                  ↳ line 20: - You MUST write at least 8 sentences.
strip/history.tg:19:3  warn   prompt/count-alignment  Count alignment — "sentence" carries two different count rules (line 20): one names a unit, one does not. Two spellings of one limit read as drift — state ONE bound with ONE unit.
                  ↳ line 20: - You MUST write at least 8 sentences.
strip/history.tg:20:3  error  logic/numeric  Conflicts with line 19. Numeric conflict — "at most 3 sentences" and "at least 8 sentences" can't both hold.
                  ↳ line 19: - UNLESS a customer asks for a fault history THEN you MUST keep every reply to at most 3 sentences.
strip/transfer.tg:16:3  error  logic/contradiction  Conflicts with line 17. Logical conflict — "transfer a customer to a human agent." is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).
                   ↳ line 17: - You MUST transfer a customer to a human agent.
strip/transfer.tg:17:3  error  logic/contradiction  Conflicts with line 16. Logical conflict — "transfer a customer to a human agent." is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).
                   ↳ line 16: - UNLESS a customer asks twice for a person THEN you MUST NOT transfer a customer to a human agent.

✗ 9 files — 12 error, 1 warning, 0 info
program: 9 independent files — no $IMPORT compositions
```

Six files, twelve errors, two per file, one on each side of the pair. `strip/history.tg` also draws a `prompt/count-alignment` warning from a separate check, one that reads two spellings of one limit as drift. Three of the nine variants do not appear in this report at all: `strip/line_test.tg`, `strip/billing.tg` and `strip/survey.tg` have nothing to say.

The scores split the same way, and they split hard.

tg score strip/*.tg - one line each

```
strip/credit.tg — F (78/100)  proven errors — grade capped at F  tiers: base+z3
strip/callout.tg — F (78/100)  proven errors — grade capped at F  tiers: base+z3
strip/eta.tg — F (78/100)  proven errors — grade capped at F  tiers: base+z3
strip/transfer.tg — F (78/100)  proven errors — grade capped at F  tiers: base+z3
strip/brevity.tg — F (78/100)  proven errors — grade capped at F  tiers: base+z3
strip/history.tg — F (76/100)  proven errors — grade capped at F  tiers: base+z3
strip/line_test.tg — A (92/100)  proven errors: none  tiers: base+z3
strip/billing.tg — A (92/100)  proven errors: none  tiers: base+z3
strip/survey.tg — A (92/100)  proven errors: none  tiers: base+z3
```

There is no middle. Six removals cap the grade at F on a file that has changed by six words (`history` gives up two more points to its warning); three land on `A (92/100)`, the same grade, to the point, as the file they were cut from.

Reading only the first six, this is a good news story, and it is a real one. A guard between two contradictory rules is not documentation, it is the load-bearing member: take it out and the prover that was quiet the whole time wakes up and names both lines. That is the behaviour you want from a scope, and it is why writing the exception *next to* the rule it excepts is worth the awkward phrasing.

## §3 The three that stayed green

The other three are the post. Here is what actually changed in the deployed prompt, which is the only place the change exists.

diff dist/care.txt dist/strip/*.txt

```
=== line_test ===
16c16
< - WHEN a customer reports a total loss of service THEN you MUST run a line test.
---
> - You MUST run a line test.

=== billing ===
17c17
< - WHEN a caller is an account holder THEN you MUST read a balance aloud.
---
> - You MUST read a balance aloud.

=== survey ===
18c18
< - WHEN you resolve a contact THEN you MUST offer a survey.
---
> - You MUST offer a survey.
```

Read the middle one as a data-protection officer rather than as an engineer. The prompt now instructs the agent to read an account balance aloud, unconditionally, to whoever is on the line. It checks at `0 error, 0 warning, 0 info`.

Nothing is broken here in any sense the compiler owns. `You MUST read a balance aloud.` is a well-formed directive with a registered doer and no hedge. It contradicts nothing, because nothing in this file forbids reading a balance. The `rules` table marks it unprovable, *read* not being a verb the checker knows, but it marked the guarded line unprovable in the same words: the row changed text and kept its verdict. Nor is provability the reason it went quiet. `survey` is the control: *offer* is a verb the checker knows, the credit pair is proven on it, `You MUST offer a survey.` is not among the unprovable four, and that removal is exactly as silent. The rule is hard and it is consistent. Every facet says so.

tg score - baseline vs. the unguarded copy

```
$ npx typeglish score care.tg | head -3
care.tg — A (92/100)  proven errors: none  tiers: base+z3
  planes  runtime 96 (what the model reads) · hygiene 79 (source only)
  facets  enforceability 91 x.21 · hardness 100 x.12 · directness 90 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 57 x.12 (hygiene) · style 100 x.08 · security 100 x.08

$ npx typeglish score strip/billing.tg | head -3
strip/billing.tg — A (92/100)  proven errors: none  tiers: base+z3
  planes  runtime 97 (what the model reads) · hygiene 79 (source only)
  facets  enforceability 91 x.21 · hardness 100 x.12 · directness 91 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 57 x.12 (hygiene) · style 100 x.08 · security 100 x.08
```

Eight facets, one of them moves, and it moves the wrong way: `directness` goes 90 to 91, and the runtime plane with it, 96 to 97. A rule with no condition in front of it *is* more direct. The scorecard is measuring the removal accurately and rewarding it by a point.

> The prover defends a guard when it is holding two rules apart. Nobody defends a guard that is holding a rule away from your customers.

Which is the whole asymmetry, and it is not a gap in the tool so much as a fact about what a file can know. A guard between two rules is a claim *about the file*: these two cannot both apply. Delete it and the claim becomes false, provably, on the spot. A guard on a lone rule is a claim *about the world*: this applies to account holders and not to everybody else. Delete it and nothing in the file becomes false. The file has never met an account holder.

The offline test lane does not close it either. Add a pair of cases that pin the scope, run the suite against the guarded file and against the unguarded one, and the two reports are identical.

tg test --dry - the same suite on both files

```
$ npx typeglish test cared.tg --dry
✓ cared.tg  coverage: 2/13 rules exercised
  uncovered (no case shares the rule's wording):
    L7  offer a credit.
    L8  offer a credit.
    L10  charge a callout fee.
    L11  charge a callout fee.
    L13  state a restoration time.
    L14  state a restoration time.
    L16  transfer a customer to a human agent.
    L17  transfer a customer to a human agent.
    L22  run a line test.
    L24  read a balance aloud.
    L26  offer a survey.
  · balance_is_scoped — "What is the balance on this account?" (not run)
      ✓ matches /^(?!.*balance is)/
      ✓ at most 3 sentences
  · balance_for_the_holder — "I am the account holder. What is my balance?" (not run)
      ✓ contains "balance"
✓ 1 prompt — 0 failed

$ npx typeglish test billingd.tg --dry
✓ billingd.tg  coverage: 2/13 rules exercised
  uncovered (no case shares the rule's wording):
    L7  offer a credit.
    L8  offer a credit.
    L10  charge a callout fee.
    L11  charge a callout fee.
    L13  state a restoration time.
    L14  state a restoration time.
    L16  transfer a customer to a human agent.
    L17  transfer a customer to a human agent.
    L22  run a line test.
    L24  read a balance aloud.
    L26  offer a survey.
  · balance_is_scoped — "What is the balance on this account?" (not run)
      ✓ matches /^(?!.*balance is)/
      ✓ at most 3 sentences
  · balance_for_the_holder — "I am the account holder. What is my balance?" (not run)
      ✓ contains "balance"
✓ 1 prompt — 0 failed
```

`billingd.tg` is the unguarded copy carrying the identical suite. Both report `coverage: 2/13`, the same eleven uncovered rules with the balance rule among them, and `0 failed`, because `--dry` validates the cases and never sends them. The negative case would fail on a live run, which is the right answer and the expensive one: it needs a key, a model and a minute, and it is not what a pre-merge gate is made of.

## §4 The guard that never guarded

Two more things fell out of building the variants, both of which are the same failure wearing different clothes: a guard that reads as a guard to a person and is not one to the compiler.

The first is punctuation. English lets you write a conditional with a comma, and this language does not.

comma.tg - a conditional with no THEN ✓ 0 error, 2 info

```typeglish
<role>
  You are a care agent for a broadband provider.
</role>

<constraints>
  - UNLESS an outage lasted more than 24 hours THEN you MUST NOT offer a credit.
  - WHEN an outage lasted more than 24 hours, you MUST offer a credit.
</constraints>
```

One token different from the credit pair in `§1`, which is clean: the `THEN` on the exception is a comma.

tg check comma.tg - output

```
$ npx typeglish check comma.tg
comma.tg:7:3  info   typeglish/if-then  WHEN needs a THEN — write WHEN <condition> THEN <action>.
comma.tg:7:3  info   prompt/unparsed-rule  Reads like a rule, but the rule reader cannot parse it as one, so it earns no Enforceability credit and no measurability check: the condition has no THEN branch, so nothing is required when it holds. Fix: add THEN MUST / NEVER / SHOULD <verb> ….

✓ 1 file — 0 error, 0 warning, 2 info
```

Green check, two `info` rows, and the exception is gone. Without a `THEN` the line never becomes a conditional, and the rule reader cannot parse it as a rule at all: nothing is required when the outage runs long, the line earns no enforceability credit, and `tg score` lists it as invisible, 1 of 2 rule-shaped lines read. The model still gets the sentence, because the artifact carries it verbatim; the compiler's ledger does not. This is the case for not filtering `info` out of a prompt review.

The second is that a guard on one side is not a guard. The first version of this post said it was, for a deontic pair, and built the whole baseline that way: a bare prohibition beside a scoped obligation. It is not, for either kind of conflict, and 0.10.0 says so.

flat.tg - the default flat, the exception scoped ✗ 2 error

```typeglish
<role>
  You are a care agent for a broadband provider.
</role>

<constraints>
  - You MUST NOT offer a credit.
  - WHEN an outage lasted more than 24 hours THEN you MUST offer a credit.
</constraints>
```

The credit pair exactly as the first version of this post wrote it, and as 0.9.0 accepted it: two blocking `logic/contradiction` errors. On a long outage the exception fires and the flat prohibition still applies, so both rules hold and the prover names both lines. The `WHEN` scopes the obligation; nothing scopes the prohibition.

onesided.tg - one bound scoped, one not ✗ 2 error

```typeglish
<role>
  You are a care agent for a broadband provider.
</role>

<constraints>
  - You MUST keep every reply to at most 3 sentences.
  - WHEN a customer asks for a fault history THEN you MUST write at least 8 sentences.
</constraints>
```

The same shape with numbers: two blocking `logic/numeric` errors. The guard is present, correctly spelled, and does not clear the conflict, because the flat cap still applies to the fault history.

twosided.tg - both bounds scoped ✓ 0 error

```typeglish
<role>
  You are a care agent for a broadband provider.
</role>

<constraints>
  - UNLESS a customer asks for a fault history THEN you MUST keep every reply to at most 3 sentences.
  - WHEN a customer asks for a fault history THEN you MUST write at least 8 sentences.
</constraints>
```

Adding `UNLESS a customer asks for a fault history THEN` to the general side clears both errors, and the same edit clears `flat.tg`: `UNLESS an outage lasted more than 24 hours THEN you MUST NOT offer a credit.` is the line the baseline carries. Two rules on one act, or two bounds on one unit, need the occasions carved apart on both sides before either prover will accept that they never meet.

There is no asymmetry, and that is the correction. If you write your exceptions the natural way, general rule flat and exception scoped, every pair refuses to build until you scope the general side too. The refusal is the feature: the language never had a rule that the more specific line wins, so an exception is not a rule placed near its default, it is a case carved out of it. It is also why every pair in the file has two witnesses. Each half holds the other in place, and removing either guard is a proven error, from whichever side you remove it.

## §5 A gate for the ones with no witness

Three of the thirteen guards in this file cannot be defended by anything that reads the source, because there is nothing in the source to contradict. What there *is*, is a list: the rules that are supposed to reach every customer. That list is short, it changes rarely, it is exactly the thing a policy owner can sign off, and the built artifact is where you check it.

scope-gate.mjs - fourteen lines

```
// Every rule that reaches the model unconditionally has to be on the list.
import { readFileSync } from 'node:fs';
const [, , artifact, listFile] = process.argv;
const GUARD = /^(?:when|unless|if|whenever|while|for each)\b/i;
const rules = readFileSync(artifact, 'utf8').split('\n')
  .filter((l) => l.startsWith('- ')).map((l) => l.slice(2).trim());
const open = rules.filter((r) => !GUARD.test(r));
const allowed = readFileSync(listFile, 'utf8').split('\n').filter(Boolean);
let fail = 0;
for (const r of open) {
  const ok = allowed.includes(r);
  if (!ok) fail = 1;
  console.log(`${ok ? 'listed    ' : 'UNSCOPED  '} ${r}`);
}
for (const a of allowed) if (!open.includes(a)) console.log(`GUARDED    ${a}`);
process.exit(fail);
```

It runs on the artifact, not the source, which is the point: by then the conditionals a `$IF` chain resolved are gone and what is left is what the model will read. The `GUARDED` row catches the drift running the other way, a rule that used to be universal and quietly acquired a scope.

node scope-gate.mjs - the baseline, then the three silent variants

```
$ node scope-gate.mjs dist/care.txt open-rules.txt
exit 0

$ node scope-gate.mjs dist/strip/line_test.txt open-rules.txt
UNSCOPED   You MUST run a line test.
exit 1

$ node scope-gate.mjs dist/strip/billing.txt open-rules.txt
UNSCOPED   You MUST read a balance aloud.
exit 1

$ node scope-gate.mjs dist/strip/survey.txt open-rules.txt
UNSCOPED   You MUST offer a survey.
exit 1
```

Nothing listed on the baseline and exit 0: every rule in `dist/care.txt` carries a guard, so `open-rules.txt` is empty, and the policy it states is that nothing in this prompt reaches every customer. Each of the three silent removals adds a row the empty list does not contain, and the gate refuses. Every guard the gate is not watching is one the prover is, so the two cover the file between them with nothing left over.

Two notes on making it honest rather than decorative. Keep `open-rules.txt` in the prompt repository and require a review on it, because the file is the policy statement and the gate is only as good as who signs it. And generate it once, from the artifact, rather than typing it: the empty list above is what the first clean build produces, which means the list starts out true and every line anybody adds to it later is a deliberate edit they have to explain. An empty file is an unusual policy statement, but it is the right one for a prompt where every rule was scoped on purpose, and a gate that refuses the first unconditional rule to arrive is the gate you want on it.

## §6 Common questions

**Does the checker notice if I delete the `WHEN` clause from a rule in my agent prompt?**

Only when a second rule contradicts the newly unconditional one. Across nine leave-one-out variants of a clean care prompt, six removals were blocking (2 errors each, `logic/contradiction` or `logic/numeric`) and three produced `0 error, 0 warning, 0 info` at `A (92/100)`, the same grade as the file they were cut from. A guard is defended by the prover when it is holding two rules apart. A guard that is holding one rule away from most of your customers has no second rule to contradict, so nothing in the file can tell that it left.

**Why does my prompt still pass after I made an exception rule apply to everybody?**

Because widening a rule is not a defect, it is a policy change, and no compiler can tell those apart without knowing your policy. Removing the guard from `WHEN a caller is an account holder THEN you MUST read a balance aloud` leaves `You MUST read a balance aloud`, which is well formed, hard, and consistent with everything else in the file. The score agrees: `enforceability`, `hardness`, `consistency`, `structure`, `annotation`, `style` and `security` are all unmoved, and `directness` goes up by a point, because a rule with no condition in front of it is more direct.

**Do I need to scope both sides of a conflicting pair of rules in TypeGlish?**

Yes, both sides, whichever prover is looking. A flat rule applies in every case, including the one its exception names, so `You MUST NOT offer a credit` next to `WHEN an outage lasted more than 24 hours THEN you MUST offer a credit` is 2 blocking `logic/contradiction` errors on typeglish 0.10.0, and `You MUST keep every reply to at most 3 sentences` next to `WHEN a customer asks for a fault history THEN you MUST write at least 8 sentences` is 2 blocking `logic/numeric` errors. Carve the default out with the complementary guard, `UNLESS an outage lasted more than 24 hours THEN you MUST NOT offer a credit`, and the pair checks clean, because the two guards can never hold at once. Earlier checkers proved only `IF` guards, which is why the first version of this post called the one-sided deontic pair clean. It never was.

**Is `WHEN the customer asks, do X` the same as `WHEN the customer asks THEN do X`?**

No. A comma is not a `THEN`. Written with a comma, the credit exception is not a rule to the compiler at all: the check stays at 0 error but reports `typeglish/if-then`, *WHEN needs a THEN*, and `prompt/unparsed-rule`, *the condition has no THEN branch, so nothing is required when it holds*. The line still reaches the model, because the artifact carries it verbatim, but it earns no enforceability credit, gets no measurability check, and the prover has no obligation to weigh against the prohibition. Written with `THEN`, the identical pair is clean and both rules are on the ledger. If you keep the info-level findings in your prompt reviews, those two are not nits.

Field note

The reason this ablation is worth running on your own file is that the six-and-three split is not a property of the checker, it is a census of your prompt. A file whose guards are nearly all holding contradictions apart is one the prover covers almost completely, and a file full of lone scoped rules is one where almost every scope is on trust. Nobody knows which they have until they count, and the count takes ten minutes: strip each guard, check the directory, and the variants that stay green are your exposure. It is the same lesson as [the verification gate](https://typeglish.dev/blog/build-verification-gate), where a guard is what stops two rules about account data from being a proven contradiction, read from the other end: there, the guard buys the green check. Here, the green check is what the guard bought, and three of the nine we cut were never paying for it.

∿ washed up Aug 27, 2026 ∿
