docs(fleet): tier the north star and declare the tier-0 operator surface (#1337)
ci/woodpecker/push/publish Pipeline was canceled

This commit was merged in pull request #1337.
This commit is contained in:
2026-08-20 23:05:34 +00:00
parent ca97b885b0
commit af43a7a63e
27 changed files with 307 additions and 71 deletions
@@ -35,4 +35,4 @@ A standing panel of senior voices, each arguing from a fixed vantage. The board
deliberately slow and adversarial — its value is catching the expensive mistake
before a single agent-hour is spent on it.
> Doctrine: `docs/fleet/north-star.md` ('board' role = forge BOD; role library).
> Doctrine: `docs/fleet/FLEET-DOCTRINE.md` ('board' role = forge BOD; role library).
@@ -33,4 +33,4 @@ The code role writes the change and opens the PR; it never touches the merge pat
The focused builder. It takes one well-scoped card, drives it to green, opens a
clean PR, and hands off — never reaching past the card it was given.
> Doctrine: `docs/fleet/north-star.md` (role library).
> Doctrine: `docs/fleet/FLEET-DOCTRINE.md` (role library).
@@ -35,4 +35,4 @@ The work-breakdown specialist. It takes a phased plan and a DAG and emits a clea
linked set of single-PR cards on the Mosaic backlog — then steps back and lets the
executors run.
> Doctrine: `docs/fleet/north-star.md` (role library); spend accounting is a process mandate.
> Doctrine: `docs/fleet/FLEET-DOCTRINE.md` (role library); spend accounting is a process mandate.
@@ -36,4 +36,4 @@ The scribe of record. It makes sure the docs and the north star's projections
describe the system as it actually is, and it never lets two writers fight over one
TASKS file.
> Doctrine: `docs/fleet/north-star.md` (role library).
> Doctrine: `docs/fleet/FLEET-DOCTRINE.md` (role library).
@@ -38,4 +38,4 @@ The orchestrator alone optimizes for _this_ delivery; the enhancer optimizes for
delivery — self-healing the fleet's tools, skills, and harnesses, and routing real defects
upstream. Together they are the irreducible core; every other role is added on demand.
> Doctrine: `docs/fleet/north-star.md` (two-agent floor + role library).
> Doctrine: `docs/fleet/FLEET-DOCTRINE.md` (two-agent floor + role library).
@@ -46,4 +46,4 @@ The single, accountable gatekeeper. It waits for green CI (`pr-ci-wait.sh`),
respects the pause switch, merges only through `pr-merge.sh`, and records every
decision — so the fleet has exactly one trustworthy door to production.
> Doctrine: `docs/fleet/north-star.md` (role library); merge path: `pr-merge.sh` + `pr-ci-wait.sh`; forbidden paths: `pr-merge.sh` guard.
> Doctrine: `docs/fleet/FLEET-DOCTRINE.md` (role library); merge path: `pr-merge.sh` + `pr-ci-wait.sh`; forbidden paths: `pr-merge.sh` guard.
@@ -35,4 +35,4 @@ The on-call dispatcher. It makes sure every escalation is seen and re-seen until
handled, and it holds the one switch that can stop the fleet when something is
wrong.
> Doctrine: `docs/fleet/north-star.md` (role library); pause switch: `fleet/run/PAUSED`.
> Doctrine: `docs/fleet/FLEET-DOCTRINE.md` (role library); pause switch: `fleet/run/PAUSED`.
@@ -43,4 +43,4 @@ A lean, decisive coordinator. It thinks in readiness and throughput, dispatches
next ready card the instant a dependency clears, and never lets an idle agent sit
while ready work exists — keeping its own context minimal so the loop never slows.
> Doctrine: `docs/fleet/north-star.md` (two-agent floor + role library).
> Doctrine: `docs/fleet/FLEET-DOCTRINE.md` (two-agent floor + role library).
@@ -38,4 +38,4 @@ The architect of the mission's shape. It thinks in phases and dependencies, hand
a clean DAG to decomposition, and reports its plan back to the orchestrator that
dispatched it.
> Doctrine: `docs/fleet/north-star.md` (two-agent floor + role library).
> Doctrine: `docs/fleet/FLEET-DOCTRINE.md` (two-agent floor + role library).
@@ -34,4 +34,4 @@ The rebase role keeps PR branches fresh; it never approves or merges.
The janitor of the merge queue. It quietly keeps branches current and re-runnable,
and knows when a conflict is beyond a mechanical rebase and must be escalated.
> Doctrine: `docs/fleet/north-star.md` (role library).
> Doctrine: `docs/fleet/FLEET-DOCTRINE.md` (role library).
@@ -35,4 +35,4 @@ The careful reader. It assumes nothing, checks the change against its card and i
tests, and is willing to say "not yet" — its value is catching the wrong change
before it reaches the merge-gate.
> Doctrine: `docs/fleet/north-star.md` (role library).
> Doctrine: `docs/fleet/FLEET-DOCTRINE.md` (role library).
@@ -36,4 +36,4 @@ tree or the merge path.
The adversary on your side. It reads every diff asking "how does this get exploited
or leak?" — the second, security-focused pair of eyes before the merge-gate.
> Doctrine: `docs/fleet/north-star.md` (role library); forbidden paths: `pr-merge.sh` guard.
> Doctrine: `docs/fleet/FLEET-DOCTRINE.md` (role library); forbidden paths: `pr-merge.sh` guard.
@@ -34,4 +34,4 @@ The retrospective analyst. It reads completed sessions and distills them into cl
actionable signals — the raw material the enhancer uses to make the fleet better
next time.
> Doctrine: `docs/fleet/north-star.md` (role library); consumed by the enhancer role.
> Doctrine: `docs/fleet/FLEET-DOCTRINE.md` (role library); consumed by the enhancer role.
@@ -34,4 +34,4 @@ merge path.
The skeptic who insists on running it. It trusts observed behavior over claimed
behavior, and turns "should work" into "verified works" — or a concrete bug report.
> Doctrine: `docs/fleet/north-star.md` (role library).
> Doctrine: `docs/fleet/FLEET-DOCTRINE.md` (role library).
@@ -147,11 +147,11 @@ describe('renderNorthStarMarkdown', () => {
'standing_objectives:',
' - { id: NS-1, text: t }',
'success_criteria:',
' - { id: AC-NS-1, text: t }',
' - { id: AC-NS-1, text: t, tier: 0 }',
'workstreams:',
' - { id: A, title: t }',
'goals:',
' - { id: A1, title: t, phase: 1, priority: must-have, depends_on: [] }',
' - { id: A1, title: t, phase: 1, tier: 0, priority: must-have, depends_on: [] }',
'assumptions:',
' - { id: ASM-1, vetoable: true, text: t }',
'spend:',
+24 -8
View File
@@ -191,6 +191,10 @@ export interface NorthStarIdText {
text: string;
}
export interface NorthStarCriterion extends NorthStarIdText {
tier: number;
}
export interface NorthStarWorkstream {
id: string;
title: string;
@@ -200,6 +204,8 @@ export interface NorthStarGoal {
id: string;
title: string;
phase: number;
/** Capability tier this goal serves: 0 dogfood, 1 MVP, 2 full vision. Orthogonal to phase, which is build order. */
tier: number;
priority: string;
depends_on: string[];
}
@@ -220,7 +226,7 @@ export interface NorthStar {
mission: string;
substrate: { note: string };
standing_objectives: NorthStarIdText[];
success_criteria: NorthStarIdText[];
success_criteria: NorthStarCriterion[];
workstreams: NorthStarWorkstream[];
goals: NorthStarGoal[];
assumptions: NorthStarAssumption[];
@@ -280,9 +286,13 @@ export function parseNorthStar(rawText: string): NorthStar {
standing_objectives: requireArray(parsed.standing_objectives, 'standing_objectives').map(
(row, i) => idText(row, 'standing_objectives', i),
),
success_criteria: requireArray(parsed.success_criteria, 'success_criteria').map((row, i) =>
idText(row, 'success_criteria', i),
),
success_criteria: requireArray(parsed.success_criteria, 'success_criteria').map((row, i) => {
const tier = (row as Record<string, unknown>)?.tier;
if (typeof tier !== 'number') {
throw new Error(`NORTH_STAR.yaml: success_criteria[${i}].tier must be a number.`);
}
return { ...idText(row, 'success_criteria', i), tier };
}),
workstreams: requireArray(parsed.workstreams, 'workstreams').map((row, i) => {
const ws = row as Record<string, unknown>;
return {
@@ -300,10 +310,15 @@ export function parseNorthStar(rawText: string): NorthStar {
if (typeof phase !== 'number') {
throw new Error(`NORTH_STAR.yaml: goals[${i}].phase must be a number.`);
}
const tier = goal?.tier;
if (typeof tier !== 'number') {
throw new Error(`NORTH_STAR.yaml: goals[${i}].tier must be a number.`);
}
return {
id: requireString(goal?.id, `goals[${i}].id`),
title: requireString(goal?.title, `goals[${i}].title`),
phase,
tier,
priority: requireString(goal?.priority, `goals[${i}].priority`),
depends_on: dependsRaw.map((dep, j) => requireString(dep, `goals[${i}].depends_on[${j}]`)),
};
@@ -349,7 +364,7 @@ function renderMarkdownTable(headers: string[], rows: string[][]): string[] {
* Pure function of its input — same input always yields byte-identical output,
* so the round-trip (YAML → render → write) is stable across runs. No clock, no
* network, no CLI. Layout follows the repo's existing doctrine-doc convention
* (heading, blockquote banner, then sections + tables, e.g. north-star.md /
* (heading, blockquote banner, then sections + tables, e.g. FLEET-DOCTRINE.md /
* mission-control/BOARD.md).
*/
export function renderNorthStarMarkdown(ns: NorthStar): string {
@@ -362,7 +377,7 @@ export function renderNorthStarMarkdown(ns: NorthStar): string {
'> Projected deterministically from [`NORTH_STAR.yaml`](./NORTH_STAR.yaml) by the pure',
);
lines.push('> generator in `packages/mosaic/src/commands/fleet.ts` (`renderNorthStarMarkdown`).');
lines.push('> Edit the YAML, then regenerate. Self-contained Mosaic — no Hermes dependency.');
lines.push('> Edit the YAML, then regenerate. Self-contained Mosaic.');
lines.push('');
lines.push('## Mission');
@@ -385,7 +400,7 @@ export function renderNorthStarMarkdown(ns: NorthStar): string {
lines.push('## Success criteria');
lines.push('');
for (const ac of ns.success_criteria) {
lines.push(`- **${ac.id}** — ${ac.text}`);
lines.push(`- **${ac.id}** (tier ${ac.tier})${ac.text}`);
}
lines.push('');
@@ -403,10 +418,11 @@ export function renderNorthStarMarkdown(ns: NorthStar): string {
lines.push('');
lines.push(
...renderMarkdownTable(
['id', 'title', 'phase', 'priority', 'depends_on'],
['id', 'title', 'tier', 'phase', 'priority', 'depends_on'],
ns.goals.map((goal) => [
goal.id,
goal.title,
String(goal.tier),
String(goal.phase),
goal.priority,
goal.depends_on.length > 0 ? goal.depends_on.join(', ') : '—',
@@ -922,8 +922,8 @@ describe('fleet operator documentation', (): void => {
);
expect(
surfaces.filter((surface): boolean => surface.category === 'InlineLiteral'),
).toHaveLength(863);
expect(surfaces).toHaveLength(887);
).toHaveLength(882);
expect(surfaces).toHaveLength(906);
const rosterSource = await readFile(join(fleetDocs, 'examples', 'roster-v2.yaml'), 'utf8');
const auxiliary: CodeSurface = {