nw-certainty-by-construction · git:20261102.c67d57b · 2026-11-02 · sha256 ce00b5f151387655
nw-certainty-by-construction git:20261102.c67d57bA
Immutable. This exact content never changes and is served at /api/v1/blob/ce00b5f151387655.
--- name: nw-certainty-by-construction description: Turn a stable layer claim (domain, application, adapter, or infrastructure) into a construction boundary so the invalid state cannot be built, and state honestly what remains unguarded. Use when a requirement says an invalid state or transition must not occur, when values need a canonical form, or when a rewrite/cache/optimisation must preserve meaning. Complements nw-fp-domain-modeling, which shows the encodings; this decides whether to encode, how strong the claim really is, and what obligation is left over. --- # Certainty by Construction `nw-fp-domain-modeling` shows encodings; this decides whether a claim belongs in construction, its honest strength and the obligation left outside. Safety is exclusion only. Also establish **valid-state inhabitation**: at least one intended value has an acyclic construction path from admitted input. Public reachability (that path reaches a public observation) and liveness (eventual progress under temporal assumptions) are separate claims; construction alone does not establish either. **Layer-neutral (ADR-SSOT-002 §6a).** A claim is encoded at whichever layer it is stable, local and high-value — domain state/transition, application/ port outcome, adapter/integration failure translation, or infrastructure/ recovery policy. Which layers apply to a target derives from that target's declared `boundary`/`contract-shape`, never a hand-picked "domain-only" scope. ## Work from the claim outward 1. **State the proposition precisely.** "A `ConfirmedBooking` has an approved payment." Name its owner and its semantic equality. 2. **Draw the trust boundary.** Which inputs and writers are untrusted, where effects and persistence enter, and **who can bypass the constructor**. 3. **Choose the strongest proportionate encoding.** A guarantee is only real if its constructors and eliminators make the prohibited state hard to express. 4. **State the residual obligation.** Record stable/local admitted, excluded and unknown states with risk owner; route triggered stressors to `nw-stress-analysis`; triage a removed-hypothesis counterexample before a missing-type claim. Do not convert external, temporal or cross-system rules into local type claims. 5. **Demonstrate both sides.** When implementing, add tests in the owned test paths that the invalid construction is refused with its reason and that a valid value is built through the public producer; a throwaway script is not that test; when no test path is owned, name the uncovered refusal in the diagnostic for the oracle owner. For a call the checker rejects, keep the receipt of a failing run of the repository's own checker or compiler on a separate probe. Before refining, ask: is the invariant stable, local and high-value; can all legitimate construction pass through this boundary; will the representation survive the API, serialization and interop edges? If not, keep it **extrinsic** — and carry it deliberately rather than pretending it is intrinsic. For each intended public value, name admitted input, public constructor or producer, consumer and public observation. A cycle is valid only with an explicit bootstrap outside the cycle; otherwise its safety claim has no inhabited valid value. ## Make propositions carry evidence Treat `P(x)` as a type of evidence for `P(x)`. - Prefer a validated constructor, opaque value object, non-empty wrapper or state-specific type over a primitive plus a convention. - Make construction the only ordinary route in. A public raw constructor that recreates the invalid state cancels the guarantee. - **Return the evidence, not a Boolean.** A decision's success branch carries a witness; its failure branch carries a reason. `true`/`false` throws away what the caller needs next. - Keep operations total over their stated input types; where the language cannot make an alternative unrepresentable, put it in the **result type** instead. **One structural owner.** When multiple boundary views express a declared closed structural contract, derive the needed schema, shape parser or field-aware traversal from one description rather than re-declaring alternatives in preflight. Check raw allowed keys before normalizing declared slots: an unknown key remains invalid even when its value is null or empty. Resolve a selector once and return its witness to the consumer. On the declared structural input domain, `schemaAccepts(x) iff shapeParse(x) succeeds`; reference existence and mutable policy remain separate semantic obligations. Generic derivation: Sandy Maguire, *Thinking with Types*, Generics → Using Generic Metadata; intrinsic construction: *Certainty by Construction*, Decidability → Intrinsic vs Extrinsic Proofs. Raw-key ordering and selector reuse are application synthesis. **Semantic closure before construction activity.** When refusal forbids issuance, interning, rendering or writes, finish selection and semantic decisions over a pure description before invoking those activities. A ready plan has no remaining semantic refusal; interpretation may still return declared environmental failure. For an account-change batch, decide against evolving descriptive state, then materialize only the accepted plan. Law: semantic refusal preserves prior state **and has an empty protected-activity trace**. Scope that trace to the contract's named activities, data and outcomes: a refusal-only promise does not forbid interning discarded data on an accepted path, nor every interning call when only a particular data subset is protected. A separate discard prohibition needs its own stated scope. Purity or rollback alone cannot establish invocation exclusion. This is application synthesis, not a theorem supplied by the books: Sandy Maguire, *Algebra-Driven Design*, Deriving Implementations → Tile Implementation → The Initial Encoding; *Thinking with Types*, Indexed Monads → Definition and Necessary Machinery; *Certainty by Construction*, Decidability → Intrinsic vs Extrinsic Proofs. This boundary is warranted by that prohibition, not by every ordinary parser or pure function. ## Prefer canonical meaning Decide first whether semantically equal values should have ONE representation. If yes, design the canonical form into the constructors and the later normalization and equality burden disappears. If several representations are intentionally valid, specify all four: the denotation map, the equivalence relation, the normalization, and the **preservation claim** — that normalization has the same denotation as its input. Never hide a lossy conversion behind the word "normalization". **Semantic comparison versus retained bytes.** When a format admits LF and CRLF as the same layout but promises byte preservation, normalize the comparison view, not the retained source. Law: no semantic change returns the original bytes; a status-only update changes only its designated region, preserving all other bytes. Exercise both admitted line endings and byte-distinct surrounding text. A new export explicitly specified as canonical LF needs no original-byte buffer. Exact newline and update policies are application synthesis from Sandy Maguire, *Algebra-Driven Design*, Designing Algebras → Tiles → Observations; *Certainty by Construction*, An Exploration of Numbers → Unique Integer Representations; Appendix: Ring Solving → Semantics. ## Preserve meaning before optimising | Change | The argument you owe | |---|---| | Same information, different representation | Isomorphism: both round trips preserve the intended equality | | Mapping computations across an operation | Homomorphism: identity and composition preserved | | Rewrite, cache, memoize, compile | A relation to the specification, plus that every operation preserves it | An equivalence argument establishes meaning. It says nothing about speed, memory or availability — those are separate claims and must be measured separately. ## Calibrate the claim to the language — say what you actually have Choose the row by the checker the repository actually runs in its verification command, not by the language's reputation. | Environment | Honest claim | |---|---| | Proof assistant | The property follows from checked definitions and admitted axioms | | Strong static language | Invalid states blocked on typed paths; casts, reflection, deserialization and foreign writers remain boundaries | | Gradual typing with the checker in verification (e.g. Python + mypy, TypeScript) | The checker rejects modeled invalid calls on annotated paths, so the guarded operation takes the evidence type; `Any`, casts, untyped callers and deserialization still need the runtime boundary | | Mainstream OO/FP | Construction is centralised and misuse reduced; the compiler does **not** prove the predicate | | Dynamic, or no checker in verification | Checked at controlled runtime boundaries, and every writer must preserve it | Do not call all type safety "proof", and do not simulate proof strength with nominal wrappers: a wrapper, annotation or nominal type the runtime can build around the same invalid value is not a proof. Name the bypass routes and keep their checks visible. ## What construction never establishes Stakeholder intent, authorization, time-dependent policy, cross-aggregate consistency under concurrency, external API behaviour, persistence integrity after a bypass, cryptographic soundness, availability. Validate those at their owning boundary and say so. A false proposition is not a missing implementation detail. Revisit the statement or the representation. ## Cross-cutting invariants Load `nw-cross-cutting-invariants` alongside this — especially `gate:self-explaining-what-why-how`: a constructor that refuses must say WHAT was invalid, WHY it cannot be built, and HOW to build a valid one.