# Write Gameplan

> Create or update a structured JSON gameplan for a codebase change, including patch sequencing, dependency graph, acceptance criteria, and formal per-patch and final-state specs. Use when the user asks for a gameplan, implementation plan, milestone plan, or structured change plan.

- Skill: `flowglad/write-gameplan` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add flowglad/write-gameplan`
- Raw SKILL.md: https://api.skillmd.com/api/skills/flowglad/write-gameplan/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: flowglad (https://skillmd.com/u/flowglad)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/flowglad/write-gameplan

---


# Write Gameplan

Create a structured, machine-readable gameplan for a complex codebase change. The gameplan is a JSON object with typed sections, and each patch includes a formal specification articulating the invariants that must hold after that patch is applied. A final-state spec describes what must be true when the entire gameplan is complete.

**Core principle**: It should be 5-10x easier to review a gameplan than the code it produces.

## Atomicity Constraint (Read This First)

A gameplan is, by definition, a bundle of work with two non-negotiable properties:

1. **Atomic.** Either every patch lands or the gameplan is reverted as a unit. There are no partial outcomes the team is supposed to evaluate and then decide whether to continue.
2. **Autonomously parallelizable.** Once the gameplan is approved, an orchestrator (or a swarm of agents) can execute the patches concurrently, respecting only the `dependencyGraph`. No human is in the loop between patches.

The following structures are therefore **prohibited inside a single gameplan**:

- **Conditional patches.** A patch that "only happens if the previous patch's behavior looks right in staging" is not a patch — it's a separate milestone. Every patch in a gameplan is committed to before execution begins.
- **Inter-patch human operations.** Anything that asks an operator to flip a flag, run a one-time script, query a dashboard, copy a value out of one system into another, or otherwise *act* between patches. The only human action a gameplan permits is the eventual review/merge of the patches themselves.
- **Inter-patch observation windows.** "Let the new metric soak for 24 hours after Patch 3 before applying Patch 4" is a milestone boundary, not a patch boundary. So is "wait for the next billing cycle," "wait for the canary to bake," or "wait until next Monday."
- **Inter-patch decisions based on outcomes.** "If the query plan looks fine after Patch 2, do A; otherwise do B" cannot live inside one gameplan — neither branch can be planned formally, and the dependency graph cannot encode the conditional.

If you find yourself drafting *any* of those — **stop and use [[write-workstream]] instead**. The work you are describing is a multi-milestone workstream, not a gameplan. Each side of the pause/decision/observation becomes its own milestone with:

- a clear Definition of Done,
- explicit instructions for the human operator(s) to follow between milestones (the flag flip, the dashboard check, the manual backfill, the decision criteria),
- and a separate gameplan generated per milestone once the prior milestone is complete.

It is **expected and healthy** that real projects contain soaking, observation, flag flips, and judgement calls. The error is not that those things exist; the error is trying to encode them inside a gameplan. They live at milestone boundaries.

**Practical test, applied while drafting**: read your patch sequence and ask, "could a fully autonomous orchestrator execute every one of these back-to-back, with no human pause and no run-time choice, and would the outcome still be correct?" If the answer is no — for any reason — you are looking at a workstream, not a gameplan. Stop drafting and switch to `/write-workstream`.

## One Repo Per Gameplan

Every gameplan pertains to **exactly one repository on a git forge** (GitHub, GitLab, Gitea, etc.), declared by the required top-level `owner` and `repo` fields. The orchestrator (e.g. onton) clones that repo and runs every patch's agent inside isolated worktrees off of it — so paths in `patches[].files[].path` and `requiredChanges[].file` are resolved **relative to the repo root** and may not escape it (e.g. `../other-repo/x.ts` is disallowed; the worktree has no notion of "next to my checkout").

`owner` and `repo` are forge-agnostic at this layer — the schema only checks that they are non-empty strings. The forge backend the orchestrator is configured for will enforce any format rules it requires (for example, GitHub rejects handles longer than 39 characters); that validation happens at session start, not at gameplan-authoring time.

**If the change spans multiple repositories**, write **multiple gameplans** — one per repo — each with a distinct `projectName` (e.g. `auth-shared` and `auth-app`) and the appropriate `owner`/`repo`. If the gameplans coordinate (one must merge before another can be implemented), express that coupling at the workstream level: name each gameplan as a separate milestone in the workstream and use `priorMilestones` / `unlocks` to capture the ordering. Never bundle cross-repo work into a single gameplan and never use repo-relative `../sibling-repo/...` paths.

## Specification File (Optional)

The user may provide a path to a **specification file** (typically a `.pant` file or any text file) containing behavioural invariants that apply to the overall gameplan. These are pre-written formal or informal constraints the implementation must satisfy.

**If a spec file is provided:**
1. Read the full contents of the file. If the file does not exist or is not readable, **abort with a clear error message** (e.g., "Spec file not found: <path>") — do not silently fall back to generating specs from first principles
2. Parse out the individual invariants, rules, or propositions (look for named chapters, rules, predicates, or bullet points). If parsing yields no recognizable invariants, **abort with a validation error** listing what was found and why it could not be parsed
3. For each patch, analyze which invariants are relevant to the changes that patch introduces — an invariant is relevant if the patch creates, modifies, or depends on any entity the invariant references
4. Include those relevant invariants in the patch's `spec` field by restating any referenced domains, predicates, or rules inside that patch's spec module, then encoding the invariant as a precondition or postcondition the patch must preserve. Per-patch specs are verified standalone — do not merely cite or import external invariants; all referenced declarations must be self-contained within the patch's spec
5. Include **all** invariants from the spec file in the `finalStateSpec`

**If no spec file is provided**, generate specs from first principles as usual.

## Workstream Context (Optional)

A gameplan can be **standalone** or part of a **workstream** (a larger project spanning multiple gameplans as milestones).

**If the user provides a workstream reference** (URL, file path, or name):
1. Retrieve the workstream definition to understand the broader context
2. Identify which milestone this gameplan corresponds to
3. **Ground the landed state of prior milestones.** For each milestone in `priorMilestones` (at minimum the immediately preceding one), determine whether and *how* it actually landed — against the codebase, not the workstream's description of it. Find the merged patches (git log, merged PRs, the milestone's flag names), open the files and surfaces its Definition of Done names, and check whether any `Operator Actions Before Next Milestone` it declared (flag flips, backfills, soak verdicts) were actually performed. Record what you find: landed as planned, landed with drift (renamed symbols, descoped work, a different flag), or not fully landed. This serves two purposes — it is the real foundation for `currentStateAnalysis` (the prior milestone's landed code *is* the current state this gameplan builds on; see [Rule 4](#rule-4--ground-efficacy-not-just-existence) on re-deriving from HEAD, not from a prior description), and it is write-back surface area (see [write-back item 4](#writing-back-to-the-parent-workstream): gameplanning milestone N is usually the first opportunity to record how milestone N−1 landed). If a prior milestone did *not* fully land or a required operator action was skipped, surface that to the programmer before planning on top of it — it may change this gameplan's scope or preconditions.
4. Review the milestone's "Definition of Done" — this informs your acceptance criteria
5. Read the workstream's terminal **Definition of Done (Acceptance Suite)** and pull out every assertion whose `Traces to` names **this** milestone as owner. These are the concrete, observable obligations this gameplan must make true; they should map onto your `acceptanceCriteria` and `finalStateSpec`. You will sharpen them with real artifact names during the [write-back](#writing-back-to-the-parent-workstream).
6. Ensure your gameplan leaves the codebase in a consistent state
7. Read the workstream's `Established Precedents` section (plus any milestone-scoped precedents). For each precedent, identify the specific patches in this gameplan that consume it — touch its API, implement its algorithm, depend on its invariants — and attach it to those patches' `precedents` arrays. Do **not** blanket-copy workstream precedents onto every patch; only the ones that actually use the technique. See [Leveraging Established Precedents](#leveraging-established-precedents) for the per-patch shape.

**If no workstream is provided**, treat this as a standalone gameplan.

## Output Format

**MANDATORY FIRST STEP**: Before writing any JSON, read `references/gameplan-schema.json` (relative to this skill's directory). It is a formal [JSON Schema (draft 2020-12)](https://json-schema.org/draft/2020-12/schema) defining every required field, its type, constraints, and structure. Do NOT generate JSON from memory — the schema is the sole source of truth for the output shape.

The gameplan is a **JSON object** written to `gameplans/<project-name>.json`. Every section is a named attribute.

### Required Top-Level Fields

All of these fields are **required** and must be present in every gameplan:

| Field | Type | Description |
|-------|------|-------------|
| `projectName` | `string` | Kebab-case, used in branch names and PR titles |
| `owner` | `string` | Repository owner on the git forge (user, org, group). Non-empty; forge-specific format rules are enforced by the orchestrator at session start. See [One Repo Per Gameplan](#one-repo-per-gameplan) |
| `repo` | `string` | Repository name on the git forge (paired with `owner`). All file paths in this gameplan are interpreted relative to this repo's root |
| `specFile` | `string \| null` | Path to the specification file the user provided, if any |
| `workstream` | `object \| null` | `{ name, milestone, priorMilestones, unlocks }` — null if standalone |
| `problemStatement` | `string` | 2-4 sentences: what problem, why it matters |
| `solutionSummary` | `string` | 3-5 sentences: high-level approach |
| `currentStateAnalysis` | `string` | Where the codebase is now vs. where it needs to be |
| `operationalConsiderations` | `object` | `{ externalSystemAccess, crossRuntimeContracts, failureBehavior, concurrencyAndIdempotency, rollbackStrategy }` — see [Operational Considerations](#operational-considerations) |
| `mergabilityStrategy` | `object` | `{ featureFlagStrategy, featureFlags, patchOrderingStrategy }` |
| `requiredChanges` | `array` | `[{ file, line, description, signature }]` |
| `functionalChanges` | `array` | `[{ id, description, ownedBy }]` — exhaustive, every entry assigned to exactly one patch. See [Functional Change Ownership](#functional-change-ownership). |
| `contextResources` | `array` | `[{ id, kind, paths, why, consumedBy }]` — authoritative context specific patches must read before editing. See [Context Resources](#context-resources). |
| `acceptanceCriteria` | `string[]` | Each is a "done" condition |
| `reachabilityTraces` | `array` | `[{ observable, tracesTo, ownedBy, path, testPath, runtimeReachabilityNote }]` — one live entry→leaf trace per observable. Empty for pure INFRA/refactor. See [Rule 4](#rule-4--ground-efficacy-not-just-existence). |
| `openQuestions` | `string[]` | Decisions for the team (empty array if none) |
| `explicitOpinions` | `array` | `[{ opinion, rationale }]` |
| `patches` | `array` | See Patch Object in schema |
| `testMap` | `array` | `[{ testName, file, stubPatch, implPatch }]` |
| `dependencyGraph` | `array` | `[{ patch, classification, dependsOn }]` |
| `mergabilityChecklist` | `object` | boolean fields including `gameplanIsAtomicAndAutonomous` (see schema for full list) |
| `mergabilityInsight` | `string` | E.g. "X of Y patches are INFRA/GATED…" |
| `finalStateSpec` | `string` | Formal specification source for the completed gameplan |

### Required Patch Fields

Each patch object must have: `number`, `classification` (INFRA\|GATED\|BEHAVIOR), `complexity` (1\|2\|3), `title`, `files` (array of `{ path, action, description }`), `changes` (string array), `requiredContext` (string array), `testStubsIntroduced` (string array or null), `testStubsImplemented` (string array or null), `spec` (string). Patches may also include an optional `precedents` array citing established libraries, algorithms, or patterns the patch should adopt — see [Leveraging Established Precedents](#leveraging-established-precedents).

The inline `spec` and `finalStateSpec` string fields in the JSON are the **sole source of truth** for formal specifications. Do not maintain separate spec files alongside the gameplan. For verification, extract the strings and validate them with the spec language's toolchain (see [Specification Language](#specification-language) below). Do not persist the extracted files.

## Ground Every Reference in Real Code

The gameplanning agent runs **inside a checkout of the target repo** (see [One Repo Per Gameplan](#one-repo-per-gameplan)), so it can open any repo file and inspect any installed or vendored dependency. The two most common mechanical defects in executed gameplans — **a named path that does not exist** and **a symbol referenced under the wrong name** — are eliminable at authoring time by reading the workspace. Read it before naming anything.

### Rule 1 — Ground every reference you can resolve

Before you write a file path, module, function, type, field, constant, enum value, command name, or signature into *any* field — `requiredChanges[].file` / `.signature`, `patches[].files[].path`, `contextResources[].paths`, `testMap[].file`, a `changes` step, or a `spec` — **resolve it against the actual code**:

- **Repo source.** Open the file; confirm the path exists. For a file the gameplan *creates*, confirm its parent directory and sibling naming convention are real. Confirm every symbol is spelled exactly as it appears in the code — the real export name, the real enum/constant value (not a display string or a paraphrase), and the real test-file convention (`foo.test.ts` vs `foo.unit.test.ts` is a recurring miss).
- **Inspectable dependencies.** If a patch calls into a third-party library that is installed or vendored in the checkout (`node_modules`, vendored modules, type stubs, generated clients), read its actual declarations before specifying the call shape. Do not reconstruct an API from memory when the real types are on disk.
- **Signatures.** When you give a `signature` for a new or modified function, make it consistent with the real types it must accept and return — look those types up; do not invent them.

If you assert a path or symbol you did not verify, you are guessing, and the patch agent inherits the guess with no way to know it was one.

### Rule 2 — Mark, don't invent, what you cannot ground

Some references are genuinely *not* resolvable from the workspace, and those must not be silently invented either:

- A file the gameplan will create does not exist yet — name it and mark it `action: create`. That is grounding the *convention*, not asserting the file is present.
- A fact that lives in an **external system** the agent cannot inspect — whether a live SaaS integration actually exposes a particular API/tool, the shape of a third-party webhook, a value held only in a dashboard or secret store — is not a thing to guess into a `spec`. Route it to `openQuestions` or the relevant `operationalConsiderations` sub-field (e.g. `externalSystemAccess`) so it is resolved deliberately.

The dividing line is precisely **inspectability from the gameplanning state**: ground what the checkout can answer; surface what it cannot. Do not let an un-inspectable external fact masquerade as a grounded one.

### Rule 3 — Give multi-patch surfaces one shared anchor

When more than one patch touches the same file or symbol — patch 1 introduces a type that patches 3 and 5 consume, two patches edit the same registry, a stub patch and its implementation patch share a test file — **name that file/symbol with one concrete, identical reference everywhere it appears.** Choose the exact path and exported identifier once, then reuse it verbatim across every patch's `files`, `changes`, `requiredChanges`, and `contextResources`. Prefer routing the shared surface through a `contextResources` entry whose `consumedBy` lists every patch that depends on it, so they all read the same authoritative description. The failure this prevents: two patch agents, working concurrently in isolated worktrees with no view of each other, each inventing a slightly different name for the same thing — and the pieces failing to fit together at merge.

### Rule 4 — Ground efficacy, not just existence

A reference can resolve (Rule 1) and still be wrong: a symbol that exists but is not on the path that produces the behavior, or a path that exists but is unreachable at runtime. For every reference that produces an **observable** (a rendered element, a reachable route, an API response, a flag taking visible effect):

- Trace outside-in from the entry point that emits the observable (route handler, rendering component, RPC) inward along real call/import/reference edges (see [Grounding Tools](#grounding-tools)) to the leaf that does the work. Name that leaf, not the first name a text search surfaces. If the symbol you intended to edit is not on the traced path, retarget it.
- Re-derive `currentStateAnalysis` from HEAD by walking these paths, not from memory or a prior description. A stale model — catalog "lives under `settings/integrations/*`" after a refactor moved it to `connectors/*` — is what causes mistargeting.
- Record the trace as a `reachabilityTraces` entry: ordered `path` from entry to leaf (each node a `{ file, symbol, status }`, `status` = `existing` or `created`), the `ownedBy` patch, optional `testPath` for the test seam, and `runtimeReachabilityNote` for framework-routed surfaces. The owning patch must edit at least one node on the `path` — the leaf for a new-feature exposure, the entry/caller for a wire-in. The validator checks created-node ordering and that the owning patch's edit lands on the path (see [Verification](#verification)).

Two failure modes, both of which pass Rule 1:

- **Wrong lever** — editing a symbol off the live path (flipping a `DISPLAYABLE_CONNECTORS`-style filter that narrows already-active rows, when the catalog is built by a different `getFirstClassIntegrationLinks()`-style source).
- **Dead surface** — adding a page/route/handler under a tree that is globally redirected or superseded, so the change is never reached.

### Grounding Tools

Existence (Rule 1) is answered by text/structural search. Efficacy (Rule 4) needs symbol resolution — walk real call/import/reference edges, not text matches.

Trace each observable from its entry point to the leaf with the `LSP` tool:

- `prepareCallHierarchy` → `outgoingCalls` to walk from a function to what it calls; `incomingCalls` to confirm the entry point reaches a leaf.
- `goToDefinition` / `goToImplementation` to resolve which concrete implementation a layer or interface dispatches to.
- `findReferences` to confirm a symbol is consumed on the path you expect.

**Tier 1 — always available, language-agnostic:**

- `LSP` tool — the operations above. Resolves symbols rather than matching text; present whenever a language server is configured for the file type.
- [ast-grep](https://ast-grep.github.io/) (`sg`) — structural search for Rule 1 symbol grounding (exact shape, enum members, call sites) across most languages. Prefer over plain grep and over Semgrep (security-scoped, slower as a CLI).
- ripgrep (`rg`) — text presence checks.

**Tier 2 — per-ecosystem (`npx` for JS/TS, no install):**

- [dependency-cruiser](https://github.com/sverweij/dependency-cruiser) (JS/TS) — `reachable` rule answers "is this module reachable from the entry point"; flags orphans.
- [madge](https://github.com/pahen/madge) (JS/TS) — import graph, `orphans()`, circular deps.
- Call-graph generators where LSP call-hierarchy is unavailable: `golang.org/x/tools/cmd/callgraph` (Go), `cargo-call-stack` / rust-analyzer (Rust), `code2flow` / `pyan` (Python), `cflow` (C).

Reach for the Tier-2 tool matching the repo's ecosystem first (try it — for JS/TS `npx` runs it without a project install); if it is unavailable, fall back to Tier-1 (`LSP` + ast-grep/`rg`), which is always present and covers the grounding needs in any language.

**Framework reachability:** static graphs see imports and calls, not redirects, rewrites, middleware, or filesystem routing. A route reached by convention will not show as an orphan even when a redirect makes it a runtime dead end. For routable/framework-dispatched surfaces: grep the redirect/rewrite/middleware config that could shadow the path, then boot the app and drive the surface (project verification skill).

**On presence:** the gameplanning agent runs inside a checkout of the target repo and cannot assume Tier-2 tools are installed. The `LSP` tool and `rg` are always available; reach for `npx <tool>` for JS/TS Tier-2 tools; otherwise fall back to ast-grep/ripgrep plus LSP traversal, which together cover the grounding needs in any language.

## Context Resources

`contextResources` names authoritative context that an implementing patch agent must read before editing. This is for existing code, contracts, docs, tests, or predecessor surfaces that constrain implementation. It is not a dumping ground for general background.

Allowed `kind` values:

- `existing-implementation` — current code path or helper whose behavior should be reused or preserved.
- `contract` — API, protocol, schema, interface, spec clause, or cross-runtime contract the patch must honor.
- `predecessor` — old surface being retired, replaced, or migrated away from.
- `reference-doc` — repo documentation or canonical maintainer docs describing the intended behavior.
- `test-or-static-check` — tests, fixtures, evals, linters, or static checks that define expected behavior.
- `external-reference` — external URL, standard, or vendor document that is authoritative for this patch.

Each resource has `id`, `kind`, `paths`, `why`, and `consumedBy`. Each patch has `requiredContext`, an array of resource IDs. The routing must match in both directions: if `contextResources[].consumedBy` includes patch `3`, then patch `3` must include that resource ID in `requiredContext`, and vice versa. Attach resources only to patches that actually consume them.

### When context is required

Require context resources for patches that:

- write docs, evals, test harnesses, adapters, replacement implementations, or policy logic;
- retire, replace, or preserve behavior from an old surface;
- describe an implementation or contract in documentation;
- implement one side of a cross-runtime/API/schema contract;
- depend on an existing test/static check as the source of truth.

Docs, evals, and reference patches must name the implementation or contract they describe. Adapter/replacement patches must name the predecessor and the target contract. If a context resource defines a contract that a patch preserves, the relevant per-patch `spec` should cite that contract in its invariants.

## Formal Specifications

Each gameplan includes two levels of formal specification. The spec language is pluggable (see [Specification Language](#specification-language)), but the structural requirements are fixed:

### Final-State Spec (`finalStateSpec`)

A spec module describing the invariants that must hold when ALL patches have been applied. This is the "acceptance criteria" expressed formally. It should capture:

- Domain entities introduced or modified by the gameplan
- Rules (properties/functions) that the gameplan establishes or changes
- Invariants that the completed system must satisfy
- Initial-state propositions where relevant

### Per-Patch Specs (`spec` on each patch)

Each patch has a spec module describing the invariants that must hold after THAT patch is applied. These are incremental — they describe the delta, not the full system. They should capture:

- New types, rules, or predicates introduced by this patch
- Invariants that become true after this patch (and must remain true for all subsequent patches)
- Preconditions that the patch assumes (from prior patches)

**Spec-writing guidance**: Use progressive disclosure (top-down structure). Never guess domain details — if something is unclear, note it in `openQuestions`.

Every functional change should map to at least one per-patch or final-state spec clause when the chosen spec language can express it. If a context resource defines a contract the patch preserves, name that contract in the spec's invariants so implementers and reviewers can trace the resource to the code obligation.

#### Complete the contract in the spec

The per-patch `spec` is the **sole source of truth for the behavioral contract** — sharpen it in the spec itself; do not restate the contract in `changes` or elsewhere. After wrong references (see [Ground Every Reference in Real Code](#ground-every-reference-in-real-code)), the largest class of executed-gameplan defects is a contract that named the right things but left a case unspecified: an unhandled error, the inverse of a specified operation, an undefined boundary. These are observable behavior at the interface — what preconditions, postconditions, and invariants exist to pin down — not implementation detail. Specify that behavior; leave the mechanism that satisfies it to the implementer.

A spec is complete when, for every rule it introduces or changes, the contract is **total over that rule's input domain**. In Pantagruel:

- **Fallible results are sum types with every arm covered.** If a grounded function can fail, model the result as a sum (`Outcome = Ok + RateLimited + Invalid.`) and constrain the rule into the whole sum — never spec only the success arm. The failure arms must match the real error union you grounded in the code, not a guessed subset.
- **Case analysis is exhaustive.** Use `cond … , true => …` so the final arm closes coverage; `pant --check` flags a `cond` whose arms miss inputs. Every variant of a grounded enum/sum is handled or explicitly excluded.
- **Partiality is a written precondition, not an omission.** A rule with no guard asserts totality (`owner d: Document => User.`); if a rule is partial, the guard *is* the precondition (`f x: T, valid? x => …`) — write it so "what must hold of the input" is on the page. A missing guard is a claim of totality; mean it.
- **Inverse and sibling operations are specified together.** Spec `create` ⇒ say what `update`/`delete` do (or that they are structurally rejected); spec `add` ⇒ `remove`. Declaring one member of an operation family and leaving the rest to the implementer is the single most common omission.
- **Invariants quantify over the whole domain.** A property that must hold at several sites (every place a secret is logged, every consumer of a changed row) is `all x: Site | …`, not an assertion about one representative — the universal *is* the claim that no site is unhandled.

Ground each of these against the code you already opened — the enum's real members, the function's real error union, the actual callers — so the completeness check has an **external oracle** (the grounded types plus `pant --check`'s exhaustiveness and contradiction analysis), not just re-reading. A case you cannot resolve from the workspace goes to `openQuestions` (Rule 2 above); never close it by guessing it into the contract.

## Patch Classification

Each patch includes a `classification` field:

- `INFRA` — No observable behavior change. Types, schemas, helpers, test stubs, feature flag additions. Safe to merge anytime.
- `GATED` — New behavior behind a feature flag. Observable behavior unchanged until flag is enabled.
- `BEHAVIOR` — Changes observable behavior. Requires careful review. Should be as small as possible.

**Goal**: Maximize `INFRA` and `GATED` patches. Minimize `BEHAVIOR` patches.

## Functional Change Ownership

When two patches share responsibility for a behavioral change, each implementer sees it mentioned and assumes the other owns it, so the change falls through the cracks — a behavior described only at the gameplan level, with no single owning patch.

The `functionalChanges` array prevents this. It is an **exhaustive enumeration** of every functional or behavioural delta the gameplan introduces, with each entry assigned to exactly one owning patch.

### What goes here

- Every observable behavior the system gains, loses, or changes as a result of this gameplan.
- Every user-visible or API-visible change (new endpoint shape, new return value, new error path, removed deprecation).
- Every change in protocol, contract, or invariant that downstream code can detect.

What does **not** go here:

- Pure refactors that have no observable effect (those still belong in `patches[].changes` as implementation steps).
- File or signature edits (those belong in `requiredChanges`).
- Internal helper introductions that are not callable from outside the module being changed.

### The mapping

Each `functionalChange` has `id` (`FC-1`, `FC-2`, …), a single-outcome `description`, and an `ownedBy` patch id. The mapping is:

- **Total**: every functional change has an owner. No orphans.
- **Single-valued**: exactly one patch owns each change. No shared ownership; co-owning a change is the failure mode this section is designed to prevent.
- **Not strictly surjective**: an INFRA-only patch that introduces types or test stubs need not own any functional change. Most observable changes land on GATED or BEHAVIOR patches.

If the same behavior is co-implemented by two patches, the change description is too coarse — split it into two changes (one per patch), each describing the slice that patch delivers.

### How it surfaces to the patch agent

Downstream consumers (notably onton's patch prompt renderer) read `functionalChanges` and inject the subset `ownedBy` each patch into that patch's agent prompt as an explicit "Functional Changes You Own" section. The implementing agent therefore sees the precise list of user-visible behaviors it is responsible for delivering, separate from its `changes` implementation steps. This is what closes the loophole — there is no longer prose-only behavior that no patch acknowledges.

### Authoring guidance

- Write each entry as the **outcome**, not the mechanism. "Merged patches are skipped instead of queued" is correct; "Add a merged-check branch to disposition" is an implementation step and belongs in `patches[].changes`.
- Cross-check against `problemStatement`, `solutionSummary`, and `acceptanceCriteria`: every behavioral promise made there must correspond to at least one `functionalChange` entry. If you cannot point at the owning patch for a sentence in the problem statement, the gameplan has a gap.
- Cross-check against `finalStateSpec`: every behavioral invariant in the spec should map to a functional change that introduces it (the spec says *what is true at the end*; the functional change says *which patch made it true*).

## Patch Boundaries (Frames and No-Ops)

[Functional Change Ownership](#functional-change-ownership) makes the *behavior* partition correct — every observable change has exactly one owning patch (total and disjoint). The same discipline must hold for the *file* partition, and each patch must make a real change. Two recurring defects come from skipping this: a patch whose change spills into files it never listed, and a patch whose change was already true (a no-op). Both are detectable at authoring time against the grounded code.

**A patch's `files` array is its frame condition.** In contract terms a routine has not only pre/postconditions but a *frame* — the exclusive set of locations it may write (this is JML's `assignable`/`modifies` clause; separation logic calls the touched region the *footprint*). The `files` list is exactly that: the patch's complete and exclusive write-set. Validate it as one:

- **Complete** — walking the patch's `changes` and `spec` against the grounded code, every file that must be edited to deliver the change is in `files`. If delivering the functional change forces an edit to a consumer, a registry, a barrel export, or a type the patch didn't list, the frame is incomplete — add the file or rescope the patch. The "consumers" axis of [Complete the contract in the spec](#complete-the-contract-in-the-spec) feeds this: every consumer you must update is part of the frame.
- **Exclusive / disjoint** — no two patches that can run concurrently (no dependency edge between them) may write the same file or symbol. Overlapping frames are the merge collision the isolated-worktree execution model cannot reconcile. If two patches must touch one surface, either serialize them with a `dependencyGraph` edge or route the shared surface through one owning patch (cf. [Rule 3 — shared anchor](#rule-3--give-multi-patch-surfaces-one-shared-anchor)).

**Each patch must be non-vacuous.** A patch whose postcondition already holds in the grounded pre-state is a no-op — satisfied *vacuously*, the way "every request is followed by a grant" holds in a system that makes no requests. Mechanical test: remove the patch and check whether its postcondition still holds against the grounded code; if it does, the patch is empty. If the field already exists, the route is already registered, or the type already has the variant, drop the patch or rescope it to the work actually missing.

**Each patch must produce its observable (the dual of non-vacuity).** The inverse of a no-op: a patch whose edit is real but lands off the live path, so its observable never changes. Test: trace the observable per [Rule 4](#rule-4--ground-efficacy-not-just-existence) and confirm the edited symbol is on the path. The edit applies, references resolve, the behavior still does not appear. If the edited leaf is off the path, retarget the patch.

**Decompose by what changes together, not by execution flow.** Parnas's module criterion applies to patches: partitioning by flow ("first do A, then B, then C") tends to produce patches with overlapping frames and vague ownership, because one surface gets touched at several flow steps. Partitioning by *what changes together* — a type with its consumers, a registry with its entries — yields disjoint frames and clean single ownership, which is what makes concurrent worktree execution safe.

## Operational Considerations

Beyond *what changes*, a gameplan must engage with *how the system behaves operationally* under the change. These concerns share a failure shape: vague descriptions get scattered across patches, every patch author assumes some other patch owns the decision, and the question surfaces in production. The `operationalConsiderations` schema field is required and contains five sub-fields — each is a required string. The schema enforces presence; the rubric below makes each response substantive. A sub-field may state "not applicable" with a brief justification when the gameplan genuinely does not touch that surface, but it must be present and engage with this gameplan's actual code.

### `externalSystemAccess`

When a gameplan newly depends on an external system (object storage, database the runtime doesn't currently reach, third-party API, queue, secret store, internal service), the runtime executing the new code must be able to reach it in production. Audit the existing access posture of that runtime (direct SDK with ambient IAM, presigned URL handed in by another service, broker proxy, VPC endpoint, etc.), pick an access mode, and assign one patch to own the wiring — IAM grant, new endpoint, presigned-URL minting path, network policy, secret rotation. Be especially suspicious of newly-invented `*Client` / `*Transport` interfaces — they are where an undecided access-mode question hides. Sentinel classes whose existence encodes a *lack* of access (e.g. a `PresignedOnly*` adapter) are signals that the runtime cannot hold the underlying credentials. The chosen capability should appear as a `functionalChange` owned by that patch, not just as an interface parameter.

### `crossRuntimeContracts`

Any data format crossing a runtime boundary (queue payloads, DB rows read or written by separate services, S3 object schemas, RPC return types, event payloads) is a contract. When the gameplan changes one side, identify the producer and consumer runtimes, name which side this gameplan touches, and explain how the other side stays in sync — patched in the same gameplan, or made forward/backward-compatible with an explicit migration plan. Failure mode: producer ships, consumer breaks silently, and the gap is not visible from any single patch's `files` array.

### `failureBehavior`

For each new dependency or runtime path, describe behavior under realistic failure: dependency slow/throttling/5xx/garbage, retry storms, partial writes, timeouts, oversized inputs, expired credentials. State which failures are handled deliberately (documented recovery path or error surface) and which are intentionally left to the caller or operator. Failure mode: only the happy path is tested against a fake, and production discovers the rest.

### `concurrencyAndIdempotency`

Any new code path entered concurrently or under retry (queue workers, scheduled jobs, race-able write paths, parallel access to the same DB row or S3 key) has a concurrency contract: locking, idempotency keys, ordering guarantees, deduplication. State it explicitly. Failure mode: a quiet double-write or deadlock that only manifests under production load.

### `rollbackStrategy`

For stateful changes (DB schema, data writes, materialized artifacts, mutations to external systems), describe the rollback story: if a patch must be reverted after data is written, what's the recovery path? Expand/contract migrations, backfill plans, opt-in flags that wind down gracefully, compensating writes. Failure mode: assuming forward-only and stranding data in a half-migrated state.

## Patch Complexity

Each patch includes a `complexity` field — an integer in `1`/`2`/`3` estimating how hard the patch is to implement correctly. This is used by orchestrators (e.g. onton's `--model auto`) to route harder patches to stronger models.

- `1` — **Mechanical / shallow / well-precedented.** A rename, a single-call-site signature change, adding a field that already has an obvious default, copy-pasting a pattern that already exists nearby. Could be done by reading only the patch description and a few surrounding lines.
- `2` — **Moderate.** Requires reading the surrounding code to understand context, designing a small abstraction, writing non-trivial tests, or coordinating two or three files. The shape of the solution is clear once you've read the relevant code, but a careless implementation would miss something.
- `3` — **Deep.** Requires reasoning about subtle invariants, concurrency, distributed state, novel algorithms, performance trade-offs, security boundaries, or unfamiliar third-party APIs whose contracts must be researched. Implementations that "look right" can still be wrong.

### Be conservative

**When in doubt, choose the higher value.** Under-estimating complexity costs more than over-estimating it: a too-weak model on a complex patch silently produces broken code, while a too-strong model on a simple pat

…(truncated)
