---
name: "task-cycle-planning"
type: task
description: The cycle-boundary ceremony, run as a scheduled task — retro the closing cycle, roll over incomplete work, and propose the next cycle's commit and its one goal, with Aled the gate on cycle membership and goal. Owned by Atlas, propose-only, Linear connector, delivery teams only (Apps, A1, MBN, Mr Pritchard). This is the self-contained process — there is no separate cycle-review skill, because the ceremony is a job you run, not a craft invoked ad hoc; its Layer 4 path is workflow-cycle-planning. Run it at a cycle boundary, or on demand to review a closing cycle and plan the next. Sized against observed throughput, not a full board; skill-plan (the weekly focus snapshot) is a sibling and is never edited here.
license: CC BY-NC-SA 4.0
---

You are running the **cycle-boundary ceremony** for Aled's delivery teams, on Atlas's behalf. At the boundary between one cycle and the next you retro the closing cycle, roll over what did not finish, and propose the commit for the cycle that is opening. This is the process itself — self-contained, not a loader over a skill. Its standing Layer 4 path is `workflow-cycle-planning`; the owning role is `agent-atlas`. Follow `convention-linear` and `core-operating-model` throughout.

**Write scope — what you may and may not change.** You **read** cycles, milestones, the board and the `[goal-review]` output; the only thing you **write** is the commit proposal, posted as a comment for Aled. You **never write cycle membership** — no add, remove, or move — in the scheduled, unattended ceremony. The membership write happens only after Aled's explicit approval, in the session where he gives it and on his instruction, carrying the approval marker (Step 4, *The membership write — in session, on the Owner's instruction*; APP-1580, 2026-09-21). You never mark Done, never cancel or defer a ticket autonomously (those are major moves, surfaced for Aled), never write code, and never touch the Pipeline team. Connector: Linear only. Teams: **every delivery team that runs cycles, resolved live from** `convention-linear`**'s team table — Apps, A1, MBN and Mr Pritchard** — and no others (never Pipeline). **Where this list and that table disagree, the table wins**, and a team that has an active cycle but appears in neither is reported rather than skipped. A hardcoded three was wrong in the direction that hides a whole team: Mr Pritchard runs cycles on the same Sunday boundary as the other three (verified 2026-09-12: cycle 10, 2026-09-06T23:00Z → 2026-09-13T23:00Z), and every downstream approval test is specified against `<team>.cycle-goal.YYYY-Www` for *each* delivery team — `skill-attention-sweep`'s freshness read names Apps, MBN, A1 **and Mr Pritchard** explicitly. So a ceremony scoped to three teams leaves Aide permanently unable to satisfy the test for the fourth: no goal document is ever created for it, the freshness read fails every boundary, and the failure reads downstream as *nothing to do* rather than as *never committed*. This is the APP-521 / APP-550 shape — a scope list that predates or under-reads the team set misses a whole team's work silently — applied to the ceremony that produces the artefact everything else tests. (APP-1383.)

## Step 0 — Resolve the boundary, per team

For each delivery team, **confirm it has an active cycle, then resolve that cycle by UUID** — call `list_cycles(teamId, type:"current")` to get the cycle object, its real UUID and its dates. **Never pass the literal `"current"`** to `list_issues`' `cycle` param: it silently returns zero for every team, with no error, and a run that trusted it would report a false negative on a genuinely committed cycle (`APP-580`; `convention-linear`, *Reading Linear safely*). Where a team genuinely has no active cycle (mid-boundary, newly created, cycles disabled), say so once in the summary and skip its cycle-dependent steps rather than no-opping silently.

Identify which teams are **at a boundary** — a team whose **current cycle's `endsAt` falls within about 72 hours of the run** (Owner decision, Aled 2026-09-21, APP-1577; this replaces the 2026-09-03 *previous cycle ended within the run window* test). The scheduled ceremony fires **before** the boundary, on Friday 16:00 Europe/London, so the Owner can approve the commit before the week it governs starts. Linear's Sunday 23:00Z boundary is unchanged: moving it was ruled out, because team settings allow no end time and a hand-edited end date leaves a gap with no `current` cycle. On the Friday run, resolve the **closing** cycle with `list_cycles(teamId, type:"current")` (still live) and the **opening** one with `type:"next"`, both by UUID. **A Friday run reads a cycle still in flight, and that is deliberate.** The retro is partial (Step 1), and the carry-over has not happened yet (Step 2). An on-demand run **after** the boundary keeps the closed-data resolution: closing = `type:"previous"`, opening = `type:"current"`. Do not guess "at a boundary" from the weekday. Resolve it from `endsAt`. The 2026-08-14 run fired 2.5 days early and read a cycle mid-flight by accident, and the 2026-08-17 run read the just-closed cycle by judgement. Both failures came from the same missing definition (APP-1007 / APP-892). The Friday run reads mid-flight on purpose, against a stated horizon. Run Steps 1–4 for each team at a boundary. On a scheduled fire with **no team at a boundary**, report "no boundary at hand" and stop; do not retro a team whose cycle is not ending. An on-demand invocation names its target team(s) and runs regardless of the day.

## Step 1 — Retro the closing cycle

**On the Friday run the retro is partial by definition (APP-1577, 2026-09-21).** Label it *as of Fri 16:00*. Throughput is `completedAt` from the cycle's start to the run time, not to `endsAt`. Completions that land between the run and Sunday's boundary still count in the closing cycle's burn-up, but not in this retro. Say so, and report them in the next Friday's retro under a *late completions of cycle N* line. The carry-over has not happened yet, so the incomplete committed set is the committed tickets still open at run time, read directly from the closing cycle.

For each team with a cycle closing, read the cycle scope and reconcile it against what happened:

- **What was committed** — the cycle's scope: its membership and count at the boundary. Read it two ways and reconcile them. The **committed list** is the one in the closing cycle's goal document (`<team>.cycle-goal.YYYY-Www`, `convention-linear`) — that list is the definition of *planned* (APP-1095). The **membership** is derived per `convention-linear` *Reading Linear safely*: a team-scoped read filtered on `cycleId` client-side, never the `cycle` param, cross-checked against the cycle's `issueCountHistory`. On an on-demand run after the boundary, Linear's built-in carry-over has already moved the incomplete items into the opening cycle, so the closing cycle reads back only its completed and cancelled members: recover the incomplete committed set as *the committed list minus the completions*, and the incomplete unplanned set as the `cycle:unplanned` tickets now sitting in the opening cycle (APP-1007). **Where the enumerated set disagrees with the cycle's own count, proceed with a caveat — never hard-fail.** Report the mismatch on the proposal with the missing IDs where you can name them, and never infer that a ticket is out of the cycle from its absence in a cycle-scoped read (Owner decision, Aled 2026-09-03, APP-890).

- **Planned vs unplanned (APP-1095).** Of the closing cycle's membership, report the count on the committed list and the count carrying `cycle:unplanned`, and the unplanned share. The label is derived daily by `skill-coordinate` from the committed list; nobody stamps at the door, the Owner included. A rising share across boundaries is a planning signal, not a delivery one — say so where it is rising.
- **What completed — throughput, read from `completedAt` inside the cycle window (APP-891).** Throughput is the count of completions **whose `completedAt` timestamp falls inside the cycle's start–end window**, computed from the issues' own timestamps — not a ratio lifted from Linear's burn-up. `completedIssueCountHistory` (against `issueCountHistory`) is the **burn-up**, which `APP-585` protects by never stripping Done tickets; it is **not** the throughput read. The two are different numbers with different jobs — the burn-up is the cumulative state of scope, throughput is work delivered within the window — and conflating them is the whole defect. The burn-up can open a cycle already high (backfilled with work Done before the window began) and can absorb a bulk close as if it were a week's delivery; neither is throughput. Report the completed count and its ratio, but derive both from `completedAt`-in-window, and treat a cycle that opened with a large already-Done count, or that took a bulk close, as *not read* for throughput until Step 3 separates the batched from the unbatched figure. (Evidence: Apps read 135/167 = 81% with `completedIssueCountHistory` opening at 64 — 62% on day one — and ~64 of ~139 completions landing in an 11-minute drain batch on 10 Aug; MBN read 7/9 = 78% with all nine closed the same day, a dormant team reading as productive. Both are burn-up artefacts, not weekly delivery.)
- **What did not, and why** — the still-open tickets in scope, with a short honest reason each (blocked, deprioritised, oversized, never started, carried in already stale).
- **Aged WIP — how long the open work has sat, not only how much of it there is (APP-1171).** For each incomplete committed ticket, read its **age since its last state change** and report the per-lane distribution beside the count: the oldest item, the median, and how many have sat a full cycle or more without moving. Depth without age cannot separate a busy cycle from a stuck one, and nothing in this ceremony measured whether the queue was moving — five consecutive zero-output `routine-delivery-loop` runs, 2026-09-03 to 2026-09-08, read as five quiet days rather than as one fully-gated queue (APP-1149). **The unit is the cycle, and it is derived rather than picked:** the boundary is the only point at which this retro observes the board, so one cycle is the smallest age it can honestly see, and on a weekly cycle "unmoved a full cycle or more" is therefore the first bucket that means anything. **This is the measure and nothing else. No threshold is stated here and nothing is gated on it** — the aged-WIP gate on the commit proposal (`APP-1171`, rule 2) needs a distribution to derive a threshold from, and this report is what produces that distribution; a threshold asserted before it exists would be a guess with teeth over the Owner's commit. Deliberately a **different unit** from `skill-plan`'s weekly-snapshot Blocked check, which flags a Blocked ticket untouched for more than two days: that is a daily-cadence signal on a different artefact, so reconcile against it and never restate it here. And once this line exists, no lane's depth is reported in this ceremony without its age beside it.
- **Milestone movement** — which milestones advanced, which slipped, which are now at risk. Movement and "at risk" are only computable where a milestone carries a **target date**; where a milestone in scope is undated, name that as a gap — you cannot assess its drift or roadmap position, and "what needs doing by when" is not trackable for it — rather than reading it as on-track (`APP-625`). Requiring dated milestones at project setup is the systemic fix and sits upstream (project setup / `playbook-client-onboard` / `convention-ticket`), not in this ceremony; here you only name the gap where it bites.
- **The `[goal-review]` output where one exists** — read the most recent `skill-goal-review` summary for the initiatives this cycle served, so the retro reads the cycle against the strategy, not activity alone. Where no goal-review output exists yet, note its absence and retro against milestone and initiative state instead (see *Composition gate*).

- **The cycle goal — was it met? (APP-815)** Read the goal set for the closing cycle — the per-cycle goal document Atlas wrote (filed per `convention-linear`) — and retro against whether that one stated objective was achieved, not only the completion ratio. A cycle can hit its completion ratio and miss its goal, or miss the ratio and still land the goal; the goal is the truer read of whether the week did what it was *for*. Where no goal was set for the closing cycle (the shape predates Atlas owning it), note its absence and retro against milestones and completion alone.

 **And retro against the closing cycle's declared tiers, not the goal alone (Owner ruling, Aled, 2026-09-28, APP-1459).** Where the cycle-goal document declared tiers, read them back: the goal tier is the pass/fail above, the second objective is reported on its own line, and trucking-tier work is reported as throughput rather than as commitment — a trucking item that did not land is not a miss and is not written up as one. Where the closing cycle predates the tiers, say so and retro on the goal alone.

Be honest about what did not land. A clear "this did not move, and here is why" is more useful than a generous reading, and it is the input the rollover and the next commit both depend on.

## Step 2 — Roll over

Take every **incomplete committed** ticket from Step 1 and classify each, with a one-line reason:

- **Carry** — still important and still live; moves into the next cycle's candidate set.
- **Drop** — no longer worth doing; propose cancel (a major move — surfaced, never applied here).
- **Return to Backlog** — still valid but not for the next cycle; leaves the cycle and loses its commitment, staying in the backlog.

The rollover is a **draft for Aled**, not an applied change. It proposes the disposition of each incomplete ticket; it does not write it. **Two things are true at the moment this runs (Owner decision, Aled 2026-09-03, APP-1007).** First, cycle automation is off on Apps, A1 and MBN (*Active issues & due date* off, *Started issues* off, *Completed issues* on), so nothing enters a cycle by automation — entry is the commit in Step 3, and this step is real gating for it. Second, Linear's built-in carry-over of incomplete issues still runs at the boundary, so every ticket still open on Sunday night lands in the opening cycle, whatever the proposal says. **On the Friday run that has not happened yet.** State *Drop* and *Return to Backlog* as **"do not carry into cycle N+1"**. The approved removal can only be applied once the carry-over has placed the ticket in cycle N+1, which is after the boundary. Until then, the approving session reports it as outstanding and never as applied, and it is applied after the boundary on the Owner's instruction, under the same clause as the rest of the membership write (Step 4; APP-1577, APP-1580, 2026-09-21). On an on-demand run after the boundary, the carry-over has already happened, and Step 2 is a **reconciliation pass over it**. *Carry* means it stays. *Drop* and *Return to Backlog* are proposed **removals from the opening cycle**, stated "remove from cycle N+1". Either way, word it so the write he is being asked to make is the one that is actually outstanding.

**Never strip a Done ticket from a closing cycle.** Linear's cycle-completion metric *is* the count of completed issues in scope (`completedIssueCountHistory` against `issueCountHistory`), so removing Done tickets drives the completion rate toward zero and destroys the burn-up — the write done for "cycle hygiene" would be the thing that wrecks the history the retro depends on (`APP-585`). Only **Canceled** tickets are ever stripped from a cycle. (This protects the burn-up; it is not the throughput read — Step 1 reads throughput from `completedAt`-in-window, `APP-891`.)

## Step 3 — Propose the next commit and the cycle goal

**Propose the cycle goal (APP-815).** Alongside the candidate set, propose **one goal for the opening cycle** — a single stated objective for Aled's whole delivery capacity (e.g. "assoc.one foundation shipped and reviewable"), not a ticket list. **One goal, not one per team** — Aled is a single delivery capacity across Apps / A1 / MBN / Mr Pritchard, and several concurrent goals is no goal; the goal names the venture it serves. Name any **stretch goals** separately: a stretch goal is stated but **never occupies a WIP slot** until the required set is done. Express the capacity split as the **tiered Owner-lane ceiling** below, never as a reserved-slot floor and never as a percentage: a reserved floor states what goal work may not lose, which read literally made every non-goal ticket formally ineligible to be pulled — the starvation APP-1459 recorded, with one team's own document instructing it to stay out of the way in place of a goal — whereas the ceiling states how much of the Owner's attention each tier may claim and leaves agent throughput unconstrained (Owner ruling, Aled, 2026-09-28, APP-1459). This supersedes the reserved-WIP-slot form for this ceremony; `standard-prioritisation` still carries that form. The goal is **propose-only**, approved at the same gate as membership; once approved it is written **by the Owner**, or by the agent in his session on his explicit instruction (APP-1580), into the per-cycle goal document — the document Step 4 creates at proposal time and files per `convention-linear` — which the standup, Aide, and the next boundary's retro all read. Approval writes the goal in; it does not conjure the document.

**Tier the board per cycle — goal, second objective, trucking (Owner ruling, Aled, 2026-09-28, APP-1459).** One goal across the whole delivery capacity stands and is reinforced; what it was missing is the rest of the board's relationship to it. Alongside the goal, declare **three tiers in the cycle-goal document**, as a per-cycle attribute and never a property of the project: **goal** — one, taking almost all of the Owner's attention; **second objective** — one named slice; **trucking** — every other live project, which agents work at full throughput, whose decisions the **operators** own, and from which nothing reaches the Owner except a stated exception list: a client waiting, money, something irreversible or public, or a hard external date. **The tier sits above the project priority field**: where a project's priority and its tier disagree, the tier governs for that cycle. **Slippage is absorbed before it reaches the Owner** — a goal-tier item that slips holds its place and displaces the lowest-tier item still in the week; the displaced item routes to its owning operator to absorb, hold or re-sequence, and reaches the Owner only where it fails that operator's own grant test (`skill-operating-loop` step 5). A trucking-tier item that slips drops out of the week rather than pushing goal work along. The complaint this answers was that one goal starves the other teams; the ruling is that they are **subordinated and absorbed by their operators**, not starved — they keep delivering at full agent throughput, and what changes is that their decisions stop arriving at the Owner's desk with the goal's weight. **Per-team goals are explicitly not the remedy**: four goals is no goal, and each team would then generate its own independent claim on him. (Measured at the 2026-09-25 boundary: the W40 proposal put **28 items in the Owner's lane** across four teams, of which **seven** served the stated cycle goal. APP-815 stands and is reinforced; APP-1459's own title remedy — goal scoped per team, WIP reserved per team — is superseded by this ruling and is not implemented.)


**A standing target declared on a team's outcome-initiative is read, not derived — and it is not a second cycle goal (Owner, 2026-10-05).** The Owner's words were: *"Mr P should have one thing to post and a stretch goal of two, with 3 comments on other people's posts and one reshare — this should be standing weekly goal."* The ruling above — *per-team goals are explicitly not the remedy* — governs the **derived** cycle goal, the one claim on the Owner's attention, and it stands unchanged. A standing target is a different object: its home is the venture's outcome-initiative, declared once as an objective and its KPIs (`skill-strategy-cascade` / `skill-strategy-charter`, Atlas's layer; MRP-19 is the open ticket carrying Mr Pritchard's), and this ceremony **reads** it rather than re-deriving it. So at each boundary, for each team at a boundary: read its outcome-initiative's objectives and KPIs for a standing weekly target, and where one exists, state it in the cycle-goal document as a **standing** line of its own — the target, and the initiative it is read from — placed above the tiers and separate from the derived goal. **A standing target does not take the goal tier and does not compete for it.** It sits with its team's trucking tier as a floor that tier carries, which is the one case in which trucking work carries a commitment: reported against its target, not merely counted as throughput. A trucking team that nonetheless owes a weekly number is exactly what the Owner has asked for, and the tier model absorbs it without amendment once the target's home is the initiative rather than the cycle. **And a standing target's tickets are planned by construction.** They belong on the committed list whether or not the Owner re-lists them each week, and the retro reads them as planned — they are the most planned work on the board, so `cycle:unplanned` on one of them is a derivation artefact and not a signal (Step 1; APP-1095; `skill-coordinate` derives the label by diffing membership against the committed list and cannot know the target). Where such a ticket is found carrying `cycle:unplanned`, name the count and exclude it from the unplanned share rather than letting it inflate the figure. **Written only into a cycle-goal document, a standing target dies at the boundary** — the APP-1546 shape, in which the thing meant to recur stops and no check fails. The initiative's KPI line is the source of truth; a native Linear recurring issue is its mechanical expression, not the target itself, because editing a template does not change recurrences already created from it (MRP-70 is the Owner-lane, UI-only setup ticket — the connector exposes no recurrence parameter on an issue write). (APP-2038. APP-815 and APP-1459 stand.)

Produce a prioritised candidate set for the opening cycle:

- **Never a `PUL-5E` note — excluded by construction, at the write.** The candidate set, and the membership write Aled later approves, exclude every ticket carrying the bare `PUL-5E` executor value; filter on that bare value, the same one `skill-plan` uses. A `PUL-5E` note is propose-only canon intake and never joins a cycle (`convention-linear`, *Never in a delivery cycle*, APP-870) — `skill-plan` already subtracts them on the read side, but nothing filtered them at the commit, so Apps cycle 6 carried ~30 of them and read scope 73 against ~43 real delivery scope, corrupting every consumer that did not know to subtract (APP-1038). Enforce once at the write rather than compensate at every read. **The write-side exclusion holds and is not enough, because these notes do not enter by the write.** With *Completed issues* automation on (`convention-linear`, *Never in a delivery cycle*; APP-1007), a ticket joins the cycle by being completed, so a Pulse drain that closes a batch of notes adds that batch to the live cycle with no commit, no membership write and nothing for a commit-time filter to intercept. The exclusion above is specified on a path these tickets never take. So **Step 1's boundary read subtracts them too**: filter the closing cycle's membership on the bare `PUL-5E` executor value before reporting scope, the completed count, the planned-versus-unplanned share or the `completedIssueCountHistory` cross-check, and report the subtracted figure as the cycle's, naming the excluded count alongside it. This is not a reversal of APP-1038 — that ruling rejected compensating at every read *instead of* enforcing at the write, and the write-side enforcement stays. It is the reading-end half `convention-linear` already sanctions for the completions automation specifically, and the same subtraction `skill-plan` already applies. **The correction is not retrospective.** APP-585 forbids stripping a Done ticket from a cycle, so a burn-up already tracking drains cannot be repaired; only the reported figure can be. (Worked case: Apps cycle 9 closed at 222 members and 204 completed, of which 117 carried the bare `PUL-5E` value — 107 of them in six bulk-close batches of 9 to 29 inside two-minute spans — against roughly 105 real delivery scope and 87 real completions; three notes were in cycle 10 and Done within a day of it opening. APP-1425.)
- **Drawn top-down by priority — the commit is not priority-blind.** Order the candidate set by priority first: the Priority Board (project rank) and the latest `skill-plan` focus recommendation are **required, gating inputs**, taken from the top down until the throughput ceiling is reached. Read `skill-plan`'s most recent focus output and the project priority ranking before drafting the set; where the focus output is stale or absent, say so and fall back to project rank, rather than committing whatever the board happens to hold. A hand-committed, priority-blind cycle is the observed failure — the first live Aide day booked work not linked to the most urgent board items, because nothing fed priority into the commit (`APP-625`). Priority orders the set; the initiative-tie below filters it; throughput sizes it.

**And the gating input's freshness is read, not assumed — the ceremony states whether it consumed the focus output or fell back.** The rule above gives the disposition for a stale input and no test for staleness, so *stale* has been a judgement made fresh each run. Make it deterministic. Read `skill-plan`'s focus output and take its age from the document's **own stated date line**, not from `updatedAt` alone: the snapshot is replaced in place each run, so there is no history, and a four-day-old body is indistinguishable from a fresh one except by that line. Where the stated date is more than **24 hours** before this run's actual fire time, the output is stale. Then state the disposition in one line at the head of the proposal — `Focus output <YYYY-MM-DD>, <n>h old: consumed`, or `Focus output <YYYY-MM-DD>, <n>h old: stale, fell back to project rank` — naming the date, the age and which of the two happened. The fallback to project rank is unchanged; what changes is that it is declared rather than silent, so a reader can tell a commit drawn from a fresh recommendation from one drawn from the board alone, and a fallback that has become the permanent state is visible as such instead of reading as the exception the rule assumes. A gating input whose headline claim is four days out of date and was falsified within the hour of being written is worse than no input, because it reads as current. This instantiates `core-operating-model`'s catch-up contract — every run states its actual fire time and degrades explicitly rather than presenting a discontinuous record as continuous (APP-993) — on an input rather than on a run; it is not a new rule. (Worked case: the first Friday boundary run under the 16:00 cadence, 2026-09-25, read `osclaude.focus-snapshot` dated 2026-09-21 05:35Z — four days old, opening "Uncommitted on all four teams: no `cycle-goal.2026-W39` document exists anywhere", which was true when written and superseded about an hour later, when the W39 proposals were filed at ~06:37Z and committed at 08:42Z. The run fell back to project rank by judgement and nothing in its output said so. Under the two current cadences — `skill-plan` on Monday, this ceremony on Friday 16:00 — the stale branch is the standing case, which is the point: the declaration is what makes the cadence question visible instead of leaving the gating-input requirement satisfied only nominally — APP-1787.)
- **Tied to initiative objectives.** Each candidate maps to an active outcome-initiative or a near-term milestone. Work that maps to neither is drift — name it as such rather than committing to it. Where no outcome-initiatives exist, fall back to milestone-target and priority, and flag the gap (as `skill-plan` does).
- **Sized against observed throughput, not a full board — and report both a batched and an unbatched figure (APP-891).** The commit is sized to what recent cycles actually delivered inside the window, not to optimism and not to a backfilled burn-up. Take the `completedAt`-in-window throughput from Step 1 as the ceiling, and report **two figures side by side**: a **batched** figure (all completions in the window) and an **unbatched** figure (the same window with an obvious bulk-close drain set aside — for example a run of dozens of completions landing within minutes of each other, which is real work but not repeatable weekly capacity). **Size the commit on the lower of the two.** Do **not** pick a bulk-close exclusion threshold: a threshold is a magic number that will be wrong at the next boundary, whereas reporting both figures makes the divergence visible at commit time and leaves the judgement with Aled — a drain closing 64 notes in 11 minutes should not size the next week. If a team has been delivering ~74% of a 74-ticket scope once the drain is set aside, a proposal of a full fresh board is a cycle that cannot land. **Overcommitment is the known failure** — a cycle running well past capacity is one that never completes and that Aide then cannot book (`APP-581`). Size down to what the lower figure supports and say what was left out. The `APP-581` coupling runs both ways off the same undefined number: an inflated ratio says "the fleet cleared 135, so 135 is affordable" and commits the overcommitment `APP-581` records; a cycle backfilled with OPEN work reads low and **undersizes** by the same mechanism. Defining throughput as `completedAt`-in-window and sizing on the lower figure closes both.
- **Carries the rollover.** The carried tickets from Step 2 are part of the commit's weight, not free additions on top of a fresh board — count them against the throughput ceiling first.
- **Names every dated commitment inside the proposed window, and says what happens to each.** Before the set is posted, list every goal, objective, milestone and gate target date falling between the proposed cycle's `startsAt` and `endsAt`, and state per date whether it is committed in this set, deliberately excluded with a reason, or escalated. A date inside the window carrying none of those three dispositions is an omission, and the proposal names it as such rather than leaving it to be found on day three. The charter and the milestone list are already open for the initiative-tie read above, so the cost is one line per date; what it buys is an exclusion list that is exhaustive rather than illustrative, and a reader who can tell a date nobody chose to drop from one that was weighed and dropped. This is the proposal-time half of the check `skill-operating-loop` makes at loop time — one read at each end, because a commitment missed at the write is invisible to every later read that trusts the committed list as the definition of *planned*. (Worked case: A1's cycle 12, 2026-09-27 to 2026-10-04, was proposed and committed with a five-item list, a held-back slot and an explicit exclusion list naming four tickets, and with no mention of O6 — `assoc.one` live by 1 October — whose gate fell on day five of eight. The gate milestone and both milestones feeding it read 0%, and the four tickets beneath it had sat Urgent in Backlog for a week. Nothing in the proposal was wrong; the window's contents had simply never been read — `APP-1861`.)

**Platform behaviour vs. what canon reads (APP-891).** Whether Linear populates a cycle with already-Done work at its start — the day-one burn-up opening at 62% — is **platform behaviour canon cannot change**. Canon can only decline to read that opening as throughput, which is exactly what the `completedAt`-in-window definition above does. Do not attempt to make Linear start the cycle clean; read the number correctly instead.

**The coupling to Aled's capacity — name it, do not silently absorb it.** The commit proposal is what **Aide** reads to turn Aled's slice of the cycle into booked time (`APP-430`). A commit that ignores the Owner's actual available hours produces a cycle Aide cannot book — the committed cycle has run ~1.8× Aide's hold-window capacity (`APP-581`). Where the Aled-lane share of the proposed commit is plainly large, **flag the coupling in the proposal** so he sees it at commit time — stated in terms this Linear-only ceremony can actually read: the count of Aled-lane items in the proposed set, the In Review depth awaiting him, and the count of items Blocked on him. The ceremony never states hours: it has no Calendar read, and an instruction that asks for a figure the run cannot compute is either fabricated or silently dropped (the 2026-08-22 cycle-6 run substituted an item count and had to say so — APP-1054). The hours comparison against the hold windows belongs to Aide, which holds the calendar (Owner decision, Aled 2026-09-03). The fix for the capacity itself sits in Aide, not here. **But the coupling is the sizing constraint, not a closing note (Owner ruling, Aled, 2026-09-28, APP-1459).** The scarce resource this ceremony allocates is **Owner attention**, not agent throughput. So size the Owner lane first: take the **Owner-lane item count of the opening cycle's projected membership** as the ceiling — the committed list **plus** the carry-over, which is knowable at proposal time — allocate it across the three tiers declared with the goal, and **commit at roughly 75–80% of that ceiling, leaving the last slot of each day unfilled** — the held-back capacity and the trucking tier are the same pool honestly labelled, and unexpected work and trucking work compete for that residue with the more urgent winning. **The projected membership, not the committed list, because the ceiling is enforced at the commit and Linear's boundary carry-over does not read the commit.** Every still-open ticket moves into the opening cycle whatever the proposal says (Step 2; `convention-linear`, APP-1007 — the built-in incomplete-issue transfer, which is not the *Active issues & due date* toggle and cannot be turned off). So a trucking tier declaring that *nothing reaches the Owner* produces a cycle in which a great deal reaches him, while the ceiling reads satisfied because the commit was small. Measured at the 2026-10-04T23:00Z boundary: three of four teams proposed an Owner lane of **zero** or **one**, A1 committed five, and **19 open Owner-lane tickets** carried over regardless — then read six hours after the open, the actual was **26 Owner-lane of 30 total members** across the four cycles, worse than the projection because A1's proposal was never approved either and its own five became residue like everyone else's. **Count the residue across all four teams against one ceiling, not per team.** The tiers are declared per team; the attention they spend is one Owner's, so a trucking team's carried Owner-lane residue counts against the goal team's ceiling — that is what APP-1459's ruling means by sizing the scarce resource. **Two consequences to report alongside the figure, because they are what the carry-over does to the downstream reads.** First, `skill-coordinate` Pass 7 derives `cycle:unplanned` by diffing membership against the committed list, so carried Owner-lane tickets against a near-empty commit read as ~95% unplanned — inflated by the carry-over, not by unplanned work (APP-1095's figure measuring the wrong thing). Second, carry-over selects on *still open*, and the population that stays open is the population only the Owner can close, so each uncommitted boundary concentrates the cycle further toward Owner-lane work while agent-routable work stays **outside** the cycle entirely: at the 2026-10-05 read, **zero of 30 members carried an agent executor** and two carried no executor label at all. **Stripping the carried tickets is not the remedy** — they are live work, and removing them hides the load rather than sizes it. Report the projected Owner-lane figure, size against it, and name the residue. (APP-1991; the uncommitted-boundary condition it compounds is APP-1994.) Agent, operator and automation work stays a separate and much larger number and is **explicitly unconstrained in volume**: it is constrained only in what it hands back to the Owner, and the throughput sizing above continues to govern it unchanged. The ceremony still states no hours — the Owner-lane figure is an item count, which is what this Linear-only surface can read.

## Step 4 — Post the proposal, do not commit

Post one commit proposal per team for Aled — the output block below — **as a comment on that team's per-cycle goal document** (`<team>.cycle-goal.YYYY-Www`, filed per `convention-linear` *Documents and the decision log*), which this step writes first where it does not yet exist — see *Write the goal document before posting anything* below. That is the proposal's one home for every team, including a team with no mirror initiative: on 2026-08-22 three teams' proposals landed in three differently chosen places and MBN's on an unrelated initiative, so the consumer that tests freshness against "the latest proposal" had nothing deterministic to find (APP-1054; Owner decision, Aled 2026-09-03). **Nothing is written to any cycle.** The proposal states explicitly that it awaits his approval. When Aled approves, the cycle membership is written in the session where he approves, on his instruction, and never by the unattended scheduled run that posted the proposal (see *The membership write* below; APP-1580, 2026-09-21). Where several teams are at a boundary in one run, post one proposal each; the runs are independent per team.

**Write the goal document before posting anything — per team, and report every team left without one.** The proposal's home is the goal document, so the document is this step's **first** write, not a clause of the posting write. For each team at a boundary: resolve the opening cycle's ISO week from its `startsAt` (cycle 9 opening 2026-09-06T23:00Z is `2026-W37`). On the Friday run the opening cycle is `type:"next"`, so a Friday 2026-09-25 run creates `<team>.cycle-goal.2026-W40` (APP-1577), search the documents store for `<team>.cycle-goal.YYYY-Www`, and where none exists **create it, then post the** `[cycle-planning]` **proposal as a comment on it — in that order**. The body it is created with is the **proposal only** — the proposed goal, the proposed committed list, each line marked *proposed — not committed* — because the committed list is the definition of *planned* and only the Owner's write makes it the commit (`convention-linear`, *Labels*; APP-1095). Creating the document is an agent write and is **never** the approval marker: the marker is the `Committed by Aled — <date>` line added on the Owner's approval (APP-1567). The ceremony never writes that line, and it never edits the body after the create (APP-820, APP-987). Step 3's *once approved it is written to the per-cycle goal document* is that Owner write into the document created here, not a reason to leave the document uncreated. **Where a team ends the run without a goal document — the create failed, no opening cycle resolved, the run stopped early — name that team, its cycle and the cycle's end date in the summary and end on that, never quietly.** A ceremony that skips the write silently is indistinguishable from one that never fired, and every downstream approval test is then specified against an artefact that has never existed: `skill-coordinate` Pass 7 has no committed list to diff, so no `cycle:unplanned` is derived anywhere, and `skill-attention-sweep`'s freshness test reads a cycle that was never committed. (Observed 2026-09-11, 04:20–04:47Z: Apps, A1 and MBN each ran a live cycle 9, 2026-09-06T23:00Z → 2026-09-13T23:00Z, at 159 / 37 / 8 issues, and **no cycle-goal document of any shape existed anywhere in the workspace** — `cycle-goal`, `cycle goal` and `2026-W37` all returned empty, and the newest 60 documents held none, so no earlier cycle has one either. Three teams, three live cycles, no commit on any of them — APP-1273.)

**The approval signal downstream consumers test (APP-820, APP-987).** When Aled approves, the goal document's body is rewritten to the approved goal and committed list (the *proposed — not committed* markings removed), and it carries the **approval marker: an explicit line `Committed by Aled — <YYYY-MM-DD>`** (Owner decision, Aled 2026-09-21, APP-1567). **The marker is that line. The write's timestamp and author are not the marker.** Every connector write authors as *Aled Pritchard*, so neither the author field nor a later `updatedAt` can tell the Owner from the fleet. Worked case: `a1.cycle-goal.2026-W38` was updated at 2026-09-14 11:07, after its 06:42 proposal comment. Its body still read *proposed, not committed*, and no membership write followed, yet it passed the timestamp test as approved (APP-1567; the same shape as APP-1466). **No agent writes the marker line on its own account**: not the ceremony, not a drain, not a tidy-up edit. The one agent write of it happens in the Owner's session, on his explicit instruction to commit (*The membership write* below). A document without the line has not been committed, whatever its timestamps say. The line is honour-system, because an agent can technically write it, so the prohibition is the control. Agent identity that would make it enforceable is APP-1619's territory, and it is not a precondition here. A cycle-membership write is *not* the marker, and neither is a comment reply or a marker ticket: membership writes are produced routinely by the delivery loop (a Forge format bounce drops membership, Relay's re-route restores it — A1-345, 2026-08-17 08:28, read as Owner approval and booked against), and Linear's boundary auto-transfer produces one at every boundary by construction (APP-1007). Aide (`skill-attention-sweep`/`task-aide-loop`/`agent-aide`) tests this marker. A committed cycle whose goal document carries no `Committed by Aled — <date>` line, dated on or after the latest `[cycle-planning]` proposal comment, has not been approved this boundary, and Aide books nothing against it. A Friday proposal is two to three days old by Monday. Its age is not a staleness signal; only the missing line is (Owner decisions, Aled 2026-09-03 and 2026-09-21; APP-1567, APP-1577).

**The signal is load-bearing only while the membership write stays a human act (APP-820).** The freshness test works because the membership write is Aled's — a human approval that necessarily post-dates the proposal it approves. If the write were ever **delegated to an agent** (Atlas proposing a cycle and then writing its membership itself), the write would always follow the proposal automatically, the timestamp comparison would still return "fresh", and the signal would degrade into a tautology **while continuing to read healthy** — with no failing check anywhere. So cycle membership **remains a human write**; if it is ever delegated it must carry an **explicit approval marker an agent may set only on the Owner's instruction**, and Aide tests *that* marker instead of the timestamp. **That delegation is now in force, in exactly that shape (Owner, 2026-09-21, APP-1580).** The Owner's words were *"once I approve, it should be committed"*. That delegates the write to the agent in his session and nothing further. The agent applies the membership diff only on his explicit in-session instruction, and it writes the `Committed by Aled — <date>` marker in the same act. Aide tests the marker (APP-1567). This is the clause above taking effect, not a reversal of APP-429 / APP-629. No agent decides the week's scope, and the unattended scheduled ceremony still never writes membership. This is now a **stated dependency of Aide's correctness**, not merely propose-only governance: with APP-828's bounded auto-hold granted, nothing else stands between a stale-cycle read and a week of wrongly-booked holds — so the human-write constraint is a precondition of the freshness test, and the freshness test a precondition of the auto-hold grant. A later optimisation pass must not "helpfully" automate the membership write (Owner confirmed, 2026-08-14). Two clarifications keep this consistent with the marker above and with APP-1095. The Owner's approving write to the goal document records the goal **and the committed ticket list** — that list is what *planned* means for the cycle, and it is the list `skill-coordinate` diffs against each day. And `skill-coordinate`'s daily add-and-stamp — adding a started ticket that has no cycle to the current cycle and labelling it `cycle:unplanned` — is a membership write by an agent, but it is **not** a commit and is never read as one: the approval marker is the goal-document write, not membership, and the stamp exists precisely to mark the ticket as outside the commit (Owner decision, Aled 2026-09-03).

**The membership write — in session, on the Owner's instruction (APP-1580, 2026-09-21).** The approval arrives in a live session, such as Friday's `work.Planning` or a stand-up. In that session, and only on the Owner's explicit instruction to commit, do the following, in this order:

1. **Write the goal document.** Rewrite the body to the approved goal and committed list, remove the *proposed — not committed* markings, and add the `Committed by Aled — <YYYY-MM-DD>` line.
2. **Apply the approved diff to the opening cycle, by UUID.** Add each committed ticket, and never a `PUL-5E` note (APP-1038). Remove only the approved drops, and never strip a Done ticket (APP-585). A removal of a ticket the carry-over has not yet placed stays outstanding until after the boundary (Step 2).
3. **Read back each added ticket's cycle *and state*.** Committing a ticket to a cycle force-moves a backlog-type state (Backlog, Refinement) to **Todo**. The reverse also holds: moving a cycle ticket back to Backlog drops its cycle. So the write can land untriaged tickets in Todo. Report every Backlog/Refinement → Todo move by ID. Observed on the W39 write, 2026-09-21: seven tickets moved (A1-298, A1-393, A1-451, A1-452, APP-1302, MBN-301, MRP-56), five of them never triaged (APP-1581). This is observed platform behaviour, not a setting canon assumes can be changed.
4. **Diff the cycle's membership against the approved list, as a set.** Step 3 reads back the tickets that landed; a ticket that was never added is not in the set being read back, so no per-ticket check can see it. So after applying the diff, re-derive the opening cycle's membership **from the cycle** and compare it with the committed list in the goal document as two sets. Report every committed ticket whose `cycleId` is not the opening cycle's, by ID, as **not applied** — a different fact from *did not land*, which is a ticket that was in scope and did not finish. Re-derive from the cycle rather than re-reading the tickets the session believes it wrote: the failure being caught is a write that returns success and applies nothing, with `updatedAt` unmoved (`convention-linear`, *Writing Linear safely*; APP-1448, APP-1417), and a read scoped to the set the session believes it wrote inherits the same mistake. Where a ticket reads as not applied, retry it in a `cycle` write of its own, never bundled with another field — `cycle` is one of the params known to coerce or no-op when bundled (APP-1647) — and diff again. A ticket still not applied after one retry is reported, not retried a third time.
5. **Post `[cycle-planning] Membership applied — <n> added, <n> removed, <n> outstanding, <n> not applied`** as a comment on each team's goal document, naming any forced state moves and naming every not-applied ticket by ID. The fifth number is stated even when it is zero: a missing figure reads as a check that did not run, and this comment is the only record that the set-level diff happened. A non-zero count is carried into the boundary retro as **never scheduled**, not as *did not land* — the two have different remedies. The goal document's list is the definition of *planned* for every consumer (APP-1095), so a committed ticket outside the cycle is counted as planned by the document's readers and absent by the cycle's, `skill-coordinate` Pass 7 stamps nothing for it, and the unplanned share is inflated by the gap rather than by the work. (Worked case: the W39 Apps write of 2026-09-21 landed all five approved removals and ten of fourteen adds and posted its applied comment. APP-747, APP-1455, APP-681 and APP-982 still read `cycleId: none` five days later. Every per-ticket read-back passed, because the four missing tickets were never in the set read back, and the run that found them had to report four tickets under *did not land* that had never been in scope — APP-1786.)
6. **File each operator-addressed instruction as a ticket, in the same act.** An approved goal document routinely carries instructions addressed to a named `operator-<venture>` in its body prose — a priority raise, a re-route off the `human` lane, a pull-out-of-Backlog, a sequencing call. `convention-ticket`'s rule that every obligation is encoded as structure rather than prose binds here exactly as `convention-comms-owner` binds it to a *Forward* line: an instruction naming no ticket has one reader, the next weekly operator loop, so it expires with the cycle it was written for. So for each operator-addressed instruction in the committed document, file a small ticket in that vertical's home delivery team carrying `operator-<venture>` and **no executor**, titled with the instruction and naming both the goal document and the ticket it concerns, and list each by ID in the step 5 comment. The operator's daily pass reads the operator-routed queue first, so the lag drops from up to a full cycle to one day. This is a write of the Owner-approved set and nothing beyond it; the unattended Friday run files none of these. (Worked case: `a1.cycle-goal.2026-W41`, committed 2026-10-05, carried four instructions addressed to `operator-a1` — A1-583, A1-574, A1-537/538, A1-491. Twenty-four hours later A1-583, named in that same document as the goal's main deliverable, was still No priority carrying `human` and the Owner as assignee; all four were actioned only because that week's loop happened to fall there, and the lag would otherwise have been up to seven days — APP-2100.)

Without that instruction nothing is written, and the proposal stays awaiting approval. The scheduled run never does any of this, and neither does any other unattended run.

**An unapproved proposal becomes an open Owner decision on its second day — the ceremony states the date, and never waits quietly for it.** A proposal awaiting approval is *reported*, and nothing escalates it. That is how the first proposals on every team sat unapproved for a whole cycle while the one consumer that depends on them reported *no committed cycle* each day and correctly swept nothing: the freshness test worked, the gate held, and the silence was the defect. So two things change, neither touching the gate. First, each `[cycle-planning]` proposal states **its own escalation date in its closing line** — "Unapproved after `<YYYY-MM-DD>` (the second day), this proposal is an open Owner decision", where the date is the day after the proposal is posted. That gives every daily Owner-facing reader a dated, deterministic trigger in the document it already reads, where until now it had only the absence of an approval marker, which is indistinguishable from a proposal posted an hour ago. Second, on **any** later run of this ceremony, scheduled or on demand, **before proposing anything**: read each team's most recent cycle-goal document, and where it carries a proposal comment with **no `Committed by Aled — <date>` line dated on or after it**, name that team at the **head** of the run's output as an open Owner decision, with the proposal's date, its age in days and the cycle it governs — never as a quiet *no committed cycle* line further down among the caveats. Nothing else moves: the approval stays the Owner's, no agent writes the marker line outside his session, the unattended run still never writes cycle membership, and this ceremony still never approves its own proposal (APP-820, APP-987, APP-1580). All that changes is that an unapproved proposal stops being silent. (Worked case: on 2026-09-18, four days after cycle-planning went live, `apps`, `mbn` and `a1`.cycle-goal.2026-W38 all still read as verbatim `[cycle-planning]` proposals from 2026-09-14 with no goal write and no committed list, Mr Pritchard had no cycle-goal document at all, and no `*.cycle-goal.2026-W39` existed for the cycle covering the next working day — so `task-aide-loop`'s sweep → size → book loop had been structurally unable to run since the ceremony's first fire, with nothing escalating it and no failing check anywhere. W39 was committed on 2026-09-21, which closes that instance and not the gap: it recurs the next time a Friday proposal is not approved by Monday — APP-1546.)


**And an uncommitted boundary that repeats is one item escalating, not a fresh finding each run (APP-1994).** The clause above names an unapproved proposal with its age in days, which is right for the first one and wrong for the third. W38/W39 (APP-1546), W40 (APP-1961) and W41 (APP-1994) were each re-derived from scratch and re-surfaced as new — three notes on one condition, which is the repeat-surfacing `convention-linear` (*Escalating a stalled decision item — by age, not repetition*) forbids and the age ladder exists to replace. So the head-of-output read also **counts**: before naming a team, read its prior cycle-goal documents backwards and state how many **consecutive** boundaries have closed carrying no `Committed by Aled — <date>` line — "W41, second consecutive uncommitted boundary on this team", never "W41 unapproved, 3 days" where it is in fact the third. From the **second** consecutive boundary the condition stops being a per-run report and becomes **one carried item**: surfaced once, on one ticket, climbing one rung per boundary per the ladder, with a later run adding dated evidence to that item rather than filing another note — which is also why the retro's capture searches the queue for a same-root note before filing. **The gate is untouched.** The approval stays the Owner's, no agent writes the marker line outside his session, and the unattended run still writes no cycle membership (APP-1567, APP-1580, APP-820). All that changes is that the count is stated, so a standing state cannot keep reading as a first occurrence. (Observed 2026-10-06: all four `*.cycle-goal.2026-W41` documents now head *✅ Committed by Aled — 2026-10-05*, which closes the W41 instance and not the gap — the count is the thing nothing was keeping. The next test is the W42 boundary.)

## Output — the commit proposal

```
[cycle-planning] Boundary — <team> — <closing cycle> to <opening cycle> — <date>

Retro (closing cycle):
  Committed: <n> | Completed (completedAt in-window): <n> (<ratio>%) | Milestones moved: <list> | Slipped: <list>
  Throughput read: batched <n> | unbatched <n> (drain set aside: <what/when>) — sizing on the lower
  Goal met?: <yes / partial / no — one line; or "no goal was set">
  Planned vs unplanned: <n> on the committed list | <n> cycle:unplanned (<share>%) — <one line: steady / rising / falling against the prior boundary>
  Membership check: enumerated <n> vs issueCountHistory <n> — <match / mismatch: missing IDs where nameable>
  Did not land:
    • <ticket> — <one-line reason>
  Aged WIP (open committed work, age since last state change):
    • <lane> — open <n> | oldest <n> cycles | median <n> | unmoved a full cycle or more: <n>
    • No threshold applied — the measure only, nothing gated on it this run (APP-1171)

Rollover (proposed — needs your decision):
  Carry:   <ticket> — <reason>
  Drop:    <ticket> — <reason>  (cancel — major, not applied)
  Backlog: <ticket> — <reason>

Proposed cycle goal (opening cycle — needs your decision):
  Goal: <one stated objective — and the venture it serves>
  Reserved WIP slots for goal work: <n> of <total>
  Stretch (named, no WIP slot until the required set is done): <list, or none>

Proposed commit (opening cycle — needs your decision):
  Drawn top-down by priority; sized to the lower throughput figure: ~<n> tickets (unbatched delivery ~<ratio>%)
  Grouped initiative → milestone → ticket. A capability milestone carries a status word, never a percentage: Linear's progress bar is weighted by estimate points, so unestimated Done tickets count as zero and a finished capability can read 0% — and a capability is done when its demo runs, so a percentage implies a precision the milestone does not carry. A dated kind carries its target date. Milestone kinds and their naming: convention-ticket. Capability kinds are how new projects are set up; an existing project keeps the kinds it has until Aled moves it, per project (Owner ruling, 2026-09-22 — APP-1658).

  <initiative>
    <capability milestone> — <unknown / shaping / building / verifying / done>
    <Enabling · <name>, or Gate · <name>> — <done / not done> · target <date> · <on track / N days over>
      <ticket> — <one line>
      <ticket> — <one line>
    No milestone — <n> tickets
      <ticket> — <one line>
  Left out to fit: <ticket> — <reason>

Capacity note:
  Aled-lane items in the proposed set: <n>; In Review awaiting him: <n>; Blocked on him: <n> (Aide sizes and books from this against the hold windows).

Goal document:
  <team>.cycle-goal.YYYY-Www — <created this run / already existed / not created: reason> — this proposal is posted as a comment on it

Nothing written — awaiting your approval to commit cycle membership.
```

**Two renderings of one commitment, not two documents.** The goal document is the Owner's approval surface, and the whole cycle stalls until he reads and agrees it — so a surface that is hard to scan is the binding constraint on the ceremony, not a presentation preference. The **document body is the Owner's rendering**: the goal, and the commit list grouped **initiative → milestone/phase → ticket** with each milestone's completion and target date on its own line. The `[cycle-planning]` **comment carries the granular detail** — the retro arithmetic, the rollover reasoning, provenance, defect cross-references. One commitment, rendered twice, with nothing to keep in sync: the body states what is being agreed and the comment states how it was arrived at. Grouping is not cosmetic — it is the structure the board already carries and a flat ranked list discards, and it makes the drift legible at approval time rather than a fortnight later in the retro, since an at-risk milestone surfaces on its own instead of reading as a peer of 21 other rows. Tickets carrying no milestone are grouped under a stated *No milestone* heading with their count, so the gap is visible to the Owner before he approves rather than inferred afterwards; admissibility of an unmilestoned ticket to the commit list is unchanged by this rule. (Owner ruling, Aled 2026-09-14: *"this isn't very human readable so maybe we need two versions … should group them by initiative, then the table outlines what phases or milestones we want to hit and then the ticket"*, on opening a 22-row flat table — APP-1460.)

## Composition gate — goal-review before the ceremony leans on it

`skill-goal-review`'s output is a retro input, relied on **only once it has produced a run of `[goal-review]` summaries** (`task-goal-review`, weekly on trial from 2026-07-21). Leaning on it before it has output would make the ceremony's first live run also its dependency's first live run — two untested things failing together, with no way to tell which broke (`APP-501`; `APP-630` gate). Until then, read goal-review where one exists and retro against milestone and initiative state otherwise. Confirm goal-review has a track record before relying on its output in a live boundary run.

## Relationship to skill-plan — shared reading, not shared behaviour

This ceremony and `skill-plan` overlap in **what they read** (outcome-initiatives, cycle state, the board) and in nothing else. `skill-plan` is the **weekly focus snapshot** — a different cadence and a different output (the ≤5 in-flight recommendation), and it currently works. This is the **boundary ceremony** — retro, rollover, and the next commit. Two artefacts by deliberate decision (Aled, 2026-07-21, `APP-429`). **`skill-plan` must not be edited under this work** — where the two read the same Linear objects that is shared reading, and it is read here as a gating input to the commit, nothing more.

## Guardrails

- **One goal per cycle, propose-only (APP-815).** Atlas proposes a single cycle goal alongside the commit; Aled approves it at the same gate as membership. Never one goal per team; stretch goals never take a reserved WIP slot until the required set is done; the capacity split is reserved WIP slots, never an a-priori percentage.
- **Throughput is `completedAt`-in-window, not the burn-up (APP-891).** Read throughput from the completions whose `completedAt` falls inside the cycle window; `completedIssueCountHistory` is the burn-up (`APP-585` protects it) and a different number with a different job. Report a batched and an unbatched figure and size the commit on the lower — never pick a bulk-close threshold. Whether Linear opens a cycle with already-Done work is platform behaviour canon cannot change; canon only declines to read it as throughput. Same undefined number drives both the overcommitment `APP-581` records and its mirror, undersizing a backfilled cycle.
- **The approval signal is the `Committed by Aled — <date>` line in the cycle-goal document (APP-820, APP-987, APP-1567).** Downstream consumers (Aide) test cycle freshness against that line, dated on or after the latest proposal comment. A goal document without it is stale, not approved. A timestamp or author read never counts, because every connector write authors as the Owner. No agent writes the line except in the Owner's session, on his explicit instruction to commit. A membership write never counts either: the delivery loop and the boundary auto-transfer produce those without any Owner act.
- **No goal document, no commit — and the ceremony says so rather than ending quietly (APP-1273).** The goal document is Step 4's first write, per team, created carrying the proposal and left for the Owner's commit write; a team that ends the run without one is named in the summary with its cycle and end date. Every downstream approval test — `skill-coordinate` Pass 7, `skill-attention-sweep`'s freshness read — is specified against that document, so its silent absence reads downstream as *nothing to do* rather than as *never committed*.
- **Propose-only on cycle membership.** Read cycles, draft a rollover, post a commit proposal. The scheduled, unattended ceremony never writes cycle membership: no add, remove, or move. The write happens only after Aled's explicit approval, in his session and on his instruction, carrying the approval marker, with each added ticket's state read back (APP-1580, APP-1581, 2026-09-21). Atlas proposes, Aled commits; autonomous commit was considered and rejected (`APP-429`, `APP-629`), because an agent deciding the Owner's week's scope is a larger grant than the delivery loop makes anywhere else.
- **The commit is drawn top-down by priority — never priority-blind.** The Priority Board (project rank) and the latest `skill-plan` focus output gate the commit; a cycle committed without them booked the wrong emphasis on day one (`APP-625`). Priority orders, the initiative-tie filters, throughput sizes.
- **Never remove a Done ticket from a cycle** — completion history depends on Done tickets staying in scope; only Canceled tickets are ever stripped (`APP-585`).
- **Resolve the cycle UUID; never trust the literal `"current"`** — an empty result is a "no active cycle" record for that team, not an error to work around (`APP-580`).
- **No boundary, no ceremony — and a boundary is the current cycle's `endsAt` within about 72 hours of the run (APP-1577).** The scheduled ceremony fires on Friday 16:00 Europe/London, before Linear's Sunday boundary, and it reads a cycle still in flight by design: closing = `current`, opening = `next`, and the retro is partial, *as of Fri 16:00*. An on-demand run after the boundary resolves `previous`/`current` and reads closed data. A scheduled fire with no team whose current cycle ends inside that horizon reports and stops (APP-1007).
- **Step 2 gates entry and reconciles the carry-over; it never writes either.** Cycle automation is off on the delivery teams, so entry is the commit; Linear's carry-over of incomplete issues still runs, so Drop and Return to Backlog are stated as "do not carry into cycle N+1" on the Friday run, and as proposed removals from the opening cycle on an on-demand run after the boundary (APP-1007, APP-1577).
- **An enumeration / count mismatch is a caveat, never a hard-fail.** Proceed, report the mismatch on the proposal with the missing IDs where nameable, and never infer absence from a cycle-scoped read (APP-890).
- **Planned is the committed list in the goal document; unplanned is derived, never hand-labelled.** The retro reports the share per team each boundary; `skill-coordinate` stamps `cycle:unplanned` daily; no step here asks the Owner to label anything (APP-1095).
- Never marks Done, never cancels or defers a ticket autonomously — those are major moves, surfaced for Aled.
- Never writes code, never opens a branch or PR, never touches the Pipeline team. Delivery teams only (Apps, A1, MBN).
- Does not edit `skill-plan` — a sibling artefact, out of scope for changes; this ceremony reads its focus output, nothing more.
- The unattended run that posts the proposal never makes the resulting write. The write waits for Aled's explicit approval, and it is applied in his session, on his instruction (APP-1580).

## Setup

- A **scheduled task on the Cowork / connector surface** (Linear connector only — no repo, so it is a task on the connector surface, not a Claude Code routine). Its config is a thin loader carrying the canonical loader line in `core-operating-model` (*The loader line — one form*) and nothing else — behaviour changes are made here, never in the config.
- **Cadence:** weekly, **Friday 16:00 Europe/London**, inside `work.Planning` (Fri 16:00–17:00, `convention-hold-windows`), as a pre-boundary proposal for the next cycle. Linear's Sunday boundary is left untouched, and which team is at a boundary is still resolved from the cycle's `endsAt`, never assumed from the weekday (Owner decision, Aled 2026-09-21, APP-1577). The scheduled task carries `CRON_TZ=Europe/London 0 16 * * 5` (read 2026-09-22). `skill-plan`'s Friday focus output should land before 16:00, so the commit reads a fresh one.
- **Before it schedules a live boundary run:** run this ceremony once by hand against a real boundary, with Aled approving the resulting commit; and confirm `skill-goal-review` has a track record (*Composition gate*). The by-hand run was the W39 planning session on 2026-09-21: the Owner approved, and the commit was applied in session (APP-1580).

On finish, run `skill-ops-retro` capture on this run's friction: file a FRICTION note to the Pulse queue for the drain pass to assess.
