---
name: convention-linear
type: convention
description: >-
  How Aled's work is expressed in Linear specifically — the teams and projects,
  the issue states per workflow, the label taxonomy, the agent single-select
  group and its routing values (including the lowercase operator-per-venture
  values such as operator-bara), the Pipeline people-vs-opportunity model and its Gmail/surfaced
  labels, document and decision-log filing, and issue numbering. Read whenever a
  task touches the Linear workspace — creating or updating issues, choosing
  teams/projects/labels, filing docs, or running agent work in Linear. This is
  the tool layer; convention-ticket holds the tracker-agnostic discipline that
  this maps onto Linear. Getting team, labels, or states wrong creates real
  cleanup, so consult this first rather than guessing.
---

> *This is the canonical copy of this skill. The corresponding Linear doc (*`convention-linear`*) is a reference copy. Edit here; re-sync to Linear after changes.*

# Linear workspace conventions

Aled runs all his work through Linear, the **system of record**. This convention is the **tool layer**: it says how the tracker-agnostic discipline in `convention-ticket` is expressed in *this* workspace — its teams, states, and labels. Read `convention-ticket` for the working discipline (how a ticket is shaped, captured, validated, and routed); read this for the Linear specifics that enact it. Where the two touch, this document names the concrete Linear binding and points back to `convention-ticket` for the why.

The failure modes here are silent — a stale label, an issue in the wrong state, a decision never logged — and they cost real cleanup later. Follow precisely.

## The workspace: teams and where work goes

Five teams. Route to the right team first, then the right project within it.

| Team | Key | What it holds |
| -- | -- | -- |
| **careerOS** | `COS` | The job-search and portfolio system. Projects: cOS.System (architecture, philosophy, canon), cOS.Content (truth captures `tc.*`, case studies, CV, narrative), cOS.Build (engineering, website, automation), cOS.App (AI behaviour for the portfolio product), cOS.Reporting (inbound product signals). |
| **Apps** | `APP` | Product/app builds and cross-cutting build infrastructure. Projects: os.Claude (how Claude is operated — skills, routines, agent instructions), app.fitness, Luna MVP, bot.Trader, and the gtd.* GTD projects (Capture, Configure, Build, Health, Home, Family, MBN, CareerOS). |
| **Pipeline** | `PIPE` | People and opportunities (see "The Pipeline model" below). Projects: Network (the people layer), Roles, Advisory, Pitches (the opportunity funnels). |
| **A1** | `A1` | The Association of One consultancy. Projects: a1.OS (standards and playbooks — the practice's operating system), a1.Brand (A1's own identity and the client-facing document system), and one project per client engagement (a1.MeirionPritchard, a1.Heroes). |
| **Mr Pritchard** | `MRP` | Editorial and personal brand. Projects: Articles (long-form, canon voice), Substack (short-form), LinkedIn (posts), Operations (decision log, project instructions, get-started). Editorial cascades: an Article is the parent; its Substack and LinkedIn derivatives are sub-issues. |

Quick routing:

* A *person* → Pipeline / Network. An *opportunity* → Pipeline / Roles, Advisory, or Pitches, titled for the opportunity (never the person).
* How Claude is operated (skills, routines, agent instructions) → Apps / os.Claude.
* A1 client work → the A1 team, one project per client; A1 standards and playbooks → a1.OS.
* Editorial → Mr Pritchard (Articles → Substack → LinkedIn).

If the right home is genuinely ambiguous, ask rather than guess. A misfiled ticket is worse than a clarifying question.

## The Pipeline model — people vs opportunities

The Pipeline team has two layers, and keeping them distinct is the whole point of the structure:

* **People layer — `Network`.** Every contact lives here, inbound or outbound. The unit is a *person*. A person enters here and never moves out; the relationship persists regardless of what it produces.
* **Opportunity layers — `Roles`, `Advisory`, `Pitches`.** Concrete opportunities, each titled for the *opportunity* — **never for a person**. An opportunity issue is created only when something real exists.

The rule: **a person never appears in an opportunity funnel, and an opportunity is never titled with a person's name.** When a relationship produces something concrete, an opportunity issue is spawned in the right funnel and *linked back* to the person in Network via a native relation. One person can throw off several opportunities over time; each is its own linked issue; the person stays put.

This is why Advisory may look near-empty: it holds live advisory *engagements*, not advisory *leads*. Leads are people, and people are in Network.

### Gmail capture labels (the `pipeline/` family)

Mail is fed into the funnels by labelling Gmail threads with the `pipeline/` prefix (renamed 2026-06-02 from `job-search/`). The sweeps read these:

* `pipeline/roles` — a specific role → swept into Roles
* `pipeline/recruiter-internal` / `pipeline/recruiter-agency` — a recruiter → swept into Network
* `pipeline/network` — a general contact → swept into Network, no `opp:` unless signalled
* `pipeline/advisory` — a person near an advisory opportunity → swept into Network, stamped `opp:advisory` (advisory *intent*; does NOT route to the Advisory project)
* `pipeline/triaged` — the processed-marker added by every sweep so threads aren't re-swept

### Surfaced / graduation labels (Linear)

The link between the people layer and the opportunity funnels runs on structured labels, not free-text scanning:

* `surfaced:role` / `surfaced:advisory` / `surfaced:pitch` — a concrete role / advisory opening / A1 new-business item has surfaced from this Network relationship
* `surfaced:done` — graduated; an opportunity issue has been created and linked (prevents re-graduation)

The feeding sweeps add `surfaced:role` / `surfaced:advisory` automatically when they spot a concrete opening; add a marker by hand after a call surfaces something. The graduation routine reads these markers, creates the linked opportunity issue, then swaps the trigger label for `surfaced:done`.

## Issue states

The capture → refined → ready lifecycle in `convention-ticket` maps onto these Linear states. Two workflows are in play, by team:

**careerOS, Apps, A1, Mr Pritchard (engineering/kanban):** `Backlog`, `Refinement`, `Todo`, `In Progress`, `In Review`, `Blocked`, `Done`, `Canceled`, `Duplicate`.

**Pipeline (forward-moving funnel):** `Captured`, `Backlog` (= identified, not yet actioned), `Todo`, `Awaiting` (acted, ball in their court), `Active` (live back-and-forth), `Engaged` (resolved well), `Passed` (no fit / cold / withdrew), `Canceled`. Network oscillates (active and dormant) rather than completing — read warmth from the labels, not the column.

The bindings to `convention-ticket`:

* **Captured → `Backlog`.** New tickets the operator hasn't refined go in **Backlog**, not Todo. Todo means "ready to action." Don't move things to Todo on his behalf unless he's said it's ready.
* **Refined-and-parked → `Refinement`.** The human refinement gate, reserved for tickets needing the operator's input or deliberate go. Agents treat Refinement tickets as read-only, with the comment-since-last-agent-comment exception (see *triage*). A no-gap refined ticket parks here **unassigned**; one carrying a genuine question is assigned to Aled. **A refined leaf that is ready except for a sibling dependency is not parked here** — it is ready, so it goes to **Todo carrying a blocked-by relation**, never Refinement (see `convention-ticket` *Structure*). The builder skips it while the blocker stands and picks it up when it clears, so no manual re-promotion is needed.
* **Ready → `Todo`.** Only Aled promotes to Todo unless he's said otherwise. A ready-but-blocked ticket also lives here, carrying its blocked-by relation.
* **Review → `In Review`** and sign-off → `Done` by Aled — see Pattern A in `convention-ticket`. **Done requires every box in *both* the `## Acceptance criteria` and `## Definition of done` sections ticked** (or struck with a recorded one-line waiver): the merge leg and Aled do not move a ticket to Done while any acceptance or definition-of-done box is unticked and unwaived. The `type:*` label selects which per-type template (skeleton + DoD) the ticket is instantiated from at refine — see `convention-ticket` *Per-type ticket formats*. This is the Linear binding of the Definition-of-Done rule in `convention-ticket`.
* **`Blocked`.** Set by the build leg when it hits an unresolvable blocker mid-run (no write access, a missing dependency or credential, a requirement too ambiguous to act on safely). The ticket leaves In Progress, carries `human` (the `agent`-group value), and is assigned to Aled, who clears it and moves it back to Todo. Distinct from a blocked-by relation (a genuine inter-ticket dependency). **Reserve Blocked for a genuine external blocker that separate work must clear.** An item whose *only* outstanding need is Aled's own review or sign-off — including an acceptance criterion that cannot be verified in the loop's environment but needs no further work, just his eyes — is **not** Blocked: it belongs in **In Review**, carrying `human`, assigned to Aled, escalated to **Urgent** only if it lingers. Routing a review-only item to Blocked mis-signals it as dependency-bound and hides it from the "In Review = awaiting your sign-off" surface (worked case: A1-129, 2026-07-06).

## Labels

Every ticket carries the labels that classify it.

**Type:** `type:epic`, `type:feature`, `type:story`, `type:task`, `type:bug`. The `type:*` value also **selects the ticket template** — its section skeleton and its standing definition-of-done — which the author pastes in at refine (see `convention-ticket` *Per-type ticket formats*).

**Executor:** one value from the `agent` **single-select group** — there is no separate executor label. Human-owned work is the value `human` in that group; functional-agent work is one of the branded agent values; operator-owned work is a lowercase `operator-<venture>` value. Setting any of them evicts the previous (it is single-select), so `human` evicts the `R3-LAY` triage marker exactly like `F0-RGE` does.

* `human` — decisions, design, launch ops, anything human-owned. (Label `human`, parent `agent`. This replaced the retired `exec:human` label, which has not been in use for a long time; treat any lingering `exec:human` reference as meaning the `agent`-group `human` value.) **Work that needs an MCP the Forge CLI lacks (e.g. a Figma export) and is realistically a one-off manual action defaults to `human` + assigned to Aled**, not to a runner-less agent value: a ticket parked on a value that no delivery loop or scheduled runner picks up looks routed but stalls unowned (worked case: A1-124, an `agent:claude` Figma export that sat in Todo and bounced to Backlog with nothing to run it, 2026-07-06).
* `agent` **group (single-select)** — which executor owns the work (the former `exec:agent:*`, `exec:human`, and `author:claude-code` labels were retired and folded here). This enumeration **must agree with the `agent-roster` fleet table** — reconcile both whenever the fleet changes:
  * `agent:R3-LAY`, `agent:F0-RGE`, `agent:PR-1SM` — the triage / build / review roles of the delivery loop (the routing pattern in `convention-ticket`, bound to Linear here)
  * `agent:PUL-5E` — Pulse, the ops / self-improvement agent. Owns the **friction queue** (below): it does *not* execute delivery-loop work, so a ticket carrying it is propose-only intake, not a build candidate. Setting it on a Backlog ticket (in place of the `agent:R3-LAY` default) is what keeps that ticket out of the Relay→Forge loop.
  * `agent:SO-N4R`, `agent:P1-XEL`, `agent:R3-ACH` — the commercial fleet: Sonar (design strategy, discovery & service design), Pixel (brand & design system), Reach (marketing & GTM). Run on the connector surface (Figma, Gmail, Shopify) via the fleet-dispatch path, not the Forge delivery loop.
  * `agent:ATL-4S` *(future)* — Atlas, planning and cross-operator portfolio arbitration. Carried for agreement with the roster; no runner yet.
  * `operator-<venture>` (lowercase, e.g. `operator-bara`) — an **operator** value: work owned by the vertical's operator (see *Operator attribution* below and `core-operating-model`, Layer 3). Lowercase like `human`, not branded like the functional fleet, so agent-vs-operator-vs-human reads at a glance.
  * `A1-DE` — **Aide**, the Owner-serving personal chief-of-staff (`agent-roster` *Owner-serving agents*): sweeps Aled's committed cycle for what needs him and books his `work.Hold` time. Reads Linear (read-only), writes only his calendar — a personal / Owner-serving role, distinct from the functional fleet and the vertical operators. MVP, not yet built (blocked on the APP-428 planning-and-time layer); carried in the enumeration because the live `agent` group already holds the value.
  * **Legacy tool-era values — `agent:codex`, `agent:gpt`, `agent:replit`.** Holdovers from when work was routed by which tool would run it (Claude vs Codex), before the role-based fleet. They map to no current role, should not be set on new work, and are retired from the workspace `agent` group once no open ticket carries each. `agent:claude` was the same class — retired 2026-07-06, removed from this enumeration, and its open work re-routed (e.g. an MCP-only export → `human`; a discovery audit → `SO-N4R`).
* **Single-select:** an issue carries exactly one `agent:*` at a time; setting a new one evicts the previous, so a handoff is just setting the next role.
* **Backlog default:** a ticket entering Backlog carries `agent:R3-LAY` so Relay triages it; the executor (`agent:F0-RGE` etc.) is set by Relay at routing, not at creation. Pre-assigning an executor at creation skips the triage gate. **Exception — canon-improvement work in os.Claude:** a canon-change intake ticket — a `FRICTION:` capture note, or any ticket whose deliverable is a saved `.skill` (a skill, convention, agent or operator edit) — enters Backlog carrying `agent:PUL-5E` (never `agent:R3-LAY`), deliberately bypassing delivery-loop triage; the Pulse retro drain processes it. `R3-LAY` is not excluded from os.Claude — it still owns ordinary delivery triage there; it simply does not own canon improvement (Pulse does). See *os.Claude Backlog routing by work type* below.

**Workstream:** the group is `work`, but the labels are **not uniformly named** — pass the exact name the workspace holds, not a guessed prefixed form. `engineering` is a true grouped label (parent group `work`, bare name `engineering`), so it is passed as **`engineering`**, not `work:engineering`. Every other workstream label is a **flat label whose full name carries the prefix**: `work:product`, `work:experience`, `work:ai-behaviour`, `work:content-data`, `work:configuration`, `work:infrastructure`, `work:analytics` — passed exactly as written. Passing `work:engineering` (the prefixed form for the one grouped label) matches nothing and is **silently dropped** by `save_issue` — no error, caught only by reading the write-back (worked case: 2026-07-01, APP-406). This is the same silent-failure class as the read-filter traps in *Reading Linear safely*; until the taxonomy is normalised (all flat, or all grouped) the exact names above are what to pass. Normalising the `engineering` label to match the flat `work:*` naming is a workspace-config tidy worth doing, but the doc records the live names meanwhile.

**Pipeline-specific (on Network / opportunity issues):**

* Contact type: `contact:network`, `contact:reconnect`, `contact:loose`, `contact:recruiter-internal`, `contact:recruiter-agency`
* Opportunity mode: `opp:advisory`, `opp:fractional`, `opp:consulting`, `opp:fulltime` — applied only when signalled
* Warmth: `warmth:warm`, `warmth:cool`, `warmth:cold`
* Role stage (Roles issues): `stage:applied`, `stage:screen`, `stage:interview-1`, etc.
* Surfaced markers: `surfaced:role` / `surfaced:advisory` / `surfaced:pitch` / `surfaced:done`
* Reporting: `reporting:contact`

A typical engineering ticket carries one `type:`, one `work:`, and one `agent`-group value — `human` or one `agent:*` (`agent:R3-LAY` while in Backlog; the real executor set at routing). A Network contact carries a `contact:*`, `reporting:contact`, `type:task`, and an `opp:*` only where a mode is known.

`author:*` marks who *created* something; the execution axis is the `agent` group.

## Operator attribution

An **operator** (`core-operating-model`, Layer 3) owns a vertical and is represented on the board by a lowercase `operator-<venture>` value in the same single-select `agent` group as the functional fleet — a domain role, deliberately not droid-branded. Rules:

* The value marks work the **operator owns and is accountable for** — typically an epic or initiative-level ticket for its vertical, or a ticket it is driving. It does not replace the functional executor on a build leaf: when an operator dispatches build work through Relay, that leaf still carries `agent:R3-LAY`→`agent:F0-RGE` in the normal way. The operator value sits on the *owning* ticket, not on every leaf beneath it.
* Setting `operator-<venture>` evicts any previous `agent:*` value (single-select), like any other executor.
* The enumeration of operator values lives in `agent-roster` (*Operators*) and must agree with the Linear `agent` group — reconcile both when a venture gains an operator.
* Anything an operator cannot do within its mandate — a strategic pivot, a budget-envelope breach, a goal-set change — is escalated to the **Owner** (Aled): assign him and comment the recommendation, never action it unilaterally. This is the operator-tier mirror of the delivery loop's "the operator is the gate", one level up.

## Agent routing — the Relay / Forge / Prism loop, in Linear

The routing pattern (triage → build → review → gate) from `convention-ticket`, bound to Linear's labels and states. There is no Linear agent app user — `@claude-code` is a label, not a user, so triggering is by label, driven by polling Cloud Routines. **Aled is the gate; nothing merges without his approval.**

* `agent:R3-LAY` — triage and PM. Sets status / priority / links; if execution is needed, sets `agent:F0-RGE`, moves to **Todo** (never Backlog), clears the assignee, and comments the acceptance criteria. Also carries the **post-review merge gate**: when Aled approves a reviewed ticket by setting `agent:R3-LAY` (evicting `agent:PR-1SM`) and giving his `@relay` signal, the merge leg squash-merges and closes it.
* `agent:F0-RGE` — execution. Claims the ticket by moving to In Progress and clearing the assignee (status is the lock; no separate lock label). Opens a PR (never merges), sets `agent:PR-1SM`, moves to In Review, **leaving it unassigned**.
* `agent:PR-1SM` — review. Reads the PR against the embedded criteria, posts a plain-language verdict, then **assigns Aled** (leaving the label `agent:PR-1SM`). Switching to `agent:R3-LAY` is Aled's approval action.

With the ticket assigned to him at `agent:PR-1SM`, Aled either bounces it (**In Review → Todo with a note** re-triggers the build leg on the same ticket) or approves by **setting `agent:R3-LAY` and giving his `@relay` signal**, triggering merge.

**`R3-LAY` is a triage marker, not a resting executor.** Once a ticket is triaged or refined it carries its *real* executor, and `agent:R3-LAY` is evicted — `agent:F0-RGE` (or another agent) for agent work, **`human`** (the `agent`-group value) for human-owned work such as a decision, a design, launch ops, or an on-device verify. So triage sets `human` at refinement for human-owned work, the mirror of setting `agent:F0-RGE` when execution is needed; a refined human-owned ticket then carries `human`, is assigned to Aled, and drops out of the `agent:R3-LAY` triage poll rather than re-surfacing on every run for no work. Setting `human` is the documented action, not a consequential call — no suggest-mode hesitation is warranted for it. This holds for a human-owned ticket parked in **Refinement** too: where it still carries the old `agent:R3-LAY` (a pre-convention leftover), the executor-hygiene relabel to `human` is the sanctioned migration that drops it from the poll — a routing change, not a re-refinement of content (see `skill-triage` *Refinement comment sweep*). A ticket in **Todo** must carry a real executor; `agent:R3-LAY` lingering there is an anomaly to fix, not a valid resting state.

Concurrency: the build leg only picks up `agent:F0-RGE` tickets in **Todo**, and skips its run if any `agent:F0-RGE` ticket is already In Progress (one agent per repo). Scope: the loop runs across delivery projects only and must never touch the Pipeline team.

## The Pulse friction queue — canon-improvement intake

A second, parallel intake into Apps / os.Claude that the delivery loop never touches. It exists because the surface that *spots* friction (a Claude Code routine, a headless run) is rarely the surface where a `.skill` can be *saved* — but filing a Linear note is something every surface can do. So friction is captured as a queue note first, and the artefact is produced later by the Pulse retro drain, where the save button works.

* **Shape:** a Backlog ticket in Apps / os.Claude, title prefixed `FRICTION:`, labels `type:task` + `work:configuration`, executor **`agent:PUL-5E`**, unassigned. The `FRICTION:` prefix and `agent:PUL-5E` together mark it as canon-improvement intake.
* **Who fills it:** `skill-ops-retro` in capture mode — embedded as a skill's final step, or run headless / on Claude Code. Capture is a note (an observation), never a proposed canon edit and never a Forge hand-off. **This holds unconditionally:** a concluded design, planning, or discussion session is *not* an implicit instruction to author the change — the session captures the note to this queue and stops. Authoring canon happens only in an explicit drain (`task-pulse-retro`, or Aled's explicit "drain / author this now"); a session never decides to author on its own read of intent (see `core-operating-model`, *Changing canon*).
* **Who drains it:** `task-pulse-retro` (the scheduled Pulse retro), or `skill-ops-retro` run on demand. It reads the queue, assesses each note against current canon, produces ready-to-save artefacts, comments the outcome, and moves the note to **Done**.
* **Distinct from `skill-issue-capture`.** That captures runtime/repo problems (`FIX:`/`OPS:`/`DRIFT:`) which *do* route to Forge or the operator as delivery work. `FRICTION:` is ops-layer canon improvement, drained into a save-and-then-publish artefact, never executed by Forge directly. Keeping the two lanes apart preserves the split: repo changes go through Forge; canon changes go through the save button.
* **Reading it:** the agent-group filter takes the bare value `PUL-5E`; a `Backlog` filter also returns Refinement, so re-check each ticket's state name (see *Reading Linear safely*).

## os.Claude Backlog routing by work type

In os.Claude specifically, the Backlog executor is chosen by the *nature* of the work, not a blanket default. The blanket `agent:R3-LAY` default still holds for ordinary delivery work, but two kinds of work are born on a different executor:

* **Canon improvement / friction → `agent:PUL-5E`.** `FRICTION:` capture notes and any canon-change intake (a ticket whose deliverable is a saved `.skill` — a skill, convention, agent or operator profile). Drained by the Pulse retro, never the delivery loop.
* **Repo-native or publish work → the delivery loop, straight through.** A change to a repo file (`CLAUDE.md`, `build-manifest.ts`, site code) or an ops-sync publish ticket is ordinary Forge work: it carries `agent:R3-LAY` in Backlog and is routed to `agent:F0-RGE` at triage like any build ticket. In os.Claude this routing is **straight-through** — because the deliverable needs no human decision, triage promotes it directly to **Todo** rather than holding it in Backlog under a suggest-mode comment for a separate confirm (the suggest-mode carve-out in `skill-triage`). Suggest-mode-hold still applies to any os.Claude ticket carrying a human decision.
* **Everything else** — operator-written, or surfaced from elsewhere, needing triage — **→ `agent:R3-LAY`**, the standard default.

So `agent:R3-LAY` is the triage marker for delivery work, not a label every os.Claude ticket wears: canon-improvement work is born on `agent:PUL-5E`. This is the Linear binding of the canon / repo / publish three-lane split defined in `core-operating-model`.

## Reading Linear safely

Several connector behaviours bite the Linear-reading and -writing skills (triage, coordinate, plan, daily-report, merge, exec) **silently** — a run looks clean while reading, or acting on, the wrong thing. They live here, once, so every reading and writing skill inherits them rather than repeating the warning.

* **Comment authorship is unreliable — read the body, not the author field.** The connector stamps every comment's `author` as the account owner (Aled), whoever actually wrote it. To tell an operator comment from an agent one — the signal the sweeps gate on — read the body's leading marker (`[exec]`, `[qa-review]`, `Relay — …`, `pm-triage …`, `[plan]`), never `author.name`. An unprefixed comment is the operator's.
* **`list_issues` state and label filters have two traps.** Filtering `state: "Backlog"` also returns **Refinement** tickets — both share the underlying `backlog` type — so a "Backlog only" sweep must re-check each ticket's state **name**, not trust the filter. And the agent-group label filter takes the **bare value** (`R3-LAY`), not the displayed form (`agent:R3-LAY`), which silently matches nothing.
* **`list_issues` returns every ticket's full description — narrow it, and for large projects parse it out-of-band.** A whole-team query returns all bodies and exceeds the tool's token limit (Apps has run ~387k characters). The first defence is to narrow: filter by state to drop Done/Canceled/Duplicate, scope to a project, use modest `limit`s, and pull a single ticket's full body with `get_issue` only when you actually need it. **For Apps specifically, narrowing by recency and limit is not enough** — os.Claude alone holds 50+ recently-touched tickets with long bodies, so even an `updatedAt`-narrowed, limit-capped read overflows (worked case: coordinate, 2026-06-27, ~90k chars at `-P3D`/`limit:60`). So for the reading skills (coordinate, plan, triage, daily-report) the documented default is to **scope per project, not per team**; and where even one project overflows, **delegate the parse to a subagent** — save the raw result, hand the subagent a character-range slice, and have it return only the compact fields (id/title/status/labels/assignee/project/parentId). The connector exposes no field-selection or description-exclusion option, so this out-of-band parse is the standard mitigation for a large board, not an ad-hoc workaround. A compact `list_issues` mode (rows without descriptions) would remove the overflow class entirely and is worth raising if the connector is ours to influence.
* **Don't mutate on a parsed or bulk-read field — re-fetch it with `get_issue` immediately before a state-changing write.** When a sweep will *change* a field (state, labels) and the candidate came from a bulk `list_issues` — especially the overflow-forced out-of-band subagent parse above, whose positional column alignment is unreliable — confirm the ticket's *current* `status` (and any other field the write depends on) via `get_issue` just before writing. A parsed `state` column has gone stale silently (worked case: triage applied in-thread cancellation decisions to four already-Canceled tickets and posted confirming comments, 2026-06-29 — harmless noise on closed tickets, but the same trust on a live state or label write would act on the wrong ticket). The read filter trap above tells you the *list* can mislead; this tells you to re-verify the single field you are about to write.
* **A state-change write's response can lag — confirm the new state by reading it back, don't trust the returned object.** When `save_issue` moves a ticket between states (notably In Progress → In Review), the response object sometimes still shows the *old* state even though the write succeeded and the Linear UI shows the new one (worked case: APP-375, APP-376, delivery loop, 2026-06-30). A second identical write returns the correct state, but the loop should not branch on the first response: after any state-change write, read the state back — via the next `get_issue`, or by treating a re-fetch as ground truth — before deciding the ticket's actual state, rather than retrying blindly or misjudging the transition from a stale response. This is the write-side mirror of the re-fetch-before-write rule above: that one distrusts a stale *read* before writing; this one distrusts a stale *response* after writing. The underlying cause — a Linear API propagation race or MCP-tool cache — is the connector's, not ours to fix; the defensive read-back is the canon mitigation.
* **Any write to a ticket resting in In Review or Blocked can silently revert its state and reassign it — read back after *every* write to a resting ticket, not only after a combined state+assignee call.** A workspace automation on the In Review / Blocked transition re-applies an "active state + assign creator" change *after* a write lands. It fires on more trigger paths than first documented: not just a call passing `state` and `assignee: null` **together**, but a **labels-only** write (setting only `labels` + `priority`, no `state`, no `assignee`) and an **assignee-only** write (setting only `assignee`, no `state`) — each has reverted an In Review ticket to In Progress and set the assignee to the creator (worked cases: A1-125 labels-only ×2, A1-124 and APP-439 assignee-only, delivery loop, 2026-07-06; the earlier A1-49 / APP-404 combined-call case, 2026-07-01). So the defensive pattern is general: **after any `save_issue` write to a ticket that should be resting in In Review or Blocked, read it back with `get_issue`; if the state (or assignee) regressed, re-set it in a separate follow-up call** — which may mean re-asserting `state: "In Review"` as well as `assignee: null`, each as its own call. The clean fix is to disable the workspace automation that reassigns and reactivates on these transitions — a workspace-settings change for the operator; the read-back-and-correct pattern is the defensive canon mitigation meanwhile. (This supersedes the narrower "state-change write that also clears the assignee" rule: the trigger is any write to a resting ticket, not only a state+assignee call.)

## Documents and the decision log

**Filing a document:** use the document tool, set `title` and the parent `project`. One doc per artefact. Type-prefix the title: `skill.`, `routine.`, `task.`.

**The decision log** is realised in Linear as a set of project documents: an index doc (`osclaude.log-decisions`) and one child doc per ISO week (`osclaude.log-decisions.YYYY-Www`). To log a decision, append the entry to the current week's doc — creating it if needed — then update that week's row in the index table. Weekly buckets exist because Linear's document write is **full-replace, with no append**: a small doc is safe to rewrite, a large one risks clobbering existing entries. The log's structure, entry format, and lifecycle are canon in `convention-decision-log`; this is only the Linear mapping.

**Issue numbers** are auto-assigned by Linear (each team has its own key — COS, APP, PIPE, A1, MRP). Never invent them.

## Canon and source-of-truth discipline (Linear)

* **Linear is canon — with one scoped exception.** Project MD files, the website, and synced copies are *derived from* Linear. When they disagree, Linear wins (or Linear needs updating — the file never silently becomes the source).
* **Exception — skill, convention, agent, and operator artefact content.** The **Claude-side saved skills are canon**; edit content there. The `assoc-one/claude-ops` GitHub repo is a **one-way published mirror, downstream of Claude** — the agents site builds from it and the remote Cloud Routines clone it, so it must be kept current, but it never feeds back. Publishing is **Claude → repo, via tickets**: `skill-ops-sync` detects when Claude is ahead and raises the publish tickets. The repo is canon only for its own repo-native files. (Supersedes the 2026-06-05 repo-is-canon rule; decision: osclaude.log-decisions, 2026-06-16.)
* For the general "don't edit canon silently" and "don't fabricate" rules, see `convention-ticket`.

## Quick checklist before creating or updating anything in Linear

- [ ] Right team and project? (Pipeline for people/opportunities; A1 for the consultancy; Mr Pritchard for editorial; careerOS for the system; Apps for builds.)
- [ ] Person → Network; opportunity → Roles/Advisory/Pitches titled for the opportunity, linked back?
- [ ] Backlog (not Todo) if Aled hasn't refined it?
- [ ] Correct label set? (engineering: one `type:`/`work:`, plus one `agent`-group value — `human`, one `agent:*`, or an `operator-<venture>`; `agent:R3-LAY` while in Backlog, real executor set at routing; Pipeline: `contact:`/`reporting:contact`/`type:task`, `opp:` only where known. Workstream: pass `engineering` bare, the rest as `work:*` — see *Labels*.)
- [ ] Assignee — Aled only where a genuine human action is needed? (Operator-mandate-edge escalations assign Aled as Owner.)
- [ ] Any comment assigning the operator an action written as numbered, novice-friendly steps — one action per line, commands spelled out literally, a decision kept separate from a thing to type (see `convention-ticket` *Writing an instruction the operator has to action*)?
- [ ] Hierarchy and milestone set? (Leaf under the right epic/feature; milestone where the project has them. Epics never carry `agent:F0-RGE`.)
- [ ] Ticket shape, Pattern A criteria, notes section — per `convention-ticket`?
- [ ] Consequential decision made? → decision-log entry added.
- [ ] Artefact content authored Claude-side (canon), published to `claude-ops` via a ticket?
- [ ] Tone clean per `cos.tov`?
