Habit · loads whenever its situation comes up, at any step

domain-and-types

about 1,800 tokens

domain-and-types as it installs, each line coloured by where its words came from.

  • 1 file
  • 1 edit
  • 6 sentences left out
  • 1 source
  • last changed 2026-10-09

Replaces model-the-domain and type-system-discipline (retired 2026-10-08).

The text

SKILL.md

about 1,800 tokens
  • pstack
  • greenline

---

name: "domain-and-types"

description: "Encode the domain in a structure instead of scattered conditionals, redesign around a new requirement instead of bolting it on, and let the type checker rule out impossible states: state machines, discriminated unions, branded primitives, parsing at the boundary, exhaustive matching. Use when writing stateful logic or a branch that grows by one more case, when a new requirement meets an existing design, and when designing a type or a function signature in any typed language."

---

greenlineOpens the habit: when it applies; boundaries handles parsing outside data

domain-and-types is the habit for the moment stateful logic is written, a branch grows by one more case, a new requirement meets an existing design, or a type or a function signature is designed, at whatever step the work is in, most often build. It writes no record of its own. Where data enters from outside, boundaries says where it is parsed and validated.

pstackprinciple-model-the-domain/SKILL.md

Encode the real domain in a data structure instead of scattering it across conditionals.

**Why:** Scattered booleans, repeated shape assumptions, and branching spread across files are accidental complexity. A structure that matches the domain makes invalid states unrepresentable and deletes branches. Choosing it at write time is cheap. Recovering it later reads as a refactor and gets deferred.

**Reach for structures like these:**

- A state machine instead of scattered booleans, phases, or lifecycle checks.

- A typed object/model instead of loose parameters or repeated shape assumptions.

- A map, registry, lookup table, or discriminated union instead of branching spread across files.

- A reducer or command/event model instead of ad hoc state mutations.

- A module organized around one body of domain knowledge instead of a sequence such as load, validate, transform, and save. Execution order is not ownership.

- A small module boundary that gathers repeated behavior, ownership, or invariants.

- A queue, cache, index, graph/tree, or normalized collection where the data access pattern calls for it.

- Any other structure that fits. When none fits, work out what the code must never allow and how the data gets read, then find the structure that encodes exactly that.

Do not force an abstraction. Prefer boring code if the current shape is already clear, local, and unlikely to grow. Be skeptical of an abstraction that adds indirection without removing branches, duplicated rules, invalid states, or lifecycle risk.

The sign that you skipped this is a new feature that grows an existing if/else chain by one more branch, or a second boolean that must stay in sync with the first. Temporal decomposition is another sign. Phase-named modules repeat the same domain rules across steps.

pstackprinciple-redesign-from-first-principles/SKILL.md

When integrating a change, don't bolt it onto the existing design. Redesign as if the requirement had been there from the start.

- Read all affected files and understand the current design

- Ask: "if we were writing this from scratch with this new requirement, what would we build?"

- Propagate the change through every reference: types, docs, examples, rationale sections

- Think about the whole redesign, then deliver it incrementally

This is the method for preserving option value when integrating changes into an existing design.

greenlineRedesign applies when the shape changes; clear boring code stands

The redesign applies where the requirement changes the shape. Where the current shape is already clear, local and unlikely to grow, the boring code above stands.

pstackprinciple-type-system-discipline/SKILL.md

The type checker is a proof assistant. Use it to eliminate impossible states, mismatched primitives, and unhandled variants at compile time. A case the types let you ignore becomes a runtime failure the compiler could have stopped. Prefer defining errors and special cases out of existence over proliferating handlers. Unrepresentable states, total functions, and interface redesign (the patterns below) are the tools.

pstackprinciple-type-system-discipline/SKILL.md

**The patterns:**

- **Make illegal states unrepresentable.** Model variants as sum types: discriminated unions in TypeScript, enums with payloads in Rust/Swift/Kotlin, sealed classes in Scala, ADTs in Haskell/OCaml. Don't model state as a bag of optional fields where contradictory combinations compile. A subtle anti-pattern: `{ completed: boolean; completedAt?: Date }` admits `completed: true; completedAt: undefined`, which is meaningless. Derive the boolean from a single source like `completedAt !== null`, or model the variants explicitly as `{ kind: 'open' } | { kind: 'done'; at: Date }`. If a bug forces the question "wait, can this combination actually happen?", the type is too loose.

- **Types are constructions, not restrictions.** Build the type up from the values you want instead of carving them out of a looser type with checks. The invariant that seems to need a refinement type is usually a construction away. A non-empty list is a head plus a rest, not a list with a length check. A valid time range is a start plus a duration, not two timestamps you must keep ordered. No representation is privileged. A list of pairs is an even-length list if you interpret it that way, so choose the shape that cannot build the illegal value and expose the interface callers need on top.

- **Brand semantic primitives.** `UserId` and `OrderId` are strings underneath but should not be interchangeable. Newtypes in Rust, opaque types in Swift, value classes in Kotlin, phantom types in Haskell, branded intersections in TypeScript. Validate once at creation, trust the type downstream.

- **External data is untyped until parsed.** RPC payloads, JSON, IPC messages, CLI args, config files, environment variables, database rows. Have a parse function at every boundary that turns unstructured input into the typed model. See the **boundaries** habit for where to put validation.

- **Don't lie to the type system.** Casts, unsafe coercions, and assertion functions that bypass the compiler are latent runtime crashes. If the compiler can't prove a fact, prove it (validate, narrow, refine the model) or accept that the cast is a hazard.

- **Exhaustive matching is the compiler's job.** When you match on a sum type, the compiler must fail compilation if a new variant is added without handling. Use the idiom your language provides: `never`-typed binding in TypeScript, unannotated `match` in Rust, `-Wincomplete-patterns` in Haskell, sealed-class match exhaustiveness in Kotlin.

- **Derive types from authoritative schemas.** When a protocol buffer, OpenAPI spec, GraphQL schema, database migration, or design-system token file defines a shape, derive from it instead of hand-rolling a parallel type.

pstackprinciple-type-system-discipline/SKILL.md

- **Strengthen a type only where partiality appears.** A runtime assertion, null check, or "this should never happen" throw marks the place a type is too weak. Push that check up into the type. Then stop. The type system's job is to track the cases each use site must handle, not to describe the data as precisely as possible. Prefer total functions. `sum` of an empty list is 0, so it takes the plain list. `head` of an empty list has no answer, so it demands the non-empty one.

**The tests:**

- "Can I write a comment explaining when this combination of fields is valid?" If yes, the type is too loose. Split it into a sum type.

- "Do two of my function arguments share a primitive type but mean different things?" Brand them.

- "Where did this `any`, this `as`, this `assertNotNull` come from?" Trace it to the boundary and validate there instead.

- "If a new variant is added next month, will the compiler tell the next agent where to add a case?" If no, the match isn't exhaustive.

- "Is this type duplicating a shape another file owns?" Derive instead.

- "Am I strengthening this type to keep an operation total, or just to be more precise?" If nothing would otherwise panic, keep the plain type.

SKILL.md as installed, byte for byte SHA-256 25d2f3cdf1c4d37cb28eb1b1d515f3cce2ace9f246c4e459394065784c379dff

The edits

Red is upstream's text; green is what greenline shipped in its place.

What the edit kinds mean
method
changes what the method does, under a recorded ruling
correction
fixes an error
scope
fits the text to this skill
pointer
re-points a reference
smaller edits
rename, relocate or adjust tool details
1 smaller edit

Renames, locations and tool details, each with its reason.

  1. Edit 1 rename SKILL.md:51

    Upstream: See the **boundary-discipline** principle skill for where to put validation.

    Shipped: See the **boundaries** habit for where to put validation.

    boundary-discipline is now the boundaries habit

    Full reason

    boundary-discipline is the habit boundaries now (R22).

What greenline wrote

The full reason for every passage greenline wrote, in the order of the text.

  1. SKILL.md:6 rename

    The habit's title is its roster name, domain-and-types, which R22 gives the habit model-the-domain and type-system-discipline became.

  2. SKILL.md:8 scope

    One opening paragraph in the habit's own voice: the situations the block's rows key it by, that it writes no record of its own, and the boundaries habit beside it for where outside data is parsed.

  3. SKILL.md:29 method

    principle-redesign-from-first-principles joins as the procedure for the sign the spine names last, a requirement that would grow an existing design by one more special case. R1

  4. SKILL.md:40 method

    model-the-domain's "Prefer boring code if the current shape is already clear" is read as the redesign's condition, not a contradiction, as the operator ruled. R1

    Credits: pstack

  5. SKILL.md:42 scope

    type-system-discipline's title becomes the section's heading, since the habit takes its name from both halves.

What it left out

pstack

pstack, principle-model-the-domain/SKILL.md: 1 of 23 sentences left out
  1. # Model the Domain left out: The title gives way to the habit's roster name (R22). replaced by line 6 of SKILL.md
pstack, principle-redesign-from-first-principles/SKILL.md: 1 of 8 sentences left out
  1. # Redesign From First Principles left out: The title becomes the section's heading. replaced by line 29 of SKILL.md
pstack, principle-type-system-discipline/SKILL.md: 4 of 61 sentences left out
  1. # Type System Discipline left out: The title becomes the section's heading. replaced by line 42 of SKILL.md
  2. 5 sentences taken
  3. Applies to any typed language.
  4. Skills like `typescript-best-practices` ground it in specific syntax. 2 sentences left out: The typescript-best-practices skill is not vendored, so the body pointed the agent at a skill the install does not carry.
  5. 30 sentences taken
  6. See the **encode-lessons-in-structure** principle skill. left out: the encode-lessons-in-structure principle skill is not vendored; the pointer stays out of the derive-types bullet

The ledger entry

greenline's ledger logs how each skill was made and changed, with a reason for each entry.

2026-10-09 2026-10-09-domain-and-types-gists

Every account gets a short gist for the website

Adds a gist of at most twelve words, for the website's edition, to every account of the recipe whose reason is stated. No text, measure or method changes.

Authorised by
2026-10-09 J-59, on the operator's words of 2026-10-09 ('okay then implement all of this'): a gist of at most twelve words on every account whose reason is stated; no text, measure or method changes
Pages it touched
none
Rulings, R1 to R6
  1. the operator's rulings of 2026-10-08 on the intake R3 ("everything is good", and "yes go ahead" on the portable forms of R21), the verdicts standing and every contradiction ruled as recommended: model-the-domain and type-system-discipline are one habit, and principle-redesign-from-first-principles grafts its procedure, ruled not a stub, with "Prefer boring code if the current shape is already clear" read as its condition
  2. The method items carry the operator's rulings of 2026-10-08 on the intake R3 ("everything is good", and "yes go ahead" on the portable forms of R21), the verdicts standing and every contradiction ruled as recommended: model-the-domain and type-system-discipline are one habit, and principle-redesign-from-first-principles grafts its procedure, ruled not a stub, with "Prefer boring code if the current shape is already clear" read as its condition
  3. the operator's ruling R22 of 2026-10-08 on the re-cut brief, "yes amend, with one change": the roster table's names stand, deep-modules, domain-and-types, boundaries, concurrency, secrets and prose among them, tdd and ponytail keeping theirs
  4. the operator's rulings of 2026-10-04, "cool then lock everything in": every change has one record its steps write, and a habit writes none of its own
  5. R16 of 2026-10-07, a habit loaded when its situation arises at any step ("update the agents md created by greenline product to mention the kind with the skill table and also prose about when to use what kind skill")
  6. J-59, on the operator's words of 2026-10-09 ('okay then implement all of this'): a gist of at most twelve words on every account whose reason is stated; no text, measure or method changes
1 earlier entry
  1. 2026-10-08 2026-10-08-domain-and-types-recipe

    Model-the-domain and type-system-discipline become one habit

    Pages it touched: SKILL.md