From 54bea30df23afac3732ae405be0992d29d84d44a Mon Sep 17 00:00:00 2001 From: petaltank Date: Fri, 11 Sep 2026 21:10:05 -0600 Subject: [PATCH 1/2] Add rollout posture (Step 1.9): feature flags as a recorded decision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In a codebase that gates new behavior behind feature flags, "is this flagged?" was a decision no pass asked โ€” it got invented mid-ticket or skipped. New grounding step, same shape as design posture: record once per project whether a rollout mechanism exists (none / flags / staged) and where its convention lives, CLAUDE.md-first. When it does: Pass 1 requires a per-feature Rollout: line; Pass 3 marks each ticket that ships behind the flag ๐Ÿšฉ (soft signal, like ๐ŸŽจ) and, for a temporary flag, adds a cleanup ticket to the DAG blocked by the last flagged one โ€” removing a flag is work, and work gets a node. Ticket detail and Fork A issue descriptions carry a one-line Rollout: field. dstack-ticket calls the marker out; dstack-yolo confirms the flag is wired and defaults per the convention before [x]. Unset โ†’ none. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01NLMK6F3vQ1HzyU1Mxqt6Kr --- CHANGELOG.md | 12 ++++ README.md | 18 +++++- examples/reference-project/TODO.md | 3 + examples/reference-project/notes.md | 8 +++ .../claude-code/commands/dstack-ticket.md | 17 +++-- harnesses/claude-code/commands/dstack-yolo.md | 12 +++- harnesses/claude-code/skill/dstack/SKILL.md | 64 +++++++++++++++++-- meta/roadmap-0.4.md | 2 +- spec/llm-coding-workflow.md | 16 +++++ 9 files changed, 135 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 382451b..e5ed42a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ _See `meta/roadmap-0.4.md` for the full list of what's planned for this release and the status of each item. Entries land here one PR at a time as each ships._ +- **Rollout posture (Step 1.9)** โ€” in a codebase that gates new behavior behind feature flags + (or a beta cohort, a canary), "is this flagged?" was a decision no pass asked, so it got + invented mid-ticket or skipped. New grounding step, same shape as design posture: record + once per project whether a rollout mechanism exists (`none` / `flags` / `staged`) and where + its convention lives (`CLAUDE.md`-first). When it does: Pass 1 requires a per-feature + *Rollout:* line (flag, default, audience โ€” or "not flagged, because"); Pass 3 marks each + ticket that ships behind the flag ๐Ÿšฉ (soft signal, like ๐ŸŽจ) and, for a temporary flag, + **adds a cleanup ticket to the DAG** blocked by the last flagged one โ€” removing a flag is + work, and work gets a node. Ticket detail and Fork A issue descriptions carry a one-line + *Rollout:* field. Persists as `rollout_posture` / `rollout_convention_location`; unset โ†’ + `none`. See `llm-coding-workflow.md`'s "Rollout posture" section. + ## 0.3.0 โ€” 2026-08-05 - **Design posture (Step 1.8)** โ€” dstack previously had no place to affirmatively decide how diff --git a/README.md b/README.md index 8eae8a6..a78a512 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,12 @@ produced one of them. existing design system/component library govern it, is it deliberately utility-only, or does one need to be established now before Pass 2 goes far? In the reference project, `design_posture: existing` โ€” the host app's `CLAUDE.md` already documents its component - library, so no new design work was needed, just a pointer to it. + library, so no new design work was needed, just a pointer to it. Last, the same question + about **rollout** (Step 1.9): does this codebase ship new behavior behind feature flags (or + a beta cohort, a canary), and if so where's the convention? The reference project answers + `rollout_posture: none` โ€” the digest is opt-in per user, so no flag. A project that answers + `flags` gets a required *Rollout:* line per feature in Pass 1, a ๐Ÿšฉ marker on each ticket + that ships behind the flag, and โ€” if the flag is temporary โ€” a cleanup ticket in the DAG. 3. **Pass 1 โ€” Intake:** guided questions fill in whatever Step 1.6 didn't already answer โ€” the "what": product intent, problem, MVP surface, success criteria. Anything that can't be settled yet becomes a row in the **open questions ledger** โ€” an id, a status (resolved with @@ -205,6 +210,15 @@ Any line only on one side of that diff is a word whose count changed โ€” worth c - **No visual surface at all (CLI, API, background job) โ†’ "none."** Also a deliberate, recorded answer, not a skipped question. +**What rollout posture do I pick (Step 1.9)?** +- **The codebase has a feature-flag system โ†’ "flags."** Point dstack at the convention (or + write it into `CLAUDE.md` now โ€” flag service, naming, add/toggle/remove, cleanup + expectation). From then on every feature says whether it's flagged, flagged tickets get ๐Ÿšฉ, + and a temporary flag's removal is a ticket in the DAG, not a reminder. +- **Beta cohorts, per-tenant enables, canaries โ†’ "staged."** Same shape, different mechanism. +- **Everything ships to everyone โ†’ "none."** The recommended default when nothing says + otherwise โ€” and a recorded answer, so a later reader knows it was considered. + ## Common mistakes Drawn from real evidence, not hypotheticals โ€” these are documented failure modes a prior, @@ -244,7 +258,7 @@ start and otherwise trusted. Downstream commands will pick up the new value on t predates the four prep questions, or one where only some got backfilled)? Every command falls back to that question's stated recommended default from Step 1.5 rather than treating it as an error: `team_shape` โ†’ `solo`, `risk_tolerance` โ†’ `gate-every-ticket`, `resumability_cadence` โ†’ -`same-day` (skip the recap), `retro_cadence` โ†’ `per-phase`. This is a deliberate default, not a +`same-day` (skip the recap), `retro_cadence` โ†’ `per-phase`, `rollout_posture` โ†’ `none`. This is a deliberate default, not a bug โ€” see "Migrating a repo that already had dstack" above for backfilling it properly instead of relying on the fallback indefinitely. diff --git a/examples/reference-project/TODO.md b/examples/reference-project/TODO.md index a9b3821..e28e789 100644 --- a/examples/reference-project/TODO.md +++ b/examples/reference-project/TODO.md @@ -15,6 +15,9 @@ than one line per ticket here, it belongs in the ticket file instead. barrel through it - ๐ŸŽจ design-touching โ€” check against the project's design ground rules (`notes.md` Step 1.8) before closing; not a hard gate, just a visible reminder +- ๐Ÿšฉ flag-gated โ€” this ticket puts behavior behind a feature flag; confirm it's wired and + defaults per the project's rollout convention (`notes.md` Step 1.9) before closing. Not used + in this project (`rollout_posture: none`) โ€” listed so the legend is complete ## Dependency graph diff --git a/examples/reference-project/notes.md b/examples/reference-project/notes.md index cf0932e..a150784 100644 --- a/examples/reference-project/notes.md +++ b/examples/reference-project/notes.md @@ -7,6 +7,8 @@ retro_cadence: per-phase repo_profile_location: mixed design_posture: existing design_ground_rules_location: claude-md +rollout_posture: none +rollout_convention_location: n/a last_session_at: 2026-06-14T09:00:00-06:00 --- @@ -36,6 +38,12 @@ longer and more specific than this, but the structure and section names are the components directly, and are marked ๐ŸŽจ in `TODO.md` as a reminder to check that reuse against `CLAUDE.md`'s conventions before either ticket is closed โ€” not because this project needed a bespoke visual identity. +- **Step 1.9 โ€” rollout posture:** `rollout_posture: none` (front matter above). The host app + has no feature-flag system, and the digest is opt-in per user anyway โ€” the preference toggle + *is* the rollout gate, so no flag, no ๐Ÿšฉ tickets, no cleanup node. Recorded so a later reader + knows this was decided, not skipped. A project whose posture is `flags` would instead carry + a *Rollout:* line under MVP surface, ๐Ÿšฉ on each flagged ticket, and a cleanup ticket in the + DAG. --- diff --git a/harnesses/claude-code/commands/dstack-ticket.md b/harnesses/claude-code/commands/dstack-ticket.md index a308d21..bd89491 100644 --- a/harnesses/claude-code/commands/dstack-ticket.md +++ b/harnesses/claude-code/commands/dstack-ticket.md @@ -25,8 +25,9 @@ points for this command: project uses rather than assuming Linear. Do not ask the user which fork โ€” detect it from the files present. - Prep-question answers (`team_shape`, `risk_tolerance`, `resumability_cadence`, - `retro_cadence`) live in the project's `notes.md` front matter โ€” read them; they shape how - this command presents its pick (see Steps 4โ€“5). + `retro_cadence`) and the grounding postures (`design_posture`, `rollout_posture`) live in + the project's `notes.md` front matter โ€” read them; they shape how this command presents + its pick (see Steps 4โ€“6). ## 2. Pick the project @@ -68,8 +69,9 @@ Inside the project folder, check what's actually there: - **`TODO.md` present โ†’ Fork B (local).** This is the default for solo/lighter-weight projects in this repo. Read `TODO.md` directly: - The **Tickets** section โ€” one line per ticket (skeleton only; full detail lives in - `tickets/.md`), checkbox (`[ ]`/`[~]`/`[x]`/`[!]`, plus ๐Ÿšง for human-gated infra steps - and ๐ŸŽจ for tickets that touch user-visible UI), with `blocked-by` / `blocks`. + `tickets/.md`), checkbox (`[ ]`/`[~]`/`[x]`/`[!]`, plus ๐Ÿšง for human-gated infra steps, + ๐ŸŽจ for tickets that touch user-visible UI, and ๐Ÿšฉ for tickets that put behavior behind a + feature flag), with `blocked-by` / `blocks`. - The **DAG** section โ€” the diagram, declared **roots**, and **Phases** table. - Checkboxes are a **hint, not ground truth** โ€” status drifts. Before treating an upstream ticket as done, sanity-check against the **code on disk** (does the file/dependency/route @@ -115,7 +117,12 @@ Present your choice to the user with: - **The ticket**: id (`KAI-####` for Fork A, or the local id like `V1` for Fork B), title, and its phase. Call out a ๐Ÿšง human-gate if present, and a ๐ŸŽจ marker if present โ€” note that its micro-plan and close-out should check the work against the project's design ground rules - (Step 1.8 in `SKILL.md`) before it's called done. + (Step 1.8 in `SKILL.md`) before it's called done. Call out a ๐Ÿšฉ marker the same way: read + the ticket's **Rollout:** line (flag name, default, cleanup ticket) and note that the + micro-plan should wire the flag per the convention `rollout_convention_location` points at + (Step 1.9 in `SKILL.md`), and that close-out confirms it defaults the way the line says. + If the ticket *is* the cleanup ticket, say so โ€” its job is removing the flag and the dead + path, nothing more. - **Why it's next**: which upstream deps are satisfied, what it unblocks downstream. - **Alternatives**: any other tickets on the eligible frontier (especially parallelizable branches). If `team_shape` is `small-team` or `larger-team`, frame these explicitly as things diff --git a/harnesses/claude-code/commands/dstack-yolo.md b/harnesses/claude-code/commands/dstack-yolo.md index d6d3508..e29f5bb 100644 --- a/harnesses/claude-code/commands/dstack-yolo.md +++ b/harnesses/claude-code/commands/dstack-yolo.md @@ -49,8 +49,8 @@ architectural context and the prep-question front matter). If exactly one projec Parse from `TODO.md`: - the **ticket set** with statuses (`[ ]` open ยท `[~]` in-progress ยท `[x]` done ยท `[!]` - blocked/paused ยท ๐Ÿšง human-gate ยท ๐ŸŽจ design-touching) โ€” one line per ticket, pointing at - `tickets/.md` for depth, + blocked/paused ยท ๐Ÿšง human-gate ยท ๐ŸŽจ design-touching ยท ๐Ÿšฉ flag-gated) โ€” one line per ticket, + pointing at `tickets/.md` for depth, - the **dependency edges** (`Blocked-by` / `Blocks`), **roots**, and **topological phases**, - the last ~10 lines of the **Execution log** inline; the full history lives in `execution-log-archive.md` if more context is needed. @@ -119,6 +119,14 @@ If this ticket is marked ๐ŸŽจ, check the work against the project's design groun autonomous execution โ€” but a ticket that visibly drifts from established ground rules should be noted in its re-spec, the same as any other divergence, not silently shipped. +If this ticket is marked ๐Ÿšฉ, confirm before `[x]` that the new behavior is actually behind +the flag named in the ticket's **Rollout:** line, that the flag is registered per the +convention `rollout_convention_location` points at (Step 1.9 in `SKILL.md`), and that it +defaults the way the line says (usually off). Same soft-gate semantics as ๐ŸŽจ: a mismatch is a +re-spec note, not a silent ship โ€” and if the flag turned out to be unnecessary or permanent, +the Rollout line and the cleanup ticket (drop it from the DAG, with a log entry) get updated +in the same commit. + If the implementation diverged from the plan, **rewrite `tickets/.md`** so it describes what was actually built (not the original promise) โ€” this is the atomic bundling that makes re-spec reliable; do it as part of closing the ticket, not as a follow-up. Flip the ticket to diff --git a/harnesses/claude-code/skill/dstack/SKILL.md b/harnesses/claude-code/skill/dstack/SKILL.md index ae27406..869553c 100644 --- a/harnesses/claude-code/skill/dstack/SKILL.md +++ b/harnesses/claude-code/skill/dstack/SKILL.md @@ -39,9 +39,10 @@ Once the project is settled, look inside it for an existing living doc (`notes.m and/or a `TODO.md`. - **If found:** name what you found and which pass it reached (scan its headers โ€” does it have a Pass 2 / Pass 3 section? a ticket DAG? front matter with prep-question answers, see - Step 1.5? a `design_posture` answer, see Step 1.8?). Ask whether to **resume** that project - (and at which pass) or **start something new**. If resuming, load it into context, skip - Step 1.5 and Step 1.8 (their answers are already in the doc's front matter), and jump to the + Step 1.5? a `design_posture` answer, see Step 1.8? a `rollout_posture` answer, see Step + 1.9?). Ask whether to **resume** that project (and at which pass) or **start something + new**. If resuming, load it into context, skip Steps 1.5, 1.8, and 1.9 (their answers are + already in the doc's front matter), and jump to the relevant step below. Before treating the doc as settled, run the **open questions ledger's required confirmation pass** (see Step 2) over any row still carrying a "Resolved" or "Deferred" status from a prior session โ€” a resumed session is exactly the kind of consuming @@ -84,6 +85,8 @@ retro_cadence: per-phase | close-out-only repo_profile_location: claude-md | dstack-file | mixed | none design_posture: none | utility | existing | new design_ground_rules_location: claude-md | design-md | dstack-file | n/a +rollout_posture: none | flags | staged +rollout_convention_location: claude-md | dstack-file | n/a --- ``` @@ -123,7 +126,7 @@ If material is provided: silently. If nothing is provided, this step is a no-op โ€” proceed to Step 1.7, then Step 1.8, then Step -2's Pass 1 as normal. +1.9, then Step 2's Pass 1 as normal. ## Step 1.7 โ€” Repo profile (existing codebases only) @@ -186,6 +189,37 @@ replace Pass 2's per-feature visual decisions โ€” once ground rules exist (or th utility/none decision is recorded), Pass 2's Component architecture still makes this feature's specific screens/components, grounded against whatever was established here. +## Step 1.9 โ€” Rollout posture (does new behavior ship behind a flag?) + +Skip this step when resuming (the project's earlier answer persists in front matter as +`rollout_posture`). Otherwise, ask once, via `AskUserQuestion` โ€” see `llm-coding-workflow.md`'s +"Rollout posture" section for the full reasoning: + +1. **No rollout mechanism** *(recommended default when nothing in `CLAUDE.md` or the codebase + says otherwise)* โ€” changes ship to everyone. Record `rollout_posture: none`; this is a + deliberate answer, not a skipped question. Nothing below applies to this project. +2. **Feature flags** โ€” a flag system governs new behavior. Ask where its **convention** lives: + which flag service/library, how flags are named, how one is added/toggled/removed, and + whether temporary flags are expected to be cleaned up. Same `CLAUDE.md`-first logic as + Step 1.7: if it's documented there, point at it; if it's partially or not documented, ask + whether to **add the missing pieces to `CLAUDE.md`** *(recommended)* or write them to + `doc/dstack//rollout-convention.md`. Record `rollout_posture: flags`. +3. **Other staged rollout** โ€” a beta cohort, per-tenant enable, canary, or similar. Same + convention question and recording. Record `rollout_posture: staged`. + +Record the answer and its location in front matter (`rollout_posture`, +`rollout_convention_location`). When the posture is `flags` or `staged`, three things follow +downstream โ€” say so now, briefly, so the question doesn't feel arbitrary: + +- **Pass 1 states the per-feature decision** (Step 2): a required *Rollout:* line under the + MVP surface โ€” behind which flag, defaulting to what, visible to whom โ€” or *not flagged, + because โ€ฆ*. Unknown yet โ†’ an open-questions ledger row, not silence. +- **Pass 3 marks flagged tickets ๐Ÿšฉ and adds a cleanup ticket** (Step 3) when the flag is + temporary โ€” removing a flag is work, and work gets a DAG node. +- **Ticket detail carries a `Rollout:` field** โ€” flag name, default, cleanup ticket id โ€” in + `tickets/.md` for Fork B and in the Linear issue description for Fork A, so whoever + picks the ticket up (or reads the issue) sees the flag guidance without opening the doc. + ## Step 2 โ€” Establish the project + entry pass If starting fresh, ask for a short **project name** (kebab-case) โ†’ this becomes @@ -193,8 +227,12 @@ If starting fresh, ask for a short **project name** (kebab-case) โ†’ this become provided material, put its recommendation to the user as the default option rather than asking blind; otherwise ask directly: - **Pass 1 โ€” Intake:** the *what*. Product intent, the problem, the MVP surface, success - criteria. Output: `doc/dstack//notes.md` with the Pass-1 sections (plus the Step 1.5 - front matter). Anything that can't be settled yet goes into an **open questions ledger** + criteria. If `rollout_posture` is `flags` or `staged` (Step 1.9), the MVP surface ends with + a required **Rollout:** line โ€” *behind flag ``, default off, visible to * or *not + flagged, because * โ€” decided here, not invented mid-ticket; if it genuinely can't + be decided yet, it's a ledger row. Output: `doc/dstack//notes.md` with the Pass-1 + sections (plus the Step 1.5 front matter). Anything that can't be settled yet goes into an + **open questions ledger** section โ€” a table with columns id / question / raised-in-pass / status โ€” not a loose bullet list. See `llm-coding-workflow.md`'s "Open questions" section for the exact row format. - **Pass 2 โ€” Architecture:** rewrite the *same* doc through a structural lens (data model, @@ -265,7 +303,8 @@ scan/triage mechanics. ### Fork B โ€” Local TODO.md (lighter weight / solo / autonomous) *(default for this repo today)* - Generate **`doc/dstack//TODO.md`** as a pure **skeleton**: a status legend - (`[ ] [~] [x] [!]`, plus ๐Ÿšง human-gate and ๐ŸŽจ design-touching), the DAG diagram, roots + + (`[ ] [~] [x] [!]`, plus ๐Ÿšง human-gate, ๐ŸŽจ design-touching, and ๐Ÿšฉ flag-gated when the + project's posture calls for it), the DAG diagram, roots + topological phases, and **one line per ticket** โ€” `- [ ] **T3** Recipient mgmt โ€” blocked-by: T1,T3 ยท blocks: T6,T7 ยท phase 3 ยท [detail](tickets/T3.md)` โ€” plus only the last ~10 lines of the Execution log (older entries roll into `execution-log-archive.md`). Match the shape used @@ -282,6 +321,14 @@ scan/triage mechanics. `new` (Step 1.8) โ€” unlike ๐Ÿšง this is **not** a hard gate, just a visible signal that the ticket's Pass-4 micro-plan and close-out should check its work against the design ground rules before the ticket is called done, so visual polish doesn't silently get skipped. +- Mark any ticket that puts behavior behind the flag with ๐Ÿšฉ when `rollout_posture` is + `flags` or `staged` (Step 1.9) โ€” same soft-signal semantics as ๐ŸŽจ: the micro-plan and + close-out should confirm the flag is wired and defaults per the convention. Each ๐Ÿšฉ + ticket's `tickets/.md` (or Linear description, Fork A) gets a one-line **Rollout:** + field โ€” flag name, default, cleanup ticket. **If the flag is temporary, add a cleanup + ticket to the DAG now** โ€” remove the flag + the dead path, `blocked-by` the last ๐Ÿšฉ ticket, + in its own phase after launch โ€” so removal is a node the next-ticket pick will find, not a + reminder that rots. If the flag is permanent, say so in the Rollout line instead. - Hand off: tell the user to run **`/dstack-yolo`** to autonomously work the whole DAG (findings scan โ†’ plan-gate per `risk_tolerance` โ†’ implement โ†’ verify โ†’ re-spec โ†’ loop), or **`/dstack-ticket`** if they just want the next ticket picked and confirmed without @@ -294,6 +341,9 @@ Summarize: the project, the pass reached, the artifacts written (`doc/dstack/`, same for `/dstack-yolo`, `/dstack-retro`, `/dstack` | `[ ]` | `feat/command-args` | โ€” | An argument picks the project or *proposes* a ticket. The findings gate and the dependency check never move โ€” a proposed ticket still goes through both. | | 2 | Mermaid โ€” two separate things | `[ ]` | `feat/mermaid-diagrams` | โ€” | **(A)** a `diagrams:` prep question: a tool for making pass artifacts readable, with a short when-to-draw-what table. **(B)** the DAG is *always* a rigid, conventional Mermaid diagram (fixed `classDef`s, one subgraph per phase, one edge per `blocked-by`), re-rendered from the ticket one-liners in the same commit as every status change. | | 3 | `/dstack-feedback` โ€” log a note about dstack itself while working | `[ ]` | `feat/feedback` | โ€” | One file per note in `doc/dstack/feedback/`, `status: new`, same shape as a finding. A gate (same step as the findings scan) clears the list: **file** as a GitHub issue on the dstack repo via `gh`, or **drop** with a reason. Agents are told to log friction the moment they hit it. | -| 4 | Rollout posture (feature flags), Step 1.9 | `[ ]` | `feat/rollout-posture` | โ€” | Asked once per project, same shape as design posture: `none` / `flags` / `staged`, with the convention's location recorded (`CLAUDE.md`-first). Per feature, Pass 1 states the rollout decision; flag-gated tickets get a soft marker; a temporary flag's cleanup becomes a DAG node, not a memory. | +| 4 | Rollout posture (feature flags), Step 1.9 | `[~]` | `feat/rollout-posture` | โ€” | Asked once per project, same shape as design posture: `none` / `flags` / `staged`, with the convention's location recorded (`CLAUDE.md`-first). Per feature, Pass 1 states the rollout decision; flag-gated tickets get a soft marker; a temporary flag's cleanup becomes a DAG node, not a memory. | | 5 | `vcs_shape` prep question โ€” where dstack's opinion on commits/branches/PRs starts and stops | `[ ]` | `feat/vcs-shape` | โ€” | `branch-per-project` / `branch-per-ticket` / `trunk`, recommended by `team_shape`. Invariants for every shape: one ticket = one commit bundling code + re-spec + status, always with trailers. Explicitly *not* dstack's: merge strategy, CI, review rules, release tagging. `/dstack-ticket` gains the close-out step it never had. | Status legend: `[ ]` not started ยท `[~]` in progress ยท `[x]` shipped (PR merged) ยท `[-]` diff --git a/spec/llm-coding-workflow.md b/spec/llm-coding-workflow.md index 5f959e7..92a5f53 100644 --- a/spec/llm-coding-workflow.md +++ b/spec/llm-coding-workflow.md @@ -127,6 +127,21 @@ The output of this isn't prose buried in Pass 1 โ€” it's a **recorded posture** --- +## Rollout posture: how new behavior reaches users, decided once + +Some codebases ship every change straight to everyone. Others put new behavior behind a feature flag, a beta cohort, a per-tenant enable, or a canary โ€” and in those codebases, "is this flagged?" is a real decision every feature has to make, with consequences the ticket text needs to carry (what the flag is called, what it defaults to, who sees it, when it comes out). Left unasked, it fails the same way design posture does: either an agent invents a flag ad hoc mid-ticket with no naming convention behind it, or a feature that should have shipped dark ships live because nobody said otherwise. Neither is a decision โ€” both are what happens when the question never gets asked. + +Same fix, same volatility split: + +- **Whether this codebase uses a rollout mechanism at all, and what its convention is** โ€” the flag system, how flags are named, how one is added/toggled/removed, whether temporary flags are expected to be cleaned up โ€” is durable. Decide it **once per project**, record it, and point at the convention (in `CLAUDE.md` if it lives there, same preference as the repo profile) rather than re-deriving it per feature. "This codebase doesn't gate anything" is a legitimate, recorded answer. +- **Whether *this feature* ships behind the mechanism** is per-feature and belongs in Pass 1, next to the MVP surface: *behind flag `X`, default off, visible to internal users* โ€” or *not flagged, because Y*. Never silent when the project's posture says a mechanism exists. If it can't be decided yet, it's a ledger row. +- **A temporary flag's removal is work, and work gets a ticket.** A flag that's meant to come out once the feature is proven is a promise with no forcing function โ€” exactly the shape that rots. So Pass 3 gives it a node in the DAG: a cleanup ticket, blocked by the last ticket that ships behind the flag, that removes the flag and the dead path. It sits on the frontier after launch like any fast-follow, where the next-ticket pick will find it. A flag that's meant to be permanent (a tenant-level capability, say) doesn't get one โ€” say so. +- **Tickets that put behavior behind the flag carry a visible marker** โ€” soft, like the design one: not a gate, but a signal that the ticket's micro-plan and close-out should confirm the flag is wired and defaults the way the convention says, and that its detail (and, in a structured ticketing backend, its issue description) carries a one-line **Rollout:** field naming the flag, its default, and the cleanup ticket. + +The point, as with design posture, is that "no rollout mechanism" and "everything flagged, cleanup ticketed" are both fine โ€” and "we never decided" is the only failure mode. + +--- + ## The context each ticket gets at execution time - **Backward (state of the world):** the model reads **both** the **ticket** (intent, kept truthful โ€” see Keystone) and the **actual code** on disk (reality), and reconciles them. @@ -298,3 +313,4 @@ Before Pass 1 begins on a new project, a small number of questions shape how the 15. **Split grounding by volatility.** Durable repo facts prefer `CLAUDE.md` and get patched, not regenerated; feature-specific facts stay scoped and fresh every single time. 16. **Design posture is a decision, not a default.** "No visual surface," "deliberately utility-only," "follow the existing system," and "establish one now" are all legitimate answers โ€” the failure mode is never picking one and letting Pass 2 invent it silently. 17. **Open questions are a ledger with required evidence, not a paragraph of good intentions.** Resolved needs a citation, deferred needs a named target, dropped needs a reason โ€” and the next agent to touch the doc re-checks the citation before trusting the label. +18. **Rollout posture is a decision too, and flag cleanup is a ticket.** Decide once whether the codebase gates new behavior and where the convention lives; then every feature states whether it's flagged, and a temporary flag's removal gets a DAG node instead of a reminder. From 06c11aaa440dfa0490662d7994b0bfd061c99e89 Mon Sep 17 00:00:00 2001 From: petaltank Date: Fri, 11 Sep 2026 21:10:16 -0600 Subject: [PATCH 2/2] Record PR number for rollout posture Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01NLMK6F3vQ1HzyU1Mxqt6Kr --- meta/roadmap-0.4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta/roadmap-0.4.md b/meta/roadmap-0.4.md index 8ddd186..2cdab3f 100644 --- a/meta/roadmap-0.4.md +++ b/meta/roadmap-0.4.md @@ -19,7 +19,7 @@ touches are the spec. | 1 | Command arguments โ€” `/dstack-ticket `, same for `/dstack-yolo`, `/dstack-retro`, `/dstack` | `[ ]` | `feat/command-args` | โ€” | An argument picks the project or *proposes* a ticket. The findings gate and the dependency check never move โ€” a proposed ticket still goes through both. | | 2 | Mermaid โ€” two separate things | `[ ]` | `feat/mermaid-diagrams` | โ€” | **(A)** a `diagrams:` prep question: a tool for making pass artifacts readable, with a short when-to-draw-what table. **(B)** the DAG is *always* a rigid, conventional Mermaid diagram (fixed `classDef`s, one subgraph per phase, one edge per `blocked-by`), re-rendered from the ticket one-liners in the same commit as every status change. | | 3 | `/dstack-feedback` โ€” log a note about dstack itself while working | `[ ]` | `feat/feedback` | โ€” | One file per note in `doc/dstack/feedback/`, `status: new`, same shape as a finding. A gate (same step as the findings scan) clears the list: **file** as a GitHub issue on the dstack repo via `gh`, or **drop** with a reason. Agents are told to log friction the moment they hit it. | -| 4 | Rollout posture (feature flags), Step 1.9 | `[~]` | `feat/rollout-posture` | โ€” | Asked once per project, same shape as design posture: `none` / `flags` / `staged`, with the convention's location recorded (`CLAUDE.md`-first). Per feature, Pass 1 states the rollout decision; flag-gated tickets get a soft marker; a temporary flag's cleanup becomes a DAG node, not a memory. | +| 4 | Rollout posture (feature flags), Step 1.9 | `[~]` | `feat/rollout-posture` | #10 | Asked once per project, same shape as design posture: `none` / `flags` / `staged`, with the convention's location recorded (`CLAUDE.md`-first). Per feature, Pass 1 states the rollout decision; flag-gated tickets get a soft marker; a temporary flag's cleanup becomes a DAG node, not a memory. | | 5 | `vcs_shape` prep question โ€” where dstack's opinion on commits/branches/PRs starts and stops | `[ ]` | `feat/vcs-shape` | โ€” | `branch-per-project` / `branch-per-ticket` / `trunk`, recommended by `team_shape`. Invariants for every shape: one ticket = one commit bundling code + re-spec + status, always with trailers. Explicitly *not* dstack's: merge strategy, CI, review rules, release tagging. `/dstack-ticket` gains the close-out step it never had. | Status legend: `[ ]` not started ยท `[~]` in progress ยท `[x]` shipped (PR merged) ยท `[-]`