home / skills / tkersey / dotfiles / invariant-ace

invariant-ace skill

/codex/skills/invariant-ace

This skill enforces inductive invariants at boundary points to prevent impossible states and improve reliability across systems.

npx playbooks add skill tkersey/dotfiles --skill invariant-ace

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
10.9 KB
---
name: invariant-ace
description: "Turn 'should never happen' into 'cannot happen' by defining owned inductive invariants and enforcing them at parse/construct/API/DB/lock/txn boundaries with a verification signal. Use when prompts mention invariants, impossible states, validation sprawl, cache/index drift, idempotency/versioning, retries/duplicates/out-of-order events, race/linearization bugs, loop correctness, or hardening another workflow (for example $fix) with invariant checks first."
---

# Invariant Ace

## Mission

Turn "should never happen" into "cannot happen" with minimal, high-leverage changes: pick owned, inductive invariants; enforce them at the strongest cheap boundary; prove via a concrete counterexample trace and a verification signal.

## Use When (Signals)

- Null/shape surprises, runtime validation sprawl, or input decoding scattered across the codebase.
- Redundant stored facts drift (cache/index/denormalized columns) or "fix-up" code runs often.
- Flags/states explode; impossible combinations appear; "unreachable" is reachable.
- Races, duplicate/out-of-order events, retries, partial failures, or "exactly once" assumptions.
- Idempotency keys, monotonic version/epoch checks, stale writes, or linearization questions are central.
- Loop/algorithm correctness depends on comments or intuition; tricky indexing/arithmetic/termination.
- "Should never happen" branches show up in logs or error trackers.

## Routing Priority

- If a task has invariant/protocol cues and also asks for broad implementation (`$tk`, `$fix`, `$work`), run this skill first to lock invariants, then execute edits.
- If you cannot name state owner + transitions, switch to clarification/discovery before implementation.

## Core Model (Fast Definitions)

- Invariant: predicate P(state) intended to hold for all reachable states in a scope.
- Inductive: true initially AND preserved by every allowed transition in that scope.
- Owner: the single module/type/transaction/lock/actor that controls all mutations needed to preserve P.
- Precondition/postcondition: caller obligation vs operation guarantee; do not mislabel these as invariants.
- Derived property: recomputable fact; avoid storing it as "must match" unless you centralize updates.
- Safety vs liveness: invariants are safety ("nothing bad"); keep progress ("eventually") separate.

## Immediate Scan

- State owner: where does the truth live (type/module/service/table)?
- State boundary: where does raw data enter (API/DB/file/queue)?
- Allowed transitions: list operations/events that mutate the state (including retries and concurrency).
- Failure today: one concrete trace (inputs + transitions + schedule) that reaches a bad state.
- Protection level: hope -> runtime -> construction-time -> type/compile-time -> persistence/protocol/atomicity.
- Pain tag(s): data | concurrency/protocol | algorithm/loop (often multiple).

## Protection Ladder

Choose the cheapest strong layer that makes the violation hard or impossible.

- Hope-based: comments, assumptions, "unreachable".
- Runtime: scattered guards/validators near use sites.
- Construction-time: parse/validate once at boundaries; core code only handles refined values.
- Type/compile-time: illegal states are unrepresentable (ADTs, typestates, opaque wrappers).
- Persistence: schema/constraints/transactions enforce invariants at rest.
- Concurrency boundary: locks/actors/CAS/txns define where invariants must hold (under lock, at commit, at linearization).

## Protocol (Counterexample-Driven)

1. Declare scope + owner.
   - Write "P holds when/where": always | after construction | under lock | at txn commit | after message apply.
   - If you cannot name an owner, the invariant will drift; pick a choke point first.

2. List transitions and try to break P.
   - For each transition (and retry/out-of-order variants), attempt a counterexample trace.
   - If P fails, decide: bug vs wrong scope vs missing state vs wrong owner.

3. Make P inductive (or downgrade it).
   - Weaken P, move it to pre/postconditions, or add auxiliary state (version/epoch/status/idempotency key) until it closes under transitions.

4. Run a coordination check (concurrency/distributed).
   - Ask: can two individually-valid concurrent transitions merge into a P-violating state?
   - If yes, you need coordination (lock/txn/consensus) OR you must redesign the invariant/operation (partition, escrow, monotone merges, idempotency).

5. Encode enforcement at the strongest cheap boundary.
   - Prefer: parser/decoder + smart constructors + narrow/opaque types + centralized mutation.
   - Avoid: N scattered validators, duplicated truths without a single writer, and "fix-up" routines on every read.

6. Add observability if full enforcement must be staged.
   - Add cheap tripwires (assert/log/metric) and quarantine paths (reject/dead-letter/compensate).
   - Record replayable context (transition name, IDs, versions), not raw secrets.

7. Verify with the right harness.
   - Data: fuzz/property tests on parsers/constructors.
   - State machines: stateful/model-based tests (sequences).
   - Concurrency: stress + schedule perturbation; assert at quiescent points.
   - Protocols: small model checking/simulation for drops/dupes/reorder.
   - Algorithms: invariant assertions in loops + differential tests vs reference.

## Compact Mode (Fast Path)

Use this when the task is small or time-boxed.

1. Counterexample: one concrete failing trace (<=5 transitions).
2. Invariants: 1-2 predicates with explicit owner + scope.
3. Enforcement Boundary: one chosen choke point (parse/construct/API/DB/lock/txn).
4. Verification: one signal tied to one predicate.

Escalate to full protocol if any of the above is ambiguous or non-inductive.

## Invariant Record (Use This Format)

- Predicate: P(state) (precise, checkable)
- Owner: module/type/service/table/lock/txn
- Holds: always | after construction | under lock | at commit | after apply
- Maintained by: transitions that must preserve P
- Enforced at: parse/construct/API/DB/lock/txn/protocol
- Counterexample to avoid: minimal trace that breaks it today
- Verification: property/stateful/stress/model/differential

## Patterns by Pain

### Data Modeling & Input Validity

- Boundary refinement: raw -> parsed -> validated; only validated enters core.
- Canonicalization: normalize early (case/whitespace/timezone/ID format) so equality and caching are stable.
- Explicit absence: model optionality explicitly; avoid "sometimes null" in the core.
- Cross-field coupling: combine coupled fields into one value to prevent illegal combinations.
- Denormalization discipline: if you store derived facts, centralize writes or make them recomputed.

### Concurrency & Protocol Correctness

- Lock/txn invariants: P holds under lock or at commit; define where the linearization point is.
- Monotonic metadata: versions/epochs/counters only increase; reject stale writes.
- Idempotency: retries and duplicates are safe (idempotency keys, dedupe tables, "apply once").
- Explicit state machines: enumerate states + allowed transitions; persist enough metadata to reject out-of-order events.
- Coordination decisions: if P depends on global uniqueness or non-negativity under concurrent debits, choose coordination or redesign (partition/escrow).

### Algorithms & Loop-Heavy Code

- Loop invariants: assert what is preserved each iteration (partitioned regions, sorted prefix, conservation laws).
- Variant/termination: name a decreasing measure; if you cannot, expect non-termination edges.
- Representation invariants: hide internal structure behind an API; add a rep-check for tests/debug.
- Differential testing: compare to a simple, slow reference implementation to catch corner cases.

## Before/After Sketches (Language-Agnostic)

### Boundary Refinement (Data)

```text
Before: functions accept RawInput and validate ad hoc
After:  parseRaw(...) -> ValidatedValue | Error
        core functions accept ValidatedValue only
```

### Idempotency + Versioning (Concurrency/Protocol)

```text
Before: handle(event) mutates state directly (retries duplicate side effects)
After:  if seen(event.id) return
        if event.version <= state.version return (or reject)
        apply(event) at a single atomic boundary (lock/txn/CAS)
```

### Loop Invariant (Algorithm)

```text
Before: comment says "array left side is partitioned"
After:  assert(invariant(state)) inside loop
        test: random arrays, shrink failing cases, compare to reference
```

## Verification

Pick at least one signal and tie it to a specific invariant predicate.

- Property/fuzz: parsers, constructors, normalization.
- Stateful/model-based: sequences over operations; check invariants after each step.
- Concurrency stress: N threads + jitter; assert invariants at quiescent points.
- Protocol simulation: reorder/duplicate/drop + crash/restart; assert safety invariants.
- Model checking (optional): small state + exhaustive exploration for protocols.
- Differential/reference: algorithm output equals reference for randomized inputs.
- Runtime tripwires: assertions/logging/metrics for staged rollout.

## Research Anchors (Mental Models, Not Requirements)

- Hoare/Floyd/Dijkstra: invariants as proof objects; weakest preconditions.
- ADT/rep invariants (Liskov-style): abstraction function + local reasoning.
- Abstract interpretation: over-approx reachable states; inferred invariants.
- Dynamic invariant mining (Daikon-style): candidate generation; falsify with counterexamples.
- Separation logic / framing: invariants tied to ownership; interference-aware reasoning.
- Rely-guarantee & linearizability: concurrency invariants under schedules.
- TLA+/Alloy mindset: protocols as transitions + invariants; counterexample traces.
- Coordination avoidance / CRDT laws: when invariants require coordination vs merge-safe design.

## Output Contract (Required Headings)

Use these exact headings in the final response for this skill:

1. Counterexample
2. Invariants
3. Owner and Scope
4. Enforcement Boundary
5. Seam (Before -> After)
6. Verification
7. Observability (optional)

## Deliverable Checklist

1. Counterexample: minimal breaking trace (include schedule/retry if relevant).
2. Invariants: 1-5 predicates with owner + scope ("holds when").
3. Enforcement Boundary: boundary/type/API/DB/lock/txn/protocol choice + why.
4. Seam (Before -> After): minimal structural change that makes violations hard.
5. Verification: property/stateful/stress/model/differential tied to at least one predicate.
6. Observability (optional): tripwires/quarantine/metrics if rollout must be staged.

## Cross-Coordination

- If broader failures emerge, lean on the Unsoundness checklist.
- If stronger invariants dent ergonomics, reference the Footgun guardrails.

## Measurement (seq)

Track adoption and compliance with `seq`:

```bash
uv run codex/skills/seq/scripts/seq.py skill-trend --skill invariant-ace --bucket week
uv run codex/skills/seq/scripts/seq.py skill-report --skill invariant-ace \
  --sections "Counterexample,Invariants,Owner and Scope,Enforcement Boundary,Seam (Before -> After),Verification,Observability (optional)" \
  --sample-missing 5
```

Overview

This skill turns "should never happen" into "cannot happen" by defining owned, inductive invariants and enforcing them at strong, cheap boundaries. It focuses on picking a single owner, proving inductiveness with a concrete counterexample trace, and encoding enforcement at parse/construct/API/DB/lock/txn boundaries with a verification signal.

How this skill works

Inspect where truth is owned, enumerate all transitions (including retries and concurrent schedules), and produce a minimal counterexample that demonstrates the failure. Select 1–2 checkable predicates, assign a single owner and scope, choose the strongest affordable enforcement layer, and add a verification harness (property/stateful/stress) tied to the predicate.

When to use it

  • When "should never happen" branches appear in logs or error trackers.
  • When validation is scattered or derived facts (cache/index) drift.
  • When idempotency, versioning, retries, duplicates, or out-of-order events are causing bugs.
  • When races, linearization, or protocol correctness are unclear.
  • When loop or algorithm correctness depends on intuition rather than assertions.

Best practices

  • Name the owner and scope before designing invariants; if owner is unclear, pause for discovery.
  • Keep invariants inductive: ensure true initially and preserved by every transition (including retries).
  • Enforce at the strongest cheap boundary (parse/constructor/opaque type or txn/lock), avoid scattered runtime checks.
  • Prefer narrow, opaque types and smart constructors so core code sees only validated state.
  • Add a single verification signal per predicate (property tests, stateful tests, concurrency stress).

Example use cases

  • Normalize and validate incoming JSON at parse time so core functions receive ValidatedValue only.
  • Add idempotency key table + atomic apply in a txn to prevent duplicate side effects from retries.
  • Introduce a monotonic version field and reject stale writes at commit to preserve non-decreasing invariants.
  • Assert loop invariants inside complex algorithms and add differential tests against a reference implementation.
  • Centralize denormalized column writes under a single owner and enforce via DB constraint + transaction.

FAQ

What if I can’t name a single owner?

Pause implementation and run a short discovery: map where mutations occur and choose a choke point. Without an owner, invariants will drift.

When should I prefer transactions/locks over types/constructors?

Prefer types/constructors for single-process, syntactic invariants; prefer txns/locks when invariants require coordination across concurrent writers or atomic multi-row state.