> Markdown rendition of https://typeglish.dev/blog/retired-plan-still-sells ("You retired the plan. The prompt still sells it. - Tidelines"). Canonical page: https://typeglish.dev/blog/retired-plan-still-sells · All pages: https://typeglish.dev/llms.txt

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

# You retired the plan. The prompt still sells it.

Platinum closed to new customers in March and was withdrawn in July. The support prompt has four rules about it, and exactly one of them is something a compiler can find.

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

One word out. One error found.

**TL;DR** A retirement is only provable where the retired thing was a typed value: drop `platinum` from `$REQUIRE variable plan: one of essential, complete, platinum` and the arm that tested for it is a blocking `structure/impossible-case` that refuses to build, while three prose rules about the same dead plan are 0 error, 0 warning, 0 info before and after, hold B (82/100) on every facet, and ship in the artifact. Move every plan rule inside a `$SWITCH ON @{plan}` arm and the retirement locks in both directions.

Products leave. A plan gets closed to new customers, runs off for a quarter, and then goes. Pricing knows, billing knows, the CRM knows, and the entitlement flag stops arriving. The system prompt is the one artefact in the estate that has no idea, because nothing about withdrawing a product touches the document that tells an agent how to talk about it.

This is not the interesting part. The interesting part is that a checker can prove some of those leftover rules are dead and provably cannot prove it about the rest, and the line between the two has nothing to do with how important the rule is. It is drawn by whether the plan was ever a value the file declared.

## §1 Four rules about a product that no longer exists

Kestrel Fibre sells three broadband plans, and the chat support prompt gates the engineer-visit promise on which one the caller is on. The host sends the plan as a typed input, so the compiler knows the set.

plans.tg - as inherited, Platinum still live ✓ B (82/100)

```typeglish
<$CONFIG>
  $REQUIRE variable plan: one of essential, complete, platinum
</$CONFIG>

# Role
You are a broadband support agent for Kestrel Fibre.

# Constraints
- You MUST keep every reply to at most 4 sentences.
- IF the customer is on Platinum THEN you MUST NOT charge an engineer callout fee.
- WHEN a Platinum customer asks about speed THEN you MUST quote the 900 Mbps profile.
- You MUST offer a Platinum upgrade to any customer who mentions buffering.
$IF @{plan} is equal to platinum:
  You MUST book a same-day engineer visit.
$ELSE IF @{plan} is equal to complete:
  You MUST book a next-day engineer visit.
$ELSE:
  You MUST book the next available engineer visit.
```

Four rules mention Platinum. Three are English sentences in the Constraints list. The fourth is the first arm of the deterministic chain, and it is the only one that names the plan as a *value* rather than as a word.

tg check plans.tg - output

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

Nothing to report, which is correct. While Platinum exists this file is a good file.

tg score plans.tg - output

```
plans.tg — B (82/100)  proven errors: none  tiers: base+z3
  planes  runtime 93 (what the model reads) · hygiene 50 (source only)
  facets  enforceability 83 x.21 · hardness 86 x.12 · directness 96 x.08 · consistency 100 x.17
          structure 100 x.12 (hygiene) · annotation 0 x.12 (hygiene) · style 100 x.08 · security 100 x.08
  lever   annotation 0/100 (up to +12 overall) — Put a "@@ why" note directly above each
          statement ("@@ name: why" also names it).
```

B (82/100), and the only lever offered is that nobody wrote `@@` notes. Hold on to this number: it is the last honest reading the file will produce.

## §2 One word out of the domain

July arrives. Platinum is gone, the entitlement stops being sent, and somebody does the tidy thing and takes the member out of the declared set. That is the whole diff: one word, on line 2.

retired.tg - the same file, minus one word ✗ structure/impossible-case

```typeglish
<$CONFIG>
  $REQUIRE variable plan: one of essential, complete
</$CONFIG>

# Role
You are a broadband support agent for Kestrel Fibre.

# Constraints
- You MUST keep every reply to at most 4 sentences.
- IF the customer is on Platinum THEN you MUST NOT charge an engineer callout fee.
- WHEN a Platinum customer asks about speed THEN you MUST quote the 900 Mbps profile.
- You MUST offer a Platinum upgrade to any customer who mentions buffering.
$IF @{plan} is equal to platinum:
  You MUST book a same-day engineer visit.
$ELSE IF @{plan} is equal to complete:
  You MUST book a next-day engineer visit.
$ELSE:
  You MUST book the next available engineer visit.
```

Line 2 lost `, platinum`. Nothing else in the file moved: the three prose rules and all three chain arms are byte-identical to §1.

tg check retired.tg - one word removed from the domain ✗ 1 error

```
$ npx typeglish check retired.tg
retired.tg:13:1  error  structure/impossible-case  @{plan} is never platinum (it's one of essential, complete).

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

$ npx typeglish build retired.tg
typeglish build: retired.tg refused — nothing written
retired.tg:13:1  error  structure/impossible-case  @{plan} is never platinum (it's one of essential, complete).

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

Line 13 is the chain arm, and the message is a statement about the world rather than about style: *is never platinum*. An arm testing a value its input cannot hold can never fire, so it is dead code, and the build refuses at exit 1.

That error is the good news, and it is worth saying plainly what it is: the compiler has found a site in your prompt that still believes in a product you withdrew, and it will not let you ship until somebody looks. This is the same machinery that catches a member [misspelled in a tier gate](https://typeglish.dev/blog/typed-inputs-bakeoff) and a member [added to a domain nobody re-read](https://typeglish.dev/blog/catch-all-vs-exhaustive). Retirement is the third direction, and it is the one nobody plans for, because adding a plan is a project and removing one is a cleanup ticket.

## §3 The green file that still sells it

Now watch what the fix does. There is one error, it names one line, and the obvious repair is to delete the arm that can no longer fire. Two lines out, and the file is clean.

cleared.tg - the dead arm deleted ✓ B (82/100)

```typeglish
<$CONFIG>
  $REQUIRE variable plan: one of essential, complete
</$CONFIG>

# Role
You are a broadband support agent for Kestrel Fibre.

# Constraints
- You MUST keep every reply to at most 4 sentences.
- IF the customer is on Platinum THEN you MUST NOT charge an engineer callout fee.
- WHEN a Platinum customer asks about speed THEN you MUST quote the 900 Mbps profile.
- You MUST offer a Platinum upgrade to any customer who mentions buffering.
$IF @{plan} is equal to complete:
  You MUST book a next-day engineer visit.
$ELSE:
  You MUST book the next available engineer visit.
```

0 error, 0 warning, 0 info, and B (82/100) again with runtime 93 and consistency 100, identical to the file from before the retirement. Three rules about a plan that cannot be sold, and every gate you own is green.

The build is the part to put in front of whoever asked for the retirement, because it is the only document in the loop that is about what the agent will actually be told.

tg build cleared.tg --vars - what the model reads now

```
$ npx typeglish build cleared.tg --vars '{"plan":"essential"}'
✓ built .typeglish/dist/cleared.txt ← cleared.tg (b99007c84615, full)

# Role
You are a broadband support agent for Kestrel Fibre.

# Constraints
- You MUST keep every reply to at most 4 sentences.
- IF the customer is on Platinum THEN you MUST NOT charge an engineer callout fee.
- WHEN a Platinum customer asks about speed THEN you MUST quote the 900 Mbps profile.
- You MUST offer a Platinum upgrade to any customer who mentions buffering.
You MUST book the next available engineer visit.
```

An agent serving an `essential` customer is instructed to waive a callout fee on a plan that no longer exists, to quote a 900 Mbps profile nobody is provisioned on, and to upsell the withdrawn product to anybody who mentions buffering. The chain resolved correctly. The prose did not resolve at all, because it was never a chain.

> The one rule the compiler could see is the one rule that got fixed. That is not a coincidence, it is the mechanism.

## §4 Why three of the four were unreachable

A closed domain is a promise about which values can occur. Once `plan` is `one of essential, complete`, every comparison against it is a claim that can be settled: `is equal to platinum` is decidable and the answer is no. That is the whole of what happened on line 13.

The other three rules never made a claim of that kind. *IF the customer is on Platinum* is a guard the model weighs at runtime against whatever it thinks it knows about the conversation, and *Platinum* there is a noun in a sentence. It is not a member of a set, so there is no set to check it against, so there is nothing to prove. The checker is not missing the rules. It has never been told they are about the same thing.

- **The machine plane is where retirement is checkable.** A typed input, a `$IF` condition, a `$SWITCH` arm, a `<tag when=…>` guard. These are read by the compiler, so a value that leaves the domain leaves an error behind at every site that referenced it.

- **The prose plane is where retirement is invisible.** Anything that reads as an instruction is passed through, and a passed-through rule about a dead product is still a live instruction. This is the same split that lets [a compile-time value and a runtime one](https://typeglish.dev/blog/prompt-compile-time-runtime) sit in one file looking identical.

- **Neither the score nor the count moves.** B (82/100) before, B (82/100) after, and the counts are 0 / 0 / 0 in both. There is no facet for *rules about things that no longer exist*, and there could not be one: the compiler has no way to know Platinum went.

- **Deleting is the direction with no gate.** A rule added to a prompt gets reviewed, because somebody wrote it this week. A rule that should have been deleted has no author and no ticket, which is why [a deletion nobody notices](https://typeglish.dev/blog/silent-deletions) and a deletion nobody performs fail the same way.

## §5 Put the plan back inside the domain

So the fix is not a better search for the word *Platinum*. It is to stop writing plan rules as sentences about plans and start writing them as arms of the plan. A `$SWITCH ON` block is the strongest form of that, because it takes members of one typed domain and proves coverage by construction.

fixed.tg - every plan rule inside an arm ✓ A (93/100)

```typeglish
<$CONFIG>
  $REQUIRE variable plan: one of essential, complete, platinum
</$CONFIG>

# Role
@@ role: inbound broadband support, plan-gated on the entitlement the host sends
You are a broadband support agent for Kestrel Fibre.

# Constraints
@@ brevity: four sentences keeps a chat reply scannable
- You MUST keep every reply to at most 4 sentences.
@@ visits: every plan rule lives in an arm, so retiring a plan is one compile error here
$SWITCH ON @{plan}
  - essential::
    @@ essential_visit: no visit SLA is sold on the entry plan
    You MUST book the next available engineer visit.
    @@ upsell: buffering is the one complaint the next plan up actually answers
    IF the customer mentions buffering THEN you MUST offer a Complete upgrade.
  - complete::
    @@ complete_visit: next-day is the sold SLA
    You MUST book a next-day engineer visit.
  - platinum::
    @@ platinum_visit: same-day is the sold SLA
    You MUST book a same-day engineer visit.
    @@ platinum_fee: the waived callout fee is the paid-for part of the plan
    You MUST NOT charge an engineer callout fee.
    @@ platinum_speed: 900 Mbps is the only profile provisioned on this plan
    IF the customer asks about speed THEN you MUST quote the 900 Mbps profile.

$TEST platinum_callout
  - input:: My line is down again and I am not paying another callout fee.
  - expect::
    - contains "no callout fee"
```

0 error, 0 warning, 0 info at A (93/100), hygiene 100, with `enforceability 68` named as the remaining lever. No policy changed and no rule reworded. Every sentence that used to say *Platinum* now sits under the member instead of naming it.

Then the retirement has two locks on it, and they close from opposite sides.

tg check - the two-way lock

```
# 1. take the member out of the domain, leave the arm alone
fixed-retired.tg:22:3  error  structure/impossible-case  "platinum" is not a member of @{plan}'s domain — one of essential, complete.

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

# 2. delete the arm, leave the domain alone
fixed-armless.tg:13:1  error  structure/non-exhaustive-switch  @{plan} can be platinum, but no arm handles it — add a "- <member>::" row for each (or a deliberate "- otherwise::" fallback).

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

You cannot half-retire a plan. Drop the member and every arm that mentions it is an error; drop the arm and the domain says a value is unhandled. The only way through is both edits, together, which is exactly the diff a retirement is.

And the payoff is the thing you can hand to the person who has to sign off that nothing else changed.

tg build --vars essential - before and after the retirement

```
# Platinum still in the domain, arm still present
$ npx typeglish build fixed.tg --vars '{"plan":"essential"}'
✓ built .typeglish/dist/fixed.txt ← fixed.tg (ad17a800993a, full)

# Platinum out of the domain, arm deleted: the two-line retirement
$ npx typeglish build retired-properly.tg --vars '{"plan":"essential"}'
✓ built .typeglish/dist/retired-properly.txt ← retired-properly.tg (ad17a800993a, full)

# Role
You are a broadband support agent for Kestrel Fibre.

# Constraints
- You MUST keep every reply to at most 4 sentences.
You MUST book the next available engineer visit. IF the customer mentions buffering THEN you MUST offer a Complete upgrade.
```

The same artifact hash, `ad17a800993a`, before and after. Retiring the plan is provably a no-op for every customer who was not on it, and the word *Platinum* is not in the deployed prompt at all. The retired-properly file still checks 0 error, 0 warning, 0 info at A (93/100).

tg test fixed.tg --dry - output

```
$ npx typeglish test fixed.tg --dry
✓ fixed.tg  coverage: 1/7 rules exercised
  · platinum_callout — "My line is down again and I am not paying another callout fe" (not run)
      ✓ contains "no callout fee"
✓ 1 prompt — 0 failed
```

One deterministic assert, settled offline, pinned to the rule most likely to survive a retirement it should not survive. When Platinum goes, this case goes with the arm, and its absence from the suite is a diff somebody reviews.

## §6 Common questions

**Why does my AI agent still offer a product we discontinued?**

Because withdrawing a product removes it from your price list and from nothing else. A support prompt with four rules about a retired broadband plan checks at 0 error, 0 warning, 0 info and scores B (82/100), and the three of those rules written in English keep checking clean after the plan is gone, because a prose rule is an instruction to the model rather than a claim about a declared set. The build proves it: with the plan retired and the file green, the artifact still reads *IF the customer is on Platinum THEN you MUST NOT charge an engineer callout fee*, *WHEN a Platinum customer asks about speed THEN you MUST quote the 900 Mbps profile*, and *You MUST offer a Platinum upgrade to any customer who mentions buffering*.

**Does TypeGlish catch a rule about a plan that no longer exists?**

Only where the plan was a member of a typed domain. Remove `platinum` from `$REQUIRE variable plan: one of essential, complete, platinum` and the chain arm that tested for it is a blocking `structure/impossible-case` reading `@{plan} is never platinum (it's one of essential, complete)`, and `typeglish build` answers *retired.tg refused, nothing written* at exit 1. The three prose rules naming Platinum in the same file produce no diagnostic at all, before or after, and the grade does not move: B (82/100) with runtime 93 and consistency 100 on both files.

**How do I remove a retired product from an agent system prompt safely?**

Put every rule about the product inside an arm of a `$SWITCH ON` over the typed input, so the compiler holds the domain and the rules together. Then the retirement locks in both directions: take `platinum` out of the domain and the arm is a blocking `structure/impossible-case`, delete the arm and leave the domain alone and it is a blocking `structure/non-exhaustive-switch` reading `@{plan} can be platinum, but no arm handles it`. Doing both is the two-line diff that actually retires a plan, and it is provably safe for everybody else: the `essential` build lands on `ad17a800993a` before the retirement and `ad17a800993a` after it.

**Why did removing one word from my prompt break the build?**

Because the word was a member of a closed set and something still compared against it. A domain is a promise about which values can occur, so an arm testing for a value the domain excludes can never fire, and an arm that can never fire is dead code rather than a harmless leftover. That error is the useful half of a retirement: it is the compiler naming every site that still believes in the thing you withdrew. The rules it cannot name are the ones written as English about the product, and those are the ones that keep reaching the model.

Field note

The habit worth taking from this is about where a fact lives rather than how it is worded. Anything in your product catalogue that a rule depends on is a closed set, and a closed set belongs in the file as one, declared once, with the rules hanging off its members. Then the catalogue and the prompt cannot drift, because the compiler holds them together and says so at exit 1. Write the same rules as sentences and you have made your prompt a place where retired products go to keep working: no error, no warning, no score movement, and an agent cheerfully upselling something the billing system would refuse to provision. The four rules in this post were written by four different people over two years, and every one of them was correct on the day it landed. That is the ordinary way a prompt becomes wrong.

∿ washed up Aug 26, 2026 ∿
