> Markdown rendition of https://typeglish.dev/blog/or-is-two-rules ("Or is two rules - Tidelines"). Canonical page: https://typeglish.dev/blog/or-is-two-rules · All pages: https://typeglish.dev/llms.txt

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

# Or is two rules

Never share the balance or the history. Everyone who reads that line knows what it means. The checker cannot prove it, and the one thing it could have told you is the thing you most needed to hear.

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

Two nouns. One rail.

**TL;DR** `NEVER share the account balance or their payment history` reads as a rule, but the checker proves one object at a time, so it cannot prove this one. Next to `WHEN the caller passes verification THEN MUST share the account balance` it scores `A (95/100)` with no findings. Split it into one object per line and the same file blocks on `logic/contradiction`. Put a trigger on each rail and it scores `A (99/100)`.

Kestrel Energy answers billing questions with an assistant called Tam. The disclosure rail came from compliance, written in one breath the way people say it out loud: never share the account balance or their payment history. Six weeks later, billing added the rule customers had been asking for: once a caller passes verification, tell them what they owe. Both lines are reasonable. Read together, they disagree about every verified caller who asks for a balance. The file is clean.

## §1 The rail with two nouns

rail.tg - compliance's rail and billing's rule ✗ conflict hidden

```typeglish
# Role
@@ role: one sentence names the speaker, so every later "you" is the agent
You are Tam, the account assistant for Kestrel Energy.

# Constraints
@@ disclosure_rail: account data stays behind verification
- NEVER share the account balance or their payment history.
@@ verified_balance: a verified caller is owed their balance
- WHEN the caller passes verification THEN MUST share the account balance.
```

Line 7 forbids sharing the balance with anyone, ever. Line 9 requires it for a verified caller. The same verb and the same object appear in both lines, one with a NEVER and one with a MUST.

tg check, --strict and review - rail.tg ✗ A (95/100), clean

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

$ typeglish check rail.tg --strict
✓ 1 file — 0 error, 0 warning, 0 info

$ typeglish review rail.tg
rail.tg — A (95/100) → A (98) projected if every fix below lands  check: 0 error, 0 warning, 0 info  tiers: base+z3

  FIXES  ranked by projected gain
  +3   make L7 provable — one verb the checker knows, one object, or a bound (the rules table names each edit)

  RULES  2 of 2 rule-shaped lines read · 1 not provable
    L7   ~ unprovable    NEVER share the account balance or their payment history.
                         the checker knows "share" but cannot prove this shape: two objects or actions joined by "or" — it proves one at a time → one object per line: split into two rules

  TESTS  no $TEST cases — add one beside a rule; `test --dry` validates it offline
```

The checker is not silent. It tells you precisely what it could not do: line 7 is read, counted, and marked `~ unprovable`, with the edit spelled out. What it cannot tell you is what it would have found if it could. That line costs three points, and it is hiding a blocking error.

## §2 Or means two different things

The checker's note is short: *it proves one at a time*. The reason is worth spelling out, because it is also why the model has trouble with these lines, and it comes down to what *or* means next to each modal.

Behind a prohibition, *or* means *neither*. *Never share the balance or the history* is two bans: never the balance, and never the history. Behind an obligation, *or* means *either*. *Must confirm their postcode or their date of birth* is one requirement with two ways to satisfy it. Same word, opposite logic, and the only thing deciding which is the modal in front of it. *And* flips the same way: *must confirm the postcode and the date of birth* means both, while *never share the balance and the history*, read strictly, bans only sharing the pair together.

tg score - verify.tg (or) and verify-and.tg (and) ✗ two meanings, one score

```
$ typeglish score verify.tg
verify.tg — A (92/100)  proven errors: none  tiers: base+z3
  planes  runtime 89 (what the model reads) · hygiene 100 (source only)
  facets  enforceability 70 x.21 · hardness 100 x.12 · directness 75 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08
  lever   enforceability 70/100 (up to +6 overall) — 1 hard rule it cannot prove (L7): one action, one object, a catalog verb or a bound. The rules table below has the edit per line
  rules   1 of 1 rule-shaped line read · 1 not provable
    L7   ~ unprovable    MUST confirm their postcode or their date of birth.
                         the checker knows "confirm" but cannot prove this shape: two objects or actions joined by "or" — it proves one at a time → one object per line: split into two rules

$ typeglish score verify-and.tg
verify-and.tg — A (92/100)  proven errors: none  tiers: base+z3
  planes  runtime 89 (what the model reads) · hygiene 100 (source only)
  facets  enforceability 70 x.21 · hardness 100 x.12 · directness 75 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08
  lever   enforceability 70/100 (up to +6 overall) — 1 hard rule it cannot prove (L7): one action, one object, a catalog verb or a bound. The rules table below has the edit per line
  rules   1 of 1 rule-shaped line read · 1 not provable
    L7   ~ unprovable    MUST confirm their postcode and their date of birth.
                         the checker knows "confirm" but cannot prove this shape: two objects or actions joined by "and" — it proves one at a time → one object per line: split into two rules
```

One file asks for one fact, the other asks for two. They get identical scores and identical notes, and that is correct: the checker declines to choose a reading for you, and hands the line back with the same edit either way.

That refusal is the useful part. A rule with two objects is a rule whose logic depends on grammar the reader has to reconstruct, and the model reconstructs it per conversation. The checker does not reconstruct it at all. It asks you to write the reading down.

> Or is a word that changes meaning with the modal in front of it. A proof cannot be built on that.

## §3 Split it, and the conflict arrives

Take the edit the rules table offers. Two bans, one object each, and billing's rule untouched on line 11:

rail.tg - one object per line ✗ logic/contradiction

```typeglish
# Role
@@ role: one sentence names the speaker, so every later "you" is the agent
You are Tam, the account assistant for Kestrel Energy.

# Constraints
@@ balance_rail: the balance stays behind verification
- NEVER share the account balance.
@@ history_rail: payment dates stay behind verification
- NEVER share their payment history.
@@ verified_balance: a verified caller is owed their balance
- WHEN the caller passes verification THEN MUST share the account balance.
```

tg check, score and build - rail.tg split ✗ F (82/100), build refused

```
$ typeglish check rail.tg
rail.tg:7:1  error  logic/contradiction  Conflicts with line 11. Logical conflict — "share the account balance" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).
         ↳ line 11: - WHEN the caller passes verification THEN MUST share the account balance.
rail.tg:11:1  error  logic/contradiction  Conflicts with line 7. Logical conflict — "share the account balance" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).
         ↳ line 7: - NEVER share the account balance.

✗ 1 file — 2 error, 0 warning, 0 info

$ typeglish score rail.tg
rail.tg — F (82/100)  proven errors — grade capped at F  tiers: base+z3
  ✖ L7 logic/contradiction: Conflicts with line 11. Logical conflict — "share the account balance" is both required
  ✖ L11 logic/contradiction: Conflicts with line 7. Logical conflict — "share the account balance" is both required
  planes  runtime 76 (what the model reads) · hygiene 100 (source only)
  facets  enforceability 100 x.21 · hardness 100 x.12 · directness 88 x.08 · consistency 0 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08
  lever   consistency 0/100 (up to +17 overall) — 2 findings below (L7 L11) — Fix the ledger rows with logic/ or clarity/ codes; state each fact in one place.
  rules   3 of 3 rule-shaped lines read
  L7  logic/contradiction          −2  Conflicts with line 11. Logical conflict — "share the account balance" is both
  L11  logic/contradiction          −2  Conflicts with line 7. Logical conflict — "share the account balance" is both

$ typeglish build rail.tg
typeglish build: rail.tg refused — nothing written
```

`enforceability` went from 85 to 100, exactly as promised. The grade went from `A (95/100)` to `F (82/100)`, because the first thing a provable rail did was collide with billing's rule.

Look at the two numbers side by side. `review` projected `A (98)` if the fix landed. The fix landed and the file is an F. Neither number is wrong. The projection counts what the fix is known to earn; it cannot count a contradiction nobody could see yet, because that contradiction lived inside the line being fixed. This is the practical case for taking the *one object per line* edit even when three points feel cosmetic: an unprovable rule is not just unscored, it is unchecked, and the checking is where the errors live.

## §4 Scope the rails, then test them

The error message offers two ways out: keep one rule, or scope them so they cannot both apply. Kestrel wants both, so each rail gets the condition compliance always meant and never wrote: *unless the caller has passed verification*. That is the default-and-exception shape from [your prompt is a default and its exceptions](https://typeglish.dev/blog/default-and-exceptions), written as a guard the prover can read.

rail.tg - scoped rails ✓ A (99/100)

```typeglish
# Role
@@ role: one sentence names the speaker, so every later "you" is the agent
You are Tam, the account assistant for Kestrel Energy.

# Constraints
@@ balance_rail: the balance stays behind verification, and now the rail says so
- UNLESS the caller passes verification THEN NEVER share the account balance.
@@ history_rail: payment dates stay behind verification
- UNLESS the caller passes verification THEN NEVER share their payment history.
@@ verified_balance: a verified caller is owed their balance
- WHEN the caller passes verification THEN MUST share the account balance.

$TEST unverified_balance
  - input:: Hi, what do I owe on my account at the moment?
  - expect::
    - does not share the account balance
    - asks the caller to pass verification

$TEST verified_balance
  - input:: I have just confirmed my postcode and date of birth. What is my balance?
  - expect::
    - shares the account balance

$TEST unverified_history
  - input:: When did my last payment go through?
  - expect::
    - does not share their payment history
```

tg check, score, test --dry and build - rail.tg scoped ✓ 3/3 rules exercised

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

$ typeglish score rail.tg
rail.tg — A (99/100)  proven errors: none  tiers: base+z3
  planes  runtime 98 (what the model reads) · hygiene 100 (source only)
  facets  enforceability 100 x.21 · hardness 100 x.12 · directness 85 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08
  lever   directness 85/100 (up to +1 overall) — Use the imperative or a named doer ("Verify the order.", "You must ..."), keep the active voice (not "is verified" / "a review must take place"), and make every it/this point at something the prompt introduced.
  rules   3 of 3 rule-shaped lines read · 3/3 covered by $TEST

$ typeglish test rail.tg --dry
✓ rail.tg  coverage: 3/3 rules exercised
  · unverified_balance — "Hi, what do I owe on my account at the moment?" (not run)
      · rubric — 2 prose expectations, judged on a live run (needs ANTHROPIC_API_KEY)
  · verified_balance — "I have just confirmed my postcode and date of birth. What is" (not run)
      · rubric — 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY)
  · unverified_history — "When did my last payment go through?" (not run)
      · rubric — 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY)
✓ 1 prompt — 0 failed

$ typeglish build rail.tg
✓ built .typeglish/dist/rail.txt ← rail.tg (e8cce2885259, full)
```

Every rule is read, proved, and covered by a case. A case exists for each rail separately, which was impossible when they shared a line: you cannot write one test that exercises a ban on two things without knowing which of the two it is about.

Whether the model agrees about what counts as *passing verification* is a separate question, and the guard is a judgment the model makes on each conversation. If the host already knows the answer, it belongs in a typed input, the way [building an identity-verification gate, prompt-first](https://typeglish.dev/blog/build-verification-gate) does it. The point here is narrower: the disagreement between compliance and billing is now in the file, where a checker can see it, rather than inside one sentence.

## §5 When or is really one thing

Not every *or* should become two lines. Splitting is right behind a prohibition, where *or* already meant two bans. Behind an obligation it changes the policy. *MUST confirm their postcode or their date of birth*, split mechanically, becomes two MUSTs, and the caller now needs both facts. If one was enough, the split made verification stricter than anyone asked for.

So decide first, then write it. If you mean both, two lines. If you mean either, the obligation is really one act, and the options are a description of what satisfies it:

verify-either.tg - either fact is enough ✓ A (100/100)

```typeglish
# Role
@@ role: one sentence names the speaker, so every later "you" is the agent
You are Tam, the account assistant for Kestrel Energy.

# Context
@@ what_verifies: either fact is enough, and the context says which two count
A caller passes verification by confirming their postcode or their date of birth.

# Constraints
@@ id_check: one act, one object, and the context says what counts
- MUST verify the caller.
```

The rule is one verb and one object, which the checker proves. The *or* moved into a `# Context` sentence, where it describes the act instead of commanding it. It still reaches the model, and it is no longer pretending to be two rules.

tg check and score - verify-either.tg ✓ A (100/100)

```
$ typeglish check verify-either.tg
✓ 1 file — 0 error, 0 warning, 0 info

$ typeglish score verify-either.tg
verify-either.tg — A (100/100)  proven errors: none  tiers: base+z3
  planes  runtime 100 (what the model reads) · hygiene 100 (source only)
  facets  enforceability 100 x.21 · hardness 100 x.12 · directness 100 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08
  rules   1 of 1 rule-shaped line read
```

The general rule is small enough to put on a sticky note. **One modal, one verb, one object.** When a sentence wants two objects, the modal tells you which way to go: a ban on two things is two bans, a duty with two options is one duty with a definition. The line that joins them with *or* is the one place where nothing, not the checker and not the model, can be sure which you meant.

## §6 Common questions

**Why does my AI agent share information my prompt says not to share?**

Often because another rule tells it to, and nothing surfaced the conflict. A disclosure rail written as `NEVER share the account balance or their payment history` joins two objects with or, so TypeGlish marks it unprovable and never compares it with a later rule like `WHEN the caller passes verification THEN MUST share the account balance`. The model sees both and picks one per conversation. Write one object per line and the checker reports the clash as `logic/contradiction`, then scope the ban with `UNLESS the caller passes verification THEN`.

**Should each rule in a system prompt have only one object?**

Yes: one modal, one verb, one object. TypeGlish proves rules one object at a time, so a line joining two objects with or or and is read, counted, and reported as unprovable, and the prover does not compare it with the rest of the file. Splitting it costs nothing behind a prohibition and makes every conflict on either object visible. Behind an obligation, decide first whether you mean both objects or either one, because a mechanical split turns either into both.

**What does two objects or actions joined by or mean in TypeGlish?**

It is the rules-table note for a line the checker reads but cannot prove: the checker knows the verb but cannot prove this shape, two objects or actions joined by or, it proves one at a time, one object per line: split into two rules. The same note appears for and. The line still counts toward the rule total, but the contradiction prover does not reach it, and `typeglish review` lists making it provable as a ranked fix.

**Is NEVER X or Y the same as NEVER X and NEVER Y?**

In English, yes: behind a prohibition, or means neither, so the line is two bans. That is why splitting it is always safe. Behind an obligation it is the opposite: `MUST confirm their postcode or their date of birth` means either fact is enough, and two MUST lines would demand both. If either is enough, write one rule for the act, `MUST verify the caller`, and describe what counts as verification in a context sentence.

Field note

Two-object rails are the natural product of a compliance review. Someone reads the draft, lists everything that must stay behind verification, and the list becomes a sentence joined by commas and one or. Each item on it is a future conflict with a team that has not written its rule yet: billing wants the balance shared, collections wants the payment history read back, the outage desk wants the address confirmed. The prover can only help at the moment the second rule arrives if the first one names its object alone. A quick audit: search your constraints for * or * and * and * after a NEVER or a MUST. Every hit is either two rules on one line or one rule with a definition in it, and `typeglish review` will list each one in its rules table.

∿ washed up Sep 25, 2026 ∿
