fsl-design · git:20261107.deab533 · 2026-11-07 · sha256 c078aa326b80cdf2

fsl-design git:20261107.deab533A

Immutable. This exact content never changes and is served at /api/v1/blob/c078aa326b80cdf2.

---
name: fsl-design
description: Write and verify FSL design-layer specs for engineers from an agreed requirements contract. Use for internal state/action design, decomposition, queues/outboxes, compose, refinement mappings to requirements (including requirements written with entities, events, rules, decision tables and a glossary), typestate checks, scenarios-to-testgen handoff, and implementation-conformance planning. Do not use for PM requirements authoring or business process discovery.
---

# FSL Design Layer

A headerless document is FSL 4; to migrate it, see the migration guide in `docs/manual/LANGUAGE.md` ("FSL 4 to FSL 5 migration").

Use this skill after a requirements contract exists or the user explicitly asks
for engineering design. The design layer is an ordinary kernel `spec` headed
`fsl 5`, plus a refinement mapping back to the requirements layer. That mapping
is the **refinement seam, not a plain baton pass**: the design must refine the
(frozen) requirements contract, and `fslc refine` verifies the seam. When a
seam breaks, repair the design or the mapping — not the upper contract, unless
a human revises it.

Kernel syntax (state, init, actions, properties, `struct`, `Map`, `Seq`,
`compose`) is in `../fsl/references/syntax.md`; the refinement file and the
seam rules are in `../fsl/references/syntax.md` and
`../fsl/references/layers.md`. Use `fsl-design-review` instead when the user
asks for a design review, SOLID judgment, substitutability, or variant
comparison.

## What the `fsl 5` header changes here

- The kernel language is the same. The stricter rules of the edition are
  errors: `check` answers `result: "error"`, `kind: "unsupported_in_edition"`
  with `findings[]` (for example `legacy_string_metadata` for an action
  string `"REQ-1: text"`: write `@requirement("REQ-1", "text")` on the line
  before the declaration). `fslc migrate --edition 5 file.fsl` plans the
  edits and the header; `--write` applies them.
- A requirements spec of this edition is written in entities, events, rules
  and decision tables, but **the design refines its lowered kernel**, not its
  surface. The mapping names the lowered state and actions.
- The glossary is not read in a design spec or a refinement file. Write both
  in canonical (ASCII) names; a requirements spec written in Japanese has the
  same lowered names as its English spelling, and the same mapping refines
  either.

## Reading the upper contract

Run `fslc explain --readable requirements.fsl` (with a glossary, add
`--names canonical`). Its "State" and "Actions" sections are what you map:

| Requirements surface | Lowered name the mapping uses |
|---|---|
| `entity Return { amount: … }` | `return_amount: Map<Return, Amount>` |
| its `lifecycle` lines | `return_stage: Map<Return, ReturnStage>`; the enum's members are the stages, in order of first appearance |
| `event request = Customer requests Return with amount Draft -> Requested` | action `request(r: Return, amount: Amount)`: the instance first, then the inputs |
| `Order belongs to User as buyer via place` | `order_buyer: Map<Order, Option<User>>`; `place` gets the `User` instance as its second parameter |
| `P only after Q` | a history field `order_did_<events of Q>` |
| `resource stock: Qty` | `stock` |
| `allow` / `never` / a decision row | an extra `requires` on the event's action |
| `always` / `enters` / `assuming` | an invariant or transition property |

With an origin-aware `fslc`, `fslc explain --readable` also prints after each
`requires` where it came from (`← move Draft -> Requested`, `← decide REQ-… row
1: …`, `← rule REQ-… allow …`), and `fslc explain --lowering` gives the same as
JSON. Read them to see which rule or decision row a refinement failure on that
action is about (`../fsl-verify/SKILL.md`, the origin self-check).

## Example

The design below refines the requirements example of
`../fsl-requirements/SKILL.md`: requests split by amount into two review
queues, and the refund is a two-step exchange with a payment provider.

```fsl check
fsl 5
spec ReturnDesign {
  entity Return
  number Amount
  const AUTO_LIMIT = 1

  enum Phase { Drafting, AutoReview, ManagerReview, Cleared, Declined, RefundSent, RefundDone }
  struct Case { phase: Phase, amount: Amount }

  state {
    cases: Map<Return, Case>
  }
  init {
    forall r: Return { cases[r] = Case { phase: Drafting, amount: 0 } }
  }

  @requirement("REQ-RET-003", "a small return goes to automatic review")
  fair action request_small(r: Return, a: Amount) {
    requires cases[r].phase == Drafting
    requires a > 0
    requires a <= AUTO_LIMIT
    cases[r] = Case { phase: AutoReview, amount: a }
  }

  @requirement("REQ-RET-004", "a large return goes to manager review")
  fair action request_large(r: Return, a: Amount) {
    requires cases[r].phase == Drafting
    requires a > AUTO_LIMIT
    cases[r] = Case { phase: ManagerReview, amount: a }
  }

  @requirement("REQ-RET-003", "the system approves a return in automatic review")
  fair action auto_approve(r: Return) {
    requires cases[r].phase == AutoReview
    cases[r].phase = Cleared
  }

  @requirement("REQ-RET-004", "a manager approves a return in manager review")
  fair action mgr_approve(r: Return) {
    requires cases[r].phase == ManagerReview
    cases[r].phase = Cleared
  }

  @requirement("REQ-RET-004", "a manager rejects a return in manager review")
  fair action mgr_reject(r: Return) {
    requires cases[r].phase == ManagerReview
    cases[r].phase = Declined
  }

  @requirement("MODEL-RET-001", "the refund is sent to the payment provider")
  fair action refund_send(r: Return) {
    requires cases[r].phase == Cleared
    cases[r].phase = RefundSent
  }

  @requirement("MODEL-RET-002", "the payment provider confirms the refund")
  fair action refund_confirm(r: Return) {
    requires cases[r].phase == RefundSent
    cases[r].phase = RefundDone
  }

  @requirement("REQ-RET-002", "only a small return is in automatic review")
  invariant AutoReviewIsSmall {
    forall r: Return { cases[r].phase == AutoReview => cases[r].amount <= AUTO_LIMIT }
  }

  @requirement("REQ-RET-005", "a requested return keeps a positive amount")
  invariant AmountPositive {
    forall r: Return { cases[r].phase != Drafting => cases[r].amount > 0 }
  }

  terminal { forall r: Return { cases[r].phase == RefundDone or cases[r].phase == Declined } }
}

verify {
  instances Return = 2
  values Amount = 0..3
}
```

The mapping (a refinement file of its own, so shown as a fragment):

```fsl-fragment
fsl 5
refinement ReturnDesignRefinesRequirements {
  impl ReturnDesign
  abs  ReturnRequirements

  map return_stage[r: Return] =
    if cases[r].phase == Drafting then Draft
    else if cases[r].phase == AutoReview then Requested
    else if cases[r].phase == ManagerReview then Requested
    else if cases[r].phase == Cleared then Approved
    else if cases[r].phase == Declined then Rejected
    else Refunded
  map return_amount[r: Return] = cases[r].amount

  action request_small(r, a) -> request(r, a)
  action request_large(r, a) -> request(r, a)
  action auto_approve(r)     -> auto_approve(r)
  action mgr_approve(r)      -> mgr_approve(r)
  action mgr_reject(r)       -> reject(r)
  action refund_send(r)      -> refund(r)
  action refund_confirm(r)   -> stutter
}
```

`fslc refine design.fsl requirements.fsl mapping.fsl` returns `refines`
(depth 8), against the English requirements spec and against its Japanese
spelling alike. Removing `requires a <= AUTO_LIMIT` from `request_small` (and
the invariant that notices it) makes it `refinement_failed`,
`abs_requires_failed` on `auto_approve`: the design would approve a large
return automatically, which the upper decision table forbids.

- Name the design's own enum members apart from the upper stage names
  (`Drafting`, not `Draft`): a mapping that sees a member in two enums is an
  error (`ambiguous enum member`).
- `refine` carries safety. An upper `policy` / `leadsTo` is checked through the
  mapping only with `preserve progress { respond … by … }`; otherwise verify
  progress in the design itself.
- Tag each design declaration with the requirement it realizes:
  `@requirement("REQ-RET-003", "…")` with the upper spec's rule, decision
  table or row ID; `MODEL-` / `ASSUME-` for a design choice the requirements
  do not state.

## Boundary

Produce design-layer artifacts only:

- A kernel `spec` for internal design state and actions
- A refinement mapping from design to requirements when a stable requirement
  spec exists
- Verification/refinement commands and result interpretation
- Optional `testgen`, `replay`, or `typestate` handoff plan

Do not rewrite business policies or PM requirements to make the design pass.
If the upper contract is too strict or ambiguous, report the counterexample and
ask for a contract decision.

## Workflow

1. Confirm the upper contract: the requirements/business file that must remain
   stable, its lowered state and actions (above), which design actions
   correspond to which upper actions and which are true stutters, and the
   human-owned design judgments.
2. Write the design spec as kernel FSL headed `fsl 5`. It may include internal
   states, queues, outboxes, two-phase operations, retries, and decomposition
   absent from the requirements, as long as they map back. List the failures
   the design must exclude (processed twice, lost, out of order, inconsistent
   after a crash, wrong on retry, interleaved in the middle of a multi-step
   procedure, a quantity not conserved) and declare for each the property it
   would break (`../fsl/references/nl-to-syntax.md`); if neither the source nor
   the upper contract states the property, ask. Model transaction boundaries,
   queues/outboxes, and retries/idempotency as state and actions, not as one
   step or a comment. `acceptance` / `forbidden` belong to the requirements
   layer; a kernel `spec` that declares them is a parse error.
3. Tag every action and invariant with `@requirement("ID", "intent")`.
4. Verify the design itself: `fslc check`, `fslc lint <file>` (no findings),
   `fslc verify --depth 8`, `fslc verify --engine induction`; for
   traceability `fslc check <file> --strict-tags --requirements ids.txt`.
   A bounded `verified` says nothing past its depth and domain sizes.
5. Write the mapping file: `map abs_var[x: T] = expr` for every lowered state
   variable of the upper spec, `action impl(...) -> abs(...)` for visible
   effects, `action impl(...) -> stutter` only when the action changes no
   mapped state, and `preserve progress { respond AbsLeadsTo by impl_action }`
   when an upper progress property must hold through the mapping.
6. Run `fslc refine design.fsl requirements.fsl mapping.fsl`. Repair by
   deciding whether the design is wrong, the mapping is wrong, or the upper
   contract needs human revision.
7. If implementation anchoring is required, run `fslc testgen` and wire the
   Adapter, or use `fslc replay` against execution logs. Generated tests do not
   enumerate interleavings or crash points inside one action; plan
   implementation tests for those (`../fsl/references/impl.md` §9).

## Guardrails

- Treat `abs_requires_failed` as a design bypass of an upper guard — an
  `allow` / `never` rule or a decision row — until proven otherwise; the
  origin of that `requires` names which.
- Treat `stutter_changed_abs` as a false claim of internal-only behavior.
- Treat `progress_lost` as a lower-layer liveness failure: inspect the
  lasso/stall trace, then add/restore `fair` on the lower progress action or
  write a lower-layer ranked `leadsTo`.
- Do not model variants by editing the abstract spec each time; keep variant
  detail in the design layer and fold it through the mapping.
- When you change an existing design spec, re-run `fslc verify` on it and
  `fslc refine` against the unchanged requirements, and, when old and new
  share state and action names, `fslc diff old.fsl new.fsl --forbid
  behavior_removed,unknown` (plus the kinds your change policy rejects;
  `../fsl-verify/SKILL.md`). `refine` passes a change that only makes the
  design refuse more; `diff` reports it as `behavior_removed`.
- Report proof categories separately: design invariants proved, refinement to
  requirements proved, implementation conformance not yet proved unless
  `testgen` or `replay` has been anchored to real code/logs.
- Author the design spec as documentation: declare domains with `entity` /
  `number` (sizes in `verify`), tag every action/invariant, and prefer
  `forall x in coll { … }` over index quantifiers in invariant bodies.