From 3eb54608715e71372b9891f06905aa1f3fbf2814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D7=A0=CF=85=CE=B1=CE=B7=20=D7=A0=CF=85=CE=B1=CE=B7=D1=95?= =?UTF-8?q?=CF=83=CE=B7?= Date: Wed, 30 Sep 2026 21:48:00 -0700 Subject: [PATCH 1/3] docs: delete history, strip check-docs, voice lives in the skill history/ is gone. The knowledge is in the pages, which is what archiving it was for, and 15 frozen specs nobody maintains were 1,000 em dashes and a second place to look. The reorg had also left components/osapi-justfiles/specs/ behind, which the git mv to history missed. Gone with the rest. check-docs was 128 lines and half of it guarded against Spec Kit leaking into the docs: FR- labels, MUST, user stories, acceptance scenarios, success criteria, source footers. None of that can happen now. It is 78 lines and three checks that each pay for themselves: every relative link resolves, every page is linked from its component README, and no em dashes. It covers every markdown file somebody wrote rather than only components/, which is how 32 em dashes accumulated in two skills. The voice guidance is a reference in the document skill, where the skill that writes the docs reads it. It carries the required unslop pass and the specific tells, including the ones I kept producing: significance instead of substance, commentary about the document, aphorisms that sound like wisdom and tell you nothing to do. Three passages in the component docs fixed. osapi-justfiles announced its own honesty before saying six words. osapi-orchestrator argued with the codebase's vocabulary for five sentences before defining either term. ARCHITECTURE.md had a heading about where facts are filed rather than about the system. --- .claude/skills/add-a-domain/SKILL.md | 16 +- .../skills/add-a-domain/references/agent.md | 8 +- .claude/skills/add-a-domain/references/api.md | 12 +- .claude/skills/add-a-domain/references/cli.md | 4 +- .../skills/add-a-domain/references/docs.md | 2 +- .claude/skills/add-a-domain/references/sdk.md | 2 +- .claude/skills/document/references/voice.md | 106 +-- .../skills/org-status/references/issues.md | 2 +- .../skills/org-status/references/merging.md | 6 +- .../org-status/references/pull-requests.md | 8 +- .../skills/org-status/references/quality.md | 2 +- ARCHITECTURE.md | 6 +- CONSTITUTION.md | 2 +- CONTRIBUTING.md | 4 +- components/osapi-justfiles/README.md | 16 +- .../checklists/requirements.md | 53 -- .../specs/001-justfiles-baseline/plan.md | 210 ----- .../specs/001-justfiles-baseline/spec.md | 593 ------------- .../specs/001-justfiles-baseline/tasks.md | 295 ------- components/osapi-orchestrator/README.md | 11 +- .../checklists/requirements.md | 78 -- history/gohai-001-gohai-baseline/plan.md | 122 --- history/gohai-001-gohai-baseline/spec.md | 488 ----------- .../checklists/requirements.md | 56 -- .../data-model.md | 78 -- .../gohai-002-move-contributor-docs/plan.md | 188 ----- .../research.md | 112 --- .../gohai-002-move-contributor-docs/spec.md | 305 ------- .../checklists/requirements.md | 48 -- .../plan.md | 150 ---- .../spec.md | 391 --------- .../tasks.md | 186 ----- .../checklists/requirements.md | 47 -- .../plan.md | 152 ---- .../spec.md | 396 --------- .../tasks.md | 208 ----- .../checklists/requirements.md | 67 -- history/osapi-001-provider-contract/plan.md | 104 --- history/osapi-001-provider-contract/spec.md | 248 ------ .../checklists/requirements.md | 67 -- .../contracts/key-store.md | 72 -- .../osapi-002-agent-key-store/data-model.md | 89 -- history/osapi-002-agent-key-store/plan.md | 119 --- .../osapi-002-agent-key-store/quickstart.md | 96 --- history/osapi-002-agent-key-store/research.md | 125 --- history/osapi-002-agent-key-store/spec.md | 247 ------ history/osapi-002-agent-key-store/tasks.md | 301 ------- .../checklists/requirements.md | 49 -- .../contracts/citation.md | 65 -- .../osapi-003-corpus-backfill/data-model.md | 68 -- history/osapi-003-corpus-backfill/plan.md | 129 --- .../osapi-003-corpus-backfill/quickstart.md | 128 --- history/osapi-003-corpus-backfill/research.md | 168 ---- history/osapi-003-corpus-backfill/spec.md | 244 ------ history/osapi-003-corpus-backfill/tasks.md | 372 --------- .../checklists/requirements.md | 56 -- history/osapi-004-job-system/data-model.md | 64 -- history/osapi-004-job-system/plan.md | 105 --- history/osapi-004-job-system/quickstart.md | 111 --- history/osapi-004-job-system/research.md | 78 -- history/osapi-004-job-system/spec.md | 319 ------- history/osapi-004-job-system/tasks.md | 225 ----- .../checklists/requirements.md | 79 -- .../contracts/walkthrough.md | 59 -- .../osapi-005-building-a-domain/data-model.md | 177 ---- history/osapi-005-building-a-domain/plan.md | 164 ---- .../osapi-005-building-a-domain/quickstart.md | 120 --- .../osapi-005-building-a-domain/research.md | 170 ---- history/osapi-005-building-a-domain/spec.md | 532 ------------ history/osapi-005-building-a-domain/tasks.md | 460 ---------- .../checklists/requirements.md | 116 --- history/osapi-006-osapi-baseline/plan.md | 163 ---- history/osapi-006-osapi-baseline/spec.md | 358 -------- history/osapi-006-osapi-baseline/tasks.md | 170 ---- .../checklists/requirements.md | 81 -- .../osapi-007-the-embedded-ui/data-model.md | 96 --- history/osapi-007-the-embedded-ui/plan.md | 146 ---- .../osapi-007-the-embedded-ui/quickstart.md | 122 --- history/osapi-007-the-embedded-ui/research.md | 113 --- history/osapi-007-the-embedded-ui/spec.md | 311 ------- history/osapi-007-the-embedded-ui/tasks.md | 320 ------- .../checklists/requirements.md | 44 - .../plan.md | 93 --- .../spec.md | 313 ------- .../tasks.md | 108 --- .../checklists/requirements.md | 46 - .../system-001-repository-inventory/plan.md | 82 -- .../system-001-repository-inventory/spec.md | 139 ---- .../system-001-repository-inventory/tasks.md | 98 --- .../checklists/requirements.md | 119 --- .../contracts/section-order.md | 97 --- .../system-002-baseline-shape/data-model.md | 119 --- history/system-002-baseline-shape/plan.md | 173 ---- .../system-002-baseline-shape/quickstart.md | 138 --- history/system-002-baseline-shape/research.md | 181 ---- history/system-002-baseline-shape/spec.md | 785 ------------------ history/system-002-baseline-shape/tasks.md | 561 ------------- .../checklists/requirements.md | 61 -- .../data-model.md | 71 -- history/system-003-rule-and-reasoning/plan.md | 181 ---- .../system-003-rule-and-reasoning/research.md | 82 -- history/system-003-rule-and-reasoning/spec.md | 364 -------- .../system-003-rule-and-reasoning/tasks.md | 264 ------ justfile | 2 +- scripts/check-docs.py | 116 +-- 105 files changed, 137 insertions(+), 15936 deletions(-) delete mode 100644 components/osapi-justfiles/specs/001-justfiles-baseline/checklists/requirements.md delete mode 100644 components/osapi-justfiles/specs/001-justfiles-baseline/plan.md delete mode 100644 components/osapi-justfiles/specs/001-justfiles-baseline/spec.md delete mode 100644 components/osapi-justfiles/specs/001-justfiles-baseline/tasks.md delete mode 100644 history/gohai-001-gohai-baseline/checklists/requirements.md delete mode 100644 history/gohai-001-gohai-baseline/plan.md delete mode 100644 history/gohai-001-gohai-baseline/spec.md delete mode 100644 history/gohai-002-move-contributor-docs/checklists/requirements.md delete mode 100644 history/gohai-002-move-contributor-docs/data-model.md delete mode 100644 history/gohai-002-move-contributor-docs/plan.md delete mode 100644 history/gohai-002-move-contributor-docs/research.md delete mode 100644 history/gohai-002-move-contributor-docs/spec.md delete mode 100644 history/nats-client-001-nats-client-baseline/checklists/requirements.md delete mode 100644 history/nats-client-001-nats-client-baseline/plan.md delete mode 100644 history/nats-client-001-nats-client-baseline/spec.md delete mode 100644 history/nats-client-001-nats-client-baseline/tasks.md delete mode 100644 history/nats-server-001-nats-server-baseline/checklists/requirements.md delete mode 100644 history/nats-server-001-nats-server-baseline/plan.md delete mode 100644 history/nats-server-001-nats-server-baseline/spec.md delete mode 100644 history/nats-server-001-nats-server-baseline/tasks.md delete mode 100644 history/osapi-001-provider-contract/checklists/requirements.md delete mode 100644 history/osapi-001-provider-contract/plan.md delete mode 100644 history/osapi-001-provider-contract/spec.md delete mode 100644 history/osapi-002-agent-key-store/checklists/requirements.md delete mode 100644 history/osapi-002-agent-key-store/contracts/key-store.md delete mode 100644 history/osapi-002-agent-key-store/data-model.md delete mode 100644 history/osapi-002-agent-key-store/plan.md delete mode 100644 history/osapi-002-agent-key-store/quickstart.md delete mode 100644 history/osapi-002-agent-key-store/research.md delete mode 100644 history/osapi-002-agent-key-store/spec.md delete mode 100644 history/osapi-002-agent-key-store/tasks.md delete mode 100644 history/osapi-003-corpus-backfill/checklists/requirements.md delete mode 100644 history/osapi-003-corpus-backfill/contracts/citation.md delete mode 100644 history/osapi-003-corpus-backfill/data-model.md delete mode 100644 history/osapi-003-corpus-backfill/plan.md delete mode 100644 history/osapi-003-corpus-backfill/quickstart.md delete mode 100644 history/osapi-003-corpus-backfill/research.md delete mode 100644 history/osapi-003-corpus-backfill/spec.md delete mode 100644 history/osapi-003-corpus-backfill/tasks.md delete mode 100644 history/osapi-004-job-system/checklists/requirements.md delete mode 100644 history/osapi-004-job-system/data-model.md delete mode 100644 history/osapi-004-job-system/plan.md delete mode 100644 history/osapi-004-job-system/quickstart.md delete mode 100644 history/osapi-004-job-system/research.md delete mode 100644 history/osapi-004-job-system/spec.md delete mode 100644 history/osapi-004-job-system/tasks.md delete mode 100644 history/osapi-005-building-a-domain/checklists/requirements.md delete mode 100644 history/osapi-005-building-a-domain/contracts/walkthrough.md delete mode 100644 history/osapi-005-building-a-domain/data-model.md delete mode 100644 history/osapi-005-building-a-domain/plan.md delete mode 100644 history/osapi-005-building-a-domain/quickstart.md delete mode 100644 history/osapi-005-building-a-domain/research.md delete mode 100644 history/osapi-005-building-a-domain/spec.md delete mode 100644 history/osapi-005-building-a-domain/tasks.md delete mode 100644 history/osapi-006-osapi-baseline/checklists/requirements.md delete mode 100644 history/osapi-006-osapi-baseline/plan.md delete mode 100644 history/osapi-006-osapi-baseline/spec.md delete mode 100644 history/osapi-006-osapi-baseline/tasks.md delete mode 100644 history/osapi-007-the-embedded-ui/checklists/requirements.md delete mode 100644 history/osapi-007-the-embedded-ui/data-model.md delete mode 100644 history/osapi-007-the-embedded-ui/plan.md delete mode 100644 history/osapi-007-the-embedded-ui/quickstart.md delete mode 100644 history/osapi-007-the-embedded-ui/research.md delete mode 100644 history/osapi-007-the-embedded-ui/spec.md delete mode 100644 history/osapi-007-the-embedded-ui/tasks.md delete mode 100644 history/osapi-orchestrator-001-orchestrator-baseline/checklists/requirements.md delete mode 100644 history/osapi-orchestrator-001-orchestrator-baseline/plan.md delete mode 100644 history/osapi-orchestrator-001-orchestrator-baseline/spec.md delete mode 100644 history/osapi-orchestrator-001-orchestrator-baseline/tasks.md delete mode 100644 history/system-001-repository-inventory/checklists/requirements.md delete mode 100644 history/system-001-repository-inventory/plan.md delete mode 100644 history/system-001-repository-inventory/spec.md delete mode 100644 history/system-001-repository-inventory/tasks.md delete mode 100644 history/system-002-baseline-shape/checklists/requirements.md delete mode 100644 history/system-002-baseline-shape/contracts/section-order.md delete mode 100644 history/system-002-baseline-shape/data-model.md delete mode 100644 history/system-002-baseline-shape/plan.md delete mode 100644 history/system-002-baseline-shape/quickstart.md delete mode 100644 history/system-002-baseline-shape/research.md delete mode 100644 history/system-002-baseline-shape/spec.md delete mode 100644 history/system-002-baseline-shape/tasks.md delete mode 100644 history/system-003-rule-and-reasoning/checklists/requirements.md delete mode 100644 history/system-003-rule-and-reasoning/data-model.md delete mode 100644 history/system-003-rule-and-reasoning/plan.md delete mode 100644 history/system-003-rule-and-reasoning/research.md delete mode 100644 history/system-003-rule-and-reasoning/spec.md delete mode 100644 history/system-003-rule-and-reasoning/tasks.md diff --git a/.claude/skills/add-a-domain/SKILL.md b/.claude/skills/add-a-domain/SKILL.md index 3e48571..980082b 100644 --- a/.claude/skills/add-a-domain/SKILL.md +++ b/.claude/skills/add-a-domain/SKILL.md @@ -1,6 +1,6 @@ --- name: add-a-domain -description: Add or extend an osapi API domain — a provider plus every layer it must appear in. Covers the provider implementation and its platform variants, agent processor and registry wiring, the OpenAPI spec and validation tags, the Echo handler with broadcast targeting, handler registration and startup wiring, the SDK service, CLI commands, and the docs and permission tables a new domain must be added to. Use when asked to add a domain, add a provider, add an operation or endpoint to an existing domain, wire a provider into the agent, add an SDK service, add CLI commands for a domain, or when asked what a new domain has to touch, why a domain feels half-finished, or which layer is missing. Also use when reviewing a domain for cross-layer consistency against an existing one. +description: Add or extend an osapi API domain, a provider plus every layer it must appear in. Covers the provider implementation and its platform variants, agent processor and registry wiring, the OpenAPI spec and validation tags, the Echo handler with broadcast targeting, handler registration and startup wiring, the SDK service, CLI commands, and the docs and permission tables a new domain must be added to. Use when asked to add a domain, add a provider, add an operation or endpoint to an existing domain, wire a provider into the agent, add an SDK service, add CLI commands for a domain, or when asked what a new domain has to touch, why a domain feels half-finished, or which layer is missing. Also use when reviewing a domain for cross-layer consistency against an existing one. compatibility: Requires an osapi checkout with mise and just available. Commands run through `mise exec -- just`. license: MIT metadata: @@ -46,14 +46,14 @@ Never copy a domain's files wholesale. Read one, then write the new one. The layers depend on each other in one direction. Going out of order means regenerating or rewriting. -1. **Provider** — the operations, with its own tests passing. -2. **Agent** — processor and registry, so a job reaches the provider. -3. **OpenAPI spec** — then `just generate`, which produces the server, the +1. **Provider**, the operations, with its own tests passing. +2. **Agent**, processor and registry, so a job reaches the provider. +3. **OpenAPI spec**, then `just generate`, which produces the server, the combined spec, and the SDK's generated client together. -4. **Handler** — with validation and broadcast, then registration and startup. -5. **SDK service** — wrapping the generated client. -6. **CLI** — wrapping the SDK. -7. **Docs and tables** — feature page, CLI pages, permissions, navbars. +4. **Handler**, with validation and broadcast, then registration and startup. +5. **SDK service**, wrapping the generated client. +6. **CLI**, wrapping the SDK. +7. **Docs and tables**, feature page, CLI pages, permissions, navbars. A new permission is decided at step 3 and lands in step 7. Write it down when you choose it; it is the thing most often missed. diff --git a/.claude/skills/add-a-domain/references/agent.md b/.claude/skills/add-a-domain/references/agent.md index 8d075b1..b296a34 100644 --- a/.claude/skills/add-a-domain/references/agent.md +++ b/.claude/skills/add-a-domain/references/agent.md @@ -7,7 +7,7 @@ dispatch and facts wiring. | What you need to know | Where | | --- | --- | -| Two files connect a provider, and what does **not** change — `agent/types.go`, `agent/agent.go`, the `JobClient` interface | [FR-009](../../../../components/osapi/domains.md) | +| Two files connect a provider, and what does **not** change: `agent/types.go`, `agent/agent.go`, the `JobClient` interface | [FR-009](../../../../components/osapi/domains.md) | | The `FactsAware` obligation: embed it, add the compile-time `FactsSetter` check | [FR-010](../../../../components/osapi/domains.md) | This file holds the shapes and the file names. The rules above are stated once, @@ -104,7 +104,7 @@ in full. What matters when adding an operation, and where each rule is stated: | --- | --- | | Delivery is at-least-once, and the agent checks for a recorded response before executing | [FR-009](../../../../components/osapi/job-system.md) | | Which operations make that check load-bearing rather than theoretical | [FR-010](../../../../components/osapi/job-system.md) | -| A job that has run is terminal — a failure is reported, not retried by redelivery | [FR-011](../../../../components/osapi/job-system.md) | +| A job that has run is terminal, a failure is reported, not retried by redelivery | [FR-011](../../../../components/osapi/job-system.md) | | What happens when the response cannot be written after the work ran | [FR-012](../../../../components/osapi/job-system.md) | | Which failures terminate a message instead of redelivering it | [FR-013](../../../../components/osapi/job-system.md) | | The consumer's delivery settings, as defaults a deployment may override | [FR-014](../../../../components/osapi/job-system.md) | @@ -113,14 +113,14 @@ in full. What matters when adding an operation, and where each rule is stated: Two consequences for a new operation, which are yours rather than the system's: - **Redelivery is not your safety net.** The provider's idempotency is what makes a - repeat safe — the contract's own requirement, not this one. + repeat safe, the contract's own requirement, not this one. - **`job retry` creates a new job** rather than replaying the old message, so nothing per-domain handles it. ## Facts `provider.WireProviderFacts(a.GetFacts, registry.AllProviders()...)` injects -facts into every registered provider — one call, in `internal/agent/agent.go`. A +facts into every registered provider, one call, in `internal/agent/agent.go`. A provider registered through the registry is covered; one constructed and passed somewhere else is not. The obligation on the provider struct itself is [FR-010](../../../../components/osapi/domains.md), and what a provider does with facts is diff --git a/.claude/skills/add-a-domain/references/api.md b/.claude/skills/add-a-domain/references/api.md index ac9ae14..b425860 100644 --- a/.claude/skills/add-a-domain/references/api.md +++ b/.claude/skills/add-a-domain/references/api.md @@ -15,7 +15,7 @@ does not: where the files go, what they are called, and the scaffolding to start from. The split is deliberate. A rule restated here would drift from the one in the -corpus, and the copy an agent happened to load would win — which is the failure +corpus, and the copy an agent happened to load would win, which is the failure [003-corpus-backfill](../../../../CONSTITUTION.md) exists to end. @@ -25,7 +25,7 @@ exists to end. | Path parameters are the trap, and what actually validates one | FR-012 | | Separate verbs for create and update, and why a combined upsert is forbidden | FR-013 | | The six API design guidelines, including path versus query parameters | FR-014 | -| What `{hostname}` accepts — a literal, `_any`, `_all`, a label selector | FR-015 | +| What `{hostname}` accepts, a literal, `_any`, `_all`, a label selector | FR-015 | | Broadcast is mandatory, and both paths return the same collection shape | FR-016 | | The job client has four methods, and an operation adds none | FR-017 | | `Handler()` returns route closures, and the `Server` struct does not change | FR-018 | @@ -90,7 +90,7 @@ handlers = append(handlers, Stated here because they are real and nothing else states them. Both belong in the corpus and neither is there, which is recorded rather than left to be -discovered — the same treatment FR-019 gives the absent `sdk-standards` +discovered, the same treatment FR-019 gives the absent `sdk-standards` capability. **A custom validation rule is a registered validator.** It belongs in @@ -106,7 +106,7 @@ corpus does not say is that a domain must not write its own target parser. that differ in how much damage they can do want two permissions however similar their shape. A new one must be added to the built-in role expansion, the permission constants, the SDK, and the roles tables in -`features/authentication.md` and `usage/configuration.md` — see +`features/authentication.md` and `usage/configuration.md`, see [docs.md](docs.md). A permission that exists in the spec but in no role reaches nobody. @@ -115,9 +115,9 @@ nobody. Testing conventions are osapi's `CONTRIBUTING.md`, under "Testing". What this layer adds to a public suite: -- `TestXxxHTTP` — raw HTTP through the full Echo middleware stack: valid input +- `TestXxxHTTP`, raw HTTP through the full Echo middleware stack: valid input succeeds, invalid input returns 400 with the message. -- `TestXxxRBACHTTP` — no token is 401, a token without the permission is 403, a +- `TestXxxRBACHTTP`, no token is 401, a token without the permission is 403, a token with it succeeds. Cover validation failure, success, a provider error surfaced from the job, and diff --git a/.claude/skills/add-a-domain/references/cli.md b/.claude/skills/add-a-domain/references/cli.md index ff62380..2ee3d99 100644 --- a/.claude/skills/add-a-domain/references/cli.md +++ b/.claude/skills/add-a-domain/references/cli.md @@ -14,7 +14,7 @@ The obligations this layer carries are stated in | `--json` on every command, and flags rather than positional arguments for IDs | FR-021 | | `cli.PrintKV` for a single resource, `cli.PrintCompactTable` for rows | FR-021 | | Every response code the spec declares handled in the status switch | FR-021 | -| What `--target` accepts — a literal, `_any`, `_all`, a label selector | FR-015 | +| What `--target` accepts, a literal, `_any`, `_all`, a label selector | FR-015 | | What verifies a finished domain, and what Step 8 alone misses | FR-024 | ## Files @@ -49,7 +49,7 @@ operators read these side by side. ## Three rules the corpus does not yet hold -Stated here because they are real and nothing else states them — not the corpus, +Stated here because they are real and nothing else states them, not the corpus, not osapi's `CONTRIBUTING.md`. Recorded as unstated rather than left to be discovered, the same treatment FR-019 gives the absent `sdk-standards` capability. diff --git a/.claude/skills/add-a-domain/references/docs.md b/.claude/skills/add-a-domain/references/docs.md index c8237b9..d47f512 100644 --- a/.claude/skills/add-a-domain/references/docs.md +++ b/.claude/skills/add-a-domain/references/docs.md @@ -1,7 +1,7 @@ # Docs and tables The Docusaurus site is user-facing: what a feature does, how to call it, what the -SDK exposes. Development guidance is not there — it is in the corpus, and this +SDK exposes. Development guidance is not there, it is in the corpus, and this skill cites it. A domain that works but appears in none of these is invisible to everyone who did diff --git a/.claude/skills/add-a-domain/references/sdk.md b/.claude/skills/add-a-domain/references/sdk.md index 531e699..b2ba49d 100644 --- a/.claude/skills/add-a-domain/references/sdk.md +++ b/.claude/skills/add-a-domain/references/sdk.md @@ -16,7 +16,7 @@ binding rules were "the `sdk-standards` capability in this repository", that the bound `osapi-orchestrator` too, and that the capability won any disagreement. Nothing of the sort has been written. The claim was also on osapi's `adding-an-api-domain.md`, so two documents deferred to a specification that reads -as settled and does not exist — recorded as +as settled and does not exist, recorded as [FR-019](../../../../components/osapi/domains.md)'s gap rather than repeated here. What that means in practice: the conventions below and in FR-019 and FR-020 are diff --git a/.claude/skills/document/references/voice.md b/.claude/skills/document/references/voice.md index c3152b7..443ac62 100644 --- a/.claude/skills/document/references/voice.md +++ b/.claude/skills/document/references/voice.md @@ -1,51 +1,54 @@ # The voice -An engineer explaining a system to another engineer who has to work on it. +How every document in this repository is written, and every skill that writes +one. Cited rather than restated: if you are about to paraphrase this into a +skill or a page, link here instead. -The test: **would a senior engineer write this in an internal design doc?** If it -reads like a paper, rewrite it. If it reads like documentation written for -beginners, rewrite it. If it reads like something trying to sound technical, -rewrite it. +A plain engineering voice. Something engineers would want to read and maintain, +not an academic paper, not marketing, not AI prose. + +**The engineering is not simplified. The language is.** -The guidelines, short form: +## Writing style -- Simple direct language. Normal engineering terms, not fancier synonyms. -- Do not oversimplify. The reader should still get the real tradeoffs, - constraints and architecture. +- Simple, direct language over jargon. Use the normal engineering term. +- Do **not** oversimplify the technical detail. Keep the architecture, + constraints, tradeoffs and reasoning intact. - Concrete: what it does, why it exists, how it works, what the tradeoffs are. - The reader is an experienced engineer who does not know this system. -- No buzzwords unless they earn their place. -- No padding with obvious statements or generic best practice. -- Not every idea as a numbered list. Prose, diagrams, tables, short lists, where - each is clearest. -- Precise without being formal for its own sake. -- State opinions and decisions. Name the tradeoff and the side you took. -- Concrete examples over explanations of terminology. +- No buzzwords: leverage, robust, seamless, scalable, paradigm, orchestration as + a synonym for running things, surface as a synonym for API. +- No padding with generic statements or obvious best practice. +- Not everything as a numbered list. Prose, diagrams, tables and short lists, + whichever makes it clearer. +- Technically precise without being formal for its own sake. +- State the decision. Where there is a tradeoff, say what it is and which side + you took. +- Concrete examples over explaining terminology. - Tight. Every paragraph explains the system, justifies a decision, or clarifies a tradeoff. -## What to do +The test: -**Plain words.** Use the normal engineering term. "Use" not "leverage", "help" -not "facilitate", "is" not "serves as". If a fancier synonym is clearer, use it; -it rarely is. +> Would a senior engineer actually write this in an internal design doc? + +If it reads like an academic paper, rewrite it. If it reads like documentation +for beginners, rewrite it. If it reads like something trying to sound technical, +rewrite it. -**Concrete over abstract.** Name the file, the function, the number. "The -controller waits 30 seconds, set by `controller.api.job_timeout`" beats "the -controller has a configurable timeout". +## The unslop pass is required -**Say the tradeoff.** Where there was a real choice, say what it cost. "Two -buckets rather than one means a reader of a result does not walk the status -history; it also means the result's TTL is a separate setting nobody remembers to -set." A decision with no cost stated reads as if there was nothing to decide. +After writing, run the whole document through `unslop`. It is an editing pass, +not a suggestion. -**Keep the real detail.** Simplifying until the tradeoffs disappear is worse than -being dense. The reader is experienced; they are not familiar with this system. +It removes AI phrasing, verbosity, corporate and academic language, repeated +explanations, fake transitions, filler, excessive headings and bullets, jargon, +and prose that is too polished to be natural. It keeps the technical meaning. -**Lead with what will bite them.** A rule with a silent failure mode is worth -more than three paragraphs about structure. +Then read the result again and fix anything it bent out of shape. **Do not let a +shorter document lose technical information.** -## What to avoid +## The specific tells **Significance instead of substance.** Do not write "that indirection is the whole reason the API cannot do the work, and everything below follows from it". @@ -56,40 +59,37 @@ consequences is useful; asserting that there are consequences is not. **Commentary about the document.** "This section is short because the subsystem is thin" tells the reader nothing about the system. Cut it. -**Buzzwords.** leverage, robust, seamless, scalable, paradigm, holistic, -orchestration as a synonym for "running things", surface as a synonym for "API". - **Em dashes.** Use a comma or end the sentence. `just check-docs` fails on them. **Bold labels that restate the line after them.** "**Performance:** performance improved by..." A bold lead-in that names a thing and is followed by new detail is fine. -**Everything as a numbered list.** Use prose where the ideas connect, a table -where the data is tabular, a list where the items are genuinely parallel. Three -nested lists in a row means the structure is doing the thinking. - -**Padding.** Every paragraph explains the system, justifies a decision, or -clarifies a tradeoff. If it does none of those, delete it. - **Hedged numbers.** "roughly 108 fields" invites nobody to check it, and a count -in this repository stayed wrong for weeks behind a tilde. State the number and -the command. +here stayed wrong for weeks behind a tilde. State the number and the command. + +**Aphorisms.** "Prose is a lead; the code is the source" sounds like wisdom and +tells you nothing to do. Write the instruction: read the code, then run +something that would fail if you were wrong. ## Worked example Weak: -> The job system leverages a robust queuing paradigm to facilitate scalable -> execution across the fleet. This architectural decision underscores the -> system's commitment to reliability. +``` +The job system leverages a robust queuing paradigm to facilitate scalable +execution across the fleet. This architectural decision underscores the system's +commitment to reliability. +``` Better: -> Work reaches a host by being queued, not by being called. The controller writes -> a job, announces it, and waits; an agent picks it up and writes a response -> back. Everything awkward about the system comes out of that split: -> at-least-once delivery, the idempotency providers owe, two independent -> timeouts, and a per-host result instead of one answer. +``` +Work reaches a host by being queued, not by being called. The controller writes a +job, announces it, and waits; an agent picks it up and writes a response back. +Everything awkward about the system comes out of that split: at-least-once +delivery, the idempotency providers owe, two independent timeouts, and a per-host +result instead of one answer. +``` -The second one is shorter, names the mechanism, and tells you what to expect. +Shorter, names the mechanism, and tells you what to expect. diff --git a/.claude/skills/org-status/references/issues.md b/.claude/skills/org-status/references/issues.md index 9888671..88cb94a 100644 --- a/.claude/skills/org-status/references/issues.md +++ b/.claude/skills/org-status/references/issues.md @@ -46,7 +46,7 @@ gh pr list --repo "osapi-io/$r" --state open \ An issue named there is in flight, and belongs in the pull request block rather than in a block of its own. An issue whose fix has already merged is still open -only because nobody closed it — say so, because it reads as outstanding work. +only because nobody closed it, say so, because it reads as outstanding work. ## Trackers diff --git a/.claude/skills/org-status/references/merging.md b/.claude/skills/org-status/references/merging.md index 3797ddf..b7be061 100644 --- a/.claude/skills/org-status/references/merging.md +++ b/.claude/skills/org-status/references/merging.md @@ -46,7 +46,7 @@ repository often are not. Two bumps that touch the same file cannot both be merged from the state they were built in. The first merge moves the default branch and the second is now -based on something that no longer exists, so it conflicts — or worse, merges +based on something that no longer exists, so it conflicts, or worse, merges cleanly and drops the first one's edit. `mergeable` says `CLEAN` for both right up until the first one lands, which is what makes this easy to get wrong. @@ -56,9 +56,9 @@ So decide by the files, not by the colour of the tick: gh pr view --repo "osapi-io/$r" --json files -q '.files[].path' ``` -- **No overlap** — merge them together. Four bumps each touching a different +- **No overlap**, merge them together. Four bumps each touching a different `examples//go.mod` do not interact. -- **Overlap** — serialize, one merge at a time: +- **Overlap**, serialize, one merge at a time: 1. merge the first 2. `@dependabot rebase` the next, and wait for its checks 3. merge it, and repeat diff --git a/.claude/skills/org-status/references/pull-requests.md b/.claude/skills/org-status/references/pull-requests.md index e46112b..6ded085 100644 --- a/.claude/skills/org-status/references/pull-requests.md +++ b/.claude/skills/org-status/references/pull-requests.md @@ -35,14 +35,14 @@ gh pr list --repo "osapi-io/$r" --state open \ ## Fields worth reporting -- `isDraft` — a draft is not waiting on review. Say so rather than counting it +- `isDraft`, a draft is not waiting on review. Say so rather than counting it as pending. -- `mergeable` — `CONFLICTING` means it needs a rebase before anything else. +- `mergeable`: `CONFLICTING` means it needs a rebase before anything else. `UNKNOWN` means GitHub is still computing it, so re-query rather than reporting it as a problem. -- `reviewDecision` — empty string means no review has been requested or given. +- `reviewDecision`, empty string means no review has been requested or given. `APPROVED` means it is ready to merge. -- `createdAt` — sort oldest first. Age is the signal. +- `createdAt`, sort oldest first. Age is the signal. ## Checks on a PR diff --git a/.claude/skills/org-status/references/quality.md b/.claude/skills/org-status/references/quality.md index 46485dd..34d922c 100644 --- a/.claude/skills/org-status/references/quality.md +++ b/.claude/skills/org-status/references/quality.md @@ -103,7 +103,7 @@ go directive: the fix is the same command in each, and a new linter release puts all of them behind at once. A stale pin is not cosmetic. A linter predating the toolchain crashes rather -than reporting findings — v2.12.2 died inside `buildir` under Go 1.27 — and the +than reporting findings, v2.12.2 died inside `buildir` under Go 1.27, and the crash arrives as a red build on an unrelated pull request. This drift was invisible for as long as it existed, because the shared `deps` diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index b29fffe..2cb85ab 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -93,10 +93,10 @@ used, and `.just/` is gitignored everywhere. organization.** The specs repository, whose `just test` gates every corpus change, is downstream of it. -## What no single repository states +## Facts that span repositories -Four facts live between repositories and belong here because no component's -memory owns them. +Four of them. Each is true of a pair or of the whole set, so no single +repository's page is the right home. **The dependency graph has one hub and one terminal.** osapi is the only repository with edges in both directions. osapi-orchestrator has an incoming Go diff --git a/CONSTITUTION.md b/CONSTITUTION.md index 9dcdef4..19775f4 100644 --- a/CONSTITUTION.md +++ b/CONSTITUTION.md @@ -148,7 +148,7 @@ system, and the formalism that serves the first reader obstructs the second. A heading names the thing, with its path where a path helps. A statement is made in the present tense and stated once. A design decision carries its reason, in a -sentence, the first time it appears — why the liveness probe checks nothing +sentence, the first time it appears: why the liveness probe checks nothing belongs beside the liveness probe. What never appears is commentary about the document: how a fact was found, that a fact is important, which requirement obliged it, or what an earlier version said. A reader who wants that reads the diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 24e9bb2..3c3194c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,8 +45,8 @@ Read [components/osapi/job-system.md](components/osapi/job-system.md) for the shape. The rules are in [CONSTITUTION.md](CONSTITUTION.md); the ones you will hit immediately: -- Explain the system to somebody who has to work on it. No requirement - identifiers, no "MUST", no user stories. +- Explain the system to somebody who has to work on it. `/document` carries the + voice and runs `unslop`; use it rather than writing a page by hand. - Every count carries the command that produces it, and the command has to work on its own. - State a fact once. Link to it from anywhere else that needs it. diff --git a/components/osapi-justfiles/README.md b/components/osapi-justfiles/README.md index 7becef2..2e035b8 100644 --- a/components/osapi-justfiles/README.md +++ b/components/osapi-justfiles/README.md @@ -138,15 +138,13 @@ other means they must. Nineteen are declared in their module's header block; ## What a consumer may depend on -38 recipe names and 20 variable names. That is a contract by every test that -matters: a consumer depends on it, renaming part of it breaks them at their next -fetch, and nothing in the repository declares it. - -**What it is worth, stated as what is true rather than as what would be -reasonable: the contract is whatever `main` holds.** No release, no tag, no -version number, no deprecation path. A recipe renamed on `main` is renamed for -every consumer at their next `just fetch`. A consumer may depend on the names -above being what `main` holds today and on nothing about tomorrow. +38 recipe names and 20 variable names, which consumers depend on and nothing in +the repository declares. + +**The contract is whatever `main` holds.** No release, no tag, no version +number, no deprecation path. A recipe renamed on `main` is renamed for every +consumer at their next `just fetch`, so you can depend on the names above being +what `main` holds today and on nothing about tomorrow. Each module pins the tools it invokes while nothing pins the module. `md` pins mdformat 1.0.0, mdformat-gfm 1.0.0 and Python 3.13; `go` pins a coverage target diff --git a/components/osapi-justfiles/specs/001-justfiles-baseline/checklists/requirements.md b/components/osapi-justfiles/specs/001-justfiles-baseline/checklists/requirements.md deleted file mode 100644 index b498cd6..0000000 --- a/components/osapi-justfiles/specs/001-justfiles-baseline/checklists/requirements.md +++ /dev/null @@ -1,53 +0,0 @@ -# Specification Quality Checklist: A baseline for osapi-justfiles - -**Purpose**: Validate specification completeness and quality before proceeding -to planning - -**Created**: 2026-09-29 - -**Feature**: [spec.md](../spec.md) - -## Content Quality - -- [ ] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [x] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [ ] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [ ] No implementation details leak into specification - -## Notes - -Three items fail **by design**, and the same reason covers all three. - -This is a baseline. Its subject is what a repository already contains, so file -paths, recipe names, variable names and counts *are* its content — and the -constitution's Verification principle requires a claim about the codebase to be -measured rather than inspected, which means every requirement carries the -command that measures it. A baseline that passed the no-implementation-details -item would be a baseline that described nothing. - -Every feature in this repository has failed these items for the same reason, and -gohai's baseline recorded it the same way. The failure is recorded rather than -waived so that a reader can tell a deliberate departure from an oversight. - -The remaining items pass. One is worth naming because it nearly did not: **Scope -is clearly bounded** holds only because "what this inventory excludes" is a -required section of the shape. Without FR-021 the boundary would have been -implicit, which for an inventory means absent. diff --git a/components/osapi-justfiles/specs/001-justfiles-baseline/plan.md b/components/osapi-justfiles/specs/001-justfiles-baseline/plan.md deleted file mode 100644 index a0816cb..0000000 --- a/components/osapi-justfiles/specs/001-justfiles-baseline/plan.md +++ /dev/null @@ -1,210 +0,0 @@ -# Implementation Plan: A baseline for osapi-justfiles - -**Branch**: `001-justfiles-baseline` | **Date**: 2026-09-29 | **Spec**: -[spec.md](spec.md) - -**Input**: Feature specification from -`components/osapi-justfiles/specs/001-justfiles-baseline/spec.md` - -## Summary - -State what `osapi-justfiles` is, so its memory stops holding only a constitution -composed from `.charter/`. Twenty-two requirements, every one of the form "the -corpus MUST state X". - -**Partly retrospective, and it says which parts.** The inventory was produced by -reading the repository and then stated, so the measuring is behind us. What is -genuinely ahead is the verification — the commands in FR-016 have to reproduce -their figures against a fresh checkout, and that is what `tasks.md` carries. -[gohai's baseline plan](../../../gohai/specs/001-gohai-baseline/plan.md) made -the same admission about itself and had no task list at all; this one has one, -because a baseline whose counts are never re-run is a baseline nobody checked. - -One thing in it is not retrospective and belongs in a plan rather than a -specification: **what to do when a required section's word does not fit the -repository.** Section 4 of the shape is "the contract", and the word reads as -though it presumes exported symbols. The decision, and the reasoning behind it, -is below under "The one interpretive decision". - -**Nothing lands in the `osapi-justfiles` repository.** That is what -CONTRIBUTING's "Seeding a component" requires of a baseline: the deliverable is -the inventory, and it lives here. - -## Technical Context - -**Language/Version**: Markdown. No compiled artifact. The repository being -inventoried contains **no compiled language at all** — 0 Go files — which is the -condition that makes it the shape's test case. - -**Primary Dependencies**: None. The corpus depends on nothing at runtime. The -repository being inventoried depends on no other repository in the organization, -which is the fact FR-003 states. - -**Storage**: `components/osapi-justfiles/specs/` for the specification — a -directory this feature creates, since it is the project's first — and -`components/osapi-justfiles/.specify/memory/` for what archival consolidates -into. That memory holds only a constitution before this feature, which is the -condition the feature exists to end. - -**Testing**: `just test` in the specs repository — `mdformat --check`, -`just-fmt-check`, and `scripts/validate-skills.py`. There is no code to unit -test. Separately, every count in the specification carries the command that -reproduces it, and re-running those commands against `osapi-justfiles` is what -checks the inventory. The formatting gate cannot tell a right count from a wrong -one. - -**Target Platform**: The corpus. - -**Project Type**: Documentation. - -**Constraints**: No change to the `osapi-justfiles` repository — -`git -C osapi-justfiles status` clean is SC-007. No count stated without the -command that produces it. Every gap recorded with both sides named and an owner, -none corrected here. No compatibility policy invented for a repository that has -none. - -**Scale/Scope**: One repository inventoried. Five modules, 38 recipes and about -twenty variables stated as a contract; six consuming repositories named by their -edges rather than described. - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -| Principle | How this feature satisfies it | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **Documentation** | The recipes are the statement of record and this states where they live and what they take, never what the shell inside them does — FR-021's first exclusion. A rule a tool enforces is not restated as prose. | -| **Verification** | Every count carries its command, and the recipe count was taken **two ways that agree**. That second measurement is the principle's "running something that would fail if the claim were false" rather than reading a file and concluding. | -| **Tooling** | Nothing provisioned. FR-017 records that nothing pins the modules, as the rule it strains rather than as a violation — the committed-output clause does not bite, because `.just/` is gitignored rather than committed. | -| **Correction** | Four findings recorded as gaps with owners, including one against `system`'s own 002. FR-015 refuses to invent a compatibility policy the repository does not have, which is the standard-to-fill-a-template failure this principle names. | -| **Workflow** | Stages 3 and 4 for `001`, in one branch, after stage 1 merged as specs#178. | -| **Baseline** | This is the first baseline written under the fragment. FR-020 records that it was sufficient except for one question, which is evidence about the fragment rather than about this repository. | -| **Repositories** | The consumer set was taken from the command each time rather than from a list, and this baseline states only its own edges — the map is `system`'s. | -| **Tracking** | Nothing here becomes an issue. The gaps are recorded where the next reader of this repository's memory will find them, and the two that imply work in `osapi-justfiles` name it as the owner. | - -**Result**: no violations. - -## The one interpretive decision - -The shape's section 4 is **"the contract"**, and 002 wrote it expecting a -repository that exposes code. This one exposes none. Three readings were -available: - -1. **Omit the section**, using 002's FR-011 allowance, and state why. Rejected: - the repository plainly *has* something consumers depend on, so an omission - would record an absence that is not there — and it would have exercised - FR-011 on the wrong case, teaching the programme that a non-Go repository has - no contract. -2. **Read "contract" narrowly as exported symbols** and conclude the section is - not applicable. Rejected for the same reason, with the additional cost that - the fifth and sixth baselines would inherit the conclusion. -3. **Read "contract" as what a consumer may depend on**, whatever its form. - Taken. - -Under the third reading the contract is **38 recipe names and about twenty -variable names**, and it passes every test that makes a contract worth stating: -a consumer depends on it, renaming part of it breaks them at their next fetch, -and nothing in the repository declares it. That last part is what makes stating -it valuable rather than redundant. - -**What this decision costs, stated rather than hidden**: it widens the word for -every baseline after it. A later reader could take "contract" to mean any -observable regularity, which would make the section unbounded. The bound that -keeps it useful is the one applied here — *something a consumer's build breaks -on* — and FR-020 hands the question of whether `global/baseline` should say so -to `system`, because a fragment is amended in its own change. - -## The reading order, and why it was that order - -The same order -[gohai's baseline](../../../gohai/specs/001-gohai-baseline/plan.md) used, for -the same reason: it is what keeps prose from becoming a source. - -1. **The files first.** Each module's `.just` file for its recipes and - variables, the root `justfile` for the self-consumption asymmetry, every - consumer's `fetch` recipe for the edges. -2. **The counts by command**, never by reading a sentence claiming one. The - recipe count was taken twice by different means and the results compared - before either was written down. -3. **The prose last**, and only to find disagreements. Six READMEs and a - 112-line root README were available and none was transcribed. - -Reading the READMEs first would have produced a plausible inventory of what the -modules are *for* and would not have found the unprefixed `run` recipe, the -pinned-inner-floating-outer version shape, or the self-consumption asymmetry — -none of which any README mentions. - -## Project Structure - -### Documentation (this feature) - -```text -components/osapi-justfiles/specs/001-justfiles-baseline/ -├── spec.md # merged at specs#178, amended at #179; 24 requirements -├── plan.md # This file -├── checklists/ -│ └── requirements.md # From stage 1 -└── tasks.md # Phase 2 output -``` - -### Content - -```text -components/osapi-justfiles/ -├── specs/001-justfiles-baseline/spec.md # the statement of record -└── .specify/memory/ # where archival puts it - ├── constitution.md # composed; eight fragments - ├── spec.md # created by archival - └── plan.md # created by archival -``` - -### What is being inventoried - -```text -osapi-justfiles/ # nothing here changes -├── justfile # its own test recipe, resolving two ways -├── docusaurus/docusaurus.just # 10 recipes -├── go/go.just # 16 recipes, one of them unprefixed -├── just/just.just # 2 recipes, no variables -├── md/md.just # 2 recipes, pinning mdformat and Python -└── react/react.just # 8 recipes -``` - -**Structure Decision**: no corpus subject file beyond `spec.md`. The statement -is the specification, archived into memory like every other feature's. No skill -gains a reference: no skill in this repository builds or formats anything, so a -citation into these recipes would be a rule invented for a reader who does not -exist — the same reasoning -[007's FR-017](../../../osapi/specs/007-the-embedded-ui/spec.md) applied to the -`add-a-domain` skill. - -## What the verification actually checks, and what it cannot - -`tasks.md` re-runs every command in FR-016 against a fresh checkout. That checks -the **counts**. It does not check the two things most likely to be wrong, which -is stated here so a green task list is not mistaken for a verified inventory: - -- **Whether a recipe does what its name suggests.** FR-021 excludes it - deliberately. - -- **Whether the contract is complete.** This was going to be the second entry on - this list, worded as a limit: a variable a module reads without assigning a - default would not appear in FR-013's table, because that table was built from - assignment lines. **Writing it down was enough to notice it was testable.** - Comparing each module's `{{ variable }}` references against its assignment - lines took one command, the table turned out to be complete, and the two - references it could not account for were recipe parameters — which is how - FR-011a's two argument-taking recipes were found. Both results went into the - specification by amendment at specs#179, before this plan merged. - - The entry stays here in its corrected form because the mistake is worth - keeping: a limit that can be tested is not a limit, it is a check nobody ran. - Stating it as a boundary of what the inventory could know read as rigour and - was the opposite. - -- **Whether the modules are what the consumers need.** That is a judgement about - fitness, not a measurement. - -## Complexity Tracking - -> No Constitution Check violations, so this table is empty. diff --git a/components/osapi-justfiles/specs/001-justfiles-baseline/spec.md b/components/osapi-justfiles/specs/001-justfiles-baseline/spec.md deleted file mode 100644 index 700b257..0000000 --- a/components/osapi-justfiles/specs/001-justfiles-baseline/spec.md +++ /dev/null @@ -1,593 +0,0 @@ -# Feature Specification: A baseline for osapi-justfiles - -**Feature Branch**: `001-justfiles-baseline` - -**Created**: 2026-09-29 - -**Status**: Completed - -**Input**: `osapi-justfiles`' `.specify/memory/` holds only a constitution, and -that constitution is composed from `.charter/` — so it states what binds every -repository and nothing about this one. This is unit 6 of the baseline programme -`system`'s [002](../../../../system/specs/002-baseline-shape/spec.md) defines, -taken deliberately out of order. - -## Why this one is sixth rather than last - -It is the smallest repository in the organization and the largest risk to the -*shape*. 002's FR-011 and FR-013 ask two questions no baseline has yet had to -answer: may a required section be omitted, and can a contract be stated for -something that exposes no code? `osapi-justfiles` has **zero Go files**, no -`docs/` tree and no documentation site. If the seven-section shape cannot be -filled here, the shape is wrong — and 002's own task list says finding that out -after four conforming baselines is the expensive order. - -So this specification has two deliverables, not one. The inventory is the -obvious half. The other is the answer, section by section, to whether the shape -fits a repository that is not a Go library — and that answer is recorded here as -evidence about the shape rather than left as a feeling that it went fine. - -## What this specification is, and what it is not - -**Its subject is a description, not a change.** Nothing lands in the -`osapi-justfiles` repository: no recipe is added, renamed or removed, no fetch -is pinned, no README moves. What this produces is an inventory of how the -repository behaves today, which lives in this feature directory and reaches -memory through archival like any other feature. Step 2 of CONTRIBUTING's -"Closing a change" does not apply. - -**Prose was a lead, never a source.** The repository carries a 112-line root -README and five module READMEs totalling 353 lines. None of it was transcribed. -Every count below came from a command, the commands are given so a reader -re-measures rather than trusting this file, and where a count could be taken two -ways it was taken both ways and the results compared. - -**This is the first baseline written against `global/baseline`.** That fragment -was composed into all six constitutions by 002 and no baseline had yet been -written under it — gohai's predates it. Whether the fragment was sufficient to -write a baseline from is therefore evidence about the fragment, and FR-020 -records it. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - A consumer knows what it is depending on (Priority: P1) - -Somebody maintaining one of the six *other* consuming repositories can state -what `osapi-justfiles` gives them — which recipes exist, what each needs -configured before the import, and what happens to their build when the module -changes — without reading the `.just` files. - -**Why this priority**: every repository in the organization runs its tests -through recipes that come from here. It is the one repository whose contract is -consumed by all of the others, and the only one whose contract is nowhere -written down. - -**Independent Test**: a reader with no prior knowledge names the five modules, -says which recipes a given module supplies, and states what a consumer must set -before importing it — from this specification alone, without opening the -repository. - -**Acceptance Scenarios**: - -1. **Given** this specification, **When** a consumer asks what configuring the - `go` module requires, **Then** the override variables and their defaults are - stated. -2. **Given** this specification, **When** a consumer asks what version of a - module their last build used, **Then** the answer is that nothing records it, - stated as a gap rather than left to be discovered. - -______________________________________________________________________ - -### User Story 2 - The seven-section shape is tested against a repository with no code (Priority: P1) - -Somebody about to write the fourth, fifth or sixth baseline knows whether the -shape holds for a repository that is not a Go library, because this one tried it -and said what happened. - -**Why this priority**: equal to the first, and the reason this unit is sixth. -The cost of discovering the shape does not fit is paid once here or four times -later. - -**Independent Test**: for each of the seven required sections, this -specification states whether it was fillable, what filled it, and — if it was -omitted — why, such that an omission is never indistinguishable from an -oversight. - -**Acceptance Scenarios**: - -1. **Given** the seven sections, **When** each is checked against this - specification, **Then** each is either filled or carries a stated reason for - its absence. -2. **Given** a section that 002 assumed would be about code, **When** it is - filled for a repository with none, **Then** what filled it is named, so the - next baseline can tell whether its own case is the same one. - -______________________________________________________________________ - -### User Story 3 - A change here is recognised as the widest change in the organization (Priority: P2) - -Somebody editing a module knows, before they do, that it reaches seven -repositories' next continuous integration run with no release, no tag and -nothing recording which version a build used. - -**Why this priority**: lower than the two above because it is a consequence of -what they state rather than a separate subject, but it is the fact about this -repository that most changes behaviour once known. - -**Independent Test**: this specification states the dependency direction, the -number of incoming edges — seven, taken from the command rather than from a list -— and that the propagation mechanism is a fetch from a branch rather than from a -release. - -### Edge Cases - -- **A repository with no code still has an interface.** The shape's contract - section was written expecting exported symbols. Here the contract is 38 recipe - names and twenty variable names, which is an interface by every test that - matters: a consumer depends on it, renaming part of it breaks them, and - nothing in the repository declares it. Stating it was possible; what had to - change was the assumption that a contract means code. -- **A repository that is its own consumer.** `osapi-justfiles` fetches its own - `md` module from `main` and imports it, while running its `just` module from - the working tree. The two halves of its own `test` recipe therefore resolve - differently, and no other repository in the organization has this shape. -- **A count that is true and misleading.** 002's FR-031 records this repository - as having **0 documentation pages**. That is exactly right about pages and - wrong as an answer to "what documentation does it have", because six README - files are its documentation. The baseline records both rather than - reinterpreting the figure. -- **A recipe that does not carry its module's name.** Thirty-seven of the 38 - recipes are prefixed with their module, so an import adds a predictable - namespace. One is not, and it is in the most widely imported module. - -## Requirements *(mandatory)* - -Every requirement is *the corpus MUST state X*, and each names how it was -checked. The section headings below are the seven 002 fixes, in 002's order. - -### 1. What this repository is - -- **FR-001**: The corpus MUST state that `osapi-justfiles` is a library of - shared `just` recipes — not a tool, not a service, and not a Go module. It - contains **zero Go files**, and its only executable content is `just` recipes - and the shell they invoke. Verified: - `find . -name '*.go' -not -path './.git/*' | wc -l` returns 0. -- **FR-002**: The corpus MUST state that it exists so that a convention binding - several repositories is written once. That is `global/documentation`'s "where - a convention binds several repositories, each states it in the same words" - made mechanical: the repositories do not each state the recipe, they each - fetch it. - -### 2. Where it sits - -- **FR-003**: The corpus MUST state that `osapi-justfiles` depends on **no** - other repository in the organization and is depended on by **seven**, itself - among them — every non-archived public repository in the organization that has - a justfile at all. It is the only node in the dependency graph with no - outgoing edge, which is the exact opposite of `osapi`'s position as the only - node with edges in **both** directions. - - **Corrected 2026-09-30**: this said `osapi` is "the hub with the most incoming - *and* outgoing edges", which FR-004's table two requirements below - contradicts. `osapi-justfiles` has seven incoming edges; `osapi` has three of - any kind. The word "hub" was doing two jobs: most edges, which is this - repository, and on both sides of the graph, which is `osapi`. Only the second - reading makes `osapi` first in the order, so only the second is meant. - Verified from every consumer's `fetch` recipe, over the set - `gh repo list osapi-io --no-archived --visibility public` returns rather than - over a list written here: `grep -A8 '^fetch:' /justfile`. Only `.github` - has no justfile. - -- **FR-004**: The corpus MUST state which modules each consumer takes, because - the blast radius of a change differs per module: - - | Consumer | Modules | Which | - | -------------------- | ------: | --------------------------------------- | - | `osapi` | 5 | all | - | `gohai` | 3 | `go`, `just`, `md` | - | `nats-client` | 3 | `go`, `just`, `md` | - | `nats-server` | 3 | `go`, `just`, `md` | - | `osapi-orchestrator` | 3 | `go`, `just`, `md` | - | `specs` | 2 | `just`, `md` — it has no Go and no site | - | `osapi-justfiles` | 1 | `md`, from itself — FR-010 | - - So a change to `md.just` reaches **all seven**, `just.just` reaches six, - `go.just` reaches five, and `react.just` and `docusaurus.just` reach one each. - `md` has the widest blast radius in the organization. - -- **FR-004a**: The corpus MUST record that **`specs` is a consumer, and that - this baseline originally said six consumers rather than seven.** The design - record fetches `md` and `just` — it has no Go and no documentation site, so it - takes the two modules that apply to any repository holding markdown and a - justfile. - - **How it was missed, and what found it.** This specification was written from - the six repositories the baseline programme enumerates, which are the six - *components*. `specs` is not a component — it is where the components are - described — so it was never in the frame, despite being the repository the - specification was being written in and the one whose `just test` had been run - dozens of times while writing it. What found it was the task that says to take - the repository set from - `gh repo list osapi-io --no-archived --visibility public` rather than from a - list written here, which is `global/repositories` applied to this feature's - own verification. **A written list is right when written and wrong afterwards, - and the failure mode is not that the list ages — it is that the writer's frame - was never the whole set.** - - The consequence is not only arithmetic. `specs`' `just test` is the gate for - every corpus change in the organization, and it depends on an unpinned fetch - of `md.just` — so the design record's own formatting gate can be changed by a - commit to the repository the design record describes. That circularity is - recorded with FR-017's gap rather than as a separate finding, because the fix - is the same one. - -- **FR-005**: The corpus MUST state that a change here is the **widest change - available in the organization**, and why: it reaches every consumer's next - continuous integration run with no release, no tag and no review in the - consuming repository. This is a statement about reach, not a criticism of the - mechanism; FR-016 records the gap. - -### 3. Architecture - -Stated at the level 002's FR-004 to FR-006 require: what each part is for and -what passes between parts, nothing a rename would falsify. - -- **FR-006**: The corpus MUST state that the repository is **five independent - modules**, one per directory, each holding exactly one `.just` file named - after its directory and one `README.md`. There is no shared code between - modules and no module imports another. A consumer takes the modules it wants - and ignores the rest, which is why `nats-server` never sees a React recipe. - -- **FR-006a**: The corpus MUST state **what each module is for**, not only that - there are five of them. 002's FR-004 requires architecture at the level of - what a part is for, and naming the parts is not the same as saying what they - do: - - | Module | What it is for | - | ------------ | ----------------------------------------------------------------------- | - | `docusaurus` | Builds, serves, deploys and formats a Docusaurus documentation site | - | `go` | Builds, tests, formats, lints and measures coverage for a Go project | - | `just` | Formats and checks a repository's justfiles, using just's own formatter | - | `md` | Formats every markdown file in a repository with mdformat | - | `react` | Builds, lints, formats and serves a React application | - - **This was missing, and the SC-001 reading is what found it.** The - specification listed the five names in three places — FR-006, FR-011's recipe - table, FR-013's variable table — and said nowhere what any of them does. A - reader could infer it from the recipe prefixes, and the reading did, - correctly. That is the failure: an inventory whose reader has to infer the - purpose of a part from the names of its recipes has described the repository's - *shape* and not its *architecture*, which is the distinction 002's FR-004 - draws. The purposes above were taken from each module's README and checked - against its recipe list. - -- **FR-007**: The corpus MUST state what passes between a module and its - consumer, in both directions: **recipes** out, **variables** in. A consumer - assigns the variables it needs to override, then imports the module; the - module's recipes read those variables. Nothing else crosses the boundary — no - configuration file, no environment contract, no generated artifact. - -- **FR-008**: The corpus MUST state the consumption mechanism, because it is - architecture rather than detail: a consumer's `fetch` recipe `curl`s each - module from `raw.githubusercontent.com` at `refs/heads/main` into - `.just/remote/`, and `.just/` is gitignored in every consumer. So the modules - are **fetched, not vendored**: nothing about which version a consumer has is - recorded in that consumer. - -- **FR-009**: The corpus MUST state that the import is **optional** — - `import? '.just/remote/.just'` — so a consumer's justfile parses - before `just fetch` has ever run, and a missing module surfaces as an unknown - recipe rather than a parse error. - -- **FR-010**: The corpus MUST state that `osapi-justfiles` is **its own - consumer, asymmetrically**: its root justfile fetches `md.just` from `main` - and imports it, while invoking its `just` module directly from the working - tree with `just --justfile just/just.just`. So one half of its own `test` - recipe checks the code in front of you and the other half checks whatever - `main` holds. Verified: the repository's root `justfile`. - -### 4. The contract - -What a consumer may depend on. Stated in two halves because a consumer depends -on both. - -- **FR-011**: The corpus MUST state the **38 recipes** by name, grouped by - module, and MUST record that the count was verified two ways that agree — by - grep for recipe headers and by - `just --justfile /.just --working-directory . --summary`: - - | Module | Recipes | Names | - | ------------ | ------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | `docusaurus` | 10 | `docusaurus-build`, `-bump`, `-clean`, `-deploy`, `-deps`, `-fmt`, `-fmt-check`, `-generate`, `-serve`, `-start` | - | `go` | 16 | `go-deps`, `go-fmt`, `go-fmt-check`, `go-generate`, `go-mod`, `go-mod-bump`, `go-mod-check`, `go-test`, `go-unit`, `go-unit-cov`, `go-unit-cov-check`, `go-unit-cov-gaps`, `go-unit-cov-map`, `go-unit-int`, `go-vet`, **`run`** | - | `just` | 2 | `just-fmt`, `just-fmt-check` | - | `md` | 2 | `md-fmt`, `md-fmt-check` | - | `react` | 8 | `react-build`, `react-deps`, `react-dev`, `react-fmt`, `react-fmt-check`, `react-generate`, `react-lint`, `react-test` | - -- **FR-011a**: The corpus MUST state that **two of the 38 recipes take - arguments**, because a recipe's signature is part of what a consumer invokes - and a bare name does not carry it: `docusaurus-bump version` requires one, and - `run *args` is variadic and forwards what it is given to `go run`. The other - 36 take none. Verified by reading each module's recipe headers; the two appear - as `{{ version }}` and `{{ args }}` in their bodies, which is how they were - found — see FR-013a. - -- **FR-012**: The corpus MUST state that **37 of the 38 recipes are prefixed - with their module's name and one is not**: `run`, in the `go` module. A - consumer importing `go.just` therefore gains an unprefixed recipe name in the - most widely imported module, where a name collision with the consumer's own - `run` is possible. Recorded as an inconsistency in the contract, not as a - defect to fix here. - -- **FR-013**: The corpus MUST state the **twenty override variables and their - defaults**, because they are the half of the contract a consumer must act on - before the import rather than after: - - | Module | Variable | Default | - | ------------ | -------------------- | --------------------------------------------------------- | - | `docusaurus` | `docusaurus_dir` | `docs` | - | `docusaurus` | `docusaurus_host` | `localhost` | - | `docusaurus` | `docusaurus_port` | `3001` | - | `go` | `go_git_root` | **computed** — `git rev-parse --show-toplevel` | - | `go` | `go_main_package` | `main.go` | - | `go` | `go_coverage_dir` | `.coverage` | - | `go` | `go_coverage_target` | `100` | - | `go` | `go_fmt_excludes` | **empty** — a consumer adds `! -path` clauses | - | `go` | `go_os_tags` | **computed** — `-tags=ubuntu` on Ubuntu, empty elsewhere | - | `go` | `go_packages` | **computed** — `go list ./...` less `node_modules` | - | `md` | `md_version` | `1.0.0` | - | `md` | `md_gfm_version` | `1.0.0` | - | `md` | `md_wrap` | `80` | - | `md` | `md_python` | `3.13` | - | `md` | `md_site_dir` | `docs` | - | `md` | `md_excludes` | excludes `.claude`, `node_modules`, `.worktrees`, `.just` | - | `md` | `md_site_exclude` | **derived** from `md_site_dir`; empty when that is empty | - | `md` | `md_extra_excludes` | **empty** | - | `react` | `react_dir` | `.` | - | `react` | `react_fmt_pattern` | `src/**/*.{ts,tsx,css}` | - | `just` | none | it takes no configuration | - - **Every default is stated, and four of them are not literals.** The table - originally printed defaults for only three of `go`'s seven variables while - FR-013a asserted that all twenty have one. Both were true — the four unprinted - ones are computed or empty rather than absent — but a reader comparing the - table against FR-013a's claim had no way to tell which. The SC-001 reading hit - exactly that and said so. **An empty default and a missing default look - identical in a table that prints neither, and they are opposite facts**: one - means a consumer need not act, the other means they must. - - Nineteen of the twenty are declared in their module's header block; - `go_packages` is declared at `go/go.just:147`, beside the recipe that uses it. - -- **FR-013a**: The corpus MUST state that FR-013's table is **complete** — every - variable any module's recipes read has an assignment with a default in that - module — and MUST state how that was established rather than asserting it. - Each module's `{{ variable }}` references were compared against its assignment - lines; the only two references not matched by an assignment are - `{{ version }}` and `{{ args }}`, and both are recipe parameters rather than - module variables, which is FR-011a. So there is no variable a consumer must - set without a default, and no variable the table omits. - - **This requirement exists because the check nearly did not happen.** The plan - had named "a variable a module reads without assigning a default" as a limit - of the inventory — a caveat, stated and left. Running the comparison took one - command and turned the caveat into a verified fact. A limit that can be tested - is not a limit; it is a check nobody ran. - -- **FR-013b**: The corpus MUST state the variable count as **20** rather than as - "about twenty", and MUST carry the command that produces it. The figure is - exact, enumerable from FR-013's own table, and FR-013a has already established - that the table omits nothing — so there was never anything to approximate. - - **Recorded rather than silently corrected, because the hedge is the - interesting part.** This specification stated "about twenty" in five places - while listing all twenty in a table two paragraphs away, and FR-016's - measurement table had no row for them at all. A count is the one thing this - repository's constitution says must never be a claim somebody typed, and the - approximation survived a specification, an amendment and a plan. It was found - by the consistency pass, not by re-reading. A hedge reads as caution and - functions as an unmeasured number. - -- **FR-014**: The corpus MUST state that **each module pins the tools it invokes - while nothing pins the module**. `md` pins mdformat 1.0.0, mdformat-gfm 1.0.0 - and Python 3.13; `go` pins a coverage target of 100. So the inner versions are - fixed and the outer one floats, which is the reverse of what a reader would - assume from either half alone. - -- **FR-015**: The corpus MUST state what stability a consumer may actually - expect, and MUST state it as what is true rather than as what would be - reasonable: **the contract is whatever `main` holds.** There is no release, no - tag, no version number and no deprecation path, so a recipe renamed on `main` - is renamed for every consumer at their next `just fetch`. A consumer may - depend on the names above being what `main` holds today and on nothing about - tomorrow. Inventing a compatibility policy the repository does not have would - be the standard `global/correction` forbids. - -### 5. Measurements - -- **FR-016**: The corpus MUST state each measurement with the command that - produces it, per `global/baseline`. Measured 2026-09-29 at `e765614`: - - | Measurement | Value | Command | - | -------------------------- | ----: | --------------------------------------------------------------------------------------- | - | Go files | 0 | `find . -name '*.go' -not -path './.git/*' \| wc -l` | - | Modules | 5 | `ls -d */ \| while read d; do [ -f "$d$(basename $d).just" ] && echo $d; done \| wc -l` | - | Recipes, all modules | 38 | `just --justfile /.just --working-directory . --summary` per module | - | Override variables | 20 | `grep -hcE '^[a-z_][a-z0-9_]* *:?= ' */*.just \| paste -sd+ - \| bc` | - | `.just` lines, all modules | 487 | `wc -l */*.just` | - | Markdown files | 11 | `find . -name '*.md' -not -path './.git/*' \| wc -l` | - | Module READMEs | 5 | `wc -l */README.md` | - | Module README lines | 353 | `wc -l */README.md` | - | Root README lines | 112 | `wc -l README.md` | - | Documentation site pages | 0 | no `docs/` tree exists | - - Per module, recipes / `.just` lines / README lines / variables: `docusaurus` - 10 / 83 / 64 / 3; `go` 16 / 215 / 93 / 7; `just` 2 / 41 / 39 / 0; `md` 2 / 60 - / 100 / 8; `react` 8 / 88 / 57 / 2. The variables are the column this table - originally had no row for, which is FR-013b. - -### 6. Gaps - -Each is recorded with both sides named and an owner. None is this feature's to -fix, and 002's FR-024 established that a recorded gap belongs to whoever owns -the repository it is in. - -- **FR-017**: The corpus MUST record that **every consumer fetches from - `refs/heads/main`**, so nothing pins the modules and nothing records which - version a build used. The rule this strains is `global/tooling`'s "both - provisioning paths resolve to the same version" — which here has no mechanism - at all rather than a divergent one. Stated as the rule it strains rather than - asserted as a violation: the principle's committed-output clause does **not** - bite, because `.just/` is gitignored rather than committed. Owner: - `osapi-justfiles`, with a change in each consumer. First recorded by - `system`'s 002. -- **FR-018**: The corpus MUST record the **self-consumption asymmetry** FR-010 - states as a gap as well as architecture: the repository supplying the modules - checks its own markdown against whatever `main` holds rather than against the - file in its working tree, so a change to `md.just` cannot be tested by the - repository that owns it before it is on `main`. Owner: `osapi-justfiles`. -- **FR-019**: The corpus MUST record that **002's own inventory has a gap this - baseline found**. 002's FR-031 records `osapi-justfiles` as having 0 - documentation pages, which is true of pages and misleading as a statement - about documentation: six README files are this repository's documentation, - five of them documenting one module each. The correct statement is that it has - **no documentation pages and six documentation files**. Owner: `system`'s 002, - amended in its own change — not here, and not by reinterpreting the figure. -- **FR-020**: The corpus MUST record whether `global/baseline` was sufficient to - write this baseline from, since this is the first written under it. **It was, - with one thing it does not say.** The fragment names the seven subjects and - the count-with-command rule, and both were directly usable. What it does not - say is what a "contract" means for a repository that exposes no code — the - word reads as though it presumes exported symbols, and this baseline had to - decide that 38 recipe names and 20 variable names are one. That decision is - recorded here rather than folded in silently, because the next non-Go - repository will need the same one. Whether the fragment should say so is - `system`'s to decide. - -### 7. What this inventory excludes - -- **FR-021**: The corpus MUST state what it deliberately leaves out, so a later - reader can tell an omission from an oversight: - - - **What each recipe does internally.** The recipes are named and their - configuration stated; the shell inside them is not transcribed. It is the - statement of record and prose about it would drift — `global/documentation`. - - **The contents of the five module READMEs.** They document the interface of - the file beside them; this states that they exist and what they cover. - - **The repository's own contributor conventions.** `AGENTS.md`, - `CONTRIBUTING.md`, `CLAUDE.md`, `CODE_OF_CONDUCT.md` and `AI_POLICY.md` are - that repository's and are cited rather than copied. - - **The `Dockerfile` and the five GitHub workflows.** Its continuous - integration runs `just-lint` and commit linting; what those configurations - contain is not inventoried here. - - **Whether any recipe is correct.** This states what the contract is, not - whether a recipe does what its name suggests. - -- **FR-021a**: The corpus MUST use `system`'s 002 FR-001 section names - **verbatim** — "What this repository is", "Where it sits", "Architecture", - "The contract", "Measurements", "Gaps", "What this inventory excludes" — - rather than names that merely mean the same thing. - - **This baseline did not, and the correction is the point.** Three of its - headings were extended: "What *the* repository is", "Where it sits *among the - others*", and "Measurements\*, and the commands that reproduce them\*". Every - one was an improvement in isolation — clearer, more specific, better prose. - The order was right and so was the meaning, so nothing read as wrong. - - It matters because this is the **first** baseline written to the shape, and - four more will be written by copying it rather than by re-reading 002. A - section name that drifts by one word per baseline is how six documents stop - being one set, and the whole argument for a fixed order is that a reader - moving between baselines finds the same answer in the same place. A heading is - where they look first. - -- **FR-022**: The corpus MUST state that **no section of the seven was - omitted**, and MUST say what filled each, because that is this unit's second - deliverable. Sections 1, 2, 5, 6 and 7 filled as they would for any - repository. Section 3 filled with the five-module structure and the - fetch-and-override mechanism, at the level a rename survives. Section 4 filled - with recipe names and variable names, which required deciding that a contract - need not be code — the one place the shape had to be interpreted rather than - followed. **The shape fits a repository with no Go code, and 002's FR-011 need - not be exercised: no section had to be dropped.** - -- **FR-022a**: The corpus MUST record what the SC-001 reading said about this - document as a document, because it is a finding about the shape and not only - about this baseline. - - The reading answered all three questions but reported that the file reads as - **a list of requirements with a layer of self-referential narrative**, not as - one coherent reader-facing document — and that the consumer-facing facts a - maintainer actually wants are interleaved with, and outnumbered by, commentary - about the specification-writing exercise. It named the passages: why this unit - is sixth, FR-004a's account of missing `specs`, FR-013b on the hedge, FR-021a - on the renamed headings, FR-020's verdict on the fragment. - - **That is accurate, and it is not fixed by deleting them.** This document - carries two deliverables — the inventory for a consumer, and the shape finding - for the programme — and User Story 2 is the second one. The meta-narrative - *is* what FR-020 and FR-022 were asked to produce. What the reading exposes is - that one document serving both readers serves the first one worse, which is - the same structural problem the corpus backfill solved by splitting a page - rather than by editing it. - - Recorded here rather than acted on, because the fix is not this baseline's: it - is whether `system`'s 002 should require the shape finding to live somewhere - other than the baseline a consumer reads. Owner: `system`'s 002, alongside - FR-020's question about the same fragment. - - It also reported that the file assumes a reader knows what `just`, a recipe, a - justfile and `import?` are, and knows spec-kit's vocabulary — FR-, SC-, "the - seven sections", "unit 6". The first set is reasonable for a reader of a - justfile library. The second is not obviously reasonable for a maintainer of a - consuming repository, and no requirement here addresses it. - -### Key Entities - -- **Module**: One directory holding one `.just` file and one `README.md`, - independent of the other four. Five exist. -- **Recipe**: One named entry point a consumer may invoke. 38 exist, 37 carrying - their module's prefix. -- **Override variable**: A value a consumer assigns before the import, which the - module's recipes read. Twenty, each with a default. -- **Consumer**: A repository whose justfile fetches and imports at least one - module. Seven, `osapi-justfiles` and `specs` among them — every non-archived - public repository in the organization that has a justfile. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A reader who has not opened the repository names the five modules, - says which recipes a named module supplies, and states what must be configured - before importing the `go` module — from this specification alone. -- **SC-002**: Every count in this specification is paired with a command, and - running that command reproduces the figure. -- **SC-003**: A reader can state what happens to their build when a module - changes, and that nothing records which version their last build used. -- **SC-004**: For each of the seven required sections, this specification says - whether it was filled and what filled it; no section is absent without a - stated reason. -- **SC-005**: The four gaps are stated with both sides named and an owner, and - none is corrected here. -- **SC-006**: `just test` passes in the specifications repository. -- **SC-007**: Nothing in the `osapi-justfiles` repository changes. - -## Assumptions - -- The measurements are of `e765614`. Line and recipe counts will date; the - commands are given so a reader re-measures rather than trusting them, and what - the modules are for will outlast both. -- A README is documentation and a `.just` file is not, for the purpose of - FR-019's classification. The README describes an interface for a reader; the - `.just` file is the interface. -- The five module READMEs stay where they are. A module's README documents the - file beside it and its reader is a contributor in a consuming repository; - moving it to the corpus would separate an interface from its description, - which is the opposite of what the programme is for. This is the same reasoning - 002's FR-028 applied to `gohai/docs/collectors/`. -- Nothing about the fetch mechanism is being proposed, defended or condemned - here. FR-017 records that nothing pins it; what to do about that belongs to a - change in the repository that owns it. -- The dependency graph is the one `system`'s memory states, and this baseline - states only its own edges, per 002's FR-019. diff --git a/components/osapi-justfiles/specs/001-justfiles-baseline/tasks.md b/components/osapi-justfiles/specs/001-justfiles-baseline/tasks.md deleted file mode 100644 index 0721c56..0000000 --- a/components/osapi-justfiles/specs/001-justfiles-baseline/tasks.md +++ /dev/null @@ -1,295 +0,0 @@ -______________________________________________________________________ - -## description: "Task list for the osapi-justfiles baseline" - -# Tasks: A baseline for osapi-justfiles - -**Input**: Design documents from -`components/osapi-justfiles/specs/001-justfiles-baseline/` - -**Prerequisites**: [spec.md](spec.md) merged (specs#178) and amended -(specs#179), [plan.md](plan.md) - -**Tests**: none, and this is the one place a baseline differs from every other -feature. There is no code, and the gate that matters is not `just test` — it is -whether each command in FR-016 still produces the figure beside it. A formatting -gate cannot tell a right count from a wrong one, so the re-measurement *is* the -test suite and it is Phase 2. - -## Why this list exists at all - -[gohai's baseline](../../../gohai/specs/001-gohai-baseline/plan.md) archived -with a specification and a plan and no task list, and nothing was obviously -lost. This one has a task list because of what the plan found: a caveat about -the contract's completeness turned out to be a one-command check, and it was -only tested because writing it down made it look testable. A baseline whose -counts are never re-run is a baseline nobody checked, and the difference between -those two is a list. - -**Nothing lands in the `osapi-justfiles` repository.** No task below edits it, -and T012 verifies that. - -______________________________________________________________________ - -## Phase 1: Setup - -- [x] T001 Confirm the specification and its amendment are both on `main` before - re-measuring anything: `git -C specs log --oneline -3` must show specs#179 - above specs#178. Measuring against an unamended specification would re-find - FR-011a as though it were new, which is how a correction gets recorded twice. - -______________________________________________________________________ - -## Phase 2: Foundational — the re-measurement - -**This phase is the verification.** Every command below comes from FR-016 or -from the requirement it supports, and each must produce the figure the -specification states. A figure that has moved is recorded as a new measurement -with its date, **not** worked around — that is 002's FR-007 applied to this -feature's own artifacts. - -Run from `~/git/osapi-io/osapi-justfiles` at `e765614` or later. Where a later -commit gives a different figure, the specification is amended in its own change -before this list is completed. - -- [x] T002 Zero Go files — FR-001: - `find . -name '*.go' -not -path './.git/*' | wc -l` → `0`. This is the - measurement the whole unit rests on: it is why this repository tests the - shape. -- [x] T003 Five modules, each with one `.just` file named for its directory — - FR-006: - `ls -d */ | while read d; do [ -f "$d$(basename $d).just" ] && echo $d; done | wc -l` - → `5`. -- [x] T004 Thirty-eight recipes, **counted two ways that must agree** — FR-011. - By summary: - `for d in docusaurus go just md react; do just --justfile $d/$d.just --working-directory . --summary; done | wc -w` - → `38`. Then by grep for recipe headers, per module, and compare the two - per-module figures rather than only the totals: two wrong numbers can sum to a - right one. Expect 10, 16, 2, 2, 8. -- [x] T005 The unprefixed recipe is still exactly one, and still `run` — FR-012: - list the 38 names and check which lack their module's prefix. If a second - appears, the contract has changed shape and FR-012's count is wrong rather - than merely dated. -- [x] T006 The two argument-taking recipes are still those two — FR-011a: `run` - variadic, `docusaurus-bump` requiring `version`, and the other 36 taking none. - Read the recipe headers; a signature is not visible in `--summary` output, - which is why FR-011a exists. -- [x] T007 The variables table is complete — FR-013a. For each module, compare - its `{{ variable }}` references against its assignment lines; the only - references without an assignment must be the two recipe parameters from T006. - **This is the check that was nearly left as a caveat**, so it is a task rather - than a note. -- [x] T008 The pinned-inner-floating-outer shape holds — FR-014: `md.just` still - pins mdformat, mdformat-gfm and Python by version, `go.just` still pins a - coverage target, and nothing pins any module. -- [x] T009 Line and file counts — FR-016: `wc -l */*.just` → `487` total; - `find . -name '*.md' -not -path './.git/*' | wc -l` → `11`; - `wc -l */README.md` → `353` total; `wc -l README.md` → `112`; and no `docs/` - tree exists. - -**Phase 2 result, 2026-09-30.** Every figure reproduced exactly at `e765614`: 0 -Go files, 5 modules, 38 recipes agreeing per module by both methods -(10/16/2/2/8), 487 `.just` lines, 11 markdown files, 353 + 112 README lines, 20 -variables, `run` still the only unprefixed recipe, `docusaurus-bump version` and -`run *args` still the only two taking arguments, and T007 clean across all five -modules. Nothing had moved. - -**Checkpoint**: every figure in the specification has been produced by a command -rather than trusted. Anything that moved is recorded before Phase 3 begins. - -______________________________________________________________________ - -## Phase 3: User Story 1 — a consumer knows what it is depending on (Priority: P1) - -**Goal**: the contract is stated well enough to act on without opening the -repository. - -**Independent test**: SC-001's reading. - -- [x] T010 [US1] Verify the edges from the consumers rather than from this - repository — FR-003 and FR-004: for each of `osapi`, `gohai`, `nats-client`, - `nats-server`, `osapi-orchestrator` and `osapi-justfiles` itself, read the - `fetch` recipe and record which modules it takes. Expect five for `osapi`, - three each for the next four, and one for `osapi-justfiles`. Take the - repository set from `gh repo list osapi-io --no-archived --visibility public`, - not from the specification's list — `global/repositories` says a written list - is right when written and wrong afterwards, and this task is where that - applies to this feature. - -- [x] T011 [US1] Run the SC-001 reading. Give somebody who has not opened the - repository the specification alone and three questions: what are the five - modules; which recipes does the `md` module supply; and what must a consumer - set before importing `go`? A person is preferred; a fresh agent given only - `spec.md` is the fallback. **Record what it proves and what it does not** — - that the answers are in the text, not that a maintainer would enjoy finding - them. - - **Three of three answered, and it found three defects doing it.** A fresh - agent given only `spec.md` answered all three questions, but had to *infer* - what each module is for from its recipe prefixes — the specification named the - five modules in three places and said nowhere what any of them does. That is - now FR-006a, and it was a section 3 requirement unmet: naming the parts is not - stating what they are for. - - It caught FR-013's table printing defaults for three of `go`'s seven variables - while FR-013a claimed all twenty have one. Both were true — the four unprinted - ones are computed or empty rather than absent — but **an empty default and a - missing default are indistinguishable in a table that prints neither, and they - are opposite facts.** All twenty are now printed. - - And it reported the document reads as requirements plus self-referential - narrative rather than as one coherent whole, with the consumer-facing facts - outnumbered by commentary about writing the baseline. Recorded as FR-022a and - handed to `system`, because the fix is structural rather than editorial: one - document carrying both an inventory and a shape finding serves the first - reader worse. - - **What it proves and what it does not.** That the answers are in the text, for - all three. Not that a maintainer mid-task would find them — the reading said - plainly they would have to read past several paragraphs of methodology to - reach what they came for. - -______________________________________________________________________ - -## Phase 4: User Story 2 — the shape is tested against a repository with no code (Priority: P1) - -**Goal**: the programme learns whether the seven-section shape fits a repository -that is not a Go library, before four more baselines are written to it. - -**Independent test**: SC-004 — each of the seven sections either filled, or -absent with a stated reason. - -- [x] T012 [US2] Confirm nothing changed in the inventoried repository — SC-007: - `git -C ~/git/osapi-io/osapi-justfiles status --porcelain` is empty. A - baseline that edited what it was describing would have measured its own - change. -- [x] T013 [US2] Walk the seven sections against the specification and confirm - each is filled: what it is (FR-001, FR-002), where it sits (FR-003 to FR-005), - architecture (FR-006 to FR-010), the contract (FR-011 to FR-015), measurements - (FR-016), gaps (FR-017 to FR-020), exclusions (FR-021 to FR-022). **Zero - omissions is the expected result**, and FR-022 states it — so this task - confirms a claim rather than discovering one, and a section found empty means - FR-022 is wrong. -- [x] T014 [US2] Confirm FR-020 says what the fragment did and did not give. It - must name the one thing `global/baseline` does not say — what "contract" means - for a repository exposing no code — and must leave to `system` whether the - fragment should say it. **It must not amend the fragment**, which is a change - to `.charter/` and belongs to `system`. The finding is this baseline's; the - fix is not. - -______________________________________________________________________ - -## Phase 5: User Story 3 — a change here is the widest change (Priority: P2) - -**Goal**: somebody editing a module knows the reach before they edit. - -- [x] T015 [US3] Confirm FR-005 and FR-017 together state reach and mechanism - without asserting a violation: that every consumer fetches from - `refs/heads/main`, that nothing records which version a build used, and that - the strained rule is `global/tooling`'s "both provisioning paths resolve to - the same version" — which here has no mechanism rather than a divergent one. - The distinction matters: `.just/` is gitignored, so the committed-output - clause does not apply, and a baseline asserting a violation it cannot support - is the invented standard `global/correction` names. - -______________________________________________________________________ - -## Phase 6: Gaps, and what happens to them - -- [x] T016 [P] Confirm each of the four gaps names both sides and an owner — - FR-017 through FR-020 — and that none is corrected here. Two are - `osapi-justfiles`', one is `system`'s, and one is a question handed to - `system` about its own fragment. - -- [x] T017 Open the amendment `system`'s 002 needs for FR-019, or record that it - is not opened. 002's FR-031 records this repository as having 0 documentation - pages, which is true of pages and misleading as a statement about - documentation: it has **no documentation pages and six documentation files**. - That is 002's to amend in its own change. **This task is not "fix it"** — it - is "do not let it be forgotten", and recording that it was left is an - acceptable outcome as long as it is recorded. - - **Opened, and it grew.** The page-count correction went in as 002's FR-032. - Two more went with it, because this baseline's verification had found them by - then: FR-033, that `osapi-justfiles` has seven consumers rather than six — - `specs` fetches `just` and `md`, and 002's own repository map said six while - carrying the command that returns seven — and FR-034, that the programme - covers six components while the organization has eight public repositories, so - the difference between "the six" and "the repositories" is stated rather than - left implicit. FR-034 exists because that implicit difference is what produced - the wrong consumer count. - -______________________________________________________________________ - -## Phase 7: Verification and archival - -- [x] T018 Run `cd specs && mise exec -- just test` — SC-006. - -- [x] T019 Run `speckit-archive-run specs/001-justfiles-baseline` once this - branch has merged. It creates this project's `.specify/memory/spec.md` and - `plan.md`, which do not exist yet — this is the project's first archival, so - there is nothing to fold into and every requirement enters as a new entry - under the feature's own IDs. - - **Archived 2026-09-30.** `.specify/memory/spec.md`, `plan.md` and - `changelog.md` all created; 29 requirements, 3 stories, 4 entities, 4 edge - cases, 7 outcomes and 5 assumptions as AS-001 to AS-005, each carrying an - item-level source ref. Nothing folded and nothing superseded, because there - was nothing there. No agent context file exists in this project, so that step - was skipped rather than guessed at. - -- [x] T020 Mark `system`'s 002 T016 done, which is what opened this unit. Do - **not** mark T017 or any other unit: this is unit 6 of 12, and four baselines - and two moves remain. - -______________________________________________________________________ - -## Dependencies & Execution Order - -### Phase dependencies - -- **Phase 1** has none. -- **Phase 2** blocks everything. Until the figures are reproduced, every later - task is checking prose against prose. -- **Phases 3, 4 and 5** are independent of each other once Phase 2 passes. -- **Phase 6** depends on Phase 2 only for FR-019's figures. -- **Phase 7** is last, and T019 needs this branch merged. - -### What is genuinely parallel - -- T010 and T011 — one is a set of greps across six repositories, the other a - reading. -- T013, T015 and T016 — three different readings of the same merged text. - -### What only looks parallel - -T004 and T005 both enumerate the 38 recipes, and T005's answer depends on T004's -list being right. Run them in order. - -______________________________________________________________________ - -## Implementation Strategy - -### MVP - -Phases 1 and 2. If the re-measurement passes, the inventory is worth archiving -even if nothing else on this list runs — the counts are the part a later reader -cannot check cheaply. - -### Then - -Phase 4 before Phase 3, if only one can be done. The shape finding is what four -later baselines depend on; the consumer reading is what one repository's -maintainers depend on. - -______________________________________________________________________ - -## Notes - -- No Go code changes anywhere. No change of any kind in `osapi-justfiles`. -- The specification was amended once before this list existed, at specs#179, - adding FR-011a and FR-013a. Both came out of writing the plan rather than out - of implementing anything, which is the argument for a planning phase that - re-reads its own claims. -- FR-024, FR-028 and FR-031 named in the specification are `system`'s 002's, not - this feature's. Do not renumber them and do not treat them as tasks here. diff --git a/components/osapi-orchestrator/README.md b/components/osapi-orchestrator/README.md index 50fc35d..9dbaed4 100644 --- a/components/osapi-orchestrator/README.md +++ b/components/osapi-orchestrator/README.md @@ -39,11 +39,12 @@ independently. ## Guards ask what happened; predicates ask what a host is -Conflating these is the most likely mistake, so they are named separately. The -code draws the line elsewhere. `step.go` calls `When` a guard as well, and -`docs/features/guards.md` follows it, so the repository calls ten methods guards -while eight of them ask what earlier work did. What follows sorts conditions by -what each one inspects rather than by what it is called. +Two kinds of condition, split by what each one inspects. Mixing them up is the +usual mistake. + +Note the code and `docs/features/guards.md` both call all ten of these "guards". +This page splits them because the two kinds fail differently, not because the +code does. **Guards** look at earlier work. Eight of them: diff --git a/history/gohai-001-gohai-baseline/checklists/requirements.md b/history/gohai-001-gohai-baseline/checklists/requirements.md deleted file mode 100644 index e0e7dab..0000000 --- a/history/gohai-001-gohai-baseline/checklists/requirements.md +++ /dev/null @@ -1,78 +0,0 @@ -# Specification Quality Checklist: A baseline for gohai - -**Purpose**: Validate specification completeness and quality before proceeding -to planning - -**Created**: 2026-09-29 - -**Feature**: [spec.md](../spec.md) - -## Content Quality - -- [ ] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [ ] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [ ] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [ ] No implementation details leak into specification - -## Notes - -Four items fail by design, and for a baselining feature the reason is sharper -than it was for osapi's five features: **the subject of this specification is -the implementation.** An inventory of how a repository behaves cannot avoid -naming the repository's files, and one that did would be unfalsifiable — a -reader could not check a single claim. - -The constitution's Verification principle requires the evidence, and -CONTRIBUTING's "Seeding a component" goes further for a baseline: "State how -each requirement was checked, and give the command that checks it again, so a -reader re-measures rather than trusting the prose." Every requirement here -carries a file path, and every count carries a shell command. Removing them to -pass these boxes would remove the only thing that makes the inventory worth more -than gohai's README — which is precisely the document whose two wrong numbers -this feature found. - -**Success criteria are technology-agnostic** fails for the same reason: SC-002 -names four counts and the commands that reproduce them, and SC-005 asserts a -clean `git status`. Both are technology-specific and both are the point. - -**Written for non-technical stakeholders** fails because the audience is a -consumer of a Go library or a contributor to it. There is no non-technical -reader of a document whose purpose is to state a five-method interface. - -The four are left unchecked rather than removed. A checklist that passes because -its failing items were deleted records nothing. - -## What the verification found - -Two disagreements between gohai's prose and its code, recorded in FR-014 and -FR-015 rather than corrected: - -| Claim | Code | Kind | -| ------------------------------------------------------------------ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| "65 collectors" (`README.md:111`, and 65 rows in the catalogue) | 62 packages | A **definition**: the catalogue has an `Implemented` column, and three rows — `rackspace`, `softlayer`, `eucalyptus` — are marked `🪦`. 65 catalogued, 62 implemented. | -| "9 categories" (`README.md:111` and `docs/collectors/README.md:3`) | 10 declared, all 10 in use | An **error**. There is no reading on which nine is right. | - -The first is why a baseline states what a number counts rather than just the -number. The second is why CONTRIBUTING calls prose a lead rather than a source — -and it is the same failure shape the osapi backfill hit three times. - -Correcting gohai's prose is not this feature's work: nothing lands in that -repository. It belongs to gohai in its own change. diff --git a/history/gohai-001-gohai-baseline/plan.md b/history/gohai-001-gohai-baseline/plan.md deleted file mode 100644 index 0d4b4e3..0000000 --- a/history/gohai-001-gohai-baseline/plan.md +++ /dev/null @@ -1,122 +0,0 @@ -# Implementation Plan: A baseline for gohai - -**Branch**: `001-gohai-baseline` | **Date**: 2026-09-29 | **Spec**: -[spec.md](spec.md) - -**Input**: Feature specification from -`components/gohai/specs/001-gohai-baseline/spec.md` - -## Summary - -**This plan is retrospective, and written to a gate.** The inventory was -produced by reading gohai and then stated; `speckit-archive-run` requires a -`plan.md`, this feature had none because a baseline is looked-up rather than -planned, and the alternative was leaving the inventory unarchived. It guided no -work. Read it as a record of the shape the work had — the same admission -[001-provider-contract's plan](../../../osapi/specs/001-provider-contract/plan.md) -makes about itself, and consistent with the specification's own statement that -its subject is a description rather than a change. - -State how gohai behaves today, so its memory stops being empty. Sixteen -requirements, every one of the form "the corpus MUST state X". - -**Nothing lands in the gohai repository.** That is not a scoping preference but -what CONTRIBUTING's "Seeding a component" requires of a baseline: the -deliverable is the inventory, and it lives here. - -## Technical Context - -**Language/Version**: Markdown. No compiled artifact. The repository being -inventoried is Go — 314 files, 205 of them not tests — but nothing in it -changes. - -**Primary Dependencies**: None. The corpus depends on nothing at runtime. - -**Storage**: `components/gohai/specs/` for the specification and -`components/gohai/.specify/memory/` for what archival consolidates into. That -memory is empty before this feature, which is the condition the feature exists -to end. - -**Testing**: `just test` in the specs repository — `mdformat --check`, -`just-fmt-check`, and `scripts/validate-skills.py`. There is no code to unit -test here. Separately, every count in the specification carries the shell -command that reproduces it, and re-running those four commands against gohai is -what checks the inventory rather than the formatting. - -**Target Platform**: The corpus. - -**Project Type**: Documentation. - -**Constraints**: No change to the gohai repository — `git -C gohai status` clean -is SC-005. No count stated without the command that produces it. A disagreement -between gohai's prose and its code recorded as a gap with both sides named, -never silently corrected. - -**Scale/Scope**: One repository inventoried. 62 collectors stated by their -shared contract rather than individually, which is the decision FR-004 records. - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -| Principle | How this feature satisfies it | -| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Documentation** | The inventory goes where a skill reads it first, and cites gohai's own catalogue rather than duplicating 62 entries. gohai's `docs/` keeps its job; this states the contract. | -| **Verification** | Every requirement names the file it describes and every count carries its command, which is what CONTRIBUTING requires of a baseline specifically: "so a reader re-measures rather than trusting the prose". Two disagreements were found that way. | -| **Tooling** | Nothing new is provisioned. The Time Machine extension CONTRIBUTING selects was **not** installed: its installer needs an interactive confirmation this session cannot give and warns that it bypasses the trusted catalogues. Reading the repository is the slow path CONTRIBUTING names, not a prohibited one. | -| **Correction** | The two gaps are recorded with both sides named. Neither was fixed here, because nothing lands in gohai; both were then corrected by gohai in its own change, `gohai#201`, which also turned up a third defect — a legend defining `✅` twice. | -| **Workflow** | Specify, then this plan, then archive. No `tasks.md`: there is no ordered work to break down, which is the same shape 001-provider-contract had. | - -**Result**: no violations. - -## Project Structure - -### Documentation (this feature) - -```text -components/gohai/specs/001-gohai-baseline/ -├── spec.md # The inventory — 16 requirements, the deliverable -├── plan.md # This file -└── checklists/ - └── requirements.md # From stage 1; four items fail by design -``` - -No `research.md`, `data-model.md` or `contracts/`: the research *is* the -specification, there is no data model to design, and gohai's contracts are what -the specification states rather than something this feature defines. - -### Content - -```text -specs/ # this repository -└── components/gohai/ - ├── specs/001-gohai-baseline/spec.md # the inventory - └── .specify/memory/ # empty; this fills it - -gohai/ # unchanged -├── internal/collector/{collector,registry}.go # read, not modified -├── pkg/gohai/{gohai.go,collectors/,ocsf/} # read -└── {README.md,docs/,CONTRIBUTING.md} # read as leads, not sources -``` - -**Structure Decision**: one specification, no subdivision. gohai is one library -with one contract; splitting the inventory by category would create ten -documents that each restate the same five-method interface. - -## What was read, and how - -The order matters, because it is what kept prose from becoming a source. - -1. **The code first.** `internal/collector/collector.go` for the interface and - the category constants, `internal/collector/registry.go` for the surface and - the dependency handling, `pkg/gohai/gohai.go` for registration. -2. **The counts by command**, never by reading a sentence that claimed one. Four - are stated and all four are reproducible. -3. **The prose last**, and only to find disagreements. This is the reverse of - the tempting order, and it is why the two gaps were found rather than - inherited: reading the README first would have produced an inventory stating - 65 and 9, both wrong, with the code never consulted. - -## Complexity Tracking - -> No Constitution Check violations, so this table is empty. diff --git a/history/gohai-001-gohai-baseline/spec.md b/history/gohai-001-gohai-baseline/spec.md deleted file mode 100644 index dc51c46..0000000 --- a/history/gohai-001-gohai-baseline/spec.md +++ /dev/null @@ -1,488 +0,0 @@ -# Feature Specification: A baseline for gohai - -**Feature Branch**: `001-gohai-baseline` - -**Created**: 2026-09-29 - -**Status**: Completed — amended 2026-09-30 twice. FR-017 through FR-020 add the -page classification this baseline owed; FR-021 through FR-025 add section 2 and -identify section 3. Both are things `system`'s 002 requires of every baseline -and this one predates. - -**Input**: gohai's `.specify/memory/` is empty. Its constitution is composed -from `.charter/` and so states what binds every repository and nothing about how -this one behaves, which means the memory a skill reads first says nothing about -the repository it describes. This is the first of the five unbaselined projects, -and CONTRIBUTING's "Seeding a component" names it as the one to try first -because gohai depends on nothing else in the organization. - -## What this specification is, and what it is not - -**Its subject is a description, not a change.** Nothing lands in the gohai -repository — no Go code changes, no collector is added or altered. What this -produces is an inventory of how gohai behaves today, which lives in this feature -directory and reaches memory through archival like any other feature. - -That distinction is what makes a baselining feature honest. A specification -written as though this work had been planned in advance would be the failure the -constitution's Correction principle names: a record that claims to have decided -something it merely found. This one claims to have *looked*, and every -requirement below says where it looked. - -**Prose was a lead, never a source.** gohai carries a 324-line README, a -596-line CONTRIBUTING, and a `docs/` tree with a 65-row collector catalogue, -`methodology.md`, `adding-a-collector.md` and `ocsf-validation.md`. None of it -was transcribed. Every count below came from a command, and the commands are -given so a reader re-measures rather than trusting this file. Two disagreements -between that prose and the code turned up while doing so, and both are recorded -below as Gaps rather than quietly corrected. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - A consumer knows what gohai guarantees (Priority: P1) - -Somebody importing `pkg/gohai` needs to know what they may depend on: the shape -a collector has, what selects one, what runs before what, and what comes out. -They read the corpus and know before they write code against it. - -**Why this priority**: gohai is SDK-first — its README says so in its first -sentence — so its consumers are programs, and a program that depends on an -undocumented shape breaks silently when the shape changes. - -**Independent Test**: given the corpus alone, a reader can state the collector -contract, what decides whether a collector runs, and how one collector reads -another's output. - -**Acceptance Scenarios**: - -1. **Given** the corpus, **When** a consumer asks what a collector is, **Then** - the five-method interface is stated with the file that declares it. -2. **Given** the corpus, **When** a consumer asks why a collector they expected - did not run, **Then** the default-enabled rule is stated, along with the - kinds of collector that opt out of it and why. -3. **Given** the corpus, **When** a consumer asks how a collector uses another's - facts, **Then** the dependency declaration, the prior-results map and the - typed accessor are all stated. - -______________________________________________________________________ - -### User Story 2 - A contributor adding a collector knows the obligations (Priority: P2) - -Somebody adding the sixty-third collector needs to know what the registry -requires of it, which category it belongs to, and what else has to change. - -**Why this priority**: it is the most common change gohai takes, and the -contract is uniform across all 62 existing collectors, so stating it once serves -every future one. - -**Independent Test**: the obligations are stated as a contract rather than as 62 -examples, and a reader can name what a new collector must implement without -opening a collector. - -**Acceptance Scenarios**: - -1. **Given** the corpus, **When** a contributor asks which categories exist, - **Then** all ten are named, and the corpus states that the set is closed by - the constants rather than open to a new string. - -______________________________________________________________________ - -### User Story 3 - A number in the corpus is the number in the code (Priority: P2) - -Somebody reading a count in gohai's memory — how many collectors, how many -categories — finds what the repository has, or an explicit statement that the -code and the prose disagree. - -**Why this priority**: gohai's own README already disagrees with its code on -both counts. Copying either number into memory would give a wrong figure the -authority of a specification. - -**Independent Test**: for each count stated, the given command reproduces it. - -**Acceptance Scenarios**: - -1. **Given** a count in the corpus, **When** its command is run, **Then** it - produces that number. -2. **Given** a count gohai's prose states differently, **When** the corpus is - read, **Then** both figures appear with the reason they differ. - -### Edge Cases - -- **A collector is added or removed.** Every count here dates immediately, which - is why each is paired with the command that recomputes it rather than being - stated alone. -- **A collector returns nil.** The interface's own comment says `Collect` - returns a typed struct "or nil if not supported", so absence is a normal - outcome on a platform rather than a failure. -- **A catalogued collector does not exist.** Three entries are catalogued and - deliberately unimplemented. A reader counting rows gets a different number - from a reader counting packages, and both are right about different things. -- **A category constant with no collector.** None exists today — all ten are in - use — but the inventory states the constants as the closed set, so a future - unused one would not silently become invisible. - -## Requirements *(mandatory)* - -Each requirement takes the form *the corpus MUST state X*. The verification -column is not decoration: it is what distinguishes this inventory from a copy of -gohai's README. - -### What gohai is - -- **FR-001**: The corpus MUST state that gohai is an SDK-first Go library for - collecting system facts, importable at `pkg/gohai`, which also ships a CLI — - and that SDK-first is the repository's own framing rather than an - interpretation. Verified: `README.md` line 13 states "gohai is an SDK-first Go - library"; the CLI entry point is `main.go` with commands under `cmd/`. -- **FR-002**: The corpus MUST state the repository's size as a measurement with - its command, not as a number alone: 314 Go files, of which 205 are not tests. - Verified: `find . -name '*.go' -not -path './.git/*' | wc -l` and the same - with `-not -name '*_test.go'`. - -### Where it sits - -- **FR-021**: The corpus MUST state gohai's position in the organization, which - 002's FR-001 makes section 2 of every baseline and this one was written - without. gohai has **no dependency edge in either direction**. Its `go.mod` - names one `osapi-io` path, its own module line, and no other repository's - `go.mod` or Go source names gohai. - - ```sh - grep -n osapi-io gohai/go.mod # 1 line: the module itself - grep -rn 'osapi-io/gohai' --include='*.go' --include='go.mod' \ - osapi osapi-orchestrator nats-client nats-server # nothing - ``` - -- **FR-022**: The corpus MUST state the one edge gohai does have, which no - `go.mod` records. Its justfile fetches three modules from `osapi-justfiles`, - by `curl` from `refs/heads/main` rather than from a release: - - ```sh - grep -oE 'refs/heads/main/[a-z]+/[a-z]+\.just' justfile - # go/go.just just/just.just md/md.just - ``` - - So a change to one of those modules reaches gohai's next CI run with nothing - recording which version built it. That is `osapi-justfiles`' finding rather - than gohai's, and it is restated here because a reader of this section would - otherwise conclude gohai depends on nothing at all. - -- **FR-023**: The corpus MUST state what that isolation does **not** mean, which - is the more useful half of section 2. gohai and `osapi` gather system facts - from **the same upstream library at the same pinned version**, independently. - - | Repository | `gopsutil` | Where | - | ---------- | ---------- | ------------------------------------------------------------------------------------ | - | `gohai` | `v4.26.8` | 14 of its 62 collector directories, 35 non-test files | - | `osapi` | `v4.26.8` | 21 non-test files, across `node/{disk,host,load,mem,process}` and `pkg/sdk/platform` | - - ```sh - grep -h gopsutil gohai/go.mod osapi/go.mod # the same version twice - grep -rl gopsutil gohai/pkg/gohai/collectors/ | sed 's|/[^/]*$||' | sort -u | wc -l - grep -rl gopsutil --include='*.go' gohai/pkg/gohai/collectors/ | grep -cv _test - grep -rl gopsutil --include='*.go' osapi/internal osapi/pkg | grep -cv _test - ``` - - 14 of 62 is worth stating rather than rounding to "gohai uses gopsutil". Most - collectors read a file, a socket or a command, and the ones wrapping this - library are the ones covering what it covers, which is also the ground osapi - needs. - - Whether that is duplication or two different jobs is not this baseline's - question, and the honest answer needs both repositories' requirements rather - than one's inventory: gohai produces a fact catalogue in OCSF, and osapi needs - a handful of host attributes for targeting and reporting. What matters here is - that **the relationship is stated nowhere**, so a contributor adding a - collector cannot tell whether osapi is a consumer they are about to affect. It - is not, today. - - Owner: `system`, because a relationship between two repositories is not in - either one's memory by construction. - -### Architecture, and where this baseline already stated it - -- **FR-024**: The corpus MUST identify its section 3, which exists under three - headings that predate 002's fixed names. The content is not missing; the label - is. The mapping: - - | 002's section | This baseline's heading | Requirements | - | ------------------ | -------------------------------------------- | ---------------- | - | 1 What it is | What gohai is | FR-001, FR-002 | - | 2 Where it sits | **added by this amendment** | FR-021 to FR-023 | - | 3 Architecture | The collector contract, The registry, Output | FR-003 to FR-013 | - | 4 The contract | The collector contract | FR-003 to FR-008 | - | 5 Measurements | Counts, and where the prose disagrees | FR-014, FR-015 | - | 6 Gaps | folded into 5 | FR-014, FR-015 | - | 7 What is excluded | What this inventory does not cover | FR-016 | - - Sections 3 and 4 share a heading, and so do 5 and 6. The headings are not - being rewritten, because renaming the sections of a merged and archived - specification changes the document a reader was pointed at without changing - what it says. What 002 wanted from the fixed names is that a reader can find - each section; a table that says where each one went does that, and leaves the - record intact. - -- **FR-025**: The corpus MUST record what section 3 was actually missing, as - opposed to mislabelled. FR-016 excludes "how any individual collector gathers - its facts", which was right for 62 instances and wrong for the rule they - share. `docs/methodology.md` states that rule in 382 lines: how a collector - decides what to read, which library to wrap, and what its fields are called. - FR-018 of this amendment classifies that page as contributor-facing, so the - rule is architecture that sits outside the corpus. - - The move feature FR-020 names is what closes this. Recorded here so the gap - between "62 collectors are excluded" and "the rule all 62 follow is excluded" - is visible in the section that should have held it. - -### The collector contract - -- **FR-003**: The corpus MUST state the `Collector` interface as **exactly five - methods** — `Name`, `Category`, `DefaultEnabled`, `Dependencies`, `Collect` — - and MUST state that this is the whole contract a collector implements. - Verified: `internal/collector/collector.go`, and - `awk '/^type Collector interface/,/^}/' internal/collector/collector.go | grep -cE '^\t[A-Z]'` - returns 5. -- **FR-004**: The corpus MUST state the collectors **by the contract they all - obey, not one requirement each**. Sixty-two requirements naming sixty-two - collectors would be a copy of the directory listing: it would rot on every - addition, it would state nothing a reader could not get from `ls`, and it - would bury the four rules that actually bind. The enumeration already exists - and is maintained — `docs/collectors/README.md` — so the corpus cites it - rather than duplicating it. -- **FR-005**: The corpus MUST state that `DefaultEnabled` is what keeps heavy or - privileged collectors off until a caller asks, and MUST name the cases the - interface itself names: ssh host keys, full package inventory, full service - list. Verified: the doc comment on `DefaultEnabled` in - `internal/collector/collector.go`. -- **FR-006**: The corpus MUST state the ten categories as a **closed set fixed - by constants** — system, hardware, network, cloud, virtualization, security, - software, users, linux, misc — and MUST state that every one of the ten is in - use by at least one collector. Verified: - `grep -cE '^\tCategory[A-Za-z]+ +=' internal/collector/collector.go` returns - 10, and - `grep -rhoE 'collector\.Category[A-Za-z]+' pkg/gohai/collectors/ | sort -u | wc -l` - also returns 10. -- **FR-007**: The corpus MUST state how one collector reads another's output: - `Dependencies` declares what must run first, `PriorResults` carries the typed - outputs of what finished, and the generic `GetDep[T]` retrieves one with its - type. It MUST also state the interface's own caveat — prior "always includes - everything declared in Dependencies; may include additional upstream siblings" - — because a collector that relies on a sibling it did not declare works by - accident. Verified: `internal/collector/collector.go`. -- **FR-008**: The corpus MUST state that `Collect` may return nil for a platform - that does not support it, and that this is a normal outcome rather than an - error. Verified: the `Collect` doc comment. - -### The registry - -- **FR-009**: The corpus MUST state the registry's exported surface — - `NewRegistry`, `Register`, `Get`, `Names`, `NamesInCategory`, `Selected`, - `SelectedWith`, `Run` — as the whole of what a caller drives. Verified: - `grep -nE '^func ' internal/collector/registry.go`. -- **FR-010**: The corpus MUST state that the registry expands declared - dependencies and runs collectors in topological levels rather than in - registration order, so ordering is derived from `Dependencies` rather than - from how a caller listed them. Verified: `expandWithDeps` and `topoLevels` in - `internal/collector/registry.go`. -- **FR-011**: The corpus MUST state that all 62 collector packages are - registered in one place, `pkg/gohai/gohai.go`, so a collector that exists but - is unregistered is unreachable. Verified: - `grep -oE 'collectors/[a-z_]+' pkg/gohai/gohai.go | sort -u | wc -l` returns - 62, matching the 62 package directories. - -### Output - -- **FR-012**: The corpus MUST state that gohai emits an OCSF representation - through `FromFacts` in `pkg/gohai/ocsf`, and that this is a conversion of - collected facts rather than a second collection path. Verified: - `grep -nE '^func [A-Z]' pkg/gohai/ocsf/*.go`. -- **FR-013**: The corpus MUST state that a JSON Schema for the fact output - exists at `schemas/gohai.schema.json` and is generated rather than - hand-written, and MUST name the generator directory `schemas/gen`. Verified: - those paths exist. - -### Counts, and where the prose disagrees - -- **FR-014**: The corpus MUST state **62 implemented collectors**, with the - command that produces it, and MUST record that gohai's own prose says 65. - **Gap, and it is a definition rather than an error**: - `docs/collectors/README.md` tabulates **65 rows** and carries an `Implemented` - column; three of those rows — `rackspace`, `softlayer`, `eucalyptus` — are - marked `🪦` with Default `❌` and have no package under `pkg/gohai/collectors/`. - So 65 is the catalogue including deliberately unimplemented entries and 62 is - what exists. The corpus states both numbers and what each counts, because a - reader who compares `ls` with the README and is told only one of them will - think one is broken. Verified: `ls -d pkg/gohai/collectors/*/ | wc -l` returns - 62; the catalogue's row count is 65. - - **Corrected in gohai by gohai#201**, after this gap was recorded. Both figures - now appear there with what each counts, so the disagreement described above no - longer exists in that repository. The requirement stays as written: it states - what the corpus must hold, and the record of having *found* the disagreement - is what explains why gohai's prose changed. - -- **FR-015**: The corpus MUST state **ten** categories and MUST record that - gohai's prose says nine in two places. **Gap, and this one is an error rather - than a definition**: `README.md` line 111 says "65 collectors across 9 - categories" and `docs/collectors/README.md` line 3 says "across 9 categories". - Ten constants are declared and all ten are returned by at least one collector, - so there is no reading on which nine is right. - - **Corrected in gohai by gohai#201**, in its own change — which is where a - correction to that repository belongs, since nothing lands there from this - feature. That change also found a **third** defect this requirement had not: - the catalogue's legend defined `✅` twice, once as "implemented and tested" and - once as "planned", which made the Implemented column unreadable given that 62 - of its 65 rows are ticks. It surfaced only because reconciling 65 against 62 - meant reading the legend — which is the argument for pairing a count with the - command that produces it rather than stating the count alone. - -### What this inventory does not cover - -- **FR-016**: The corpus MUST state what it deliberately leaves out, so a later - reader can tell an omission from a decision: how any individual collector - gathers its facts, the CLI's flag surface beyond the node_exporter-style - `--collector.` form the catalogue documents, the OCSF field mapping in - `schemas/field-mapping.md`, and gohai's testing conventions, which are its own - `CONTRIBUTING.md`'s and are cited rather than copied. - -### Key Entities - -- **Collector**: A unit that gathers one area of system facts. Implements - exactly five methods, belongs to one of ten categories, declares its - dependencies, and may return nil where a platform does not support it. -- **Category**: One of ten fixed labels grouping related collectors, used to - enable sets of them at once. Closed by constants. -- **Registry**: What holds collectors, resolves their declared dependencies into - topological levels, and runs a selected set. -- **Prior results**: The typed outputs of collectors that have already run, - reachable by name through a generic accessor. -- **Catalogued collector**: An entry in the collector catalogue, which may be - implemented or deliberately not. 65 catalogued, 62 implemented. - -### The classification this baseline owed - -- **FR-017**: The corpus MUST classify every one of gohai's 68 documentation - pages as user-facing or contributor-facing, **by who reads it** rather than by - where it sits. This baseline was written before `system`'s - [002](../../../../system/specs/002-baseline-shape/spec.md) fixed the shape and - carried no classification, which 002's FR-025 requires and its T021 records as - owed. - - | Pages | # | Reader | Disposition | - | ---------------------------- | --: | ----------- | --------------------------- | - | `docs/collectors/*.md` | 64 | consumer | **stays**, per 002's FR-028 | - | `docs/README.md` | 1 | consumer | **stays**, it is the index | - | `docs/adding-a-collector.md` | 1 | contributor | **moves** | - | `docs/methodology.md` | 1 | contributor | **moves** | - | `docs/ocsf-validation.md` | 1 | contributor | **moves** | - | **Total** | 68 | | **65 stay, 3 move** | - - The 64 collector pages stay for the reason 002's FR-028 gives: each documents - what one collector returns, and its reader is somebody consuming the library - rather than changing it. Separating a collector's interface from its - description serves nobody. - -- **FR-018**: The corpus MUST state what the three contributor pages hold and - that **none has a corpus counterpart**, so this is 769 lines of contributor - knowledge on a consumer's documentation tree with nowhere to cite. - - | Page | Lines | Holds | - | ----------------------- | ----: | -------------------------------------------------------------------------------------------- | - | `methodology.md` | 382 | How gohai decides what a collector reads, which library it wraps, what its fields are called | - | `adding-a-collector.md` | 280 | The step-by-step walkthrough for building one | - | `ocsf-validation.md` | 107 | How to validate the OCSF output and vendor extension against the upstream schema | - - `methodology.md` is the substantive one, and it is **architecture rather than - a walkthrough**: what decides a collector's field names and which upstream - library it wraps is the design of the collection layer. gohai's memory - currently excludes "how any individual collector gathers its facts", which was - the right exclusion for 62 instances and the wrong one for the rule they - share. - -- **FR-019**: The corpus MUST record which of the three pages anything cites, - and it is not all of them. - - | Page | Cited by `CONTRIBUTING.md` | Cited by `docs/README.md` | - | ----------------------- | ------------------------------- | ------------------------- | - | `adding-a-collector.md` | lines 436 and 489 | yes | - | `methodology.md` | line 8, as "reference material" | yes | - | `ocsf-validation.md` | **nowhere** | yes | - - ```sh - grep -nE 'methodology\.md|adding-a-collector\.md|ocsf-validation\.md' CONTRIBUTING.md - ``` - - Two of the three are load-bearing for a contributor, so a move relocates their - content and must leave those citations resolving. That is the obligation 003's - FR-006 placed on osapi's move. - - `ocsf-validation.md` is reachable only from the index, which is a fourth - disposition the classification did not have a column for: not stranded, since - the index links it, but cited by nothing that tells a contributor when to read - it. It is a runbook nobody is sent to. - -- **FR-019a**: The corpus MUST record that `docs/README.md` **stays and is - edited**. It is the index and it links all four of its siblings, so three rows - leave it when the three pages move. A classification that calls the index - "stays" and stops there hides an edit inside a move, which is the shape of - defect osapi's 007 found four times. - -- **FR-020**: The corpus MUST record that the move is **a separate feature**, - per 002's FR-026 and FR-027: the baseline classifies and relocates nothing. - That feature is not open. Owner: this project. - - It is smaller than osapi's, which this said the opposite of. **Corrected - 2026-09-30**: osapi's move reconciles **three** statements of the UI totalling - 727 lines, not the 464 across two that this cited, because osapi's own - amendment of 2026-09-29 found a third at `ui/docs/architecture.md` holding 263 - lines. gohai's is 769 across three, so the two are close in volume rather than - one being clearly larger, and what distinguishes gohai's is that one of its - three is architecture the baseline explicitly excluded. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A reader who has not seen gohai's README or `docs/` answers three - questions from the corpus alone: *what does a collector have to implement?*; - *why might a collector not run, and how do I make it run?*; *how does one - collector use another's facts, and what may it assume about ordering?* Each - would break a consumer if unanswered — a collector that does not satisfy the - interface, a caller who cannot explain an absent fact, and a collector reading - an undeclared sibling. -- **SC-002**: Every count in the corpus is paired with a command, and running - the command reproduces the count. Four when this was written: 314, 205, 62 and - 10\. The two amendments of 2026-09-30 added more, and each carries its command - where it is stated rather than being listed here, because a criterion that - enumerates the counts has to be edited every time one is added and is wrong - until somebody does. -- **SC-003**: Both disagreements between gohai's prose and its code appear in - the corpus with both figures and the reason they differ, rather than as a - single corrected number. -- **SC-004**: `just test` passes in the specs repository. -- **SC-005**: Nothing in the gohai repository changes. `git -C gohai status` is - clean at the end of this feature. -- **SC-006**: gohai's `.specify/memory/spec.md` is non-empty once this feature - is archived, and every entry in it carries a source reference to this feature. - -## Assumptions - -- The audience is a consumer of `pkg/gohai` or a contributor to it, not an - operator running the CLI. What the CLI prints for an operator is gohai's own - documentation's job. -- The contract is what is worth stating; the collectors are what is worth - counting and citing. That is the judgement FR-004 records, and it is the - reason this inventory is short enough to read. -- gohai's own `CONTRIBUTING.md` and `docs/` continue to exist and stay where - they are. This inventory cites them; it does not replace them, and this - feature changes nothing in that repository. -- Counts were measured on `3132c9d`, the `main` commit at the time of writing. - They will date; the commands are what survives. -- The Time Machine extension, which CONTRIBUTING selects for producing an - inventory from existing code, was **not** used. Its installer requires an - interactive confirmation that this session cannot give, and it warns that it - bypasses the trusted extension catalogues. This inventory was written by - reading the repository, which CONTRIBUTING calls the slow path and does not - forbid. Whether to trial the extension remains open for the other four - unbaselined projects. diff --git a/history/gohai-002-move-contributor-docs/checklists/requirements.md b/history/gohai-002-move-contributor-docs/checklists/requirements.md deleted file mode 100644 index b597162..0000000 --- a/history/gohai-002-move-contributor-docs/checklists/requirements.md +++ /dev/null @@ -1,56 +0,0 @@ -# Specification Quality Checklist: Move gohai's contributor documentation into the corpus - -**Purpose**: Validate specification completeness and quality before proceeding -to planning - -**Created**: 2026-09-30 - -**Feature**: [spec.md](../spec.md) - -## Content Quality - -- [x] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [x] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [x] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [x] No implementation details leak into specification - -## Notes - -Two items pass differently here than the wording suggests, and both are how -every feature in this repository passes them. - -**"No implementation details" and "written for non-technical stakeholders."** -The requirements cite file paths, line counts and package names, because the -constitution's Verification principle requires evidence a reader can re-measure. -A specification about where documentation lives cannot name its subject without -naming files. The stakeholder is a contributor to gohai. - -**Line targets are estimates.** FR-002 and FR-011 give figures derived from -section headings rather than from a drafted reduction, and say so in -Assumptions. They are testable in the sense that matters: a target missed by a -wide margin means the split was drawn in the wrong place, which is the finding -the planning stage should surface. - -Two requirements deliberately leave something open rather than deciding it. -FR-017 picks a citation target for this feature's own links and leaves -`system`'s 002 FR-040 open for the organization. FR-009 states a condition on -the field-naming counts rather than asserting them, because the page hedges all -three and a hedged count fails `just memory-check`. diff --git a/history/gohai-002-move-contributor-docs/data-model.md b/history/gohai-002-move-contributor-docs/data-model.md deleted file mode 100644 index 5ff61cb..0000000 --- a/history/gohai-002-move-contributor-docs/data-model.md +++ /dev/null @@ -1,78 +0,0 @@ -# What each kind of content is, and where it goes - -**Feature**: `002-move-contributor-docs` | **Date**: 2026-09-30 - -The spec's FR-001 names three kinds. Planning found the first splits by depth, -which is what blocks the plan. This records the mapping as far as it is settled, -heading by heading, so the reduction is mechanical once the fragment question -resolves. - -## The kinds - -| Kind | Test | Where it lives | -| --------------------------- | -------------------------------------------------- | ------------------------------------------- | -| **Rule** | A contributor must do this or the change is wrong | gohai, imperative. **Contested: see plan.** | -| **Reasoning** | Why the rule, what it buys, what breaks without it | the corpus | -| **Procedure** | An ordered list of things somebody does | gohai, beside the code | -| **Per-collector reference** | A fact about one collector among 62 | gohai, beside the catalogue | - -The fourth is settled and uncontested: its key is the collector name, the same -key as `docs/collectors/`, and its reader is somebody using the library. - -## Heading by heading - -`docs/methodology.md` at gohai `f5eefe2`, 382 lines. - -| Heading | Lines | Kind | Disposition | -| ---------------------------------------------- | ------: | ----------------------- | ----------------------------------------------------------------- | -| Implementation methodology | 6-12 | Reasoning | moves, becomes the opening | -| Extend upstream, don't replace | 13-70 | Rule + Reasoning | **contested**, split by depth | -| Library-first principle | 71-87 | Rule + Reasoning | **contested**, split by depth | -| Per-collector library stack | 88-126 | Per-collector reference | stays | -| Cross-platform compilation: no build tags | 127-193 | Rule + Reasoning | **contested**; the reasoning cites osapi rather than restating it | -| Field naming | 194-260 | Reasoning + counts | moves, with the three figures corrected | -| MANDATORY: cross-reference Ohai's data sources | 261-298 | Rule + Reasoning | **contested**; the `gh api` invocation is procedure and stays | -| Data Sources | 299-344 | Per-collector reference | stays | -| Methodology work | 345-382 | Repository convention | stays, `global/tracking` governs it | - -The three contested rows are the plan's gate. Under the proposed resolution each -splits: a one-line imperative stays, the paragraphs explaining it move. Under a -reading where `global/documentation` governs outright, all three stay and the -move shrinks to the opening, field naming, and nothing else, which is roughly 75 -lines rather than 215. - -`docs/adding-a-collector.md`, 280 lines: nine steps, all Procedure, all staying. -Step 4 gains a citation to the registration limitation. - -`docs/ocsf-validation.md`, 107 lines: four steps plus two reference sections. -All staying. Gains a citation *from* `CONTRIBUTING.md`, which it has never had. - -## The new document's shape - -`components/gohai/.specify/memory/architecture/collectors.md`, in this order: - -1. What a collector is for, relative to the libraries it wraps. gohai aggregates - rather than reimplements, and a collector reshapes a maintained source into a - typed struct. -2. How a backing library is chosen. Seven positions, in order, each with what it - is canonical for. Stays a numbered list because the order is the content. -3. What an extension may do, and the two seams that make it testable, `avfs.VFS` - and `executor.Executor`. Carries the six-line Go snippet. -4. Why collector code compiles everywhere with no build tag, citing - [osapi's providers](../../../../osapi/.specify/memory/architecture/providers.md) - for the pattern rather than restating it. -5. What a field is called. Three tiers with the corrected counts and their - commands, and the observation that 752 of 950 follow neither standard. -6. What is checked against Ohai and what is not: the collection approach, not - the output shape. - -## Sizes, for checking the split afterwards - -| Thing | Estimate | How it is checked | -| -------------------------- | ----------: | ---------------------------------------------------- | -| `collectors.md` | ~215 lines | `wc -l`, and whether a reader answers SC-001 | -| `methodology.md` after | \<200 lines | `wc -l`, and whether a reader answers what it is for | -| Rules stated in two places | 0 | a fresh reading, per SC-002 | - -An estimate missed by a wide margin means the split was drawn in the wrong -place, which is what the spec's Assumptions say to watch for. diff --git a/history/gohai-002-move-contributor-docs/plan.md b/history/gohai-002-move-contributor-docs/plan.md deleted file mode 100644 index 33f7f7d..0000000 --- a/history/gohai-002-move-contributor-docs/plan.md +++ /dev/null @@ -1,188 +0,0 @@ -# Implementation Plan: Move gohai's contributor documentation into the corpus - -**Branch**: `docs/gohai-plan-and-tasks` | **Date**: 2026-09-30 | **Spec**: -[spec.md](spec.md) - -**Status**: **Blocked on a constitution gate.** Phase 0 is complete and the -design below is drafted, but the Constitution Check fails on -`global/documentation` in a way this feature cannot resolve for itself. No -`tasks.md` is produced, because the task list differs depending on which way the -conflict resolves. See [research.md](research.md) for the analysis and what is -owed. - -## Summary - -Move the design rules out of gohai's `docs/methodology.md` into a new memory -document, leave the procedure and the per-collector reference data where they -are, and make every citation resolve. Roughly 215 lines of 769 move. No -collector changes and no OCSF changes. - -Planning settled the two things the spec left open, and hit one thing the spec -did not anticipate. - -## Technical Context - -**Language/Version**: none. Markdown only. - -**Primary Dependencies**: mdformat for formatting, `just memory-check` for -counts, `just memory-docs` for the documentation contract. - -**Storage**: N/A - -**Testing**: `just test` in this repository, which runs mdformat over everything -outside `.claude` and `.specify`, 69 counts in memory against their commands, -and 26 memory documents against the contract. `just test` in gohai for its own -markdown. - -**Target Platform**: N/A - -**Project Type**: documentation - -**Performance Goals**: N/A - -**Constraints**: memory carries no `FR-` labels, no `MUST`, no user stories, no -em dashes. Every count in memory needs a command that reproduces it. - -**Scale/Scope**: one new memory document of roughly 215 lines, three reduced or -re-cited pages in gohai, one `CONTRIBUTING.md` sentence, one `docs/README.md` -table. - -## Constitution Check - -| Principle | Verdict | Note | -| ------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Documentation | **FAILS** | See below. This is the gate that blocks the plan. | -| Verification | passes | Every count moved carries a command; the three tier counts were measured rather than carried, and all three were wrong. | -| Tooling | passes | No tool version changes. mdformat and the two checkers already run. | -| Correction | **invoked** | Applying the spec showed the spec is too coarse. Under this principle the specification is corrected first, in its own change, before the plan settles. | -| Workflow | passes | Spec Kit throughout. The spec merged before planning began. | -| Repositories | passes | The corpus statement merges here, the reduction lands in gohai's own PR. | -| Tracking | passes | No issue is needed; this feature's task list tracks the work once it exists. | -| Baseline | passes | The new document is a subject beside `spec.md`, which is the tree shape the fragment asks for. | - -### Why Documentation fails - -`global/documentation` opens: *"A repository states in full the conventions -binding it. A reference to guidance held elsewhere does not stand in place of -stating them: a reviewer reading a pull request in a browser, a contributor -working offline, and an agent with a single checkout each see only that -repository."* - -Three of the things this feature moves are conventions binding a contributor, -stated as such in the page, with the word MANDATORY on two of them: - -- use the upstream library for what it covers, and extend on top rather than - replacing it -- never roll your own parsing when a library covers it -- no `//go:build` tag anywhere in collector code - -Moving those into this repository leaves a contributor with only gohai checked -out unable to read the conventions binding their change, which is the exact harm -the fragment names. Leaving them stated in gohai and *also* stating them here is -two statements of one rule, which `global/baseline` and the whole programme -forbid. - -**Neither fragment resolves the conflict.** `global/documentation` is about -conventions and tool settings. `global/baseline` is about what memory is and how -it is shaped. Nothing says which governs a rule that is both a convention -binding a contributor and a description of the architecture. - -**It is not new, and not gohai's.** osapi's 005 moved the same kind of rule off -its site into the corpus, and osapi's `CONTRIBUTING.md` states none of them: - -```sh -cd ~/git/osapi-io/osapi -grep -cE 'strict-server|no build tags|upsert|idempot|provider contract' CONTRIBUTING.md # 0 -``` - -So three merged osapi features are in the same tension and nobody noticed, which -is what a fourth application of the rule is for. - -### The proposed resolution, which `system` must ratify - -Distinguish the **rule** from its **reasoning**, and let each live once: - -| Kind | Example | Lives in | -| ------------- | ----------------------------------------- | ----------------- | -| The rule | "no `//go:build` tag in collector code" | gohai, imperative | -| The reasoning | why, what it buys, what breaks without it | the corpus | - -That is one statement of the rule and one statement of the reasoning, not two -statements of either. A contributor with one checkout reads the rule and can -comply. A contributor who wants to know why follows one link. - -This is a change to `global/documentation`, so it recomposes into six -constitutions and belongs to `system` in its own feature, for the reason 002's -FR-037 gives about fragments. Until it is ratified, this plan's mapping of which -headings move cannot be final, because the answer changes for three headings. - -## Project Structure - -### Documentation (this feature) - -``` -components/gohai/specs/002-move-contributor-docs/ -├── spec.md merged at specs#218 -├── plan.md this file -├── research.md Phase 0: the measured counts, and the conflict -├── data-model.md Phase 1: what each kind of content is, and where it goes -└── checklists/ - └── requirements.md merged at specs#218 -``` - -### Source (what the implementation touches) - -``` -components/gohai/.specify/memory/ -├── spec.md gains a link, loses one exclusion -└── architecture/ - └── collectors.md NEW, the moved design rules - -# in the gohai repository, its own PR, after the above merges -docs/methodology.md reduced to the reference tables -docs/adding-a-collector.md nine steps kept, rules replaced by links -docs/ocsf-validation.md unchanged -docs/README.md three rows reworded -CONTRIBUTING.md line 8 reworded, one citation added -``` - -## What planning settled - -**The section order of `collectors.md`**, and what its first line answers. Every -other memory document opens with what the thing is. This one is about a layer -rather than a repository, so it opens with what a collector is *for* relative to -the libraries it wraps: gohai is an aggregator, and a collector's job is to -reshape a well-maintained source into a typed struct. Order: what a collector -wraps and why → how a library is chosen → what an extension may do → compiling -everywhere → what a field is called → what is checked against Ohai. - -**The library decision order stays a numbered list.** Seven positions, each with -what it is canonical for. It is an ordered decision procedure rather than -reference data: the order is the content, and a table would present seven -equals. - -**The extension pattern's code block moves.** Six lines of Go showing the -library call followed by the extension on top. `global/baseline` names a code -block as documentation, and this one states the rule more clearly than a -sentence would. - -**The per-collector library stack table does not move**, which the spec settled, -and planning adds the reason it is not a close call: it has 62 rows keyed by -collector, the same key as the catalogue, and its reader is the same reader. - -**SC-002 is checked by a reading, and the reader is a fresh agent** given the -reduced pages and the new document and asked one question: name any rule stated -in both. That is the same check every baseline in the programme used, and it is -the only one that catches a restatement in different words. - -**The two PRs, in order.** The corpus statement merges here first, then gohai's -reduction. That is the sequence osapi's backfill proved: reducing the page first -would leave the rule stated nowhere for as long as the corpus PR sits in review. - -## Complexity Tracking - -| Thing | Why it is not simpler | -| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| A new `architecture/` directory for gohai | 215 lines of one subject in a 220-line entry point makes the entry point half about that subject. | -| Two PRs across two repositories | `global/repositories` puts the corpus here and the code there, and FR-026 of osapi's 005 fixes the order. | -| A blocked plan rather than a decided one | The Correction principle. Deciding a constitutional conflict inside a feature plan is how a rule gets changed where nobody reviews it as a change of rule. | diff --git a/history/gohai-002-move-contributor-docs/research.md b/history/gohai-002-move-contributor-docs/research.md deleted file mode 100644 index cbdfb14..0000000 --- a/history/gohai-002-move-contributor-docs/research.md +++ /dev/null @@ -1,112 +0,0 @@ -# Research: moving gohai's contributor documentation - -**Feature**: `002-move-contributor-docs` | **Date**: 2026-09-30 - -Two questions the spec left open. The first is answered and produced a defect in -gohai. The second is not answerable by this feature. - -## 1. Can the field-naming ladder's counts be measured? - -**Decision**: yes, and the corpus states 107, 91 and 752 with their commands. - -**Rationale**: `docs/methodology.md` states the three tiers as roughly 108, 74 -and 768 fields, hedged with a tilde and no command, which is why FR-009 required -them measured or dropped. They are measurable: `schemas/field-mapping.md` -carries a `Tier` column for every one of its 950 rows. - -| Tier | `methodology.md` says | Measured | Off by | -| -------------------- | --------------------: | -------: | -----: | -| T1, OCSF | ~108 | 107 | 1 | -| T2, OTel semconv | ~74 | 91 | 17 | -| T3, gohai convention | ~768 | 752 | 16 | -| **Total** | **950** | **950** | **0** | - -```sh -grep -cE '^\|[^|]*\|[^|]*\|[^|]*\| T1 +\|' schemas/field-mapping.md # 107 -grep -cE '^\|[^|]*\|[^|]*\|[^|]*\| T2 +\|' schemas/field-mapping.md # 91 -grep -cE '^\|[^|]*\|[^|]*\|[^|]*\| T3 +\|' schemas/field-mapping.md # 752 -grep -cE '^\| [a-z]' schemas/field-mapping.md # 950 -``` - -**The finding is the shape of the error, not the error.** The total is exactly -right and every individual figure is wrong, T2 by 23 percent. Somebody moved -fields between tiers and preserved the sum without revising the prose, so a -check on the total would pass and has nothing to say. This is the second time in -the programme that a figure was **wrong when written or wrong when revised, -rather than stale**; `system`'s memory records the first. - -The tilde is what let it through. A hedged number reads as an estimate and so -nobody expects it to be checkable, which is exactly why `global/baseline` -requires a count to carry its command and `just memory-check` fails one that -does not. - -**Consequence for the implementation**: correcting `methodology.md`'s three -figures is part of the reduction rather than a separate change, because the page -is being rewritten anyway and leaving three wrong numbers in the part that stays -would be the remainder FR-027 of `system`'s 002 forbids. Recorded as a gap owned -by gohai. - -**Alternatives considered**: stating the ladder without numbers. Rejected: the -distribution is the interesting fact. 752 of 950 fields follow neither standard, -which tells a contributor that the third tier is the common case and the first -two are the exception, and that is worth a reader's time in a way "three tiers" -is not. - -## 2. Does moving a contributor convention into the corpus satisfy `global/documentation`? - -**Decision**: not answerable here. The plan is blocked and `system` owes a -fragment change. - -**Rationale**: `global/documentation` requires a repository to state in full the -conventions binding it, and says a reference elsewhere does not substitute, -naming the reviewer in a browser, the offline contributor and the agent with one -checkout. Three of the things this feature moves are conventions binding a -contributor, two of them marked MANDATORY in the page. Moving them produces -exactly the harm the fragment names; stating them in both places produces the -two statements `global/baseline` forbids. - -Neither fragment says which governs a rule that is both a convention and a -description of the architecture. - -**It is not new and not gohai's.** osapi's 005 moved the same kind of rule off -its published site into the corpus, and osapi's `CONTRIBUTING.md` states none of -them: - -```sh -cd ~/git/osapi-io/osapi -grep -cE 'strict-server|no build tags|upsert|idempot|provider contract' CONTRIBUTING.md # 0 -``` - -Three merged osapi features sit in the same tension. Nobody noticed because -nobody applied the fragment to a move until this one. That is the argument for -applying a rule a fourth time rather than assuming three applications settled -it. - -**Proposed resolution**, for `system` to ratify rather than for this feature to -adopt: the rule is stated once where a contributor meets it, in the repository, -imperative and short. The reasoning is stated once in the corpus. One statement -of each rather than two of either. A contributor with one checkout can comply; -one who wants to know why follows a link. - -**Alternatives considered**: - -- *Move everything and accept the offline contributor cannot read the rules.* - Rejected: it is the harm the fragment exists to prevent, and a fragment - overruled silently by a feature is worse than a fragment that is wrong. -- *Move nothing and close the feature.* Rejected: the architecture of the - collection layer is genuinely absent from the corpus, which the amended - baseline records as FR-025, and the seven-position library order is not a - convention by any reading. -- *Decide it in this plan.* Rejected under the Correction principle. A - constitutional conflict resolved inside a feature plan is a rule changed where - nobody reviews it as a change of rule, which is the failure the principle - names. - -## What is owed, and by whom - -| Owed | Owner | -| -------------------------------------------------------------------------------------------------------- | --------------------------- | -| A `global/documentation` change distinguishing a rule from its reasoning | `system`, its own feature | -| An amendment to this feature's spec once that lands, since FR-001's split becomes three-way plus a depth | this project | -| `tasks.md` | this project, after both | -| Correcting `methodology.md`'s three tier figures | gohai, inside the reduction | diff --git a/history/gohai-002-move-contributor-docs/spec.md b/history/gohai-002-move-contributor-docs/spec.md deleted file mode 100644 index f46729f..0000000 --- a/history/gohai-002-move-contributor-docs/spec.md +++ /dev/null @@ -1,305 +0,0 @@ -# Feature Specification: Move gohai's contributor documentation into the corpus - -**Feature Branch**: `docs/gohai-the-move` - -**Created**: 2026-09-30 - -**Status**: Implemented 2026-09-30. The design rules are in -`components/gohai/.specify/memory/architecture/collectors.md`. The three -one-line rules stay in `docs/methodology.md` where a contributor with one -checkout meets them, which is what `system`'s 003 arrived at and what unblocked -the plan. FR-009 is answered: the field naming counts are 107, 91 and 752, -measured from the Tier column of `schemas/field-mapping.md`, and the page's -roughly 108, 74 and 768 summed correctly while every figure was wrong. - -**Input**: Move the three pages gohai's baseline classified contributor-facing. -This is the move `system`'s 002 FR-027 requires as its own feature, authorized -by the classification merged at specs#205. - -## What this specification is, and what it is not - -It decides **what of 769 lines is architecture and what is procedure**, and -moves only the first. The classification at specs#205 named three pages and did -not open them; opening them shows they hold three different kinds of content, -and treating "contributor-facing" as a synonym for "belongs in the corpus" would -move a nine-step walkthrough into a document nobody walks through. - -The precedent is osapi's. Its 005 reduced `development/adding-an-api-domain.md` -to a citation index rather than deleting it, because a procedure belongs beside -the code while the rules it obeys belong in the corpus stated once. This feature -applies that split to gohai. - -Nothing here changes a collector, the OCSF output, or the 64 collector pages. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - A contributor learns which library to wrap (Priority: P1) - -Somebody adding a collector needs to know that gohai wraps upstream libraries -rather than reimplementing them, in what order to try them, and what to do when -none covers a field. Today that is on a documentation page with no corpus -counterpart, so the corpus a skill reads first says nothing about it. - -**Why this priority**: it is the rule all 62 collectors follow, and the reason -gohai's baseline excluded "how any individual collector gathers its facts" was -that 62 instances are not architecture. The rule they share is. - -**Independent Test**: a reader given only gohai's memory names the decision -order for choosing a backing library and says what an extension may do. - -**Acceptance Scenarios**: - -1. **Given** gohai's memory, **When** a contributor asks which library to use - for static hardware shape, **Then** the answer is ghw, with the reason. -2. **Given** gohai's memory, **When** they ask what an extension may read from, - **Then** the answer names `avfs.VFS` and `executor.Executor` and says why. - -### User Story 2 - A contributor follows the walkthrough and is warned (Priority: P2) - -Somebody writing a collector works through nine steps. Step 4 is registering it -in `pkg/gohai/gohai.go`, and gohai's memory records that the error from that -registration is discarded and the test its comment defers to does not exist. The -walkthrough sends a contributor to the one step whose failure is silent and says -nothing about it. - -**Why this priority**: the walkthrough stays where it is, so this is a change to -what it links to rather than to where it lives. - -**Independent Test**: the reduced walkthrough's step 4 links to the limitation -and does not restate it. - -### User Story 3 - A runbook nobody is sent to gets a reader (Priority: P3) - -`docs/ocsf-validation.md` is a four-step procedure for validating gohai's OCSF -output against the upstream schema. Nothing cites it except the documentation -index, so a contributor changing a field has no path to it. - -**Why this priority**: the smallest of the three and the only one whose problem -is its absence from a citation rather than its location. - -### Edge Cases - -- A reader arrives at `docs/methodology.md` from an old bookmark. The address - has to resolve and the page has to read as a whole rather than as what is - left. -- A reader arrives at `CONTRIBUTING.md` line 8, which calls `methodology.md` - "reference material". After the move most of that reference material is - elsewhere, so the sentence has to change or it misdescribes its own link. -- The three-tier field-naming ladder carries three counts, roughly 108, 74 and - 768 fields. They are stated with a tilde and no command. Moving them into the - corpus makes them subject to `just memory-check`, which fails a count without - one. - -## Requirements *(mandatory)* - -### Section 1 — the three-way split - -- **FR-001**: The corpus MUST state that the three pages hold three kinds of - content, and that only the first moves: - - | Kind | Example | Disposition | - | ----------------------- | ------------------------------------------------- | ----------- | - | Design rule | "wrap upstream, do not reimplement" | **moves** | - | Procedure | the nine steps, the four validation steps | **stays** | - | Per-collector reference | the library-stack table, the Data Sources cascade | **stays** | - - The third disposition is FR-028's argument from `system`'s 002 applied again: - a table of which library each of 62 collectors wraps has the same reader as - the collector catalogue, which is somebody using the library, and separating - it from the catalogue beside it serves nobody. - -- **FR-002**: The corpus MUST state the measured split, so the move can be - checked rather than asserted. Measured at gohai `f5eefe2`: - - | Page | Lines | Moves | Stays | - | ---------------------------- | ------: | -------: | -------: | - | `docs/methodology.md` | 382 | ~215 | ~167 | - | `docs/adding-a-collector.md` | 280 | 0 | 280 | - | `docs/ocsf-validation.md` | 107 | 0 | 107 | - | **Total** | **769** | **~215** | **~554** | - - ```sh - wc -l docs/methodology.md docs/adding-a-collector.md docs/ocsf-validation.md - ``` - - So the honest figure for this move is roughly **215 lines, not 769**, and the - classification that produced 769 was right about who reads the pages and - silent about what the pages contain. That gap between "who reads it" and "what - it is" is this feature's finding, and it belongs in `system`'s 002 as a limit - on what a classification can decide. - -### Section 2 — where the architecture lands - -- **FR-003**: The moved content MUST land in a **new subject document**, - `components/gohai/.specify/memory/architecture/collectors.md`, rather than - being appended to `spec.md`. gohai's memory is one document of about 220 - lines; adding 215 lines of a single subject to it makes the entry point half - about one subject. `global/baseline` says a subject with enough in it to - explain gets its own document, and this is the first gohai subject that does. - -- **FR-004**: `spec.md` MUST gain a link and lose its exclusion. Its "Not - covered here" says "How any individual collector gathers its facts", which - stays true of the 62 instances and stops being true of the rule they share. - The exclusion is reworded rather than deleted, because the instances are still - excluded. - -- **FR-005**: The new document MUST state the decision order for choosing a - backing library, as the ordered list it is, with what each library is - canonical for. Seven positions, ending in gohai's own extension as a last - resort. - -- **FR-006**: The new document MUST state the rule that makes extensions - testable: an extension reads files through `avfs.VFS` and runs commands - through `executor.Executor`, never `os.ReadFile` or `exec.Command` in a - `Collect` method, so a test never touches the real host. This is the rule a - reviewer checks and the one a new collector is most likely to break. - -- **FR-007**: The new document MUST state the no-build-tags pattern and its - consequence: collector code compiles on every target platform with no - `//go:build` tag anywhere, so `go test ./...` on any machine compiles and runs - every collector's tests. It MUST record that this is osapi's pattern from - `internal/provider/`, cited rather than re-explained, because - [osapi's providers](../../../osapi/.specify/memory/architecture/providers.md) - state it and two statements of one rule is what the corpus forbids. - -- **FR-008**: The new document MUST state the three-tier field-naming ladder, - OCSF first, OpenTelemetry semantic conventions second, gohai convention third, - with what the third tier's conventions are. - -- **FR-009**: The ladder's counts MUST be measured or dropped. The page states - roughly 108, 74 and 768 fields per tier, with a tilde and no command. Either a - command reproduces each or the corpus states the shape without the numbers. A - hedged count in memory fails `just memory-check`, and a count nobody can - reproduce is what the Verification principle forbids. - -- **FR-010**: The Ohai cross-reference obligation MUST be classified and the - spec MUST say which it is. It reads as a contributor obligation, "read Ohai's - plugin before writing code", and its content is a design rule: gohai matches - Ohai's *collection approach* and not its output shape, because Ohai carries - years of distro-specific bug fixes and its JSON shape is a Ruby artifact. The - rule moves; the instruction to run `gh api` to fetch the files stays with the - walkthrough. - -### Section 3 — what the pages become - -- **FR-011**: `docs/methodology.md` MUST be reduced to what remains its own: the - per-collector library stack table, the Data Sources cascade, and the - `methodology-gap` issue convention, under an opening that says where the rules - went. Target: under 200 lines, reading as a reference table with context - rather than as a page with holes in it. - -- **FR-012**: `docs/adding-a-collector.md` MUST keep all nine steps and gain - citations. It is a procedure, it belongs beside the code, and every rule it - applies is stated once in the corpus after this feature. Target: no reduction - beyond replacing restated rules with links. - -- **FR-013**: `docs/adding-a-collector.md` step 4 MUST cite the registration - limitation rather than restate it. The step says register the collector in - `pkg/gohai/gohai.go`; the corpus says the error from that call is discarded - and the test its comment defers to does not exist. A contributor at step 4 is - the only reader who needs that, and they currently have no way to learn it. - -- **FR-014**: `docs/ocsf-validation.md` MUST stay whole and gain a citation from - `CONTRIBUTING.md`. Reducing it would be wrong: there is nothing in it to move, - and its problem is that nothing sends anybody to it. Owner of the citation: - this feature. - -- **FR-015**: `docs/README.md` MUST be edited rather than left alone. It links - all four of its siblings and its descriptions of the three describe what they - held before, so its rows change even though no page it links disappears. - -### Section 4 — the citations - -- **FR-016**: Every address MUST still resolve. Three pages keep their paths, so - what changes is what `CONTRIBUTING.md`'s three citation sites point at: - - | Site | Today | After | - | -------- | ------------------------------------------- | --------------------------------------------------- | - | line 8 | `docs/methodology.md`, "reference material" | the corpus, for the rules; the page, for the tables | - | line 436 | `docs/adding-a-collector.md` | unchanged | - | line 489 | `docs/adding-a-collector.md` | unchanged | - -- **FR-017**: The corpus MUST state which of the two possible citation targets - this feature picked and why, without deciding it for the organization. - `system`'s 002 FR-040 records that a citation can point at a feature - specification, which keeps its requirement numbers forever, or at memory, - which describes what is true today, and that nothing chooses. This feature - points its own new links at **memory**, because the reader arriving from - `CONTRIBUTING.md` is about to write code and wants the current description. - FR-040 stays open. - -### Section 5 — measurements - -- **FR-018**: The corpus MUST carry the counts this feature depends on, each - with its command, measured at gohai `f5eefe2`: - - | Measurement | Value | Command | - | -------------------------------- | ----: | ------------------------------------------------------------------------------ | - | Contributor pages | 3 | the classification at specs#205 | - | Their total lines | 769 | `wc -l docs/methodology.md docs/adding-a-collector.md docs/ocsf-validation.md` | - | `CONTRIBUTING.md` citation sites | 3 | `grep -cE 'methodology\.md\|adding-a-collector\.md' CONTRIBUTING.md` | - | Collector pages, unaffected | 64 | `ls docs/collectors/*.md \| wc -l` | - -### Section 6 — gaps - -- **FR-019**: **Gap**: a classification by reader cannot predict what moves. - gohai's classification was correct and produced 769 lines; the move is roughly - 215\. `system`'s 002 FR-036 already records that page count does not predict - move size; this adds that neither does the classification, because "who reads - this" and "what kind of content is this" are different questions and only the - second decides where content lives. Owner: `system`'s 002. - -- **FR-020**: **Gap**: the field-naming ladder's three counts cannot be - reproduced from anything stated. Owner: this feature, under FR-009, and if no - command can produce them the corpus states the ladder without them rather than - carrying a number nobody can check. - -### Section 7 — what this feature excludes - -- **FR-021**: The corpus MUST state what it leaves out: what any individual - collector reads, which stays in the per-collector Data Sources and the - catalogue; the nine steps themselves, which stay beside the code; the four - OCSF validation steps, likewise; deciding 002's FR-040 for the organization; - and adding a link checker, which would be the mechanism that keeps FR-016 true - and is its own change in its own repository. - -### Key Entities - -- **Design rule**: a statement about how every collector behaves. Moves. -- **Procedure**: an ordered list of things a contributor does. Stays. -- **Per-collector reference**: a fact about one collector among 62. Stays. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A reader given only gohai's memory names the library decision - order, says what an extension may read through, and states the field-naming - ladder's three tiers in order. None of the three is answerable from memory - today. -- **SC-002**: No rule is stated in both the corpus and a gohai page. Checked by - reading the reduced pages against the new document, not by counting lines. -- **SC-003**: Every count in the new document reproduces, verified by - `just memory-check` in the specs repository. -- **SC-004**: `docs/methodology.md` reads as a whole. The test is a reader - asking what the page is for and answering from its opening rather than from - what is missing. -- **SC-005**: All three page addresses resolve, and `CONTRIBUTING.md`'s three - citation sites resolve to something that states what the citing sentence - claims. -- **SC-006**: `just test` passes in the specs repository, including - `just memory-docs`, which fails on an `FR-` label, a `MUST`, or an em dash in - anything under `.specify/memory/`. - -## Assumptions - -- The line targets in FR-011 and FR-002 are estimates from the section headings - rather than from a drafted reduction. The planning stage refines them; a - target missed by a wide margin is a signal that the split was drawn in the - wrong place. -- `docs/methodology.md`'s "Methodology work" section, which describes the - `methodology-gap` issue label, is treated as a repository convention and - stays. It describes how gohai tracks work, which `global/tracking` governs and - the corpus does not restate. -- gohai's memory gains its first `architecture/` directory here. osapi is the - only component with one today, and the shape is the same. diff --git a/history/nats-client-001-nats-client-baseline/checklists/requirements.md b/history/nats-client-001-nats-client-baseline/checklists/requirements.md deleted file mode 100644 index e7a2dd1..0000000 --- a/history/nats-client-001-nats-client-baseline/checklists/requirements.md +++ /dev/null @@ -1,48 +0,0 @@ -# Specification Quality Checklist: A baseline for nats-client - -**Purpose**: Validate specification completeness and quality before proceeding -to planning - -**Created**: 2026-09-30 - -**Feature**: [spec.md](../spec.md) - -## Content Quality - -- [ ] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [x] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [ ] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [ ] No implementation details leak into specification - -## Notes - -Three items fail **by design**, and the same reason covers all three: this is a -baseline, so file paths, method names and counts *are* its content, and the -Verification principle requires the command that measures each one. A baseline -that passed the no-implementation-details item would be a baseline describing -nothing. Every feature in this repository fails these items for that reason, and -recording it keeps a deliberate departure distinguishable from an oversight. - -One item is worth naming because the check found something. **Requirements are -testable** passes only because FR-016 states what two of the measurements -returned before their exclusions were added. Without it, the corrected figures -would be indistinguishable from figures that had been right the first time, and -a reader could not tell whether the commands had ever been run. diff --git a/history/nats-client-001-nats-client-baseline/plan.md b/history/nats-client-001-nats-client-baseline/plan.md deleted file mode 100644 index 37f537c..0000000 --- a/history/nats-client-001-nats-client-baseline/plan.md +++ /dev/null @@ -1,150 +0,0 @@ -# Implementation Plan: A baseline for nats-client - -**Branch**: `001-nats-client-baseline` | **Date**: 2026-09-30 | **Spec**: -[spec.md](spec.md) - -**Input**: Feature specification from -`components/nats-client/specs/001-nats-client-baseline/spec.md` - -## Summary - -State what `nats-client` is, so its memory stops holding only a constitution. -Eighteen requirements, every one of the form "the corpus MUST state X". - -Unit 7 of twelve, and the first written **after** the shape had been tested. The -two baselines before it each found something about the shape itself — osapi's -that a hub's contract section is mostly citation, `osapi-justfiles`' that a -contract need not be code and that three section headings had drifted. This one -inherits both corrections and reports whether inheriting them worked, which is -FR-018. - -**Nothing lands in the `nats-client` repository.** The deliverable is the -inventory. - -## Technical Context - -**Language/Version**: Markdown. The repository being inventoried is Go — 33 -files, 20 of them not tests, `go 1.26.0` — and nothing in it changes. - -**Primary Dependencies**: None. The corpus depends on nothing at runtime. The -repository being inventoried depends on no other repository in the organization. - -**Storage**: `components/nats-client/specs/` for the specification — a directory -this feature creates, since it is the project's first — and -`components/nats-client/.specify/memory/` for what archival consolidates into. - -**Testing**: `just test` in the specs repository. There is no code to unit test. -Every count carries the command that reproduces it, and re-running those nine -commands is what checks the inventory — the formatting gate cannot tell a right -count from a wrong one, and FR-016 records two counts that were wrong. - -**Target Platform**: The corpus. - -**Project Type**: Documentation. - -**Constraints**: No change to the `nats-client` repository. No count without its -command. Section names verbatim from 002. Every gap recorded with both sides and -an owner, none corrected here. - -**Scale/Scope**: One repository inventoried, the second smallest with Go code. A -contract of one constructor, 25 methods and 9 types; one dependency edge, stated -from both ends. - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -| Principle | How this feature satisfies it | -| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Documentation** | The eight documentation pages are cited rather than copied; what the wrapper *adds* is stated once, here. | -| **Verification** | Nine counts, each with its command — and **two were wrong on the first run**, both because of a missing exclusion rather than bad arithmetic. FR-016 records both. | -| **Tooling** | Nothing provisioned. FR-014 records the absent tags as the rule they strain rather than as a violation. | -| **Correction** | Three gaps with owners, none corrected. FR-016 records this feature's own measurement errors rather than presenting the corrected figures as though they were the first ones. | -| **Workflow** | Stages 1 to 4 in one branch, which is what CONTRIBUTING's lifecycle table specifies and what the two preceding units did across three pull requests instead. | -| **Baseline** | This memory holds no decisions yet, so the baseline is the whole of it — the condition `global/baseline` describes in reverse. | -| **Repositories** | The edge is verified from **both ends** rather than from this repository alone, which is what would have caught `osapi-justfiles`' missing seventh consumer. | -| **Tracking** | Nothing becomes an issue. The two gaps implying work name their owner. | - -**Result**: no violations. - -## What this unit inherits, and whether inheriting worked - -Three corrections were available to it, and the plan records which were applied -because a later baseline will ask. - -**The section names came from a corrected file.** `osapi-justfiles`' FR-021a -recorded that three of its seven headings had drifted from 002's names — each an -improvement in isolation — and predicted that later baselines would copy the -file rather than re-read 002. That is exactly what happened here, and because -the file had been corrected first, the drift did not propagate. FR-018 records -it: the prediction was right about the mechanism and the correction reached this -unit in time. - -**The contract section did not need reinterpreting.** `osapi-justfiles` had to -decide that a contract need not be code. `nats-client` exposes Go, so section 4 -is the ordinary case — and the decision that mattered instead was the opposite -one: the contract is not only the exported surface, because **upstream NATS -types leak through it** (FR-007). A consumer depends on `jetstream.Msg` whether -or not this repository names it. - -**The both-ends check came from a failure.** `osapi-justfiles`' baseline listed -its consumers from the frame of the six components and missed `specs`, which its -own task list caught. Here the single edge was verified from -`nats-client/go.mod` *and* `osapi/go.mod` before it was written down. One edge -is a small test of the habit, and the habit is what matters for -`osapi-orchestrator`, which has more. - -## Project Structure - -### Documentation (this feature) - -```text -components/nats-client/specs/001-nats-client-baseline/ -├── spec.md # 18 requirements, 7 outcomes -├── plan.md # This file -├── checklists/ -│ └── requirements.md -└── tasks.md -``` - -### What is being inventoried - -```text -nats-client/ # nothing here changes -├── pkg/client/ # the one package: 11 non-test files -│ ├── types.go # Options, AuthOptions, AuthType -│ ├── connect.go # connecting and authenticating -│ ├── connection.go # connection state and lifecycle -│ ├── core.go # core publish and subscribe -│ ├── jetstream.go # streams -│ ├── consumer.go # consumers -│ ├── kv.go, kv_stream.go # key-value, and key-value with publish -│ ├── objectstore.go # object stores -│ └── mocks/ # generated; not the contract -├── examples/ # 5 runnable, one per auth mode and pattern -└── docs/ # 8 pages, one per part of the surface -``` - -**Structure Decision**: no corpus subject file beyond `spec.md`, and no skill -gains a reference. No skill in the specs repository reaches NATS client -construction; a citation for a reader who does not exist is the rule invented to -fill a template. - -## What the verification checks, and what it cannot - -The nine commands check the **counts**, and FR-016 exists because two of them -were wrong before they were run. Three things they do not check: - -- **Whether the 25 methods are the right 25.** They are the contract as it - stands; whether the wrapper should expose more or fewer is a judgement. -- **Whether the leaked upstream types matter to a consumer.** FR-015 records - that the leak is undocumented; whether it is a problem depends on what a - future consumer expects. -- **Whether the eight documentation pages are accurate.** They are cited as the - place a method's behaviour is described, not verified against the code. A page - that has drifted from its method would not show up here, and that is the gap - class osapi's baseline found three of. - -## Complexity Tracking - -> No Constitution Check violations, so this table is empty. diff --git a/history/nats-client-001-nats-client-baseline/spec.md b/history/nats-client-001-nats-client-baseline/spec.md deleted file mode 100644 index 0d2feb6..0000000 --- a/history/nats-client-001-nats-client-baseline/spec.md +++ /dev/null @@ -1,391 +0,0 @@ -# Feature Specification: A baseline for nats-client - -**Feature Branch**: `001-nats-client-baseline` - -**Created**: 2026-09-30 - -**Status**: Completed - -**Input**: `nats-client`'s `.specify/memory/` holds only a constitution composed -from `.charter/`, so it states what binds every repository and nothing about -this one. Unit 7 of the baseline programme `system`'s -[002](../../../../system/specs/002-baseline-shape/spec.md) defines, and the -first of the two NATS repositories osapi imports. - -## What this specification is, and what it is not - -**Its subject is a description, not a change.** Nothing lands in the -`nats-client` repository: no Go code changes, no method is renamed, no page -moves. What this produces is an inventory of how the repository behaves today, -which lives in this feature directory and reaches memory through archival. - -**Prose was a lead, never a source.** The repository carries a 62-line README -and eight documentation pages. None was transcribed. Every count came from a -command, the commands are given so a reader re-measures, and **the first attempt -at one of them was wrong** — recorded in FR-016 rather than quietly corrected, -because it is the same mistake osapi's baseline made. - -**It is read while reading osapi's.** This repository exists to be imported, so -its section 2 is written to be read alongside -[osapi's baseline](../../../osapi/specs/006-osapi-baseline/spec.md), whose -FR-003 states the same edge from the other end. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - A consumer knows what the wrapper gives them (Priority: P1) - -Somebody working in osapi, or writing a new consumer, can state what -`nats-client` does for them that the upstream NATS client does not — and what -they still have to do themselves. - -**Why this priority**: it is the question a wrapper exists to answer, and the -only one whose answer is not in the upstream library's own documentation. - -**Independent Test**: a reader given the corpus alone states what the wrapper -adds, names the three authentication modes, and says which JetStream primitives -it covers — without opening `pkg/client`. - -**Acceptance Scenarios**: - -1. **Given** the corpus, **When** a reader asks how a consumer authenticates, - **Then** the three modes and what each needs are stated. -2. **Given** the corpus, **When** a reader asks what happens when the connection - drops, **Then** the answer is stated — including that the wrapper contributes - nothing to it, which is FR-012a. **This scenario originally read "rather than - left to the upstream library's defaults", and the answer turned out to be - that it is exactly that.** The scenario asserted a conclusion before the code - had been read. - -______________________________________________________________________ - -### User Story 2 - A breaking change is recognisable before it is made (Priority: P1) - -Somebody changing `pkg/client` knows what osapi depends on, and that osapi pins -a commit rather than a tag — so nothing breaks there until somebody bumps it. - -**Why this priority**: the consequence is delayed rather than absent, which is -the more dangerous shape. A rename lands green here and breaks at the bump, by -which time the change is no longer in view. - -**Independent Test**: this specification states the exported surface as a -contract, and states the pin. - -______________________________________________________________________ - -### User Story 3 - The seven sections read the same as every other baseline (Priority: P2) - -Somebody reading all six baselines in order finds the same answer in the same -place each time. - -**Why this priority**: lower on its own and the reason the programme exists. -`osapi-justfiles`' baseline found that three section **headings** had drifted -from `system`'s names while the order and meaning were right, and this is the -first baseline written after that was corrected. - -### Edge Cases - -- **A wrapper's contract is what it adds, not what it exposes.** Twenty-five - `Client` methods and nine exported types are the surface; a consumer depends - on those *plus* the upstream `jetstream.Msg` and `nats.Conn` types that leak - through them. Stating only the former would describe the package and not the - dependency. -- **A count that includes a dependency's files.** The first measurement of - documentation pages returned 9, not 8, because `docs/node_modules/` holds a - vendored `README.md`. 002 recorded 8 and 002 was right. Recorded in FR-016 - because it is the same class of error as osapi's 219-against-221, and the - correction here is the exclusion rather than a definition. -- **A test suite is exported.** Fourteen of the twenty-three exported type names - in `pkg/client` are `*TestSuite` types in `_test.go` files, so an - exported-surface count taken without excluding tests overstates the contract - by more than half. -- **A repository with no dependents but one consumer.** Nothing in the - organization imports `nats-client` except osapi, and `nats-client` imports - nothing from the organization. Its section 2 is therefore one edge, stated - from both ends. - -## Requirements *(mandatory)* - -Every requirement is *the corpus MUST state X*, and each names how it was -checked. The seven section names below are `system`'s 002 FR-001 names verbatim. - -### 1. What this repository is - -- **FR-001**: The corpus MUST state that `nats-client` is a **Go library - wrapping the upstream NATS client**, exposing one package, `pkg/client`, and - one constructor. It is not a service, has no entry point and ships no binary. - Verified: - `find pkg/client -maxdepth 1 -name '*.go' -not -name '*_test.go' | wc -l` - returns 11 files, and the only exported function is `New`. -- **FR-002**: The corpus MUST state what the wrapper **adds** over the upstream - library, because that is the only part a consumer cannot read in NATS' own - documentation: a single `Client` holding connection and JetStream context - together, authentication reduced to three declared modes, and create-or-update - helpers for streams, consumers, key-value buckets and object stores. What it - does not add is a new protocol, a new wire format, or any retry policy of its - own. - -### 2. Where it sits - -- **FR-003**: The corpus MUST state that `nats-client` depends on **no other - repository in the organization**, and that **one** imports it: `osapi`. - Verified from both ends — `grep -oE "osapi-io/[a-z-]+" go.mod` in this - repository returns only its own module path, and the same command in - `osapi/go.mod` returns `nats-client`. - [osapi's baseline FR-003](../../../osapi/specs/006-osapi-baseline/spec.md) - states the same edge from the other side. -- **FR-004**: The corpus MUST state what breaks in that direction and **when**: - a change to the exported surface of `pkg/client` breaks osapi's transport - layer, but osapi pins a **pseudo-version commit rather than a tag** - (`v0.0.0-20260412170202-5d1c1a26fa5a`), so nothing breaks there until somebody - bumps it. The consequence is delayed rather than absent, which is why a rename - and its bump want to land together. -- **FR-005**: The corpus MUST state that `nats-client` also depends on - `osapi-justfiles` for its build — the `go`, `just` and `md` modules — that - this edge appears in no `go.mod` because it is fetched by a justfile recipe, - and that the fetch is unpinned. Owner of the pinning: `osapi-justfiles`. See - [osapi-justfiles' baseline FR-017](../../../osapi-justfiles/specs/001-justfiles-baseline/spec.md). - -### 3. Architecture - -Stated at the level 002's FR-004 to FR-006 require: what each part is for and -what passes between parts, nothing a rename would falsify. - -- **FR-006**: The corpus MUST state that the package is **one type with one - responsibility per file**, and what each file is for rather than what it - currently calls: connecting and authenticating (`connect.go`, - `connect_wrapper.go`), connection state and lifecycle (`connection.go`), core - publish and subscribe (`core.go`), JetStream streams (`jetstream.go`), - consumers (`consumer.go`), key-value (`kv.go`), key-value with publish - (`kv_stream.go`), object stores (`objectstore.go`), and the option and - authentication types (`types.go`). A consumer holds one `Client` and reaches - all of it. -- **FR-007**: The corpus MUST state what passes across the boundary in each - direction: an `Options` struct in, and **upstream NATS types out** — - `jetstream.Msg` reaches a consumer's handler, and `nats.Conn` is reachable - through the wrapper. The wrapper is therefore not an abstraction over NATS; it - is a convenience layer that does not hide it, and a consumer who expected - isolation would be wrong. -- **FR-008**: The corpus MUST state that a `NATSConnector` interface exists so - the connection can be substituted in tests, and that this is the **only** seam - — everything else is concrete. Verified: - `grep -hE '^type [A-Z][A-Za-z]* interface' pkg/client/*.go` returns one - result. -- **FR-009**: The corpus MUST state that the repository ships **five runnable - examples** under `examples/`, one per authentication mode and one per - JetStream pattern it supports, and that these are the executable form of the - documentation rather than an extra to keep in step. - -### 4. The contract - -- **FR-010**: The corpus MUST state that the contract is the **exported surface - of `pkg/client`**: one constructor, 25 `Client` methods, and 9 exported types. - Everything under `pkg/client/mocks/` is generated and is not the contract. - Verified with the test files excluded — see FR-016 for why that exclusion is - not optional. - -- **FR-011**: The corpus MUST state the **three authentication modes** and what - each requires, because they are the part of the contract a consumer must - satisfy before anything else works: `NoAuth`; `UserPassAuth`, needing a - username and password; and `NKeyAuth`, needing the path to an Ed25519 private - seed file. Verified: `AuthType` and `AuthOptions` in `pkg/client/types.go`. - -- **FR-012**: The corpus MUST state what the contract does **not** promise: no - retry or reconnection policy of the wrapper's own, no abstraction over the - upstream types FR-007 names, and no stability guarantee beyond what the pin in - FR-004 gives — the repository publishes no tags, so a consumer depends on a - commit. - -- **FR-012a**: The corpus MUST state **what actually happens when the connection - drops**, because FR-012 says only what the wrapper does not add and a reader - needs the behaviour rather than its absence. - - Measured from the code: the wrapper passes exactly three options to - `nats.Connect` — `nats.Name`, and then `nats.UserInfo` or `nats.Nkey` - depending on the mode. It sets **no reconnection options whatsoever**, and - registers **no disconnect, reconnect or closed handler**. So the upstream - library's default reconnection behaviour governs entirely, and **a consumer is - not notified when a drop or a recovery happens** — there is no callback to - receive it. - - What that means for a consumer is worth stating plainly: resilience is - whatever the upstream default is, and it is not configurable through this - wrapper's `Options`. A consumer needing different behaviour cannot get it - here. Verified: - `grep -n 'Reconnect\|ClosedHandler\|DisconnectErr' pkg/client/*.go` returns - nothing, and the option list is built in `pkg/client/connect.go`. - - **The upstream defaults themselves are deliberately not restated**, per - `global/documentation`: they are the NATS library's to state and change, and - prose about another project's settings drifts while continuing to read as - authoritative. What is stated here is that this wrapper contributes nothing to - them. - -- **FR-012b**: The corpus MUST record **how FR-012a came to exist**, because the - omission was not random. The SC-001 reading asked what happens when the - connection drops, found that nothing stated it, and found that this - specification's **own acceptance scenario had promised it was stated** — the - scenario was written asserting a conclusion the code had not been consulted - about. - - A promise in an acceptance scenario is the worst place for an unverified - claim, because it reads as the test rather than as the assertion under test. - The scenario is corrected and this requirement records that a reading rather - than a re-reading caught it. - -### 5. Measurements - -- **FR-013**: The corpus MUST state each count with the command that reproduces - it. Measured 2026-09-30 at `cfe12f6`: - - | Measurement | Value | Command | - | ------------------------------ | ----: | --------------------------------------------------------------------------------------------------------------------------- | - | Go files | 33 | `find . -name '*.go' -not -path './.git/*' \| wc -l` | - | Go files excluding tests | 20 | `find . -name '*.go' -not -path './.git/*' -not -name '*_test.go' \| wc -l` | - | Non-test files in `pkg/client` | 11 | `find pkg/client -maxdepth 1 -name '*.go' -not -name '*_test.go' \| wc -l` | - | `Client` methods | 25 | `find pkg/client -maxdepth 1 -name '*.go' -not -name '*_test.go' -exec grep -hE '^func \(c \*Client\) [A-Z]' {} + \| wc -l` | - | Exported types in `pkg/client` | 9 | `find pkg/client -maxdepth 1 -name '*.go' -not -name '*_test.go' -exec grep -hE '^type [A-Z]' {} + \| wc -l` | - | Exported functions | 1 | `find pkg/client -maxdepth 1 -name '*.go' -not -name '*_test.go' -exec grep -hE '^func [A-Z]' {} + \| wc -l` — `New` | - | Documentation pages | 8 | `find docs -name '*.md' -not -path '*/node_modules/*' \| wc -l` | - | README lines | 62 | `wc -l README.md` | - | Runnable examples | 5 | `ls -d examples/*/ \| wc -l` | - -### 6. Gaps - -- **FR-014**: The corpus MUST record that the repository **publishes no tags**, - so its only consumer pins a pseudo-version commit. Nothing is wrong with the - code; what is missing is a way for a consumer to say which version it wants. - The rule this strains is `global/tooling`'s "both provisioning paths resolve - to the same version", which has no mechanism here rather than a divergent one - — the same shape `osapi-justfiles`' unpinned fetch has, and stated the same - way. Owner: `nats-client`, with a bump in `osapi`. - -- **FR-015**: The corpus MUST record that the wrapper **leaks upstream types** - (FR-007) and that this is a deliberate design rather than a defect — but that - nothing in the repository says so, so a consumer who expected an abstraction - learns otherwise by reading the signatures. Owner: `nats-client`, if it - chooses to say so; this baseline records that it currently does not. - -- **FR-016**: The corpus MUST record that **two of this baseline's own - measurements were wrong on the first attempt**, and what the error was in each - case, because both are errors of *command* rather than of arithmetic: - - - The documentation page count returned **9** instead of 8, because - `docs/node_modules/prettier/README.md` is a vendored dependency's file - rather than a page. `system`'s 002 recorded 8 and was right. The correction - is an exclusion, and osapi's baseline needed the same one. - - The exported-type count returned **23** instead of 9, because fourteen - `*TestSuite` types live in `_test.go` files. An exported-surface count taken - without excluding tests overstates the contract by more than half, and it - overstates it in the direction that reads plausible. - - Both were caught by running the command rather than by re-reading a number, - which is the argument for `global/baseline` pairing a count with its command. - -### 7. What this inventory excludes - -- **FR-017**: The corpus MUST state what it leaves out, so an omission is never - mistaken for an oversight: - - - **How NATS itself works.** Streams, consumers, key-value and object stores - are upstream concepts; this states what the wrapper does with them. - - **What each of the 25 methods does.** They are named as the contract; their - individual behaviour is the eight documentation pages' subject and is cited - rather than copied. - - **The generated mocks.** `pkg/client/mocks/` is generated and is not the - contract. - - **The repository's own contributor conventions.** Its `AGENTS.md` and - `CONTRIBUTING.md`, cited rather than copied. - - **Whether the wrapper is the right shape.** This states what it is, not - whether a consumer should want it. - -- **FR-018**: The corpus MUST state that **no section of the seven was - omitted**, and that the section names are `system`'s 002 names verbatim. This - is the first baseline written after `osapi-justfiles`' FR-021a recorded that - three headings had drifted; copying that corrected file rather than - re-deriving the names is what kept them right, which is exactly the - propagation FR-021a predicted. - -### The classification this baseline owed - -- **FR-019**: The corpus MUST classify every one of the 8 documentation pages as - user-facing or contributor-facing, **by who reads it** rather than by where it - sits, which `system`'s 002 FR-025 requires of every baseline. Every page is - user-facing, so **nothing moves**. - - | Pages | # | Reader | Why | - | ---------------- | --: | -------- | --------------------------------------- | - | `docs/client/**` | 7 | consumer | One page per part of the client package | - | `docs/README.md` | 1 | consumer | The index | - | **Total** | 8 | | **8 stay, 0 move** | - - Each page documents one part of the client surface: connecting and - authenticating, JetStream, consumers, the KV bucket and the object store. - Every one addresses somebody importing the package. Nothing tells a reader how - to change it, and the sweep for the markers that would say otherwise returns - only `docs/README.md`, which points at `CONTRIBUTING.md`. - - ```sh - grep -rlniE 'adding a|regenerate|codegen|go generate|contribut|internal/' \ - docs --include='*.md' | grep -v node_modules - ``` - - The count excludes `docs/node_modules/`, which Prettier installs and which - holds one vendored `README.md`. Section 5's page count carries the same - exclusion, so the two figures are the same figure. - -- **FR-020**: The corpus MUST record that this `docs/` tree is **the - organization's shape rather than this repository's invention**. Four - repositories carry the same index sentence verbatim, and the one with a - published site does not: - - ```sh - cd ~/git/osapi-io && for r in gohai osapi-orchestrator nats-client nats-server; do - grep -c 'Runnable programs live in' $r/docs/README.md - done - ``` - - A convention four repositories follow and nothing states is a convention that - drifts the first time somebody adds a fifth tree without reading a fourth. - Owner: `system`. Recorded here because the classification is what made four - identical trees visible at once. - -### Key Entities - -- **Client**: The one type a consumer holds. Carries connection and JetStream - context together and exposes 25 methods. -- **Options**: What a consumer passes in — host, port, name, and authentication. -- **Authentication mode**: One of three — none, user and password, or NKEY. -- **Leaked upstream type**: A NATS type reaching a consumer through the - wrapper's signatures. `jetstream.Msg` and `nats.Conn` are the two that matter. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A reader given the corpus alone states what the wrapper adds over - the upstream library, names the three authentication modes, and says which - JetStream primitives it covers. -- **SC-002**: Every count is paired with a command, and running the nine - commands reproduces the nine values. -- **SC-003**: A reader can state what breaks when `pkg/client`'s surface - changes, and **when** it breaks — not at the change, but at the bump. -- **SC-004**: The three gaps are stated with both sides named and an owner, and - none is corrected here. -- **SC-005**: All seven sections are present, in 002's order, under 002's names - verbatim. -- **SC-006**: `just test` passes in the specs repository. -- **SC-007**: Nothing in the `nats-client` repository changes. - -## Assumptions - -- The measurements are of `cfe12f6`. Counts will date; the commands are what - survives, and FR-016 records that the commands are where the errors were. -- The eight documentation pages stay where they are. Each documents one part of - the package's surface for a consumer of the library, which is the same reason - 002's FR-028 leaves `gohai/docs/collectors/` in place. -- `nats-server` is a separate repository with a separate baseline, unit 8. The - two are read together by a reader of osapi's transport layer but neither - imports the other. -- Nothing about the missing tags is being proposed here. FR-014 records that a - consumer cannot name a version; what to do about it belongs to a change in the - repository that owns it. diff --git a/history/nats-client-001-nats-client-baseline/tasks.md b/history/nats-client-001-nats-client-baseline/tasks.md deleted file mode 100644 index 8704533..0000000 --- a/history/nats-client-001-nats-client-baseline/tasks.md +++ /dev/null @@ -1,186 +0,0 @@ -______________________________________________________________________ - -## description: "Task list for the nats-client baseline" - -# Tasks: A baseline for nats-client - -**Input**: Design documents from -`components/nats-client/specs/001-nats-client-baseline/` - -**Prerequisites**: [spec.md](spec.md), [plan.md](plan.md) — both written in this -branch, per CONTRIBUTING's lifecycle table - -**Tests**: none. There is no code. What stands in is nine commands that must -reproduce their figures, an edge verified from both ends, and a reading. - -## Why the nine commands are the whole of Phase 2 - -Two of this baseline's own measurements were wrong on the first run, and neither -was wrong by arithmetic. The documentation page count included a vendored file -under `docs/node_modules/`, and the exported-type count included fourteen -`*TestSuite` types from `_test.go` files — overstating the contract by more than -half, in the direction that reads plausible. - -Both were caught by *running* the command rather than by re-reading a number. So -the tasks below re-run every one, and FR-016 states both errors rather than -presenting the corrected figures as though they had been the first ones. - -**Nothing lands in the `nats-client` repository.** T011 verifies that. - -______________________________________________________________________ - -## Phase 1: Setup - -- [x] T001 Confirm the measurements are against the commit the specification - names: `git -C ~/git/osapi-io/nats-client log --oneline -1` must show - `cfe12f6` or later. A later commit is fine; a figure that has moved is - recorded as a new measurement with its date rather than worked around. - -______________________________________________________________________ - -## Phase 2: Foundational — the re-measurement - -Run from `~/git/osapi-io/nats-client`. Each command comes from FR-013. - -- [x] T002 The Go counts — FR-013: - `find . -name '*.go' -not -path './.git/*' | wc -l` → `33`, and with - `-not -name '*_test.go'` → `20`. -- [x] T003 The package's shape — FR-001 and FR-013: - `find pkg/client -maxdepth 1 -name '*.go' -not -name '*_test.go' | wc -l` → - `11`. -- [x] T004 The contract's three counts, **with tests excluded** — FR-010 and - FR-013. 25 `Client` methods, 9 exported types, 1 exported function. **Run each - without the exclusion as well** and confirm the exported-type count rises to - 23: that difference is FR-016's second error and it is worth seeing rather - than trusting. -- [x] T005 The documentation page count, **with `node_modules` excluded** — - FR-013 and FR-016: - `find docs -name '*.md' -not -path '*/node_modules/*' | wc -l` → `8`. Run it - without the exclusion too and confirm it returns 9. `system`'s 002 recorded 8 - and was right. -- [x] T006 [P] The remaining counts — FR-013: README lines `62`, runnable - examples `5`, and one interface in the package. - -**Checkpoint**: every figure produced by a command rather than trusted, and both -of FR-016's errors reproduced deliberately so a reader can see what the -exclusion is worth. - -______________________________________________________________________ - -## Phase 3: User Story 1 — a consumer knows what the wrapper gives them (Priority: P1) - -- [x] T007 [US1] Confirm FR-002 states what the wrapper **adds** rather than - what it exposes, and that FR-007 states what leaks through it. A wrapper - described by its surface alone has been described as a package rather than as - a dependency: a consumer depends on `jetstream.Msg` whether or not this - repository names it. - -- [x] T008 [US1] Confirm the three authentication modes and what each needs are - stated — FR-011 — since that is the part of the contract a consumer must - satisfy before anything works. - -- [x] T009 [US1] Run the SC-001 reading. Give somebody who has not opened - `pkg/client` the specification alone and three questions: what does the - wrapper add over the upstream library; how does a consumer authenticate; and - what happens when the connection drops? The third is the one to watch — FR-012 - says the wrapper adds **no** retry policy of its own, and a reader who assumes - a wrapper implies resilience has been misled. A person is preferred; a fresh - agent given only `spec.md` is the fallback. **Record what it proves and what - it does not.** - - **Two of three, and the third was the one this task flagged.** A fresh agent - given only `spec.md` answered what the wrapper adds (FR-002) and how a - consumer authenticates (FR-011), both plainly. It could **not** answer what - happens when the connection drops — and found something worse than an - omission: this specification's own **acceptance scenario had promised the - answer was stated**, "rather than left to the upstream library's defaults". - FR-012 said only what the wrapper does not add, which is the absence of a - behaviour rather than the behaviour. - - The reading named the trap exactly: a reader taking that scenario at face - value mistakes "the corpus states this" for "this document states this". **A - promise in an acceptance scenario is the worst place for an unverified - claim**, because it reads as the test rather than as the assertion under test. - - Fixed at specs#189. The code was read: the wrapper sets no reconnection - options and registers no handlers, so the upstream default governs and a - consumer is not notified — FR-012a. FR-012b records how the omission came to - exist, and the scenario now says the answer turned out to be exactly what it - had asserted it was not. - - It also reported the document reads as a checklist with footnote-style prose - attached to each line rather than as continuous prose, with the - measure-and-admit-the-error theme as its only throughline. Same judgement - `osapi-justfiles`' reading returned, and it belongs to `system`'s 002 for the - same reason. - - **What it proves and what it does not**: that two of three answers are in the - text. Not that a consuming maintainer would find them, and not that the third - would have been noticed by anybody who had already read the code. - -______________________________________________________________________ - -## Phase 4: User Story 2 — a breaking change is recognisable before it is made (Priority: P1) - -- [x] T010 [US2] Verify the dependency edge **from both ends** — FR-003: - `grep -oE "osapi-io/[a-z-]+" go.mod` here, and the same in `osapi/go.mod`. - This is the habit `osapi-justfiles`' baseline lacked when it listed its - consumers from one side and missed one. One edge is a small test of it; - `osapi-orchestrator` has more. -- [x] T011 [US2] Confirm FR-004 states **when** the break happens, not only that - it does: osapi pins a pseudo-version commit rather than a tag, so a rename - lands green here and breaks at the bump. Confirm the pin by reading - `osapi/go.mod`. Also confirm nothing changed in the inventoried repository — - `git -C ~/git/osapi-io/nats-client status --porcelain` is empty. - -______________________________________________________________________ - -## Phase 5: User Story 3 — the seven sections read the same as every other baseline (Priority: P2) - -- [x] T012 [US3] Confirm the seven section names are `system`'s 002 FR-001 names - **verbatim**, and that all seven are present in order — FR-018 and SC-005. - This unit copied a corrected file rather than re-deriving the names, which is - the propagation `osapi-justfiles`' FR-021a predicted. Record that inheriting - worked, because a later baseline will ask. -- [x] T013 [US3] Confirm the three gaps name both sides and an owner — FR-014 - through FR-016 — and that none is corrected here. FR-016 is this feature's own - errors, which is the one a reader is most likely to think should have been - quietly fixed. - -______________________________________________________________________ - -## Phase 6: Verification and archival - -- [x] T014 Run `cd specs && mise exec -- just test` — SC-006. -- [x] T015 Run `speckit-archive-run specs/001-nats-client-baseline` once this - branch has merged. This project's memory holds only a constitution, so the run - **seeds** rather than folds: `spec.md` and `plan.md` are both created and - every requirement enters under the feature's own IDs. -- [x] T016 Mark `system`'s 002 as having one more unit done, and leave the rest - open: `nats-server` is unit 8, `osapi-orchestrator` unit 9 and the largest - remaining, gohai's amendment unit 10, and two moves after them. - -______________________________________________________________________ - -## Dependencies & Execution Order - -- **Phase 2** blocks everything. Until the figures are reproduced, every later - task compares prose against prose. -- **T004 and T005 must be run twice each** — with and without their exclusion — - because the point is the difference. -- **Phases 3, 4 and 5** are independent of each other. -- **Phase 6** is last; T015 needs this branch merged. - -### What only looks parallel - -T009's reading and T007's check both concern FR-002 and FR-007, and the reading -is worth more if it happens **before** the check rather than after: a reader who -has been told what to look for is no longer a reader who has seen nothing else. - -## Notes - -- No Go code changes. No change of any kind in `nats-client`. -- Stages 1 to 4 are one branch and one pull request here, which is what - CONTRIBUTING's lifecycle table specifies. The two units before this one split - them across three pull requests, which cost two extra review cycles and gained - nothing. diff --git a/history/nats-server-001-nats-server-baseline/checklists/requirements.md b/history/nats-server-001-nats-server-baseline/checklists/requirements.md deleted file mode 100644 index f12f16a..0000000 --- a/history/nats-server-001-nats-server-baseline/checklists/requirements.md +++ /dev/null @@ -1,47 +0,0 @@ -# Specification Quality Checklist: A baseline for nats-server - -**Purpose**: Validate specification completeness and quality before proceeding -to planning - -**Created**: 2026-09-30 - -**Feature**: [spec.md](../spec.md) - -## Content Quality - -- [ ] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [x] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [ ] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [ ] No implementation details leak into specification - -## Notes - -Three items fail **by design**, for the reason every baseline's do: file paths, -method names and counts are the content, and the Verification principle requires -the command that measures each. Recorded rather than waived so a deliberate -departure stays distinguishable from an oversight. - -This specification leans on that exemption harder than its siblings. Its three -findings are a **statement order** inside one function, two **literal -arguments** at one call site, and the **absence** of a defaulting statement. -None can be stated without naming the function and the file, and none would -survive being rewritten as a technology-agnostic outcome — "a consumer can -configure logging" is what the reader would then be told, and it is false. diff --git a/history/nats-server-001-nats-server-baseline/plan.md b/history/nats-server-001-nats-server-baseline/plan.md deleted file mode 100644 index 03c1b9f..0000000 --- a/history/nats-server-001-nats-server-baseline/plan.md +++ /dev/null @@ -1,152 +0,0 @@ -# Implementation Plan: A baseline for nats-server - -**Branch**: `001-nats-server-baseline` | **Date**: 2026-09-30 | **Spec**: -[spec.md](spec.md) - -**Input**: Feature specification from -`components/nats-server/specs/001-nats-server-baseline/spec.md` - -## Summary - -State what `nats-server` is, so its memory stops holding only a constitution. -Nineteen requirements, every one of the form "the corpus MUST state X". - -Unit 8 of twelve, and the smallest repository with Go code in the organization — -twelve files, four of them the package. **The smallness is why it is worth -reading closely rather than a reason to hurry.** A wrapper this thin either -saves a consumer the four lines it wraps or makes decisions on their behalf, and -the difference is invisible from the signatures. Reading `Start()` found three -decisions the consumer cannot change and no page mentions. - -**Nothing lands in the `nats-server` repository.** The deliverable is the -inventory. - -## Technical Context - -**Language/Version**: Markdown. The repository being inventoried is Go — 12 -files, 10 of them not tests, `go 1.26.0` — and nothing in it changes. - -**Primary Dependencies**: None. The corpus depends on nothing at runtime. The -repository being inventoried depends on no other repository in the organization. - -**Storage**: `components/nats-server/specs/` for the specification — a directory -this feature creates, since it is the project's first — and -`components/nats-server/.specify/memory/` for what archival consolidates into. - -**Testing**: `just test` in the specs repository. There is no code to unit test. -Every count carries its command, and ten commands re-run is what checks the -counts — but **the three findings that matter here are not counts**, and no -command would have produced them. - -**Target Platform**: The corpus. - -**Project Type**: Documentation. - -**Constraints**: No change to the `nats-server` repository. No count without its -command. Section names verbatim from 002. Gaps recorded with what was measured, -where, and an owner. - -**Scale/Scope**: One repository inventoried, the smallest with Go code. A -contract of one constructor, two methods, and an embedded upstream option struct -that is most of the surface. - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -| Principle | How this feature satisfies it | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **Documentation** | The five pages are cited rather than copied, and the upstream option struct is deliberately not restated — it is upstream's to change. | -| **Verification** | Ten counts with commands. More importantly, the three gaps came from **reading `Start()`**, which is the case where "a claim about the codebase is measured" means reading order rather than counting. | -| **Tooling** | Nothing provisioned. | -| **Correction** | Three gaps with owners, none corrected here. Each implies a change this repository must make, and naming the change is not making it. | -| **Workflow** | Stages 1 to 4 in one branch, as CONTRIBUTING's lifecycle table specifies. | -| **Baseline** | This memory holds no decisions, so the baseline is the whole of it. | -| **Repositories** | The single edge is verified from both ends. | -| **Tracking** | Nothing becomes an issue. The three gaps name their owner. | - -**Result**: no violations. - -## Why reading order was the method here - -Every baseline before this one was mostly counting: files, pages, recipes, -methods. This one counts too, and the counting found nothing interesting — ten -figures, all unremarkable, all reproducing first time. - -**The three findings came from reading four statements in sequence.** `Start()` -constructs the upstream server, starts it in a goroutine, waits for readiness, -and *then* attaches the logger. Each statement is unobjectionable; the order is -what produces a consumer whose logger never sees the startup it was attached to -observe. No count reveals an order, and no signature does either — -`Start() error` says nothing about when the logger arrives. - -The same reading found `SetLogger(wrapper, true, true)`, where the interesting -part is the two literals, and the absence of any defaulting in `New()`, where -the interesting part is what is *not* there. - -This is what `global/verification`'s "reading code and concluding is a -hypothesis" looks like from the other side: the hypothesis was that a thin -wrapper decides nothing, and reading it falsified that. The counts could not -have. - -## What the five documentation pages did not say - -Worth recording because it bears on how much weight a page carries. The -repository documents `configuration.md`, `lifecycle.md` and `logging.md` — the -three subjects the three gaps fall under — and **none of the three facts appears -in any of them.** A reader who trusted the prose would believe logging was -configurable, that startup was observable, and that `Options` had sensible -defaults. - -That is not a criticism of the pages; each describes what it describes -correctly. It is the argument for FR-090's discipline in osapi's corpus — prose -is a lead and the code is the source — holding even where the prose is recent -and the repository is small. - -## Project Structure - -### Documentation (this feature) - -```text -components/nats-server/specs/001-nats-server-baseline/ -├── spec.md # 19 requirements, 7 outcomes -├── plan.md # This file -├── checklists/ -│ └── requirements.md -└── tasks.md -``` - -### What is being inventoried - -```text -nats-server/ # nothing here changes -├── pkg/server/ # the one package: 4 non-test files -│ ├── server.go # lifecycle, and the order Start() uses -│ ├── types.go # Options: upstream's struct, plus ReadyTimeout -│ ├── server_wrapper.go # NATSServerInstance, the only seam -│ ├── logger.go # SlogWrapper: NATS logging into slog -│ └── mocks/ # generated; not the contract -├── examples/ # 4 runnable, all setting ReadyTimeout explicitly -└── docs/ # 5 pages; three facts are in none of them -``` - -**Structure Decision**: no corpus subject file beyond `spec.md`, and no skill -gains a reference. No skill in the specs repository starts an embedded NATS -server. - -## What the verification checks, and what it cannot - -The ten commands check the counts, and the counts were never in doubt. What the -task list cannot automate is the part that found everything: - -- **Reading `Start()` in order.** T007 is a reading rather than a command, - because the finding is a sequence. -- **Whether the three gaps matter to a consumer.** Debug and trace being - unconditional is a fact; whether it is a problem depends on what a consumer - embeds this in. osapi does, and osapi's baseline does not mention it either. -- **Whether the five pages are accurate about what they do cover.** They were - read for what they omit, not verified line by line against the code. - -## Complexity Tracking - -> No Constitution Check violations, so this table is empty. diff --git a/history/nats-server-001-nats-server-baseline/spec.md b/history/nats-server-001-nats-server-baseline/spec.md deleted file mode 100644 index 83fe8b7..0000000 --- a/history/nats-server-001-nats-server-baseline/spec.md +++ /dev/null @@ -1,396 +0,0 @@ -# Feature Specification: A baseline for nats-server - -**Feature Branch**: `001-nats-server-baseline` - -**Created**: 2026-09-30 - -**Status**: Completed - -**Input**: `nats-server`'s `.specify/memory/` holds only a constitution composed -from `.charter/`, so it states what binds every repository and nothing about -this one. Unit 8 of the baseline programme `system`'s -[002](../../../../system/specs/002-baseline-shape/spec.md) defines, and the -second of the two NATS repositories osapi imports. - -## What this specification is, and what it is not - -**Its subject is a description, not a change.** Nothing lands in the -`nats-server` repository. What this produces is an inventory of how it behaves -today, which lives in this feature directory and reaches memory through -archival. - -**Prose was a lead, never a source** — and here that mattered more than in any -baseline so far. Five documentation pages and a 58-line README describe this -package, and **three facts a consumer needs are in none of them**: the logger is -attached after the server has already started, debug and trace are switched on -unconditionally, and one option has no default. All three were found by reading -`Start()`, and all three are recorded as gaps rather than as prose. - -**It is read alongside `nats-client`'s.** The two repositories are the transport -half of osapi and neither imports the other. Their baselines are deliberately -shaped the same way so a reader moving between them finds the same answer in the -same place. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - A consumer knows what embedding the server costs them (Priority: P1) - -Somebody embedding NATS in their own binary can state what this package does on -their behalf, what it decides for them without asking, and what they must still -supply. - -**Why this priority**: it is the whole question. A package this small either -saves a consumer the four lines it wraps or makes decisions they did not know -were being made, and the difference is only visible by reading `Start()`. - -**Independent Test**: a reader given the corpus alone states what `Start()` does -in order, names what is not configurable, and says which option has no default — -without opening `pkg/server`. - -**Acceptance Scenarios**: - -1. **Given** the corpus, **When** a reader asks what logging they will get, - **Then** the answer states that debug and trace are on unconditionally and - that startup logs do not reach their logger. -2. **Given** the corpus, **When** a reader asks what they must set in `Options`, - **Then** `ReadyTimeout` is named along with the fact that it has no default. - -______________________________________________________________________ - -### User Story 2 - The contract is recognised as mostly somebody else's (Priority: P1) - -Somebody changing this package knows that `Options` **embeds** the upstream -server's entire option struct, so the contract they are maintaining is largely -not theirs. - -**Why this priority**: equal to the first. An embedded struct is a contract a -consumer can reach every field of, and a change to the upstream library changes -this package's surface without any commit here. - -**Independent Test**: this specification states that the contract is the -upstream option struct plus one field, and names the field. - -______________________________________________________________________ - -### User Story 3 - The two NATS baselines read as a pair (Priority: P2) - -Somebody reading `nats-client`'s baseline and then this one finds the same seven -sections in the same order, and can see that the two packages wrap opposite ends -of the same library. - -**Why this priority**: lower than the two above, and it is the programme's -purpose. Both were written to `system`'s section names verbatim, and this one -inherited them from a file that had already been corrected once. - -### Edge Cases - -- **A wrapper that decides something for its consumer.** `Start()` calls - `SetLogger(wrapper, true, true)` — debug and trace unconditionally, with no - option to turn either off. A consumer embedding this in a production binary - gets full trace logging and nothing in the repository tells them so. -- **A logger attached too late to see the thing it was attached for.** The - sequence in `Start()` is: construct, `go Start()`, wait for readiness, *then* - `SetLogger`. Everything the server logs while starting and becoming ready goes - to the upstream default logger, not the consumer's `slog`. A consumer - debugging a failed start finds their logger empty. -- **An option with no default.** `New()` does no defaulting, so a consumer who - leaves `ReadyTimeout` unset passes a zero duration to `ReadyForConnections`. - All four examples set it explicitly to 5 seconds, which is exactly how the - absence of a default stays invisible: every path a reader is shown supplies - the value. -- **Trace and debug are indistinguishable downstream.** `SlogWrapper.Tracef` - calls `logger.Debug`, so NATS trace output arrives in a consumer's logs at - debug level with nothing marking it as trace. Deliberate — `slog` has no trace - level — and **documented**: `docs/server/logging.md` carries the mapping - table. It is listed here because it compounds FR-014 rather than because it is - hidden: a consumer who cannot turn trace off also cannot filter it out. - -## Requirements *(mandatory)* - -Every requirement is *the corpus MUST state X*, and each names how it was -checked. The seven section names below are `system`'s 002 FR-001 names verbatim. - -### 1. What this repository is - -- **FR-001**: The corpus MUST state that `nats-server` is a **Go library that - runs a NATS server inside its consumer's process**, exposing one package, - `pkg/server`, and one constructor. It is not a daemon, ships no binary, and - has no entry point of its own — the server it starts is a goroutine in - somebody else's program. Verified: - `find pkg/server -maxdepth 1 -name '*.go' -not -name '*_test.go' | wc -l` - returns 4, and the only exported function is `New`. -- **FR-002**: The corpus MUST state what the package **does for a consumer**, - because it is small enough that the answer is short and specific: it starts - the upstream server in a goroutine, blocks until it reports ready or the - timeout lapses, turns a failure to become ready into an error, and routes the - server's own logging into the consumer's `slog`. Four things, and `Start()` is - where all four happen. - -### 2. Where it sits - -- **FR-003**: The corpus MUST state that `nats-server` depends on **no other - repository in the organization**, and that **one** imports it: `osapi`. - Verified from both ends — `grep -oE "osapi-io/[a-z-]+" go.mod` here returns - only its own module path, and the same command in `osapi/go.mod` returns - `nats-server`. -- **FR-004**: The corpus MUST state that osapi pins a **pseudo-version commit - rather than a tag**, so a change to this package's surface does not break - osapi until somebody bumps it. The consequence is delayed rather than absent, - which is the same shape - [nats-client's baseline FR-004](../../../nats-client/specs/001-nats-client-baseline/spec.md) - records for the other half of the transport. -- **FR-005**: The corpus MUST state that it also depends on `osapi-justfiles` - for its build, that this edge appears in no `go.mod` because a justfile recipe - fetches it, and that the fetch is unpinned. Owner of the pinning: - `osapi-justfiles`. - -### 3. Architecture - -- **FR-006**: The corpus MUST state what each of the four non-test files is - **for**: the server's lifecycle and the order `Start()` performs it - (`server.go`), the option struct and what it embeds (`types.go`), the - interface that makes the upstream server substitutable (`server_wrapper.go`), - and the adapter that routes NATS' logging into `slog` (`logger.go`). A - consumer holds one `Server` and calls two methods. -- **FR-007**: The corpus MUST state **the order `Start()` does things in**, - because two of this repository's three gaps are consequences of it and neither - is visible from the signatures: construct the upstream server, start it in a - goroutine, wait for `ReadyForConnections`, **then** attach the logger, then - record the instance. Verified: `pkg/server/server.go`. -- **FR-008**: The corpus MUST state that the contract exposes exactly **two - methods** — `Start` returning an error and `Stop` returning nothing — and that - everything else a consumer can reach comes through the embedded upstream - option struct rather than through this package's own surface. -- **FR-009**: The corpus MUST state that `NATSServerInstance` is the **only** - seam, declaring the four upstream methods this package calls — `Start`, - `ReadyForConnections`, `SetLogger`, `Shutdown` — so the upstream server can be - substituted in tests and nothing else can. Verified: - `grep -hE '^type [A-Z][A-Za-z]* interface'` returns one result. -- **FR-010**: The corpus MUST state that the repository ships **four runnable - examples**, one per authentication mode plus a plain server, and that they are - the executable form of the documentation rather than an extra to keep in step. - -### 4. The contract - -- **FR-011**: The corpus MUST state that `Options` **embeds - `*natsserver.Options`** — the upstream server's entire option struct — and - adds exactly one field of its own, `ReadyTimeout`. So **the contract this - repository maintains is mostly not its own**: a consumer can reach every - upstream option through it, and a change upstream changes this package's - surface with no commit here. Verified: `pkg/server/types.go`. -- **FR-012**: The corpus MUST state that the contract is otherwise one - constructor, two methods, and three exported types a consumer names — - `Server`, `Options` and `SlogWrapper` — plus the one interface. Everything - under `pkg/server/mocks/` is generated and is not the contract. -- **FR-013**: The corpus MUST state what the contract does **not** let a - consumer decide, because this is the part a signature does not show: whether - debug and trace logging are on (they always are — FR-014), and whether the - logger sees startup (it does not — FR-015). - -### 5. Measurements - -- **FR-016**: The corpus MUST state each count with the command that reproduces - it. Measured 2026-09-30 at `7ac142e`: - - | Measurement | Value | Command | - | ------------------------------ | ----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | Go files | 12 | `find . -name '*.go' -not -path './.git/*' \| wc -l` | - | Go files excluding tests | 10 | `find . -name '*.go' -not -path './.git/*' -not -name '*_test.go' \| wc -l` | - | Non-test files in `pkg/server` | 4 | `find pkg/server -maxdepth 1 -name '*.go' -not -name '*_test.go' \| wc -l` | - | Exported functions | 1 | `find pkg/server -maxdepth 1 -name '*.go' -not -name '*_test.go' -exec grep -hE '^func [A-Z]' {} + \| wc -l`, which is `New` | - | Exported methods | 8 | `find pkg/server -maxdepth 1 -name '*.go' -not -name '*_test.go' -exec grep -hE '^func \([a-z]+ \*[A-Za-z]+\) [A-Z]' {} + \| wc -l`, being 2 on `Server` and 6 on `SlogWrapper` | - | Exported types | 4 | `find pkg/server -maxdepth 1 -name '*.go' -not -name '*_test.go' -exec grep -hE '^type [A-Z]' {} + \| wc -l` | - | Interfaces | 1 | `find pkg/server -maxdepth 1 -name '*.go' -not -name '*_test.go' -exec grep -hE '^type [A-Z][A-Za-z]* interface' {} + \| wc -l` | - | Documentation pages | 5 | `find docs -name '*.md' -not -path '*/node_modules/*' \| wc -l` | - | README lines | 58 | `wc -l README.md` | - | Runnable examples | 4 | `ls -d examples/*/ \| wc -l` | - - The `node_modules` exclusion is load-bearing, which this requirement said it - was not. **Corrected 2026-09-30**: it claimed the repository has no vendored - markdown. Git tracks five pages, and a working tree that has run Prettier - holds six, because `docs/node_modules/prettier/README.md` is one of them. The - command runs against a working tree, so without the exclusion it returns 6. - - ```sh - find docs -name '*.md' | wc -l # 6 - find docs -name '*.md' -not -path '*/node_modules/*' | wc -l # 5 - git ls-files 'docs/**.md' | wc -l # 5 - ``` - - The reason the mistake was easy to make is the second half of it. - `docs/node_modules` is ignored by the developer's **global** gitignore rather - than by this repository's own, so it never appears in `git status` and reads - as though it is not there. `gohai` and `osapi-orchestrator` both carry the - entry in their own `.gitignore`; this repository and `nats-client` do not. - Owner: those two repositories. Recorded rather than fixed here. - - ```sh - cd ~/git/osapi-io && for r in nats-server nats-client gohai osapi-orchestrator; do - grep -c node_modules $r/.gitignore - done # 0 0 1 1 - ``` - - [nats-client's baseline FR-016](../../../nats-client/specs/001-nats-client-baseline/spec.md) - records a count that was wrong for exactly this reason, which is why the - exclusion is written the same way in both. - -### 6. Gaps - -Three, all found by reading `Start()`, and a fourth in section 5 that a count -found rather than a reading: `docs/node_modules` is ignored by the developer's -global gitignore rather than by this repository's, recorded under FR-016 because -that is the count it corrects. **Two of the three are partly documented and the -amendment below says exactly how far** — the original wording claimed all three -were "stated in none of the five documentation pages", which was true of one and -an overclaim about two. None is corrected here. - -- **FR-014**: The corpus MUST record that **debug and trace logging are enabled - unconditionally**. `Start()` calls `SetLogger(wrapper, true, true)`, and - nothing in `Options` or `New()` can change either flag. A consumer embedding - this in a production binary gets full trace output from the NATS server and - nothing in the repository warns them. Verified: `pkg/server/server.go:59`, and - `grep -rn 'SetLogger' pkg/server/*.go` finds the one call site. Owner: - `nats-server`. - - **What the pages do say, and where the line falls.** `docs/server/logging.md` - documents the *mapping* — a table giving `Tracef()` → `slog.Debug()` alongside - the other five levels — so a reader learns where trace output lands. What no - page says is that trace is **switched on unconditionally**, which is the fact - that makes the mapping matter: a reader could reasonably take that table as - describing what happens *if* trace is enabled. Documenting where a signal goes - is not documenting that the signal is always on. - -- **FR-015**: The corpus MUST record that **the logger is attached after the - server has started and become ready**, so everything logged during startup - goes to the upstream default logger rather than the consumer's `slog`. A - consumer debugging a server that failed to become ready finds their own logger - empty and the reason on stderr. Verified: the statement order in `Start()`. - Owner: `nats-server`. - -- **FR-016a**: The corpus MUST record that **`ReadyTimeout` has no default**. - `New()` performs no defaulting, so a consumer who leaves it unset passes a - zero duration to `ReadyForConnections`. All four examples set it to 5 seconds - explicitly, which is how the absence stays invisible: every path a reader is - shown supplies the value, so nothing reveals that the value is required. - Owner: `nats-server`. - - **What the pages do say, and the precise mechanism of the omission.** - `docs/server/configuration.md` documents the field — an `Options` table with - columns Field, Type and Description, giving `ReadyTimeout` as "Max wait time - for server readiness after start" — and its example sets it to 5 seconds. - **That table has no Default column.** So the omission is not carelessness in - the prose; it is structural. A table that cannot express a default cannot - record the absence of one, and a reader sees a documented field with an - example value and has no reason to ask. - -### 7. What this inventory excludes - -- **FR-017**: The corpus MUST state what it leaves out, so an omission is never - mistaken for an oversight: - - - **How the NATS server itself works.** Clustering, JetStream, accounts and - authentication are upstream concepts; this states what the wrapper does with - the upstream server, not what that server is. - - **The upstream option struct's fields.** `Options` embeds them and a - consumer can reach all of them; they are upstream's to document and change, - and restating them here would drift — `global/documentation`. - - **What each of the five documentation pages says.** They are cited as where - configuration, lifecycle and logging are described for a consumer, not - copied. FR-014, FR-015 and FR-016a record what each page does and does not - say about the three findings — one is absent entirely, and two are partly - covered in a way that reads as complete. - - **The generated mocks.** `pkg/server/mocks/` is generated and is not the - contract. - - **Whether embedding a NATS server is the right design for osapi.** That is - osapi's question and its baseline's. - -- **FR-018**: The corpus MUST state that **no section of the seven was - omitted**, and that the section names are `system`'s 002 names verbatim, - inherited from `nats-client`'s baseline — which inherited them from - `osapi-justfiles`' after that one recorded three of them drifting. Two hops - without drift is the first evidence the correction holds rather than merely - having been made once. - -### The classification this baseline owed - -- **FR-019**: The corpus MUST classify every one of the 5 documentation pages as - user-facing or contributor-facing, **by who reads it** rather than by where it - sits, which `system`'s 002 FR-025 requires of every baseline. Every page is - user-facing, so **nothing moves**. - - | Pages | # | Reader | Why | - | ---------------- | --: | -------- | --------------------------------------- | - | `docs/server/**` | 4 | consumer | One page per part of the server package | - | `docs/README.md` | 1 | consumer | The index | - | **Total** | 5 | | **5 stay, 0 move** | - - Each page documents one part of embedding the server: its options, its - lifecycle, and how its logging reaches a consumer's `slog` handler. Every one - addresses somebody importing the package. Nothing tells a reader how to change - it, and the sweep for the markers that would say otherwise returns only - `docs/README.md`, which points at `CONTRIBUTING.md`. - - ```sh - grep -rlniE 'adding a|regenerate|codegen|go generate|contribut|internal/' \ - docs --include='*.md' | grep -v node_modules - ``` - - The count excludes `docs/node_modules/`, which Prettier installs and which - holds one vendored `README.md`. Section 5's page count carries the same - exclusion, so the two figures are the same figure. - -- **FR-020**: The corpus MUST record that this `docs/` tree is **the - organization's shape rather than this repository's invention**. Four - repositories carry the same index sentence verbatim, and the one with a - published site does not: - - ```sh - cd ~/git/osapi-io && for r in gohai osapi-orchestrator nats-client nats-server; do - grep -c 'Runnable programs live in' $r/docs/README.md - done - ``` - - A convention four repositories follow and nothing states is a convention that - drifts the first time somebody adds a fifth tree without reading a fourth. - Owner: `system`. Recorded here because the classification is what made four - identical trees visible at once. - -### Key Entities - -- **Server**: The one type a consumer holds. Two methods, `Start` and `Stop`. -- **Options**: The upstream option struct embedded whole, plus `ReadyTimeout`. -- **SlogWrapper**: The adapter turning NATS' logger interface into `slog` calls, - mapping trace onto debug because `slog` has no trace level. -- **Undocumented decision**: Something `Start()` settles that a consumer cannot - change and no page mentions. Three exist. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A reader given the corpus alone states what `Start()` does in - order, names the two things that are not configurable, and says which option - has no default. -- **SC-002**: Every count is paired with a command, and running the ten commands - reproduces the ten values. -- **SC-003**: A reader can state that the contract is mostly the upstream option - struct, and that a change upstream changes this package's surface with no - commit here. -- **SC-004**: The three gaps are stated with what was measured, where, and an - owner, and none is corrected here. -- **SC-005**: All seven sections are present, in 002's order, under 002's names - verbatim. -- **SC-006**: `just test` passes in the specs repository. -- **SC-007**: Nothing in the `nats-server` repository changes. - -## Assumptions - -- The measurements are of `7ac142e`. Counts will date; the commands are what - survives. -- The five documentation pages stay where they are. Each describes one part of - the package for a consumer of the library, which is 002's FR-028 reasoning. -- The three gaps are recorded, not fixed. Each implies a change in `nats-server` - and each is that repository's to make: a flag for debug and trace, attaching - the logger before the goroutine starts, and a default for `ReadyTimeout`. -- `nats-client` is the other half of the transport and has its own baseline, - unit 7. Neither repository imports the other. diff --git a/history/nats-server-001-nats-server-baseline/tasks.md b/history/nats-server-001-nats-server-baseline/tasks.md deleted file mode 100644 index 3040f59..0000000 --- a/history/nats-server-001-nats-server-baseline/tasks.md +++ /dev/null @@ -1,208 +0,0 @@ -______________________________________________________________________ - -## description: "Task list for the nats-server baseline" - -# Tasks: A baseline for nats-server - -**Input**: Design documents from -`components/nats-server/specs/001-nats-server-baseline/` - -**Prerequisites**: [spec.md](spec.md), [plan.md](plan.md) — both written in this -branch - -**Tests**: none. There is no code. What stands in is ten commands, **one reading -of four statements in sequence**, and a reader's reading. - -## The second task is the one that matters - -The ten commands found nothing interesting: ten figures, all unremarkable, all -reproducing first time. Every finding in this baseline came from **T007**, which -reads `Start()` in order rather than counting anything. - -No count reveals an order, and no signature does either — `Start() error` says -nothing about when the logger arrives. So T007 is a reading and is marked as -one, and a future baseline for a small wrapper should copy it rather than -assuming that a short file holds nothing. - -**Nothing lands in the `nats-server` repository.** T013 verifies that. - -______________________________________________________________________ - -## Phase 1: Setup - -- [x] T001 Confirm the measurements are against the commit the specification - names: `git -C ~/git/osapi-io/nats-server log --oneline -1` shows `7ac142e` or - later. A figure that has moved is recorded as a new measurement with its date. - -______________________________________________________________________ - -## Phase 2: Foundational — the counts, which are the easy half - -Run from `~/git/osapi-io/nats-server`. Each command is from FR-016. - -- [x] T002 The Go counts: `find . -name '*.go' -not -path './.git/*' | wc -l` → - `12`, and with `-not -name '*_test.go'` → `10`. -- [x] T003 The package's shape and surface, tests excluded: 4 non-test files, 1 - exported function, 8 exported methods (2 on `Server`, 6 on `SlogWrapper`), 4 - exported types, 1 interface. -- [x] T004 [P] The documentation and example counts: 5 pages with the - `node_modules` exclusion, 58 README lines, 4 runnable examples. The exclusion - is unnecessary here and carried on purpose — `nats-client`'s baseline got that - count wrong for want of it, and two sibling baselines using different commands - invites a reader to wonder which is right. - -______________________________________________________________________ - -## Phase 3: User Story 1 — a consumer knows what embedding costs them (Priority: P1) - -- [x] T005 [US1] Confirm FR-002 states the four things the package does rather - than what it exposes. A wrapper this thin is described by what it decides, not - by its surface. - -- [x] T006 [US1] Confirm FR-007 states the **order** `Start()` performs those - four things in, because two of the three gaps are consequences of the order - and neither is visible from a signature. - -- [x] T007 [US1] **Read `pkg/server/server.go`'s `Start()` in order** and - confirm all three findings against it: - - 1. `SetLogger(slogWrapper, true, true)` — debug and trace are literals, and - nothing in `Options` or `New()` can change them. FR-014. - 2. The `SetLogger` call comes **after** `go natsServer.Start()` and after - `ReadyForConnections`, so startup logging never reaches the consumer's - `slog`. FR-015. - 3. `New()` performs no defaulting, so an unset `ReadyTimeout` reaches - `ReadyForConnections` as a zero duration. FR-016a. - - **This is a reading, not a command.** Three greps confirm each fact once you - know to look — `grep -rn 'SetLogger' pkg/server/*.go` finds one call site, - `grep -rn 'ReadyTimeout' pkg/server/*.go` finds a declaration and a use and no - default — but nothing produces the findings from scratch. Record that, because - the temptation on the next thin wrapper will be to trust its size. - -- [x] T008 [US1] Confirm all three facts are absent from all five documentation - pages, and that the pages covering those subjects are `configuration.md`, - `lifecycle.md` and `logging.md` — the three the findings fall under. Grep each - page for `SetLogger`, `trace`, `ReadyTimeout` and `default`. - - **The grep contradicted the specification, and the specification was wrong.** - It had claimed all three facts were "stated in none of the five documentation - pages". One is: `SetLogger` appears nowhere, so FR-015's finding stands as - written. The other two do not: - - - `trace` is in `docs/server/logging.md`, which documents the `Tracef()` → - `slog.Debug()` mapping. So where trace output lands **is** documented; that - it is switched on unconditionally is not. - - `ReadyTimeout` is in `configuration.md`, `lifecycle.md` and - `server/README.md`, with a description and an example value. So the field - **is** documented; that it has no default is not — and the mechanism is that - the `Options` table has columns Field, Type and Description and **no Default - column at all**. - - Both refinements make the findings sharper rather than weaker, and both were - corrected at specs#192 before archival. **The overclaim is the lesson**: - "stated nowhere" is a stronger claim than "the fact I care about is not - stated", and the first is easier to write and harder to defend. - -- [x] T009 [US1] Run the SC-001 reading. Give somebody who has not opened - `pkg/server` the specification alone and three questions: what does `Start()` - do and in what order; what logging will a consumer get; and what must they set - in `Options`? **The second is the one to watch** — a reader who learns only - that logging is routed into `slog`, and not that debug and trace are forced on - and startup is missed, has been told the reassuring half. A person is - preferred; a fresh agent given only `spec.md` is the fallback. **Record what - it proves and what it does not.** - - **Three of three — the first clean reading in the programme.** A fresh agent - given only `spec.md` answered the order `Start()` works in (FR-007, and it - noted the order is stated three times in the document), what logging a - consumer gets, and what must be set in `Options`. - - On the second question it reported precisely what this task was watching for: - FR-002 alone gives **only the reassuring half** — "routes the server's own - logging into the consumer's slog" — and the full picture required FR-013, - FR-014, FR-015 and an edge case. It assembled it correctly, and recorded that - the answer is distributed across four places rather than stated in one. - - Two observations worth keeping: - - - **The amendment paragraphs are easy to skim past.** FR-014 and FR-016a each - carry the finding in a bolded sentence with the narrowing directly beneath - it, so a reader taking only the bold text would leave with the overclaim the - amendment removed. Recorded rather than restructured — the alternative is - burying the finding to protect the caveat. - - **It judged the document to read as one argument rather than a checklist - with footnotes**, explicitly contrasting it with the two baselines before - it, which both drew that verdict. Its reason: one thread runs from the Input - section through the priority-1 story, the edge cases, four requirements and - the success criteria, and the Gaps section narrates its own prior overclaim - rather than footnoting it. The requirement-list skeleton is still there; - what changed is that the prose connects evidence to conclusion. - - **What it proves and what it does not.** That all three answers are in the - text. Not that a consuming maintainer would assemble the second one — the - reading was told to watch for it. - -______________________________________________________________________ - -## Phase 4: User Story 2 — the contract is mostly somebody else's (Priority: P1) - -- [x] T010 [US2] Confirm FR-011 states that `Options` **embeds** - `*natsserver.Options` rather than wrapping or copying it, and what follows: a - consumer can reach every upstream option through it, and a change upstream - changes this package's surface with no commit here. Verified in - `pkg/server/types.go`, which is four lines long and is the most consequential - file in the repository. -- [x] T011 [US2] Confirm the single edge from **both ends** — `go.mod` here and - `osapi/go.mod` — and that FR-004 states **when** a break arrives: at the bump, - not at the change, because osapi pins a commit. -- [x] T012 [US2] Confirm FR-013 states what the contract does **not** let a - consumer decide. A contract stated only as what it offers is half a contract - when two of its decisions are unreachable. - -______________________________________________________________________ - -## Phase 5: User Story 3 — the two NATS baselines read as a pair (Priority: P2) - -- [x] T013 [US3] Confirm the seven section names are 002's verbatim and all - seven are present in order — FR-018 and SC-005. These were inherited from - `nats-client`'s baseline, which inherited them from `osapi-justfiles`' after - that one recorded three drifting. **Two hops without drift** is the first - evidence the correction holds rather than merely having been made once. Also - confirm nothing changed in the inventoried repository: - `git -C ~/git/osapi-io/nats-server status --porcelain` is empty. -- [x] T014 [US3] Read `nats-client`'s baseline and this one back to back and - confirm a reader can see that the two wrap opposite ends of the same library. - This is the pair test, and it is the smallest version of the programme's whole - purpose — SC-001 of `system`'s 002 asks it of all six. - -______________________________________________________________________ - -## Phase 6: Verification and archival - -- [x] T015 Run `cd specs && mise exec -- just test` — SC-006. -- [x] T016 Run `speckit-archive-run specs/001-nats-server-baseline` once this - branch has merged. This project's memory holds only a constitution, so the run - **seeds** rather than folds. -- [x] T017 Mark `system`'s 002 as having unit 8 done. Remaining after it: - `osapi-orchestrator` (unit 9, the largest), gohai's amendment (unit 10), and - two moves. - -______________________________________________________________________ - -## Dependencies & Execution Order - -- **T007 blocks Phase 4 and Phase 5** in substance rather than mechanically: the - three findings are what make FR-013 and FR-014 through FR-016a checkable. -- **T009 should run before T008** if only one can. A reader who has been shown - which pages omit the facts is no longer a reader who has seen nothing else. -- **Phase 6** is last; T016 needs this branch merged. - -## Notes - -- No Go code changes. No change of any kind in `nats-server`. -- Every gap here implies a change this repository must make — a flag for debug - and trace, attaching the logger before the goroutine starts, a default for - `ReadyTimeout` — and naming the change is not making it. -- osapi embeds this server and **osapi's own baseline does not mention any of - the three facts either**. Worth knowing when reading the pair. diff --git a/history/osapi-001-provider-contract/checklists/requirements.md b/history/osapi-001-provider-contract/checklists/requirements.md deleted file mode 100644 index c57d227..0000000 --- a/history/osapi-001-provider-contract/checklists/requirements.md +++ /dev/null @@ -1,67 +0,0 @@ -# Specification Quality Checklist: Provider contract - -**Purpose**: Validate specification completeness and quality before proceeding -to planning **Created**: 2026-09-17 **Feature**: [spec.md](../spec.md) - -## Content Quality - -- [ ] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [ ] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [x] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [ ] No implementation details leak into specification - -## Notes - -- Items marked incomplete require spec updates before `/speckit-clarify` or - `/speckit-plan` - -### Two items fail by design, and iterating would make the spec worse - -**"No implementation details" and "No implementation details leak"**. Every -requirement cites the file, interface or advisory it was written from: -`internal/provider/errors.go:28`, `internal/provider/facts.go:64`, -GHSA-7fjw-v3g9-326g. The constitution's Verification principle requires a claim -about the codebase to be measured rather than asserted, and its Correction -principle requires a requirement to be written from evidence the repository -already carries. Removing the citations would satisfy the checklist and destroy -the spec's only means of being checked. The requirement text itself stays -behavioural — what a provider must do — and the paths appear as evidence, never -as instructions. - -**"Written for non-technical stakeholders"**. The subject is how one layer of a -codebase behaves, and the readers are contributors and agents adding a provider. -There is no non-technical audience for it. What a domain does for an operator -stays in the published documentation, which is stated in Assumptions. - -Both deviations are consequences of the checklist being written for -product-feature specs, applied here to a retrospective specification of existing -behaviour. Recorded rather than resolved; no iteration attempted, because each -available fix removes something the constitution requires. - -### Deliberate wording choices - -- **SC-003** names `add-a-domain`, a repository artifact rather than a - technology. The spec exists partly so that skill can stop restating mechanics, - so the outcome is only measurable by naming it. -- **FR-011** and **FR-013** state rules the code does not fully meet yet, with - the open gaps named in Assumptions. A requirement the code fails is a gap in - the code; writing the requirement to match current behaviour instead would be - the inverse of the Correction principle. diff --git a/history/osapi-001-provider-contract/plan.md b/history/osapi-001-provider-contract/plan.md deleted file mode 100644 index 67f106d..0000000 --- a/history/osapi-001-provider-contract/plan.md +++ /dev/null @@ -1,104 +0,0 @@ -# Implementation Plan: Provider contract - -**Branch**: `001-provider-contract` | **Date**: 2026-09-26 | **Spec**: -[spec.md](spec.md) - -**Input**: Feature specification from `specs/001-provider-contract/spec.md` - -## Summary - -**This plan is retrospective, and written to a gate.** The contract was stated -before it existed: `/speckit-archive-run` requires a `plan.md`, this feature had -none because it was specified and written rather than planned and implemented, -and the alternative was leaving the corpus unarchived. It guided no work. Read -it as a record of the shape the work had, not as a plan anybody followed — the -specification itself says the same about its own retrospective nature. - -State the provider contract in the corpus, so a reader learns what a provider -must do from the corpus rather than from whichever sibling provider they opened -first. Sixteen requirements, every one of the form "the corpus MUST state X". - -No Go source changes. The fourteen providers under `internal/provider/node/` and -the categorized ones beside them already conform; this documents existing -practice rather than proposing a change, which is why the work is writing and -citing rather than refactoring. - -## Technical Context - -**Language/Version**: Markdown. No compiled artifact. - -**Primary Dependencies**: None. The corpus depends on nothing at runtime. - -**Storage**: The corpus itself — `components/osapi/specs/` for the -specification, `.specify/memory/` for what archival consolidates into, and -`.claude/skills/add-a-domain/references/` for the skill that cites it. - -**Testing**: `just test` in the specs repository: `mdformat --check` over the -markdown, `just-fmt-check`, and `scripts/validate-skills.py` for every -`SKILL.md` and the relative links in its references. There is no code to unit -test, so link resolution and formatting are the whole gate. - -**Target Platform**: Not applicable. The readers are contributors and agents. - -**Project Type**: Documentation. No dependencies, no modules, no configuration, -no routing — stated plainly because a plan that leaves those sections looking -unfilled reads as an oversight rather than a fact about the work. - -**Performance Goals**: Not applicable. - -**Constraints**: A rule lives in exactly one place. FR-016 exists to enforce -that: the skill cites the requirement rather than restating it, because two -copies drift and the copy an agent happened to load wins. - -**Scale/Scope**: Sixteen requirements covering one layer of one repository. - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -| Principle | Assessment | -| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Documentation** — a repository states in full the conventions binding it | This feature is that principle applied to one layer. **Pass.** | -| **Verification** — a claim is measured, not inspected | Each requirement cites the file and symbol it describes, so a reader checks the rule against the code rather than taking the corpus on trust. `just test` measures format and links. **Pass.** | -| **Tooling** — a tool a repository invokes is declared where it declares its tools | No new tool. mdformat and the skill validator are already declared. **Pass.** | -| **Correction** — when applying a rule shows the rule is wrong, fix the rule first | Writing this spec found two errors in the skill it documents — a provider package that does not exist, and two helpers attributed to the wrong package — and both were corrected in the skill before the spec was finished. **Pass.** | -| **Workflow** — design output goes where the workflow reads it | The specification lives in the feature directory and consolidates into `.specify/memory/` on archive; the skill cites it from there. **Pass.** | - -No violations. Complexity Tracking is therefore empty and omitted. - -## Project Structure - -### Documentation (this feature) - -```text -specs/001-provider-contract/ -├── plan.md # This file -├── spec.md # The sixteen requirements and their evidence -└── checklists/ - └── requirements.md # From /speckit-specify -``` - -No `research.md`, `data-model.md` or `contracts/`. There was nothing to -research: the contract was read out of the code that already implements it. -There are no entities beyond the ones the spec names, and the corpus exposes no -interface to contract. - -No `quickstart.md` either, and that is the honest answer rather than an -omission: a corpus statement is validated by a reader following it, not by a -command that can be run. What stands in for it is the gate — `just test` — plus -the requirement that every rule cite the code it describes, so the two can be -compared. - -### Source Code (repository root) - -```text -components/osapi/specs/001-provider-contract/spec.md the contract -components/osapi/.specify/memory/spec.md where archival puts it -.claude/skills/add-a-domain/references/provider.md cites it, per FR-016 -``` - -**Structure Decision**: The rule lives in the corpus and the skill points at it. -FR-016 makes that direction binding rather than conventional, so the skill -reference carries a table mapping each rule to its requirement number and holds -only what the specification does not: where a provider's files go, what they are -called, and the scaffolding to start from. diff --git a/history/osapi-001-provider-contract/spec.md b/history/osapi-001-provider-contract/spec.md deleted file mode 100644 index 2f96a4f..0000000 --- a/history/osapi-001-provider-contract/spec.md +++ /dev/null @@ -1,248 +0,0 @@ -# Feature Specification: Provider contract - -**Feature Branch**: `feat/provider-contract` - -**Created**: 2026-09-17 - -**Status**: Completed - -**Input**: User description: "The provider contract — the corpus description of -how an osapi provider behaves, so the specs corpus states it once and the -add-a-domain skill can point at it instead of restating it." - -This is a retrospective specification. osapi has fourteen node domains plus -network, container, command, file and scheduled providers, all written against a -contract that has never been stated anywhere the workflow reads. Every -requirement below is written from evidence the repository already carries, and -names it. Where the published guide and the code disagreed, the code won. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - The contract can be read without reading a sibling provider (Priority: P1) - -Someone adding a provider learns what a provider must do from the corpus: the -operations it exposes, what each reports when the desired state is already met, -and what happens on an OS family it does not support. Today that knowledge is -recovered by opening an existing provider and inferring the rules from it. - -**Why this priority**: This is the feature. Everything else here is detail -underneath it. - -**Independent Test**: A reader with no osapi knowledge, given only the corpus, -can say what creating an existing resource reports, and what the caller sees -when the provider does not support the host's OS family. - -**Acceptance Scenarios**: - -1. **Given** the corpus alone, **When** a contributor asks what a provider must - implement, **Then** the operation set, the context-first convention, and the - result fields are stated without reference to a specific domain. -2. **Given** the corpus alone, **When** a contributor asks what makes an - operation idempotent here, **Then** the three desired-state outcomes are - stated as a rule rather than illustrated by one domain. -3. **Given** a provider that returns an error when deleting an absent resource, - **When** it is reviewed against the corpus, **Then** the review cites a - requirement rather than a sibling provider's behaviour. - -______________________________________________________________________ - -### User Story 2 - The four implementation patterns are distinguishable (Priority: P2) - -A provider that runs commands, one that writes its own configuration files, one -that delegates file writes, and one that calls an external API are four -different shapes with different obligations. A contributor can tell which shape -a new domain needs before writing it. - -**Why this priority**: Choosing the wrong shape is expensive to undo: it decides -whether the domain gets change tracking and drift detection for free, and -whether it has platform variants at all. - -**Independent Test**: Given a description of a new domain, a reader can name its -pattern from the corpus and say whether it needs stubs for the OS families it -does not implement. - -**Acceptance Scenarios**: - -1. **Given** a domain that manages a file under a well-known path, **When** the - contributor consults the corpus, **Then** delegating writes to the file - deployer is identified as the pattern, with what that delegation provides. -2. **Given** a domain that talks to an external daemon, **When** the contributor - consults the corpus, **Then** no platform variants are expected and - availability is established when the provider is constructed instead. - -______________________________________________________________________ - -### User Story 3 - The trust boundary is stated where a provider is the second caller (Priority: P3) - -A provider validates its inputs even though the request was already validated, -because the request path is not the only way a provider is reached: work is read -back from storage and executed later. - -**Why this priority**: Three of the four critical findings in the September 2026 -review were provider-side input handling. The rule existed in nobody's head as a -rule, so each domain decided for itself. - -**Independent Test**: A reviewer can point at a requirement stating that a value -which becomes a path, a filename or a command argument is validated in the -provider, and check any domain against it. - -**Acceptance Scenarios**: - -1. **Given** a provider that builds a filesystem path from a request value, - **When** it is reviewed against the corpus, **Then** validating that value in - the provider is required, not optional hardening. -2. **Given** a provider that passes a secret to a command, **When** it is - reviewed, **Then** the corpus requires the secret to reach the command - without appearing in its arguments. - -### Edge Cases - -- A host runs an OS family the provider does not implement. This is not a - failure: the provider reports the shared unsupported outcome and the work is - recorded as skipped, so the caller can tell "not available here" from - "broken". Evidence: `internal/provider/errors.go:28`, - `internal/agent/handler.go:414`. -- A provider is constructed but never registered with the agent's registry. - Facts are injected by walking registered providers, so its facts are absent - and any fact-dependent operation fails when called rather than at startup. - Evidence: `internal/provider/facts.go:64`, `internal/agent/agent.go`. -- The same unit of work is delivered twice. The provider's idempotency is what - makes the repeat harmless; delivery handling is specified separately. -- A request value arrives that the request path would now reject, because it was - stored before that rule existed, or because something else wrote to the store. - The provider's own validation is the only remaining check. -- A provider writes a file and the process dies mid-write. A partially written - configuration file is worse than none, so the write does not happen in place. - Evidence: `internal/provider/file/deploy.go`. - -## Requirements *(mandatory)* - -### Functional Requirements - -- **FR-001**: The corpus MUST state that a provider is the operations layer, - runs in the agent process rather than the controller, receives its parameters - from the unit of work, and returns a result; the controller never executes - operations itself. Evidence: `internal/agent/processor_*.go`. -- **FR-002**: The corpus MUST state the provider operation set and the - context-first convention on every method, and the list/get/create/update/ - delete shape used where a domain is a collection of resources. Evidence: - `internal/provider/node/sysctl/types.go`. -- **FR-003**: The corpus MUST state that a mutation result carries whether - anything changed, and that a per-resource error is reported in the result - rather than replacing it, so one failing host does not hide the others. - Evidence: `internal/provider/node/sysctl/types.go`. -- **FR-004**: The corpus MUST state the idempotency contract as a rule: creating - a resource that already exists reports no change and no error; deleting a - resource that is absent reports no change and no error; updating a resource - that is absent is an error. Evidence: - `internal/provider/node/sysctl/debian.go`, its Create, Update and Delete. -- **FR-005**: The corpus MUST state that an operation unsupported on the host's - OS family reports the shared unsupported outcome, and that this is distinct - from reporting no change — it is recorded as skipped. Evidence: - `internal/provider/errors.go:28`, `internal/agent/handler.go:414`. -- **FR-006**: The corpus MUST state the four implementation patterns, how to - recognise which applies, and what each obliges: a direct provider that runs - commands (`internal/provider/node/process`); a provider that delegates file - writes to the file deployer and gains change tracking, idempotency and - template rendering (`internal/provider/scheduled/cron`, - `internal/provider/node/service`, `internal/provider/node/certificate`, - `internal/provider/node/user`); a provider that manages its own configuration - files and marks them with a reserved filename prefix so managed files are - distinguishable from hand-written ones (`internal/provider/node/sysctl`); and - a provider that calls an external API, has no platform variants, and - establishes availability when constructed (`internal/provider/container`). -- **FR-007**: The corpus MUST state that platform-specific providers are - selected outside the provider — by OS family, and by whether the process is - containerised — and that the families a domain does not implement are present - as stubs rather than absent. Evidence: `pkg/sdk/platform` `Detect` and - `IsContainer`, consumed in `cmd/agent_setup.go`. -- **FR-008**: The corpus MUST state how a provider obtains host facts: by - embedding the shared facts holder, asserting the setter contract at compile - time, and being registered so facts are injected at startup. Evidence: - `internal/provider/facts.go:30`, `:41`, `:64`. -- **FR-009**: The corpus MUST state that validation on the request path does not - discharge the provider's: any value that becomes a filesystem path, a - filename, or a command argument is validated in the provider before use, - because work executed from storage is a second caller. Evidence: - GHSA-7fjw-v3g9-326g, fixed in `internal/provider/node/sysctl/debian.go`. -- **FR-010**: The corpus MUST state that a secret reaches a command without - appearing in its arguments, and that arguments are treated as logged and - publicly visible. Evidence: GHSA-6gc6-px2x-q95j; the stdin-carrying variant in - `internal/exec`, and the argument logging in `internal/exec/exec.go`. -- **FR-011**: The corpus MUST state that a value supplied by a caller is never - allowed to be parsed as an option by a command the provider runs. -- **FR-012**: The corpus MUST state that filesystem access goes through the - virtual filesystem abstraction rather than the standard library or a - substitute, so a provider is testable in memory and with injected failures, - and that commands go through the shared exec manager rather than being spawned - directly. Evidence: `CONTRIBUTING.md` "Filesystem access"; the constructors in - `internal/provider/*/debian.go`. -- **FR-013**: The corpus MUST state that a file a provider writes is not written - in place, so a crash cannot leave a half-written configuration file behind, - and that the mode and ownership a caller asked for are applied even when the - content is unchanged. Evidence: `internal/provider/file/deploy.go`; - osapi-io/osapi#498. -- **FR-014**: The corpus MUST state the testing obligations belonging to the - contract rather than to house style: each of the three idempotency outcomes is - covered; every stub family is asserted to report the unsupported outcome; and - each rejection required by FR-009 through FR-011 is covered by a case proving - no command ran and no file was written. Evidence: - `internal/provider/node/sysctl/debian_public_test.go`, - `internal/provider/node/*/darwin_public_test.go`. -- **FR-015**: The corpus MUST state what a provider does not touch: it adds no - message subject, stream, consumer or key-value bucket, and nothing in the - provider layer depends on the sibling messaging libraries. Evidence: no file - under `internal/provider/` imports `osapi-io/nats-client`; buckets and streams - are declared in `internal/job/config.go`. -- **FR-016**: The `add-a-domain` skill MUST cite these requirements rather than - restating them, so the corpus is the single statement and the skill routes to - it. - -### Key Entities - -- **Provider**: A domain's operations, running in the agent, selected by OS - family. Exposes the operation set in FR-002 and honours FR-004. -- **Result**: What an operation returns: the resource identity, whether anything - changed, and a per-resource error when one occurred. -- **Unsupported outcome**: The shared signal meaning "not available on this OS - family", distinct from a failure and from no change. -- **Facts**: Host attributes collected by the agent and injected into registered - providers. -- **File deployer**: The narrow contract a provider delegates file writes to in - order to gain change tracking, idempotency and template rendering. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A contributor states all three idempotency outcomes and the - unsupported outcome from the corpus alone, without opening a provider. -- **SC-002**: Every existing provider can be checked against the contract, and - each deviation is expressible as a named requirement it fails rather than as a - difference from a sibling. -- **SC-003**: The `add-a-domain` skill's provider reference contains no restated - mechanics: what remains is routing plus citations into this corpus. -- **SC-004**: A new domain's provider is reviewable against the corpus with no - appeal to "look at how sysctl does it". -- **SC-005**: Someone asking whether a provider needs to know about the - messaging layer gets a stated answer rather than inferring one from imports. - -## Assumptions - -- The audience is contributors and agents working on osapi, not operators. What - each domain does for a user stays in the published documentation; this states - how a provider behaves. File paths and named errors appear as evidence for - requirements, which the constitution's Verification principle requires. -- Requirements state current behaviour. Two known gaps are recorded rather than - specified away: the option-parsing rule in FR-011 is not yet enforced for user - and group names (recorded on GHSA-6gc6-px2x-q95j), and the atomic-write and - mode-application rules in FR-013 are not yet honoured by the file deployer - (osapi-io/osapi#498). A requirement the code does not yet meet is a gap in the - code, not an error in the corpus. -- The job system, the request, SDK and CLI layers, and the UI are out of scope - and become their own features. This covers only what a provider is and must - do. -- The four patterns are the four in the codebase today. A fifth would amend this - spec rather than being decided locally in one domain. -- `osapi-orchestrator` consumes osapi through its SDK and implements no - providers, so nothing here binds it. diff --git a/history/osapi-002-agent-key-store/checklists/requirements.md b/history/osapi-002-agent-key-store/checklists/requirements.md deleted file mode 100644 index 4a0334b..0000000 --- a/history/osapi-002-agent-key-store/checklists/requirements.md +++ /dev/null @@ -1,67 +0,0 @@ -# Specification Quality Checklist: Per-agent public key store - -**Purpose**: Validate specification completeness and quality before proceeding -to planning **Created**: 2026-09-17 **Feature**: [spec.md](../spec.md) - -## Content Quality - -- [ ] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [ ] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [x] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [ ] No implementation details leak into specification - -## Notes - -- Items marked incomplete require spec updates before `/speckit-clarify` or - `/speckit-plan` - -### The same two items fail here as in 001, for the same reason - -**"No implementation details" and "No implementation details leak"**. -Requirements cite the file, advisory or type they were written from: -`internal/controller/enrollment/accept.go`, `internal/agent/heartbeat.go`, -`internal/validation/target.go`, GHSA-3jh4, GHSA-j73r. The constitution's -Verification principle requires a claim about the codebase to be measured rather -than asserted, and its Correction principle requires requirements to come from -evidence the repository already carries. Removing the citations would satisfy -the checklist and leave the spec uncheckable. The requirement text itself stays -behavioural — what must be true — and the citations sit alongside as evidence. - -**"Written for non-technical stakeholders"**. The subject is how one layer of a -codebase authenticates another. The readers are contributors and agents. What an -operator needs is covered by FR-012 and FR-013, which require the fleet view and -the documentation. - -Recorded rather than resolved, as in `001-provider-contract`. No iteration -attempted: each available fix removes something the constitution requires. - -### Deliberate choices - -- **FR-009 was a question; clarify answered it.** Enforcement is opt-in per - side, and once on, an agent with no stored key is refused there. Recorded - under Clarifications, applied to FR-009, with SC-007 added for the staged - rollout. -- **FR-006 constrains targeting, not just storage.** Deterministic resolution is - what actually closes GHSA-j73r; a store nobody consults would satisfy the - letter of the other requirements and leave the attack open. -- **Two advisories, one spec.** Response verification and registry - authentication need the same store, so speccing them apart would have produced - two designs for one mechanism. diff --git a/history/osapi-002-agent-key-store/contracts/key-store.md b/history/osapi-002-agent-key-store/contracts/key-store.md deleted file mode 100644 index bed218e..0000000 --- a/history/osapi-002-agent-key-store/contracts/key-store.md +++ /dev/null @@ -1,72 +0,0 @@ -# Contract: the key store and its callers - -Phase 1. The store is internal, so this states the behavioural contract its -callers depend on rather than a wire format. - -## The store - -Three operations, all keyed by machine ID. - -| Operation | Called by | Contract | -| --------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Record | Enrollment acceptance only | Creates or replaces the record. On replacement, the outgoing key becomes the superseded key with an expiry set from the configured grace period. Never called by any message path. | -| Look up | Response and registration verification, fleet view | Returns the record, or a distinct "no record" answer. A failure to read is its own answer and never resembles "no record". | -| Remove | Enrollment rejection, agent removal | Deletes the record. After it returns, nothing signed by the removed key verifies. | - -**Concurrency**: acceptance and removal are rare and serialised through -enrollment; lookups are frequent and read-only. A cached lookup must be -invalidated by record and remove, not by elapsed time. - -## Response verification - -| Condition | Result | -| -------------------------------------------------------- | -------------------------------- | -| Controller not enforcing | Unchanged from today | -| Signature verifies against current key | Response is a result | -| Signature verifies against superseded key, inside grace | Response is a result | -| Signature verifies against superseded key, grace expired | Rejected, signature mismatch | -| Signature absent or malformed | Rejected, distinct from mismatch | -| No stored record | Rejected, "no stored key" | -| Store unreadable | Rejected, "store unavailable" | - -Rejection is never a silent drop on a single-target call: the job reports -failure. For a broadcast, the response does not count as that agent's reply and -the agent is reported as not having answered. - -## Registration verification - -| Condition | Result | -| ------------------------------------------------------------- | ------------------------------------------ | -| Controller not enforcing | Unchanged from today | -| Signature verifies, hostname and fingerprint match the record | Resolvable | -| Signature verifies, hostname differs from the record | Not resolvable; the record's hostname wins | -| Signature absent, malformed, or mismatched | Not resolvable | -| No stored record | Not resolvable | -| Store unreadable | Not resolvable | - -"Not resolvable" means invisible to target resolution, label matching, facts and -fleet status. It is not an error returned to the agent — the agent keeps -heartbeating, and the fleet view shows why it is not authoritative. - -## Target resolution - -| Situation | Behaviour | -| ----------------------------------------------- | ---------------------------------------------------------------------------- | -| One resolvable registration claims the hostname | Resolves to it | -| Several resolvable registrations claim it | Deterministic choice, preferring the enrolled machine; never iteration order | -| Only unresolvable registrations claim it | Resolves to nothing; the caller is told the target is unknown | -| Controller not enforcing | Unchanged from today | - -## Fleet view - -Each agent in the list reports whether a key is stored and, when it is, the -fingerprint. An operator can therefore see, before enabling enforcement, exactly -which agents would be refused. - -## Rollout - -Enforcement is per side and opt-in (FR-009). Enabling the controller first makes -responses and registrations verifiable while agents that have not re-enrolled -are visible in the fleet view. Enabling agents makes them refuse unsigned jobs, -which the GHSA-3jh4 fix already implements. Neither switch flips as a -consequence of upgrading. diff --git a/history/osapi-002-agent-key-store/data-model.md b/history/osapi-002-agent-key-store/data-model.md deleted file mode 100644 index 1d2d694..0000000 --- a/history/osapi-002-agent-key-store/data-model.md +++ /dev/null @@ -1,89 +0,0 @@ -# Data Model: Per-agent public key store - -Phase 1. Entities, their rules, and the transitions between them. - -## AcceptedAgent - -The stored record. One per accepted agent, in the enrollment KV bucket under -`accepted.`. - -| Field | Meaning | Rules | -| ---------------- | ----------------------------------------------- | --------------------------------------------------------------------- | -| Machine ID | The agent's permanent identifier | Required; the key of the record; never changes for a record | -| Hostname | The hostname claimed at acceptance | Required; what a registration's hostname is checked against | -| Public key | The key every later message is verified against | Required; recorded only at acceptance (FR-002) | -| Fingerprint | Digest of the public key | Required; what the fleet view shows | -| Accepted at | When acceptance happened | Required | -| Superseded key | The key replaced by the most recent rotation | Optional; absent unless a rotation is inside its grace period | -| Superseded until | When the superseded key stops being accepted | Required when a superseded key is present; an instant, not a duration | - -**Identity**: machine ID. Two records cannot share one, and a record is replaced -in place rather than duplicated. - -**Lifecycle**: - -```text -(none) ──accept──> current key -current key ──accept again (rotation)──> new current key + superseded key + expiry -current key + superseded ──expiry passes──> current key only -any state ──reject or remove──> (none) -``` - -Only enrollment acceptance moves a record rightwards. Nothing an agent sends -creates, changes or refreshes one (FR-002). - -## Registration claim - -What the agent publishes about itself, already present as `AgentRegistration` in -the registry bucket. This feature adds a signature and reclassifies two fields. - -| Field | Before | After | -| ----------- | ------------------------------------ | -------------------------------------------------------------------------------------- | -| Machine ID | Self-reported, trusted | Self-reported, used only to find the stored record | -| Hostname | Self-reported, trusted for targeting | A claim, valid only when the signature verifies against the record found by machine ID | -| Fingerprint | Self-reported, unchecked | A claim, must match the stored fingerprint | -| Signature | Absent | Required when the controller is enforcing; covers the identity-bearing fields | - -**Rule**: a registration is *resolvable* — visible to target resolution, labels, -facts and fleet status — only when its signature verifies against the stored -record for its machine ID (FR-004, FR-005). An unresolvable registration is not -an error to the agent; it is simply not authoritative. - -**Contested hostname**: when more than one registration claims a hostname, only -resolvable ones are candidates, and among those the enrolled machine wins -deterministically (FR-006). - -## Job response claim - -What an agent returns for a unit of work. Already signed by the agent; this -feature makes the signature checkable. - -| Property | Rule | -| ------------------- | ---------------------------------------------------------------------------------------------------------------- | -| Verified | Signature checks out against the stored record for the responding agent | -| Rejected | Signature absent, malformed, or signed by a key that is neither current nor within-grace superseded | -| Effect of rejection | The response is not a result: single-target reports failure, broadcast drops it from that agent's tally (FR-003) | - -## Verification outcome - -Every verification resolves to exactly one of these, and they are never -collapsed (FR-010): - -| Outcome | Meaning | What an operator does | -| ------------------ | ------------------------------------------------------- | ------------------------------------------------------- | -| Verified | Signature matched current or within-grace key | Nothing | -| No stored key | The agent has not been accepted since the store existed | Re-enrol that agent; expected during rollout | -| Signature mismatch | A key that is not this agent's signed the message | Investigate; this is the attack the advisories describe | -| Store unavailable | The record could not be read | Investigate the store; never treated as verified | -| Not enforcing | PKI is off for this side | Nothing; pre-existing behaviour (FR-009) | - -## Relationships - -```text -AcceptedAgent 1 ──── * Registration claim (by machine ID; verifies it) -AcceptedAgent 1 ──── * Job response claim (by machine ID; verifies it) -AcceptedAgent 0..1 ── 1 Superseded key (only during a rotation grace period) -``` - -The store is the authority. Both claim types carry self-reported identity, and -neither may write to the store. diff --git a/history/osapi-002-agent-key-store/plan.md b/history/osapi-002-agent-key-store/plan.md deleted file mode 100644 index 7b3eb3d..0000000 --- a/history/osapi-002-agent-key-store/plan.md +++ /dev/null @@ -1,119 +0,0 @@ -# Implementation Plan: Per-agent public key store - -**Branch**: `002-agent-key-store` | **Date**: 2026-09-18 | **Spec**: -[spec.md](spec.md) - -**Input**: Feature specification from `specs/002-agent-key-store/spec.md` - -## Summary - -Keep an accepted agent's public key after enrollment, and make the two things an -agent sends — its job responses and its registration — verifiable against it. -Today the key is written onto the pending-enrollment record and deleted with -that record on acceptance, so every controller-side verification path finds no -key and skips. One store closes both open advisories: GHSA-3jh4's deferred -response half, and GHSA-j73r, where an unauthenticated registration lets a -machine claim a hostname it never enrolled under and receive that host's work. - -Approach: store the key in the enrollment KV under a distinct prefix at the -moment of acceptance, sign registrations with the agent's existing key, and -verify both paths against the store. Enforcement is opt-in per side, so an -upgrade changes nothing until an operator turns it on. - -## Technical Context - -**Language/Version**: Go, `go 1.26.0` directive, CI builds the floor and stable. - -**Primary Dependencies**: NATS JetStream KV (`nats-io/nats.go/jetstream`), -`crypto/ed25519`, the sibling `osapi-io/nats-client`. No new dependency. - -**Storage**: NATS JetStream KV. The enrollment bucket already exists and already -holds `PendingAgent` records under the `enrollment.` prefix. Accepted keys go in -the same bucket under a second prefix, so no new bucket, config field, or -provisioning step appears. - -**Testing**: `testify/suite` table tests with `validateFunc`, generated mocks, -`just test` as the gate at 99.9% coverage; integration under `test/integration` -behind the `integration` build tag. - -**Target Platform**: Linux controller and agents; Darwin for development. - -**Project Type**: Single Go module — controller, agent and shared packages. - -**Performance Goals**: Verification adds one KV read per verified message on the -controller. Agents heartbeat every 10s and jobs are human-triggered, so the -added load is proportional to fleet size, not throughput. A per-machine-ID cache -with invalidation on acceptance and removal keeps steady-state reads near zero. - -**Constraints**: No new configuration knob — `ControllerPKI.Enabled`, -`AgentPKI.Enabled` and `ControllerPKI.RotationGracePeriod` already exist and are -documented as covering exactly this. Behaviour with PKI disabled must be -byte-for-byte unchanged. Signature verification must never fail open. - -**Scale/Scope**: Fleets in the hundreds. One key per machine ID, plus at most -one superseded key during a rotation grace period. - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -| Principle | Assessment | -| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **Documentation** — a repository states in full the conventions binding it | `agent-identity.md` gains what is signed, what is verified, what each failure means, and the rollout order. Nothing is left to this plan alone. **Pass.** | -| **Verification** — a claim is measured, not inspected | Every requirement maps to a test: forged response rejected, unsigned registration invisible to targeting, contested hostname resolved deterministically, grace period honoured then expired, removal effective. `just test` is the evidence. **Pass.** | -| **Tooling** — a tool a repository invokes is declared where it declares its tools | No new tool or dependency. **Pass.** | -| **Correction** — when applying a rule shows the rule is wrong, fix the rule first | The spec already records one such correction: FR-009 began as an open question and was settled by clarify before planning, not during implementation. **Pass.** | -| **Workflow** — design output goes where the workflow reads it | This plan, its research and design artifacts live in the feature directory and consolidate into memory on archive. **Pass.** | - -No violations. Complexity Tracking is therefore empty and omitted. - -## Project Structure - -### Documentation (this feature) - -```text -specs/002-agent-key-store/ -├── plan.md # This file -├── research.md # Phase 0 output -├── data-model.md # Phase 1 output -├── quickstart.md # Phase 1 output -├── contracts/ -│ └── key-store.md # Phase 1 output: the store's contract and failure modes -├── checklists/ -│ └── requirements.md # From /speckit-specify, updated by /speckit-clarify -└── tasks.md # Phase 2 output (/speckit-tasks, not created here) -``` - -### Source Code (repository root) - -```text -internal/controller/enrollment/ -├── types.go # AcceptedAgent record, store interface -├── accept.go # write the key on accept; remove it on reject -├── keystore.go # new: lookup, put, remove, rotation grace -└── keystore_public_test.go - -internal/job/client/ -├── signing.go # verify responses against the store, not a nil check -├── agent.go # ListAgents surfaces whether a key is held -└── client.go # response paths fail closed when enforcing - -internal/agent/ -├── heartbeat.go # sign the registration -└── pki/ # existing Sign/Fingerprint/VerifyWithGrace, unchanged - -internal/validation/ -└── target.go # only verified registrations are resolvable - -internal/controller/api/agent/ -└── agent_list.go # expose key-held state in the fleet view - -docs/docs/sidebar/features/ -└── agent-identity.md # what is signed, what is verified, rollout order -``` - -**Structure Decision**: The store belongs to the enrollment package, because -acceptance is the only event allowed to write it (FR-002) and enrollment already -owns that moment and the KV handle. Verification callers depend on a narrow -lookup interface rather than on the enrollment package's internals, so the job -client and target resolution do not import enrollment wholesale. diff --git a/history/osapi-002-agent-key-store/quickstart.md b/history/osapi-002-agent-key-store/quickstart.md deleted file mode 100644 index b7739c5..0000000 --- a/history/osapi-002-agent-key-store/quickstart.md +++ /dev/null @@ -1,96 +0,0 @@ -# Quickstart: validating the key store - -Phase 1. Scenarios that demonstrate the feature end to end. Each states what to -run and what proves it worked. - -## Prerequisites - -```bash -mise exec -- just react-build # ui/dist must exist before anything lints -mise exec -- just deps -``` - -## 1. Nothing changes with PKI off - -```bash -mise exec -- just test -``` - -**Proves**: with `controller.pki.enabled` and `agent.pki.enabled` unset, the -suite passes exactly as before. Responses and registrations behave as they do -today (FR-009, SC-005). - -## 2. An accepted agent keeps its key - -Start a controller and agent with PKI enabled on the controller, accept the -agent, then inspect the fleet view: - -```bash -osapi client agent list -``` - -**Proves**: the agent shows a stored key and its fingerprint (SC-003). Before -acceptance it shows none. - -## 3. A forged response is rejected - -Exercised in unit tests rather than by hand, since forging requires a second -key: - -```bash -mise exec -- go test ./internal/job/client/... -run Signature -``` - -**Proves**: a response signed by a key that is not the agent's is rejected, and -the job reports failure rather than a result (FR-003, SC-001). - -## 4. A hostname cannot be stolen - -```bash -mise exec -- go test ./internal/validation/... ./internal/job/client/... -run Resolve -``` - -**Proves**: an unsigned or mismatched registration is not resolvable, a second -machine claiming an enrolled hostname does not displace it, and resolution among -several claimants is deterministic (FR-004, FR-005, FR-006, SC-002). - -## 5. Rotation does not cause an outage - -```bash -mise exec -- go test ./internal/controller/enrollment/... -run Rotation -``` - -**Proves**: after a rotation the new key verifies, the superseded key verifies -until its expiry instant and not after, and a removed agent's key verifies never -(FR-007, FR-008, SC-004). - -## 6. Failures are distinguishable - -```bash -mise exec -- go test ./internal/job/client/... ./internal/controller/enrollment/... -run Verify -``` - -**Proves**: no stored key, signature mismatch and store unavailable produce -different, identifiable outcomes, and none of them is "verified" (FR-010, -SC-006). - -## 7. A staged rollout works - -Enable the controller side on a fleet where no agent has re-enrolled, then -observe: the fleet view lists every agent as having no stored key, and their -registrations are not authoritative. Re-enrol one agent and it becomes -authoritative without restarting the others. - -**Proves**: enforcement begins when the operator chooses, and progress is -visible throughout (FR-009, SC-007). - -## Gate before review - -```bash -mise exec -- just ready -mise exec -- just test -mise exec -- just docusaurus-fmt-check -``` - -The last is not covered by the first two, and a change touching -`agent-identity.md` needs it. diff --git a/history/osapi-002-agent-key-store/research.md b/history/osapi-002-agent-key-store/research.md deleted file mode 100644 index 6251a1c..0000000 --- a/history/osapi-002-agent-key-store/research.md +++ /dev/null @@ -1,125 +0,0 @@ -# Research: Per-agent public key store - -Phase 0. Each decision below was open when planning began; the Technical Context -now carries no NEEDS CLARIFICATION. - -## 1. Where the store lives - -**Decision**: The existing enrollment KV bucket, under an `accepted.` key -prefix, keyed by machine ID. Pending records keep the `enrollment.` prefix. - -**Rationale**: Acceptance is the only writer (FR-002), and the enrollment -watcher already holds that bucket handle and runs at exactly that moment. The -bucket is already declared, provisioned and configured, so no new bucket name, -config field or startup path appears — and nothing new can be forgotten in a -deployment. Key material here is public, so bucket-level sensitivity does not -change. - -**Alternatives considered**: - -- *A new dedicated KV bucket.* Cleaner separation, but adds a bucket name to - config, a creation path at startup, and an upgrade step, for one small record - per agent. -- *A field on `AgentRegistration` in the registry bucket.* Rejected outright: - the registry is what the agent writes, so storing the authority there would - let the attacker supply the key that validates their own message. That is - GHSA-j73r. -- *A file on the controller's disk beside its own keypair.* Breaks with more - than one controller and has no replication story; the KV already replicates. - -## 2. What identifies an agent - -**Decision**: Machine ID, matching enrollment. - -**Rationale**: Enrollment already records identity by machine ID, and both -advisories stem from hostname being self-asserted and mutable. Hostname becomes -a claim checked against the stored record rather than an identifier. - -**Alternatives considered**: *Hostname* — the thing being attacked. -*Fingerprint* — derived from the key, so it cannot be the lookup for the key -without circularity. - -## 3. How rotation and removal are represented - -**Decision**: The record holds the current key, optionally a superseded key, and -the instant the superseded key stops being accepted. Verification tries current, -then superseded while inside the grace period. Removal deletes the record. - -**Rationale**: Mirrors what the controller key already does — `VerifyWithGrace`, -`PreviousControllerPublicKey` and `ControllerPKI.RotationGracePeriod` — so -operators meet one rotation concept, not two. Storing the expiry instant rather -than a duration means a restart cannot silently extend the window. - -**Alternatives considered**: *Key history list* — unbounded, and nothing needs -the third-oldest key. *No grace at all* — rejects messages signed moments before -a rotation, which is the reason operators turn verification off. - -## 4. How enforcement is switched on - -**Decision**: No new configuration. `ControllerPKI.Enabled` governs the -controller side, `AgentPKI.Enabled` the agent side, per FR-009 and the clarify -answer. Enforcement begins only when the operator sets the flag for that side. - -**Rationale**: Both flags are already documented as covering PKI enrollment -*and* signing, so this makes the documented behaviour true rather than adding a -knob. A new flag would also create a second way to be "half on". - -**Alternatives considered**: *A separate `enforce_signatures` flag* — more -precise, but three states to reason about and a migration story for a field that -duplicates an existing one. - -## 5. What a verification failure reports - -**Decision**: Three distinguishable causes, on both sides: no stored key, -signature mismatch, and store unavailable. Never collapsed into one error. - -**Rationale**: FR-010, and the agent side already does this — GHSA-3jh4 shipped -`ErrControllerKeyUnknown`, `ErrJobEnvelopeMissing` and `ErrJobSignatureInvalid`. -An operator staging a rollout needs "this agent has not re-enrolled yet" to look -nothing like "something is forging messages". - -**Alternatives considered**: *A single verification error* — indistinguishable -in logs at exactly the moment it matters. - -## 6. How the fleet view shows readiness - -**Decision**: `ListAgents` reports, per agent, whether a key is stored and its -fingerprint, and the agent list endpoint surfaces it. - -**Rationale**: SC-003 and SC-007 require an operator to see who would be refused -*before* enabling enforcement. `ListAgents` already walks the registry, so this -is one lookup per agent on a path that is already a list operation. - -**Alternatives considered**: *A separate command* — another surface to learn for -something the fleet view is already for. - -## 7. Cost of verification - -**Decision**: Cache the stored key per machine ID in the controller, invalidated -on acceptance and removal. - -**Rationale**: Verification touches responses and every heartbeat, so an -uncached KV read per message would scale with fleet chatter. Acceptance and -removal are rare and already flow through one package, which makes invalidation -exact rather than time-based. - -**Alternatives considered**: *No cache* — simplest, and acceptable at current -scale, but the read sits on the heartbeat path. *TTL cache* — reintroduces a -window where a removed agent still verifies, which FR-008 forbids. - -## 8. What the agent signs in a registration - -**Decision**: A canonical serialisation of the registration's identity-bearing -fields, with the signature carried beside the record rather than inside the -signed bytes. - -**Rationale**: `AgentRegistration` already carries a self-reported -`Fingerprint`, and today nothing checks it. The stored key is the authority; the -fingerprint becomes a claim that must match. Signing requires a byte sequence -both sides derive identically, which rules out signing the marshalled struct -as-is if field order or optional fields can vary. - -**Alternatives considered**: *Sign the whole marshalled record* — fragile -against serialisation differences and any added field. *Sign only the machine -ID* — a valid signature would then authenticate a registration whose hostname -and labels had been altered, leaving GHSA-j73r open. diff --git a/history/osapi-002-agent-key-store/spec.md b/history/osapi-002-agent-key-store/spec.md deleted file mode 100644 index 2d6b551..0000000 --- a/history/osapi-002-agent-key-store/spec.md +++ /dev/null @@ -1,247 +0,0 @@ -# Feature Specification: Per-agent public key store - -**Feature Branch**: `feat/agent-key-store` - -**Created**: 2026-09-17 - -**Status**: Completed - -**Input**: User description: "Persist each accepted agent's public key so the -controller can verify what an agent sends: its job responses, and the -registration that decides where work is routed. Closes the deferred half of -GHSA-3jh4 and the identity half of GHSA-j73r together, because both need the -same missing store." - -osapi has PKI enrollment, signing on both sides, and verification code on both -sides. What it does not have is anywhere for the controller to keep an agent's -public key after enrollment. An accepted agent's key is written into the -pending-enrollment record and deleted when the record is, so every -controller-side verification path finds no key and skips. This specifies the -store and what must depend on it. - -## Clarifications - -### Session 2026-09-18 - -- Q: When the controller holds no key for an agent — today, every agent — must - it refuse that agent's messages, or accept them until the agent re-enrols? → - A: Refuse, but only once the operator enables enforcement for that side, with - the fleet view showing which agents still lack a key so re-enrolment can be - staged. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - The controller can tell a real agent's answer from a forged one (Priority: P1) - -An operator asks for a command to run on a host. The result that comes back is -checked against the key of the agent that enrolled as that host, so a result -written by something else does not reach the operator as fact. - -**Why this priority**: Agents already sign their responses. Nothing checks the -signature, so the signing is decorative. Everything else here builds on the -store this requires. - -**Independent Test**: With the store in place, a response carrying a valid -signature is accepted, and the same response re-signed by a different key is -rejected, without the operator's request appearing to succeed. - -**Acceptance Scenarios**: - -1. **Given** an accepted agent, **When** it answers a job, **Then** the - controller verifies the response against that agent's stored key before - treating it as a result. -2. **Given** a response signed by a key that is not the stored key for that - agent, **When** it arrives, **Then** it is rejected and the job does not - report success. -3. **Given** an agent with no stored key, **When** a response claiming to be - from it arrives, **Then** it is rejected rather than accepted unverified. - -______________________________________________________________________ - -### User Story 2 - A host's name cannot be claimed by another machine (Priority: P1) - -Work targeted at `web-01` reaches the machine that enrolled as `web-01`. A -second machine that announces the same hostname does not receive that work, and -does not replace the first in the operator's view of the fleet. - -**Why this priority**: Registration is what targeting reads. An unauthenticated -registration means the identity established at enrollment can be overridden -afterwards by anything able to write, which makes enrollment's guarantee -conditional on the transport alone. - -**Independent Test**: A registration whose signature does not verify against the -stored key for its machine ID is not visible to targeting, and work aimed at -that hostname continues to reach the enrolled machine. - -**Acceptance Scenarios**: - -1. **Given** an enrolled agent, **When** it registers, **Then** the registration - is signed and verified against its stored key before targeting will use it. -2. **Given** a registration claiming a hostname already held by a different - enrolled machine, **When** it is verified, **Then** it does not displace the - enrolled machine for targeting purposes. -3. **Given** a registration that is unsigned or fails verification, **When** - targeting resolves a hostname or label, **Then** that registration is not - among the candidates. - -______________________________________________________________________ - -### User Story 3 - Keys change without an outage, and leave when the agent does (Priority: P2) - -An operator rotates an agent's key, or removes an agent, and neither action -requires re-enrolling the fleet or leaves a stale key behind that would still be -trusted. - -**Why this priority**: A store that cannot be updated becomes a reason to turn -verification off. The controller key already has a rotation story with a grace -period; the agent key needs the equivalent. - -**Independent Test**: After a rotation, messages signed with the new key verify, -messages signed with the previous key verify until the grace period lapses, and -messages signed with a key that was removed do not verify at all. - -**Acceptance Scenarios**: - -1. **Given** an agent that rotates its key, **When** it re-enrolls or presents - the new key through the supported path, **Then** the stored key is replaced - and subsequent messages verify against it. -2. **Given** a rotation in progress, **When** a message signed with the previous - key arrives inside the grace period, **Then** it verifies, and after the - grace period it does not. -3. **Given** an agent that is removed or rejected, **When** anything signed by - its key arrives afterwards, **Then** it does not verify. - -### Edge Cases - -- An agent enrolled before this feature exists has no stored key. The system - must state which of "reject its messages" or "accept until it re-enrolls" - applies, and the same answer must hold for responses and registrations, so an - upgrade does not silently create a trusted-by-default class of agent. - Evidence: `internal/controller/enrollment/accept.go` deletes the pending - record, so no existing deployment has a stored key. -- Two machines present the same hostname. Enrollment records identity by machine - ID, so both can exist; targeting must choose deterministically rather than by - iteration order. Evidence: GHSA-j73r; `internal/validation/target.go` resolves - a hostname to the first registry entry claiming it. -- A machine ID is reused, for example a restored VM image. The stored key must - be replaced only through an accepted enrollment, never by the arrival of a - message signed with a different key. -- The store is unavailable when a response or registration arrives. Verification - cannot silently pass; the behaviour must be stated, and it must not be "treat - as verified". -- An agent is accepted while a previous registration for its hostname is still - present. The older entry must stop being authoritative for targeting once the - new agent is accepted. - -## Requirements *(mandatory)* - -### Functional Requirements - -- **FR-001**: The system MUST retain an accepted agent's public key beyond the - lifetime of its enrollment request, keyed by machine ID, so the key remains - available for every later verification. Evidence: - `internal/controller/enrollment/types.go` holds `PublicKey` on the pending - record; `accept.go` deletes that record on acceptance. -- **FR-002**: The system MUST record the key only as part of accepting an - enrollment. No message, registration or heartbeat may introduce or change a - stored key. Evidence: GHSA-j73r, where unauthenticated registration is the - attack. -- **FR-003**: The controller MUST verify an agent's job response against that - agent's stored key before the response is treated as a result, and MUST NOT - fall back to accepting an unverified response. Evidence: GHSA-3jh4; - `internal/job/client/signing.go` currently skips verification when no - counterpart key is set. -- **FR-004**: The agent MUST sign what it registers, and the controller MUST - verify that signature against the stored key before the registration is used - for targeting, labels, facts or fleet status. Evidence: - `internal/agent/heartbeat.go` writes the registration unsigned; - `internal/validation/target.go` reads it to resolve a target. -- **FR-005**: A registration that is unsigned, unverifiable, or signed by a key - other than the stored key for its machine ID MUST NOT be visible to target - resolution, and MUST NOT replace the entry of an enrolled machine. -- **FR-006**: Where a hostname is claimed by more than one machine, target - resolution MUST be deterministic and MUST prefer the enrolled machine, rather - than depending on iteration order. Evidence: GHSA-j73r. -- **FR-007**: The system MUST support replacing a stored key through an accepted - enrollment, and MUST accept the previous key for a bounded grace period after - replacement, so a rotation does not reject messages signed just before it. - Evidence: the controller key already has this shape — - `ControllerPKI.RotationGracePeriod`, `internal/agent/pki` `VerifyWithGrace` - and `PreviousControllerPublicKey`. -- **FR-008**: The system MUST remove a stored key when its agent is removed or - its enrollment is rejected, after which nothing signed by that key verifies. -- **FR-009**: Enforcement MUST be something the operator turns on per side, and - MUST NOT begin as a side effect of upgrading. Once enforcement is on for a - side, a message from an agent with no stored key MUST be refused there. Until - it is on, such a message is handled as it is today. "Treat as verified" is - never an outcome: an agent is either enforced against, or not yet enforced. - Evidence: `internal/controller/enrollment/accept.go` deletes the pending - record, so no existing deployment holds a stored key and every agent starts in - this state. -- **FR-010**: Verification failures MUST be distinguishable by cause: no stored - key, signature mismatch, and store unavailable are different conditions and - MUST be reported differently, so an operator can tell "not enrolled yet" from - "something is forging messages". Evidence: the agent side already does this - with three distinct sentinels, added for GHSA-3jh4. -- **FR-011**: The behaviour MUST be governed by the existing PKI switches rather - than a new one, and with PKI disabled the system MUST behave exactly as it - does today. Evidence: `ControllerPKI.Enabled`, `AgentPKI.Enabled`. -- **FR-012**: Operators MUST be able to see which agents have a stored key, and - its fingerprint, so a fleet can be checked before verification is enforced. -- **FR-013**: The documentation MUST state the rollout order, the failure modes - from FR-010, and what an operator does when an agent reports no stored key. - -### Key Entities - -- **Stored agent key**: An accepted agent's public key, held against its machine - ID, with the fingerprint recorded at acceptance and, during rotation, the - previous key and the moment it stops being accepted. -- **Registration**: What an agent publishes about itself — hostname, labels, - machine ID and status — which targeting reads. Authenticated under FR-004. -- **Job response**: What an agent returns for a unit of work. Authenticated - under FR-003. -- **Enrollment acceptance**: The only event that may create or replace a stored - key. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A response or registration signed by a key other than the one - recorded at acceptance is rejected in every case, and never reaches an - operator as a result or as fleet state. -- **SC-002**: Work targeted at a hostname reaches the machine that enrolled - under that hostname, including when another machine is publishing the same - hostname. -- **SC-003**: An operator can determine, for any agent, whether the controller - holds its key and which key that is, without reading storage directly. -- **SC-004**: Rotating an agent's key causes no rejected messages for correctly - behaving agents, and messages signed with the old key stop verifying once the - grace period lapses. -- **SC-007**: An operator can enable enforcement on one side, see which agents - would be refused, re-enrol them, and complete the rollout without the fleet - refusing work at a moment they did not choose. -- **SC-005**: With PKI disabled, behaviour is unchanged from before this - feature. -- **SC-006**: Every rejection is attributable to one of the stated causes, and - no rejection is reported only as a generic failure. - -## Assumptions - -- Enrollment remains the only trust anchor. This feature makes the identity - established there durable and checkable; it does not change how an agent is - accepted, nor introduce a second way to become trusted. -- Agents already hold a key pair and already sign responses, and the controller - already holds a key pair and signs jobs. Evidence: `internal/agent/pki` and - the GHSA-3jh4 fix merged as `f00dca607`. -- The grace period for an agent key follows the pattern already used for the - controller key rather than inventing a second mechanism. -- Transport credentials are not identity. NATS credentials continue to control - who may connect; this feature decides whose messages are believed once - connected. -- Out of scope: controller key rotation, which exists; the enrollment handshake - itself; and NATS authorization, which is deployment configuration rather than - osapi behaviour. -- The two advisories this closes are GHSA-3jh4 (its deferred - response-verification half) and GHSA-j73r. Both remain draft until the - implementation merges. diff --git a/history/osapi-002-agent-key-store/tasks.md b/history/osapi-002-agent-key-store/tasks.md deleted file mode 100644 index d638de9..0000000 --- a/history/osapi-002-agent-key-store/tasks.md +++ /dev/null @@ -1,301 +0,0 @@ -# Tasks: Per-agent public key store - -**Input**: Design documents from `specs/002-agent-key-store/` - -**Prerequisites**: [plan.md](plan.md), [spec.md](spec.md), -[research.md](research.md), [data-model.md](data-model.md), -[contracts/key-store.md](contracts/key-store.md), [quickstart.md](quickstart.md) - -**Tests**: Included. Coverage is gated at 99.9% by `just test`, so a task that -adds a branch without a test cannot merge — test tasks are not optional here. - -**Organization**: Grouped by the spec's three user stories. US1 and US2 are both -P1 and both independently shippable once the foundation lands; US3 is P2. - -## Format: `[ID] [P?] [Story] Description` - -- **[P]**: can run in parallel — different file, no dependency on an incomplete - task -- **[Story]**: the user story the task serves; Setup, Foundational and Polish - carry no story label - -## Path Conventions - -Paths are relative to the `osapi` repository root, not this repository. The -design documents live here; the code lives in -`https://github.com/osapi-io/osapi`. - -______________________________________________________________________ - -## Phase 1: Setup - -**Purpose**: Make the local gate runnable before any code changes. - -- [x] T001 Run `mise exec -- just react-build` to populate `ui/dist`, without - which `just ready` fails typechecking `ui/embed.go` -- [x] T002 Record the baseline by running `mise exec -- just ready` and - `mise exec -- just test`, capturing the current filtered coverage total from - `just test` output so the 99.9% gate is measured against a known start - -______________________________________________________________________ - -## Phase 2: Foundational (Blocking Prerequisites) - -**Purpose**: The store itself, and the single write point. Every user story -verifies against this, so none can begin until it exists. - -**⚠️ CRITICAL**: No user story work can begin until this phase is complete. - -- [x] T003 Add the `AcceptedAgent` record — machine ID, hostname, public key, - fingerprint, accepted-at, optional superseded key and superseded-until instant - — in `internal/controller/enrollment/types.go`, per - [data-model.md](data-model.md) -- [x] T004 Add the `accepted.` key prefix constant beside the existing - `enrollment.` prefix in `internal/controller/enrollment/types.go`, so both - prefixes are declared in one place -- [x] T005 Add the three verification-outcome sentinels — no stored key, - signature mismatch, store unavailable — in - `internal/controller/enrollment/keystore.go`, mirroring the agent-side - sentinels `ErrControllerKeyUnknown` / `ErrJobSignatureInvalid` / - `ErrJobEnvelopeMissing` (FR-010) -- [x] T006 Implement `Record`, `Lookup` and `Remove` against the enrollment KV - bucket in `internal/controller/enrollment/keystore.go`, with a read failure - returning the store-unavailable sentinel and a missing record returning the - no-stored-key sentinel — the two must never be the same answer - ([contracts/key-store.md](contracts/key-store.md)) -- [x] T007 Add the per-machine-ID lookup cache in - `internal/controller/enrollment/keystore.go`, invalidated by `Record` and - `Remove` and never by elapsed time (research decision 7; a TTL would leave a - removed agent verifying, which FR-008 forbids) -- [x] T008 Declare the narrow lookup interface the verification callers depend - on in `internal/controller/enrollment/keystore.go`, so `internal/job/client` - and `internal/validation` do not import the enrollment package wholesale (plan - Structure Decision) -- [x] T009 Write the accepted key at acceptance and remove it on rejection in - `internal/controller/enrollment/accept.go`, replacing the current behaviour - where deleting the pending record discards the only copy of the key (FR-001, - FR-002, FR-008) -- [x] T010 Wire the store through `setupEnrollmentWatcher` in - `cmd/controller_setup.go` so the watcher, the job client and target resolution - share one instance and therefore one cache -- [x] T011 Register the lookup interface for mock generation and run - `mise exec -- just generate`; generated mocks only, no handwritten fakes -- [x] T012 Add `testify/suite` table tests with `validateFunc` for record, - lookup, remove, cache invalidation and each distinct failure cause in - `internal/controller/enrollment/keystore_public_test.go` -- [x] T013 Add table tests covering key stored on accept and key removed on - reject in the existing accept tests under `internal/controller/enrollment/` - -**Checkpoint**: The controller retains an accepted agent's key, and can look it -up and remove it. US1 and US2 can now proceed in parallel. - -______________________________________________________________________ - -## Phase 3: User Story 1 - The controller can tell a real agent's answer from a forged one (Priority: P1) 🎯 MVP - -**Goal**: A job response is verified against the responding agent's stored key -before it is treated as a result. Closes the deferred half of GHSA-3jh4. - -**Independent Test**: A response with a valid signature is accepted; the same -response re-signed by a different key is rejected and the job does not report -success. - -- [x] T014 [US1] Replace the nil-counterpart-key skip in `unwrapSignedEnvelope` - in `internal/job/client/signing.go` with a store lookup by the responding - agent's machine ID, so verification is inert only when the controller is not - enforcing (FR-003, FR-011) -- [x] T015 [US1] Map each lookup and verification result to its own outcome in - `internal/job/client/signing.go` — verified, no stored key, signature - mismatch, store unavailable, not enforcing — with no path collapsing two of - them (FR-010) -- [x] T016 [US1] Fail closed on the single-target response path in - `internal/job/client/client.go`: a rejected response makes the job report - failure rather than returning an unverified result -- [x] T017 [US1] Fail closed on the broadcast response path in - `internal/job/client/client.go`: a rejected response does not count as that - agent's reply and the agent is reported as not having answered - ([contracts/key-store.md](contracts/key-store.md) Response verification) -- [x] T018 [P] [US1] Add table tests in `internal/job/client/` for a response - signed by the stored key, one signed by a foreign key, one unsigned, one with - no stored record, and one where the store read fails — asserting a distinct - outcome for each -- [x] T019 [P] [US1] Add a table test asserting that with - `ControllerPKI.Enabled` false the response path behaves exactly as before, - byte for byte (SC-005) - -**Checkpoint**: Job response verification is live and enforceable. GHSA-3jh4 is -fully closed. - -______________________________________________________________________ - -## Phase 4: User Story 2 - A host's name cannot be claimed by another machine (Priority: P1) - -**Goal**: A registration is authenticated before targeting reads it, and a -contested hostname resolves to the machine that enrolled under it. Closes -GHSA-j73r. - -**Independent Test**: A registration whose signature does not verify against the -stored key for its machine ID is not visible to targeting, and work aimed at -that hostname still reaches the enrolled machine. - -- [x] T020 [US2] Add the canonical serialisation of a registration's - identity-bearing fields — machine ID, hostname, fingerprint — as a shared - helper both sides derive identically, in a new `internal/job/registration.go` - (research decision 8; do not sign the marshalled struct, whose field order and - optional fields can vary) -- [x] T021 [US2] Add the signature field to `AgentRegistration` in - `internal/job/types.go` alongside its existing self-reported `Fingerprint`, - carried beside the signed bytes rather than inside them -- [x] T022 [US2] Sign the canonical bytes in `writeRegistration` in - `internal/agent/heartbeat.go` using the agent's existing PKI manager, and - leave the registration unsigned when `AgentPKI.Enabled` is false -- [x] T023 [US2] Add registration verification against the stored record — - signature valid, hostname matching, fingerprint matching — in - `internal/validation/target.go` or a helper it calls, returning resolvable or - one of the stated non-resolvable causes (FR-004, FR-005) -- [x] T024 [US2] Filter `ResolveTarget` in `internal/validation/target.go` to - resolvable registrations only, so an unverified registration is invisible to - target resolution, label matching, facts and fleet status -- [x] T025 [US2] Make a contested hostname deterministic in - `internal/validation/target.go`, preferring the enrolled machine rather than - the first registry entry claiming the name — the current first-entry-wins - behaviour is GHSA-j73r (FR-006) -- [x] T026 [P] [US2] Add table tests in `internal/agent/` asserting the - registration is signed over the canonical bytes when agent PKI is on and - unsigned when it is off -- [x] T027 [P] [US2] Add table tests in `internal/validation/` for a verified - registration, an unsigned one, one signed by a foreign key, one whose hostname - differs from the record, one whose fingerprint differs, and one with no stored - record — asserting resolvability for each -- [x] T028 [P] [US2] Add a table test for two registrations claiming one - hostname where only one is resolvable, asserting the enrolled machine resolves - and the result does not depend on iteration order (SC-002) - -**Checkpoint**: Targeting trusts only authenticated registrations. GHSA-j73r is -closed. - -______________________________________________________________________ - -## Phase 5: User Story 3 - Keys change without an outage, and leave when the agent does (Priority: P2) - -**Goal**: A rotation replaces the stored key and accepts the previous one for a -bounded grace period; a removal takes effect immediately. - -**Independent Test**: After a rotation, messages signed with the new key verify, -messages signed with the previous key verify until the grace period lapses, and -messages signed with a removed key never verify. - -- [x] T029 [US3] On re-acceptance, move the outgoing key to the superseded key - and set superseded-until from `ControllerPKI.RotationGracePeriod` in - `internal/controller/enrollment/keystore.go` — store the instant, not the - duration, so a restart cannot extend the window (FR-007) -- [x] T030 [US3] Verify against the current key, then the superseded key while - inside its grace period, in `internal/controller/enrollment/keystore.go`, - mirroring `VerifyWithGrace` in `internal/agent/pki` so operators meet one - rotation concept -- [x] T031 [US3] Treat a signature that matches only an expired superseded key - as a signature mismatch, not as a separate outcome - ([contracts/key-store.md](contracts/key-store.md)) -- [x] T032 [US3] Remove the stored record and invalidate its cache entry in both - `RejectAgent` and `RejectByHostname` in - `internal/controller/enrollment/accept.go`, so nothing signed by the removed - key verifies afterwards (FR-008) — note - `internal/controller/enrollment/rotation.go` rotates the *controller* key and - is out of scope -- [x] T033 [P] [US3] Add table tests in `internal/controller/enrollment/` for - rotation: new key verifies, superseded key verifies inside grace, superseded - key fails after the instant passes, and a third key never verifies -- [x] T034 [P] [US3] Add a table test asserting a removed agent's key stops - verifying immediately, with no cached hit surviving the removal - -**Checkpoint**: Verification can be enabled without rotation or decommissioning -becoming a reason to disable it. - -______________________________________________________________________ - -## Phase 6: Polish & Cross-Cutting Concerns - -- [x] T035 Report per agent whether a key is stored, and its fingerprint, from - `ListAgents` in `internal/job/client/agent.go` — the fields go on `AgentInfo` - in `internal/job/types.go`, which is what `ListAgents` returns and is a - separate struct from `AgentRegistration` (FR-012, SC-003) -- [x] T036 Surface the key-held state and fingerprint in the fleet view response - in `internal/controller/api/agent/agent_list.go`, then run - `mise exec -- just generate` for the OpenAPI and SDK artifacts -- [x] T037 Document what is signed, what is verified, what each of the failure - causes means, and the rollout order — controller side first, re-enrol the - agents the fleet view shows without a key, then agent side — in - `docs/docs/sidebar/features/agent-identity.md` (FR-013, SC-007) -- [x] T038 Bring the filtered coverage total back to 99.9% against the T002 - baseline, adding table rows rather than `//nolint` or ignore directives -- [x] T039 Run the full gate in order: - `mise exec -- just react-build && mise exec -- just generate && mise exec -- just ready && mise exec -- just test && mise exec -- just docusaurus-fmt-check` - — the last is required because T037 touches `docs/`, which `just md-fmt` - excludes -- [x] T040 **Not run.** Walking [quickstart.md](quickstart.md) needs a live - controller and agent, and this deployment has neither. Recorded as not done - rather than ticked: the store, both verification paths and rotation are - covered by the suites at 100%, and none of that is the same claim as a fleet - having been watched enrolling. -- [x] T041 Add the fix to GHSA-3jh4 and GHSA-j73r once merged, so both - advisories are ready to publish with the first release - -______________________________________________________________________ - -## Dependencies - -```text -Setup (T001-T002) - └─> Foundational (T003-T013) ← blocks everything below - ├─> US1 (T014-T019) ← independent of US2 - ├─> US2 (T020-T028) ← independent of US1 - └─> US3 (T029-T034) ← needs Foundational; verifies best after US1 - └─> Polish (T035-T041) -``` - -- **US1 and US2 are independent.** Both read the store; neither writes it, and - they touch different files. Either can ship first, and either closes its own - advisory on its own. -- **US3 depends only on Foundational**, but its grace-period tests are more - meaningful once US1 exercises the verification path. -- **T036 depends on T035**; **T039 depends on T037**; **T041 depends on the - whole thing being merged**. - -## Parallel Execution Examples - -Within Foundational, after T006 lands: - -```text -T012 (keystore tests) and T013 (accept tests) — different test files -``` - -Within US1, after T014-T017 land: - -```text -T018 (verification outcome tests) and T019 (PKI-disabled test) -``` - -Within US2, after T020-T025 land: - -```text -T026 (agent signing tests), T027 (resolvability tests), T028 (contested hostname) -``` - -Across stories, once Foundational is complete, US1 and US2 can be worked -simultaneously by two people without touching the same file. - -## Implementation Strategy - -**MVP**: Setup + Foundational + US1. That alone closes GHSA-3jh4's deferred half -and makes agent response signing mean something, which is the larger of the two -open advisories. - -**Increment 2**: US2. Closes GHSA-j73r. At this point both advisories are fixed -and only a release is needed to publish them. - -**Increment 3**: US3 + Polish. Rotation, removal, the fleet view and the docs — -what an operator needs before turning enforcement on across a real fleet. - -Enforcement stays off throughout: every increment is safe to merge because -`ControllerPKI.Enabled` and `AgentPKI.Enabled` govern when any of it takes -effect (FR-009, FR-011). diff --git a/history/osapi-003-corpus-backfill/checklists/requirements.md b/history/osapi-003-corpus-backfill/checklists/requirements.md deleted file mode 100644 index 03a37fe..0000000 --- a/history/osapi-003-corpus-backfill/checklists/requirements.md +++ /dev/null @@ -1,49 +0,0 @@ -# Specification Quality Checklist: Corpus backfill from the published site - -**Purpose**: Validate specification completeness and quality before proceeding -to planning - -**Created**: 2026-09-26 - -**Feature**: [spec.md](../spec.md) - -## Content Quality - -- [x] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [ ] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [x] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [x] No implementation details leak into specification - -## Notes - -One item fails by design, the same one the provider contract fails: - -- **Written for non-technical stakeholders.** The readers are contributors, - agents and operators. Both audiences are technical, and the whole subject is - which of them a given page serves. Writing it for a non-technical stakeholder - would require removing the distinction the feature is about. File paths and - line counts appear as evidence, which the constitution's Verification - principle requires. - -Line counts are cited as evidence rather than as requirements. They establish -the size of the problem — 1,898 lines of 16,938 — and will drift as the site -changes; a requirement keyed to a count would be stale by the time the work -starts, which is why FR-001 keys on the reader instead. diff --git a/history/osapi-003-corpus-backfill/contracts/citation.md b/history/osapi-003-corpus-backfill/contracts/citation.md deleted file mode 100644 index 91a8b73..0000000 --- a/history/osapi-003-corpus-backfill/contracts/citation.md +++ /dev/null @@ -1,65 +0,0 @@ -# Contract: what a citation is, and what checks it - -**Feature**: `003-corpus-backfill` | **Date**: 2026-09-28 - -A documentation feature exposes no interface. What it has is a convention that -other things depend on, and a gate that fails when the convention is broken. -This records both, because FR-007 and FR-008 are only enforceable if a citation -has a shape a checker can resolve. - -## The shape - -A citation is a relative markdown link from the citing file to the corpus -requirement, in a table that maps each rule to the requirement stating it. The -provider contract set this pattern; -`.claude/skills/add-a-domain/references/provider.md` is the working example. - -```markdown -| Rule | Stated in | -| --- | --- | -| A provider returns a typed result, never a formatted string | [FR-004](../../../components/osapi/specs/001-provider-contract/spec.md) | -``` - -Three properties matter: - -1. **Relative, not absolute.** `scripts/validate-skills.py` resolves relative - links from the file's own directory. An absolute URL is not checked by - anything, which makes it a restatement with extra steps. - - From a reference file the depth is **four** levels up — `references/` → - `/` → `skills/` → `.claude/` → the repository root — so the path opens - `../../../../components/osapi/specs/…`. Three levels lands in `.claude/` and - resolves to nothing; the gate says so by name, which is how this note came to - be written. - -2. **Named to a requirement, not to a document.** "See the job system - specification" is a pointer; "FR-007" is a citation. Only the second tells a - reader whether the thing they are looking for is there. - -3. **One statement per rule.** The citing file states the rule's *name* and - where it lives. It does not restate the rule, because two statements are what - this feature exists to end. - -## What checks it - -| Check | Where | What it catches | -| ------------------------------------------------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `scripts/validate-skills.py`, via `just skill-lint` and `just test` | this repository | A citation in a skill whose target file does not exist. This is the gate FR-008 leans on. | -| mdformat, via `just md-fmt-check` | this repository | Formatting of the corpus itself. It excludes `.claude/`, so a skill's own formatting is not checked — which is deliberate and documented in CONTRIBUTING. | -| Prettier, via `just docusaurus-fmt-check` in osapi | osapi | Site formatting. Runs with the tests, not the pre-commit checks. | -| The Docusaurus build | osapi | A broken internal link between site pages, including one left behind by a moved page. | - -## What nothing checks - -Three gaps, stated so the tasks can cover them by hand rather than assuming a -gate exists: - -- **A citation that resolves to a file but names a requirement that is not in - it.** The checker resolves paths, not anchors. Mitigation: the task that adds - a citation quotes the requirement it names. -- **A rule stated twice**, once in the corpus and once on the site. No tool - compares them. Mitigation: FR-010's single-change rule, and the verification - in [quickstart.md](../quickstart.md). -- **A rule that describes behaviour the code does not have.** FR-009 exists - because nothing catches this either; the corpus citing the code per FR-011 is - what makes the next reader able to. diff --git a/history/osapi-003-corpus-backfill/data-model.md b/history/osapi-003-corpus-backfill/data-model.md deleted file mode 100644 index cecb8f6..0000000 --- a/history/osapi-003-corpus-backfill/data-model.md +++ /dev/null @@ -1,68 +0,0 @@ -# Data Model: Corpus backfill from the published site - -**Feature**: `003-corpus-backfill` | **Date**: 2026-09-28 | **Spec**: -[spec.md](spec.md) - -A documentation feature has no schema. What it has instead is a mapping: for -each section of each page, which of two readers it serves, and therefore where -it ends up. This is that mapping, and it is the artifact the implementation -works from. - -The entities the specification names — candidate page, subject, citation, reader -— are defined there. What follows is their population. - -## Subject A: the job system → `004` - -| Section, as it stands | Lines | Destination | -| -------------------------------------------------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Overview, Architecture Principles | 9–27 | Corpus. What the job system is for and the constraints it holds to. | -| System Components, Core Components, Job Flow | 28–73 | Corpus. | -| NATS Configuration: KV buckets, JetStream | 74–111 | Corpus. Bucket names, TTLs and consumer settings are what a contributor needs and an operator never sets by hand. | -| Subject Hierarchy, Semantic Routing Rules | 112–152 | Corpus. | -| Target Types, Label-Based Routing, Label Limits | 153–212 | **Split.** The rules are corpus; the `_all` / `_any` / label syntax an operator types stays, and already has a home under the usage documentation to cite. | -| Supported Operations | 213–218 | Corpus. | -| Job Lifecycle: Submission | 221–236 | **Split.** The CLI examples stay; the submission mechanics move. | -| Job Lifecycle: Job States | 237–278 | **Split.** The state list and what each means to somebody polling stays on the site. The transition rules and the priority model move. | -| Job Lifecycle: Job Polling | 279–313 | Site. This is what an operator does. | -| Agent Implementation: Processing Flow, Append-Only Status, Multi-Host, Subscription Patterns | 314–400 | Corpus. | -| Facts Collection | 401–422 | Corpus, with the operator-facing reference already on the features pages cited rather than restated. | -| CLI Commands | 436–465 | Site. | -| Package Architecture, Separation of Concerns | 466–495 | Corpus. | -| Security Considerations, Performance Optimizations | 488–553 | Corpus. | -| Error Handling | 554–600 | Corpus. The newest content on the page: acknowledgement and redelivery, the idempotency obligation, command deadlines and the backstop, what cancelling a request does not reach, and the four per-host row statuses. | -| Monitoring | 601–630 | **Split.** The metrics an operator watches stay; what emits them moves. | - -## Subject B: building a domain → `005` - -| Source | Lines | Destination | -| --------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `adding-an-api-domain.md`, all sections | 1–654 | Corpus, wholly. Cross-layer consistency, provider types and file structure, the provider interface, the idempotency obligation, platform variants, naming, `FactsAware`, agent wiring, provider testing, the OpenAPI step and code generation, validation in specifications, handler implementation and broadcast support, registration, startup wiring, the SDK step and its conventions, the CLI step. | -| `api-guidelines.md` | 1–61 | Corpus, folded in: top-level categories, resource-oriented paths, verb mapping, when to split a category, node as top-level resource. | -| `principles.md` | 1–46 | Corpus, folded in — **after** each of the five is checked against `.charter/fragments/global/` and this project's constitution. One already stated there becomes a citation, not a second statement (research Finding 1). | -| `system-architecture.md`: Component Map, Entry Points, Layers, Request Flow | 12–174, 241–266 | Corpus, folded in. This is what a contributor reads immediately before the domain instructions. | - -## What stays on the site - -| Page | What remains, and for whom | -| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `architecture/architecture.md` | Unchanged: the three processes, the deployment models, how a request flows. Somebody deciding what to deploy. | -| `architecture/system-architecture.md` | Health checks — liveness, readiness, status, their endpoints and CLI access — plus authentication, authorization, CORS and external dependencies. Somebody configuring and calling osapi. | -| `architecture/job-architecture.md` | The job states as observed, polling, the CLI command reference, and the metrics worth watching. Somebody running jobs. | -| `development/adding-an-api-domain.md` | A short page: what adding a domain involves, a citation table into the corpus, a pointer to the `add-a-domain` skill. Its reader is a contributor, so citing is right here — and only here. | -| `architecture/api-guidelines.md`, `architecture/principles.md` | Nothing. Both addresses become client-side redirects to the contributor page above. | - -## The relationship to what memory already holds - -`components/osapi/.specify/memory/` holds two archived features: the provider -contract (001) and the agent key store (002). Both subjects overlap it, and the -overlap is a citation rather than a restatement in every case: - -| Moved content | What memory already states | Treatment | -| --------------------------------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| Provider types, file structure, the provider interface, naming, platform variants | The provider contract's FR-001 through FR-015 | The corpus statement cites the existing requirement. Subject B does not restate a provider rule that 001 already holds. | -| The idempotency obligation on a provider | 001 | Cited. | -| Job signing, response verification, agent identity | The agent key store (002) | Subject A's security section cites 002 rather than describing signing again. | -| Everything else | Nothing yet | Stated for the first time, with the code cited per FR-011. | - -This is the same test FR-008 applies to the site: one statement, any number of -citations. Memory is not exempt from it. diff --git a/history/osapi-003-corpus-backfill/plan.md b/history/osapi-003-corpus-backfill/plan.md deleted file mode 100644 index f53ee36..0000000 --- a/history/osapi-003-corpus-backfill/plan.md +++ /dev/null @@ -1,129 +0,0 @@ -# Implementation Plan: Corpus backfill from the published site - -**Branch**: `003-corpus-backfill` | **Date**: 2026-09-28 | **Spec**: -[spec.md](spec.md) - -**Input**: Feature specification from -`components/osapi/specs/003-corpus-backfill/spec.md` - -## Summary - -1,898 lines of the published site tell a contributor or an agent how osapi is -built rather than telling an operator how to use it. This moves that content -into the corpus as two subjects — the job system, then building a domain — -leaves every address resolving, and replaces each restatement in the -`add-a-domain` skill with a citation. - -The approach is the one the provider contract established: the corpus states the -rule, everything else cites it, and a citation whose target does not exist fails -the build. What that feature proved on one reference — 171 lines down to 127 — -this applies to the six pages that hold the rest. - -Nothing in osapi's Go source changes. What changes is where prose lives, which -of two readers each page serves, and whether a rule is stated once or twice. - -## Technical Context - -**Language/Version**: None. Markdown in two repositories: the corpus under -`components/osapi/`, the site under `docs/docs/sidebar/` in osapi. - -**Primary Dependencies**: `@docusaurus/plugin-client-redirects`, which osapi's -site does not yet install and which two of the six pages need. It is the only -dependency this feature adds. - -**Storage**: N/A. - -**Testing**: The gates that already run. In this repository, `just test` runs -mdformat and `scripts/validate-skills.py`, which resolves every relative link — -so a citation into the corpus that does not resolve fails the build. In osapi, -`just docusaurus-fmt-check` runs Prettier over the site, and the site build -fails on a broken internal link. - -**Target Platform**: The published documentation site and the specifications -corpus. - -**Project Type**: Documentation. There is no runtime, no interface and no -deployment. - -**Performance Goals**: N/A. - -**Constraints**: No address that resolves today may 404 afterwards. No surviving -site page may answer an operator with "see the specifications repository". Each -subject's corpus statement and its site change must both land, since the state -between them is duplication. - -**Scale/Scope**: Six pages, 1,898 lines by the specification's count and 1,925 -as they stand (see research Finding 2). Two subjects, becoming feature -specifications `004` and `005`. One skill updated. Two client-side redirects. - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -| Principle | How this feature satisfies it | -| ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Documentation** — a repository states in full the conventions binding it; a reference held elsewhere does not stand in place of stating them | This is the principle the feature serves. The corpus states each rule; the skill cites it. The one place to be careful is the reverse direction: a site page must not become a reference that stands in place of a statement an operator needs, which FR-012 forbids and the classification in research Decision 1 enforces. | -| **Verification** — a claim is measured, not inspected; where a check can be automated it is automated | The citation gate is `scripts/validate-skills.py`, which already fails on an unresolvable relative link. FR-009 and FR-011 are what verification means here: a rule is checked against the code before it is written, and the corpus statement cites the code so the next reader can check it again rather than trust it. | -| **Tooling** — versions are pinned; generated artifacts are not hand-edited | Adding the redirects plugin pins it in the site's `package.json` like every other dependency. Nothing generated is touched. | -| **Correction** — a rule invented to fill a template is noise; requirements come from evidence the repository carries | Every requirement in this feature's specification cites a page and a line count. Research Finding 1 applies the same test to the content being moved: a principle already stated in the charter becomes a citation rather than a second statement. | -| **Workflow** — one workflow governs specification, planning and implementation | This plan produces the artifacts Spec Kit expects, and the two subjects it identifies become specifications in their own right rather than being implemented out of this one. | - -**Result**: no violations. The Complexity Tracking table below is therefore -empty. - -One consequence of the Workflow principle is worth stating plainly: this -feature's own implementation is *not* the writing of the two subject -specifications. It decides the classification, the subjects, the addresses and -the citation pattern, and it carries out the first subject. The second follows -the same path as its own feature. - -## Project Structure - -### Documentation (this feature) - -```text -components/osapi/specs/003-corpus-backfill/ -├── plan.md # This file -├── research.md # Phase 0: classification, subjects, addresses, findings -├── data-model.md # Phase 1: what moves where, page by page -├── quickstart.md # Phase 1: how to verify the move -├── contracts/ -│ └── citation.md # Phase 1: the shape of a citation and the gate that checks it -├── checklists/ -│ └── requirements.md # From /speckit-specify -└── tasks.md # Phase 2 output (/speckit-tasks) -``` - -### Content (two repositories) - -```text -specs/ # this repository -├── components/osapi/specs/ -│ ├── 004-job-system/ # Subject A, its own feature -│ └── 005-building-a-domain/ # Subject B, its own feature -└── .claude/skills/add-a-domain/ - ├── SKILL.md # routing, unchanged in shape - └── references/ # restatements become citation tables - -osapi/ # the site -└── docs/docs/sidebar/ - ├── architecture/ - │ ├── architecture.md # stays; its Deep Dives links change - │ ├── job-architecture.md # splits: operator half stays - │ ├── system-architecture.md # splits: operator half stays - │ ├── api-guidelines.md # redirect - │ └── principles.md # redirect - └── development/ - └── adding-an-api-domain.md # short contributor page with citations -``` - -**Structure Decision**: the corpus half lands here, under -`components/osapi/specs/`, because it describes how one repository behaves. The -page half lands in osapi, because that is where the site is. CONTRIBUTING's -"Closing a change" assumes one implementation repository; this feature has two, -and research Finding 3 records the ordering that makes that safe and the risk it -leaves. - -## Complexity Tracking - -> No Constitution Check violations, so this table is empty by design. diff --git a/history/osapi-003-corpus-backfill/quickstart.md b/history/osapi-003-corpus-backfill/quickstart.md deleted file mode 100644 index 6c2d6af..0000000 --- a/history/osapi-003-corpus-backfill/quickstart.md +++ /dev/null @@ -1,128 +0,0 @@ -# Quickstart: verifying the corpus backfill - -**Feature**: `003-corpus-backfill` | **Date**: 2026-09-28 | **Spec**: -[spec.md](spec.md) - -Six success criteria, and what to run for each. Three are checked by a gate; -three are checked by reading, and this says what reading means so that "it looks -right" is not the answer. - -## Prerequisites - -```bash -cd ~/git/osapi-io/specs && mise install && just fetch -cd ~/git/osapi-io/osapi && mise install -``` - -## The gates - -```bash -# In the specifications repository: markdown formatting, justfile lint, and the -# citation checker that resolves every relative link in every skill. -cd ~/git/osapi-io/specs -just test - -# In osapi: site formatting, and the build, which fails on a broken internal link. -cd ~/git/osapi-io/osapi -just docusaurus-fmt-check -just docusaurus-build -``` - -`just test` failing on `skill-lint` is SC-004's gate doing its job: a citation -whose target does not exist. - -## SC-001 — a contributor answers from the corpus alone - -Not automatable, so it is a reading with a fixed question set. Open only -`components/osapi/specs/004-job-system/spec.md` and answer: - -1. What carries a job from the API to an agent, and what guarantees its - delivery? -2. What must an agent not do when the same job arrives twice, and what does it - do instead? -3. What bounds how long an operation runs, and what happens when the controller - stops waiting before the agent stops working? - -Each answer must be in the corpus, not in a link to the site. Question 3 exists -because that content is the newest on the page being moved (research Finding 2), -and is the easiest to leave behind. - -## SC-002 — an operator still finds everything on the site - -```bash -# No surviving site page may send an operator to the specifications corpus. -cd ~/git/osapi-io/osapi -grep -rn "specs repository\|specifications repository\|osapi-io/specs" docs/docs/sidebar/ \ - | grep -v "development/adding-an-api-domain.md" -``` - -Expected: no output. The one exclusion is the contributor page, whose reader is -not an operator — FR-012 is about operators. - -Then open the three surviving pages and confirm each reads as a whole page -rather than a remainder: `architecture/job-architecture.md`, -`architecture/system-architecture.md`, `architecture/architecture.md`. A page -whose first heading follows a paragraph that references content no longer -present has failed FR-003. - -## SC-003 — no address stops resolving - -```bash -cd ~/git/osapi-io/osapi - -# Every address that resolved before the change, including the two that became -# redirects. -for p in \ - architecture/architecture \ - architecture/system-architecture \ - architecture/job-architecture \ - architecture/api-guidelines \ - architecture/principles \ - development/adding-an-api-domain -do - test -f "docs/docs/sidebar/$p.md" && echo "page $p" && continue - grep -q "$p" docs/docusaurus.config.ts && echo "redirect $p" && continue - echo "MISSING $p" -done -``` - -Expected: six lines, none of them `MISSING`. A `MISSING` is a 404 nobody would -otherwise notice — which is why this check exists rather than a manual click -through the sidebar. - -## SC-004 — one statement, the rest citations - -```bash -# For each moved subject, the rule must be stated once. Pick a phrase unique to a -# moved rule and count where it appears. -cd ~/git/osapi-io -grep -rln "append-only" specs/components/osapi osapi/docs/docs osapi/.claude 2>/dev/null -``` - -Expected: the corpus specification, plus any file whose match is inside a -citation table. A second *statement* of the rule is the failure this catches; -`just test` catches only a citation that does not resolve. - -## SC-005 — the skill shrinks - -```bash -cd ~/git/osapi-io/specs -git diff --stat main -- .claude/skills/add-a-domain/ -``` - -Expected: net negative on `references/`, and `SKILL.md` unchanged in shape — it -routes, it does not explain. The provider contract's precedent is 171 lines down -to 127 on one reference. - -## SC-006 — every moved rule cites the code, and a gap is recorded as a gap - -```bash -# A corpus requirement about behaviour must name the file it describes. -cd ~/git/osapi-io/specs -grep -c "internal/" components/osapi/specs/004-job-system/spec.md -``` - -Expected: a count in the same order as the requirement count. Then, for a sample -of five requirements, open the file each cites and confirm the code does what -the requirement says. A requirement whose code does not match must say so — -FR-009 — and the phrase to look for is a recorded gap, not a silent restatement. diff --git a/history/osapi-003-corpus-backfill/research.md b/history/osapi-003-corpus-backfill/research.md deleted file mode 100644 index f7ef2f5..0000000 --- a/history/osapi-003-corpus-backfill/research.md +++ /dev/null @@ -1,168 +0,0 @@ -# Research: Corpus backfill from the published site - -**Feature**: `003-corpus-backfill` | **Date**: 2026-09-28 | **Spec**: -[spec.md](spec.md) - -The specification defers two decisions to planning: which subjects the moved -content becomes (FR-004), and what happens to each page's address (FR-006). Both -need the pages read in full. This records the answers, and three findings the -reading produced that the specification did not anticipate. - -## Decision 1: the classification of each candidate page - -FR-001 requires each page classified as moving wholly, splitting, or staying, -justified by who reads it. The six pages, read in full: - -| Page | Lines | Classification | Reader, and why | -| ------------------------------------- | ----- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `development/adding-an-api-domain.md` | 654 | moves wholly | Every section instructs somebody changing the code: provider types, file layout, the idempotency obligation, agent wiring, OpenAPI generation, handler and registration, SDK and CLI. Nothing tells an operator anything. | -| `architecture/job-architecture.md` | 630 | splits | The mechanics — subjects, streams, consumers, KV buckets, delivery, error handling — are for a contributor. The job states an operator polls and the CLI command reference are for an operator. | -| `architecture/system-architecture.md` | 330 | splits | The component map, the layer descriptions and the request flow are for a contributor. Health checks, their endpoints and CLI access, authentication, authorization and CORS are what an operator configures and calls. | -| `architecture/architecture.md` | 204 | stays | The three processes, the single-host and multi-host deployment models, and how a request flows are what somebody deciding what to deploy needs. Its "Deep Dives" links change; its content does not. | -| `architecture/api-guidelines.md` | 61 | moves wholly, folded | Path shape, verb mapping and where a new domain sits are rules for whoever adds one. Too small for a specification of its own, per FR-004. | -| `architecture/principles.md` | 46 | moves wholly, folded | Design principles constrain how osapi is built. Also too small to stand alone, and partly already stated elsewhere — see Finding 1. | - -**Rationale**: the test is who is served, not where the page sits. Two of the -six pages under `architecture/` are operator-facing in part or in whole, which -is why the specification's own Assumptions expected at least one "stays" or -"splits". - -**Alternatives considered**: moving all six wholly, which is what the line -counts suggest and what a reader of the directory names would guess. Rejected -because it would take the health check endpoints, the CORS configuration and the -deployment models with it — the content an operator opens the site for. - -## Decision 2: the subjects - -FR-004 requires grouping by subject rather than by page, each large enough to -warrant a specification. Two subjects, which become the next two feature -specifications in this project: - -### Subject A — the job system (becomes `004`) - -From `job-architecture.md`: the architecture principles, the component map, the -NATS configuration (KV buckets, JetStream), the subject hierarchy and semantic -routing, target types and label routing, the job lifecycle as the system runs -it, the agent's processing flow and append-only status model, multi-host -processing, and the error handling rules — acknowledgement, redelivery and the -idempotency obligation, command deadlines, what cancellation does and does not -reach, and the four per-host row statuses. - -First, per FR-005: it is the largest body of contributor knowledge on the site, -and the `add-a-domain` skill leans on it most. - -### Subject B — building a domain (becomes `005`) - -From `adding-an-api-domain.md` wholly; from `api-guidelines.md` the path, verb -and placement rules; from `principles.md` what survives Finding 1; and from -`system-architecture.md` the component map, the layer descriptions and the -request flow, which are what a contributor needs before touching any of it. - -**Alternatives considered**: one specification per page, which the specification -already rejects; and four subjects, splitting the API surface conventions and -the layer map into their own. Rejected because a 61-line and a 46-line -specification are the "specifications nobody reads" the spec's Edge Cases name, -and because the layer map exists to be read immediately before the domain -instructions. - -## Decision 3: what happens to each address - -FR-006 requires a per-page answer, and FR-012 forbids sending an operator to the -corpus. - -| Address | What it keeps | Why this and not the other | -| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `development/adding-an-api-domain.md` | A short contributor page: what adding a domain involves, a citation table to the corpus requirements, and a pointer to the `add-a-domain` skill | Its reader is a contributor, so citing the corpus is right for this page. A redirect would strand the reader arriving from an issue or a bookmark with no explanation of where the content went. | -| `architecture/job-architecture.md` | An operator page: the job states, polling, and the CLI command reference, keeping the title | Something real remains, so the address keeps a page rather than a redirect. | -| `architecture/system-architecture.md` | An operator page: health checks and their endpoints, authentication, authorization, CORS, external dependencies | Same reason. The layer map leaves; what an operator configures stays. | -| `architecture/architecture.md` | Unchanged | Nothing moves out of it. | -| `architecture/api-guidelines.md` | A client-side redirect to the surviving contributor page | Nothing operator-facing remains, and a 61-line stub beside a 46-line stub is clutter that answers nobody. | -| `architecture/principles.md` | A client-side redirect to the same page | Same. | - -Two redirects require `@docusaurus/plugin-client-redirects`, which the site does -not currently install: `docusaurus.config.ts` declares only the OpenAPI plugin. -Adding it is in scope for the page change, and is the only dependency this -feature introduces. - -**Confirmed 2026-09-28 by T003.** The plugin publishes at `3.10.2`, the exact -version of `@docusaurus/core` and `@docusaurus/preset-classic` in -`docs/package.json`, so it pins alongside them and `global/tooling`'s rule about -both provisioning paths resolving to one version holds without special handling. -The fallback in T003 — a stub page each instead — is not needed. - -**Alternatives considered**: deleting both files outright. Rejected by FR-006 — -their addresses resolve today, they are linked from the architecture sidebar, -and a 404 is invisible to whoever caused it. - -## Finding 1: none of the principles are already stated, and the near misses are not misses - -**Corrected 2026-09-28 by T002, the task that exists to check this.** The first -version of this finding claimed two of the five principles were already charter -rules. They are not, and the claim was made from the headings rather than from -the text. - -`principles.md` states five design principles. Checked against -`.charter/fragments/global/` and this project's constitution, all five are -unstated: - -| Principle | Nearest existing rule | Why it is not the same rule | -| ------------------------------------ | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| Simplicity and Minimalism | `global/documentation`: "a rule a tool already enforces is never restated as prose" | That governs where a rule is written down. This one governs what the codebase contains. | -| Automation through OpenAPI | `global/tooling`: "a tool whose output is committed is pinned" | That governs pinning a generator. This one says the API and its clients are generated at all, which nothing states. | -| Pluggability and Extensibility | — | Nothing. | -| Job System for Privileged Operations | — | Nothing. The agent key store (002) says how that system authenticates, not why privileged work goes through it. | -| RESTful API Design | — | Nothing. `api-guidelines.md`, also moving, elaborates it. | - -So all five are stated for the first time in Subject B. The check was still -worth running: a second statement of a charter rule is the drift FR-008 exists -to prevent, and the way to find out is to read the fragment rather than its -heading. T002 stays in the task list for the next reader, and its answer is now -recorded rather than assumed. - -## Finding 2: the job system page has grown since the specification was written - -`job-architecture.md` is 630 lines, not the 603 the specification records. The -difference is an Error Handling section added while closing the September -review: command deadlines and the backstop, why cancelling an API request does -not stop a running agent operation, and the four per-host row statuses including -`timeout`. - -This is the newest and most precise contributor knowledge on the page, it cites -the code it describes, and it is exactly what Subject A is for. The line count -in the specification's Assumptions is stale by 27 lines; the classification it -supports is not. - -**Confirmed 2026-09-28 by T001.** The live section boundaries, which -[data-model.md](data-model.md) works from: Facts Collection 414–435, CLI -Commands 436–457, Package Architecture 458–500, Security Considerations 501–507, -Performance Optimizations 508–568, Error Handling 569–621, Monitoring 622–630. -The 27 added lines all sit inside Error Handling, and every section after it -moved down by the same amount. - -## Finding 3: moving a subject cannot be one change - -FR-010 requires each subject to move in a single change, so no reader finds two -disagreeing statements. The corpus lives in the specifications repository and -the pages live in osapi, and `main` is protected in both: one change cannot span -them. - -What is achievable, and what this plan commits to: - -1. The corpus specification merges first, in the specifications repository. It - is the statement of record, and at this moment the site still holds the same - text — two copies, saying the same thing, because one was written from the - other. -2. The osapi change follows, in one pull request per subject: it removes or - rewrites the pages, adds the redirects, and updates the skills to cite. - -The window between them holds two identical statements and no citation pointing -at either, so no reader is sent somewhere that disagrees with where they are. -The risk is not divergence but abandonment: a corpus specification merged -without its site change leaves the duplication permanent. The task list -therefore carries the site change as a blocking task of the same subject, not as -follow-up work. - -**Alternatives considered**: moving the page in the same pull request that adds -the corpus specification, which is impossible across repositories; and writing -the corpus specification only after the page is deleted, which loses the content -if the second change stalls. diff --git a/history/osapi-003-corpus-backfill/spec.md b/history/osapi-003-corpus-backfill/spec.md deleted file mode 100644 index 19b9a39..0000000 --- a/history/osapi-003-corpus-backfill/spec.md +++ /dev/null @@ -1,244 +0,0 @@ -# Feature Specification: Corpus backfill from the published site - -**Feature Branch**: `003-corpus-backfill` - -**Created**: 2026-09-26 - -**Status**: Archived 2026-09-28 - -**Input**: User description: "Move the domain knowledge out of the Docusaurus -site and into the specs corpus, so the skills read it from the corpus and the -site keeps only what a user needs." - -The corpus holds two specifications against roughly twenty domains. The -knowledge that should be in it is in two other places instead: the -`add-a-domain` skill, which only whoever loads it benefits from, and the -published site, where a contributor's rules sit beside an operator's -instructions. This specifies which site content belongs in the corpus, what -stays, and what happens to a page whose contents move. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - A contributor answers a question from the corpus alone (Priority: P1) - -Someone adding an endpoint needs to know how a request becomes work an agent -runs: what carries it, what guarantees delivery, what happens on a retry. They -read the corpus and get an answer, without opening the published site and -without loading a skill. - -**Why this priority**: This is the feature. The corpus is meant to be what makes -the skills useful; while the knowledge lives elsewhere, every skill either -restates it or routes to a page written for a different reader. - -**Independent Test**: Given only the corpus, a reader can state how work reaches -an agent, what is guaranteed about delivery, and what a second delivery of the -same work must not cause. - -**Acceptance Scenarios**: - -1. **Given** the corpus alone, **When** a contributor asks how work reaches an - agent, **Then** the answer is stated in the corpus rather than linked to the - published site. -2. **Given** the corpus alone, **When a** contributor asks what a retry may do, - **Then** the delivery guarantee and the idempotency obligation are both - stated. -3. **Given** a skill that needs one of these rules, **When** it is read, - **Then** it cites the corpus requirement rather than restating the rule. - -______________________________________________________________________ - -### User Story 2 - An operator still finds what they need on the site (Priority: P1) - -Someone running osapi opens the site and finds how to use it: what each domain -does, how to call it, what the responses mean. Nothing they relied on has -disappeared, and no page answers them with "see the specifications repository". - -**Why this priority**: Equal to the first. A backfill that empties the site of -things operators use has moved a problem rather than solved one, and the damage -is invisible to the person doing the moving. - -**Independent Test**: Every task in the site's usage and feature documentation -can still be completed from the site alone, and no surviving page sends an -operator to the corpus. - -**Acceptance Scenarios**: - -1. **Given** a page whose contributor content moved, **When** an operator opens - its old address, **Then** they reach something useful rather than a missing - page. -2. **Given** the site after the move, **When** an operator looks for how a - domain behaves for them, **Then** it is still on the site. -3. **Given** a page that served both readers, **When** it is split, **Then** the - operator's half stays and reads as a whole page rather than a remainder. - -______________________________________________________________________ - -### User Story 3 - A rule has one home, and drift becomes impossible (Priority: P2) - -A rule about how osapi is built is stated once. A reader who finds it twice -finds a citation the second time, not a copy that may already disagree. - -**Why this priority**: Lower than the two above because it is the durable payoff -rather than the immediate one, but it is why moving beats copying: the provider -contract already proved a restated rule drifts, and the copy an agent happens to -load wins. - -**Independent Test**: For each moved subject, exactly one statement of each rule -exists across the corpus, the site and the skills; every other mention is a -citation. - -**Acceptance Scenarios**: - -1. **Given** a moved rule, **When** the site, the corpus and the skills are - searched, **Then** one statement and any number of citations are found. -2. **Given** a citation that names a requirement, **When** the gate runs, - **Then** a citation whose target does not exist fails the build. - -### Edge Cases - -- A page serves both readers in the same paragraph rather than in separate - sections. Splitting by section will not divide it, so the specification must - say what happens: the paragraph is rewritten for the operator and the - contributor's half restated in the corpus, rather than the page moving - wholesale and taking operator content with it. -- A moved page is linked from outside the repository — a README, an issue, a - bookmark, a search result. Deleting its address breaks those silently, and the - person who moved it will not see the breakage. -- The site and the corpus disagree during the move, because a page is moved in - one change and its citation added in another. A reader in between finds two - statements, which is the state this feature exists to end. -- A subject is too small to be its own specification. Four of the six candidate - pages are under 350 lines, and one is 46; a specification per page would - produce specifications nobody reads. -- A rule on the site is wrong, or describes behaviour the code no longer has. - Moving it moves a falsehood into the corpus, where it carries more authority - than it did on the site. - -## Requirements *(mandatory)* - -### Functional Requirements - -- **FR-001**: Each candidate page MUST be classified as moving wholly, - splitting, or staying, and the classification MUST be justified by who reads - it rather than by where it currently sits. Evidence: the six pages named in - the Assumptions. -- **FR-002**: Content that tells a contributor or an agent how osapi is built - MUST end up in the corpus. Content that tells an operator how to use osapi - MUST stay on the site. -- **FR-003**: A page that serves both readers MUST be split so that the - operator's half is a coherent page in its own right, not the remainder left - after the contributor's half was removed. -- **FR-004**: Moved content MUST be grouped by subject rather than by the page - it came from, and each subject MUST be large enough to be worth a - specification of its own. A subject too small to stand alone MUST be folded - into a larger one rather than given its own. -- **FR-005**: The job system MUST be the first subject moved, because it is the - largest body of contributor knowledge on the site and the one the - `add-a-domain` skill leans on most. -- **FR-006**: No page address that exists before this feature MUST 404 after it. - The specification MUST state, per moved page, whether its address keeps a - user-facing page or redirects, and why that choice fits that page. -- **FR-007**: A skill that needs a moved rule MUST cite the corpus requirement - rather than restate it, following the pattern the provider contract set: a - table mapping each rule to its requirement. -- **FR-008**: After a subject moves, exactly one statement of each of its rules - MUST exist across the corpus, the site and the skills. Every other mention - MUST be a citation. -- **FR-009**: A moved rule MUST be checked against the code before it is written - into the corpus, and a rule the code does not match MUST be recorded as a gap - rather than restated as though it held. -- **FR-010**: Each subject MUST move in a single change that moves the content, - updates the citations, and leaves the page's address resolving — so no state - exists where a reader can find two disagreeing statements of the same rule. -- **FR-011**: The corpus statement of a moved rule MUST cite the code it - describes, so a reader can check the rule rather than trust it. -- **FR-012**: The published site MUST NOT send an operator to the corpus. A - citation is for contributors and agents; an operator page that answers with - "see the specifications repository" has lost them. - -### Key Entities - -- **Candidate page**: A page on the published site holding contributor or agent - knowledge. Six are identified, totalling 1,898 lines. -- **Subject**: A body of related knowledge that becomes one corpus specification - — the job system, for example — independent of which pages it came from. -- **Citation**: A reference from a skill or a page to a corpus requirement, - replacing a restatement. Validated by the gate: a citation whose target does - not exist fails the build. -- **Reader**: Either an operator, who uses osapi, or a contributor or agent, who - changes it. Every classification decision turns on which one is being served. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A contributor with no prior knowledge can state how work reaches - an agent, what delivery guarantees hold, and what a retry must not cause, - using the corpus alone. -- **SC-002**: Every task in the site's usage and feature documentation can still - be completed from the site alone, and no surviving page refers an operator to - the corpus. -- **SC-003**: No address that resolved before this feature fails to resolve - after it. -- **SC-004**: For every moved subject, one statement of each rule exists and - every other mention is a citation. -- **SC-005**: The `add-a-domain` skill shrinks, and what remains is routing plus - citations rather than restated mechanics — the same outcome the provider - contract produced for its provider reference. -- **SC-006**: Every rule moved into the corpus cites the code it describes, and - any rule the code does not match is recorded as a gap rather than stated as - fact. - -## Assumptions - -- The six candidate pages are `development/adding-an-api-domain.md` (654 lines), - `architecture/job-architecture.md` (630), - `architecture/system-architecture.md` (330), `architecture/architecture.md` - (204), `architecture/api-guidelines.md` (61) and `architecture/principles.md` - (46), totalling 1,925 of the site's 16,938 lines. The remaining 15,013 lines — - thirty feature pages, the usage and SDK documentation — are user-facing and - out of scope. - - **Corrected 2026-09-28.** Three of this feature's own records were wrong, and - each was found by the work rather than by re-reading this file. They are - corrected here, ahead of archival, because a specification archived with wrong - inventories records the error as knowledge. - - | This said | It is | Found by | - | ----------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | `job-architecture.md` is 603 lines | **630** | Subject A's research. The page grew by an Error Handling section added while closing the September review — the newest and most precise contributor knowledge on it. | - | `api-guidelines.md` holds five guidelines | **six** | Subject B, verifying before writing. The sixth is path-versus-query parameters. | - | `principles.md` holds five principles | **eight** | Subject B. The three never named are Reliability and Stability, CLI Parity with API, and Least Privilege Mode. | - - The pattern in the last two is worth naming, because it is the argument for - FR-009. **Every line count in this list was right**; both pages are exactly as - long as recorded. What was wrong was the count of *items inside* two of them, - which was taken from reading about the pages rather than from the pages. A - measurement is evidence and a recollection is not, which is why FR-009 - requires each requirement checked against the repository before it is written - rather than transcribed from prose. - -- The thirty feature pages stay where they are. Each describes what a domain - does for an operator, which is the site's job. - -- Not every candidate page moves. The classification in FR-001 is the work, and - the answer for at least one page is expected to be "stays" or "splits" rather - than "moves". - -- No Go code changes. This moves and cites prose. - -- The site is formatted by Prettier and checked by a site formatting gate that - runs with the tests rather than with the pre-commit checks; the corpus is - formatted by a Markdown formatter that excludes the skills directory; and the - skills are validated by a checker that resolves every relative link. A - citation into the corpus that does not resolve therefore fails the build, - which is what makes FR-008 enforceable rather than aspirational. - -- The provider contract is the precedent for how this is done: its rules live in - the corpus and its skill reference cites them, which cut that reference from - 171 lines to 127. - -- Which subjects the moved content becomes is decided during planning, not here. - This specification requires grouping by subject and names the first one; it - does not enumerate the rest, because that decision needs the pages read in - full. diff --git a/history/osapi-003-corpus-backfill/tasks.md b/history/osapi-003-corpus-backfill/tasks.md deleted file mode 100644 index 7d6c27d..0000000 --- a/history/osapi-003-corpus-backfill/tasks.md +++ /dev/null @@ -1,372 +0,0 @@ -# Tasks: Corpus backfill from the published site - -**Feature**: `003-corpus-backfill` | **Date**: 2026-09-28 | **Spec**: -[spec.md](spec.md) | **Plan**: [plan.md](plan.md) - -**Input**: [research.md](research.md) for the classification, the subjects and -the addresses; [data-model.md](data-model.md) for the section-by-section -mapping; [contracts/citation.md](contracts/citation.md) for what a citation is; -[quickstart.md](quickstart.md) for how each criterion is verified. - -## Format: `[ID] [P?] [Story] Description` - -- **[P]** — may run in parallel with other **[P]** tasks: different files, no - dependency on an incomplete task. -- **[US1]**, **[US2]**, **[US3]** — the user story from the specification that - the task serves. -- Every task names the file it touches. - -## Path Conventions - -Two repositories, written in full because CONTRIBUTING's "Closing a change" -assumes one: - -- `specs/` — this repository. Corpus under `components/osapi/specs/`, skill - under `.claude/skills/add-a-domain/`. -- `osapi/` — the site under `docs/docs/sidebar/`, its config at - `docs/docusaurus.config.ts`. - -A task in `osapi/` and a task in `specs/` are never in the same pull request. -The ordering that makes that safe is research Finding 3: the corpus statement -merges first, the site change follows, and both must land. - -______________________________________________________________________ - -## Phase 1: Setup - -- [x] T001 Read `osapi/docs/docs/sidebar/architecture/job-architecture.md` in - full and confirm the section ranges in [data-model.md](data-model.md) against - the file as it stands. It is 630 lines, not the 603 the specification records - (research Finding 2), so the ranges shift by 27 near the end. -- [x] T002 [P] Check each of the five principles in - `osapi/docs/docs/sidebar/architecture/principles.md` against - `specs/.charter/fragments/global/` and - `specs/components/osapi/.specify/memory/constitution.md`. Record, per - principle, whether it is already stated there — research Finding 1. The answer - decides whether it becomes a citation or a statement in Subject B. -- [x] T003 [P] Confirm `@docusaurus/plugin-client-redirects` is compatible with - the site's Docusaurus version in `osapi/docs/package.json`. Two addresses - depend on it; if it is not, the fallback is a stub page each, and research - Decision 3 needs amending first. - -## Phase 2: Foundational (Blocking Prerequisites) - -These bind both subjects. Nothing in Phase 3 or later starts until they are -done. - -- [x] T004 [US3] Write the citation convention into - `specs/.claude/skills/add-a-domain/README.md`: the table shape from - [contracts/citation.md](contracts/citation.md), that citations are relative - and name a requirement rather than a document, and that `just skill-lint` is - what fails when one does not resolve. -- [x] T005 [US3] Verify the gate does what the contract claims: add a - deliberately broken citation to a scratch copy of a reference file, run - `cd specs && just skill-lint`, confirm it fails, and remove it. FR-008 is only - enforceable if this is true, and the Verification principle says measure - rather than assume. - -> Phases 1 through 3 are done, and Subject A has landed in both repositories: -> `004-job-system` is specified, planned, tasked, implemented and archived, its -> site page is 203 operator-facing lines, and the skill cites it. Phase 4's -> tasks were carried out as `004`'s own T003–T011 rather than from this list, -> which is what "Subject A is its own feature" means in practice. -> -> Phases 1 and 2 are done. T002 corrected research Finding 1 rather than -> confirming it: none of the five principles are already stated in the charter, -> and the two that looked like they were are near misses recorded in the -> finding. T001 confirmed the live section ranges, T003 confirmed the redirects -> plugin publishes at the site's exact version, and T005 measured the citation -> gate failing on a broken citation and passing once it was restored. - -## Phase 3: User Story 1 — a contributor answers from the corpus alone (Priority: P1) 🎯 MVP - -**Goal**: the job system is stated in the corpus, completely enough that the -three questions in [quickstart.md](quickstart.md) are answerable without opening -the site. - -**Independent test**: SC-001's reading, with only -`components/osapi/specs/004-job-system/spec.md` open. - -This is Subject A, and per FR-005 it goes first. It is its own feature: these -tasks carry it as far as a merged specification, because a corpus statement is -what the site change then cites. - -- [x] T006 [US1] Run `/speckit-specify` in `components/osapi` for Subject A, - naming the sections in [data-model.md](data-model.md) marked *Corpus*, and - producing `specs/components/osapi/specs/004-job-system/spec.md`. Per FR-009 - each requirement is checked against the code before it is written, and per - FR-011 it cites the file it describes. -- [x] T007 [US1] In the same branch, run `/speckit-plan` and `/speckit-tasks` - for `004`, then `/speckit-analyze`, and fix what it reports. Stages 2 through - 4 are one unit of review. -- [x] T008 [US1] Record, in `004`'s specification, every rule the code does not - match as a gap rather than as a statement — FR-009. A rule moved from the site - that the code has outgrown is the Edge Case the specification warns about. -- [x] T009 [US1] Cite `002` rather than restating it wherever Subject A reaches - signing, response verification or agent identity — - [data-model.md](data-model.md), "The relationship to what memory already - holds". -- [x] T010 [US1] Open the pull request for `004` in `specs/` and merge it. This - is the statement of record, and nothing in Phase 4 may start before it lands. - -**Checkpoint**: the job system is stated in the corpus. The site still says it -too, identically, and nothing points at either — the window research Finding 3 -describes. - -## Phase 4: User Story 2 — an operator still finds what they need (Priority: P1) - -**Goal**: the site keeps what an operator uses, every address resolves, and no -surviving page sends an operator to the corpus. - -**Independent test**: SC-002 and SC-003 from [quickstart.md](quickstart.md). - -- [x] T011 [US2] Split - `osapi/docs/docs/sidebar/architecture/job-architecture.md` per - [data-model.md](data-model.md): the job states as observed, polling, the CLI - reference and the metrics stay; the mechanics go. The result must read as a - whole page, not a remainder — FR-003. -- [x] T012 [US2] Split - `osapi/docs/docs/sidebar/architecture/system-architecture.md`: health checks - with their endpoints and CLI access, authentication, authorization, CORS and - external dependencies stay; the component map, the layers and the request flow - go to Subject B. -- [x] T013 [US2] [P] Update the Deep Dives and Further Reading links in - `osapi/docs/docs/sidebar/architecture/architecture.md`, which is otherwise - unchanged. A link to a section that moved is the broken internal link the site - build catches. -- [x] T014 [US2] Replace - `osapi/docs/docs/sidebar/development/adding-an-api-domain.md` with the short - contributor page from research Decision 3: what adding a domain involves, a - citation table into the corpus, and a pointer to the `add-a-domain` skill. -- [x] T015 [US2] Add `@docusaurus/plugin-client-redirects` to - `osapi/docs/package.json` and `osapi/docs/docusaurus.config.ts`, redirecting - `architecture/api-guidelines` and `architecture/principles` to the page from - T014, and delete both files. -- [x] T016 [US2] Run the SC-003 address check from - [quickstart.md](quickstart.md). Six lines, none `MISSING`. -- [x] T017 [US2] Run - `cd osapi && just docusaurus-fmt-check && just docusaurus-build`. The build is - what catches a link left pointing at moved content. -- [x] T018 [US2] Run the SC-002 grep. No surviving page may answer an operator - with "see the specifications repository"; the contributor page from T014 is - the only exclusion. -- [x] T019 [US2] Open the pull request in `osapi/` for T011–T018 and merge it. - The duplication from the Phase 3 checkpoint ends here — this task is what - closes FR-010's window, and leaving it undone leaves the duplication - permanent. - -**Checkpoint**: the job system is stated once. Both readers are served, and -every address resolves. - -## Phase 5: User Story 3 — a rule has one home (Priority: P2) - -**Goal**: the skill cites rather than restates, and Subject B follows Subject -A's path. - -**Independent test**: SC-004 and SC-005 from [quickstart.md](quickstart.md). - -- [x] T020 [US3] Replace every restatement of a job system rule in - `specs/.claude/skills/add-a-domain/references/` with a citation table naming - the `004` requirement, following `references/provider.md`'s existing example. - -- [x] T021 [US3] Run `cd specs && just test`. `skill-lint` resolving every - citation is SC-004's gate. - -- [x] T022 [US3] Run the SC-005 check: - `git diff --stat main -- .claude/skills/add-a-domain/` must be net negative on - `references/`, with `SKILL.md` unchanged in shape. - - **T022 passes, and the margin is worth recording.** Measured against - `d370e37`, the commit that planned this feature: - - | Reference | Then | Now | - | ------------- | ------- | ------- | - | `agent.md` | 119 | 136 | - | `api.md` | 193 | 125 | - | `cli.md` | 83 | 83 | - | `docs.md` | 60 | 81 | - | `provider.md` | 127 | 127 | - | `sdk.md` | 122 | 140 | - | **Total** | **704** | **692** | - - Net **-12** on `references/`, and `SKILL.md` unchanged at 140 lines, so the - check is met. It is met narrowly, and the reason is honest rather than a - shortfall: five rules turned out to have no home in the corpus — three CLI and - validation rules in `005`'s T016 to T018, and the absent `sdk-standards` - capability named twice — and each is now stated where it was with a note - saying the corpus does not hold it. Deleting them to make this number look - better would have lost five real rules. - - The measure that shows what the backfill actually achieved is the site, not - the skill: **950 lines of contributor knowledge left `docs/`** across the two - subjects — 427 from `job-architecture.md`, 578 from `adding-an-api-domain.md`, - 107 in two deleted pages, and 188 from `system-architecture.md`. - -- [x] T023 [US3] Run `/speckit-specify` for Subject B — building a domain — - producing `specs/components/osapi/specs/005-building-a-domain/spec.md` from - the sources in [data-model.md](data-model.md), citing `001` for every provider - rule it already holds rather than restating it. - -- [x] T024 [US3] Fold in the principles that T002 found are *not* already stated - in the charter or the constitution; cite the ones that are. - -- [x] T025 [US3] Carry Subject B through the same two-repository sequence: merge - `005` in `specs/`, then the osapi pull request that empties - `adding-an-api-domain.md` of what `005` now states and updates the citations. - -**Subject B completed 2026-09-28.** T012 through T019 and T023 through T025 were -carried out by [005-building-a-domain](../005-building-a-domain/tasks.md), which -ran the same two-repository sequence Subject A did: the corpus statement and the -skill citations merged in `specs/` as #152 and #154, then the site reduction -merged in `osapi/` as #545. - -| This task | Carried out by `005` | -| -------------------------------------------- | ----------------------------------------------------------------- | -| T012 the `system-architecture.md` split | T009, T010 — 330 lines to 142 | -| T013 `architecture.md` links | T012 — it also described `system-architecture.md` as what left it | -| T014 the contributor page | T005, T006 — 654 lines to 76 | -| T015 the redirects plugin and both deletions | T007, T008 | -| T016 the address check | T014's build, verified in the generated HTML | -| T017 the osapi gate | T014 | -| T018 the SC-002 grep | T013 | -| T019 merge the osapi pull request | T015 — osapi#545 | -| T023 specify Subject B | specs#152 | -| T024 fold in the principles | T003, which found all **eight** unstated, not five | -| T025 the two-repository sequence | the whole of `005` | - -Two of this feature's own records were wrong and `005` recorded both rather than -correcting them quietly: `api-guidelines.md` states six guidelines, not five, -and `principles.md` states eight principles, not five. The line counts here were -right, so neither page had drifted — the counts were taken from memory. They are -corrected when this feature is archived, which is T028. - -Only T026 through T029 remain. Archiving `003` comes after both its subjects, -and both have now landed. - -## Phase 6: Polish & Cross-Cutting Concerns - -- [x] T026 [P] Run the SC-006 check from [quickstart.md](quickstart.md): every - behavioural requirement in `004` names the file it describes, and a sample of - five is opened and confirmed against the code. - - **Passes.** 24 of `004`'s 25 requirements name the file they describe or cite - another specification. The exception is its FR-024, which obliges the skill to - cite rather than restate — a process rule with no code to name, so nothing is - missing. - - Five opened and confirmed against the code, not against the specification: - - | Requirement | Claim | Confirmed at | - | ----------- | --------------------------------------- | --------------------------------------------------------------------------- | - | FR-001 | a job is keyed `jobs.{job-id}` | `internal/job/client/client.go:334` and `:489` — `kvKey := "jobs." + jobID` | - | FR-014 | `MaxDeliver: 5`, `AckWait: 2m` | `cmd/root.go:175` and `:176` | - | FR-017 | the backstop is `DefaultCommandTimeout` | `internal/exec/types.go:36` — `10 * time.Minute` | - | FR-021 | the `job-queue` TTL is `1h` | `configs/osapi.yaml:92` | - | FR-019 | four per-host statuses | `pkg/sdk/client/status.go:46–51` — failed, skipped, timeout beside ok | - -- [x] T027 [P] Run the SC-001 reading with somebody who has not read the site - pages, using the three fixed questions. An author cannot test their own corpus - for completeness. - - **Already satisfied, and deliberately not re-run.** This task's reading is - defined in [quickstart.md](quickstart.md) as the three fixed questions asked - against `components/osapi/specs/004-job-system/spec.md` — which is exactly the - reading `004` ran as its own T015, by a fresh agent given that one file and - nothing else: no repository, no site, no other specification, no session - context. All three came back ANSWERABLE. - - Re-running it would put the same questions to the same file, so what it would - test is the reader rather than the corpus. Recorded here instead, with two - things a later reader should know: - - - That reading also found two real gaps, and `004` was amended for both before - this was written — the two clocks now give their durations, and the terminal - case of exhausted redelivery is stated. The file satisfies these questions - by a wider margin now than when the reading passed. - - Subject B was read separately, against its own specification and its own - four questions, as `005`'s T021. That reading found three further holes, all - since amended. Both subjects have therefore been read by somebody who had - not seen the site pages, which is what this task exists for. - -- [x] T028 Run `/speckit-archive-run specs/003-corpus-backfill` once T019 and - T025 have merged, consolidating this feature into - `specs/components/osapi/.specify/memory/`. Archive after the implementation, - not before: this feature's outcome is the classification and the pattern, and - both are only true once the moves have landed. - - **Archived 2026-09-28. The backfill is complete.** Merged into - `components/osapi/.specify/memory/`: 1 user story as US13, 12 requirements as - FR-082–FR-093, 4 entities, 3 edge cases, 2 outcomes as SC-024 and SC-025, and - 4 assumptions as AS-019–AS-022. The citation contract and the two redirected - addresses went to `memory/plan.md`. - - **Two stories and four outcomes folded rather than duplicated**, into entries - this feature's own subjects had already put in memory: the operator story into - US11, the one-home story into US12, and the outcomes about reading from the - corpus, citing the code, one statement per rule, and addresses resolving into - SC-013, SC-014, SC-016 and SC-019. Four of those folds widened the existing - entry from one subject to the general case. Nothing was superseded. - - That folding is the honest shape for a method feature archived after its - subjects. The alternative was twelve near-duplicates of rules already stated - per-subject. - -- [x] T029 Update `specs/components/osapi/specs/003-corpus-backfill/spec.md`'s - Assumptions with the corrected line count for `job-architecture.md` — 630, not - 603 — in its own pull request. A merged specification that turns out wrong is - amended before the work that depends on it, per the Correction principle. - - **Merged as specs#157, and it was three corrections rather than one.** The - task named the line count; the work had found two more: - - | This feature recorded | It is | - | ----------------------------------------- | ----- | - | `job-architecture.md` is 603 lines | 630 | - | `api-guidelines.md` holds five guidelines | six | - | `principles.md` holds five principles | eight | - - **Every line count was right.** What was wrong was the count of items inside - two of the pages, taken from reading about them rather than from them — which - is the failure FR-009 exists to catch, in this feature's own record. Corrected - in its own pull request ahead of archival, because a specification archived - with wrong inventories records the error as knowledge. - -## Dependencies & Execution Order - -### Phase Dependencies - -- **Phase 1** (T001–T003) has no dependencies. T002 and T003 are parallel. -- **Phase 2** (T004–T005) depends on Phase 1 and blocks everything after it. -- **Phase 3** (T006–T010) depends on Phase 2. T010 is a merge gate. -- **Phase 4** (T011–T019) depends on **T010**. The site cannot cite a - specification that is not merged. -- **Phase 5** (T020–T025) depends on T010 for the citations and on T019 for the - precedent. T023–T025 repeat Phase 3 and Phase 4 for Subject B. -- **Phase 6** (T026–T029) depends on T019 and T025, except T029, which can - happen at any time and should happen early. - -### The dependency that is not a phase - -T010 → T019 is the one that matters and the one nothing enforces. Between them -the job system is stated twice, identically. If T019 stalls, FR-010 is violated -permanently and quietly. Any pause between those two tasks is worth an issue in -osapi naming T019 as the remaining work — which is what `global/tracking` means -by an intent surviving being put down. - -### Parallel Opportunities - -- T002 and T003, both reading and neither writing. -- T013 alongside T011 and T012: a different file, and its links are checked by - the same build. -- T026 and T027, once the moves have landed. - -## Implementation Strategy - -**MVP is Phase 3 plus Phase 4** — Subject A, stated in the corpus and removed -from the site. That alone satisfies FR-005, the first two user stories and -SC-001 through SC-003, and it proves the two-repository sequence works before -Subject B, which is four times the page count, depends on it. - -Subject B follows as its own feature. It is deliberately not implemented out of -this one: the Workflow principle gives each specification its own lifecycle, and -Subject A's outcome is what tells Subject B whether the pattern holds. diff --git a/history/osapi-004-job-system/checklists/requirements.md b/history/osapi-004-job-system/checklists/requirements.md deleted file mode 100644 index 1f869a4..0000000 --- a/history/osapi-004-job-system/checklists/requirements.md +++ /dev/null @@ -1,56 +0,0 @@ -# Specification Quality Checklist: The job system - -**Purpose**: Validate specification completeness and quality before proceeding -to planning - -**Created**: 2026-09-28 - -**Feature**: [spec.md](../spec.md) - -## Content Quality - -- [ ] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [ ] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [ ] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [ ] No implementation details leak into specification - -## Notes - -Four items fail by design, and they fail the same way the provider contract's -checklist failed: - -- **No implementation details** and **no implementation details leak in**: every - requirement names a file, a constant or a bucket. That is deliberate, not an - oversight. The corpus exists so a contributor can check a rule rather than - trust it, which the Verification principle requires and 003's FR-011 states - outright. A version of this specification without file paths would be a worse - document that passed a checklist. -- **Written for non-technical stakeholders**: the audience is somebody adding an - operation to osapi or writing the agent side of one. The specification whose - audience is an operator is the site page this replaces half of. -- **Success criteria are technology-agnostic**: SC-002 and SC-003 are about - whether a cited file says what the requirement says. A technology-agnostic - version cannot express that, and it is the criterion that catches the failure - this subject actually has — three of the page's statements were untrue when it - was written. - -These four are the same trade the provider contract made, and 003's -specification records that precedent. Nothing here is blocked on them. diff --git a/history/osapi-004-job-system/data-model.md b/history/osapi-004-job-system/data-model.md deleted file mode 100644 index 5f8beee..0000000 --- a/history/osapi-004-job-system/data-model.md +++ /dev/null @@ -1,64 +0,0 @@ -# Data Model: The job system - -**Feature**: `004-job-system` | **Date**: 2026-09-28 | **Spec**: -[spec.md](spec.md) - -No schema. What this feature has instead is a correspondence: each requirement -in the specification replaces a passage on the site, and each replaced passage -leaves the page. This is that correspondence, and it is what the osapi change -works from. - -## The corpus statement, and what each part of it replaces - -| Requirement | Replaces, in `job-architecture.md` | Lines | -| ---------------------- | --------------------------------------------------------------------------------------------- | ---------------- | -| FR-001, FR-002 | Overview, Architecture Principles, Job Flow | 9–27, 52–73 | -| FR-003, FR-004 | NATS Configuration: KV buckets — the key format | 76–96 | -| FR-005 | KV buckets — the response bucket | 76–96 | -| FR-006, FR-007 | Subject Hierarchy, Semantic Routing Rules | 112–152 | -| FR-008 | Target Types, Label-Based Routing, Label Limits — the rules, not the syntax an operator types | 153–212 | -| FR-009 through FR-013 | Error Handling — items 1, 2 and 3 | 569–621 | -| FR-014, FR-015 | JetStream Configuration; Error Handling item 4 | 97–111, 569–621 | -| FR-016, FR-017, FR-018 | Error Handling — items 9 and 10 | 569–621 | -| FR-019, FR-020 | Job States — the transition rules; Error Handling items 7 and 8 | 237–278, 569–621 | -| FR-021 | KV buckets — the TTLs | 76–96 | -| FR-022, FR-023 | Security Considerations | 501–507 | -| — | Agent Implementation: Processing Flow, Append-Only Status, Multi-Host, Subscription Patterns | 314–413 | -| — | Facts Collection | 414–435 | -| — | Package Architecture, Separation of Concerns | 458–500 | -| — | Performance Optimizations | 508–568 | - -The last four rows are contributor content the specification does not restate -requirement by requirement: it is description rather than rule. It still leaves -the site, and it is stated in the corpus as the specification's context rather -than as numbered requirements — a rule is something a change can violate, and -"the package layout is like this" is not. - -## What the page keeps - -| Section | Lines | Why an operator needs it | -| ------------------------------------------------------------- | ------- | ------------------------------------- | -| Job Lifecycle: Submission — the CLI examples | 221–236 | How to submit one. | -| Job States — the list and what each means to somebody polling | 237–278 | What a status means when they see it. | -| Job Polling | 279–313 | How to watch it. | -| Target Types — the `_all` / `_any` / label syntax | 153–212 | What to type. | -| CLI Commands | 436–457 | The command reference. | -| Monitoring — the metrics worth watching | 622–630 | What to alert on. | - -## The three corrections - -| What the page states | What the corpus states | Where the page's version goes | -| ------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| Job key `{status}.{uuid}` | `jobs.{job-id}`, with status events keyed separately (FR-003, FR-004) | Removed. Contributor knowledge. | -| `MaxDeliver: 3`, `AckWait: 30s` | Defaults of 5 and 2m, overridable (FR-014) | Removed. Contributor knowledge, and the configuration file is the statement of record. | -| TTL 24h for completed and failed jobs | One bucket-wide TTL of 1h (FR-021) | Removed, for the same reason. | - -Nothing corrected stays on the page. Correcting a number in two places is how it -drifts again. - -## The skill - -| Reference | Section | Becomes | -| ------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| `.claude/skills/add-a-domain/references/agent.md` | Delivery semantics | A citation table naming FR-009 through FR-015. | -| `.claude/skills/add-a-domain/references/agent.md` | Processor, registry registration, platform selection | Unchanged. That is how to wire a domain, not how the job system behaves, and it belongs to `005` if it belongs anywhere. | diff --git a/history/osapi-004-job-system/plan.md b/history/osapi-004-job-system/plan.md deleted file mode 100644 index 6fef6a5..0000000 --- a/history/osapi-004-job-system/plan.md +++ /dev/null @@ -1,105 +0,0 @@ -# Implementation Plan: The job system - -**Branch**: `004-job-system` | **Date**: 2026-09-28 | **Spec**: -[spec.md](spec.md) - -**Input**: Feature specification from -`components/osapi/specs/004-job-system/spec.md` - -## Summary - -The specification states 24 requirements about how work reaches an agent. This -plan says what turning them into a landed change consists of, and the answer is -mostly subtraction: roughly 430 lines leave -`docs/docs/sidebar/architecture/job-architecture.md`, the operator half stays, -and the `add-a-domain` skill's restatements of the mechanics become citation -rows. - -Three of the requirements are corrections rather than statements, and they -change what the site says as well as what the corpus holds: the job key shape, -the consumer defaults and the bucket TTL. A site page left stating the old -numbers beside a corpus stating the new ones is worse than either alone. - -No Go code changes. The riskiest part is not the writing — the specification is -merged — it is that this lands across two repositories and the second half is -what ends the duplication. - -## Technical Context - -**Language/Version**: None. Markdown in two repositories. - -**Primary Dependencies**: None new. The redirects plugin that -[003's research](../003-corpus-backfill/research.md) identified belongs to the -pages `005` moves, not to this subject. - -**Storage**: N/A. - -**Testing**: `just test` here — mdformat, just-fmt and -`scripts/validate-skills.py`, which resolves every citation. -`just docusaurus-fmt-check` and `just docusaurus-build` in osapi, where the -build fails on a link left pointing at removed content. - -**Target Platform**: The corpus and the published site. - -**Project Type**: Documentation. - -**Performance Goals**: N/A. - -**Constraints**: `job-architecture.md` must keep its address and read as a whole -page afterwards, not as a remainder. No corpus requirement may restate what -[001](../001-provider-contract/spec.md) or [002](../002-agent-key-store/spec.md) -states. The three corrections must land in both places or neither. - -**Scale/Scope**: One page split, roughly 430 lines moving and roughly 200 -staying. One skill reference rewritten. Two pull requests, one per repository. - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -| Principle | How this feature satisfies it | -| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Documentation** | The corpus states the mechanics in full; the skill cites them; the site keeps what an operator needs and does not point them here. The three corrections exist because the same rule was stated twice and the copies disagreed — which is the failure this principle describes, caught in the act. | -| **Verification** | Every requirement cites the file it describes, and the three gaps were found by reading those files rather than by trusting the page. `just skill-lint` is what fails when a citation stops resolving; the quickstart's checks are commands rather than instructions to look. | -| **Tooling** | Nothing new is provisioned. | -| **Correction** | The three gaps are recorded as corrections with both sides named. FR-024 carries the obligation that the site change lands, rather than leaving the corpus and the page disagreeing — the state the correction principle calls out. | -| **Workflow** | This is stage 3 for `004`; tasks follow in the same branch, and the implementation is its own pull request per repository. | - -**Result**: no violations. - -## Project Structure - -### Documentation (this feature) - -```text -components/osapi/specs/004-job-system/ -├── plan.md # This file -├── research.md # Phase 0: what to verify before writing, and what was found -├── data-model.md # Phase 1: the corpus sections, and what each replaces -├── quickstart.md # Phase 1: how to verify the move -├── checklists/ -│ └── requirements.md # From stage 1 -└── tasks.md # Phase 2 output -``` - -### Content - -```text -specs/ # this repository -├── components/osapi/specs/004-job-system/ -│ └── spec.md # merged; the statement of record -└── .claude/skills/add-a-domain/ - └── references/agent.md # delivery mechanics become citations - -osapi/ -└── docs/docs/sidebar/architecture/ - └── job-architecture.md # contributor half removed, operator half stays -``` - -**Structure Decision**: the corpus half is merged already. What remains is the -osapi half, in one pull request: the page split, the three corrected numbers, -and the citation rows. `internal/` is untouched. - -## Complexity Tracking - -> No Constitution Check violations, so this table is empty. diff --git a/history/osapi-004-job-system/quickstart.md b/history/osapi-004-job-system/quickstart.md deleted file mode 100644 index b00427d..0000000 --- a/history/osapi-004-job-system/quickstart.md +++ /dev/null @@ -1,111 +0,0 @@ -# Quickstart: verifying the job system move - -**Feature**: `004-job-system` | **Date**: 2026-09-28 | **Spec**: -[spec.md](spec.md) - -Five success criteria. Two are commands, three are readings with fixed questions -so that "it looks complete" is not the answer. - -## Prerequisites - -```bash -cd ~/git/osapi-io/specs && mise install && just fetch -cd ~/git/osapi-io/osapi && mise install -``` - -## SC-002 — every cited file says what the requirement says - -```bash -# Each requirement that names a value or a key format cites a file. Open the file. -cd ~/git/osapi-io/osapi - -# FR-003, FR-004: the job key, and the status event key -grep -n 'kvKey := "jobs\."' internal/job/client/client.go -grep -n 'statusKey := fmt.Sprintf' internal/job/client/jobs.go internal/job/client/agent.go - -# FR-014: the consumer defaults -grep -n 'agent.consumer.max_deliver\|agent.consumer.ack_wait' cmd/root.go - -# FR-021: the bucket TTL -sed -n '/^ kv:/,/^ dlq:/p' configs/osapi.yaml - -# FR-009: the redelivery check -grep -n 'HasJobResponse' internal/agent/handler.go - -# FR-017: the backstop -grep -n 'DefaultCommandTimeout' internal/exec/types.go -``` - -Each must print. A citation that no longer resolves is a requirement describing -code that has moved, and the corpus is then stating a rule nobody can check. - -## SC-003 — the corrections read as corrections - -```bash -cd ~/git/osapi-io/specs -grep -n "Gap" components/osapi/specs/004-job-system/spec.md -``` - -Expected: three, at FR-004, FR-014 and FR-021, each naming both what the page -said and what the code does. A correction that reads as a plain statement leaves -the next reader wondering whether the page or the corpus is stale. - -## SC-004 — one statement, and the citations resolve - -```bash -# After the osapi change: the mechanics must be gone from the page. -cd ~/git/osapi-io/osapi -grep -n "MaxDeliver\|AckWait\|{status}.{uuid}\|24 hours" \ - docs/docs/sidebar/architecture/job-architecture.md -``` - -Expected: no output. Any match is a second statement of a rule the corpus now -holds — and for the first three, a second statement that is wrong. - -```bash -# And the citations that replaced the restatements must resolve. -cd ~/git/osapi-io/specs -just test -``` - -`skill-lint` is the gate. 003's T005 measured it failing on a broken citation, -so a pass here means the citations point at something. - -## SC-005 — nothing restates 001 or 002 - -```bash -cd ~/git/osapi-io/specs -grep -n "001-provider-contract\|002-agent-key-store" \ - components/osapi/specs/004-job-system/spec.md -``` - -Expected: FR-022 and FR-023, plus SC-005 itself. Then read the Security -Considerations requirements and confirm they cite rather than describe: a -sentence explaining how signing works is a restatement however it is introduced. - -## SC-001 — a contributor answers from the corpus alone - -Not automatable. Give somebody who has not read the site page only -`components/osapi/specs/004-job-system/spec.md`, and ask: - -1. What carries a job from the API to an agent, and what guarantees its - delivery? -2. What must an agent not do when the same job arrives twice, and what does it - do instead? -3. What bounds how long an operation runs, and what happens when the controller - stops waiting before the agent stops working? - -All three must be answerable without opening the site. The third exists because -it is the newest content on the page and the easiest to leave behind — 003's -research Finding 2. - -## The check that matters most - -```bash -# Is the duplication over? -cd ~/git/osapi-io/osapi && git log --oneline -1 -- docs/docs/sidebar/architecture/job-architecture.md -``` - -If that commit predates the merge of `004`'s specification, the corpus and the -page both state these rules and three of the page's numbers are wrong. The -feature is not done, whatever the corpus says. diff --git a/history/osapi-004-job-system/research.md b/history/osapi-004-job-system/research.md deleted file mode 100644 index 1e84d73..0000000 --- a/history/osapi-004-job-system/research.md +++ /dev/null @@ -1,78 +0,0 @@ -# Research: The job system - -**Feature**: `004-job-system` | **Date**: 2026-09-28 | **Spec**: -[spec.md](spec.md) - -The specification is already the output of this phase for the parts that needed -code reading: 24 requirements, each citing a file, three of them recording a gap -between what the page said and what the code does. This records what remains — -the decisions the writing left open — rather than repeating the specification. - -## Decision 1: the corrections land in both places, in the osapi change - -Three of the page's statements were wrong (spec FR-004, FR-014, FR-021). The -corpus now states the right values and records the wrong ones. The question this -leaves is what the *page* says afterwards, since the operator half survives. - -**Decision**: the surviving page states none of the three. The job key shape and -the consumer settings are contributor knowledge and leave with the rest of the -mechanics; the bucket TTL leaves too, because an operator who needs it reads the -configuration file, which is the statement of record for a configured value -under `global/documentation`. - -**Alternative considered**: correcting the numbers in place on the page and -leaving them there. Rejected — that keeps two statements of each, which is what -the subject moved to end, and the page's version has already drifted once. - -## Decision 2: which skill reference changes, and how far - -`.claude/skills/add-a-domain/references/agent.md` covers "processor, registry -registration, platform selection, delivery semantics". The last of those is what -`004` now states. - -**Decision**: the delivery-semantics section becomes a citation table naming -FR-009 through FR-015. The processor, registration and platform-selection -material stays as it is: it is how to wire a domain into the agent, not how the -job system behaves, and `005` is where it goes if it goes anywhere. - -**Alternative considered**: rewriting the whole reference now. Rejected — it -mixes two subjects in one change, and the part that belongs to `005` has not -been specified yet. - -## Decision 3: how much of the page moves - -Roughly 430 of 630 lines. The split is recorded line by line in -[003's data-model](../003-corpus-backfill/data-model.md) and confirmed against -the live file by 003's T001. - -**Decision**: the page keeps the job states as observed, polling, the CLI -command reference, and the metrics worth watching — and gains nothing. A page -that keeps its operator content and loses its mechanics is shorter, not -different in kind. - -## What was already verified, and where it is recorded - -| Claim | Verified against | Result | -| ----------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | -| Job definition key | `internal/job/client/client.go` | `jobs.{job-id}`; the page's `{status}.{uuid}` described the status-event keys. Spec FR-004. | -| Status event key | `internal/job/client/jobs.go`, `agent.go` | `status.{job-id}.{state}.{source}.{unix-nano}`. Spec FR-003. | -| Consumer settings | `cmd/root.go`, `configs/osapi.yaml`, `internal/agent/consumer.go` | `MaxDeliver` 5, `AckWait` 2m, not 3 and 30s. Spec FR-014. | -| Bucket TTL | `configs/osapi.yaml` | One TTL of `1h` for `job-queue`, not 24h per status. Spec FR-021. | -| Subject prefixes | `internal/job/subjects.go` | `jobs.query` and `jobs.modify`, built from a configurable base. Spec FR-006, FR-007. | -| Redelivery obligation | `internal/job/client/types.go`, `internal/agent/handler.go` | `HasJobResponse` before executing; ack without re-running. Spec FR-009. | -| Keepalive | `internal/agent/handler.go` | `startInProgressKeepAlive` extends the deadline while an operation runs. Spec FR-015. | -| Command deadline and backstop | `internal/exec/types.go` | `DefaultCommandTimeout`. Spec FR-016, FR-017. | -| Row statuses and causes | `pkg/sdk/client/status.go`, `internal/job/errors.go` | Four statuses; a machine-readable cause beside the message. Spec FR-019, FR-020. | - -## The risk this feature carries - -The specification is merged, so the corpus states these rules now. The site -states them too, and until the osapi change lands the duplication is real — -including for the three the page gets wrong, which is the worst version of it: a -reader who finds the page first gets numbers the code has not used for some -time. - -[003's research Finding 3](../003-corpus-backfill/research.md) describes this -window in general. For this subject it is not theoretical: it exists as of the -merge of spec.md, and the task list treats the osapi change as the completion of -this feature rather than as follow-up. diff --git a/history/osapi-004-job-system/spec.md b/history/osapi-004-job-system/spec.md deleted file mode 100644 index e9eb7c2..0000000 --- a/history/osapi-004-job-system/spec.md +++ /dev/null @@ -1,319 +0,0 @@ -# Feature Specification: The job system - -**Feature Branch**: `004-job-system` - -**Created**: 2026-09-28 - -**Status**: Archived 2026-09-28, amended 2026-09-28 — FR-016 and FR-017 gave -their clocks' names without their durations, and no requirement stated what -happens once redelivery is exhausted. Both were found by the SC-001 reading -(T015). FR-025 is new; the amendment is recorded in -[changelog.md](../../.specify/memory/changelog.md). - -**Input**: Subject A of [003-corpus-backfill](../003-corpus-backfill/spec.md) — -"the job system MUST be the first subject moved, because it is the largest body -of contributor knowledge on the site and the one the `add-a-domain` skill leans -on most" (FR-005). - -This states how work reaches an agent and what is guaranteed about it, so that a -contributor or an agent can answer that from the corpus rather than from a page -written for somebody running osapi. It replaces the contributor half of -`docs/docs/sidebar/architecture/job-architecture.md`, whose operator half stays -on the site. - -Every requirement cites the code it describes, per 003's FR-011. Three of them -correct what the page said, because the page had drifted — recorded as gaps -rather than restated, per 003's FR-009. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - A contributor adds an operation and knows what they are handed (Priority: P1) - -Somebody adding an operation to a domain needs to know what carries their -request, what the agent is guaranteed to receive, and what the system does to -them if the network hiccups. They read this, and they know before they write. - -**Why this priority**: it is the reason the subject moves first. Every domain -added to osapi passes through this system, and until this is stated the -`add-a-domain` skill either restates it or sends the reader to a page written -for an operator. - -**Independent Test**: given this specification alone, a reader can say which -store holds a job, which store holds its result, what a second delivery obliges -the agent to do, and what bounds how long the work may run. - -**Acceptance Scenarios**: - -1. **Given** this specification, **When** a contributor asks how a request - becomes work an agent runs, **Then** the path — store, notify, fetch, - execute, record — is stated with the code that implements each step. -2. **Given** this specification, **When** a contributor asks what happens when - the same job arrives twice, **Then** the obligation is stated as an - obligation, not as an observation about the current implementation. -3. **Given** this specification, **When** a contributor asks what an operation - may assume about time, **Then** the deadline, the backstop and what - cancellation does not reach are all stated. - -______________________________________________________________________ - -### User Story 2 - An agent implementer knows what the system will not do for them (Priority: P1) - -Somebody writing the agent side needs the obligations: what to check before -executing, what to write and when, what to acknowledge, and which failures are -theirs to handle rather than the system's to retry. - -**Why this priority**: equal to the first. The delivery guarantee is weaker than -it looks, and an implementer who assumes at-most-once will execute a reboot -twice. - -**Independent Test**: given this specification alone, a reader can state what -the agent must check before running an operation, what it must record before -acknowledging, and which of a failure's causes it must not retry. - -**Acceptance Scenarios**: - -1. **Given** the specification, **When** an implementer asks what to do with a - redelivered message, **Then** the check that makes re-execution unnecessary - is stated, along with what happens when the response cannot be written. -2. **Given** the specification, **When** an implementer asks which failures - terminate the message rather than letting it redeliver, **Then** the list is - stated with its reason. - -______________________________________________________________________ - -### User Story 3 - A rule that was wrong on the site is not wrong in the corpus (Priority: P2) - -Somebody reading a number in the corpus — a timeout, a delivery count, a key -format — finds the number the code uses, or an explicit statement that the two -disagree. - -**Why this priority**: lower than the two above, but it is what makes this a -specification rather than a copy. Three of the page's statements were already -untrue when this was written. - -**Independent Test**: for each number and key format stated, the cited code says -the same thing, or the requirement records the disagreement. - -**Acceptance Scenarios**: - -1. **Given** a requirement naming a configured value, **When** the cited file is - opened, **Then** it holds that value or the requirement says it does not. -2. **Given** a value the page stated wrongly, **When** the corpus is read, - **Then** the correction is visible as a correction rather than silently - different. - -### Edge Cases - -- A configured value has a default in code and a different value in the shipped - config file. Stating one hides the other, so a requirement about a configured - value names the default and the file that may override it. -- An operation is not idempotent and cannot be made so — `command.exec`, - `power.reboot`. The delivery guarantee cannot be strengthened for them, so the - obligation lands on the agent, and the specification has to say which side - owns it. -- A job outlives the controller's patience. Two clocks run, and the - specification must say what each bounds, because an operator reading "timed - out" will otherwise conclude the work did not happen. -- A subject prefix is configurable per namespace, so a stated subject is a shape - rather than a literal. - -## Requirements *(mandatory)* - -### Functional Requirements - -#### How work reaches an agent - -- **FR-001**: A job MUST be stored before it is announced. The definition is - written to the `job-queue` KV bucket under `jobs.{job-id}`, and only then is a - notification published to the `JOBS` stream. Evidence: - `internal/job/client/client.go` — `kvKey := "jobs." + jobID`, then the publish - that follows it. -- **FR-002**: The notification MUST carry the job's identity rather than its - content. An agent receives an ID on a subject and fetches the definition from - KV. Evidence: `internal/job/client/client.go`, and the agent's fetch in - `internal/agent/handler.go`. -- **FR-003**: Status MUST be recorded as append-only events rather than as a - mutated field. Each event is its own key, - `status.{job-id}.{state}.{source}.{unix-nano}`, and the job's status is - computed from them by priority. Evidence: `internal/job/client/jobs.go` and - `internal/job/client/agent.go` for the key shape; `job.StatusPriority` in - `internal/job/types.go` for the computation. -- **FR-004**: The corpus MUST state that the job definition key is - `jobs.{job-id}`, and MUST record that the published site said - `{status}.{uuid}` — a shape the code does not use. **Gap**: the page described - the status-event keys as though they were the job key. Evidence: - `internal/job/client/client.go` against - `docs/docs/sidebar/architecture/job-architecture.md`. -- **FR-005**: Results MUST be stored separately from the job, in the - `job-responses` bucket, so a caller reading a result does not read the job's - history to find it. Evidence: `internal/config/nats.go` declares both buckets. - -#### Routing - -- **FR-006**: Operations MUST be routed by a dot-notation subject under one of - two prefixes, `jobs.query` for reads and `jobs.modify` for writes, so that a - consumer can subscribe to one class of work without filtering the other. - Evidence: `JobsQueryPrefix` and `JobsModifyPrefix` in - `internal/job/subjects.go`. -- **FR-007**: The subject prefix MUST be treated as namespaced rather than - literal. `internal/job/subjects.go` builds both prefixes from a base that a - deployment can change, so a corpus statement naming `jobs.query` names a - shape. -- **FR-008**: A target MUST be resolvable as a hostname, a machine ID, a - broadcast (`_all`, `_any`) or a label selector, and the rules that decide - which agents a broadcast expects MUST be stated. Evidence: - `internal/job/subjects.go`, `job.ExpectedAgentHostnames`. - -#### What is guaranteed, and what is not - -- **FR-009**: The corpus MUST state that delivery is at-least-once, not - at-most-once, and MUST state the obligation that follows: before executing, an - agent checks whether it has already recorded a response for that job, and if - it has, acknowledges without re-executing. Evidence: `HasJobResponse` in - `internal/job/client/types.go`, used in `internal/agent/handler.go`. -- **FR-010**: The corpus MUST state which operations make this obligation load- - bearing rather than theoretical: `command.exec`, `command.shell`, - `power.reboot` and `power.shutdown` are not safe to run twice. Evidence: the - Error Handling section of the site page, which states this correctly, and the - operations in `internal/agent/processor_command.go`. -- **FR-011**: The corpus MUST state that a job which has run is terminal: the - agent acknowledges after recording a response, success or failure, so a failed - operation is not retried by redelivery. Evidence: `internal/agent/handler.go`. -- **FR-012**: The corpus MUST state what happens when the response cannot be - written after the operation ran — the failure is recorded best-effort and the - message is still acknowledged, because leaving it unacknowledged would - redeliver it and run the operation a second time. Evidence: - `internal/agent/handler.go`. -- **FR-013**: The corpus MUST state which failures terminate a message instead - of letting it redeliver — a malformed payload, an unparsable subject, a failed - signature — and why: none of them can succeed on retry. A failure to read the - job data itself is treated as possibly transient and left to redeliver. - Evidence: `internal/agent/handler.go`. -- **FR-014**: The corpus MUST state the consumer's delivery settings as defaults - that a deployment may override, and MUST record that the published site stated - different values. **Gap**: the page said `MaxDeliver: 3` and `AckWait: 30s`; - the defaults are `5` and `2m`. Evidence: `cmd/root.go` — - `agent.consumer.max_deliver` 5, `agent.consumer.ack_wait` 2m — and - `configs/osapi.yaml`, which sets the same values; consumed in - `internal/agent/consumer.go`. -- **FR-015**: The corpus MUST state that an operation outliving `AckWait` is not - redelivered mid-flight, because the agent extends the deadline while it runs. - Evidence: the in-progress keepalive in `internal/agent/handler.go`. - -#### Time - -- **FR-016**: The corpus MUST state that two clocks bound a job, that they bound - different things, and MUST give each one's duration rather than only its name: - `controller.api.job_timeout`, `30s` by default, bounds how long the controller - waits for a response, and the agent's command deadline bounds how long the - work itself may run. The gap between them is the point — the controller stops - waiting long before the work must stop. Evidence: `cmd/root.go`, which sets - `controller.api.job_timeout` to `30s`, and `configs/osapi.yaml`, which ships - the same value; `internal/job/client/client.go` for the wait; - `internal/exec/types.go` for the command deadline. -- **FR-017**: The corpus MUST state that a command with no deadline of its own - is bounded by a backstop rather than left to run forever, and MUST give both - its name and its duration: `DefaultCommandTimeout`, `10m`. A name alone does - not answer what an operation may assume about time, which is what US1 asks of - this section. Evidence: `DefaultCommandTimeout` in `internal/exec/types.go`, - applied in `internal/exec/exec.go`. -- **FR-018**: The corpus MUST state that cancelling the originating API request - does not stop a running agent operation, and that `job delete` removes the - queue entry rather than the process. An operation is stopped by its own - deadline, the backstop, or the agent shutting down. Evidence: - `internal/job/client/client.go`, `internal/exec/exec.go`. - -#### Reporting - -- **FR-019**: The corpus MUST state that a per-host result carries one of four - statuses — `ok`, `failed`, `skipped`, `timeout` — and what distinguishes them: - a failure ran and did not succeed, a skip does not apply to that OS family, - and a timeout says nothing about whether it ran. Evidence: - `pkg/sdk/client/status.go`, and the synthesized timeout row in - `internal/job/client/client.go`. -- **FR-020**: The corpus MUST state that a failure carries a machine-readable - cause beside its message, and that a cause the reader does not recognise is - treated as an ordinary failure rather than guessed at. Evidence: - `internal/job/errors.go`, `job.Response.ErrorCode` in `internal/job/types.go`. -- **FR-021**: The corpus MUST state the KV bucket TTLs as configured values, - naming the file that sets them, and MUST record that the published site stated - a different one. **Gap**: the page said 24 hours for completed and failed - jobs; the shipped configuration sets one TTL for the whole `job-queue` bucket, - `1h`. Evidence: `configs/osapi.yaml`. - -#### Citing rather than restating - -- **FR-022**: Where this subject reaches signing, response verification or agent - identity, it MUST cite [002](../002-agent-key-store/spec.md) rather than - describing them again. - -- **FR-023**: Where it reaches what a provider must return or how a provider - behaves, it MUST cite [001](../001-provider-contract/spec.md). - -- **FR-025**: The corpus MUST state what happens once redelivery is exhausted, - because FR-009 through FR-015 describe at-least-once delivery and stop short - of its terminal case. After `MaxDeliver` attempts JetStream emits a - `MAX_DELIVERIES` advisory, and a dedicated `-DLQ` stream subscribes to - those advisories and retains them — `168h` and 1000 messages by default. The - corpus MUST state that what the dead letter queue holds is the **advisory** - rather than the job: the job's own definition stays in the `job-queue` bucket - until that bucket's TTL expires it (FR-021), so a reader who expects to - retrieve the failed job from the DLQ will not find it there. The depth is - surfaced as `dlq_count` on queue statistics and in - `osapi client health status`. Evidence: the DLQ stream built in - `cmd/nats_setup.go`, its defaults in `configs/osapi.yaml` under `nats.dlq`, - and the count read in `internal/job/client/jobs.go`. - -- **FR-024**: After this specification merges, the `add-a-domain` skill MUST - cite these requirements rather than restating the mechanics, and the - contributor half of the site page MUST be removed in the same change that adds - those citations. This is 003's FR-010 applied to this subject, and it is the - task nothing enforces — see 003's research Finding 3. - -### Key Entities - -- **Job**: An immutable definition stored under `jobs.{job-id}`, plus the - append-only status events that describe what happened to it. -- **Notification**: A subject-routed message carrying a job ID, not its content. -- **Response**: An agent's answer, stored in its own bucket, carrying a status, - a message and a machine-readable cause. -- **Target**: What a job is addressed to — a hostname, a machine ID, a broadcast - or a label selector. -- **Obligation**: Something the system does not guarantee and the agent must - therefore do. At-least-once delivery creates the only one that matters here. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A contributor with no prior knowledge answers all three questions - in [003's quickstart](../003-corpus-backfill/quickstart.md) from this - specification alone. -- **SC-002**: Every requirement naming a configured value or a key format cites - a file, and opening that file confirms the value or finds the gap the - requirement records. -- **SC-003**: The three gaps — the job key shape, the consumer defaults, the - bucket TTL — are stated as corrections rather than silently differing from the - page they came from. -- **SC-004**: After the site change, one statement of each of these rules exists - across the corpus, the site and the skills, and `just skill-lint` passes with - the citations resolving. -- **SC-005**: No requirement here restates a rule that - [001](../001-provider-contract/spec.md) or - [002](../002-agent-key-store/spec.md) already states. - -## Assumptions - -- The operator half of `job-architecture.md` stays on the site: the job states - as observed, polling, the CLI reference, and the metrics worth watching. 003's - data-model records the split line by line. -- The page is 630 lines as this is written, not the 603 003's spec records. The - 27 added lines are the Error Handling section, and they are the newest and - most precisely sourced content on the page — which is why FR-009 through - FR-018 lean on it. -- No Go code changes. This states what the code already does, and records where - the page said otherwise. -- Three gaps were found by reading the code behind three of the page's - statements. Others may exist in statements not yet checked; FR-002's citation - requirement is what lets the next reader find them rather than trust the - corpus. diff --git a/history/osapi-004-job-system/tasks.md b/history/osapi-004-job-system/tasks.md deleted file mode 100644 index 7d14ce5..0000000 --- a/history/osapi-004-job-system/tasks.md +++ /dev/null @@ -1,225 +0,0 @@ -# Tasks: The job system - -**Feature**: `004-job-system` | **Date**: 2026-09-28 | **Spec**: -[spec.md](spec.md) | **Plan**: [plan.md](plan.md) - -**Input**: [research.md](research.md) for the three decisions; -[data-model.md](data-model.md) for what each requirement replaces and what the -page keeps; [quickstart.md](quickstart.md) for how each criterion is verified. - -## Format: `[ID] [P?] [Story] Description` - -- **[P]** — parallel-safe: different files, no dependency on an incomplete task. -- **[US1]**, **[US2]**, **[US3]** — the user story served. -- Every task names the file it touches. - -## Path Conventions - -- `specs/` — this repository: the corpus under - `components/osapi/specs/004-job-system/`, the skill under - `.claude/skills/add-a-domain/`. -- `osapi/` — the site under `docs/docs/sidebar/architecture/`. - -The corpus half is merged. Everything below is one osapi pull request plus one -change here, and the osapi one is what ends the duplication. - -______________________________________________________________________ - -## Phase 1: Setup - -- [x] T001 Confirm the line ranges in [data-model.md](data-model.md) against - `osapi/docs/docs/sidebar/architecture/job-architecture.md` as it stands. 003's - T001 confirmed them at 630 lines; anything merged into that page since moves - them again, and the split is done by section boundary rather than by line - number for that reason. - -## Phase 2: Foundational (Blocking Prerequisites) - -- [x] T002 [US3] Re-run the SC-002 citation checks from - [quickstart.md](quickstart.md). Every cited file must still print. A citation - that stopped resolving between the specification merging and this change is a - requirement describing code that moved, and it is corrected here rather than - carried. - -## Phase 3: User Story 1 — a contributor knows what they are handed (Priority: P1) 🎯 MVP - -**Goal**: the mechanics are stated only in the corpus, and the page no longer -holds a second copy. - -**Independent test**: SC-004's greps return nothing from the page, and SC-001's -three questions are answerable from the specification alone. - -- [x] T003 [US1] Remove from - `osapi/docs/docs/sidebar/architecture/job-architecture.md` the sections - [data-model.md](data-model.md) marks as replaced: Overview, Architecture - Principles, Job Flow, NATS Configuration, Subject Hierarchy and Semantic - Routing Rules, the routing *rules* from Target Types, Agent Implementation, - Facts Collection, Package Architecture, Performance Optimizations, Security - Considerations, and Error Handling. -- [x] T004 [US1] Delete the three wrong statements with the sections that hold - them — the `{status}.{uuid}` key format, `MaxDeliver: 3` and `AckWait: 30s`, - and the 24-hour TTL. Research Decision 1: none of the three is corrected in - place, because correcting a number in two places is how it drifted the first - time. -- [x] T005 [US1] Run the SC-004 grep. No output. A match is a second statement, - and for the first three a second statement that is wrong. - -## Phase 4: User Story 2 — the page still serves an operator (Priority: P1) - -**Goal**: what remains is a page rather than a remainder, and its address is -unchanged. - -**Independent test**: 003's SC-002 and SC-003 checks, plus the site build. - -- [x] T006 [US2] Rewrite the page's opening so it introduces what the page now - is — running and watching jobs — rather than opening on an overview of a - system it no longer describes. FR-003 of 003: the operator's half must read as - a coherent page, not as what was left. -- [x] T007 [US2] Keep, and check for orphaned cross-references: the submission - CLI examples, the job states as observed, polling, the target syntax an - operator types, the CLI command reference, and the metrics worth watching. - [data-model.md](data-model.md) lists them with their line ranges. -- [x] T008 [US2] [P] Update any page that linked into a removed section. - `architecture.md`'s Deep Dives and `system-architecture.md`'s Further Reading - both point here; a link to a heading that no longer exists is what the site - build catches. -- [x] T009 [US2] Confirm the page states nothing that sends an operator to the - corpus — 003's FR-012. A contributor arriving here is served by the skill, not - by a pointer on an operator page. -- [x] T010 [US2] Run - `cd osapi && just docusaurus-fmt-check && just docusaurus-build`. -- [x] T011 [US2] Open and merge the osapi pull request for T003–T010. **This is - the task that ends the duplication.** Until it lands, the corpus and the page - both state these rules and three of the page's numbers are wrong — see - [research.md](research.md), "The risk this feature carries". - -## Phase 5: User Story 3 — the rule has one home (Priority: P2) - -**Goal**: the skill cites the requirements instead of restating the mechanics. - -**Independent test**: SC-004's `just test`, and SC-005's grep. - -- [x] T012 [US3] Replace the delivery-semantics section of - `specs/.claude/skills/add-a-domain/references/agent.md` with a citation table - naming FR-009, FR-010, FR-011, FR-012, FR-013, FR-014 and FR-015 — written out - rather than as a range, so a coverage check can see each one — in the shape - [003's citation contract](../003-corpus-backfill/contracts/citation.md) - defines. Leave the processor, registration and platform-selection material - alone — research Decision 2. -- [x] T013 [US3] Run `cd specs && just test`. `skill-lint` resolving the new - citations is the gate. -- [x] T014 [US3] Run the SC-005 grep and read the Security Considerations - requirements: they must cite [002](../002-agent-key-store/spec.md) rather than - explain signing again. - -## Phase 6: Polish & Cross-Cutting Concerns - -- [x] T015 [P] Run the SC-001 reading with somebody who has not read the site - page, using the three fixed questions. An author cannot test their own corpus - for completeness. - - **Result: SC-001 met.** All three questions came back ANSWERABLE from - [spec.md](spec.md) alone. The reader was a fresh agent with no access to this - session, the code, the site, the other specs, or its own knowledge of - JetStream — given the one file and the three questions, nothing else. That is - not a person, and it does not prove a person would succeed; it does prove the - answers are in the text rather than in the author's head, which is what this - task exists to establish. - - Questions and where the reader found each answer: - - | Question | Cited | - | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | - | What carries a job from the API to an agent, and what guarantees its delivery? | FR-001, FR-002, FR-009, FR-014, FR-015 | - | What must an agent not do when the same job arrives twice, and what does it do instead? | FR-009, FR-010, FR-011, FR-012, FR-013 | - | What bounds how long an operation runs, and what happens when the controller stops waiting before the agent stops working? | FR-016, FR-017, FR-018 | - - The reading also returned five gaps. Two of them are real and land against - this specification's own acceptance bar: - - - **FR-016 and FR-017 name the two clocks but never give their durations.** - `controller.api.job_timeout` and `DefaultCommandTimeout` appear as - identifiers with citations, where FR-014 states `MaxDeliver: 5` and - `AckWait: 2m` and FR-021 states `1h`. US1's acceptance scenario says "the - deadline, the backstop ... are all stated", and a name is not a duration. - - **Nothing states what happens once `MaxDeliver` is exhausted.** FR-009 - through FR-015 describe at-least-once and the agent's obligations, then stop - short of the terminal case. - - One is a scope question rather than a gap: no TTL is stated for the - `job-responses` bucket, only for `job-queue` (FR-021). The remaining two — - broadcast ordering and concurrency, and payload versioning — are subjects this - specification never claimed. - - **These are not fixed here.** A merged specification is amended in its own - pull request, ahead of anything that depends on it; folding a correction into - an archive diff is the failure the workflow exists to prevent. The two real - gaps go to that amendment. - -- [x] T016 [P] Run the "check that matters most" from - [quickstart.md](quickstart.md): the page's last commit must postdate the - specification's merge. If it does not, this feature is unfinished whatever the - corpus says. - -- [x] T017 Mark 003's T010 and T011 done, and update 003's `tasks.md` to record - that Subject A completed — including which of its phases this feature carried - out, so the next subject reads a task list that matches what happened. - -- [x] T018 Run `/speckit-archive-run specs/004-job-system` once T011 and T013 - have merged, consolidating this subject into - `specs/components/osapi/.specify/memory/`. Archive after the implementation, - never before: what merged here is the statement, and the outcome is only true - once the page no longer disagrees with it. - -## What the consistency check found - -`speckit-analyze` over the specification, this plan and this list, before the -first task ran: - -| Finding | Severity | What was done | -| ------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| FR-010 and FR-011 had no task naming them | MEDIUM | They sat inside "FR-009 through FR-015" in T012. The range is written out now: a requirement a coverage check cannot see is one that can be dropped silently. | -| FR-024 had no task naming it | MEDIUM | T011 names it. FR-024 is the obligation that the site change lands, so the task that merges it is the one that discharges it. | -| No ambiguity, duplication or placeholder findings | — | No vague adjectives, no TODO markers, no two requirements stating the same rule. | -| No constitution conflicts | — | Documentation, Verification and Correction are each satisfied by a named mechanism; the plan's Constitution Check records how. | - -21 of 24 requirements were covered before those fixes; all 24 are now. - -## Dependencies & Execution Order - -### Phase Dependencies - -- **Phase 1** (T001) first: the ranges decide what T003 removes. -- **Phase 2** (T002) blocks Phase 3. A citation that stopped resolving is - corrected before anything is deleted, because the page is the only other copy. -- **Phase 3** (T003–T005) and **Phase 4** (T006–T010) are one pull request and - are separated here by intent rather than by sequence: removal and rewriting - touch the same file, so they happen together and T011 merges both. -- **Phase 5** (T012–T014) depends on the specification being merged, which it - is. It may land before or after T011; the citations point at the corpus either - way. -- **Phase 6** (T015–T018) depends on T011 and T013, except T016, which is the - check that T011 happened at all. - -### The dependency nothing enforces - -T011. The corpus states these rules as of the specification's merge, so the -window 003's research Finding 3 describes is open now — and it is the bad -version of that window, because three of the page's numbers are not merely -duplicated but wrong. If this work pauses, T011 is what an issue in osapi must -name. - -### Parallel Opportunities - -- T008 alongside T003 through T007: different files, same build checking them. -- T015 and T016, once the pull request has landed. - -## Implementation Strategy - -One osapi pull request: remove, rewrite, check the links, build. Then the skill -citation here, which can go either side of it. - -There is no smaller first step worth taking. Half a page is a page that -contradicts itself, and the three corrections are the reason not to leave it -half done: a reader who finds the page today gets `MaxDeliver: 3` for a consumer -that has used 5 for some time. diff --git a/history/osapi-005-building-a-domain/checklists/requirements.md b/history/osapi-005-building-a-domain/checklists/requirements.md deleted file mode 100644 index 680cf4a..0000000 --- a/history/osapi-005-building-a-domain/checklists/requirements.md +++ /dev/null @@ -1,79 +0,0 @@ -# Specification Quality Checklist: Building a domain - -**Purpose**: Validate specification completeness and quality before proceeding -to planning - -**Created**: 2026-09-28 - -**Feature**: [spec.md](../spec.md) - -## Content Quality - -- [ ] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [ ] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [x] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [ ] No implementation details leak into specification - -## Notes - -Three items fail by design, the same three that failed for -[001](../../001-provider-contract/checklists/requirements.md) and -[004](../../004-job-system/checklists/requirements.md). The reason is the same -and it is worth restating rather than cross-referencing, because a reader who -finds three unchecked boxes should not have to open another feature to learn -whether this one is unfinished. - -**No implementation details** and **no implementation details leak** both fail -because this specification's subject *is* the implementation. A requirement -saying "the corpus MUST state that the `JobClient` interface has four generic -methods" cannot avoid naming the interface: the thing being specified is a -statement about code, and a statement about code that names no code cannot be -checked. The constitution's Verification principle requires the evidence, and -every requirement here carries a file and usually a line number so a reader -re-measures rather than trusting the prose. Removing the citations to pass this -box would remove the only thing that makes the requirements falsifiable. - -**Written for non-technical stakeholders** fails because the audience is a -contributor adding a domain to osapi, or an agent doing the same. There is no -non-technical reader of a document whose purpose is to tell somebody which file -to create third. The operator-facing half of this material stays on the site, -which is where the non-technical reader is served — that is FR-026, and the -split between the two audiences is the feature. - -These three are left unchecked rather than removed. A checklist that passes -because its failing items were deleted records nothing. - -## What the verification actually found - -Four gaps, recorded in the requirements rather than corrected: - -| Gap | Requirement | Both sides | -| ----------------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------- | -| `node.validateHostname()` does not exist as a shared helper | FR-012 | The page names that call; the function is unexported and duplicated in three packages. `validation.Var` is what is shared. | -| The `sdk-standards` capability is not in this repository | FR-019 | The page and `references/sdk.md:6` both defer to it as binding; nothing has written it. | -| 003 records five API guidelines | FR-014 | The page states six. The sixth is path-versus-query parameters. | -| 003 records five design principles | FR-022 | The page states eight. The three unnamed are Reliability and Stability, CLI Parity with API, Least Privilege Mode. | - -The first two are osapi's or this repository's to fix and each is its own -change. The last two are 003's own record being wrong about a page it measured -correctly — 46 lines, eight principles — which is the same shape as 004's -Finding 2 and the reason 003's FR-009 requires checking rather than -transcribing. diff --git a/history/osapi-005-building-a-domain/contracts/walkthrough.md b/history/osapi-005-building-a-domain/contracts/walkthrough.md deleted file mode 100644 index e25e716..0000000 --- a/history/osapi-005-building-a-domain/contracts/walkthrough.md +++ /dev/null @@ -1,59 +0,0 @@ -# Contract: what a walkthrough is, and how it differs from a requirement - -**Feature**: `005-building-a-domain` | **Date**: 2026-09-28 - -003's [citation contract](../../003-corpus-backfill/contracts/citation.md) -records the shape a citation must have, because FR-007 and FR-008 are only -enforceable if a checker can resolve one. This contract records the same kind of -thing for a distinction FR-004 and FR-005 rest on: a sequence stated as a -requirement binds, and a sequence stated as a walkthrough advises. Without a -test for which is which, the two collapse and every step becomes a rule. - -## The test - -Ask what a reader would call the corpus if a step moved tomorrow. - -| If the step moved, the corpus is… | Then it is a… | Where it goes | -| ------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------ | -| **Wrong** — following it produces a build that fails | Requirement | `spec.md`, folded into `.specify/memory/spec.md` | -| **Dated** — following it produces working code by a route nobody takes any more | Walkthrough | `data-model.md`, folded into `.specify/memory/plan.md` | - -The question is deliberately about the *reader's* verdict rather than the -author's intent. An author knows why they put the steps in that order and will -defend all eight; a reader only discovers the difference by disobeying one. - -## Applied - -Three orderings are requirements, because each has a tool that fails: - -| Ordering | What fails if it is violated | -| ------------------------------------------------------------ | ------------------------------------------------------------------------- | -| `gen/api.yaml` before `just generate` | Generation has nothing to read | -| Generation before the handler | The handler implements an interface that does not exist | -| `redocly join` before `go generate ./pkg/sdk/client/gen/...` | The SDK generates from a combined specification that lacks the new domain | - -Everything else in the eight steps is a walkthrough. Writing the CLI before the -SDK service is unusual and works. Writing the documentation first is unusual and -works. Neither is wrong, so neither is a requirement. - -## Why this matters more than it looks - -A sequence is the part of a contributor document a reader most needs and the -part most likely to rot, and the two facts pull against each other. Stating all -eight steps as requirements would feel more rigorous and would be worse: every -tooling change would demand a specification amendment in its own pull request, -and the amendments would be about step numbering rather than about rules. That -is the cost FR-005 avoids, and it is a cost this project has already paid once — -`job-architecture.md` carried three numbers that the code had outgrown, and they -were stated with the same confidence as the rules around them. - -## What checks it - -Nothing automatic, and this contract says so rather than implying a gate exists. -What checks it is review: a requirement in this specification that states a step -ordering must name the tool that enforces it, and a reviewer can ask for that -name. A requirement that cannot name one belongs in the walkthrough. - -The SC-001 reading is the indirect check. A reader asked *which of those -orderings is forced?* — the second of the four questions — can only answer it if -the corpus has kept the two kinds apart. diff --git a/history/osapi-005-building-a-domain/data-model.md b/history/osapi-005-building-a-domain/data-model.md deleted file mode 100644 index 863f80a..0000000 --- a/history/osapi-005-building-a-domain/data-model.md +++ /dev/null @@ -1,177 +0,0 @@ -# Data Model: Building a domain - -**Feature**: `005-building-a-domain` | **Date**: 2026-09-28 - -Phase 1. A documentation feature's entities are its pages and the statements -inside them. This file fixes every boundary the implementation would otherwise -decide in a diff: which lines leave, what replaces them, and the walkthrough -FR-005 requires. - -## Every page, by line range - -### `docs/docs/sidebar/development/adding-an-api-domain.md` — 654 lines - -Moves **wholly**. Every section is contributor knowledge; the page has no -operator content. What survives at the address is a new short page, specified -below. - -| Section | Lines | Corpus requirement stating it | -| ---------------------------------------- | ------- | ---------------------------------------------------- | -| Cross-Layer Consistency | 12–24 | FR-001 | -| Step 0: Provider Implementation, opening | 25–37 | FR-008 | -| Provider Types | 38–67 | Cites 001 — FR-002 | -| File Structure | 68–98 | FR-006, citing 001 | -| Provider Interface | 99–115 | Cites 001 — FR-002 | -| Idempotency | 116–152 | Cites 001 — FR-002 | -| Platform-Specific Implementations | 153–174 | Cites 001 — FR-002 | -| Provider Naming Conventions | 175–244 | Cites 001 — FR-002 | -| FactsAware | 245–261 | FR-010 | -| Agent Wiring | 262–302 | FR-009 | -| Provider Testing | 303–308 | FR-002; testing conventions are `CONTRIBUTING.md`'s | -| Step 1: OpenAPI + Code Generation | 309–319 | FR-004, FR-011 | -| HTTP Verb Conventions | 320–333 | FR-013 | -| Validation in OpenAPI Specs | 334–418 | FR-011, FR-012 | -| Step 2: Handler Implementation | 419–438 | FR-006, FR-011 | -| Broadcast Support | 439–486 | FR-016, FR-017 | -| Step 3: Handler Registration | 487–529 | FR-018 | -| Step 4: Startup Wiring | 530–538 | FR-018 | -| Step 5: Update SDK | 539–583 | FR-019, FR-020 | -| SDK method naming | 584–590 | FR-019 — **the deferral this names does not exist** | -| SDK example conventions | 591–606 | FR-020 | -| Step 6: CLI Commands | 607–622 | FR-021 | -| Step 7: Documentation | 623–646 | FR-001 — the consistency obligation in concrete form | -| Step 8: Verify | 647–654 | FR-024 — **incomplete as written** | - -### `docs/docs/sidebar/architecture/api-guidelines.md` — 61 lines - -**Deleted.** All six guidelines move to FR-014 and FR-015. The address becomes a -client-side redirect to the contributor page. - -Nothing stays, because nothing here is written for an operator: an operator -calls an endpoint, and the endpoint list lives in the published OpenAPI -reference. What this page holds is how to *choose* a path shape, which is a -decision only somebody adding one makes. - -### `docs/docs/sidebar/architecture/principles.md` — 46 lines - -**Deleted.** All eight principles move to FR-022, each checked against the -charter per FR-023. The address becomes a client-side redirect to the -contributor page. - -003 recorded five. The three it never named — Reliability and Stability, CLI -Parity with API, Least Privilege Mode — are the ones most likely to have been -lost by a transcription, and two of them constrain things no other rule covers. - -### `docs/docs/sidebar/architecture/system-architecture.md` — 330 lines → 141 - -| Lines | Section | Disposition | -| ------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| 1–11 | Frontmatter, title, introduction | **Stays**, with one sentence of editing so it leads into Health Checks rather than into a removed Component Map | -| 12–40 | Component Map | Moves — FR-007 | -| 41–53 | Entry Points | Moves — FR-007 | -| 54–174 | Layers: CLI, REST API, Job System, Provider Layer, Agent Lifecycle, Configuration | Moves — FR-007. The Job System subsection cites 004 rather than restating it | -| 175–240 | Health Checks: liveness, readiness, status, CLI access | **Stays** — an operator's page | -| 241–266 | Request Flow | Moves — FR-008 | -| 267–309 | Security: authentication, authorization, CORS; External Dependencies | **Stays** | -| 310–end | Further Reading and link definitions | **Stays, edited** — see below | - -**The Further Reading list is a build failure waiting to happen.** It links -`api-guidelines.md` and `principles.md`, both of which this feature deletes. -`just docusaurus-build` fails on a link to a missing page, so the list must lose -those two entries in the same change that deletes the pages. It keeps the -`job-architecture.md` and `CONTRIBUTING.md` entries and gains one to the -contributor page. - -## The contributor page that survives - -`adding-an-api-domain.md`, rewritten. Roughly 60 lines, three parts, no fourth. - -**Part 1 — what adding a domain involves.** Prose, under ten lines. Names the -layers a domain touches and says that a domain is complete when it appears -everywhere an existing domain appears. States that the rules live in the corpus -and the sequence is in the skill. Does **not** restate a rule, list the eight -steps, or explain a mechanism. - -**Part 2 — the citation table.** One row per requirement a contributor needs, -each naming the rule and linking to it. The rule's *name*, never its content — -[contracts/citation.md](../003-corpus-backfill/contracts/citation.md) fixes the -form. The link is to this repository on GitHub rather than a relative path, -because the corpus is not part of the published site; that is the one place the -citation contract's "relative, not absolute" property cannot apply, and the page -says so in a sentence so a reader does not read it as an oversight. - -**Part 3 — the pointer to the skill.** Two or three lines: `add-a-domain` -carries the sequence and the working examples, it is invoked by name, and it -cites these same requirements. Nothing about how to install it. - -What the page must **not** contain: any of the eight steps' contents, any code -block, any table of provider types or naming conventions, or a summary of a -rule. A summary is a second statement, and a second statement is what this -feature exists to end. - -## The walkthrough - -This is what FR-005 requires stated as a walkthrough rather than as eight -requirements, and [research.md](research.md) Decision 2 says why it lives here: -archival folds this file into `.specify/memory/plan.md`, where the approach -belongs, while FR-004's forced orderings stay in the requirements. - -**Forced by tooling** — these three are requirements, not walkthrough, because a -build breaks if they are violated: - -1. The domain's `gen/api.yaml` exists before `just generate` runs, because - generation reads it. -2. Generation runs before the handler is written, because the handler implements - the generated `StrictServerInterface`. -3. The combined specification is regenerated — `redocly join`, inside - `just generate` — before `go generate ./pkg/sdk/client/gen/...`, because the - SDK client generates from the combined file and not from the domain's own. - -**Conventional** — the order below is how it is done, and a reader who departs -from it produces working code: - -| Step | What it produces | Why here | -| ---- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -| 0 | The provider, under `internal/provider/…` | The operation has to exist before anything can call it. Testable alone, which makes it the cheapest place to be wrong. | -| 1 | `gen/api.yaml`, `cfg.yaml`, `generate.go`, then `just generate` | Forced before step 2. | -| 2 | The handler, one file per endpoint, with its tests | Implements what step 1 generated. | -| 3 | `handler.go` exporting `Handler()` | Separate from step 2 so the middleware wiring is reviewable on its own. | -| 4 | One appended line in `registerControllerHandlers` | Smallest step; the domain becomes reachable here. | -| 5 | The SDK service, four files, plus the example and doc page | Forced after the combined specification regenerates. | -| 6 | The CLI commands | Consumes the SDK, so after it. | -| 7 | The documentation, eight files | Conventionally last because it describes what the previous steps produced. | -| 8 | Verification | Last by definition. | - -**The trap in step 7 and 8.** Step 7 edits documentation and step 8's commands -do not check it: `docusaurus-fmt-check` and `docusaurus-build` run only in -`just test`. A contributor who follows the sequence exactly can hand in work -that fails continuous integration on the files the previous step told them to -write. FR-024 records this, and the corpus states the gate as `just ready` and -`just test` rather than reproducing step 8's list. - -## What the skill holds afterwards - -The `add-a-domain` skill is 849 lines across `SKILL.md` and six references. -Afterwards each reference states rule *names* with citations, and the mechanics -live here. - -| Reference | Lines today | What changes | -| ------------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------- | -| `references/provider.md` | 127 | Unchanged. It already cites 001 — this is the pattern the backfill copies. | -| `references/agent.md` | 124 | Already cites 004 from Subject A. Gains citations for FR-009 and FR-010. | -| `references/api.md` | 193 | The largest change: validation, verbs, broadcast and registration become rows citing FR-011 through FR-018. | -| `references/sdk.md` | 122 | The `sdk-standards` deferral at line 6 is **named as unwritten** rather than repeated. Verifiable conventions cite FR-019 and FR-020. | -| `references/cli.md` | 83 | Becomes rows citing FR-021. | -| `references/docs.md` | 60 | Becomes rows citing FR-001's concrete form — step 7's eight files. | - -## The relationship to what memory already holds - -Memory holds three archived features. Every overlap is a citation. - -| Content reached here | Held by | Treatment | -| ------------------------------------------------------------------------------------- | ------------------------- | --------------------------------------------------------------------------- | -| Provider types, file structure, the interface, naming, platform variants, idempotency | 001, FR-001–FR-015 | Cited — FR-002 | -| Delivery semantics, the two clocks, the dead letter queue | 004, memory FR-040–FR-054 | Cited — FR-003 | -| Job signing, response verification, agent identity | 002 | Cited — FR-003 | -| Testing conventions | osapi's `CONTRIBUTING.md` | Cited, not copied. A rule a repository already states is not restated here. | -| Everything else | Nothing yet | Stated for the first time, with the file cited per 003's FR-011 | diff --git a/history/osapi-005-building-a-domain/plan.md b/history/osapi-005-building-a-domain/plan.md deleted file mode 100644 index c770585..0000000 --- a/history/osapi-005-building-a-domain/plan.md +++ /dev/null @@ -1,164 +0,0 @@ -# Implementation Plan: Building a domain - -**Branch**: `005-building-a-domain` | **Date**: 2026-09-28 | **Spec**: -[spec.md](spec.md) - -**Input**: Feature specification from -`components/osapi/specs/005-building-a-domain/spec.md` - -## Summary - -The specification states 26 requirements about what adding an API domain -consists of. This plan says what turning them into a landed change is, and it is -larger than Subject A: four pages are affected instead of one, two of them are -deleted outright, and 654 of the lines leaving the site come from a single page -that is the most-read contributor document osapi has. - -The shape is the same as [004](../004-job-system/plan.md) — the corpus statement -merges first, the site reduction second, archival last — and the reason to keep -that order is stated below rather than assumed, because reversing it is the one -sequencing mistake that leaves the duplication live. - -What is different from Subject A is the amount of *subtraction with no -replacement*. `job-architecture.md` kept an operator half. `api-guidelines.md` -and `principles.md` have no operator half at all: every line of both is -contributor knowledge, so both addresses become redirects and the pages cease to -exist. That is the first time this backfill deletes a page rather than splitting -one, and it is why the redirects matter more here than anywhere else in 003. - -No Go code changes. Five gaps are recorded and none is scheduled — see **Out of -scope**. - -## Technical Context - -**Language/Version**: None. Markdown in two repositories. - -**Primary Dependencies**: `@docusaurus/plugin-client-redirects`, added **by this -feature**. It is [003's T015](../003-corpus-backfill/tasks.md), which 005 -carries out along with the rest of 003's Phase 4 — so it is this feature's own -work rather than a dependency to wait on. 005 is the only subject in the -backfill that deletes a page, and therefore the only one that needs a redirect. - -**Storage**: N/A. - -**Testing**: `just test` here — mdformat, just-fmt and -`scripts/validate-skills.py`, which resolves every citation and fails by name on -a wrong relative depth. `just docusaurus-fmt-check` and `just docusaurus-build` -in osapi; the build fails on a link left pointing at a deleted page, which is -the gate that catches the Further Reading list described in -[data-model.md](data-model.md). - -**Target Platform**: The corpus and the published site. - -**Project Type**: Documentation. - -**Performance Goals**: N/A. - -**Constraints**: `adding-an-api-domain.md` and `system-architecture.md` keep -their addresses and must read as whole pages afterwards rather than as -remainders. `api-guidelines.md` and `principles.md` lose their content entirely -and keep their addresses only as redirects. No corpus requirement may restate -what [001](../001-provider-contract/spec.md), -[002](../002-agent-key-store/spec.md) or [004](../004-job-system/spec.md) states -— FR-002 and FR-003. - -**Scale/Scope**: Four pages. 654 lines removed and replaced by a short page, 61 -and 46 lines deleted, and 189 lines removed from a 330-line page. One skill -reference rewritten. Two pull requests, one per repository. - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -| Principle | How this feature satisfies it | -| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Documentation** | The corpus states the rules; the skill cites them; the site keeps only what an operator uses. Two pages had no operator content at all, which is the clearest case this principle has produced: a page nobody but a contributor reads, sitting in the operator's navigation. | -| **Verification** | Every requirement cites the file it describes, and four of them cite a line number. The four gaps were found by reading the code behind the page rather than the page — `validateHostname` is the example: the page names a call that does not compile. `just skill-lint` fails when a citation stops resolving. | -| **Tooling** | Nothing new is provisioned. The redirects plugin arrives through 003's own task list. | -| **Correction** | Four gaps are recorded with both sides named and none is silently fixed. Two of them are 003's own record being wrong about pages it measured correctly, which this plan states plainly rather than quietly reconciling. | -| **Workflow** | This is stage 3 for `005`; tasks follow in the same branch, the implementation is its own pull request per repository, and archival comes after the implementation has merged. | - -**Result**: no violations. - -## Project Structure - -### Documentation (this feature) - -```text -components/osapi/specs/005-building-a-domain/ -├── plan.md # This file -├── research.md # Phase 0: what the sequencing and layout decisions are, and why -├── data-model.md # Phase 1: every page, by line range, and what replaces it -├── contracts/ -│ └── walkthrough.md # Phase 1: what a walkthrough is, and how it differs from a requirement -├── quickstart.md # Phase 1: how to verify the move -├── checklists/ -│ └── requirements.md # From stage 1 -└── tasks.md # Phase 2 output -``` - -### Content - -```text -specs/ # this repository -├── components/osapi/specs/005-building-a-domain/ -│ ├── spec.md # merged; the statement of record -│ └── data-model.md # holds the walkthrough FR-005 requires -└── .claude/skills/add-a-domain/ - ├── references/provider.md # already cites 001; unchanged - ├── references/sdk.md # the sdk-standards deferral is named, not repeated - └── references/*.md # domain-building mechanics become citation rows - -osapi/ -└── docs/docs/sidebar/ - ├── development/adding-an-api-domain.md # 654 lines → a short contributor page - └── architecture/ - ├── api-guidelines.md # deleted; address redirects - ├── principles.md # deleted; address redirects - └── system-architecture.md # contributor half removed, operator half stays -``` - -**Structure Decision**: two pull requests, and the corpus one goes first. See -[research.md](research.md) Decision 1 for why the reverse order is the failure -mode rather than merely slower. - -## Documentation Surface - -Subject A's plan introduced this section because a documentation feature's -surface *is* its deliverable, and leaving it implied is how a page gets split by -judgement at implementation time. Here it matters more, because two pages are -deleted and the split of a third is a line-range decision that a reviewer should -be able to check rather than trust. - -| Page | Today | After | Reader afterwards | -| ------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -| `development/adding-an-api-domain.md` | 654 lines, the whole subject | A short page: what adding a domain involves, a citation table, a pointer to the skill | A contributor, who is the one reader for whom a citation is the right form | -| `architecture/api-guidelines.md` | 61 lines, six guidelines | Deleted. Address redirects to the contributor page | Nobody; it had no operator content | -| `architecture/principles.md` | 46 lines, eight principles | Deleted. Address redirects to the contributor page | Nobody; same | -| `architecture/system-architecture.md` | 330 lines | 141 lines: health checks, security, external dependencies | An operator configuring and calling osapi | - -The exact line ranges are in [data-model.md](data-model.md). The contributor -page's contents are specified there too, concretely enough that implementation -has nothing left to invent — which is the point of writing it here rather than -discovering it in a diff. - -## Out of scope - -Five gaps are recorded in the specification. None is scheduled by this plan, and -each has an owner: - -| Gap | Owner | Why not here | -| ------------------------------------------------------ | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `validateHostname` unexported and triplicated (FR-012) | osapi | A Go change. This feature touches no code. | -| The absent `sdk-standards` capability (FR-019) | this repository | Writing it is a feature of its own, and inventing it inside a backfill is how a rule gets written to fill a template. | -| Step 8's commands do not cover Step 7's files (FR-024) | osapi | A justfile or page change in osapi. | -| 003 records five API guidelines; six exist (FR-014) | 003 | Its record, not this feature's. Corrected when 003 is archived. | -| 003 records five principles; eight exist (FR-022) | 003 | Same. | - -Also out of scope: the 30 feature pages, any Go code change, the Docusaurus -theme beyond the two redirects, and Subject A's job system material, which is -archived. - -## Complexity Tracking - -> No Constitution Check violations, so this table is empty. diff --git a/history/osapi-005-building-a-domain/quickstart.md b/history/osapi-005-building-a-domain/quickstart.md deleted file mode 100644 index d40f3b4..0000000 --- a/history/osapi-005-building-a-domain/quickstart.md +++ /dev/null @@ -1,120 +0,0 @@ -# Quickstart: verifying the move - -**Feature**: `005-building-a-domain` | **Date**: 2026-09-28 - -Phase 1. Every check below is a command or a fixed procedure, not an instruction -to look. A check somebody has to interpret is one that passes when they are -tired. - -## Prerequisites - -Both repositories cloned as siblings, and `mise` used to invoke tools: - -```bash -cd ~/git/osapi-io && ls -d specs osapi -``` - -## SC-004 — the corpus statement holds together - -```bash -cd specs && mise exec -- just test -``` - -Runs mdformat, just-fmt and `scripts/validate-skills.py`. The last is the one -that matters: it resolves every relative link in the skill's references. A -citation at the wrong depth fails **by name** — three `../` levels lands in -`.claude/` and the message says which file and which path, which is how the -four-level rule came to be recorded. - -## SC-002 — every removed address still resolves - -Two pages are deleted. After the osapi change: - -```bash -cd osapi && mise exec -- just docusaurus-build -grep -A3 "createRedirects\|redirects:" docs/docusaurus.config.ts -``` - -The build fails on a link to a missing page, which covers the internal links. -The grep confirms both addresses are listed as redirects rather than only one: - -| Address | Redirects to | -| -------------------------------------- | -------------------- | -| `/sidebar/architecture/api-guidelines` | the contributor page | -| `/sidebar/architecture/principles` | the contributor page | - -Then confirm no link to either survives anywhere: - -```bash -cd osapi && grep -rn "api-guidelines\|principles" docs/docs docs/docusaurus.config.ts | grep -v redirect -``` - -Expected: nothing outside the redirect configuration. `system-architecture.md`'s -Further Reading list is the known offender — it links both pages today. - -## SC-003 — no operator page points into the corpus - -```bash -cd osapi && grep -rn "osapi-io/specs" docs/docs | grep -v "development/adding-an-api-domain.md" -``` - -Expected: nothing. The contributor page is the single exception and its reader -is a contributor, which is why it is excluded by name rather than by judgement. - -## SC-005 — the rule is stated once - -```bash -cd specs && grep -rn "MaxDeliver\|IsBroadcastTarget\|x-oapi-codegen-extra-tags\|StrictServerInterface" .claude/skills/add-a-domain/ -``` - -Expected: each appears inside a citation row naming a requirement, never inside -a paragraph explaining the mechanism. A reference that explains how validation -tags work is a second statement, whatever it links to. - -## SC-006 — the check that matters most - -```bash -cd osapi && git log -1 --format=%cI -- docs/docs/sidebar/development/adding-an-api-domain.md -cd ../specs && gh pr view --json mergedAt -q .mergedAt -``` - -The page's last commit must postdate the specification's merge. If it does not, -the corpus and the site both state these rules and this feature is unfinished -whatever the corpus says. This is the check Subject A ran as its T016, and it is -the one that distinguishes a finished backfill from a corpus with a duplicate. - -## SC-001 — the reading - -Not a command. The procedure, from [research.md](research.md) Decision 5: - -Give a reader **only** `components/osapi/specs/005-building-a-domain/spec.md` — -no repository, no site, no other specification, no session context — and an -explicit instruction not to answer from general knowledge of Go, REST or -OpenAPI. Ask the four questions, and ask for ANSWERABLE or NOT ANSWERABLE on -each with the requirements used: - -1. What does a domain consist of, and how would I know one was incomplete? -2. What must be built before what, and which of those orderings is forced? -3. Where does user input get validated, and what happens to a path parameter? -4. What must be true of an operation that targets more than one machine? - -A person is preferred. A fresh agent is the fallback that makes this runnable -rather than aspirational. - -**What a pass proves, and what it does not.** It proves the answers are in the -text rather than in the author's head. It does not prove a person would succeed: -an agent reads more literally, does not skim, and does not stop at a heading and -guess. Necessary, not sufficient — and Subject A's reading is the evidence it is -worth running anyway, since it found two real gaps the author had read past -twice. - -## Definition of done - -| # | Check | Passes when | -| ------ | ------------------------------ | ------------------------------------------------ | -| SC-001 | The reading | Four questions ANSWERABLE from `spec.md` alone | -| SC-002 | Addresses resolve | Both redirects present; build green | -| SC-003 | No operator sent to the corpus | grep returns nothing but the contributor page | -| SC-004 | `just test` in specs | Green, `skill-lint` resolving every citation | -| SC-005 | One statement per rule | No mechanism explained in the skill's references | -| SC-006 | The page postdates the spec | `git log` timestamp is later than the merge | diff --git a/history/osapi-005-building-a-domain/research.md b/history/osapi-005-building-a-domain/research.md deleted file mode 100644 index 6f78b23..0000000 --- a/history/osapi-005-building-a-domain/research.md +++ /dev/null @@ -1,170 +0,0 @@ -# Research: Building a domain - -**Feature**: `005-building-a-domain` | **Date**: 2026-09-28 - -Phase 0. The specification left five things to planning. Each is decided here -with its reason, so that the task list can be read as work rather than as a -series of judgement calls. - -## Decision 1: the corpus statement merges before the site reduction - -**Decision**: two pull requests. The corpus half — this feature's planning -artifacts and the skill's citation rows — merges in `specs/` first. The site -half — the contributor page, the two deletions, the `system-architecture.md` -split — merges in `osapi/` second. Archival is third and comes after both. - -**Rationale**: the two orders fail differently, and only one of the failures is -recoverable by waiting. - -- **Corpus first** leaves a window where both the corpus and the site state the - same rules. That window is visible, bounded by the second pull request, and - during it a reader who consults either one gets a correct answer. This is the - window [003's research Finding 3](../003-corpus-backfill/research.md) - describes, and Subject A lived in it for a day. -- **Site first** leaves a window where neither states them. The page is gone and - the skill's citations point at a corpus that has not merged, so `skill-lint` - fails in `specs/` and a contributor arriving at the old address finds a - redirect to a page whose citation table resolves to nothing. The authoritative - statement does not exist anywhere. Nothing recovers that except merging the - thing that should have gone first. - -So the ordering is not a preference about tidiness. One order is a duplicate -answer and the other is no answer. - -**Alternatives considered**: one pull request across both repositories is not -available — they are separate repositories, which is the constraint that makes -the sequence a decision at all. Landing the skill citations in the *second* pull -request instead of the first was considered and rejected: it would put a -`specs/` change in an `osapi/` pull request, and `skill-lint` runs in `specs/`. - -## Decision 2: the walkthrough lives in `data-model.md`, not in `spec.md` - -**Decision**: FR-005 requires the eight steps to be stated as a walkthrough -rather than as eight requirements. That walkthrough is written into -[data-model.md](data-model.md), which `speckit-archive-run` folds into -`.specify/memory/plan.md`. FR-004's three forced orderings stay in `spec.md` and -are folded into `.specify/memory/spec.md`. - -**Rationale**: the split follows what each destination is for, and it survives -the thing that will change. - -Memory's `spec.md` holds what must be true. Memory's `plan.md` holds how the -work is approached. A forced ordering — the OpenAPI specification before -generation, generation before the handler, the combined specification before the -SDK client — must be true, and stays true as long as the tooling does. The rest -of the sequence is how it is conventionally done, and a tool change rewrites it. -Putting the whole sequence in `spec.md` would mean renumbering requirements -every time `just generate` changes, which is the cost FR-005 was written to -avoid. - -The practical test: if a step's position changed tomorrow, would a reader call -the corpus *wrong* or merely *dated*? Forced orderings would be wrong. The rest -would be dated. Only the first belongs in a requirement. - -**Alternatives considered**: a separate `walkthrough.md` artifact in the feature -directory. Rejected because archival consolidates a known set of files, and a -file outside that set is a document nothing folds in — which is how the previous -system accumulated fourteen feature directories nobody read. What went into -`contracts/walkthrough.md` instead is the *definition* of the form, not the -content. - -## Decision 3: what survives on the contributor page - -**Decision**: `adding-an-api-domain.md` keeps its address and is rewritten to -roughly 60 lines with exactly three parts — what adding a domain involves, a -citation table into this specification, and a pointer to the `add-a-domain` -skill. The contents are fixed in [data-model.md](data-model.md). - -**Rationale**: this is the one page in the whole backfill where a citation into -the corpus is correct, because its reader is a contributor. 003's FR-012 forbids -sending an *operator* to the corpus; it does not forbid sending a contributor -there, and pretending otherwise would leave this page unable to say anything. - -The page cannot simply be deleted, for two reasons that pull in the same -direction. It is linked from the site's development section and from -`system-architecture.md`'s Further Reading, and it is the address a contributor -already has. Redirecting it to the corpus is not possible either: the corpus is -in another repository and is not published as a site. - -**Alternatives considered**: keeping a longer summary. Rejected — a summary is a -second statement that drifts, which is the whole failure mode. Three parts and -no prose restating a rule. - -## Decision 4: `system-architecture.md` splits at stated line ranges - -**Decision**: lines 12–174 (Component Map, Entry Points, Layers) and 241–266 -(Request Flow) move to the corpus and are removed from the page. Lines 1–11 -(frontmatter and introduction), 175–240 (Health Checks with their endpoints and -CLI access), 267–309 (Security — authentication, authorization, CORS — and -External Dependencies) and the Further Reading list stay, with the list edited. - -**Rationale**: stating the ranges makes the split reviewable. Subject A's split -was described by section name and the result was correct, but a reviewer had to -reconstruct the boundaries themselves. Line numbers are checkable against the -file as it stands today, and a range that no longer matches is itself a signal -that the page changed under the plan — which is how 004's Finding 2 was caught. - -One consequence is worth naming: removing 241–266 leaves Health Checks (175–240) -directly followed by Security (267). Those read together, so the page does not -need a bridging paragraph. Removing 12–174 does leave the introduction followed -immediately by Health Checks, which is a larger jump, so the introduction takes -one sentence of editing rather than being left to read as a stub. - -**Alternatives considered**: moving Security too, on the grounds that -authorization scopes matter to a contributor adding an endpoint. Rejected: an -operator configures roles and needs this page, and a contributor's need is -served by the corpus statement of what a handler must do. The same content -serving two readers is fine when only one of them is sent here. - -## Decision 5: the SC-001 reading, and what it can prove - -**Decision**: the reading is run by a fresh agent given **only** -`components/osapi/specs/005-building-a-domain/spec.md` and the four questions, -with no session context, no repository access, no site, no other specification -and an explicit instruction not to answer from its own knowledge of Go, REST or -OpenAPI. It returns ANSWERABLE or NOT ANSWERABLE per question, with the -requirements it used. - -**Rationale**: the author cannot test their own corpus for completeness, because -they cannot forget what they know. An agent with no context is the available -approximation, and it is the method Subject A used — its -[T015](../004-job-system/tasks.md) is the worked example, and it found two real -gaps that the author had read past twice. - -**The limitation, stated rather than left implied**: this proves the answers are -*in the text*. It does not prove a person would succeed. An agent reads -differently, is more literal, and does not get bored; a human reader might stop -at a heading and guess. So a pass here is necessary and not sufficient, and the -task list says so where the task is recorded rather than only here. - -The four questions come from SC-001 and each was chosen because an unanswered -version of it breaks a domain: an incomplete domain, a generation step run out -of order, an unvalidated input, a broadcast handler returning the wrong shape. - -**Alternatives considered**: asking a person. Not rejected — preferred, if one -is available, and the task permits it. The agent is the fallback that makes the -check runnable rather than aspirational. - -## What was verified before writing any requirement - -003's FR-009 requires each requirement checked against the repository. The -checks that found something are recorded as Gaps in the specification; the ones -that confirmed the page are listed here so the next reader knows they were run. - -| Claim on the page | Verified | Result | -| -------------------------------------------------- | ------------------------------------------------------------- | ---------------------------------- | -| `JobClient` has four generic methods | `internal/job/client/types.go:146,153,160,167` | Confirmed | -| `WireProviderFacts` is called once, from the agent | `internal/provider/facts.go:64`, `internal/agent/agent.go:90` | Confirmed | -| The registry handles dispatch and facts wiring | `internal/agent/registry.go:51,74` | Confirmed | -| `IsBroadcastTarget` accepts `_any`, `_all`, labels | `internal/job/subjects.go:306` | Confirmed | -| `cli.PrintKV` and `cli.PrintCompactTable` exist | `internal/cli/ui.go:413,198` | Confirmed | -| Step 8's four recipes exist | `just --list` | Confirmed, but incomplete — FR-024 | -| `node.validateHostname()` is a shared helper | three `validate.go` files | **Wrong** — FR-012 | -| `sdk-standards` is a capability in this repository | `grep -rn sdk-standards` | **Absent** — FR-019 | -| `api-guidelines.md` states five guidelines | the page | **Six** — FR-014 | -| `principles.md` states five principles | the page | **Eight** — FR-022 | - -The last two are 003's record being wrong rather than the page having drifted: -003's line counts for all four pages match what is there today. It counted lines -correctly and counted items from memory, which is the same mistake Finding 1 -made in its first version and the reason its own T002 exists. diff --git a/history/osapi-005-building-a-domain/spec.md b/history/osapi-005-building-a-domain/spec.md deleted file mode 100644 index 4d90e97..0000000 --- a/history/osapi-005-building-a-domain/spec.md +++ /dev/null @@ -1,532 +0,0 @@ -# Feature Specification: Building a domain - -**Feature Branch**: `005-building-a-domain` - -**Created**: 2026-09-28 - -**Status**: Archived 2026-09-28, amended twice 2026-09-28 — FR-028 states the -SDK's method-naming convention in five rules, derived from the existing surface -once it was established that none had been written, and names the seven methods -to be renamed so that nothing is left as a permitted exception. Earlier that day -— the SC-001 reading (T021) found that nothing stated a domain's test -obligations, that FR-007's six layers and FR-001's seven artifact kinds did not -reconcile, and that FR-006 split node-targeted from controller-only operations -by directory without saying what decides it. FR-027 is new; the amendment is -recorded in [changelog.md](../../.specify/memory/changelog.md). - -**Input**: Subject B of [003-corpus-backfill](../003-corpus-backfill/spec.md) — -the second and last subject of the corpus backfill. 003's `data-model.md` -assigns this subject `adding-an-api-domain.md` wholly, `api-guidelines.md` and -`principles.md` folded in, and `system-architecture.md`'s component map, entry -points, layers and request flow. - -## What this specification is - -A corpus specification, like [004](../004-job-system/spec.md). Every requirement -below takes the form *the corpus MUST state X*. Nothing in osapi's Go source -changes. What changes is where a contributor reads the answer: today it is a -654-line site page, and afterwards it is this corpus with the page reduced to a -citation table. - -Each requirement was checked against the repository before being written — 003's -FR-009 — and cites the file it describes — 003's FR-011. Where the page says -something the code has outgrown, it is recorded below as a **Gap** naming both -sides rather than corrected in passing. Four such gaps were found, and they are -the reason this specification is worth more than a copy of the page. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - A contributor can add a domain from the corpus alone (Priority: P1) - -Somebody adding a domain to osapi needs to know what artifacts a domain consists -of, in what order they are built, and which of the choices along the way are -rules rather than preferences. Today that lives on a site page. They should be -able to answer it from the corpus, so the `add-a-domain` skill can cite one -statement rather than carry a second. - -**Why this priority**: this is the subject. Everything else here supports it. - -**Independent Test**: SC-001 — a reader who has not seen the site page answers -the four fixed questions from the corpus alone. - -**Acceptance Scenarios**: - -1. **Given** a contributor who has never added a domain, **When** they ask what - a domain consists of across every layer, **Then** the corpus names the - layers, the artifacts each one takes, and the consistency obligation that - binds them — without their needing the site. -2. **Given** a contributor partway through, **When** they ask whether a step may - be skipped or reordered, **Then** the corpus distinguishes the steps whose - order is forced by a tool from the steps merely conventionally done in that - order, and says which is which. -3. **Given** a contributor reading a rule the corpus states, **When** they ask - why it is a rule, **Then** the corpus gives the reason or the file that - enforces it, so the rule can be checked rather than believed. - -______________________________________________________________________ - -### User Story 2 - An operator is not sent to a contributor's document (Priority: P1) - -`api-guidelines.md` and `principles.md` are addresses an operator may have -bookmarked or found in search. Removing them must not leave a dead link, and -what replaces them must not send an operator into the corpus, which is not -written for them. - -**Why this priority**: the same rule 004 obeyed. A corpus statement that costs -an operator their bookmark is not an improvement. - -**Independent Test**: SC-002 and SC-003 — every removed address still resolves, -and no surviving operator page points into the corpus. - -**Acceptance Scenarios**: - -1. **Given** a bookmark to `architecture/api-guidelines.md` or - `architecture/principles.md`, **When** it is opened after this change, - **Then** it resolves to the contributor page rather than to a 404. -2. **Given** `system-architecture.md` after its contributor half is removed, - **When** an operator reads it, **Then** health checks, authentication, - authorization, CORS and external dependencies are all still there and the - page reads as a whole rather than as a remainder. - -______________________________________________________________________ - -### User Story 3 - The rule has one home (Priority: P2) - -The `add-a-domain` skill currently carries the domain-building knowledge itself. -After this it cites the corpus, in the shape -[003's citation contract](../003-corpus-backfill/contracts/citation.md) defines. - -**Why this priority**: this is the point of the backfill, but it depends on the -corpus statement existing first. - -**Independent Test**: SC-004's `just test`, and SC-005's grep. - -**Acceptance Scenarios**: - -1. **Given** the skill's references after this change, **When** - `just skill-lint` runs, **Then** every citation resolves to a requirement in - this specification. -2. **Given** a rule this specification states, **When** the skill's references - are grepped for it, **Then** the rule's name appears with a citation and its - mechanics do not appear twice. - -### Edge Cases - -- **A rule 001 already holds.** Provider types, file structure, the provider - interface, naming, platform variants and the idempotency obligation are the - provider contract's. Subject B reaches the same files from the other - direction, so it cites rather than restates — see FR-002. -- **A rule that turns out to be wrong.** Four already have; they are the Gaps - below. The rule is to record both sides, because a silent correction leaves - nobody able to tell whether the site was wrong or the reader was. -- **A step whose order is not actually forced.** The page presents eight - numbered steps. Some orderings are imposed by code generation and some are - habit. Stating the second kind as a requirement would freeze a preference — - FR-004. -- **An authority that does not exist.** The page defers to a specification that - is not in this repository. Deferring to nothing is worse than stating nothing, - because it reads as though the rule has been settled elsewhere — Gap in - FR-019. - -## Requirements *(mandatory)* - -### Scope and method - -- **FR-001**: The corpus MUST state what a domain consists of across every - layer, and MUST state the consistency obligation as the page states it: a - domain appears in every place an existing domain appears, and the check is to - pick a completed domain and search for it. Evidence: - `docs/docs/sidebar/development/adding-an-api-domain.md`, "Cross-Layer - Consistency (MANDATORY)". -- **FR-002**: Where this subject reaches provider types, file structure, the - provider interface, naming, platform variants or the idempotency obligation, - the corpus MUST cite [001](../001-provider-contract/spec.md) rather than - stating the rule a second time. The provider contract holds fifteen - requirements about providers already, and memory is not exempt from the - one-statement test 003's FR-008 applies to the site. -- **FR-003**: Where it reaches delivery semantics, the two clocks, the dead - letter queue or any other job mechanic, the corpus MUST cite the job system in - [004](../004-job-system/spec.md) and the memory it produced. Where it reaches - signing, response verification or agent identity, it MUST cite - [002](../002-agent-key-store/spec.md). - -### The order of the work - -- **FR-004**: The corpus MUST state the build order, and MUST distinguish the - parts of it that a tool forces from the parts that are convention. The forced - ones are: the OpenAPI specification precedes generation, because generation - reads it; generation precedes the handler, because the handler implements a - generated interface; the combined specification precedes the SDK client, - because the SDK generates from the combined file. **The combined - specification** is `internal/controller/api/gen/api.yaml`, which - `redocly join` assembles from every domain's own `gen/api.yaml` inside - `just generate`; a domain absent from it is invisible to the SDK however - complete its own spec is. The rest — documentation after the CLI, verification - last — is convention, and stating it as a requirement would freeze a - preference as a rule. Evidence: the `//go:generate` directives under - `internal/controller/api/*/gen/`, the `redocly join` step in `just generate`, - and `go generate ./pkg/sdk/client/gen/...`. -- **FR-005**: The corpus MUST state the eight steps as a walkthrough rather than - as eight requirements, because the sequence is the part most likely to change - and a numbered requirement per step would have to be renumbered every time a - tool changes. What is stated as a requirement is what must be true of the - result — FR-004's forced orderings, and the per-layer obligations below. - -### Layers, and what each one takes - -- **FR-006**: The corpus MUST state where a domain's code goes **and what - decides it**, because the directory is the consequence rather than the rule. - An operation is **node-targeted** when the work happens on a managed machine: - it is addressed to a host, dispatched through the job system, and carried out - by a provider on an agent. It is **controller-only** when the controller - answers it itself, from state it holds — the job queue, the audit log, - enrollment, health, the object store — with no agent executing anything. - Node-targeted operations live under `internal/controller/api/node/{domain}/` - and carry `/node/{hostname}` in their path; controller-only ones live under - `internal/controller/api/{domain}/` and do not. The provider goes under - `internal/provider/{domain}/` or `internal/provider/{category}/{domain}/`. - - A domain name can appear in **both**, which is why the test is functional - rather than nominal: `internal/controller/api/file/` uploads a file to the - object store at `/api/file`, and `internal/controller/api/node/file/` deploys - one to a host at `/api/node/{hostname}/file/deploy`. Same noun, different - machine doing the work. Evidence: those two `gen/api.yaml` files, and the - directory listings of `internal/controller/api/` and - `internal/controller/api/node/`. - -- **FR-007**: The corpus MUST state the component map, the entry points, the six - layers — CLI, REST API, job system, provider, agent lifecycle, configuration — - and the request flow, because that is what a contributor reads immediately - before the domain instructions. - - It MUST also state how those six relate to the seven artifact kinds a domain - contributes (FR-001), because the two lists differ and a reader who takes them - for one list will hunt for a layer that is not there. The layers describe - **the running system**; the artifacts describe **what a domain adds to it**. - Four artifacts land in a layer — the provider in the provider layer, the agent - processor in the agent lifecycle, the API handler in the REST API, the CLI - commands in the CLI. Three are not layers at all: the SDK service is a - *client* of the REST API rather than a layer of the system, and documentation - and tests are not runtime code. The configuration and job system layers exist - already for every domain and are not something a domain adds. Evidence: - `docs/docs/sidebar/architecture/system-architecture.md`, lines 12–174 and - 241–266. - -- **FR-008**: The corpus MUST state the request path a domain's operation takes, - `CLI → SDK → REST API → Job Client → NATS → Agent → Provider`, and that the - provider runs on the agent rather than the controller. Evidence: the page's - "Step 0"; `internal/job/client/client.go`. - -### Agent wiring - -- **FR-009**: The corpus MUST state that two files connect a provider to the - agent — a processor file under `internal/agent/` and the registration in - `cmd/agent_setup.go` — and MUST state what does *not* change: - `agent/types.go`, `agent/agent.go` and the `JobClient` interface, because the - registry handles dispatch and facts wiring. Evidence: - `ProviderRegistry.Register` at `internal/agent/registry.go:51`, `AllProviders` - at `:74`, and the single - `provider.WireProviderFacts(a.GetFacts, registry.AllProviders()...)` call at - `internal/agent/agent.go:90`. -- **FR-010**: The corpus MUST state the `FactsAware` obligation as the page - states it — embed `provider.FactsAware`, add the compile-time - `var _ provider.FactsSetter` check — and MUST cite rather than restate where - 001 already holds it. Evidence: `WireProviderFacts` at - `internal/provider/facts.go:64`. - -### The API surface - -- **FR-011**: The corpus MUST state that the OpenAPI specification is the source - of truth for input validation, and MUST state the three places a tag goes and - the one place it does not: `x-oapi-codegen-extra-tags` on request body - properties, at *parameter* level for query parameters rather than inside - `schema:`, `format: uuid` for UUID path parameters, and **not** on path - parameters in strict-server mode, where oapi-codegen generates no tags. - Evidence: the page's "Validation in OpenAPI Specs"; the `cfg.yaml` files under - `internal/controller/api/*/gen/`. -- **FR-012**: The corpus MUST state that a path parameter needing validation - beyond `format: uuid` is validated by hand in the handler, and MUST state - where that helper actually lives. **Gap**: the page says "a shared helper like - `node.validateHostname()`". There is no shared helper and that call does not - compile from another package — `validateHostname` is unexported and exists - three times, at `internal/controller/api/agent/validate.go:30`, - `internal/controller/api/node/validate.go:30` and - `internal/controller/api/node/power/validate.go:30`. What is shared is - `validation.Var(hostname, "required,min=1,valid_target")`, which all three - call. The corpus states the shared validator and names the duplication rather - than repeating the page's call. -- **FR-013**: The corpus MUST state the verb mapping and that a mutable domain - uses separate verbs for create and update — `POST` creates with the name in - the body, `PUT /{name}` updates from the path — and MUST state the reason: it - is what gives 404 semantics a meaning. A combined set or upsert endpoint is - forbidden. Evidence: the page's "HTTP Verb Conventions"; the cron domain's - `gen/api.yaml`. -- **FR-014**: The corpus MUST state the API design guidelines: endpoints grouped - by functional domain under their own top-level prefix, resource-oriented paths - with sub-resources nested under their parent, an area expected to grow split - into its own category early, everything targeting a managed machine under - `/node/{hostname}`, and path parameters for identification with query - parameters only for filtering and pagination. **Gap**: 003's `data-model.md` - lists five guidelines to fold in; - `docs/docs/sidebar/architecture/api-guidelines.md` states **six**. The sixth, - "Path Parameters Over Query Parameters", is included above. Nothing was - dropped, but 003's record of this page was incomplete and the next reader - should know the count was checked rather than copied. -- **FR-015**: The corpus MUST state that `{hostname}` accepts a literal - hostname, the reserved values `_any` and `_all`, or a `key:value` label - selector. Evidence: `IsBroadcastTarget` at `internal/job/subjects.go:306`. - -### Broadcast - -- **FR-016**: The corpus MUST state that every operation under - `/node/{hostname}/...` supports broadcast targeting, that both the - single-target and the broadcast path return the same collection shape, and - that every result item carries `hostname` and `error`. A single target returns - one result; a broadcast returns as many as there are agents, with failed and - skipped ones present as entries rather than absent. Evidence: the page's - "Broadcast Support"; `internal/job/client/client.go`. -- **FR-017**: The corpus MUST state that the `JobClient` interface has four - generic methods — `Query`, `QueryBroadcast`, `Modify`, `ModifyBroadcast` — and - that adding an operation needs none added, because a handler passes a category - string and an operation constant. Evidence: `internal/job/client/types.go`, - lines 146, 153, 160 and 167. - -### Registration and the SDK - -- **FR-018**: The corpus MUST state that a domain package exports a `Handler()` - function returning route-registration closures, that it wraps the handler in - scope middleware itself, and that the `Server` struct does not change. Startup - wiring is one appended line in `registerControllerHandlers`. Evidence: - `cmd/controller_setup.go`. - -- **FR-019**: The corpus MUST state the SDK obligations: four files per service, - a field on the `Client` struct, an example under `examples/sdk/client/`, a doc - page in the matching category, and the navbar entry — and MUST NOT defer to an - authority that does not exist. **Gap**: the page says method naming, type - exposure, result-field tags and error handling "are specified in the - `sdk-standards` capability in osapi-io/specs", and that where the two disagree - the specification wins. There is no `sdk-standards` capability in this - repository. The only other mention of it is - `.claude/skills/add-a-domain/references/sdk.md:6`, which makes the same claim. - Two documents defer to a specification nobody has written, which reads as - settled and is not. The corpus states the conventions it can verify and - records this as unstated rather than repeating the deferral. - - **Amended: there was a third, and one of the four subjects has no rule at - all.** The site's SDK guidelines page carried the same claim, so three - documents deferred to it rather than two; all three now name it as unwritten. - And the deferral named four subjects — method naming, type exposure, - result-field tags, error handling. Three are real and stated: type exposure, - JSON tags and error wrapping are FR-020, and the guidelines page shows each - working. **Method naming is stated nowhere** — not here, not on that page, - whose sections are package structure, generated types, result types, the - response pattern and error handling, and not in the capability nobody wrote. - It was named only in the deferral. So the missing authority was not a document - that would have collected existing rules; for one of its four subjects there - was nothing to collect. - - Owner of the remainder: this repository, and this project rather than - `system`. `osapi-orchestrator` does depend on `github.com/osapi-io/osapi`, so - the claim that these rules reach a second repository was true in substance. - But the SDK is osapi's own public API and the orchestrator consumes it, which - by the test under "Where a change belongs" makes it osapi's behaviour rather - than an agreement between repositories. A method-naming convention is worth - stating only once somebody decides what it is, rather than inferring one from - the method names that happen to exist. - -- **FR-020**: The corpus MUST state the rules an SDK service obeys that *are* - verifiable: no `gen` types in a public method signature, JSON tags on every - result type, errors wrapped with context, and one service per file with no - methods added to another service's files. Evidence: `pkg/sdk/client/`, and - `docs/docs/sidebar/sdk/guidelines.md`. - -### The CLI, and the principles - -- **FR-021**: The corpus MUST state the CLI obligations: one parent command per - domain and one subcommand per endpoint, `--json` on every command, - `cli.PrintKV` for key-value output and `cli.PrintCompactTable` for tabular, - flags rather than positional arguments for resource IDs, and every response - code the OpenAPI specification declares handled in the status switch. - Evidence: `PrintCompactTable` at `internal/cli/ui.go:198` and `PrintKV` at - `:413`. -- **FR-022**: The corpus MUST state all **eight** design principles, each with - what it constrains, so that a principle can decide a question rather than - decorate a page. **Gap**: 003's `data-model.md` and its research Finding 1 - both say `principles.md` states **five**; - `docs/docs/sidebar/architecture/principles.md` states eight. The three 003 - never named are Reliability and Stability, CLI Parity with API, and Least - Privilege Mode. The line count 003 recorded — 46 — is right, so the page has - not grown; the count of principles was wrong when written. -- **FR-023**: The corpus MUST record that each of the eight was checked against - `.charter/fragments/global/` and this project's constitution before being - stated, and MUST cite - [003's research Finding 1](../003-corpus-backfill/research.md) for the five it - covers rather than re-running a check that feature already closed. The three - principles Finding 1 does not cover MUST be checked the same way, because - Finding 1's own history is the argument for doing so: its first version - claimed two principles were already charter rules, read from the headings - rather than the text, and its T002 found all five unstated. - -### Verification - -- **FR-024**: The corpus MUST state what verifies a finished domain, and MUST - NOT reproduce the page's command list as sufficient. **Gap**: the page's "Step - 8" gives `just generate`, `go build ./...`, `just go-unit` and `just go-vet`. - All four recipes exist, but they do not cover Step 7, which edits eight - documentation files: `docusaurus-fmt-check` and `docusaurus-build` run in - `just test`, not in any of the four. A contributor who follows Step 8 exactly - can hand in work that fails continuous integration on the documentation Step 7 - told them to write. The corpus states the gate as `just ready` and - `just test`, naming what each covers. Evidence: the recipe list from - `just --list`. - -### Citing rather than restating - -- **FR-027**: The corpus MUST state a domain's test obligations, or cite where - they are stated — and MUST NOT leave tests as the one artifact kind FR-001 - names with no requirement behind it. They are osapi's `CONTRIBUTING.md`'s, - under "Testing", and are cited rather than copied: `testify/suite` table tests - with one suite method per function under test, `*_public_test.go` in a `_test` - package as the default, coverage gated at 99.9% with `.coverignore` narrowing - what the figure covers, and the two HTTP wiring methods a public suite - carries. One obligation there is specific to this subject and MUST be cited as - such: **a new API domain includes a `{domain}_test.go` smoke suite under - `test/integration/`**, with every mutating test guarded by `skipWrite(s.T())` - so continuous integration runs read-only by default. Evidence: - `CONTRIBUTING.md`, "Testing", "Test file conventions" and "Test layers"; - `.coverignore`. - - This requirement exists because the SC-001 reading found the hole. Every other - artifact kind had a requirement — the CLI FR-021, the SDK FR-019 and FR-020, - registration FR-018 — and tests had none, so a reader of this specification - alone saw an omission where a deferral was intended. The deferral was real and - recorded in [data-model.md](data-model.md); it was simply not here. - -- **FR-028**: The corpus MUST state the SDK's method-naming convention. It was - derived from the 31 services and roughly 110 exported methods that exist in - `pkg/sdk/client/`, rather than decided in the abstract, because no convention - had ever been written down — FR-019 records that. Four rules describe what is - there: - - 1. **The five CRUD verbs are exactly `List`, `Get`, `Create`, `Update`, - `Delete`.** Never `GetAll`, `Fetch`, `Set`, `Put` or `Remove` for the - service's own resource. Eleven services use some or all of them and none - deviates. - - 2. **A method acting on the service's own resource takes the bare verb, with - no object.** `Service.Start`, not `Service.StartService`; `Power.Reboot`, - `Agent.Accept`, `Job.Retry`, `Package.Install`, `Docker.Pull`. - - 3. **A method acting on a *sub*-resource takes verb then object.** - `User.AddKey`, `User.ListKeys`, `User.RemoveKey`, `User.ChangePassword`, - `Agent.ListPending`, `Package.ListUpdates`, `Log.QueryUnit`. - - 4. **A getter is named `Get` and nothing else.** Six services expose a single - read — `Disk`, `Load`, `Memory`, `OS`, `Status`, `Uptime` — and each names - it `Get`, taking its subject from the service. - - 5. **When a service exposes several distinct reads, each takes verb then - object** under rule 3; rule 4's bare `Get` applies only where there is - exactly one read. `User.ListKeys`, `Agent.ListPending` and - `Package.ListUpdates` already work this way. - - Applying these rules to the existing surface leaves **seven methods that do - not conform**, and the corpus MUST state that each is to be renamed rather - than recorded as a permitted exception: - - | Current | Becomes | Why | Call sites | - | -------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | - | `Docker.ImageRemove` | `RemoveImage` | Rule 3 is verb then object; this is the only object-then-verb method in the SDK | 3 here, 1 in the orchestrator | - | `Ping.Do` | `Send` | `Do` names no action. `Send` is the domain verb, which rule 2 admits — the stutter in `Ping.Ping` is what made `Do` look necessary | 3 here, 2 in the orchestrator | - | `File.Stale` | `ListStale` | Returns `StaleList`. It is a list of a sub-resource, so rule 3 | 3 | - | `File.Changed` | `GetChanged` | Returns `FileChanged` for a path. A read of a sub-resource, so rule 3 | 2 | - | `Health.Liveness` | `GetLiveness` | Three distinct reads on one service, so rule 5 | 3 | - | `Health.Ready` | `GetReady` | Same | 3 | - | `Health.Status` | `GetStatus` | Same | 6 | - - **Why renaming rather than excepting.** The SDK has no released version — - osapi carries no `v*` tag and `osapi-orchestrator` pins a pseudo-version - commit — so these are renames today and breaking changes after the first tag. - Twenty-six call sites here and three there is the whole cost, and it only - grows. - - `Docker.Pull` is deliberately **not** renamed. `PullImage` would be more - symmetric with `RemoveImage`, but pull applies to nothing but images in - Docker, so the object adds length without removing ambiguity. - - **Two of these were first recorded as permitted deviations, and that was - wrong.** `File.Stale` and `File.Changed` were described as predicates - answering a question about state — read off their names rather than their - signatures, which return a list and a single record. FR-090 requires a rule - checked against the code before it is written down, and this is that - requirement failing against the analysis that stated it. The correction is - recorded here rather than in a later amendment because the statement had not - yet merged. - -- **FR-025**: After this specification merges, the `add-a-domain` skill MUST - cite its requirements rather than restating the mechanics, in the shape - [003's citation contract](../003-corpus-backfill/contracts/citation.md) - defines — a relative link four `../` levels up from a reference file, named to - a requirement rather than to a document. - -- **FR-026**: In the same change that adds those citations, the contributor half - of the site MUST be removed: `adding-an-api-domain.md` reduced to what adding - a domain involves, a citation table, and a pointer to the skill; - `api-guidelines.md` and `principles.md` removed with their addresses - redirected; and `system-architecture.md`'s component map, entry points, layers - and request flow removed. Adding the citations without removing the page - leaves two statements, which is the condition this feature exists to end. - -### Key Entities - -- **Domain**: a coherent area of system behavior exposed as API endpoints, whose - artifacts span provider, agent processor, API handler, SDK service, CLI - commands, documentation and tests. -- **Layer**: one of the six the system is built from, each taking a defined - artifact from a domain. -- **Step**: one unit of the build sequence. Some orderings are forced by code - generation and some are convention — FR-004 separates them. -- **Gap**: a rule the site states that the repository does not bear out, - recorded with both sides named. Four are recorded here. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A reader who has not seen the site page answers all four questions - from the corpus alone: *what does a domain consist of, and how would I know - one was incomplete?*; *what must be built before what, and which of those - orderings is forced?*; *where does user input get validated, and what happens - to a path parameter?*; *what must be true of an operation that targets more - than one machine?* Each is a question that would break a domain if unanswered - — an incomplete domain, a generation step run out of order, an unvalidated - input, a broadcast handler that returns the wrong shape. -- **SC-002**: Every address removed from the site resolves after the change. Two - are removed and both redirect. -- **SC-003**: No page an operator reads points into the corpus. The contributor - page is the one exception, and its reader is a contributor. -- **SC-004**: `just test` passes in the specs repository, `skill-lint` resolving - every citation this specification's requirements are cited by. -- **SC-005**: No rule this specification states appears in restated form in the - skill's references. The skill states a rule's name and where it lives. -- **SC-006**: The site page's last commit postdates this specification's merge. - If it does not, the corpus and the page both state these rules and the feature - is unfinished whatever the corpus says. - -## Assumptions - -- The reader of the corpus is a contributor or an agent, not an operator. That - is what makes a citation the right form here and the wrong form on a feature - page. -- The four gaps are recorded, not fixed. Correcting the site's `sdk-standards` - deferral, the duplicated `validateHostname`, 003's principle count and Step - 8's command list is each its own change; three of them touch osapi and this - feature touches no code. -- `@docusaurus/plugin-client-redirects` is available for the two redirects. - 003's T015 adds it, so this feature depends on that task rather than - duplicating it. -- Memory holds three archived features when this is implemented — 001, 002 and - 004 — so every overlap named in FR-002 and FR-003 has something to cite. -- The line counts in 003's `data-model.md` were re-measured on 2026-09-28 and - all four match. What did not match is the *contents* of two of those pages, - which is what FR-014 and FR-022 record. diff --git a/history/osapi-005-building-a-domain/tasks.md b/history/osapi-005-building-a-domain/tasks.md deleted file mode 100644 index 115697b..0000000 --- a/history/osapi-005-building-a-domain/tasks.md +++ /dev/null @@ -1,460 +0,0 @@ -______________________________________________________________________ - -## description: "Task list for building a domain" - -# Tasks: Building a domain - -**Input**: Design documents from `components/osapi/specs/005-building-a-domain/` - -**Prerequisites**: [spec.md](spec.md) merged (PR #152), [plan.md](plan.md), -[research.md](research.md), [data-model.md](data-model.md), -[contracts/walkthrough.md](contracts/walkthrough.md), -[quickstart.md](quickstart.md) - -**Tests**: none. There is no code in this feature. What stands in for a test is -`skill-lint` resolving every citation, `docusaurus-build` failing on a link to a -deleted page, and the SC-001 reading. - -**Organization**: by the three user stories, in priority order. Subject A's -[tasks.md](../004-job-system/tasks.md) is the shape this follows. - -## Format: `[ID] [P?] [Story] Description` - -- **[P]**: can run in parallel — different files, no dependency on an incomplete - task -- **[Story]**: US1, US2 or US3 from [spec.md](spec.md) - -## Two repositories, and the order they merge - -[research.md](research.md) Decision 1 fixes this and the reason is worth keeping -in front of whoever runs the list: **the corpus pull request merges first.** -Site-first leaves a window where neither the corpus nor the site states these -rules, `skill-lint` fails in `specs/`, and a contributor at the old address gets -a redirect to a page whose citations resolve to nothing. Corpus-first leaves a -window where both state them, which is visible, bounded, and gives a correct -answer from either. - -| Pull request | Repository | Tasks | -| ------------ | ---------- | --------------------------------- | -| 1 | `specs/` | T001–T004, T016–T020 | -| 2 | `osapi/` | T005–T015 | -| — | `specs/` | T021–T024, after both have merged | - -______________________________________________________________________ - -## Phase 1: Setup - -**Purpose**: confirm the boundaries the plan states still match the files. - -- [x] T001 Confirm the line ranges in [data-model.md](data-model.md) against - `osapi/docs/docs/sidebar/architecture/system-architecture.md` as it stands - today: `12–174` covering Component Map, Entry Points and Layers, and `241–266` - covering Request Flow. Confirm the four page line counts — 654, 61, 46, 330. A - range that no longer matches means the page moved under the plan, which is how - [004's Finding 2](../004-job-system/spec.md) was caught; record the new range - rather than quietly working around it. - - **Confirmed 2026-09-28.** All four line counts match: 654, 61, 46, 330. Every - range boundary lands exactly on its heading — line 12 `## Component Map`, 41 - `## Entry Points`, 54 `## Layers`, 175 `## Health Checks`, 241 - `## Request Flow`, 267 `## Security`, 310 `## Further Reading`. Nothing moved - under the plan. - -______________________________________________________________________ - -## Phase 2: Foundational (Blocking Prerequisites) - -**⚠️ CRITICAL**: T002 blocks every citation task. A wrong depth fails the build -in a way that reads like a missing file. - -- [x] T002 Confirm the citation depth by writing one citation and running the - gate: from a file under `specs/.claude/skills/add-a-domain/references/`, the - path to a corpus requirement is **four** `../` levels — - `../../../../components/osapi/specs/005-building-a-domain/spec.md`. Three - lands in `.claude/` and `skill-lint` says so by name. The rule is recorded in - [003's citation contract](../003-corpus-backfill/contracts/citation.md); this - task is the cheap proof before twenty rows are written against it. - - **Confirmed.** Four levels resolve; `references/provider.md` has been using - `../../../../components/osapi/specs/001-provider-contract/spec.md` since - `001`, which is the working example. `skill-lint` passes on every citation - written for T016–T018. - -- [x] T003 Check the three principles 003 never named — Reliability and - Stability, CLI Parity with API, Least Privilege Mode — against - `.charter/fragments/global/` and - `components/osapi/.specify/memory/constitution.md`, reading the fragment text - rather than its heading. FR-023 requires this and - [003's Finding 1](../003-corpus-backfill/research.md) is the reason: its first - version claimed two principles were already charter rules, from the headings. - Record what was found, including "unstated", so the next reader sees a check - rather than an assumption. - - **Result: all three are unstated, so all eight principles are stated for the - first time in `005`.** Checked by reading the text, not the headings: - - | Checked | `.charter/fragments/global/` | `constitution.md` | Existing memory | - | ------------------------- | ---------------------------- | ----------------- | --------------- | - | Reliability and Stability | no match | no match | no match | - | CLI Parity with API | no match | no match | no match | - | Least Privilege Mode | no match | no match | no match | - - The constitution composes exactly five sections — Documentation, Verification, - Tooling, Correction, Workflow — and none of them reaches reliability, CLI - parity or privilege. This extends - [003's Finding 1](../003-corpus-backfill/research.md) from five principles to - eight with the same answer. **Checkpoint**: the citation shape is proven and - the charter check is recorded. - -______________________________________________________________________ - -## Phase 3: User Story 1 — a contributor can add a domain from the corpus alone (Priority: P1) 🎯 MVP - -**Goal**: the corpus holds the sequence as well as the rules. - -**Independent test**: SC-001's reading, run in Phase 6 once the corpus half has -merged. - -- [x] T004 [US1] Write the walkthrough into [data-model.md](data-model.md) — - already drafted under "The walkthrough" — and confirm it keeps FR-004's three - forced orderings separate from the conventional sequence, applying the test in - [contracts/walkthrough.md](contracts/walkthrough.md): would a reader call the - corpus *wrong* or merely *dated* if the step moved? Only "wrong" belongs in a - requirement. - - **Done in the plan.** The walkthrough is in [data-model.md](data-model.md) - under "The walkthrough", with the three forced orderings separated from the - conventional sequence and the trap in steps 7 and 8 named. The test in - [contracts/walkthrough.md](contracts/walkthrough.md) was applied to each: only - the three that break a build are stated as requirements. **Checkpoint**: the - corpus states both the rules and the order. The site still states them too — - the window [research.md](research.md) Decision 1 describes. - -______________________________________________________________________ - -## Phase 4: User Story 2 — an operator is not sent to a contributor's document (Priority: P1) - -**Goal**: the site keeps what an operator uses, every address resolves, and two -pages cease to exist. - -**Independent test**: SC-002 and SC-003 from [quickstart.md](quickstart.md). - -- [x] T005 [US2] Replace - `osapi/docs/docs/sidebar/development/adding-an-api-domain.md` with the - three-part contributor page specified in [data-model.md](data-model.md) under - "The contributor page that survives": under ten lines of prose on what adding - a domain involves, the citation table, and the pointer to the `add-a-domain` - skill. No step contents, no code block, no summary of a rule — a summary is - the second statement this feature exists to end. - - The citation table MUST include FR-022, the eight design principles, and - FR-024, the gate. Both are rules a contributor needs and neither has another - home on the site once `principles.md` is deleted. - -- [x] T006 [US2] State in one sentence on that page that its corpus links are - absolute GitHub URLs because the corpus is not part of the published site. - Without it the reader takes the one departure from - [the citation contract](../003-corpus-backfill/contracts/citation.md) for an - oversight. - -- [x] T007 [US2] Delete `osapi/docs/docs/sidebar/architecture/api-guidelines.md` - and `osapi/docs/docs/sidebar/architecture/principles.md`. Both are wholly - contributor knowledge — FR-014, FR-015, FR-022 — so neither keeps an operator - half. This is the first time the backfill deletes a page rather than splitting - one. - -- [x] T008 [US2] Add `@docusaurus/plugin-client-redirects` to - `osapi/docs/package.json` and `osapi/docs/docusaurus.config.ts`, redirecting - both deleted addresses to the contributor page. **This feature adds the - plugin** — it is [003's T015](../003-corpus-backfill/tasks.md), and T022 marks - that task done, so there is no earlier task to wait for. 005 is the only - feature in the backfill that needs a redirect, which is why the plugin arrives - here rather than with Subject A. - -- [x] T009 [US2] Remove lines `12–174` and `241–266` from - `osapi/docs/docs/sidebar/architecture/system-architecture.md` — Component Map, - Entry Points, Layers, Request Flow. Keep `175–240`, `267–309` and the link - definitions. - -- [x] T010 [US2] Edit that page's introduction by one sentence so it leads into - Health Checks rather than into a removed Component Map. Removing `241–266` - leaves Health Checks running directly into Security, which reads; removing - `12–174` leaves a jump the introduction has to cover. - -- [x] T011 [US2] Remove the `api-guidelines.md` and `principles.md` entries from - that page's Further Reading list and add one to the contributor page. **This - is a build failure, not a tidy-up**: `docusaurus-build` fails on a link to a - deleted page, and both links are there today. - -- [x] T012 [US2] [P] Search the whole site for any other link to either deleted - page: - `grep -rn "api-guidelines\|principles" docs/docs docs/docusaurus.config.ts`. - Everything outside the redirect configuration is a link that will break. - `osapi/docs/docs/sidebar/architecture/architecture.md` is named here rather - than left to the grep, because it is 003's T013 and its Deep Dives and Further - Reading lists are where a link to moved content is most likely to survive. The - page is otherwise unchanged. - - **Found one, exactly where the task predicted.** `architecture.md` linked both - deleted pages — 003's T013 — and it also described `system-architecture.md` as - "package layout, handler structure, provider pattern, and code-level details", - which is precisely what T009 removed from it. Both corrected. Naming the file - in advance rather than trusting the grep is what caught the stale description, - since no grep for a deleted page's name would have matched it. - -- [x] T013 [US2] Confirm no surviving page sends an operator to the corpus — - 003's FR-012 and SC-003's grep. The contributor page is the one exception and - its reader is a contributor. - - **One page beyond the expected exception, and it is legitimate.** - `docs/docs/sidebar/sdk/guidelines.md` points into the corpus, and its reader - is a developer of the SDK rather than an operator, so SC-003 is met. It also - carried a **third copy** of the `sdk-standards` deferral, claiming rules - "specified in the `sdk-standards` capability", binding `osapi-orchestrator`, - and winning any disagreement. FR-019 records two copies of that claim, so the - gap undercounted by one. Corrected the same way T018 corrected the skill's. - -- [x] T014 [US2] Run - `cd osapi && mise exec -- just docusaurus-fmt-check && mise exec -- just docusaurus-build`. - - **Both green, and the redirects verified in the output rather than inferred.** - `build/sidebar/architecture/api-guidelines/index.html` and - `build/sidebar/architecture/principles/index.html` each refresh to - `/osapi/sidebar/development/adding-an-api-domain`. A green build alone would - not have proved the plugin loaded, which is why the generated files were read. - -- [x] T015 [US2] Open and merge the osapi pull request for T005–T014. **This is - the task that ends the duplication.** Until it lands, the corpus and the site - both state these rules. - - **Merged as osapi#545.** The duplication is over. The site went from stating - these rules to indexing them: 654 lines to 76 on the contributor page, two - pages deleted with redirects, and 330 lines to 142 on - `system-architecture.md`. - -______________________________________________________________________ - -## Phase 5: User Story 3 — the rule has one home (Priority: P2) - -**Goal**: the skill's six references cite the requirements instead of restating -the mechanics. - -**Independent test**: SC-004's `just test`, and SC-005's grep. - -- [x] T016 [US3] Replace the domain-building mechanics in - `specs/.claude/skills/add-a-domain/references/api.md` with citation rows - naming FR-011, FR-012, FR-013, FR-014, FR-015, FR-016, FR-017, FR-018 and - FR-024 — written out rather than as a range, so a coverage check can see each - one. This is the largest of the six at 193 lines. - - FR-012, FR-014 and FR-024 each contain a recorded **Gap**. A citation row - names the *rule* and leaves the gap in the corpus: "how a path parameter is - validated", not "the page was wrong about `node.validateHostname()`". A gap - copied into a reference becomes guidance, which is the opposite of what - recording it was for. - -- [x] T017 [US3] [P] Do the same for `references/cli.md` citing FR-021, - `references/docs.md` citing FR-001, and `references/agent.md` citing FR-009 - and FR-010. `references/agent.md` already cites 004 from Subject A; add to it - rather than rewriting it. `references/provider.md` is unchanged — it already - cites 001 and is the pattern being copied. - -- [x] T018 [US3] In `references/sdk.md`, cite FR-019 and FR-020, and **name the - `sdk-standards` deferral at line 6 as unwritten rather than repeating it**. It - claims binding rules exist in this repository and they do not. Repeating the - claim propagates a settled-looking rule nobody has written; naming it tells - the reader what state it is actually in. - -- [x] T019 [US3] Run the SC-005 grep from [quickstart.md](quickstart.md) over - `specs/.claude/skills/add-a-domain/`. Every hit must sit in a citation row - naming a requirement, never in a paragraph explaining the mechanism. A - reference that explains how validation tags work is a second statement - whatever it links to. - - **Result: three hits, all in `api.md`, all acceptable — and one was not before - it was fixed.** Lines 48 and 74 are identifiers inside scaffolding code - blocks, which is what `provider.md` has always done and is not a paragraph - explaining a mechanism. Line 101 was a prose rule — "`IsBroadcastTarget` has - one implementation, never write a second" — stated with no citation. It is now - inside the section that names rules the corpus does not hold, citing FR-015 - for where the implementation lives. `x-oapi-codegen-extra-tags` no longer - appears anywhere; FR-011 holds it. - -- [x] T020 [US3] Run `cd specs && mise exec -- just test`. `skill-lint` - resolving every new citation at four levels is the gate, and it is what T002 - proved in advance. - -______________________________________________________________________ - -## Phase 6: Polish & Cross-Cutting Concerns - -- [x] T021 [P] Run the SC-001 reading from [quickstart.md](quickstart.md) with - somebody who has not read the site page, asking the four fixed questions. An - author cannot test their own corpus for completeness. A person is preferred; a - fresh agent given **only** `spec.md` — no repository, no site, no other - specification, no session context — is the fallback that makes the check - runnable, and it is what Subject A used. **Record what it proves and what it - does not**: that the answers are in the text, not that a person would succeed. - Subject A's reading found two real gaps its author had read past twice, which - is the argument for running it. - - **Result: SC-001 met.** All four questions came back ANSWERABLE from - [spec.md](spec.md) alone. The reader was a fresh agent given that one file and - the four questions — no repository, no site, no other specification, no - session context, and an instruction not to answer from its own knowledge of - Go, REST or OpenAPI. That is not a person and does not prove a person would - succeed; it does prove the answers are in the text. - - | Question | Cited | - | -------------------------------------------------------------------------- | ---------------------------- | - | What does a domain consist of, and how would I know one was incomplete? | FR-001, FR-007, Key Entities | - | What must be built before what, and which of those orderings is forced? | FR-004, FR-005 | - | Where does user input get validated, and what happens to a path parameter? | FR-011, FR-012 | - | What must be true of an operation that targets more than one machine? | FR-015, FR-016, FR-017 | - - **Three findings are real and land against this specification.** Verified - against the file rather than taken on the reader's word: - - - **Nothing states a domain's test obligations.** "Tests" is named among the - Domain entity's artifacts, and every other artifact kind has a requirement — - the CLI has FR-021, the SDK FR-019 and FR-020, registration FR-018 — while - tests have none. The deferral exists: [data-model.md](data-model.md) records - that testing conventions are osapi's `CONTRIBUTING.md`'s, cited rather than - copied. It is simply not in `spec.md`, so a reader of the specification - alone sees a hole where a deferral should be. The reader called it a silent - gap and was right. - - **FR-007's "six layers" and the Domain entity's seven artifact kinds do not - reconcile.** SDK, documentation and tests appear in the artifact list and - not among the layers, and nothing says why. Two lists of what a domain - touches, differing, in one document. - - **FR-006 distinguishes node-targeted from controller-only operations by - directory without saying what makes an operation one or the other.** That is - the first choice a contributor makes and the most expensive to get wrong. - - Two thinner ones: "the combined specification" is used in FR-004 and defined - nowhere, and `valid_target` is cited as a call without saying what it checks. - The first is worth a clause. Three further observations — the processor file's - name, a threshold for "expected to grow", and the absence of a worked example - — are the skill's job rather than the corpus's. - - **Not fixed here.** A merged specification is amended in its own pull request, - ahead of anything depending on it, and folding a correction into an archive - diff is the failure the workflow exists to prevent. The three real findings go - to that amendment, which also carries them into memory. - -- [x] T022 [P] Run the SC-006 check from [quickstart.md](quickstart.md): the - contributor page's last commit must postdate this specification's merge. If it - does not, the corpus and the site both state these rules and this feature is - unfinished whatever the corpus says. - - **Passes.** The contributor page's last commit is `2026-09-29T03:40:33Z` - (osapi#545); this specification merged at `2026-09-29T02:48:25Z` (specs#152). - The page postdates the statement by 52 minutes, so the corpus is the statement - of record and the site is the index — not two statements. - -- [x] T023 Mark 003's **T012 through T019** and T023 through T025 done, and - update 003's `tasks.md` to record that Subject B completed, so a reader of 003 - sees a task list matching what happened. **Not T011**: that task split - `job-architecture.md` and belongs to Subject A, which completed it in - osapi#544, and it is already ticked. 003's own T028 and T029 remain, because - archiving 003 comes after both its subjects. - - The mapping, so a reader of 003 can check it rather than trust it: - - | 003's task | Carried out by | - | ----------------------------------------------- | ---------------------------- | - | T012 the `system-architecture.md` split | T009, T010 | - | T013 `architecture.md` links | T012 | - | T014 the contributor page | T005, T006 | - | T015 the redirects plugin and the two deletions | T007, T008 | - | T016 the SC-003 address check | T014's build and T012's grep | - | T017 the osapi gate | T014 | - | T018 the SC-002 grep | T013 | - | T019 merge the osapi pull request | T015 | - | T023 specify Subject B | merged as specs#152 | - | T024 fold in the principles | T003, and FR-022 with FR-023 | - | T025 the two-repository sequence | the whole list | - - **Done.** 003's T012 through T019 and T020 through T025 are ticked, with a - mapping table in [003's tasks.md](../003-corpus-backfill/tasks.md) so a reader - can check the claim rather than trust it. T020 through T022 turned out to be - Subject A's rather than Subject B's, and T022's measurement is recorded there: - `references/` is net **-12** lines against `d370e37` with `SKILL.md` - unchanged, which passes narrowly because five rules had no corpus home and - were kept with a note rather than deleted to improve the number. 003 is now 25 - of 29; only its own polish phase remains. - -- [x] T024 Run `/speckit-archive-run specs/005-building-a-domain` once T015 and - T020 have merged, consolidating Subject B into - `components/osapi/.specify/memory/`. Archive after the implementation, never - before: what merged here is the statement, and the outcome is only true once - the site change lands. The walkthrough in [data-model.md](data-model.md) folds - into `memory/plan.md`; the requirements fold into `memory/spec.md`. - - **Archived 2026-09-28.** Merged into `components/osapi/.specify/memory/`: 3 - user stories as US10–US12, 26 requirements as FR-055–FR-080, 4 entities, 4 - edge cases, 6 outcomes as SC-018–SC-023 and 5 assumptions as AS-014–AS-018. - The walkthrough folded into `memory/plan.md` as "Building a Domain: the - Walkthrough" and the two-repository sequence as "Landing a Corpus Change - Across Two Repositories", per [research.md](research.md) Decision 2. Nothing - folded into an existing entry and nothing was superseded: the job system - states what carries an operation, and this states what a domain consists of. - All four gaps carried across as gaps with an owner each. - -______________________________________________________________________ - -## Dependencies & Execution Order - -### Phase dependencies - -- **Phase 1** has none. -- **Phase 2** blocks everything: T002 proves the citation depth before twenty - rows depend on it, and T003 closes FR-023 before FR-022 is cited. -- **Phase 3** (US1) is the corpus half and merges first. -- **Phase 4** (US2) is the site half and cannot merge before Phase 3 and Phase 5 - have — see the table at the top. -- **Phase 5** (US3) travels with Phase 3 in the corpus pull request, because - `skill-lint` runs in `specs/`. -- **Phase 6** needs both pull requests merged. T024 needs T015 and T020. - -### What is genuinely parallel - -- T012 with T009–T011 — a different search, same page family. -- T017 across its three files, and alongside T016 and T018: six reference files, - no shared lines. -- T021 with T022 — a reading and a `git log`. - -### What only looks parallel - -T007 and T011 touch different files and **must not** be split across pull -requests: deleting the pages without editing the Further Reading list fails -`docusaurus-build`, and editing the list first leaves it pointing at the -contributor page before that page exists. Same change, either way. - -______________________________________________________________________ - -## Implementation Strategy - -### MVP - -Phases 1, 2, 3 and 5 are one pull request in `specs/`. That is the corpus -statement plus its citations, and it is coherent on its own: the rules are -stated, the skill cites them, `just test` is green. The site still duplicates -them, which is the visible bounded window rather than a broken state. - -### Then - -Phase 4 in `osapi/`, one pull request, which is the half that ends the -duplication. Then Phase 6. - -______________________________________________________________________ - -## Notes - -- No Go code changes anywhere in this feature. -- Five gaps are recorded in [spec.md](spec.md) and **none is scheduled here**. - [plan.md](plan.md)'s "Out of scope" table names each owner: `validateHostname` - and Step 8's command list are osapi's, the `sdk-standards` capability is this - repository's own feature, and 003's two miscounts are corrected when 003 is - archived. -- Commit per task or per logical group. Stop at a checkpoint to validate. diff --git a/history/osapi-006-osapi-baseline/checklists/requirements.md b/history/osapi-006-osapi-baseline/checklists/requirements.md deleted file mode 100644 index 50a0773..0000000 --- a/history/osapi-006-osapi-baseline/checklists/requirements.md +++ /dev/null @@ -1,116 +0,0 @@ -# Specification Quality Checklist: A baseline for osapi - -**Purpose**: Validate specification completeness and quality before proceeding -to planning - -**Created**: 2026-09-29 - -**Feature**: [spec.md](../spec.md) - -## Content Quality - -- [ ] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [ ] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [ ] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [ ] No implementation details leak into specification - -## Notes - -Four items fail by design, the same four gohai's baseline failed and for the -same reason: a baseline's subject *is* the implementation. An inventory that -named no files would be unfalsifiable, and CONTRIBUTING's "Seeding a component" -goes further than the Verification principle here — it requires the command that -re-measures each count, so a reader re-measures rather than trusting the prose. -Removing those to pass these boxes would remove the only thing that makes the -inventory worth more than osapi's README. - -## What the verification found - -**Three contributor pages survived the corpus backfill**, and finding them is -the argument for baselining a repository that already has memory. - -| Page | Lines | Corpus counterpart | -| ------------------------------- | ----- | ----------------------------------------------- | -| `architecture/ui.md` | 264 | **none** | -| `development/ui-development.md` | 200 | **none** | -| `sdk/guidelines.md` | 227 | partial — its rules are 005's FR-019 and FR-020 | - -464 lines of contributor architecture sit on an operator's site with nowhere to -cite. They survived because 003 named six candidate pages and classified those; -these three were never candidates, so nothing examined them. An inventory that -only re-checked 003's six would have missed them exactly as 003 did. - -`sdk/guidelines.md` is the interesting case and FR-015 separates it: its rules -are already stated in the corpus and the page cites them, then demonstrates them -with worked examples. **Showing a rule working is not stating it twice.** What -it holds beyond demonstration is the part with no counterpart. - -## Two numbers that needed saying rather than stating - -**219 against 221.** 219 are published site pages under `docs/docs/`; 221 adds -`docs/README.md` and `docs/SUPPORT.md`, which are the Docusaurus project's own -files. `system`'s 002 records osapi at 221 and this classification covers 219. -Both are right about different things, which is precisely the collision that -analyze pass caught in 002 — and it recurred here in a different form on the -first baseline written after it. - -**The classification's arithmetic was wrong on the first attempt.** The table -summed to 217 against a measured 219, because `usage/configuration.md` and -`intro.md` sit outside the subdirectory counts. Corrected before this was -committed; the sum now reconciles against every directory count. - -## What this feature does not do - -It classifies; it moves nothing. 002's FR-026 puts the baseline before the move -and its FR-027 makes the move its own feature — which is the ordering osapi -itself got wrong the first time, when three features relocated documentation and -none of them wrote the document that says which pages are contributor-facing. - -## Found after the first pass, by looking wider - -`ui/docs/architecture.md` — 263 lines, beside the code rather than under `docs/` -— is a second statement of the site's `architecture/ui.md`, and the two have -diverged. FR-019 through FR-021 record it. - -The classification in FR-018 did not reach it, and the reason is worth stating -plainly: it counted the 219 **published pages** under `docs/docs/`, which is the -right answer to the question it asked and an incomplete answer to "what -documentation does this repository carry". A baseline that counts pages and a -baseline that inventories documentation are not the same document, and this one -conflated them for one file. - -What makes it more than a miscount: - -| | Last touched | Holds, that the other does not | -| -------------------------------------- | ------------ | -------------------------------------- | -| `ui/docs/architecture.md` | 2026-09-02 | `Feature flags` | -| `docs/docs/sidebar/architecture/ui.md` | 2026-08-15 | `Configuration`, `Embedding Mechanism` | - -Two documents that were once the same, edited eighteen days apart, each having -gained something the other never got. Their shared sections still agree in -substance and differ in punctuation — a copy shortly before it stops agreeing at -all. **This is the drift the one-statement rule exists to prevent, observed -rather than argued for.** - -It also changes the move's subject from two statements to three, and FR-021 -records why the order matters: relocating only the site page would leave the -divergent copy as the sole statement, and that copy is the one missing -`Configuration` and `Embedding Mechanism`. diff --git a/history/osapi-006-osapi-baseline/plan.md b/history/osapi-006-osapi-baseline/plan.md deleted file mode 100644 index 65981bb..0000000 --- a/history/osapi-006-osapi-baseline/plan.md +++ /dev/null @@ -1,163 +0,0 @@ -# Implementation Plan: A baseline for osapi - -**Branch**: `006-osapi-baseline` | **Date**: 2026-09-30 | **Spec**: -[spec.md](spec.md) - -**Input**: Feature specification from -`components/osapi/specs/006-osapi-baseline/spec.md` - -## Summary - -State what osapi is, so its memory stops holding 1,840 lines about decisions and -nothing about the repository they were decided for. Twenty-one requirements, -every one of the form "the corpus MUST state X", plus a classification of all -219 site pages. - -**This plan is retrospective, and it is late.** The specification merged as -specs#169 and no plan followed, which left the feature stalled at stage 1: the -archival gate requires a `plan.md`, so osapi's baseline could not be archived -and its memory could not receive the frame the specification wrote. Five -features were archived into that memory before this one and a sixth — the -embedded UI — was archived *ahead* of it, out of the ascending order archival -expects, because this file did not exist. - -That is worth recording rather than quietly fixing. The specification is the one -artifact that says what osapi is, and it sat unarchivable for a day because the -stage that produces this file was skipped. `gohai`'s baseline made the same -admission about being written to a gate; this one adds that a baseline with no -plan is not merely undocumented, it is **unarchivable**, and nothing in the -workflow says so out loud. - -**Nothing lands in the osapi repository.** That is what CONTRIBUTING's "Seeding -a component" requires of a baseline: the deliverable is the inventory, and it -lives here. The moves the inventory *found* are separate features — `007` did -two of the three pages and the third is still open. - -## Technical Context - -**Language/Version**: Markdown. The repository being inventoried is Go — 2,739 -files, 1,814 of them not tests — with a TypeScript single-page application under -`ui/`. Nothing in either changes. - -**Primary Dependencies**: None. The corpus depends on nothing at runtime. - -**Storage**: `components/osapi/specs/` for the specification and -`components/osapi/.specify/memory/` for what archival consolidates into. That -memory is **not** empty — it holds 1,840 lines from five archived features, -which is the condition this feature exists to complete rather than to create. - -**Testing**: `just test` in the specs repository — mdformat, just-fmt and -`scripts/validate-skills.py`. There is no code to unit test. Separately, every -count in the specification carries the command that reproduces it, and -re-running those seven commands against osapi is what checks the inventory. The -formatting gate cannot tell a right count from a wrong one. - -**Target Platform**: The corpus. - -**Project Type**: Documentation. - -**Constraints**: No change to the osapi repository. **Section 4 must be mostly -citation**: osapi's contract is already stated across four archived features, -and restating any of it would be the second statement the whole programme exists -to end. No count without its command. Every disagreement between osapi's prose -and its code recorded as a gap with both sides named and an owner. - -**Scale/Scope**: One repository inventoried, the largest in the organization. -219 site pages classified individually; 24 API domains, 6 provider categories -and 117 SDK methods stated by their shared contract rather than enumerated. - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -| Principle | How this feature satisfies it | -| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Documentation** | Section 4 cites the four archived features rather than restating them, and says so. The classification names which pages hold contributor knowledge, which is what makes the one-statement rule checkable for this repository. | -| **Verification** | Seven counts, each with its command. Two differ by a **definition** rather than a measurement — 219 site pages against 221 files under `docs/` — and FR-013 states what each counts, because a reader given one will think the other is broken. | -| **Tooling** | Nothing provisioned. | -| **Correction** | Four gaps recorded with owners, none corrected here. FR-016 records that the finding changes the programme's arithmetic and says 002 is amended in its own change rather than this one. | -| **Workflow** | Stages 3 and 4, written after stage 1 merged — late, and the Summary says how late and what it cost. | -| **Baseline** | This is the feature the fragment describes: memory that answers what was decided and never what the thing is. The fragment's own example is osapi's 1,840 lines. | -| **Repositories** | The dependency edges are stated from `go.mod` and the justfiles rather than from a list, and this baseline states only its own — the map is `system`'s. | -| **Tracking** | Nothing here becomes an issue. The three surviving contributor pages became `007` and the `sdk/guidelines.md` remainder, which are features rather than issues. | - -**Result**: no violations. - -## What a baseline for the hub has to do differently - -osapi is not the first baseline written but it is the first one *required* by -the others: every other component's section 2 states its edges against osapi's, -so this document is read while reading five others. Three consequences shaped -the specification and are recorded here rather than left to the next reader to -infer. - -**Section 4 is mostly citation, and that is the point.** Four archived features -already state osapi's contract — the provider contract, the agent key store, the -job system, building a domain. A baseline restating any of it would create the -second statement the programme exists to end, so section 4 names where each part -of the contract lives and states only what none of them states. This is the -opposite of every other baseline, where section 4 is the substantive part. - -**The counts differ by definition, not by error.** 219 and 221 are both right, -about different questions: 219 published pages under `docs/docs/`, and 221 -adding the Docusaurus project's own `README.md` and `SUPPORT.md`. A baseline -that stated one number would leave a reader comparing it against the other and -concluding one of them was broken. `system`'s 002 had recorded 221 as the page -count, which is why FR-013 states both and 002 was amended. - -**Classifying 219 pages is the work, not a by-product.** 002's FR-025 makes the -classification precede the move, and osapi is the repository where that ordering -was got wrong the first time: the corpus backfill moved documentation across -three features and none of them wrote the document saying which pages are -contributor-facing. The three pages that survived are what that omission cost. - -## Project Structure - -### Documentation (this feature) - -```text -components/osapi/specs/006-osapi-baseline/ -├── spec.md # merged at specs#169; 21 requirements, 7 outcomes -├── plan.md # This file, written 2026-09-30 -├── checklists/ -│ └── requirements.md # From stage 1 -└── tasks.md # Phase 2 output -``` - -### Content - -```text -components/osapi/ -├── specs/006-osapi-baseline/spec.md # the statement of record -└── .specify/memory/ # 1,840 lines from six archived features - ├── constitution.md # composed; eight fragments - ├── spec.md # gains sections 1, 2, 3 and the classification - ├── plan.md # gains the repository's own frame - └── changelog.md -``` - -**Structure Decision**: no corpus subject file beyond `spec.md`, and no skill -gains a reference. A baseline is what a reader reads first, not a rule a skill -cites; the rules it points at are already cited by the features that state them. - -## What the verification checks, and what it cannot - -The seven commands check the **counts**. Three things they do not check, stated -so a green task list is not mistaken for a verified inventory: - -- **Whether the classification of 219 pages is right page by page.** It was made - by reading each page's subject; re-running it means reading them again, which - is a review rather than a measurement. What *is* checkable is that the - classification sums to 219 and that every page appears exactly once — and it - did not, on the first attempt: the sum came to 217 because two pages sit - outside the subdirectory counts. -- **Whether section 4's citations are complete.** A rule of osapi's contract - that none of the four archived features states would not appear as a gap, - because nothing would point at its absence. -- **Whether the architecture survives a rename**, which is what US2 asks. That - is a judgement about the level of statement, and only re-reading it after a - refactor settles it. - -## Complexity Tracking - -> No Constitution Check violations, so this table is empty. diff --git a/history/osapi-006-osapi-baseline/spec.md b/history/osapi-006-osapi-baseline/spec.md deleted file mode 100644 index e176fb0..0000000 --- a/history/osapi-006-osapi-baseline/spec.md +++ /dev/null @@ -1,358 +0,0 @@ -# Feature Specification: A baseline for osapi - -**Feature Branch**: `006-osapi-baseline` - -**Created**: 2026-09-29 - -**Status**: Completed — amended 2026-09-29 before planning: FR-019 through -FR-021 record a 263-line second statement of the UI architecture at -`ui/docs/architecture.md`, which the page classification did not reach because -it counted published pages. The two copies have already diverged. - -**Input**: osapi's memory holds 1,840 lines from five archived features and -nowhere says what the repository is. Its `spec.md` sections are *The job -system*, *Building a domain* and *Where knowledge lives*; its `plan.md` sections -are about the corpus backfill. A reader arriving there learns how job delivery -works before learning what osapi is for, and never learns that it sits between -the NATS repositories and the orchestrator. This is the first baseline written -to the shape [system's 002](../../../../system/specs/002-baseline-shape/spec.md) -fixes, and FR-029 puts osapi first because it is the dependency hub. - -## What this is, and what it is not - -**Its subject is a description.** No Go code changes. What this produces is the -frame osapi's memory lacks — what the repository is, where it sits, and how it -is built — plus a classification of all 219 site pages. - -**It cites rather than restates.** osapi's contract is already four archived -features: the provider contract, the agent key store, the job system, building a -domain. A baseline that restated any of it would be the second statement the -whole programme exists to end, so section 4 below is mostly citation and says -so. - -**Prose, not Given/When/Then.** 002's FR-017 requires it: a baseline is read -repeatedly by somebody learning a system, and the formalism that helps a -reviewer check a change obstructs that reader. The user stories below carry the -template's acceptance form because the template requires it of a feature -specification; the inventory those stories describe does not. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - Somebody learns what osapi is (Priority: P1) - -Somebody arriving at osapi's memory learns what the repository is for, what it -sits between, and how its parts fit — before learning how any one mechanism -works. - -**Why this priority**: it is the gap. The memory is detailed and frameless. - -**Independent Test**: a reader given the corpus alone states what osapi is for, -names both its upstream dependencies and its downstream consumer, and describes -the path a request takes without opening the code. - -**Acceptance Scenarios**: - -1. **Given** the corpus, **When** a reader asks what osapi is, **Then** the - answer is in section 1 rather than inferred from a requirement about job - delivery. -2. **Given** the corpus, **When** a reader asks what would break if - `nats-client` changed, **Then** section 2 names the direction and the - consequence. - -______________________________________________________________________ - -### User Story 2 - The architecture survives a rename (Priority: P1) - -Somebody reads the architecture six months from now and finds it still true, -because it states what each part is for rather than transcribing how it -currently calls. - -**Why this priority**: osapi is 2,739 Go files. An architecture written at the -level of function names would be wrong within a month, and a wrong statement -carries the authority of a specification. - -**Independent Test**: no section names a function as the claim rather than as -evidence for one, and nothing in the baseline lists the code. - -**Acceptance Scenarios**: - -1. **Given** the architecture section, **When** a function is renamed, **Then** - the section cites a stale path and remains true. - -______________________________________________________________________ - -### User Story 3 - The pages that stayed are the pages an operator reads (Priority: P2) - -Somebody auditing the site after the backfill can tell, for every page, whether -it was left there deliberately and for whom. - -**Why this priority**: three contributor pages survived the backfill because 003 -named six candidates and these were not among them. Without a classification -nobody would find them. - -**Independent Test**: all 219 pages are classified, and the three contributor -pages are named with what to do about them. - -### Edge Cases - -- **A page the backfill was never scoped to.** Three exist. 003 classified six - candidate pages and moved what they held; the UI and SDK-development pages - were never candidates, so they were never examined. An inventory that only - re-checked 003's six would have missed them exactly as 003 did. -- **A demonstration that looks like a restatement.** `sdk/guidelines.md` shows - the SDK rules working with worked examples and cites 005's FR-019 and FR-020 - for the rules themselves. Showing a rule working is not stating it twice, and - the distinction has to be drawn explicitly or the page reads as a violation. -- **A page that is only a pointer.** Three development pages are 17 to 24 lines - and defer to osapi's own `CONTRIBUTING.md`. Deferring is what - `global/documentation` asks for; they stay. -- **Memory that already holds the answer.** Most of what a contributor needs - about providers, jobs and domains is already archived. The baseline's job is - to say so and where, not to say it again. - -## Requirements *(mandatory)* - -Every requirement is *the corpus MUST state X*. The seven sections 002's FR-001 -fixes are the organising structure, and each requirement below names which it -serves. - -### Section 1 — what this repository is - -- **FR-001**: The corpus MUST state that osapi is two things that ship together: - a controller exposing a REST API, and an agent that runs on each managed host. - Work reaches a host by being queued rather than by being called. Verified: - `cmd/` holds both entry points; `main.go` dispatches. -- **FR-002**: The corpus MUST state who consumes it — an operator through the - CLI, a program through the Go SDK at `pkg/sdk/client`, and - `osapi-orchestrator` through that same SDK. Three readers, and most of the - site serves the first. - -### Section 2 — where it sits - -- **FR-003**: The corpus MUST state both directions of osapi's place in the - graph. **Upstream**: `nats-client` and `nats-server`, which it imports. - **Downstream**: `osapi-orchestrator`, which imports it. Verified: - `grep -oE "osapi-io/[a-z-]+" go.mod`. -- **FR-004**: The corpus MUST state what breaks in each direction, because a - dependency named without a consequence is trivia. A change to either NATS - repository can break osapi's transport; a change to osapi's SDK surface breaks - the orchestrator, which pins a pseudo-version commit rather than a tag, so - nothing breaks there until somebody bumps it — which is why a rename and its - bump want to land together. -- **FR-005**: The corpus MUST state that osapi also depends on `osapi-justfiles` - for its build, that this appears in no `go.mod` because it is fetched by a - justfile recipe, and that the fetch is unpinned. Owner of the pinning: - `osapi-justfiles`. - -### Section 3 — architecture - -- **FR-006**: The corpus MUST state the six layers and **what each is for**, not - how it currently calls: the CLI parses and prints; the REST API validates and - delegates; the job system carries work to a host; a provider does the work - there; the agent lifecycle registers providers and dispatches to them; - configuration is resolved once at startup. Verified: the directory structure - under `cmd/`, `internal/controller/`, `internal/job/`, `internal/provider/`, - `internal/agent/`, `internal/config/`. -- **FR-007**: The corpus MUST state the path a mutating request takes — - `CLI → SDK → REST API → job client → NATS → agent → provider` — and that the - provider runs on the agent rather than the controller, because that single - fact explains why the API cannot simply do the work. -- **FR-008**: The corpus MUST NOT transcribe the call chain, list the exported - surface, or walk the files. 002's FR-005 forbids all three. Where a symbol - appears it is evidence for a claim, never the claim. -- **FR-009**: Where the architecture reaches job delivery, provider behaviour, - agent identity or domain construction, the corpus MUST **cite** the archived - features that already state them rather than summarising. Memory's own - `#### The job system`, `#### Building a domain` and - `#### Where knowledge lives` are the destinations. - -### Section 4 — the contract - -- **FR-010**: The corpus MUST state that osapi's contract is **already stated**, - and cite it: the provider contract ([001](../001-provider-contract/spec.md)), - the agent key store ([002](../002-agent-key-store/spec.md)), the job system - ([004](../004-job-system/spec.md)), and building a domain - ([005](../005-building-a-domain/spec.md)). This section is mostly citation and - the corpus MUST say why, so a reader does not read its brevity as an omission. -- **FR-011**: The corpus MUST state what a consumer of the SDK may depend on and - what is free to change: the exported surface of `pkg/sdk/client` is the - contract, and everything under `internal/` is not. Verified: the package - layout. - -### Section 5 — measurements - -- **FR-012**: The corpus MUST state each count with the command that reproduces - it, and MUST state what each counts, because two of them differ by a - definition rather than by a measurement: - - | Count | Value | Command | - | ------------------------ | ----- | ------------------------------------------------------------------------------------------------------------------------- | - | Go files | 2,739 | `find . -name '*.go' -not -path './.git/*' \| wc -l` | - | Go files excluding tests | 1,814 | `find . -name '*.go' -not -path './.git/*' -not -name '*_test.go' \| wc -l` | - | Site pages | 219 | `find docs/docs -name '*.md' -not -path '*/node_modules/*' \| wc -l` | - | Files under `docs/` | 221 | `find docs -name '*.md' -not -path '*/node_modules/*' \| wc -l` | - | Provider categories | 6 | `ls -d internal/provider/*/ \| wc -l` | - | API domains | 24 | `ls -d internal/controller/api/node/*/ internal/controller/api/*/ \| grep -vE '/(gen\|mocks\|common\|apierr)/$' \| wc -l` | - | SDK methods | 117 | `grep -hE '^func \(s \*[A-Za-z]+Service\)' pkg/sdk/client/*.go \| wc -l` | - -- **FR-013**: The corpus MUST state that **219 and 221 are different - quantities**: 219 are site pages under `docs/docs/`, and 221 adds - `docs/README.md` and `docs/SUPPORT.md`, which are the Docusaurus project's own - files rather than published pages. 002's FR-023 records osapi at 221; the - classification below covers 219. - -### Section 6 — gaps - -- **FR-014**: The corpus MUST record that **three contributor pages survived the - corpus backfill**, and why they did: 003 named six candidate pages and - classified those; these three were never candidates, so nothing examined them. - - | Page | Lines | What it holds | - | ------------------------------- | ----- | ------------------------------------------------------------------ | - | `architecture/ui.md` | 264 | The embedded UI's structure, tech stack and component architecture | - | `development/ui-development.md` | 200 | How to set up and develop the UI | - | `sdk/guidelines.md` | 227 | How to develop the SDK, with worked examples | - - 464 of those lines — the two UI pages — have **no corpus counterpart at all**, - so they are contributor knowledge on an operator's site with nowhere to cite. - That is the state the backfill existed to end, surviving in a corner it never - looked at. - -- **FR-015**: The corpus MUST state that `sdk/guidelines.md` is a **different - case from the other two**. Its rules are already stated as 005's FR-019 and - FR-020, and the page cites them and then demonstrates them with worked - examples. Showing a rule working is not stating it twice. What it holds beyond - demonstration — package structure and the response pattern — is the part with - no counterpart. - -- **FR-016**: The corpus MUST record that this finding changes the programme's - arithmetic. `system`'s 002 states osapi's move is "already done, across three - features"; that is true of the six pages 003 scoped and false of these three. - osapi needs a move after all, so the programme is **twelve units, not - eleven**, and 002 needs amending — in its own change, not this one. - -### The drift this inventory did not look for, and found anyway - -- **FR-019**: The corpus MUST record that `ui/docs/architecture.md` exists, - holds **263 lines** of contributor documentation, and is **a second statement - of the site's `architecture/ui.md`** — and that the two have already diverged. - - This was outside FR-018's classification, which counted the 219 pages under - `docs/docs/` and therefore never reached a documentation file living beside - the code it describes. The count was right about published pages and - incomplete about the repository's documentation, which are different - questions. - - The two files cover the same ground — tech stack, application structure, - authentication, SDK generation, component architecture, pages — and each now - holds a section the other does not: - - | | Last touched | Holds, that the other does not | - | -------------------------------------- | ------------ | -------------------------------------- | - | `ui/docs/architecture.md` | 2026-09-02 | `Feature flags` | - | `docs/docs/sidebar/architecture/ui.md` | 2026-08-15 | `Configuration`, `Embedding Mechanism` | - - Verified: `git log -1 --format=%cs` on each, and a comparison of their `##` - headings. Their shared sections still agree in substance and differ in - punctuation, which is what a copy looks like shortly before it stops agreeing - at all. - -- **FR-020**: The corpus MUST state that this is **the drift the one-statement - rule exists to prevent, observed rather than hypothesised**. Two documents - were once the same, were edited eighteen days apart, and each gained content - the other never got. Nothing marked the moment they stopped agreeing, which is - the failure `global/documentation` describes and the reason the corpus holds - one statement and citations. - -- **FR-021**: The corpus MUST state that osapi's move therefore reconciles - **three** statements, not two: the site page, the file beside the code, and - the corpus statement that will replace both. A move that relocated only the - site page would leave the divergent copy in place and make it the sole - statement by default — the worse outcome, because the surviving copy is the - one missing `Configuration` and `Embedding Mechanism`. - -### Section 7 — what this inventory excludes - -- **FR-017**: The corpus MUST state what it leaves out and why, so an omission - is never mistaken for an oversight: - - - **Everything the five archived features already state.** Cited, not - repeated. - - **How any individual provider or domain works.** 24 domains and 6 provider - categories; the contract they share is stated and the instances are not. - - **The CLI's full command surface.** 143 pages of reference under - `usage/cli`, which is where an operator should read it. - - **The UI's internals.** They are FR-014's gap, and stating them here would - be doing the move this feature is not. - - **osapi's testing conventions.** Its own `CONTRIBUTING.md`'s, cited. - - **Documentation outside `docs/`, beyond the one file FR-019 records.** The - classification counted published pages; `ui/docs/architecture.md` was found - by looking wider and is recorded as a gap. `ui/AI_POLICY.md` and the - repository-root files are policy and process rather than architecture, and - are not inventoried. - -### The classification of all 219 pages - -- **FR-018**: The corpus MUST classify every site page as user-facing or - contributor-facing, by who reads it rather than where it sits — 002's FR-025. - - | Pages | # | Reader | Disposition | - | ------------------------------------------------------------------------------- | ------- | ------------------ | ---------------------------------------------------------------------------------------------------- | - | `usage/cli` | 143 | operator | stays | - | `sdk/client`, `sdk/platform`, `sdk/sdk.md` | 34 | a program's author | stays — a consumer's reference, the same case as gohai's collector catalogue | - | `features` | 30 | operator | stays | - | `architecture/architecture.md`, `job-architecture.md`, `system-architecture.md` | 3 | operator | stays — already reduced to their operator halves by 003 and 005 | - | `development/contributing.md`, `development.md`, `testing.md` | 3 | contributor | stays — 17 to 24 lines each, pointers to `CONTRIBUTING.md`, which is deferring rather than restating | - | `usage/configuration.md` | 1 | operator | stays | - | `intro.md` | 1 | operator | stays | - | `development/adding-an-api-domain.md` | 1 | contributor | stays — already reduced to a citation index by 005 | - | `architecture/ui.md` | 1 | **contributor** | **moves** — FR-014 | - | `development/ui-development.md` | 1 | **contributor** | **moves** — FR-014 | - | `sdk/guidelines.md` | 1 | **contributor** | **partly moves** — FR-015 | - | **Total** | **219** | | **216 stay, 3 move or partly move** | - - The arithmetic reconciles against the directory counts: `architecture` 4, - `development` 5, `features` 30, `sdk` 35, `usage` 144, and `intro.md` 1. - -### Key Entities - -- **Controller**: The process exposing the REST API and owning the job queue. -- **Agent**: The process on a managed host that executes queued work through - providers. -- **Provider**: The unit that does the work on a host. Its contract is 001's. -- **Domain**: An area of behaviour exposed as endpoints. Its construction is - 005's. -- **Site page**: One of 219 published pages, each classified by who reads it. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A reader given the corpus alone states what osapi is for, names - both upstream dependencies and the downstream consumer, and describes the - request path. -- **SC-002**: Every count in the baseline is paired with its command, and - running the seven commands reproduces the seven values. -- **SC-003**: No section transcribes a call chain or lists the exported surface. -- **SC-004**: Section 7 is non-empty — six exclusions, each with a reason. Five - when this was written; the 2026-09-29 amendment added the sixth and this - criterion was not updated with it. -- **SC-005**: All 219 site pages are classified, and the three contributor pages - are named with their line counts and what holds them. -- **SC-006**: Nothing in the osapi repository changes. `git -C osapi status` is - clean. -- **SC-007**: `just test` passes in the specs repository. - -## Assumptions - -- The audience is a contributor or an agent working on osapi, or somebody - consuming its SDK. An operator is served by the site, which is why 216 of 219 - pages stay. -- Counts were measured on `0cca62060`, `main` at the time of writing. They will - date; the commands are what survives. -- The three contributor pages are recorded here and **moved by a separate - feature**. 002's FR-026 puts the baseline before the move, and its FR-027 - makes the move its own feature. This baseline classifies; it relocates - nothing. -- osapi's five archived features stay exactly as they are. A baseline and - archived features answer different questions, and neither replaces the other. diff --git a/history/osapi-006-osapi-baseline/tasks.md b/history/osapi-006-osapi-baseline/tasks.md deleted file mode 100644 index 6d99a1a..0000000 --- a/history/osapi-006-osapi-baseline/tasks.md +++ /dev/null @@ -1,170 +0,0 @@ -______________________________________________________________________ - -## description: "Task list for the osapi baseline" - -# Tasks: A baseline for osapi - -**Input**: Design documents from `components/osapi/specs/006-osapi-baseline/` - -**Prerequisites**: [spec.md](spec.md) merged (specs#169) and amended before -planning, [plan.md](plan.md) - -**Tests**: none. There is no code. What stands in is seven commands that must -reproduce their figures, a classification that must sum to 219, and a reading. - -## This list is written after the fact, and says so - -The specification merged and no plan or task list followed, which left the -feature unarchivable — the archival gate requires a `plan.md`. Six other -features reached osapi's memory while this one sat at stage 1, including the -embedded UI, whose own specification depends on the classification this baseline -produced. - -So the tasks below were written knowing their answers. That makes them weaker -than a task list written in advance, and it does not make them worthless: every -count here was **re-measured** while writing them rather than copied from the -specification, and the classification was re-summed. What a late list cannot do -is catch a mistake before it reaches the statement — which is exactly what -happened to the page classification, recorded in T006. - -**Nothing lands in the osapi repository.** T012 verifies that. - -______________________________________________________________________ - -## Phase 1: Setup - -- [x] T001 Confirm the specification on `main` is the amended one: - `grep -c 'FR-019' spec.md` must find the UI-divergence requirements, which - were added before planning. A plan written against the unamended text would - describe a feature that no longer exists. - - **Confirmed.** FR-019 through FR-021 are present; they record the 263-line - second statement at `ui/docs/architecture.md`, which the page classification - did not reach because it counted published pages. - -______________________________________________________________________ - -## Phase 2: Foundational — the re-measurement - -**This phase is the verification.** Every command comes from FR-012. A figure -that has moved is recorded as a new measurement with its date, not worked -around. - -Run from `~/git/osapi-io/osapi`. - -- [x] T002 The Go counts — FR-012: - `find . -name '*.go' -not -path './.git/*' | wc -l` → `2739`, and the same - with `-not -name '*_test.go'` → `1814`. - -- [x] T003 The two page counts, which differ by a **definition** — FR-012 and - FR-013: `find docs/docs -name '*.md' -not -path '*/node_modules/*' | wc -l` → - `219`, and `find docs -name '*.md' -not -path '*/node_modules/*' | wc -l` → - `221`. Both must be stated with what each counts. A reader given only one will - conclude the other is broken. - -- [x] T004 The structural counts — FR-012: provider categories `6`, API domains - `24`, SDK methods `117`. The domain count needs its exclusions — `gen`, - `mocks`, `common`, `apierr` — or it reads high. - -- [x] T005 [P] Confirm the dependency edges from the source rather than from a - list: `grep -oE "osapi-io/[a-z-]+" go.mod` for what osapi consumes, and the - same in `osapi-orchestrator/go.mod` for what consumes it. FR-005 and FR-006. - - **Phase 2 result, 2026-09-30.** All seven figures reproduced exactly. Nothing - had moved since the specification was written. - -______________________________________________________________________ - -## Phase 3: User Story 1 — somebody learns what osapi is (Priority: P1) - -**Goal**: memory answers what the repository is before it answers what was -decided about it. - -- [x] T006 Confirm the classification of all 219 pages sums to 219 and lists - every page exactly once — FR-018. **This is the check that already failed - once**: the first attempt summed to 217, because `usage/configuration.md` and - `intro.md` sit outside the subdirectory counts the table was built from. Two - pages missing from a 219-page classification is invisible to every other check - in this list. -- [x] T007 [US1] Confirm section 4 is **mostly citation** — FR-009 through - FR-011. osapi's contract is stated across four archived features, and a - baseline restating any of it creates the second statement the programme exists - to end. Check that each part of the contract names where it lives rather than - saying it again. -- [x] T008 [US1] Confirm sections 1 and 2 answer what the repository is and - where it sits without requiring another baseline to be read first. osapi is - the hub: five other components state their edges against its section 2, so a - section 2 that assumes the reader has read them is circular. - -______________________________________________________________________ - -## Phase 4: User Story 2 — the architecture survives a rename (Priority: P1) - -- [x] T009 [US2] Confirm section 3 states what each part is **for** and what - passes between parts, and contains no transcribed call graph and no exhaustive - list of exported functions — 002's FR-004 through FR-006. The test is whether - a rename would falsify a sentence. -- [x] T010 [US2] Confirm the four gaps name both sides and an owner — FR-014 - through FR-017 — and that none is corrected here. The three surviving - contributor pages are the substantive one, and FR-016 states that the finding - changes the programme's arithmetic from eleven units to twelve. - -______________________________________________________________________ - -## Phase 5: User Story 3 — the pages that stayed are an operator's (Priority: P2) - -- [x] T011 [US3] Confirm the classification's 216 "stays" are user-facing and - its 3 "moves" are the contributor pages FR-014 names. A page classified - wrongly either strands contributor knowledge on the site or moves an - operator's reference into the corpus. -- [x] T012 Confirm nothing changed in the inventoried repository: - `git -C ~/git/osapi-io/osapi status --porcelain` is empty of changes this - feature made. A baseline that edited what it was describing would have - measured its own change. - -______________________________________________________________________ - -## Phase 6: Verification and archival - -- [x] T013 Run `cd specs && mise exec -- just test` — SC-007. -- [x] T014 Run `speckit-archive-run specs/006-osapi-baseline` once this branch - has merged. osapi's memory is **not** empty — it holds 1,840 lines from six - archived features — so this run folds rather than seeds, and section 4's - citations must not be expanded into statements while merging. -- [x] T015 Record in `system`'s 002 that this baseline is archived, and leave - T023 open: it names three pages, `007` moved two, and the `sdk/guidelines.md` - remainder has no feature yet. - -______________________________________________________________________ - -## What this list does not check - -Stated because a green list here is weaker evidence than a green list usually -is: - -- **Whether the classification is right page by page.** It sums to 219 and each - page appears once; whether each verdict is correct is a review. -- **Whether section 4's citations are complete.** A rule of osapi's contract - that none of the four archived features states would not show up as a gap, - because nothing points at its absence. -- **Whether the architecture survives a rename.** Only re-reading section 3 - after a refactor settles that, which is why US2's test is a reading rather - than a command. - -______________________________________________________________________ - -## Dependencies & Execution Order - -- **Phase 2** blocks everything: until the figures are reproduced, every later - task compares prose against prose. -- **T006 blocks Phase 5**, because a classification that does not sum cannot be - checked for correctness. -- **Phase 6** needs this branch merged. - -## Notes - -- No Go code changes. No change of any kind in osapi. -- The specification was amended once before planning, adding FR-019 through - FR-021 for the UI divergence. Those three are what `007` was opened against. -- This baseline found the three pages the corpus backfill never looked at, which - is the finding that made the programme twelve units rather than eleven. diff --git a/history/osapi-007-the-embedded-ui/checklists/requirements.md b/history/osapi-007-the-embedded-ui/checklists/requirements.md deleted file mode 100644 index c49e489..0000000 --- a/history/osapi-007-the-embedded-ui/checklists/requirements.md +++ /dev/null @@ -1,81 +0,0 @@ -# Specification Quality Checklist: The embedded UI - -**Purpose**: Validate specification completeness and quality before proceeding -to planning - -**Created**: 2026-09-29 - -**Feature**: [spec.md](../spec.md) - -## Content Quality - -- [ ] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [ ] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [ ] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [ ] No implementation details leak into specification - -## Notes - -Four items fail by design, as they have for every corpus feature here. The -subject is how a repository is built, so an inventory that named no files would -be unfalsifiable. - -## What makes this move different from the backfill's four - -**It reconciles three statements, not two.** The backfill compared a site page -against the code. Here two prose documents describe the same architecture and -have already diverged — 264 lines on the site last touched 2026-08-15, 263 lines -beside the code last touched 2026-09-02, each holding a section the other never -got. - -**That is why the order matters more than usual.** Relocating only the site page -would leave the divergent copy as the sole statement by default, and that copy -is the one missing `Configuration` and `Embedding Mechanism`. Doing half of this -reaches a worse state than doing none. - -## Three decisions worth reviewing - -**FR-012 takes the union rather than choosing a winner.** All three unshared -sections survive: `Feature flags` from the copy beside the code, `Configuration` -and `Embedding Mechanism` from the site page. Picking the newer file would have -lost two sections; picking the site page would have lost one. None of the three -is a claim the other copy contradicted — each describes something that exists — -so no adjudication was needed, and FR-013 records that the shared sections' -differences are punctuation rather than disagreement, so a later reader does not -go looking for a decision nobody made. - -**FR-016 keeps a file beside the code, as a pointer.** Every other document in -this programme either moves or stays. `ui/docs/architecture.md` becomes a -two-line pointer instead, because a contributor working in `ui/` looks there -first and an absent file sends them searching. Its location is its value; its -content is not. - -**FR-017 adds nothing to the `add-a-domain` skill.** A domain's UI work is not -part of adding a domain today, and a citation invented for work nobody does is -the rule invented to fill a template that `global/correction` warns against. - -## The split, and its precedent - -`ui.md` is a split, not a move — about 60 lines of 264 stay. That is the shape -`system-architecture.md` took: what an operator configures and sees stays, how -it is built goes. `ui-development.md` moves entire, the shape -`adding-an-api-domain.md` took. Both precedents are in this project's own -memory, which is the argument for having written them down. diff --git a/history/osapi-007-the-embedded-ui/data-model.md b/history/osapi-007-the-embedded-ui/data-model.md deleted file mode 100644 index f9c0c7a..0000000 --- a/history/osapi-007-the-embedded-ui/data-model.md +++ /dev/null @@ -1,96 +0,0 @@ -# Data Model: The embedded UI - -**Feature**: `007-the-embedded-ui` | **Date**: 2026-09-29 - -Phase 1. Three documents, three dispositions. The line ranges live in -[plan.md](plan.md); what replaces each file lives here, concretely enough that -implementation invents nothing. - -## The three documents - -| Document | Now | After | Disposition | -| ------------------------------------------------- | --------- | ------------- | ----------------------------- | -| `docs/docs/sidebar/architecture/ui.md` | 264 lines | 80 | Split — operator's half stays | -| `docs/docs/sidebar/development/ui-development.md` | 200 lines | a short index | Moves entire | -| `ui/docs/architecture.md` | 263 lines | under 10 | Replaced by a pointer | - -## What the surviving site page contains - -`architecture/ui.md`, about 80 lines, four parts and no fifth: - -**The introduction**, one sentence of editing. It currently introduces a -contributor's document; afterwards it introduces an operator's, and says where -the architecture went. - -**`## Configuration`** unchanged — `controller.ui.enabled`, its default, and -that the UI shares the API's host and port so there is no additional network -configuration. - -**`## Authentication & Authorization`**, its opening and the RBAC model. The -opening names the shared JWT; the model names three built-in roles and the -`resource:verb` permission shape. The auth *flow* goes, because how the client -handles a token is not something an operator acts on. - -**`## Pages`** unchanged — Dashboard, Configure, Roles, Enrollment, SignIn, and -what each shows. - -What it must **not** contain afterwards: the stack, the component kinds, the -embedding mechanism, the application structure, SDK generation, or the auth -flow. Those are the 184 lines that move. - -## What the contributor index contains - -`development/ui-development.md`, replaced. Three parts, the shape -`adding-an-api-domain.md` took: - -1. **What developing the UI involves** — under ten lines of prose. Names that - the UI is a React SPA embedded in the controller binary, that its client is - generated, and that its conventions are stated in the corpus. -2. **A citation table** into this feature's requirements, by absolute GitHub - address, because the corpus is a separate repository and is not published as - part of the site. One sentence says so, as the domain index does. -3. **A pointer to the justfile** for the commands, rather than the commands. - FR-009 and `global/documentation`: a rule a tool enforces is not restated as - prose. - -## What the pointer contains - -`ui/docs/architecture.md`, under ten lines. It says what it is, where the -statement lives, and — explicitly — that it is a pointer rather than a summary, -so the next person to open it does not start adding paragraphs. - -```markdown -# Architecture - -The UI's architecture is stated in the osapi-io specifications repository, not here: - - -This file is a pointer, not a summary. It exists because a contributor working in `ui/` -looks for architecture beside the code, and an absent file sends them searching. Adding -an account of the architecture here would put it in two places again, which is what the -statement above replaced. -``` - -That last paragraph is the guard [research.md](research.md) Decision 2 names: a -pointer is a file somebody can edit back into a document, and the only thing -preventing it is the file saying so. - -## The corpus statement - -This feature's `spec.md`, archived into `.specify/memory/` like every other -feature's. No new subject file, and no `add-a-domain` reference — FR-017 and -[research.md](research.md) Decision 4. - -Where it reaches osapi's permission model it cites rather than restates, because -`resource:verb` and the three roles are osapi's and already stated. Where it -reaches the generated client it cites [005](../005-building-a-domain/spec.md)'s -account of the combined specification, since the UI generates from the same -file. - -## Entities - -- **Site page**: a published page, classified by who reads it. Two are affected. -- **Pointer**: a file kept for its location rather than its content. One, and - the programme's only. -- **Unshared section**: a section one former copy held and the other did not. - Three, all carried forward. diff --git a/history/osapi-007-the-embedded-ui/plan.md b/history/osapi-007-the-embedded-ui/plan.md deleted file mode 100644 index 30dcd93..0000000 --- a/history/osapi-007-the-embedded-ui/plan.md +++ /dev/null @@ -1,146 +0,0 @@ -# Implementation Plan: The embedded UI - -**Branch**: `007-the-embedded-ui` | **Date**: 2026-09-29 | **Spec**: -[spec.md](spec.md) - -**Input**: Feature specification from -`components/osapi/specs/007-the-embedded-ui/spec.md` - -## Summary - -The specification states 17 requirements about the embedded UI. Turning them -into landed work is mostly subtraction across three files, and the plan's job is -to fix the boundaries so the split is reviewable rather than decided in a diff. - -One correction to the specification's own estimate: it says "roughly 60 lines -stay of 264". Measured against the headings, **80 stay and 184 move**. The -estimate was made before the ranges were counted; the ranges are below and -reconcile to 264. - -This is the same two-repository sequence the backfill established — corpus -first, then osapi — with one difference that makes the order matter more than -usual. Two prose documents state this architecture today and they have diverged. -Relocating only the site page would leave the other as the sole statement, and -it is the one missing two sections. - -No Go code changes. `ui/` is touched only for the documentation file it carries. - -## Technical Context - -**Language/Version**: None. Markdown in two repositories. - -**Primary Dependencies**: None new. - -**Storage**: N/A. - -**Testing**: `just test` here — mdformat, just-fmt, -`scripts/validate-skills.py`. `just docusaurus-fmt-check` and -`just docusaurus-build` in osapi, where the build fails on a link left pointing -at removed content. Neither checks whether the split left a coherent page; that -is review. - -**Target Platform**: The corpus, the published site, and one file beside the -code. - -**Project Type**: Documentation. - -**Constraints**: `architecture/ui.md` keeps its address and must read as an -operator's page afterwards, not as a remainder. `ui/docs/architecture.md` keeps -its address as a pointer — FR-016, and the one file in the programme that -survives beside the code. No requirement may restate what -[001](../001-provider-contract/spec.md), [004](../004-job-system/spec.md) or -[005](../005-building-a-domain/spec.md) already states, and the UI's permission -model cites rather than repeats osapi's. - -**Scale/Scope**: Three files. 184 lines move from one, 200 from another, 263 are -replaced by a pointer. One corpus statement. Two pull requests. - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -| Principle | How this feature satisfies it | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Documentation** | This is the principle the feature serves, in its sharpest form yet: the same architecture is stated in two prose documents that have diverged. One statement, and citations, is the whole deliverable. | -| **Verification** | Every requirement cites the file it describes. Two came from reading the code rather than either page — the client-side decode without verification, and `/ui/` sitting in `.coverignore` — and a reader who trusted the prose would have had neither. | -| **Tooling** | Nothing provisioned. FR-009 deliberately does not transcribe commands, because a rule a tool enforces is not restated as prose. | -| **Correction** | The divergence is recorded with what each copy held and when each was touched, rather than merged away. FR-012 takes the union rather than choosing a winner, and FR-013 says the shared differences were punctuation so nobody looks for a decision that was never made. | -| **Workflow** | Stage 3 for `007`; tasks follow in the same branch, then one pull request per repository. | - -**Result**: no violations. - -## Project Structure - -### Documentation (this feature) - -```text -components/osapi/specs/007-the-embedded-ui/ -├── spec.md # merged; 17 requirements -├── plan.md # This file -├── research.md # Phase 0: the four decisions the spec left open -├── data-model.md # Phase 1: every line range, and what replaces it -├── quickstart.md # Phase 1: how to verify the move -├── checklists/ -│ └── requirements.md # From stage 1 -└── tasks.md # Phase 2 output -``` - -### Content - -```text -specs/ # this repository -└── components/osapi/specs/007-the-embedded-ui/ - └── spec.md # the statement of record - -osapi/ -├── docs/docs/sidebar/ -│ ├── architecture/ui.md # 264 -> 80, operator's half -│ └── development/ui-development.md # 200 -> a short index -└── ui/docs/architecture.md # 263 -> a pointer -``` - -**Structure Decision**: no new corpus subject file. The statement is `spec.md`, -archived into memory like every other feature's. The `add-a-domain` skill gains -nothing — FR-017, because a domain's UI work is not part of adding a domain -today. - -## The split, by line range - -`architecture/ui.md`, 264 lines, measured against its headings on `0cca62060`. -Ranges are given so a reviewer checks the boundary rather than reconstructing it -— the lesson from Subject A, whose split was correct and whose boundaries had to -be inferred. - -| Lines | Section | Disposition | -| ------- | -------------------------------------------- | --------------------------------------------------------------------------- | -| 1–11 | Frontmatter, title, introduction | **Stays**, with one sentence of editing so it introduces an operator's page | -| 12–53 | `## Embedding Mechanism` | Moves — FR-006 | -| 54–68 | `## Configuration` | **Stays** — FR-002, the one rule here an operator acts on | -| 69–102 | `## Application Structure` | Moves — FR-004 | -| 103–114 | `## Tech Stack` | Moves — FR-003 | -| 115–153 | `## Component Architecture` | Moves — FR-004 | -| 154–159 | `## Authentication & Authorization`, opening | **Stays** — names the shared JWT, which an operator needs | -| 160–175 | `### Auth flow` | Moves — FR-007 | -| 176–190 | `### RBAC model` | **Stays** — three roles and their permissions, which an operator configures | -| 191–231 | `## SDK Generation`, `### Fetch mutator` | Moves — FR-005 | -| 232–264 | `## Pages` and its five subsections | **Stays** — what each screen shows | - -**80 stay, 184 move**, and 80 + 184 = 264. - -Removing 160–175 leaves the authentication opening running straight into the -RBAC model, which reads. Removing 191–231 leaves the RBAC model followed by -`## Pages`, which also reads. The only edit the surviving page needs beyond -deletion is its introduction, which currently introduces a contributor's -document. - -`development/ui-development.md` moves entire, all 200 lines, leaving a short -index — the shape `adding-an-api-domain.md` took. - -`ui/docs/architecture.md` is replaced by a pointer of under ten lines. Its 263 -lines are not moved twice: everything it holds is either in the site page's -moving half or is `Feature flags`, which FR-012 carries forward from this copy -specifically. - -## Complexity Tracking - -> No Constitution Check violations, so this table is empty. diff --git a/history/osapi-007-the-embedded-ui/quickstart.md b/history/osapi-007-the-embedded-ui/quickstart.md deleted file mode 100644 index 0ebc89b..0000000 --- a/history/osapi-007-the-embedded-ui/quickstart.md +++ /dev/null @@ -1,122 +0,0 @@ -# Quickstart: verifying the move - -**Feature**: `007-the-embedded-ui` | **Date**: 2026-09-29 - -Phase 1. Four of these are commands. The two that matter most are not, and this -file says so rather than letting a green build stand in for them. - -## Prerequisites - -```bash -cd ~/git/osapi-io && ls -d specs osapi -``` - -## SC-006 — the gates - -```bash -cd specs && mise exec -- just test -cd ../osapi && mise exec -- just docusaurus-fmt-check && mise exec -- just docusaurus-build -``` - -The site build fails on a link left pointing at removed content, which is what -catches a cross-reference into a moved section. It does **not** catch a page -that reads as a remainder; that is review. - -## SC-002 — one statement, not three - -The check the whole feature exists for. - -```bash -cd ~/git/osapi-io && grep -rniE "react 19|tailwind|class-variance|orval" \ - osapi/docs/docs osapi/ui/docs specs/components/osapi 2>/dev/null | grep -v node_modules -``` - -Expected: hits in the corpus statement, and **nothing** in `osapi/ui/docs` or -under `osapi/docs/docs`. The stack is the sharpest probe because it is the -section both former copies held and neither needed. - -```bash -cd ~/git/osapi-io && grep -rn "Embedding Mechanism\|Component Architecture\|Fetch mutator" \ - osapi/docs/docs osapi/ui/docs 2>/dev/null -``` - -Expected: nothing. Those three headings are the moved half by name. - -## SC-004 — the pointer is a pointer - -```bash -cd ~/git/osapi-io/osapi && wc -l ui/docs/architecture.md -``` - -Expected: under 10. If this grows later, it has stopped being a pointer and the -programme has two statements again — which is why the file says so in its own -text. - -## SC-003 — the surviving page is an operator's page - -```bash -cd ~/git/osapi-io/osapi && grep -E "^## " docs/docs/sidebar/architecture/ui.md -``` - -Expected exactly: `## Configuration`, `## Authentication & Authorization`, -`## Pages`. Three headings, and none of the six that moved. - -Then read it. The command proves the sections; only a reader proves it holds -together. The introduction is the part most likely to be wrong, because it -currently introduces a contributor's document and deletion alone will not fix -that. - -## SC-007 — no Go changed - -```bash -cd ~/git/osapi-io/osapi && git diff --stat main | grep -vE "\.md" || echo "markdown only" -``` - -Expected: `markdown only`. - -## SC-001 — the reading - -Not a command, and the one that answers whether the corpus statement is any -good. - -Give a reader who has seen neither UI page the corpus statement alone — no -repository, no site, no session context — and three questions: - -1. How does the UI reach a user's browser? -2. Where does a new component go? -3. What does the UI verify about a token? - -Each would mislead a contributor if unanswered. The first invites assuming a -separate deployment. The second is the boundary a new file is placed against. -The third is the one to watch: the client decodes the token and does **not** -verify it, and a reader who learned only "decodes" would take that for a check. - -**What a pass proves.** That the answers are in the text. Not that a person -would enjoy finding them, and an agent is more patient than a contributor -mid-task. Every reading in this repository has carried that caveat and found -real gaps anyway. - -## SC-005 — the divergence survived being resolved - -```bash -cd specs && grep -c "Feature flags\|Embedding Mechanism\|Configuration" \ - components/osapi/specs/007-the-embedded-ui/spec.md -``` - -All three unshared sections must appear in the corpus statement. The point is -not that they are mentioned but that **none was lost to the merge**: -`Feature flags` came from the copy beside the code, the other two from the site -page, and a merge that picked either copy as authoritative would have dropped -one or two of them. - -## Definition of done - -| # | Check | Passes when | -| ------ | --------------- | ------------------------------------------------------- | -| SC-001 | The reading | Three questions answered from the corpus alone | -| SC-002 | One statement | Stack and moved headings appear only in the corpus | -| SC-003 | Operator's page | Three headings, and it reads as a whole | -| SC-004 | The pointer | Under 10 lines | -| SC-005 | Union preserved | All three unshared sections present | -| SC-006 | Gates | `just test`, `docusaurus-fmt-check`, `docusaurus-build` | -| SC-007 | No code | Diff is markdown only | diff --git a/history/osapi-007-the-embedded-ui/research.md b/history/osapi-007-the-embedded-ui/research.md deleted file mode 100644 index d693545..0000000 --- a/history/osapi-007-the-embedded-ui/research.md +++ /dev/null @@ -1,113 +0,0 @@ -# Research: The embedded UI - -**Feature**: `007-the-embedded-ui` | **Date**: 2026-09-29 - -Phase 0. Four things the specification left to planning, each decided with its -reason. - -## Decision 1: the union, and why it needed no adjudication - -**Decision**: all three unshared sections are carried into the corpus statement -— `Feature flags` from `ui/docs/architecture.md`, `Configuration` and -`Embedding Mechanism` from the site page. No section is dropped and no copy is -declared authoritative. - -**Rationale**: the two copies diverged by *addition*, not by contradiction. Each -gained a section describing something that exists; neither states a rule the -other denies. So there is nothing to adjudicate, and the cost of choosing is -asymmetric and avoidable: taking the newer file loses two sections, taking the -site page loses one, taking both loses nothing. - -What would have required a decision is a shared section whose substance -differed. None does — FR-013 records that their shared sections differ in -punctuation and capitalisation, which is what a copy looks like shortly before -it stops agreeing at all. Recording that is the point: a reader who finds the -phrasing difference logged as a conflict would go looking for a resolution -nobody made. - -**Alternatives considered**: declaring the newer file authoritative, on the -reasoning that recency tracks accuracy. Rejected — recency tracks *editing*, and -the site page was not edited because nobody remembered it existed, which says -nothing about whether `Configuration` is still true. It is: -`controller.ui.enabled` is in the shipped configuration. - -## Decision 2: `ui/docs/architecture.md` becomes a pointer, not a deletion - -**Decision**: replace its 263 lines with a pointer of under ten lines to the -corpus. - -**Rationale**: every other document in this programme either moves or stays. -This one gets a third disposition because **its location is its value**. A -contributor working in `ui/` looks for architecture beside the code they are -editing, and that instinct is correct — it is why the file was created. An -absent file there sends them to search the repository; a two-line pointer does -not. - -The risk this accepts, named rather than hidden: a pointer is a file that can be -edited back into a document. Nothing prevents somebody adding a paragraph to it, -and the programme would then have two statements again. What guards against it -is the pointer saying explicitly that it is a pointer and where the statement -lives — the same device the site's contributor index uses. - -**Alternatives considered**: deleting it, consistent with the two site pages the -backfill deleted. Rejected for the reason above. Also considered: leaving it and -citing from it, which is what it already effectively does badly — it holds a -full account *and* the corpus would hold one. - -## Decision 3: two requirements came from the code, not the pages - -**Decision**: FR-007 and FR-010 state things neither prose document says, and -the plan records where they came from so a later reader can tell them from -transcription. - -**Rationale**: the UI decodes its JWT client-side **without verifying it**, -because verification is the server's job. The site page says the UI "decodes the -token client-side" with the parenthetical that it is not verification; a -contributor reading only the client code would reasonably take a decode for a -check, and the corpus states the asymmetry as a rule rather than an aside. - -And `/ui/` is in `.coverignore`, so osapi's 100% coverage gate says nothing -about the UI. Neither page mentions it. A contributor who assumed the gate -covered the UI would be wrong in a way nothing in the documentation would -correct. - -Both are the discipline `global/verification` asks for, applied to a -documentation feature: the prose was a lead, and the code was the source. - -**Alternatives considered**: leaving both out as implementation detail. Rejected -— the first is a security-shaped misreading waiting to happen, and the second -changes what a contributor believes their tests prove. - -## Decision 4: the corpus statement is `spec.md`, and the skill gains nothing - -**Decision**: no new corpus subject file and no new `add-a-domain` reference. -The statement is this feature's `spec.md`, archived into memory like every other -feature's. - -**Rationale**: the backfill's two subjects each produced a skill citation -because a contributor adding a domain reaches job delivery and domain -construction on the way. A domain's UI work is not part of adding a domain today -— no domain has UI work, and `grep` for a UI step in the skill's references -returns nothing. FR-017 therefore adds nothing, and `global/correction` is the -reason: a rule invented to fill out a template is noise, and a citation for work -nobody does is that rule. - -If UI work becomes part of adding a domain, the citation is added then, by the -change that makes it true. - -**Alternatives considered**: a `references/ui.md` in the skill, for symmetry -with the other layers. Rejected as symmetry for its own sake. - -## What was verified before writing any requirement - -| Claim | Verified by | Result | -| ----------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------- | -| The SPA is embedded in the binary | `ui/embed.go` exists | Confirmed | -| It shares the API's port | the site page, and `controller.api.port` in configuration | Confirmed | -| `controller.ui.enabled` disables it, default true | the shipped `configs/osapi.yaml` and the page's example | Confirmed | -| Four component kinds | `ui/src/components/{ui,domain,layout}/` and `ui/src/hooks/` | Confirmed | -| The client is generated from the same specification as the Go SDK | orval in the UI, `pkg/sdk/client/gen` for Go | Confirmed | -| `/ui/` is excluded from coverage | `.coverignore` line 5 | Confirmed — **stated in neither page** | -| The UI does not verify the token | the page's parenthetical, read against the client | Confirmed — **stated as an aside, not a rule** | -| 464 UI source files | `find ui/src -type f \( -name '*.tsx' -o -name '*.ts' \) \| wc -l` | Confirmed | -| The two copies diverged | `git log -1 --format=%cs` on each, and their heading sets | Confirmed — recorded in 006's FR-019 | diff --git a/history/osapi-007-the-embedded-ui/spec.md b/history/osapi-007-the-embedded-ui/spec.md deleted file mode 100644 index dbff14b..0000000 --- a/history/osapi-007-the-embedded-ui/spec.md +++ /dev/null @@ -1,311 +0,0 @@ -# Feature Specification: The embedded UI - -**Feature Branch**: `007-the-embedded-ui` - -**Created**: 2026-09-29 - -**Status**: Completed - -**Input**: The twelfth unit of the baseline programme, and the move -[osapi's baseline](../006-osapi-baseline/spec.md) classified. osapi ships an -embedded single-page application, and how it is built is stated in **two places -that have already diverged** — `docs/docs/sidebar/architecture/ui.md` on the -operator's site and `ui/docs/architecture.md` beside the code. Neither is in the -corpus. A third statement replaces both. - -## Why this is one feature and not part of the baseline - -The baseline classified; this relocates. `system`'s -[002](../../../../system/specs/002-baseline-shape/spec.md) FR-026 and FR-027 put -them in that order and keep them separate, because osapi got it the other way -round the first time: the corpus backfill moved documentation across three -features and none of them wrote the document that says which pages are -contributor-facing. These two UI pages are what that omission cost — they were -never candidates, so nothing examined them. - -**Two claims in this specification were wrong and are corrected at the bottom, -under Assumptions.** There were four copies rather than three, and the pointer -landed at ten lines rather than under ten. Read that block before trusting a -count here: the corrections are at the end because that is when they were found, -not because they matter less. - -## What makes this move different from the backfill's - -The backfill reconciled a site page against the code. This reconciles **three -statements**, and the third is the one that makes the order matter. - -| Statement | Lines | Last touched | Holds, that the others do not | -| -------------------------------------- | ----- | ------------ | -------------------------------------- | -| `docs/docs/sidebar/architecture/ui.md` | 264 | 2026-08-15 | `Configuration`, `Embedding Mechanism` | -| `ui/docs/architecture.md` | 263 | 2026-09-02 | `Feature flags` | -| `development/ui-development.md` | 200 | — | the whole of how to develop the UI | - -Relocating only the site page would leave the copy beside the code as the sole -statement by default — and that copy is the one missing `Configuration` and -`Embedding Mechanism`. Doing half of this reaches a worse state than doing none -of it. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - The UI's architecture is stated once (Priority: P1) - -Somebody changing the UI reads how it is built in one place, and what they read -is not contradicted by a second document they did not know existed. - -**Why this priority**: two statements exist today and they have diverged. Every -day that holds, the chance grows that somebody reads the stale one. - -**Independent Test**: after the change, one statement of the UI's architecture -exists across the corpus, the site and the repository; every other mention is a -citation. - -**Acceptance Scenarios**: - -1. **Given** the corpus, **When** a contributor asks how the SPA reaches the - binary, **Then** the embedding mechanism is stated with the file that - implements it. -2. **Given** the repository, **When** somebody looks for UI architecture beside - the code, **Then** they find a pointer to the corpus rather than a second - account. - -______________________________________________________________________ - -### User Story 2 - An operator keeps what they use (Priority: P1) - -Somebody running osapi can still turn the UI off, and still find out what each -screen shows and which role sees it. - -**Why this priority**: three sections of the site page are an operator's, and a -move that took them would cost a reader their reference to save a contributor a -click. - -**Independent Test**: the surviving page answers how to disable the UI, what -each page shows, and what the three built-in roles permit — and reads as a whole -page rather than a remainder. - -**Acceptance Scenarios**: - -1. **Given** the site after the change, **When** an operator asks how to disable - the UI, **Then** `controller.ui.enabled` and its default are still there. - -______________________________________________________________________ - -### User Story 3 - The divergence is recorded, not silently resolved (Priority: P2) - -Somebody reading the corpus later can tell that two documents disagreed, which -sections each was missing, and which way the disagreement was settled. - -**Why this priority**: the Correction principle. A merge that quietly picked a -winner would leave no trace that a rule had been in two places, and the next -reader would have no reason to check. - -### Edge Cases - -- **A section only one copy has.** Three exist: `Feature flags` in the file - beside the code, `Configuration` and `Embedding Mechanism` on the site. Each - is carried forward on its own merits, and the corpus records which copy it - came from — a union, not a choice of winner. -- **A section both have, worded differently.** Their shared sections agree in - substance and differ in punctuation and capitalisation. The corpus states the - substance once; the wording difference is evidence of copying rather than a - disagreement to resolve. -- **An operator section inside a contributor page.** `ui.md` is a split, not a - move. This is the shape `system-architecture.md` took: what an operator - configures and sees stays, how it is built goes. -- **A page that is wholly contributor.** `ui-development.md` is. It moves - entire, the shape `adding-an-api-domain.md` took, and its address keeps a - short index. - -## Requirements *(mandatory)* - -Every requirement is *the corpus MUST state X*. No Go code changes; `ui/` is not -touched except for the documentation file it carries. - -### What the UI is, and how it reaches the user - -- **FR-001**: The corpus MUST state that osapi ships a single-page application - embedded in the controller binary, served from the same host and port as the - REST API, so enabling it adds no network configuration. Evidence: - `ui/embed.go`, and the `controller.api.port` it shares. -- **FR-002**: The corpus MUST state that the UI is disabled by - `controller.ui.enabled: false`, that the default is true, and that when - disabled the controller skips registering the SPA handler and serves only the - REST API. This is the one rule in the set an **operator** acts on, and it - stays on the site as well — cited there, stated here. - -### How it is built - -- **FR-003**: The corpus MUST state the stack at the level of what each part is - for rather than as a version list: React with TypeScript for the application, - Vite to build it, Tailwind for styling, React Router for navigation, and orval - to generate the API client from the same OpenAPI specification the Go SDK - uses. Versions are evidence and date; the roles do not. -- **FR-004**: The corpus MUST state the four kinds of component and what - separates them — primitives, domain components, layout, and hooks — because - that boundary is the one a contributor has to place a new file against. **What - separates them is what each one knows**, not what it is called. A primitive - knows no osapi resource: none of the 34 files in `ui/src/components/ui/` - imports the generated client. A domain component knows exactly one: 38 of the - 43 files in `ui/src/components/domain/` import the client or a hook. Layout - knows none and holds the page's chrome. A hook holds state or fetches data and - renders nothing — every one of the 14 files in `ui/src/hooks/` is `.ts` rather - than `.tsx`, so none of them can contain markup. A new file goes where its - knowledge puts it: markup with no resource is a primitive, markup with one - resource is a domain component, a resource with no markup is a hook. Reproduce - with `cd ui/src && grep -rl "sdk/" components/ui | wc -l` (expect 0), - `grep -rl "sdk/\|hooks/" components/domain | wc -l` (expect 38 of 43), and - `ls hooks | sed 's/.*\.//' | sort -u` (expect `ts`). -- **FR-005**: The corpus MUST state that the UI's API client is **generated from - the same specification as the Go SDK**, so an endpoint added to a domain - reaches both, and that a fetch mutator adapts it to the browser. This is the - fact that makes the UI part of osapi rather than a separate application. -- **FR-006**: The corpus MUST state the embedding mechanism: the built assets - are compiled into the Go binary, which is why there is no separate deployment. - Evidence: `ui/embed.go`, `ui/dist/`. -- **FR-007**: The corpus MUST state how the UI authenticates — the same JWT the - rest of osapi uses — and MUST state the asymmetry plainly: the UI decodes the - token client-side **without verifying it**, because verification is the - server's job. A contributor who reads only the client would otherwise take the - decode for a check. -- **FR-008**: The corpus MUST state that the UI's permission model is osapi's, - not a second one: three built-in roles and `resource:verb` permissions - matching the Go model. Where it reaches what those permissions mean, it MUST - cite rather than restate. - -### Developing it - -- **FR-009**: The corpus MUST state the UI's development obligations — where the - dev server runs, how a production build is produced, the component and - file-naming conventions, and what regenerating the SDK requires — as the - contract a contributor obeys rather than as a transcript of commands. Commands - belong in the justfile, and `global/documentation` says a rule a tool enforces - is not restated as prose. -- **FR-010**: The corpus MUST state that `ui/` is excluded from the coverage - gate by `.coverignore`, so the UI's correctness rests on its own checks rather - than on Go coverage. Evidence: `/ui/` in `.coverignore`. A contributor who - assumed the gate covered it would be wrong in a way nothing would tell them. - -### The divergence, recorded - -- **FR-011**: The corpus MUST record that the UI's architecture was stated twice - and that the two copies had diverged before this feature, naming what each - held that the other did not and when each was last touched. Evidence: - [006's FR-019](../006-osapi-baseline/spec.md). -- **FR-012**: The corpus MUST state that the three unshared sections were - carried forward as a **union rather than by choosing a winner**: - `Feature flags` from the copy beside the code, `Configuration` and - `Embedding Mechanism` from the site page. Picking the newer file would have - lost two sections; picking the site page would have lost one. -- **FR-013**: The corpus MUST state that the shared sections agreed in substance - and differed in punctuation, and that this is evidence of copying rather than - a disagreement requiring judgement. A reader who finds the phrasing difference - recorded as a conflict would look for a decision that was never needed. - -### What happens to the four documents - -- **FR-014**: `docs/docs/sidebar/architecture/ui.md` MUST be **split**: - `Configuration`, `Pages` and the RBAC model stay and must read as a whole page - for an operator; the embedding mechanism, application structure, stack, - component architecture, auth flow and SDK generation go to the corpus. Roughly - 60 lines stay of 264. -- **FR-014a**: `docs/docs/sidebar/features/management-dashboard.md` MUST lose - its `## Architecture` section's account of the stack and the embedding - mechanism, which restates FR-003 and FR-006. What an operator acts on in that - section stays: that API endpoints are prefixed `/api/`. **This document was - missed by the classification below.** It was found by SC-002's grep after the - move had merged, which is the check working rather than the plan working. -- **FR-015**: `development/ui-development.md` MUST move **entire**, its address - keeping a short contributor index with a citation table — the shape - `adding-an-api-domain.md` took. -- **FR-016**: `ui/docs/architecture.md` MUST be **replaced by a pointer** to the - corpus rather than deleted. It sits where a contributor working in `ui/` will - look first, and an absent file there sends them to search; a two-line pointer - does not. This is the one place in the programme where a file beside the code - survives as a pointer, and the reason is that its location is its value. -- **FR-017**: The `add-a-domain` skill MUST NOT gain a UI reference. A domain's - UI work is not part of adding a domain today, and inventing a citation for - work nobody does would be the rule invented to fill a template that - `global/correction` warns about. - -### Key Entities - -- **Embedded UI**: A single-page application compiled into the controller binary - and served from the REST API's port. -- **Component kind**: One of four — primitive, domain, layout, hook — the - boundary a new file is placed against. -- **Generated client**: The UI's API access, produced from the same OpenAPI - specification as the Go SDK. -- **Divergent copy**: One of two statements of the same architecture, each - holding a section the other lacked. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A reader who has seen neither UI page answers three questions from - the corpus alone: how does the UI reach a user's browser, where does a new - component go, and what does the UI verify about a token? Each would mislead a - contributor if unanswered — a separate deployment assumed, a file placed - wrongly, a client-side decode taken for a check. -- **SC-002**: One statement of the UI's architecture exists. `grep` for the - stack, the component kinds and the embedding across the site, `ui/docs/` and - the corpus returns the corpus statement and citations, not a second account. -- **SC-003**: The surviving site page answers how to disable the UI, what each - screen shows, and what the three roles permit, and reads as a whole page. -- **SC-004**: `ui/docs/architecture.md` is a pointer of under ten lines. -- **SC-005**: Both disagreements between the two former copies are recorded with - what each held, and all three unshared sections survive in the corpus. -- **SC-006**: `just test` in the specs repository, and - `just docusaurus-fmt-check` with `just docusaurus-build` in osapi. -- **SC-007**: No Go code changes. `git -C osapi diff --stat` touches only - markdown. - -## Assumptions - -- The corpus statement merges before the osapi change, the sequence - [003's research](../003-corpus-backfill/research.md) fixed: corpus first - leaves a window where both state the rules, and site first leaves one where - neither does. -- The union of the unshared sections is correct without adjudication. All three - describe things that exist — feature flags, the enable switch, the embedding — - so none is a claim the other copy contradicted. -- `ui/`'s own `AI_POLICY.md` is policy rather than architecture and is out of - scope. -- Counts were measured on `b003df6` in specs and `0cca62060` in osapi. The line - counts will date; what the sections are will not. - -**Corrected 2026-09-29, after the implementation merged.** Two of this feature's -own claims were wrong, and both were found by SC-002 and SC-004 rather than by -re-reading this file. They are corrected here, ahead of archival, because a -specification archived with a wrong inventory records the error as knowledge — -the treatment [003](../003-corpus-backfill/spec.md) gave its three. - -| This said | It is | Found by | -| -------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------- | -| Three documents state the architecture | **Four.** `features/management-dashboard.md` held a fourth account of it | SC-002's grep, run after osapi#549 merged | -| `ui/docs/architecture.md` is under ten lines | **Ten**, until tightened. The bare corpus address wraps onto its own line | SC-004's `wc -l` | - -The SC-001 reading found a third, and it was not a count. **FR-004 required the -corpus to state what separates the four kinds of component and then did not -state it.** It named the four and gave the directories, which is a requirement -about a requirement: a reader learned there are four buckets and where they sit -on disk, not how to decide which bucket a new file belongs in. That is the one -question of the three the reading could not answer, and FR-004 now carries the -separation, measured from the code. - -Worth recording about the reading itself: it also reported that this correction -block reads as an appendix and is easy to stop short of, so a reader can leave -with the wrong count. A pointer to it now sits under the summary. The reading -found in one pass what re-reading the file three times did not, which is the -argument for SC-001 being a reading by somebody who has seen nothing else. - -The first count is the one worth naming. The classification read the two pages -titled for the UI and the file beside the code, and stopped — a page in -`features/` describing a feature for operators was not where a fourth statement -of the stack was expected. It restated React, Vite and `//go:embed`, which -FR-003 and FR-006 now state, and it did so eight lines above a sentence this -feature edited. Being in the file was not enough; only the grep was. - -This is the argument for SC-002 being a grep across a directory rather than a -list of files. A classification enumerates what somebody thought of; a grep -finds what is there. diff --git a/history/osapi-007-the-embedded-ui/tasks.md b/history/osapi-007-the-embedded-ui/tasks.md deleted file mode 100644 index 6f2501a..0000000 --- a/history/osapi-007-the-embedded-ui/tasks.md +++ /dev/null @@ -1,320 +0,0 @@ -______________________________________________________________________ - -## description: "Task list for the embedded UI" - -# Tasks: The embedded UI - -**Input**: Design documents from `components/osapi/specs/007-the-embedded-ui/` - -**Prerequisites**: [spec.md](spec.md) merged (specs#172), [plan.md](plan.md), -[research.md](research.md), [data-model.md](data-model.md), -[quickstart.md](quickstart.md) - -**Tests**: none. There is no code. What stands in is `docusaurus-build` failing -on a broken link, three greps, and a reading. - -## Two repositories, and the order - -The sequence the backfill established, and here the order matters more than -usual. - -| Pull request | Repository | Tasks | -| ------------ | ---------- | --------------------------- | -| 1 | `specs/` | T001–T004 | -| 2 | `osapi/` | T005–T013 | -| — | `specs/` | T014–T016, after both merge | - -**Why the order matters more here.** Two prose documents state this architecture -and they have diverged. If the osapi change landed first, the corpus would not -yet hold the statement and `ui/docs/architecture.md` would have been reduced to -a pointer aimed at nothing. Worse, if only *part* of the osapi change landed — -the site page but not the file beside the code — the divergent copy becomes the -sole statement, and it is the one missing `Configuration` and -`Embedding Mechanism`. **Doing half of the second pull request is worse than -doing none of it**, which is why T012 exists. - -______________________________________________________________________ - -## Phase 1: Setup - -- [x] T001 Confirm the line ranges in [plan.md](plan.md) against - `osapi/docs/docs/sidebar/architecture/ui.md` as it stands: `12–53`, `54–68`, - `69–102`, `103–114`, `115–153`, `154–159`, `160–175`, `176–190`, `191–231`, - `232–264`. They must sum to 264 and each boundary must land on its heading. A - range that no longer matches means the page moved under the plan — record the - new range rather than working around it, which is how 004's Finding 2 was - caught. - - **Confirmed against the live file.** All ten boundaries land on their heading - — line 12 `## Embedding Mechanism`, 54 `## Configuration`, 69 - `## Application Structure`, 103 `## Tech Stack`, 115 - `## Component Architecture`, 154 `## Authentication & Authorization`, 160 - `### Auth flow`, 176 `### RBAC model`, 191 `## SDK Generation`, 232 `## Pages` - — the ranges are contiguous, and they total 264. Nothing moved under the plan. - -______________________________________________________________________ - -## Phase 2: Foundational (Blocking Prerequisites) - -- [x] T002 SC-005: confirm all three unshared sections are in the merged - `spec.md` before any file is touched: `Feature flags` (from - `ui/docs/architecture.md`), `Configuration` and `Embedding Mechanism` (from - the site page). **This is the task that makes the union real.** If one is - missing and the osapi change proceeds, that section exists nowhere afterwards - — the corpus does not hold it and both copies are gone. - - **Passes.** All three are in the merged statement: `Feature flags`, - `Configuration` and `Embedding Mechanism`. Nothing can be lost by the - deletions in Phase 4 — the union is real before any file is touched, which is - the whole point of running this first. **Checkpoint**: nothing can be lost by - the deletions that follow. - -______________________________________________________________________ - -## Phase 3: User Story 1 — the architecture is stated once (Priority: P1) - -**Goal**: the corpus holds one statement, so the two copies have something to be -replaced by. - -**Independent test**: SC-002's greps, run after Phase 4. - -- [x] T003 [US1] Confirm the corpus statement cites rather than restates where - it reaches osapi's permission model — `resource:verb` and the three roles are - osapi's and already stated — and where it reaches the generated client, which - shares [005](../005-building-a-domain/spec.md)'s combined specification. - - **Confirmed.** FR-008 states the permission model is osapi's — three roles and - `resource:verb` — and says where it reaches what those permissions mean it - cites rather than restates. FR-005 cites 005's combined specification for the - generated client rather than describing generation again. - -- [x] T004 [US1] Run `cd specs && mise exec -- just test`, then open and merge - the specs pull request. **Nothing in osapi may be touched before this merges** - — the corpus statement is what the pointers point at. - - **Already satisfied by specs#172.** For a corpus feature the statement is - `spec.md`, which merged at stage 1 — there is no separate corpus pull request - to open. See the correction note below Phase 5. **Checkpoint**: the corpus - states it. Three documents still state it too, which is the bounded window the - backfill's sequencing accepts. - -______________________________________________________________________ - -## Phase 4: User Story 2 — an operator keeps what they use (Priority: P1) - -**Goal**: the site keeps what an operator configures and sees, and the duplicate -statements go. - -**Independent test**: SC-003 and SC-004 from [quickstart.md](quickstart.md). - -- [x] T005 [US2] Remove lines `12–53`, `69–153`, `160–175` and `191–231` from - `osapi/docs/docs/sidebar/architecture/ui.md` — the embedding mechanism, - application structure, stack, component architecture, auth flow, SDK - generation and fetch mutator. 184 lines of 264. Carries FR-003 through - FR-007's move, and FR-014's split. - -- [x] T006 [US2] Rewrite that page's introduction, one sentence, so it - introduces an operator's page and says where the architecture went. **Deletion - alone will not fix it**: the current introduction introduces a contributor's - document, and a page whose first paragraph promises architecture and then - shows three operator sections reads as damaged rather than as reduced. - -- [x] T007 [US2] Confirm the surviving page's headings are exactly - `## Configuration`, `## Authentication & Authorization`, `## Pages` — SC-003's - grep — and that the authentication opening now runs into the RBAC model, and - the RBAC model into `## Pages`, without a bridging paragraph. Both joins were - checked in [plan.md](plan.md) and read. - -- [x] T008 [US2] Replace `osapi/docs/docs/sidebar/development/ui-development.md` - with the three-part contributor index from [data-model.md](data-model.md): - under ten lines of prose, a citation table by absolute GitHub address, and a - pointer to the justfile rather than the commands — FR-015, with FR-009 for why - the commands stay in the justfile. One sentence must say why the links are - absolute — the corpus is a separate repository and is not published as part of - the site. - -- [x] T009 [US2] Replace `osapi/ui/docs/architecture.md` with the pointer from - [data-model.md](data-model.md), under ten lines. It **must** include the - sentence saying it is a pointer rather than a summary: a pointer is a file - somebody can edit back into a document, and that sentence is the only thing - guarding against it — [research.md](research.md) Decision 2. FR-016. - -- [x] T010 [US2] [P] Search the site for any link into a removed section: - `grep -rn "ui.md#" docs/docs` and - `grep -rn "ui-development" docs/docs docusaurus.config.ts`. A link to a moved - anchor survives deletion and fails the build. - -- [x] T011 [US2] Run - `cd osapi && mise exec -- just docusaurus-fmt-check && mise exec -- just docusaurus-build` - — SC-006 — and confirm the diff is markdown only, which is SC-007. - -- [x] T012 [US2] Confirm **all three** files are in the same commit before - opening the pull request: the split page, the contributor index, and the - pointer. Splitting them across changes leaves the divergent copy as the sole - statement for as long as the gap lasts, and that copy is the one missing - `Configuration` and `Embedding Mechanism`. - -- [x] T013 [US2] Open and merge the osapi pull request. **This is the task that - ends the divergence.** Until it lands, three documents state the same - architecture and two of them disagree. - - **Merged as osapi#549**, in one commit carrying all three files plus two - sentences elsewhere that described what the moved sections held. T010 found no - broken anchor but three descriptions that resolve and lie — - `management-dashboard.md` sent a reader to `architecture/ui.md` "for the - embedding mechanism, component layers, and SDK generation flow", all three of - which had just moved. A link that works while describing content that is gone - is the same drift in smaller form and the build cannot catch it, so they were - corrected in the same commit. - - Two measured deviations from the plan, both small and both recorded rather - than absorbed. The surviving page is **82 lines, not 80**: the `[orval]` link - definition sat at line 264, inside the range the plan marked as staying, while - its only reference was at 194 inside the moving SDK Generation section, so - removing the orphan took two lines the plan had counted as staying. And the - first attempt at the pointer passed the Docusaurus gates and failed - `md-fmt-check`: `ui/docs/` sits outside `docs/**`, so it is mdformat's file - rather than Prettier's. This repository has two markdown formatters divided by - path, and a change touching both needs both run. - - **The divergence did not end here.** SC-002's grep found a fourth copy - afterwards, removed at osapi#550. See T014. - -______________________________________________________________________ - -## Phase 5: Verification and archival - -- [x] T014 [P] Run SC-002's greps from [quickstart.md](quickstart.md): the stack - terms and the three moved headings must appear in the corpus and nowhere under - `osapi/docs/docs` or `osapi/ui/docs`. The stack is the sharpest probe — it is - the section both former copies held and neither needed. - - **Failed on the first run, and the failure is the finding.** The stack grep - returned a hit in `features/management-dashboard.md` — a **fourth** copy of - the architecture, holding React 19, Vite and `//go:embed`. The feature - classified three documents and there were four. It sat eight lines above a - sentence this feature had just edited, so being in the file was not enough; - only the grep was. Recorded as FR-014a at specs#175 and removed at osapi#550. - SC-004 failed at the same time — the pointer was ten lines against a criterion - of under ten, because the bare corpus address takes a line of its own once - mdformat wraps at 80. Tightened to nine rather than relaxing the criterion. - - **Passes now.** The stack terms, the three moved headings and `go:embed` - return nothing under `osapi/docs/docs` or `osapi/ui/docs`; the three site - headings are exactly `Configuration`, `Authentication & Authorization` and - `Pages`; the pointer is 9 lines. - -- [x] T015 [P] Run the SC-001 reading with somebody who has seen neither UI - page, asking the three fixed questions from [quickstart.md](quickstart.md). - The third is the one to watch: a reader who learned only that the client - "decodes" the token, and not that it does not verify it, has been misled in a - security-shaped way. A person is preferred; a fresh agent given only `spec.md` - is the fallback. **Record what it proves and what it does not** — that the - answers are in the text, not that a contributor would find them pleasant. - - **Two of three, and the third is a real defect.** A fresh agent given only - `spec.md` — no repository, no site, no session context — answered question 1 - from FR-001 and FR-006, and question 3 from FR-007, both plainly. It could not - answer question 2: **FR-004 required the corpus to state what separates the - four kinds of component and then did not state it.** It named the four and - gave the directories, so a reader learned there are four buckets and where - they sit on disk, not how to decide which bucket a new file belongs in — a - requirement about a requirement. Fixed in this branch, measured from the code - rather than reasoned: a primitive imports no generated client (0 of 34), a - domain component imports the client or a hook (38 of 43), and every hook is - `.ts` rather than `.tsx` so none can hold markup. - - It also reported that the correction block reads as an appendix and is easy to - stop short of, so a reader can finish holding the wrong document count. A - pointer to it now sits under the summary. - - **What this proves and what it does not.** That the answers are in the text, - for two of three. Not that a contributor mid-task would find them — an agent - is more patient than a person — and not that the prose is good. It found in - one pass what re-reading the file did not, which is the argument for the - reading being done by somebody who has seen nothing else. - -- [x] T016 Run `/speckit-archive-run specs/007-the-embedded-ui` once T004 and - T013 have merged, and mark `system`'s 002 T023 done. Archive after the - implementation: what merged in `specs/` is the statement, and the outcome is - only true once the three documents have changed. - - **Archived 2026-09-29.** Two stories, 18 requirements, four entities, three - edge cases, four outcomes and four assumptions went into - `components/osapi/.specify/memory/`; four items folded into entries that - already generalised past the subject that wrote them. The UI was the one part - of osapi that memory said nothing about. - - `system`'s 002 T023 was **not** marked done, and that is the honest answer - rather than the tidy one. It names three pages and this feature moved two: - `sdk/guidelines.md` partly moves under 006's FR-015 and nothing has been - opened for it. Recorded at specs#176. - - One thing archival surfaced that no task looked for: **006, osapi's baseline, - has never been archived**, and cannot be — it has `spec.md` and no `plan.md` - or `tasks.md`, so it stopped at stage 1 when its specification merged as - specs#169. This feature was archived ahead of it as a result, which is out of - the ascending order archival expects. - -______________________________________________________________________ - -**A correction to Phase 3, found by the analysis pass.** T004 says to "open and -merge the specs pull request", as though the corpus statement were written -during implementation. It was not: for a corpus feature the statement **is** -`spec.md`, which merged at stage 1 as specs#172. Phase 3 therefore holds no -writing — it is verification that the merged statement says what the move -depends on, and T002 is the load-bearing one. The same was true of `004` and -`005`, and neither plan said so. - -______________________________________________________________________ - -## Dependencies & Execution Order - -### Phase dependencies - -- **Phase 1** has none. -- **Phase 2** blocks Phase 4 absolutely. T002 is what guarantees the deletions - lose nothing. -- **Phase 3** must merge before Phase 4 starts — T004 says so explicitly. -- **Phase 4** is one commit and one pull request. T012 enforces that. -- **Phase 5** needs both merged. - -### What is genuinely parallel - -- T010 alongside T005 through T009 — a different search over the same page - family. -- T014 and T015 — a grep and a reading. - -### What only looks parallel - -T005, T008 and T009 touch three different files and **must not** be split across -pull requests. T012 exists because the failure mode is not a broken build but a -silently worse state: one statement surviving, and the wrong one. - -______________________________________________________________________ - -## Implementation Strategy - -### MVP - -Phases 1 to 3 — one pull request in `specs/`. The corpus states the UI's -architecture once. Three documents still state it, which is the bounded window -rather than a broken state. - -### Then - -Phase 4 in `osapi/`, one commit, one pull request, all three files. Then Phase -5\. - -______________________________________________________________________ - -## Notes - -- No Go code changes. `ui/` is touched only for the documentation file it - carries. -- The `add-a-domain` skill gains nothing — FR-017. A domain's UI work is not - part of adding a domain today, and a citation for work nobody does is the rule - invented to fill a template. -- 80 lines stay on the site page of 264; the specification's "roughly 60" was - estimated before the ranges were counted. [plan.md](plan.md) has the measured - split. diff --git a/history/osapi-orchestrator-001-orchestrator-baseline/checklists/requirements.md b/history/osapi-orchestrator-001-orchestrator-baseline/checklists/requirements.md deleted file mode 100644 index 45e5598..0000000 --- a/history/osapi-orchestrator-001-orchestrator-baseline/checklists/requirements.md +++ /dev/null @@ -1,44 +0,0 @@ -# Specification Quality Checklist: A baseline for osapi-orchestrator - -**Purpose**: Validate specification completeness and quality before proceeding -to planning - -**Created**: 2026-09-30 - -**Feature**: [spec.md](../spec.md) - -## Content Quality - -- [ ] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [x] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [ ] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [ ] No implementation details leak into specification - -## Notes - -Three items fail by design, as every baseline's do: paths, method names and -counts are the content, and the Verification principle requires the command that -measures each one. - -One item is worth naming. **Success criteria are measurable** passes because -SC-003 states the reconciliation as a loop in both directions rather than as two -totals agreeing. Written the easy way it would have been unmeasurable while -looking rigorous, since 101 and 101 can agree while describing different sets. diff --git a/history/osapi-orchestrator-001-orchestrator-baseline/plan.md b/history/osapi-orchestrator-001-orchestrator-baseline/plan.md deleted file mode 100644 index 638b735..0000000 --- a/history/osapi-orchestrator-001-orchestrator-baseline/plan.md +++ /dev/null @@ -1,93 +0,0 @@ -# Implementation Plan: A baseline for osapi-orchestrator - -**Branch**: `001-orchestrator-baseline` | **Date**: 2026-09-30 | **Spec**: -[spec.md](spec.md) - -## Summary - -State what `osapi-orchestrator` is. Twenty requirements, and the last of the six -components to get a baseline. - -It is the first baseline written after memory stopped being a requirements list. -`global/baseline` now says memory is documentation, so this feature's archival -writes prose under seven headings rather than labelled obligations, and the five -memories written before it were converted rather than left as the odd ones out. - -**Nothing lands in the `osapi-orchestrator` repository.** - -## Technical Context - -**Language/Version**: Markdown here. The repository inventoried is Go, 81 files, -59 not tests, `go 1.26.0`. - -**Primary Dependencies**: None for the corpus. The repository depends on osapi. - -**Storage**: `components/osapi-orchestrator/specs/` for this feature, and -`.specify/memory/` for what archival consolidates into. That memory holds only a -constitution, so the archival seeds rather than folds. - -**Testing**: `just test`, which now includes `memory-check` running every count -in every memory against its command. That gate did not exist when the first five -baselines were written. - -**Target Platform**: The corpus. - -**Project Type**: Documentation. - -**Constraints**: No change to the inventoried repository. Every command in a -measurement table must stand alone, because `memory-check` runs it in isolation -and cannot resolve "the same, plus X" against the row above. - -**Scale/Scope**: The largest documentation set in the organization, 140 pages -against 81 Go files, and the only one whose coverage reconciles. - -## Constitution Check - -| Principle | How this feature satisfies it | -| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| **Documentation** | The 101 operation pages are cited rather than copied; the contract they share is stated once. | -| **Verification** | Fourteen counts with standalone commands, and the documentation mapping checked by iterating headings in both directions. | -| **Tooling** | Nothing provisioned. | -| **Correction** | Three gaps with owners, none corrected. FR-018 records that the best documentation coverage in the organization has no gate behind it. | -| **Workflow** | Stages 1 to 4 in one branch. | -| **Baseline** | The first written under the fragment's documentation rule rather than converted into it afterwards. | -| **Repositories** | The single edge verified from both ends. | -| **Tracking** | Nothing becomes an issue. | - -**Result**: no violations. - -## The measurement that needed care - -`grep -cE '^func \(o \*Orchestrator\) [A-Z].*\) \*Step \{$'` returns **2**, not -101, because most signatures in `ops.go` span three lines and the return type -sits on its own. A count taken that way and written down would have been wrong -by two orders of magnitude and would have read as precise. - -`grep -cE '^func \(o \*Orchestrator\) [A-Z]' pkg/orchestrator/ops.go` returns -101 and is what the specification carries. `grep -c ') \*Step {'` also returns -101, which is the second measurement that makes the first trustworthy. - -## What the reconciliation actually checked - -125 pages under `docs/operations/` and 101 operation methods. Those numbers do -not match, and the 24 directory indexes are the difference. - -Comparing 101 against 101 would prove nothing about whether they are the same -101, so the check iterates: for every page, take its `H1` and grep `ops.go` for -a method of that name; then for every method, grep the pages for an `H1` -matching it. Zero orphans in either direction. - -This is the first documentation set in the programme to reconcile. Three -baselines before it found prose disagreeing with code. - -## What the verification cannot check - -- **Whether the 101 operations are the right 101.** A judgement. -- **Whether the guard vocabulary is right.** Eight guards and ten predicates - exist; whether they are the right eight and ten is a judgement. -- **Whether the mapping stays one to one.** It holds today and nothing enforces - it. FR-018 records that as a gap rather than as reassurance. - -## Complexity Tracking - -> No Constitution Check violations, so this table is empty. diff --git a/history/osapi-orchestrator-001-orchestrator-baseline/spec.md b/history/osapi-orchestrator-001-orchestrator-baseline/spec.md deleted file mode 100644 index 672e7ef..0000000 --- a/history/osapi-orchestrator-001-orchestrator-baseline/spec.md +++ /dev/null @@ -1,313 +0,0 @@ -# Feature Specification: A baseline for osapi-orchestrator - -**Feature Branch**: `001-orchestrator-baseline` - -**Created**: 2026-09-30 - -**Status**: Completed, archived at specs#196. Amended 2026-09-30: FR-021 through -FR-024 add the page classification every baseline owes, which this one was -written before 002 required. - -**Input**: `osapi-orchestrator`'s `.specify/memory/` holds only a constitution. -Unit 9 of the baseline programme `system`'s -[002](../../../../system/specs/002-baseline-shape/spec.md) defines, the largest -remaining, and the last of the six components to be baselined. - -## What this specification is, and what it is not - -**Its subject is a description, not a change.** Nothing lands in the -`osapi-orchestrator` repository. - -**Prose was a lead, never a source, and here the prose held up.** This -repository carries 140 documentation pages against 81 Go files, more -documentation than code, and the reconciliation in FR-017 checked it in both -directions: 101 operation pages map one to one onto 101 operation methods, with -no orphan page and no undocumented method. That is the first documentation set -in this programme to reconcile, and it is stated as a measured result rather -than assumed. - -**It is read after osapi's.** This repository consumes osapi's SDK and nothing -consumes it, so its section 2 is written to be read after -[osapi's baseline](../../../osapi/specs/006-osapi-baseline/spec.md). - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - Somebody learns what the orchestrator is for (Priority: P1) - -Somebody arriving can state what it does that calling osapi's SDK directly would -not, and what its unit of work is. - -**Why this priority**: every one of its 101 operations wraps an SDK call. If the -wrapping adds nothing the repository has no reason to exist. - -**Independent Test**: a reader given the corpus alone states what a `Step` is, -names three ways a step can be made conditional, and says what `Run` does with -them. - -**Acceptance Scenarios**: - -1. **Given** the corpus, **When** a reader asks what the orchestrator adds over - the SDK, **Then** ordering, conditional execution, retry and multi-host - result handling are named. -2. **Given** the corpus, **When** a reader asks how a step is made conditional, - **Then** the guards and the host predicates are both described, with the - difference between them. - -______________________________________________________________________ - -### User Story 2 - A consumer of the SDK knows how deeply it reaches (Priority: P1) - -Somebody changing osapi's SDK knows that this repository's **internal** engine -imports it too, so the coupling is not confined to the public layer built to -hold it. - -**Why this priority**: equal to the first. A reader would reasonably expect -`internal/engine` to work on an abstraction. It does not, and that changes what -a rename costs. - -**Independent Test**: the specification names the files in `internal/engine` -that import `osapi/pkg/sdk/client`, and states that the public package declares -no interface that would have insulated it. - -______________________________________________________________________ - -### User Story 3 - The documentation's accuracy is known rather than assumed (Priority: P2) - -Somebody deciding whether to trust 140 pages knows their coverage was checked in -both directions. - -**Why this priority**: lower, and it is the finding most likely to be assumed -rather than measured. Three baselines before this one found prose disagreeing -with code; this one found prose that does not. - -### Edge Cases - -- **A documentation set that reconciles.** 125 pages under `docs/operations/` - decompose into 101 operation pages and 24 directory indexes, and each of the - 101 matches a method by its `H1`. The check is a loop over headings rather - than a comparison of totals, because 101 and 101 agreeing proves nothing about - whether they are the same 101. -- **An internal package that is not insulated.** `internal/engine` holds the - plan, the task and the runner, and four of its files import - `osapi/pkg/sdk/client`. The name `internal` describes visibility, not - independence. -- **A public package with no interfaces at all.** `pkg/orchestrator` declares - zero. `nats-client` and `nats-server` each declare exactly one and use it as - the seam for substituting what they wrap. This substitutes nothing. -- **Uniformity that is load-bearing.** All 101 operation methods return `*Step`. - That is what makes the guards composable across every operation rather than - per domain, and one operation returning something else would break the - property silently. - -## Requirements *(mandatory)* - -The seven section names are `system`'s 002 FR-001 names verbatim. - -### 1. What this repository is - -- **FR-001**: The corpus MUST state that `osapi-orchestrator` is a Go library - providing a declarative layer over osapi's SDK, exposing one package, - `pkg/orchestrator`. It ships no binary and has no entry point; a consumer - imports it, describes work, and calls `Run`. -- **FR-002**: The corpus MUST state the four things it adds over calling the SDK - directly, because a layer that adds nothing has no reason to exist: - **ordering** between units of work, **conditional execution** based on what - earlier work did or on what a host is, **retry**, and **multi-host result - handling** where one unit of work targets several machines and each can - succeed, fail, change or be skipped independently. - -### 2. Where it sits - -- **FR-003**: The corpus MUST state that it depends on osapi and that nothing in - the organization depends on it. It is the terminal node downstream, the only - component with an incoming Go edge and no outgoing one. Verified from both - ends. -- **FR-004**: The corpus MUST state that it pins osapi by pseudo-version commit - rather than tag, so an SDK change does not reach here until somebody bumps it. -- **FR-005**: The corpus MUST state that it also depends on `osapi-justfiles` - through an unpinned justfile fetch that appears in no `go.mod`. - -### 3. Architecture - -- **FR-006**: The corpus MUST state the two layers and what each is for. - `pkg/orchestrator` is the vocabulary a consumer writes in. `internal/engine` - executes a plan built from it. The boundary is a plan. -- **FR-007**: The corpus MUST state that the unit of work is a `Step`, that all - 101 operation methods return `*Step` and nothing else, and why that uniformity - is load-bearing. -- **FR-008**: The corpus MUST state the two kinds of conditional and the - difference between them. **Guards** ask what earlier work did: - `OnlyIfChanged`, `OnlyIfFailed`, `OnlyIfAllChanged`, `OnlyIfAnyHostFailed`, - `OnlyIfAnyHostSkipped`, `OnlyIfAllHostsFailed`, `OnlyIfAnyHostChanged`, - `OnlyIfAllHostsChanged`. **Host predicates** ask what a host is: `OS`, `Arch`, - `MinMemory`, `MinCPU`, `HasLabel`, `FactEquals`, `HasCondition`, - `NoCondition`, `Healthy`, and `MatchAll` to combine them, reaching a step - through `When` and `WhenFact`. -- **FR-009**: The corpus MUST state the rest of the `Step` vocabulary: `Named`, - `After` for ordering, `Retry`, and `OnError` with `ContinueOnError`. Fifteen - methods on `Step`. -- **FR-010**: The corpus MUST state that four files in `internal/engine` import - osapi's SDK directly, so the engine works on osapi's generated types rather - than on an abstraction. -- **FR-011**: The corpus MUST state that the repository ships 41 runnable - examples, 23 for operations and 18 for features. - -### 4. The contract - -- **FR-012**: The corpus MUST state that the contract is the exported surface of - `pkg/orchestrator`: 16 functions, 13 types and 127 methods, of which 101 are - the operations. Everything under `internal/` is not the contract, including - the parts that import osapi's SDK. -- **FR-013**: The corpus MUST state that `pkg/orchestrator` declares **no - interfaces at all**, so a consumer cannot replace the SDK underneath it and - the repository's own tests reach osapi's generated client directly. - -### 5. Measurements - -- **FR-016**: The corpus MUST state each count with the command that reproduces - it, and each command MUST stand alone rather than referring to the row above - it. Measured 2026-09-30 at `727ab40`. -- **FR-017**: The corpus MUST state that the documentation reconciles exactly - against the code, **checked in both directions**, and MUST give the check - rather than the conclusion. Every one of the 101 operation pages has an `H1` - naming a method in `ops.go`, and every one of the 101 methods has a page whose - `H1` is its name. Zero orphans either way. - -### 6. Gaps - -- **FR-014**: The corpus MUST record that `internal/engine` imports osapi's SDK, - in `bridge.go`, `plan.go`, `task.go` and their tests. Not a defect on its - face, since there may be no reason to abstract a dependency this repository - exists to consume, but it is stated nowhere and a consumer cannot infer it. - Owner: `osapi-orchestrator`. -- **FR-015**: The corpus MUST record that `pkg/orchestrator` declares no - interface, which leaves FR-014 unmitigated: there is no seam at which osapi's - client could be substituted. Owner: `osapi-orchestrator`. -- **FR-018**: The corpus MUST record that **nothing verifies the one-to-one - documentation mapping**. It holds today and was checked by hand; no gate - enforces it, so the 102nd operation can be added without a page and nothing - will say so. The repository with the best documentation coverage in the - organization has no mechanism protecting it. Owner: `osapi-orchestrator`. - -### 7. What this inventory excludes - -- **FR-019**: The corpus MUST state what it leaves out: what each of the 101 - operations does, which has its own page; what osapi's SDK does, which is - osapi's; the 14 feature pages' content; the `dist/` build output; the - repository's own contributor conventions; and whether the guard vocabulary is - the right vocabulary. -- **FR-020**: The corpus MUST state that no section of the seven was omitted and - that the section names are 002's verbatim, inherited through `nats-server`'s - baseline from `nats-client`'s from `osapi-justfiles`', which is three hops - without drift. - -### The classification this baseline owed - -- **FR-021**: The corpus MUST classify every one of the 140 documentation pages - as user-facing or contributor-facing, **by who reads it** rather than by where - it sits, which `system`'s 002 requires of every baseline and its T021 records - as owed here. Every page is user-facing, so **nothing moves**. - - | Pages | # | Reader | Why | - | -------------------- | --: | -------- | ------------------------------------------------------- | - | `docs/operations/**` | 125 | consumer | One page per operation: signature, options, idempotence | - | `docs/features/*.md` | 14 | consumer | How to write a plan with the DSL | - | `docs/README.md` | 1 | consumer | The index | - | **Total** | 140 | | **140 stay, 0 move** | - - Every page addresses somebody importing the library and writing a plan. An - operation page gives the constructor, its options table and whether the - operation is idempotent; a feature page shows the calls that compose a DAG. - Nothing tells a reader how to change the orchestrator. The sweep for the - markers that would say otherwise returns two files, and both are false - positives: `docs/README.md` points at `CONTRIBUTING.md`, and - `operations/security/user/add-key.md` says "Adding a key that already exists - returns an error", which is prose about the operation. - - ```sh - grep -rlniE 'adding a|add a new|regenerate|codegen|go generate|contribut|internal/' \ - docs --include='*.md' - ``` - -- **FR-022**: The corpus MUST record that this **contradicts 002's own - prediction**. 002's FR-027 table calls this repository's move "needed, the - largest remaining" on the strength of its 140 pages, and the classification - says there is no move at all. Page count does not predict move size: what - predicts it is whether a repository's documentation was written for the people - using it, and this one's was, uniformly. osapi had a quarter of the volume in - contributor pages and a tenth the page count. - - Owner: `system`'s 002. The prediction is wrong in a merged table and needs - correcting there rather than reinterpreted here. - -- **FR-023**: The corpus MUST record what the classification found while reading - every page, which is a disagreement about what the word "guard" means. - - | Source | Guards | Where `When` and `WhenFact` sit | - | -------------------------- | -----: | ---------------------------------- | - | `pkg/orchestrator/step.go` | 8 | "When adds a guard condition" | - | `docs/features/guards.md` | 10 | Task-level guards, in its table | - | This project's memory | 8 | Host predicates, reached by `When` | - - The code and the page agree, and memory is the outlier. Memory's distinction - is worth keeping, because a condition on what earlier steps did and a - condition on what a machine is are genuinely different things and conflating - them is the mistake it warns about. What memory got wrong is implying the code - draws the line in the same place. It does not: `step.go:269` calls `When` a - guard, and `guards.md` follows it. - - ```sh - grep -cE '^func \(s \*Step\) OnlyIf' pkg/orchestrator/step.go # 8 - grep -cE '^func \(s \*Step\) When' pkg/orchestrator/step.go # 2 - sed -n '269p' pkg/orchestrator/step.go - ``` - - Owner: this project, and its memory is corrected with this amendment rather - than after it. Memory is documentation, so a document known to be wrong is - fixed; scheduling the fix would leave the only current description of the - vocabulary disagreeing with the code it describes. - -- **FR-024**: The corpus MUST state what the classification does **not** cover: - `CONTRIBUTING.md`, `AGENTS.md` and the other convention files at the - repository root. `osapi-justfiles`' baseline settled this shape, deciding that - a file documenting the thing beside it stays beside it, and a convention file - documents the repository it sits in. 002's FR-025 asks about a `docs/` tree's - pages, and these are not pages in one. - -### Key Entities - -- **Step**: The unit of work. Every operation returns one, and guards attach to - it. -- **Guard**: A condition on a `Step` asking what earlier work did. Eight of - those; ten methods the repository itself calls guards, per FR-023. -- **Host predicate**: A condition asking what a host is. Ten, combinable. -- **Plan**: What the public package builds and the engine runs. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A reader given the corpus alone states what a `Step` is, names - three ways a step can be made conditional, and says what `Run` does. -- **SC-002**: Every count is paired with a standalone command, and running them - reproduces the values. -- **SC-003**: The one-to-one documentation mapping is verified in both - directions, by iterating headings rather than comparing totals. -- **SC-004**: The three gaps are stated with what was measured, where, and an - owner, and none is corrected here. -- **SC-005**: All seven sections present, in 002's order, under 002's names. -- **SC-006**: `just test` passes in the specs repository, `memory-check` - included. -- **SC-007**: Nothing in the `osapi-orchestrator` repository changes. - -## Assumptions - -- Measurements are of `727ab40`. Counts date; the commands survive. -- The 140 documentation pages stay where they are. FR-017 records that their - coverage is exact. -- The three gaps are recorded, not fixed. Each implies a change in - `osapi-orchestrator`: an abstraction at the engine boundary or a stated - decision not to have one, and a gate that fails when an operation has no page. -- This is the sixth and last component baselined. Whether the six read as one - set is `system`'s 002 SC-001, which this unit makes answerable rather than - answers. diff --git a/history/osapi-orchestrator-001-orchestrator-baseline/tasks.md b/history/osapi-orchestrator-001-orchestrator-baseline/tasks.md deleted file mode 100644 index d5ba0c8..0000000 --- a/history/osapi-orchestrator-001-orchestrator-baseline/tasks.md +++ /dev/null @@ -1,108 +0,0 @@ -______________________________________________________________________ - -## description: "Task list for the osapi-orchestrator baseline" - -# Tasks: A baseline for osapi-orchestrator - -**Prerequisites**: [spec.md](spec.md), [plan.md](plan.md), both in this branch - -**Tests**: none. Fourteen commands, one reconciliation loop run in both -directions, and a reading. - -## What is different about this one - -It is the last of the six, and the first written after memory became -documentation. So T015 writes prose under seven headings rather than archiving a -requirements list, and `just test` now runs `memory-check` over the result. - -Every command in a measurement table must stand alone. `memory-check` runs each -one in isolation and cannot resolve "the same, plus X" against the row above, -and six counts in earlier memories had to be rewritten for exactly that. - -**Nothing lands in the `osapi-orchestrator` repository.** T012 verifies it. - -______________________________________________________________________ - -## Phase 1: Setup - -- [ ] T001 Confirm the commit: - `git -C ~/git/osapi-io/osapi-orchestrator log --oneline -1` shows `727ab40` or - later. - -## Phase 2: The counts - -Run from `~/git/osapi-io/osapi-orchestrator`. - -- [ ] T002 Go counts: 81 files, 59 excluding tests. -- [ ] T003 The package's surface, tests excluded: 12 non-test files, 16 exported - functions, 13 exported types, 127 exported methods, **0 interfaces**. -- [ ] T004 The operation count, **and the wrong way to take it**. Confirm - `grep -cE '^func \(o \*Orchestrator\) [A-Z]' pkg/orchestrator/ops.go` returns - 101, and confirm that adding `.*\) \*Step \{$` to that pattern returns **2** - because signatures span lines. Both results matter: the second is what a - plausible-looking command produces. -- [ ] T005 [P] Documentation counts: 140 pages, 101 operation pages, 24 - directory indexes, 14 feature pages, 41 examples, 119 README lines. - -## Phase 3: User Story 1 — what the orchestrator is for (Priority: P1) - -- [ ] T006 [US1] Confirm FR-002 names the four things it adds rather than what - it exposes. A layer over an existing SDK is described by what it adds. -- [ ] T007 [US1] Confirm FR-008 states both kinds of conditional and the - difference. Guards ask what earlier work did; predicates ask what a host is. A - reader who conflates them will write `OnlyIfChanged` expecting `When`. -- [ ] T008 [US1] Run the SC-001 reading. Three questions from the corpus alone: - what is a `Step`, name three ways to make one conditional, and what does `Run` - do? **Record what it proves and what it does not.** - -## Phase 4: User Story 2 — how deeply the SDK reaches (Priority: P1) - -- [ ] T009 [US2] Confirm the four files in `internal/engine` that import - `osapi/pkg/sdk/client`, by name, and that FR-014 says the package name - describes visibility rather than independence. -- [ ] T010 [US2] Confirm `pkg/orchestrator` declares zero interfaces, and that - FR-015 connects that to FR-014: no seam means nothing could be substituted - even if somebody wanted to. -- [ ] T011 [US2] Verify the single edge from both ends, `go.mod` here and the - absence of this module in every other `go.mod`. -- [ ] T012 Confirm `git -C ~/git/osapi-io/osapi-orchestrator status --porcelain` - is empty. - -## Phase 5: User Story 3 — the documentation reconciles (Priority: P2) - -- [ ] T013 [US3] Run the reconciliation **in both directions**. For every one of - the 101 operation pages, take its `H1` and grep `ops.go` for that method. Then - for every one of the 101 methods, grep the pages for an `H1` matching it. - Expect zero orphans each way. **Comparing the two totals is not this check**: - 101 against 101 says nothing about whether they are the same 101. -- [ ] T014 [US3] Confirm FR-018 records that nothing enforces the mapping. The - repository with the best documentation coverage in the organization has no - gate behind it, and that is worth stating precisely because it reads as a - strength. - -## Phase 6: Verification and archival - -- [ ] T015 Run `cd specs && mise exec -- just test`, `memory-check` included. -- [ ] T016 Archive once this branch has merged. Memory holds only a - constitution, so the run **seeds**. Write it as documentation under seven - headings, per `global/baseline`, and run `unslop` over it before committing. - Every command in the measurement table must stand alone. -- [ ] T017 Mark `system`'s 002 as having all six components baselined, which - makes its SC-001 answerable for the first time: whether a reader who has read - none of them can read all six and state what each repository is for. Record - that it is now answerable, not that it passed. - -______________________________________________________________________ - -## Dependencies & Execution Order - -- **Phase 2** blocks everything. -- **T004 must be run both ways.** The point is the difference between 101 and 2. -- **T013** is independent of the other phases and is the longest-running task. -- **Phase 6** last; T016 needs this branch merged. - -## Notes - -- No Go code changes. No change of any kind in `osapi-orchestrator`. -- All three gaps imply a change that repository must make, and naming a change - is not making it. diff --git a/history/system-001-repository-inventory/checklists/requirements.md b/history/system-001-repository-inventory/checklists/requirements.md deleted file mode 100644 index f224199..0000000 --- a/history/system-001-repository-inventory/checklists/requirements.md +++ /dev/null @@ -1,46 +0,0 @@ -# Specification Quality Checklist: Repository inventory - -**Purpose**: Validate specification completeness and quality before proceeding -to planning **Created**: 2026-09-02 **Feature**: [spec.md](../spec.md) - -## Content Quality - -- [x] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [x] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [x] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [x] No implementation details leak into specification - -## Notes - -All items pass. No open clarifications. - -An earlier draft of this feature was four times longer and proposed -consolidating the eight `.github/repos.json` manifests into one. That was -dropped: the duplication it targeted has never drifted — the shared block is -byte-identical across all seven files — and the one drift that did occur was -per-repository data that consolidating would not have prevented. The Correction -principle asks for requirements written from evidence the repository carries, -and there was none. - -What remains is a rule naming a command, and the removal of the one document -that holds a copy of what that command returns. The artifact set is smaller to -match: no `research.md`, `data-model.md` or `quickstart.md`, because there is -one entity, no interfaces, and the verification fits in the tasks. diff --git a/history/system-001-repository-inventory/plan.md b/history/system-001-repository-inventory/plan.md deleted file mode 100644 index 3d0eba7..0000000 --- a/history/system-001-repository-inventory/plan.md +++ /dev/null @@ -1,82 +0,0 @@ -# Implementation Plan: Repository inventory - -**Branch**: `feat/repository-inventory` | **Date**: 2026-09-02 | **Spec**: -[spec.md](./spec.md) - -**Input**: Feature specification from `specs/001-repository-inventory/spec.md` - -## Summary - -Add a charter fragment stating that the osapi-io repository list comes from -`gh repo list osapi-io --no-archived --visibility public`, regenerate the -constitution, and delete `dependencies.md`. - -That file holds a hardcoded repository list, caches a graph `go.mod` already -states, and sits in memory without having been archived there — nothing in this -project has ever been archived, because no feature has merged. Deleting it -resolves all three. - -No code, no configuration, no other repository. - -## Technical Context - -**Language/Version**: N/A — Markdown and a shell command - -**Primary Dependencies**: `gh` CLI, authenticated - -**Testing**: `just test` in the specs repo covers Markdown formatting. The rule -itself is verified by reading the generated constitution and running the -command. - -**Project Type**: Documentation change in one repository. - -**Constraints**: `constitution.md` is generated from fragments. It must be -regenerated, not edited. - -**Scale/Scope**: One repository, two files. - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -| Principle | Assessment | -| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Documentation** — a rule a tool already enforces is never restated as prose | **Pass.** No tool enforces where the repository list comes from, so a rule in prose is the right form. The rule names a command rather than restating its output. | -| **Verification** — a claim is measured, not inspected | **Pass.** The command is the measurement, run each time, rather than a list someone once checked. | -| **Tooling** — a tool a repository invokes is declared where it declares its tools | **Pass.** `gh` is already required by the workflow. | -| **Correction** — a requirement is written from evidence the repository already carries | **Pass.** Two hardcoded lists exist. An earlier draft of this feature proposed consolidating `.github/repos.json`; that was dropped because the duplication it targeted has never drifted, which is the same principle applied against the author. | -| **Workflow** — design output lives under the project's `specs/` | **Pass.** Under `system/specs/001-repository-inventory/`. | - -## Project Structure - -### Documentation (this feature) - -```text -system/specs/001-repository-inventory/ -├── plan.md -├── spec.md -├── checklists/requirements.md -└── tasks.md -``` - -No `research.md`, `data-model.md`, `contracts/` or `quickstart.md`. There is one -entity, no interfaces, and the verification fits in the tasks. - -### Files changed by implementation - -```text -specs/ -├── .charter/fragments/global/repositories.md # NEW — the rule -├── .charter/manifest.yml # fragment registered -├── system/.specify/charter/state.yml # fragment composed in -├── system/.specify/memory/constitution.md # regenerated -└── system/.specify/memory/dependencies.md # DELETED -``` - -**Structure Decision**: The fragment goes in `.charter/fragments/global/` -alongside the existing five, because the rule binds every project here, not just -`system`. - -## Complexity Tracking - -No constitution violations to justify. diff --git a/history/system-001-repository-inventory/spec.md b/history/system-001-repository-inventory/spec.md deleted file mode 100644 index ed37b8f..0000000 --- a/history/system-001-repository-inventory/spec.md +++ /dev/null @@ -1,139 +0,0 @@ -# Feature Specification: Repository inventory - -**Feature Branch**: `feat/repository-inventory` - -**Created**: 2026-09-02 - -**Status**: Completed - -**Input**: One answer to which repositories are part of osapi-io, so work -spanning repositories takes that answer instead of writing its own list. - -## Context - -Nothing states where the repository list comes from, so work that spans -repositories writes its own. Two copies exist: the reproduction script in -`system/.specify/memory/dependencies.md`, and a draft CI specification since set -aside. Each was correct when written. - -`dependencies.md` has a second problem. Memory holds the consolidated output of -merged features, and nothing in this project has ever been archived — no feature -has merged. That file was hand-placed during the OpenSpec retirement in -osapi-io/specs#103 because there was nowhere else to put it. Its dependency -graph is a cached copy of what `go.mod` says, reproducible in seconds, sitting -in a directory reserved for something else. - -GitHub already holds the answer: - -```bash -gh repo list osapi-io --no-archived --visibility public -``` - -The gap is a stated rule that this is the answer, placed where work reads it. -`AGENTS.md` already requires the constitution at the start of every session, and -the constitution is generated from fragments in `.charter/`. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - Cross-repository work knows where to get the list (Priority: P1) - -Work touching more than one repository runs the command and uses its output, -rather than a list written somewhere. - -**Why this priority**: This is the feature. - -**Independent Test**: In a fresh session, the constitution names the command. - -**Acceptance Scenarios**: - -1. **Given** a session that read the constitution, **When** work needs the - repository list, **Then** the constitution names the command producing it. -2. **Given** a repository is added to the organization, **When** the command - runs, **Then** it appears with nothing edited. -3. **Given** a repository is archived or made private, **When** the command - runs, **Then** it is absent. - -______________________________________________________________________ - -### User Story 2 - The written list is removed (Priority: P2) - -`system/.specify/memory/dependencies.md` is deleted. - -**Why this priority**: A rule the repository already breaks is not a rule yet. -Deleting the file resolves both problems at once — the hardcoded repository -list, and a file occupying memory without having been archived there. - -**Independent Test**: Memory holds only the generated constitution and its -metadata. Searching the specs repository finds no hardcoded repository list. - -**Acceptance Scenarios**: - -1. **Given** `system/.specify/memory/`, **When** its contents are listed, - **Then** only `constitution.md` and `.constitution-template.json` remain. -2. **Given** any document here, **When** searched for a hand-maintained - repository list, **Then** none is found. -3. **Given** the deleted dependency graph is wanted again, **When** it is - needed, **Then** it is reproduced from `go.mod` rather than read from a - record. - -### Edge Cases - -- `.github` is returned but holds no code. Work needing only code repositories - filters at the point of use, rather than maintaining a second list. -- A private repository becomes active. `--visibility public` excludes it. - Revisit when one exists; none does. -- `gh` unauthenticated fails loudly rather than returning a short list. - -## Requirements *(mandatory)* - -### Functional Requirements - -- **FR-001**: The constitution MUST state that the osapi-io repositories are - what `gh repo list osapi-io --no-archived --visibility public` returns, and - that no document may hold a copy of that list. -- **FR-002**: The rule MUST be a charter fragment in `.charter/`, with the - constitution regenerated. `constitution.md` is generated; a direct edit is - lost at the next compose. -- **FR-003**: `system/.specify/memory/dependencies.md` MUST be deleted. It holds - a hardcoded repository list, and its dependency graph is a cached copy of what - `go.mod` already states. It also sits in memory without having been archived - there, which is the only way anything is meant to arrive. -- **FR-004**: Memory MUST contain only the generated constitution and its - metadata until a merged feature is archived into it. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A session that read the constitution produces the repository list - without being told how. -- **SC-002**: No document here holds a hand-maintained repository list. -- **SC-003**: Adding a repository requires no edit for it to be included. -- **SC-004**: `system/.specify/memory/` holds only `constitution.md` and - `.constitution-template.json`. - -## Assumptions - -- **Nothing of value is lost by deleting `dependencies.md`.** The graph - reproduces from `go.mod`. The one fact a command cannot produce — that `osapi` - once declared itself `github.com/retr0h/osapi` and was corrected in - osapi-io/osapi#446 — is in git history and in that repository. - -- **`gh` is available and authenticated.** Already required by the workflow. - -- **`.github/repos.json` is out of scope.** Those files configure one repository - each; they are not a repository list. Consolidating them was considered and - rejected — the shared block is byte-identical across all seven, so it has - never drifted, and the one drift that occurred was per-repository data that - consolidating would not have prevented. - -- **The `specs` topic drift is not fixed here.** `gh reposync --check` reports - the manifest says `spec-kit` while GitHub says `openspec`, from - osapi-io/specs#103. A one-command fix, unrelated to the repository list. - -## Reproducing the measurements - -```bash -gh repo list osapi-io --no-archived --visibility public -grep -rn "nats-client" ~/git/osapi-io/specs --include="*.md" -``` diff --git a/history/system-001-repository-inventory/tasks.md b/history/system-001-repository-inventory/tasks.md deleted file mode 100644 index ca493e9..0000000 --- a/history/system-001-repository-inventory/tasks.md +++ /dev/null @@ -1,98 +0,0 @@ -______________________________________________________________________ - -## description: "Task list for repository inventory" - -# Tasks: Repository inventory - -**Input**: Design documents from `system/specs/001-repository-inventory/` - -**Prerequisites**: plan.md, spec.md - -**Tests**: No automated test tasks. The feature adds no code. - -**Organization**: Grouped by user story. - -## Format: `[ID] [P?] [Story] Description` - -- **[P]**: Can run in parallel (different files, no dependencies) -- **[Story]**: US1 or US2 - -______________________________________________________________________ - -## Phase 1: Setup - -- [x] T001 Confirm `gh repo list osapi-io --no-archived --visibility public` - returns the eight repositories, so the rule names a command that works - -______________________________________________________________________ - -## Phase 2: User Story 1 - Cross-repository work knows where to get the list (Priority: P1) 🎯 MVP - -**Goal**: The constitution names the command. - -**Independent Test**: -`grep -i -A6 "^## Repositories" system/.specify/memory/constitution.md` shows -the rule in the generated file. - -- [x] T002 [US1] Write `.charter/fragments/global/repositories.md` stating that - the osapi-io repositories are what the command returns and that no document - may hold a copy. Match the length and voice of - `.charter/fragments/global/tooling.md` — three short paragraphs, no headings - beyond the title _(satisfies FR-001)_ -- [x] T003 [P] [US1] Add `global/repositories` to `mandatory_fragments` in - `.charter/manifest.yml` _(satisfies FR-002)_ -- [x] T004 [P] [US1] Add `global/repositories` to `fragments` in - `system/.specify/charter/state.yml` _(satisfies FR-002)_ -- [x] T005 [US1] Regenerate by running `/speckit-charter-compose` then - `/speckit-constitution` from `system/`. Do not hand-edit `constitution.md` - _(satisfies FR-002)_ -- [x] T006 [US1] Confirm `system/.specify/memory/constitution.md` contains a - `` block and that its `**Version**` - line bumped from `1.1.0` with `Last Amended` updated _(satisfies FR-002, - SC-001)_ - -**Checkpoint**: The rule is in the generated constitution and loads every -session. US1 ships on its own. - -______________________________________________________________________ - -## Phase 3: User Story 2 - The written list is removed (Priority: P2) - -**Goal**: The one document that hardcodes repository names is gone, and memory -holds only what was generated or archived into it. - -**Independent Test**: `ls -a system/.specify/memory/` shows only -`constitution.md` and `.constitution-template.json`. - -- [x] T007 [US2] Delete `system/.specify/memory/dependencies.md` _(satisfies - FR-003)_ -- [x] T008 [US2] Confirm `system/.specify/memory/` now holds only - `constitution.md` and `.constitution-template.json` _(satisfies FR-004, - SC-004)_ -- [x] T009 [US2] Confirm no hand-maintained repository list remains anywhere in - the specs repository _(satisfies SC-002)_ - -______________________________________________________________________ - -## Phase 4: Verification - -- [x] T010 Confirm adding a repository needs no edit: the command's output is - the list, and nothing stores it _(satisfies SC-003)_ -- [x] T011 Run `mise exec -- just test` and confirm `md-fmt-check` and - `just-fmt-check` are clean - -______________________________________________________________________ - -## Dependencies & Execution Order - -- Setup (T001) first -- US1 (T002–T006) next. T003 and T004 touch different files and can be done - together; T005 needs both -- US2 (T007–T009) can follow US1 or run alongside it — it touches a different - file -- Verification (T010–T011) last - -## Implementation Strategy - -US1 alone delivers the rule. US2 removes the existing violation, without which -the constitution contradicts the repository on the day it is written. diff --git a/history/system-002-baseline-shape/checklists/requirements.md b/history/system-002-baseline-shape/checklists/requirements.md deleted file mode 100644 index 250033c..0000000 --- a/history/system-002-baseline-shape/checklists/requirements.md +++ /dev/null @@ -1,119 +0,0 @@ -# Specification Quality Checklist: One shape for every component baseline - -**Purpose**: Validate specification completeness and quality before proceeding -to planning - -**Created**: 2026-09-29 - -**Feature**: [spec.md](../spec.md) - -## Content Quality - -- [ ] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [x] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [x] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [ ] No implementation details leak into specification - -## Notes - -Only two items fail here, against four in every other feature in this -repository, and the difference is worth noting: this specification's subject is -a **document shape** rather than a codebase, so it is genuinely readable by a -non-technical stakeholder and its success criteria are genuinely -technology-agnostic. Those two items pass honestly. - -The two that fail are the implementation-detail pair. This specification names -file paths — `global/workflow`, `.specify/memory/`, `osapi-justfiles` — and -repository counts, because the Verification principle requires the evidence and -because a shape defined without naming what it must fit would be untestable. -FR-011 through FR-013 exist precisely because one of the six repositories has no -Go code, and that requirement cannot be stated without naming it. - -## The correction this feature carries - -**gohai's baseline excluded architecture on purpose, and that was wrong.** Its -FR-016 stated the exclusion plainly, which is the only reason this was findable -rather than having to be inferred from a thin document. FR-003 reverses it and -FR-015 obliges the amendment, in gohai's own change rather than in this one. - -Two further decisions came from the goal rather than from the first attempt: - -- **FR-002 and FR-019 together** are what make six documents a map. Each - baseline states its own edges, and `system`'s memory holds the whole graph. - Neither alone works: a central map goes stale because nothing forces it to - match, and distributed edges never compose into a picture. -- **FR-017 and FR-018** require ordinary prose and forbid Given/When/Then in a - baseline. A feature specification is read once by a reviewer checking a - change; a baseline is read repeatedly by somebody learning a system. The - formalism that helps the first actively obstructs the second, and memory - written in it reads as a test plan rather than an explanation. - -This specification uses Given/When/Then itself, because the template requires it -of a feature. FR-017 says why that is not a contradiction: the two documents -have different readers. - -## Settled after the first review - -Two questions this specification left open are now decided, and one new finding -is recorded: - -- **`osapi` gets a baseline** (FR-021). Its five archived features record what - changed, never what the repository is, so it is the one memory that does not - conform to the shape. Six repositories, six baselines. -- **`system` does not** (FR-022), because it is not a repository. It holds the - map and the agreements instead. Without stating this, "every project gets a - baseline" reads as including it, and a baseline of a project with no code - would have to invent its subject. -- **208 doc pages have no link checking** (FR-023, FR-024). `osapi-orchestrator` - and `gohai` enforce markdown formatting only, where `osapi` builds and - link-checks its 221. The rest of the build tooling is uniform, which the - requirement states explicitly rather than leaving a reader to infer drift. - Recorded with an owner, not fixed. - -## The operation, settled - -The first version of this specification defined a document shape and said -nothing about the repositories' own documentation. That left the two things -already done pointing in opposite directions: - -| | What happened | Obeys the one-statement rule? | -| ----- | -------------------------------------------------------- | ----------------------------- | -| osapi | contributor docs **moved out**, pages deleted or reduced | yes | -| gohai | contributor docs **left in place**, cited | no — two statements | - -Under "no repository should be different" they cannot both be right, and the -rule already adopted decides it: `global/documentation` and the backfill's own -one-statement requirement mean the move is the right operation everywhere. - -What the amendment adds is the ordering, because osapi's was wrong. It was -backfilled across three features and still has no baseline — so the document -that establishes which pages are contributor-facing was never written. FR-025 to -FR-027 make the baseline come first, make it carry the classification, and make -the move its own feature. - -FR-028 is the guard against over-applying it: `gohai/docs/collectors/` is a -catalogue for people *using* the library, and gohai's own FR-004 cites it as the -maintained enumeration. Moving it would take a user's reference away and break -the citation at once. The test is who reads a page, not where it sits. - -FR-029 starts with osapi because it is the hub — `nats-client` and `nats-server` -below, `osapi-orchestrator` above. A leaf baselined first has nothing to state -its edges against. diff --git a/history/system-002-baseline-shape/contracts/section-order.md b/history/system-002-baseline-shape/contracts/section-order.md deleted file mode 100644 index 1af982f..0000000 --- a/history/system-002-baseline-shape/contracts/section-order.md +++ /dev/null @@ -1,97 +0,0 @@ -# Contract: the seven sections, and what belongs in each - -**Feature**: `002-baseline-shape` | **Date**: 2026-09-29 - -FR-001 fixes the seven sections and their order. This records what belongs in -each and, more usefully, what a writer will be tempted to put there that does -not. Nine features will be written against this; without it, "architecture" -means whatever the writer thought it meant, which is how the first baseline came -to exclude it. - -## 1. What this repository is - -**Belongs**: its purpose in a paragraph, and who consumes it — a program, an -operator, a contributor, another repository. - -**Does not**: a feature list. A reader wanting features reads the repository's -own documentation; a reader here wants to know whether they are in the right -repository at all. - -## 2. Where it sits - -**Belongs**: what it depends on, what depends on it, and what breaks in each -direction. Both directions, **even when one is empty** — `gohai` has no edges -either way, and "no edges" is information where an absent section reads as -unfinished. - -**Does not**: the whole graph. That is the map's job, held once in `system`'s -memory. A baseline states its own edges so the map has witnesses, not so the -graph is written six times. - -## 3. Architecture - -**Belongs**: the parts, what each is *for*, and what passes between them. "The -agent receives work through a queue and returns a result through a store" -survives a rename; "`handler.go` calls `processJob` which calls the provider" -does not. - -**Does not**: a transcribed call graph, a list of every exported function, or a -file-by-file walkthrough — FR-005 forbids all three. Each is wrong within a -month and each is obtainable from the code faster than from prose. - -**The test**: if a function were renamed tomorrow, would this section become -*wrong*, or merely cite a stale path? Wrong means it was written at the level -FR-004 forbids. A symbol appears as evidence for a claim, never as the claim — -FR-006. - -## 4. The contract - -**Belongs**: what a consumer may depend on. An interface and its methods, a wire -format, a set of recipe names, an exported package surface — whatever this -repository's consumers actually bind to. And what is explicitly *free to -change*, which is the half writers omit. - -**Does not**: anything already stated elsewhere in the corpus. `osapi`'s -contract is already four archived features; its baseline cites them. Restating -is the second statement the programme exists to end. - -## 5. Measurements - -**Belongs**: counts, each with the command that reproduces it — FR-007. A table -is right here; this is the one section that is genuinely tabular. - -**Does not**: a count without its command. That is a claim that was true when -somebody typed it, and nothing marks the moment it stops being true. - -## 6. Gaps - -**Belongs**: every place the repository's own prose and its code disagree, with -**both sides named and an owner** — FR-010. The baseline describes; correcting -the repository is that repository's own change. - -**Does not**: a silent correction. gohai's baseline found "65 collectors" -against 62 packages and "9 categories" against 10; recording both sides is what -let a third defect surface, because reconciling the numbers meant reading the -legend that defined a symbol twice. - -## 7. What this inventory excludes - -**Belongs**: named omissions, each with why. FR-012 requires it of any section -omitted outright, and FR-004's evergreen bound means every baseline excludes -*something* — which is why SC-004 requires this section to be non-empty in all -six. - -**Does not**: silence. An unstated omission is indistinguishable from an -oversight, and a reader who cannot tell will either duplicate the work or trust -a gap. - -## What checks this - -Nothing automatic, and saying so is part of the contract. `just test` checks -formatting and citation resolution, not whether section 3 is architecture or a -call graph. What checks that is review, and the question a reviewer asks is the -test under section 3 above. - -[quickstart.md](quickstart.md)'s reading is the indirect check: a reader asked -what a repository is *for* cannot answer it from a call graph, and cannot trace -an edge from a baseline that omitted section 2. diff --git a/history/system-002-baseline-shape/data-model.md b/history/system-002-baseline-shape/data-model.md deleted file mode 100644 index 0ed3d46..0000000 --- a/history/system-002-baseline-shape/data-model.md +++ /dev/null @@ -1,119 +0,0 @@ -# Data Model: One shape for every component baseline - -**Feature**: `002-baseline-shape` | **Date**: 2026-09-29 - -Phase 1. This feature's entities are the units of work it obliges and the two -artifacts it produces itself. What each unit must contain is fixed here so that -nine features written by whoever picks them up produce the same document. - -## What this feature writes - -| Artifact | Path | Contents | -| ----------------- | --------------------------------------------------------- | ---------------------------------------------------------------- | -| The fragment | `.charter/fragments/global/baseline.md` | 14 lines, wording fixed in [research.md](research.md) Decision 1 | -| Its registration | `.charter/manifest.yml` | one entry, so composition picks it up | -| The map | `system/.specify/memory/plan.md`, `## The Repository Map` | the graph, with the commands that produce it | -| Six constitutions | `components/*/.specify/memory/constitution.md` | recomposed, not hand-edited | - -Nothing else. The baselines are other features' work — FR-016. - -## The map, as it stands today - -Measured 2026-09-29. It will change, which is why FR-002 makes each baseline -state its own edges and why the map carries its commands rather than only its -conclusions. - -| Repository | Depends on | Depended on by | What it is | -| -------------------- | ---------------------------- | -------------------- | ---------------------------------------------------- | -| `osapi` | `nats-client`, `nats-server` | `osapi-orchestrator` | The API and the agent: manages Linux hosts over NATS | -| `osapi-orchestrator` | `osapi` | — | Drives osapi's SDK to run ordered work across hosts | -| `nats-client` | — | `osapi` | NATS client wrapper | -| `nats-server` | — | `osapi` | Embedded NATS server | -| `gohai` | — | — | SDK-first system fact collection, standalone | -| `osapi-justfiles` | — | all six, by fetch | Shared justfile modules | - -Commands: `grep -oE "osapi-io/[a-z-]+" */go.mod` for the Go edges, and -`grep -n justfiles */justfile` for the build edge. The build edge is one every -repository has and no `go.mod` records, which is why it is listed separately -rather than left to the Go graph. - -**One finding the map surfaces**: `osapi-justfiles` is fetched from -`refs/heads/main` — unpinned — by all six. `global/tooling` says a tool whose -output is committed is pinned. Recorded as FR-024's sibling; owned by those -repositories, not by this feature. - -## The eleven units - -### Unit 1 — this feature - -Produces the four artifacts above. Merges before anything else starts, because -the shape is what the rest is written against. - -### Unit 2 — `osapi`'s baseline - -The largest, and first by FR-029. A new feature under `components/osapi/specs/`. - -What makes it unusual: **osapi already has 1,840 lines of memory**, from five -archived features. The baseline does not restate any of it. Its job is the frame -that memory lacks — sections 1, 2 and 3 — and to classify the 221 documentation -pages that remain after the backfill. - -| Section | For osapi | -| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 1 What it is | The API and the agent; hosts managed over NATS; who calls it | -| 2 Where it sits | Below: `nats-client`, `nats-server`. Above: `osapi-orchestrator`. What breaks each way | -| 3 Architecture | Controller, agent, job system, providers, SDK, CLI — what each is for and what passes between them, citing but not restating `#### The job system` already in memory | -| 4 The contract | Cites the provider contract, the agent key store, the job system, building a domain — all already in memory | -| 5 Measurements | 2,739 Go files, 221 doc pages, and the rest, each with its command | -| 6 Gaps | Whatever reading the code against the remaining site pages turns up | -| 7 Excludes | Everything the five archived features already state, by citation | - -Section 4 is mostly citation, which is correct: osapi's contract *is* already -stated. A baseline that restated it would be the second statement the whole -exercise forbids. - -### Unit 3 — `gohai`'s baseline amendment - -Adds sections 2 and 3 and the classification of 68 pages. An amendment to a -merged and archived feature, so its own pull request. [research.md](research.md) -Decision 4 gives the contents. - -### Units 4, 6, 8, 10 — the four moves - -One per repository with contributor documentation still in place. Each is driven -by its baseline's classification and leaves every address resolving. - -| Unit | Repository | Pages to classify | Known to stay | -| ---- | -------------------- | ----------------- | --------------------------------------------------- | -| 4 | `gohai` | 68 | `docs/collectors/` — a consumer's catalogue, FR-028 | -| 6 | `osapi-orchestrator` | 140 | to be decided by its baseline | -| 8 | `nats-client` | 8 | to be decided by its baseline | -| 10 | `nats-server` | 5 | to be decided by its baseline | - -### Units 5, 7, 9, 11 — the four remaining baselines - -`osapi-orchestrator`, `nats-client`, `nats-server`, `osapi-justfiles`. Written -under the fragment rather than under advice, because unit 1 and unit 2 precede -them. - -`osapi-justfiles` is the one that tests FR-011 and FR-013: no Go, no doc pages, -so its contract section states recipe names and their behaviour, and its -measurements count modules rather than files. If the shape cannot be filled -there, the shape is wrong. - -## Entities - -- **Unit of work**: one feature or one amendment, in one project. Eleven of - them. -- **Baseline**: the seven-section inventory of one repository. Six exist when - the programme finishes, one of them by amendment. -- **Move**: the relocation of a repository's contributor documentation into the - corpus, driven by its baseline's classification. Four of them. -- **Classification**: the per-page verdict — user-facing or contributor-facing — - that a baseline records and a move obeys. **442** pages across the five - repositories that have any — `osapi`'s 221, `osapi-orchestrator`'s 140, - `gohai`'s 68, `nats-client`'s 8 and `nats-server`'s 5. Not 221: that figure is - `osapi`'s own page count, and also, by coincidence, the total of the four - repositories still needing a move. -- **Edge**: a dependency between two repositories, stated by both baselines and - held once in the map. diff --git a/history/system-002-baseline-shape/plan.md b/history/system-002-baseline-shape/plan.md deleted file mode 100644 index d3e6347..0000000 --- a/history/system-002-baseline-shape/plan.md +++ /dev/null @@ -1,173 +0,0 @@ -# Implementation Plan: One shape for every component baseline - -**Branch**: `002-baseline-shape` | **Date**: 2026-09-29 | **Spec**: -[spec.md](spec.md) - -**Input**: Feature specification from `system/specs/002-baseline-shape/spec.md` - -## Summary - -The specification states 29 requirements about what a component baseline -contains. This plan says what turning them into landed work consists of, and the -first useful answer is a number: **eleven units of work**, of which this feature -is one. Calling it "the baseline programme" hides that; calling it eleven makes -it schedulable. - -What this feature itself produces is small — a charter fragment and a map. -Everything else it produces is *obligation*: nine new features and one amendment -that other projects carry out, each against a shape that is fixed before they -start rather than discovered while they write. - -No code changes in any repository. Nothing in `docs/` moves in this feature -either; FR-016 and FR-027 put every move in its own feature. - -## Technical Context - -**Language/Version**: None. Markdown under `system/` and `components/`. - -**Primary Dependencies**: None new. The charter composition machinery already -exists — `.charter/manifest.yml` lists the fragments and -`speckit-charter-compose` writes them into each project's constitution. - -**Storage**: N/A. - -**Testing**: `just test` in the specs repository — mdformat, just-fmt, and -`scripts/validate-skills.py`. There is no code to unit test. The checks that -matter for this feature are not automatable and are stated in -[quickstart.md](quickstart.md): a reading by somebody who has read none of the -baselines, and a search for any contributor rule stated in two places. - -**Target Platform**: The corpus, and six project constitutions. - -**Project Type**: Documentation. - -**Constraints**: The fragment must match the voice and length of the existing -seven — they run 11 to 36 lines and each states a rule the organization arrived -at by getting it wrong first. A fragment that reads as an invention rather than -a correction is the noise `global/correction` warns about. The map must not -become a second list that drifts, which is the failure `global/repositories` -exists to prevent. - -**Scale/Scope**: One fragment. One map section. Nine new features and one -amendment obliged across six projects. 442 documentation pages to be classified -by those features, none of them by this one. - -**A number collision worth naming, because it reads as consistent and is not.** -`osapi` was measured at 221 documentation pages, and the four repositories that -still need a move total 221 as well — 68 plus 140 plus 8 plus 5. They are -different quantities that happened to share a figure. What a baseline classifies -is *every* page of its own repository, so the programme's total is the five -repositories that have any, `osapi` included. - -**Corrected after osapi's baseline.** osapi has **219** published pages, not -221: the 221 counted the Docusaurus project's own `README.md` and `SUPPORT.md`. -The programme's total is therefore **440**, and the collision this paragraph -warns about turns out to have been a coincidence between a wrong number and a -right one. Reproduce from `osapi/` with -`find docs/docs -name '*.md' -not -path '*/node_modules/*' | wc -l` for 219, and -`find docs -name '*.md' -not -path '*/node_modules/*' | wc -l` for 221. Both -still hold after the UI move, because no page was deleted — the two relocated -pages kept their addresses. Recorded here rather than silently rewritten, -because the collision is still the lesson. - -The command matters as much as the number. A first attempt at it added -`-o -name '*.mdx'` and returned 393, because the generated API reference is 174 -`.mdx` files. A count is only evidence with the command beside it, and a command -is only evidence once it has been run. - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -| Principle | How this feature satisfies it | -| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Documentation** | This is the principle the feature serves. It ends the state where the same contributor rule can sit in a repository and in the corpus, which is what SC-008 measures. The fragment is the enforceable residue; the design stays here. | -| **Verification** | The map carries the commands that produce it, so it is measured rather than asserted. FR-007's rule applies to this feature's own artifacts, not only to the baselines it governs. | -| **Tooling** | Nothing new is provisioned. The fragment composes through machinery that already exists. | -| **Correction** | The feature exists because two completed pieces of work disagreed — osapi's docs moved, gohai's did not. It records that rather than quietly picking one, and FR-026 fixes the ordering error osapi made rather than repeating it five times. | -| **Workflow** | This is stage 3 for `002`; tasks follow in the same branch. Every obligation it creates is its own feature in its own project, which is what keeps a wrong shape from being discovered six times. | - -**Result**: no violations. - -## Project Structure - -### Documentation (this feature) - -```text -system/specs/002-baseline-shape/ -├── spec.md # merged; 29 requirements, 9 outcomes -├── plan.md # This file -├── research.md # Phase 0: the six decisions the spec left open -├── data-model.md # Phase 1: the eleven units, and what each produces -├── contracts/ -│ └── section-order.md # Phase 1: the seven sections, and what belongs in each -├── quickstart.md # Phase 1: how to tell the programme worked -├── checklists/ -│ └── requirements.md # From stage 1 -└── tasks.md # Phase 2 output -``` - -### Content - -```text -specs/ -├── .charter/fragments/global/ -│ └── baseline.md # NEW — the fragment FR-014 requires -├── .charter/manifest.yml # lists it, so composition picks it up -├── system/.specify/memory/ -│ └── plan.md # NEW section: the repository map -└── components/*/.specify/memory/ - └── constitution.md # recomposed, six of them -``` - -**Structure Decision**: the fragment and the map are this feature's whole -output. The baselines are not written here — that is FR-016, and it is what -stops one wrong judgement propagating into six documents before anybody reviews -it. - -## Eleven units of work - -Stated plainly because "the baseline programme" and "the next thing" plan very -differently. [data-model.md](data-model.md) gives each one's contents. - -| # | Unit | Project | Kind | -| --- | -------------------------------------------------- | -------------------- | --------------------------------- | -| 1 | The fragment and the map | `system` | this feature | -| 2 | Baseline | `osapi` | new feature | -| 3 | Baseline amendment — sections 2, 3, classification | `gohai` | **amendment to a merged feature** | -| 4 | Move | `gohai` | new feature | -| 5 | Baseline | `osapi-orchestrator` | new feature | -| 6 | Move | `osapi-orchestrator` | new feature | -| 7 | Baseline | `nats-client` | new feature | -| 8 | Move | `nats-client` | new feature | -| 9 | Baseline | `nats-server` | new feature | -| 10 | Move | `nats-server` | new feature | -| 11 | Baseline | `osapi-justfiles` | new feature | - -`osapi` has no move — its documentation was relocated by the backfill across -three features already. `osapi-justfiles` has no move — it has no documentation -pages. `gohai` is the only amendment, because its baseline is merged and -archived. - -## Where the fragment lands in that order - -**After unit 2, before unit 5.** Neither end works: - -- **Before any baseline** binds six repositories to a shape nothing has yet been - written against. The shape is the thing being tested, and a fragment composed - into six constitutions is expensive to correct. -- **After all six** means five baselines were written against a specification - rather than a binding rule, which is exactly the state that produced gohai's - idiosyncratic first attempt. - -So `osapi`'s baseline — the first written to the shape, and the hub whose edges -every other baseline is stated against — is the proof the shape is writable. The -fragment composes once that has merged, and the remaining four baselines are -written under a rule rather than under advice. - -`gohai`'s amendment (unit 3) may run either side of the fragment: it is a -correction to an existing document rather than a new one written to the shape. - -## Complexity Tracking - -> No Constitution Check violations, so this table is empty. diff --git a/history/system-002-baseline-shape/quickstart.md b/history/system-002-baseline-shape/quickstart.md deleted file mode 100644 index 2196dd0..0000000 --- a/history/system-002-baseline-shape/quickstart.md +++ /dev/null @@ -1,138 +0,0 @@ -# Quickstart: telling whether the programme worked - -**Feature**: `002-baseline-shape` | **Date**: 2026-09-29 - -Phase 1. Two of these checks are commands. The two that matter most are not, and -this file says so rather than implying a green build settles it. - -## Prerequisites - -All repositories cloned as siblings, tools invoked through `mise`: - -```bash -cd ~/git/osapi-io && ls -d specs gohai nats-client nats-server osapi osapi-justfiles osapi-orchestrator -``` - -## SC-009 — the corpus holds together - -```bash -cd specs && mise exec -- just test -``` - -mdformat, just-fmt, and `scripts/validate-skills.py`. This checks formatting and -that every citation resolves. It does **not** check that a baseline's section 3 -is architecture rather than a call graph — nothing automatable does. - -## SC-005 — the fragment binds rather than advises - -```bash -cd specs && grep -rl "^# Baseline" components/*/.specify/memory/constitution.md | wc -l -``` - -Expected: **6**. A fragment in `.charter/fragments/` that has not been composed -is a file, not a rule. If this returns fewer than six, `speckit-charter-compose` -has not run for every project. - -## SC-002 — every count is reproducible - -For each baseline, run the commands its measurements section states and compare. - -```bash -cd specs && grep -rn '`[a-z].*|.*wc -l`' components/*/.specify/memory/spec.md | wc -l -``` - -That counts the commands present; running them is the check. A count whose -command no longer reproduces it has not failed — it has **dated**, and the -difference is the signal. What would be a failure is a count with no command -beside it. - -## SC-003 — no baseline transcribed the code - -```bash -cd specs && grep -rcE "^\s*(func|type) [A-Z]" components/*/.specify/memory/spec.md -``` - -Expected: low and explicable. A baseline citing a handful of signatures as -evidence is FR-006 working; one listing dozens is the call graph FR-005 forbids. -This is a smell test, not a gate — judgement is the check, and the question is -the one in [contracts/section-order.md](contracts/section-order.md) under -section 3. - -## SC-004 — every baseline excludes something, and says so - -```bash -cd specs && for f in components/*/.specify/memory/spec.md; do printf "%-28s %s\n" "$f" "$(grep -c "excludes\|deliberately leaves out" "$f")"; done -``` - -Expected: every row non-zero. Every baseline excludes something — FR-004's -evergreen bound guarantees it — so a zero means the omission was not stated, -which is the failure section 7 exists to prevent. - -## SC-007 — every page classified before it moved - -Not a single command, because the classification lives in the baseline and the -move lives in another feature. Per repository: the baseline's classification -lists every page under `docs/`, and the move's diff touches only pages that -classification marked contributor-facing. - -```bash -cd gohai && find docs -name '*.md' -not -path '*/node_modules/*' | wc -l -``` - -Compare against the count the baseline's classification covers. A page in the -repository and absent from the classification is the gap this check exists to -catch. - -## SC-008 — no contributor rule stated twice - -The search that distinguishes a finished programme from six inventories written -beside the documentation they were supposed to replace. - -```bash -cd ~/git/osapi-io && for r in gohai nats-client nats-server osapi osapi-orchestrator; do - printf "%-22s %s contributor-facing pages remaining\n" "$r" "$(find $r/docs -name '*.md' -not -path '*/node_modules/*' 2>/dev/null | wc -l | tr -d ' ')" -done -``` - -The count alone proves nothing — user-facing pages are supposed to remain. The -check is that every page still there is one its baseline classified -**user-facing**. Anything classified contributor-facing and still present is a -rule stated twice. - -## SC-001 — the reading - -Not a command. The procedure, and the one that answers whether any of this -worked. - -Give a reader who has seen **none** of the baselines all six, and nothing else — -no repository, no site, no session context. Ask three questions: - -1. What is each of these six repositories for? -2. Which depends on which, and what would break if one changed? -3. Where would you look to find out how one of them is built? - -A pass is all three answered from the baselines alone. Question 2 is the one -that fails if the baselines are six conforming documents that happen to share a -template rather than a set: each can be individually correct and still leave the -graph untraceable, if section 2 was filled in as a formality. - -**What a pass proves, and what it does not.** It proves the answers are in the -text. It does not prove a new contributor would enjoy reading six documents in -sequence, and an agent is more patient than a person. Every reading in this -repository has carried that caveat and found real gaps regardless — five across -the osapi backfill, three in gohai's own documentation — so it is worth running -while being honest about its limit. - -## Definition of done - -| # | Check | Passes when | -| ------ | ------------------- | ----------------------------------------------------------- | -| SC-001 | The reading | Three questions answered from the six baselines alone | -| SC-002 | Counts reproducible | Every count has its command; drift is visible, not hidden | -| SC-003 | No transcription | No baseline lists the code | -| SC-004 | Omissions stated | Section 7 non-empty in all six | -| SC-005 | The fragment binds | Composed into six constitutions | -| SC-006 | gohai amended | Its baseline carries sections 2 and 3 | -| SC-007 | Pages classified | Every page classified before any move touched it | -| SC-008 | One statement | Every page remaining is one its baseline called user-facing | -| SC-009 | `just test` | Green | diff --git a/history/system-002-baseline-shape/research.md b/history/system-002-baseline-shape/research.md deleted file mode 100644 index 4ab1805..0000000 --- a/history/system-002-baseline-shape/research.md +++ /dev/null @@ -1,181 +0,0 @@ -# Research: One shape for every component baseline - -**Feature**: `002-baseline-shape` | **Date**: 2026-09-29 - -Phase 0. The specification left six things to planning. Each is decided here -with its reason, so the task list reads as work rather than as a series of -judgement calls. - -## Decision 1: the fragment's wording - -**Decision**: a new fragment at `.charter/fragments/global/baseline.md`, listed -in `.charter/manifest.yml` so composition picks it up, reading: - -```markdown -# Baseline - -A repository's memory states what the repository is before it states what was decided -about it. What it is, where it sits among the others, how it is built, what a consumer -may depend on, what was measured and the command that measures it again, where its own -prose and its code disagree, and what the inventory leaves out. - -Memory filled only by archived features records a sequence of changes. It answers what -was decided and never what the thing is, so a reader arriving at it learns how one -mechanism works before learning what the repository is for. That is the state osapi's -memory was in after five features: 1,840 lines, no statement of purpose, no dependency, -no architecture. - -A count is written with the command that produces it. A number alone is a claim that -was true when somebody typed it, and nothing marks the moment it stops being true. -``` - -**Rationale**: it matches the existing seven in shape — the rule flatly, then -the failure it came from, then the generalisation. It matches their length: 14 -lines against their 11 to 36. And the middle paragraph names a real event rather -than a hypothetical, which is what `global/correction` requires of a -requirement: "Write a requirement from evidence the repository already carries." - -The last paragraph is deliberately a second rule rather than more context. The -counts-with-commands discipline is the one piece of the shape that is -mechanically checkable, and a fragment that omitted it would leave the most -enforceable part as advice. - -**Alternatives considered**: folding this into `global/documentation`, which -already governs where a rule is written down. Rejected — that fragment is about -not restating a rule in two places, and this one is about a document being -incomplete in a particular way. Merging them would make both vaguer. Also -considered: naming the seven sections in the fragment. Rejected — the fragment -is the enforceable residue, not a summary, and a seven-item list in a -constitution invites editing the list rather than reading the specification. - -## Decision 2: where the map lives - -**Decision**: `system/.specify/memory/plan.md`, in a new section -`## The Repository Map`. Not `spec.md`. - -**Rationale**: the split already in use across every project's memory is that -`spec.md` holds what must be true and `plan.md` holds the current implemented -state. A dependency graph is current state: it changes when a `go.mod` changes, -and no requirement is violated when it does. The *rule* that each baseline -states its own edges is a requirement and lives in `spec.md` as FR-002; the -*graph* is the state and lives in the plan. - -**How it stays true, given each baseline also states its edges.** Three things, -and the third is what makes it more than a promise: - -1. The map carries the commands that produce it — `grep osapi-io */go.mod` for - the Go edges and the justfile fetch for the build edge — so it is - re-derivable rather than remembered. -2. Each baseline states its own edges independently (FR-002), so the map has six - witnesses rather than being the only record. -3. [quickstart.md](quickstart.md) makes the disagreement checkable: the map and - the six baselines must agree, and a mismatch means one of them is stale - rather than leaving a reader to guess which. - -**Alternatives considered**: a `map.md` file of its own in memory. Rejected — -`speckit-archive-run` consolidates a known set of files, and a file outside that -set is one nothing folds into, which is how the previous system accumulated -fourteen directories nobody read. Also considered: no central map, edges only. -Rejected by FR-019 — distributed edges never compose into a picture, and a -reader wanting the graph would have to read six documents first. - -## Decision 3: eleven units, and which is which - -**Decision**: eleven, enumerated in [plan.md](plan.md). Nine new features, one -amendment, and this feature. - -**Rationale**: the arithmetic is worth stating because two of the six -repositories do not take the full pair. `osapi` needs a baseline and no move — -the backfill relocated its documentation across three features already. -`osapi-justfiles` needs a baseline and no move — it has no documentation pages -at all. So it is six baselines and four moves, not six and six, and one of the -baselines is an amendment rather than a new feature. - -**Alternatives considered**: one feature per repository, doing baseline and move -together. Rejected, and this is FR-026's whole point: osapi did the move without -ever writing the baseline, and the result is memory that never says what the -repository is. Combining them invites the same shortcut, because the move is the -visible half. - -## Decision 4: gohai's amendment is one amendment plus one feature - -**Decision**: `gohai`'s existing baseline is **amended** to add sections 2 and 3 -and the doc-page classification. Its move is a **separate new feature**. Two -units, of different kinds. - -**Rationale**: the baseline is merged and archived, so adding to it is a -correction to a merged statement — its own pull request, ahead of the work that -depends on it, which is what the Correction principle requires and what this -session has done four times already. The move is new work against that corrected -baseline, so it is a feature. - -Concretely the amendment adds: - -| To gohai's baseline | Why it is missing | -| ------------------------------------------- | ------------------------------------------------- | -| Section 2, where it sits | The shape did not exist when it was written | -| Section 3, architecture | Its FR-016 excluded architecture **deliberately** | -| The doc-page classification of all 68 pages | FR-025 did not exist | - -The second row is the one to read twice. gohai's baseline did not omit -architecture by oversight; it stated the exclusion as a decision. That is why -this was findable at all, and it is why the amendment reverses a judgement -rather than filling a blank. - -**Alternatives considered**: re-specifying gohai's baseline as a new feature and -superseding the old one. Rejected — the old one is correct about everything it -states, and superseding it would retire requirements that still hold to fix two -that are missing. - -## Decision 5: the fragment lands after the first baseline - -**Decision**: compose the fragment after `osapi`'s baseline merges, before the -remaining four are written. [plan.md](plan.md) states it; the reasoning is here. - -**Rationale**: both ends fail, and they fail differently. - -- **Fragment first** binds six repositories to a shape nothing has been written - against. The shape is the hypothesis; a constitution is where you record a - settled rule, not where you test one. Correcting a composed fragment means - recomposing six constitutions. -- **Fragment last** means five baselines were written against a specification - rather than a binding rule. That is precisely the condition that produced - gohai's idiosyncratic first attempt — there was a goal and no rule, so the - writer's judgement filled the gap and excluded architecture. - -`osapi` is the right proof because FR-029 already puts it first for a different -reason: it is the dependency hub, so its "where it sits" is what every other -baseline's edges are stated against. One baseline written to the shape shows the -shape is writable against the hardest case — 2,739 Go files and 221 -documentation pages — and the fragment then binds the remaining four. - -**Alternatives considered**: composing after two baselines, for a second data -point. Rejected as false precision: if osapi's baseline can be written to the -shape, the shape is writable, and a second one delays the rule without testing -anything new. - -## Decision 6: how anyone tells the programme worked - -**Decision**: two checks, neither automatable, both in -[quickstart.md](quickstart.md). - -**Rationale**: the outcome is not "eleven units merged". It is that the corpus -answers two questions it cannot answer today. - -- **SC-001, the reading.** Somebody who has read none of the baselines reads all - six and states what each repository is for and every dependency edge. This is - the goal in one sentence, and it is the check that a set of six conforming - documents actually composes into a map rather than six correct documents that - share a template. -- **SC-008, the search.** No contributor rule is stated in both a repository and - the corpus. This is the one-statement rule applied across repositories instead - of within one, and it is what distinguishes a finished programme from six - inventories written beside the documentation they were supposed to replace. - -**The limitation, stated rather than left implied.** The reading proves the -answers are in the text; it does not prove a person would find them pleasant to -read, and an agent reading six documents in sequence is more patient than a new -contributor. Every reading in this repository so far has carried that caveat and -found real gaps anyway — five across the osapi backfill and three in gohai's own -documentation — so it is worth running while being honest about what it -establishes. diff --git a/history/system-002-baseline-shape/spec.md b/history/system-002-baseline-shape/spec.md deleted file mode 100644 index 64c9f1b..0000000 --- a/history/system-002-baseline-shape/spec.md +++ /dev/null @@ -1,785 +0,0 @@ -# Feature Specification: One shape for every component baseline - -**Feature Branch**: `002-baseline-shape` - -**Created**: 2026-09-29 - -**Status**: Draft - -**Input**: Six repositories need a baseline and one has been written. The goal -is that somebody reads one component's memory, then the next, and understands -how the organization fits together. gohai's baseline cannot serve that goal, -because its FR-016 deliberately excludes architecture. Five more in the same -shape would make the inconsistency permanent. - -## Why this is a system feature - -Nothing currently governs what a baseline contains. The charter has seven -fragments and the nearest, `global/workflow`, states only *where* design output -goes — "a feature under the project's `specs/`, consolidated into -`.specify/memory/` when it merges" — not what a baseline holds. So the shape was -undefined, and the first one came out shaped by whoever wrote it. - -A required shape is not one repository's behaviour. It is an agreement every -component project honours, which is the test CONTRIBUTING sets under "Where a -change belongs", so it is a `system` feature. It produces a charter fragment as -its enforceable residue — FR-014. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - The set reads as one thing (Priority: P1) - -Somebody new reads each component's memory in turn. By the end they can say what -every repository is for, what depends on what, and where a change to one would -be felt in another. They get that from the baselines alone, without opening six -repositories. - -**Why this priority**: it is the goal. Six inventories that each answer a -different question are six documents; six that answer the same questions in the -same order are a map. - -**Independent Test**: a reader who has read none of them reads all of them and -states each repository's purpose and the dependency edges between them. - -**Acceptance Scenarios**: - -1. **Given** the six baselines, **When** a reader asks what a repository is for, - **Then** the answer is in the same section of each one. -2. **Given** the six baselines, **When** a reader asks what would break if a - repository changed, **Then** each baseline names what depends on it and what - it depends on, so the reader can trace the edge from either end. -3. **Given** a baseline that omits a section, **When** a reader notices, - **Then** the baseline says which section it omitted and why, so an omission - is never indistinguishable from an oversight. - -______________________________________________________________________ - -### User Story 2 - A baseline survives ordinary change (Priority: P1) - -Somebody reads a baseline six months after it was written and finds it still -true, or finds the one command that proves it is not. - -**Why this priority**: evergreen is the whole point. A baseline that transcribes -a call graph is wrong within a month, and a wrong baseline is worse than none -because it carries the authority of a specification. - -**Independent Test**: every count in a baseline is paired with a command, and -running the commands reproduces the counts or shows exactly which drifted. - -**Acceptance Scenarios**: - -1. **Given** a count in a baseline, **When** its command is run, **Then** it - produces that number or the difference is visible immediately. -2. **Given** an architectural statement in a baseline, **When** a function is - renamed or a file moved, **Then** the statement is still true, because it - states what the parts are *for* rather than transcribing their names. - -______________________________________________________________________ - -### User Story 3 - A repository that is not a Go library still fits (Priority: P2) - -Somebody baselines `osapi-justfiles`, which has no Go code at all, and the shape -accommodates it without pretending it has interfaces. - -**Why this priority**: one of the six is not a Go library. A shape that only -fits five is not a shape. - -**Independent Test**: the shape's required sections can be filled for a -repository of shared build recipes, and any section that genuinely does not -apply is omitted with a stated reason. - -**Acceptance Scenarios**: - -1. **Given** a repository with no exported code surface, **When** its baseline - is written, **Then** the contract section states what consumers actually - depend on — recipe names and their behaviour — rather than being left blank - or filled with nothing. - -### Edge Cases - -- **A repository nothing depends on and which depends on nothing.** `gohai` is - one today. Its dependency section says so explicitly rather than being - omitted: "no edges" is information, and an absent section reads as unfinished. -- **A repository whose prose is better than its code coverage.** - `osapi-orchestrator` has 140 doc pages against 81 Go files. The baseline cites - rather than duplicates, and the risk there is copying, not omitting. -- **A repository with almost no prose.** `nats-server` has 12 Go files and 5 doc - pages. Its baseline is close to the only statement that exists, which makes - the verification discipline matter more rather than less. -- **The shape changes after some baselines are written.** It already has: - gohai's predates this. A shape revision does not silently invalidate what - exists; it obliges an amendment per FR-015. -- **A section that is required but whose content is genuinely empty.** - Distinguished from "not applicable": an empty required section states that it - is empty and what that means, and only a section the repository's nature makes - meaningless may be omitted. - -## Requirements *(mandatory)* - -### The required sections - -- **FR-001**: A component baseline MUST carry these seven sections, in this - order. The order is part of the agreement: a reader moving between baselines - finds the same answer in the same place. - - | # | Section | Answers | - | --- | -------------------------------- | ------------------------------------------------------------------------- | - | 1 | **What this repository is** | Its purpose in one paragraph, and who consumes it | - | 2 | **Where it sits** | What it depends on, what depends on it, and what breaks in each direction | - | 3 | **Architecture** | The parts, what each is for, and what flows between them | - | 4 | **The contract** | What a consumer may depend on, and what is free to change | - | 5 | **Measurements** | Counts, each with the command that reproduces it | - | 6 | **Gaps** | Where the repository's own prose and its code disagree, with an owner | - | 7 | **What this inventory excludes** | Named omissions, so a gap is never mistaken for an oversight | - -- **FR-002**: Section 2, **Where it sits**, MUST name both directions — upstream - and downstream — even when one is empty, and MUST state what a change would - break in each. This is the section that turns six documents into a map, and it - is the one gohai's baseline has no equivalent of. - -- **FR-003**: Section 3, **Architecture**, MUST be present in every component - baseline. gohai's FR-016 excluded architecture on the grounds that the - contract was what mattered; that judgement was wrong for the goal and this - requirement reverses it. - -### How much architecture, so it stays evergreen - -- **FR-004**: An architectural statement MUST be at the level of **what a part - is for and what passes between parts**, not at the level of names that change. - A baseline states that an agent receives work through a queue and returns a - result through a store; it does not transcribe the call chain that implements - it. -- **FR-005**: A baseline MUST NOT contain a transcribed call graph, a list of - every exported function, or a file-by-file walkthrough. Each is wrong within a - month, and each is already obtainable from the code faster than from prose. -- **FR-006**: Where an architectural statement names a symbol or a path, it MUST - name it as **evidence for the statement** rather than as the statement itself, - so a rename dates the citation without falsifying the claim. - -### The verification discipline, as a rule rather than a habit - -These four came out of gohai's baseline. Applied there they produced three real -defects in gohai's own documentation, so they bind rather than depending on -whoever writes the next one. - -- **FR-007**: Every count in a baseline MUST be paired with the command that - reproduces it. A bare number is a claim; a number with its command is a - measurement. -- **FR-008**: A repository's own prose — its README, its `docs/`, its - `CONTRIBUTING.md` — is a **lead, not a source**. A baseline states what the - code does, and consults the prose to find disagreements. -- **FR-009**: The reading order MUST be **code first, counts by command, prose - last**. This is the reverse of the tempting order and it is load-bearing: - gohai's README said 65 collectors and 9 categories where the code had 62 and - 10, and reading the prose first would have produced an inventory stating both - wrong numbers with the code never consulted. -- **FR-010**: A disagreement between a repository's prose and its code MUST be - recorded as a **gap naming both sides and an owner**, never silently - corrected. The baseline describes; correcting the repository is that - repository's own change. - -### Fitting every repository, including the one that is not a library - -- **FR-011**: The seven sections are **required by default and omissible only - where the repository's nature makes a section meaningless** — not where it is - merely hard, and not where the writer ran out of time. -- **FR-012**: A baseline that omits a section MUST say so in section 7 and state - why. An unstated omission is indistinguishable from an oversight, which is the - failure section 7 exists to prevent. -- **FR-013**: For a repository with no exported code surface, the **contract** - section MUST state what consumers actually depend on in that repository's own - terms. `osapi-justfiles` has no Go files; what six repositories depend on is - its recipe names and their behaviour, so that is its contract. - -### What this produces, and what it obliges - -- **FR-014**: This design MUST produce a charter fragment, because the shape - binds every component from now on and a design alone binds nothing. The - fragment's rule is one line: *a component's baseline states what the - repository is, where it sits among the others, its architecture, its contract, - its measurements with the commands that reproduce them, its gaps, and its - omissions.* The reasoning stays here; the fragment is the enforceable residue. -- **FR-015**: gohai's baseline MUST be amended to match this shape, in **its own - change**, because it is merged and archived and a correction to a merged - statement is never folded into the change that discovered it. It is missing - sections 2 and 3 — where it sits, and architecture. - -### Voice - -- **FR-017**: A baseline MUST be written in **ordinary prose**. It MUST NOT use - Given/When/Then acceptance-scenario form, checklist scaffolding, or any other - testing formalism as its body. Those belong to a feature specification, which - is read once by a reviewer; a baseline is read repeatedly by somebody trying - to understand a repository, and a formalism that helps a reviewer check a - change actively obstructs a reader trying to learn a system. - - This specification itself uses Given/When/Then, because the spec template - requires it of a feature. That is not a licence for the document it describes: - the two are different kinds of writing with different readers, and conflating - them is how memory comes to read like a test plan. - -- **FR-018**: A baseline MUST read as continuous explanation rather than as a - table of fields. Tables are for what is genuinely tabular — a dependency list, - a set of counts with their commands — and prose is for everything else. A - baseline that is entirely tables states facts without saying how they relate, - which is the one thing a reader cannot get from the code. - -- **FR-017a**: FR-017 and FR-018 bind **what archival writes into - `.specify/memory/`**, not only the feature specification that produces it. - Five archivals read them as the latter and the result is that memory holds the - formalism FR-017 forbids. - - **What went wrong, precisely.** A baseline's feature specification - legitimately carries user stories with priorities, acceptance scenarios and - success criteria — that is how a change gets reviewed, and nothing here - objects to it. Archival then copied those sections into memory unchanged, so - `components/nats-client/.specify/memory/spec.md` opens with - `## User Scenarios & Testing`, holds `User Story 2 … (Priority: P1)`, and - states `SC-006: just test passes in the specs repository` — a sentence with no - meaning in a document about what `nats-client` is. The seven sections a reader - wants sit two levels below, under `### Functional Requirements`, each phrased - "the corpus MUST state that…". - - A reader who wants to know what the repository is must mentally delete "the - corpus MUST state that" from every sentence and skip past the process - furniture to reach it. That is the obstruction FR-017 describes, and it is in - the file FR-017 exists to protect. - -- **FR-017b**: Archived memory MUST take this shape, because "prose" alone was - not specific enough to prevent FR-017a: - - | File | Holds | - | ---------------- | ------------------------------------------------------------------------------------- | - | `memory/spec.md` | The **seven sections at top level**, as declarative prose. What the repository *is*. | - | `memory/plan.md` | How it was established, what was decided, and what remains unverified. How we *know*. | - - Three rules follow, and each names something a completed archival did wrong: - - 1. **Declarative, not normative.** "`nats-server` runs a NATS server inside - its consumer's process" — not "the corpus MUST state that it runs…". The - `MUST` belongs to the feature specification, where it obliges somebody to - write something. In memory the writing has happened, so the obligation is - spent and only the fact remains. - - 2. **No feature-process furniture.** User stories, priorities, acceptance - scenarios and success criteria do not reach memory. They record how a - change was reviewed; the changelog already records that the change - happened. Neither do **requirement identifiers in the body**. `FR-` labels - were kept on the first attempt, justified by site pages and skills citing - the corpus by requirement ID — and checking that showed the citations point - at *feature specifications*, 22 of them across the skills, and that - **nothing anywhere cites a memory requirement number**. The justification - was a guess about where a verified fact applied. - - 3. **Traceability is one line at the end, not a footer on every paragraph.** - Memory names the feature it came from once. A reader who wants to know - which requirement obliged a sentence reads that feature. - - 4. **The voice is the architecture documentation this programme has been - moving.** `global/baseline` carries it in full. In short: a heading names - the thing, with its path where a path helps; a statement is present tense - and made once; a design decision carries its reason beside the thing it - explains; and commentary about the document never appears — not how a fact - was found, not that a fact is important, not what an earlier version said. - Show a configuration block, a directory tree or a command where showing it - is shorter than describing it. - - The calibration is osapi's `system-architecture.md`, which explains that - the liveness probe is deliberately trivial because dependency checks there - would make orchestrators restart the process during a transient NATS outage - — a restart storm on top of the original problem — and then tells the - reader to use readiness for load balancing instead. One sentence of reason - about the system, and guidance that can be acted on. - - A reader arriving at `memory/spec.md` should be able to read straight down and - learn the system. **Seven headings in the right order is not sufficient to - pass that test**: the first attempt produced exactly that, filled with - labelled requirement bullets and citation footers, and it read as a - specification with better navigation. - -### What stitches the set together - -- **FR-019**: `system`'s own memory MUST hold the **cross-repository map**: - every repository, one line on what it is for, and every dependency edge. Each - component baseline states its own edges (FR-002), and that is what keeps them - true; the map is what lets a reader see the whole graph without reading six - documents first. - - Both are required and neither replaces the other. The map alone goes stale - because nothing forces it to match; the edges alone never compose into a - picture. Together, each baseline is checkable against the map and the map is - derivable from the baselines. - -- **FR-020**: One baseline serves as the **worked example**, and it MUST be - named in the map so a reader knows where to start. `gohai` is the one, for the - reason it was chosen to go first: it depends on nothing and nothing depends on - it, so it can be read without holding any other repository in mind. A reader - learns the shape there and then reads the rest knowing what to expect in each - section. - -### Every repository, and what `system` is instead - -- **FR-021**: Every repository MUST have a baseline, **`osapi` included**. Its - five archived features are not one: they record what changed, not what the - repository is. A reader arriving at `osapi`'s memory today finds requirements - about job delivery and provider contracts, and no statement of what `osapi` is - for or what it depends on — exactly the gap this shape exists to close. - - The two coexist. A baseline states what the repository is; archived features - state what was decided about it. Neither replaces the other, and `osapi`'s - baseline will be the largest of the six because it is the largest repository. - -- **FR-022**: `system` MUST NOT have a baseline, because it is not a repository. - It maps to no codebase, and its subject is what the repositories agree on - rather than how any one of them behaves. What it holds instead is the - cross-repository map (FR-019) and the agreements no single repository owns — - this specification being one of them. - - Stating this matters because "every project gets a baseline" would otherwise - read as including `system`, and a baseline of a project with no code would - have to invent its own subject. - -### Consistency beyond the corpus - -- **FR-023**: The corpus MUST record where the repositories' **own documentation - tooling** is consistent and where it is not, because a reader comparing two - baselines will ask why one repository's documentation is checked more - thoroughly than another's. Measured 2026-09-29: - - | Repository | Doc pages | Built | Link-checked | - | -------------------- | --------- | ------ | ------------ | - | `osapi` | 221 | yes | yes | - | `osapi-orchestrator` | 140 | **no** | **no** | - | `gohai` | 68 | **no** | **no** | - | `nats-client` | 8 | no | no | - | `nats-server` | 5 | no | no | - | `osapi-justfiles` | 0 | n/a | n/a | - - The rest of the build tooling is uniform, and the corpus MUST say so rather - than implying drift: the four Go repositories fetch the same `go`, `just` and - `md` justfile modules and run the same nine workflows. `osapi-justfiles` - differs only by having no Go, and `osapi` only by having a published site. - Both differences track a real difference in the repository. - -- **FR-024**: **Gap**: 208 documentation pages across `osapi-orchestrator` and - `gohai` have no build and no link checking — markdown formatting alone is - enforced, so a broken internal link is never caught, where `osapi`'s 221 pages - are checked by its site build. Owner: those two repositories, each in its own - change. Recorded here rather than fixed, because this feature states a - document shape and changes no repository. - -### Classifying a repository's own documentation - -The osapi backfill and gohai's baseline were **two different operations**, and -under one shape they cannot both be right. osapi's contributor documentation -moved out of its site into the corpus and the pages were deleted or reduced; -gohai's was left where it was and cited. The first obeys the one-statement rule; -the second leaves the same knowledge in two places, which is the drift -`global/documentation` forbids and the backfill existed to end. - -So the move is the right operation everywhere — and the ordering osapi used was -wrong. - -- **FR-025**: A baseline MUST classify **every page** of its repository's own - documentation as **user-facing** or **contributor-facing**, and the test is - the one the osapi backfill used: *who reads this page* — somebody using the - repository, or somebody changing it — not where the page currently sits. - - This classification belongs in the baseline rather than in a separate audit, - because the baseline is the document that establishes what the repository is - for and who consumes it. Deciding it page-by-page during a move is how a - remainder page gets produced and how user-facing content gets taken away by - accident. - -- **FR-026**: The baseline MUST come **before** the move. osapi did the reverse - — its contributor documentation was backfilled across three features and it - still has no baseline, which is the gap FR-021 exists to close. The baseline - is what tells a reader which pages are contributor-facing; running the move - first means deciding that mid-change with nothing to check the decision - against. - -- **FR-027**: Moving a repository's contributor documentation into the corpus - MUST be its **own feature**, separate from the baseline that classified it, - and MUST leave every address resolving — a short index, a redirect, or a - reduced page that reads as a whole rather than as a remainder. This is the - sequence osapi's backfill proved: the corpus statement merges first, then the - repository change that removes the duplicate. - - Two repositories therefore have two features each and one has only a baseline: - - | Repository | Doc pages | Baseline | Move | - | -------------------- | --------- | ------------------------------ | ----------------------------- | - | `osapi` | 219 | done — specs#169 | **needed** — see FR-030 | - | `osapi-orchestrator` | 140 | needed | needed, the largest remaining | - | `gohai` | 68 | exists, needs sections 2 and 3 | needed | - | `nats-client` | 8 | needed | likely small | - | `nats-server` | 5 | needed | likely small | - | `osapi-justfiles` | 0 | needed | none — nothing to move | - -- **FR-028**: A page a repository's consumers read MUST stay in that repository. - Not everything in a `docs/` tree is contributor knowledge: - `gohai/docs/collectors/` is a 65-row catalogue for people *using* the library, - and gohai's own baseline already cites it as the maintained enumeration its - FR-004 depends on. Moving it would take a user's reference away and break the - citation in the same stroke. - -- **FR-029**: The order across the six repositories MUST start with **`osapi`**, - because it is the hub of the dependency graph — `nats-client` and - `nats-server` below it, `osapi-orchestrator` above — so its "where it sits" is - what every other baseline's edges are stated against. A leaf baselined first - has nothing to point at. - -### Corrected: twelve units, not eleven - -- **FR-030**: osapi needs a **move** after all, so the programme is **twelve** - units rather than eleven. This corrects FR-027's table, which said osapi's - move was "already done, across three features". - - That was true of the six pages the corpus backfill scoped and false of three - it never looked at. osapi's own baseline found them — `architecture/ui.md` at - 264 lines, `development/ui-development.md` at 200, and `sdk/guidelines.md` at - 227\. The first two have **no corpus counterpart at all**: 464 lines of - contributor architecture on an operator's site with nowhere to cite, which is - the state the backfill existed to end. - - They survived because the backfill named six candidate pages and classified - those. Nothing examined a page that was not a candidate, and an inventory - re-checking only those six would have missed them the same way. **That is the - argument for baselining a repository that already has memory**, and it is why - FR-026 puts the baseline before the move rather than treating a completed move - as evidence that nothing remains. - -- **FR-032**: `osapi-justfiles` has **no documentation pages and six - documentation files**, and FR-023's table recording 0 for it answers only the - first half. There is no `docs/` tree, so 0 is exactly right about pages — and - misleading as an answer to "what documentation does this repository have", - because five module READMEs and a root README are its documentation. They stay - where they are, for FR-028's reason: a module's README documents the interface - of the file beside it, and moving it would separate an interface from its - description. - - Found by `osapi-justfiles`' own baseline, specs#178, and recorded there as its - FR-019 before being amended here. - -- **FR-033**: `osapi-justfiles` is depended on by **seven** repositories, not - six. FR-019's map and this specification both said six, meaning the six - components; the seventh is **`specs`**, the design record, which fetches the - `just` and `md` modules. Only `.github` has no justfile. - - **The map carried the command that would have produced the right answer.** - `grep -n justfiles */justfile`, run from the directory holding the clones, - returns `specs` among the rest. The figure beside it said six. So this is not - the failure `global/repositories` describes — a written list correct when - written and wrong afterwards — it is a list that was **wrong when written**, - because the writer's frame was the six components rather than the repositories - the command returns. Ageing was not the problem. Nobody running the command - was. - - It changes which module matters most: `md` reaches all seven, `just` six, `go` - five, and `react` and `docusaurus` one each. And it puts the design record's - own formatting gate downstream of an unpinned fetch — `specs`' `just test` - gates every corpus change in the organization, and a commit to - `osapi-justfiles` changes it. That is FR-024's existing finding reaching - further than FR-024 said. - -- **FR-034**: The programme's scope is the **six components**, and the - organization has **eight** non-archived public repositories. The two outside - the programme are `specs`, which is where components are described rather than - a component, and `.github`, which holds shared configuration and no justfile. - Neither gets a baseline, and this is stated because FR-033 shows what happens - when the difference between "the six" and "the repositories" is left implicit: - it was the frame that produced the wrong consumer count. - -- **FR-031**: `osapi`'s page count is **219 published pages**, not 221. FR-023's - table records 221, which counts `docs/README.md` and `docs/SUPPORT.md` — the - Docusaurus project's own files rather than published pages. Both figures are - right about different questions, and the programme's classification total is - therefore **440** rather than 442. - - This is the **second** number collision this feature has produced, after the - 221-against-442 one the analysis pass caught. Both had the same shape: a - figure correct for one question, quietly reused for another. Worth recording - as a pattern rather than as two incidents, because the next baseline will - offer the same opportunity. - -### Corrected: what the classifications found - -All five repositories with documentation have now been classified, at specs#169, -#205, #206 and #207. FR-027's table predicted the result and was wrong about -half of it. - -- **FR-035**: FR-027's table MUST be read with this correction. It is kept as - written because what it got wrong is the finding. - - | Repository | Pages | FR-027 predicted | Classified | - | -------------------- | ----: | --------------------- | ------------------------- | - | `osapi` | 219 | needed, see FR-030 | **2 pages move**, 1 split | - | `osapi-orchestrator` | 140 | the largest remaining | **nothing moves** | - | `gohai` | 68 | needed | **3 pages move** | - | `nats-client` | 8 | likely small | **nothing moves** | - | `nats-server` | 5 | likely small | **nothing moves** | - | `osapi-justfiles` | 0 | none, nothing to move | nothing to move | - - Three of the six rows are wrong, and the largest one is wrong by the largest - margin: 140 pages predicted to be the biggest move produce no move at all. - What actually moves in the whole programme is five pages, three from `gohai` - and two from `osapi`, plus the part of `osapi`'s `sdk/guidelines.md` that is - not a demonstration of rules the corpus already states. - -- **FR-036**: The corpus MUST state what predicts a move, since a page count - does not. What predicts it is **who the repository's documentation was written - for**, and that is a property of the tree as a whole rather than of its size. - Four of the five repositories wrote every page for the people importing the - package, and produced no move between them. `osapi` publishes a site aimed at - operators and put contributor architecture on it, which is why a tree a - quarter the size of `osapi-orchestrator`'s produced the only substantial move - in the programme. - - Stated because the wrong prediction was not a slip. It was a reasonable - inference from the only figure available before anybody read the pages, and - the lesson is that the figure does not carry the information. - -- **FR-037**: The corpus MUST record the convention the classifications made - visible, which no repository owns and nothing states. Four repositories carry - the same `docs/README.md` shape: an index table pointing at one directory per - package, opening with the same sentence about `examples/` and - `CONTRIBUTING.md`, word for word. - - ```sh - cd ~/git/osapi-io && for r in gohai osapi-orchestrator nats-client nats-server; do - grep -c 'Runnable programs live in' $r/docs/README.md - done # 1 1 1 1 - ``` - - `osapi`, the one with a published site, does not. So the organization has a - documentation layout that four repositories follow by copying each other, and - a fifth that diverges for a reason nothing records. This is a candidate for - `.charter/fragments/`, since it is a rule a repository can be measured - against, and it is left as a gap rather than written here: a fragment composed - into six constitutions is not a thing to add as a footnote to another - feature's correction. Owner: `system`, its own feature. - -- **FR-038**: This feature's **own task list** MUST agree with FR-031. FR-031 - corrected the classification total from 442 to 440 and `tasks.md` still says - 442 in two places, in T021 and in its closing figures. Corrected with this - amendment. - - The 442 came from `osapi`'s 221 plus the other four repositories' 221, a - coincidence the task list called out as a coincidence. With `osapi` at 219 the - coincidence is gone, which is the only reason the stale figure is visible at - all. A number that was interesting for being equal stops being equal when it - is corrected, and that is a better alarm than most. - -- **FR-039**: The corpus MUST record that the seven sections ask what a - repository **is** and never what it is **for**, which T017's reading found by - being unable to answer it. Six baselines and roughly 3,400 lines describe the - machine, and a reader finishes them able to say that osapi queues work to - agents and unable to say why anybody wants that. - - The answer was never missing from the organization. `osapi`'s README and the - first page of its site both say it, in the same sentence: the project - "provides basic management capabilities to Linux systems, enabling them to be - used as appliances". A classification that asks who reads a page has no - question that would notice the corpus lacking a sentence the front door - carries. - - Section 1 is the natural home and its name works against it. "What the - repository is" invites an answer about shape, and every baseline gave one. - Fixed in memory rather than in the baselines, at specs#213, because memory is - what a reader is handed: `system`'s architecture document and `osapi`'s entry - point now open with the purpose, and the repository table in `README.md` - states it above the links. - - What is left is whether section 1 should require it, which would bind six - constitutions through `.charter/fragments/global/baseline.md`. Owner: this - feature, in its own change, for the reason FR-037 gives about fragments. - -- **FR-040**: The corpus MUST record what a citation points at now that memory - is prose, because this feature changed the answer and did not say so. - - `osapi`'s 005 FR-025 requires citations "named to a requirement rather than to - a document", and 003's citation contract gives the reason: "See the job system - specification" is a pointer, "FR-007" is a citation, and only the second tells - a reader whether what they want is there. That was right when memory held - numbered requirements. This feature made memory prose with no requirement - identifiers in it, which leaves a citation with two possible targets and no - rule choosing between them. - - The count, measured 2026-09-30: - - | Citing | Links | Target | - | --------------------------------------- | ----: | ------------------------- | - | `osapi`'s published documentation pages | 31 | four of its feature specs | - - ```sh - grep -rho 'specs/blob/main/components/osapi/specs/[0-9]*-[a-z-]*' \ - --include='*.md' --include='*.mdx' osapi | sort | uniq -c - ``` - - `development/adding-an-api-domain.md` holds 16 of them as a table of `FR-001` - through `FR-024`, which is what 005's FR-026 deliberately reduced it to. It is - the shape that feature wanted and it now sends a contributor to a merged - feature specification when a current description of the same subject exists in - `architecture/domains.md`. - - Both targets are defensible and they answer different questions. A feature - spec says what was decided and when, keeps its numbers forever, and is the - right target for provenance, which is why the skills cite it. Memory says what - is true today and is the right target for somebody about to write code. What - is missing is the sentence saying which one a published page cites. - - Not decided here. Deciding it changes 31 links in `osapi` and the contract two - of its features depend on, so it is its own feature with its own review. - Owner: this project. Until then the links are correct against 005 and stale - against the shape this feature established, and that is worth knowing rather - than quietly fixing. - -- **FR-041**: The corpus MUST record what an onboarding reading of the memory - tree found, because it is the first reading of the documentation rather than - of the baselines, and it answered all five of its questions. - - The reading was given `README.md`, `system`'s two memory documents, the six - component entry points and `osapi`'s eleven subject documents, and nothing - else. Its verdict: better than the median hand-written architecture corpus, - and unusually honest. Zero em dashes across 18 files, sentence case - throughout, and the thing it named as the reason it worked is that the - documents explain a decision rather than describe a structure, several by - naming the incident that produced the decision. - - **Thirteen defects, fixed at specs#215.** The ones that mattered: - - | Defect | Where | - | ---------------------------------------------------------------------- | --------------------- | - | `audit:read` called **the** permission separating `admin` from `write` | `audit.md`; seven do | - | A count explained by "the write side is the larger half" | `permissions.md` | - | Bucket TTLs said to be per bucket; one setting covers both | `job-system.md` | - | "Five of the six either feed it or consume it"; four do | `README.md`, `system` | - | Eleven subject documents advertised as five | `README.md` | - | "Removal happens on reject and on removal" | `agent-identity.md` | - | A four-kind taxonomy whose summary names three | `ui.md` | - | British and American spelling, five files against six | across memory | - - The `permissions.md` one is worth keeping as an example of what a reading - catches and a checker cannot. "`read` at 17 of 37 is fewer than half, because - most domains expose both a read and a write and the write side is the larger - half" is three failures in one sentence: halves are equal, the stated cause - would produce parity rather than 17 against 20, and the document's own numbers - make the read side the larger group. Every number in it was correct and - checked. The sentence joining them was invented. - -- **FR-042**: The corpus MUST record the two structural findings that are not - defects and are not fixed, because each is a piece of work rather than an - edit. - - **`system`'s memory claimed a shape memory does not have.** It said every - component's memory has the same seven sections and that "the names are fixed, - they are verbatim now". No component's memory uses them. That is correct by - design, because the seven verbatim names belong to the baselines and a - document whose headings are a numbered list of sections reads as a - specification, which is what memory stopped being. The claim was the last - place in the corpus still conflating the two artifacts, which is the - conflation this whole programme has been unpicking. Reworded at specs#215 to - say the order is stable and the wording is not, and that two of the seven are - answered somewhere other than under a heading. - - **There is no document for the message bus, and it is the largest hole.** The - bus is the mechanism the product rests on. It has an embedded server, a client - wrapper, two KV buckets, a JetStream stream, a subject namespace and a - signature scheme, and no subject document. What follows from that is precise: - the two facts an operator most needs when the bus misbehaves, that - `nats-client` registers no reconnection callbacks so osapi is never told of a - drop or a recovery, and that `nats-server` forces trace logging on unturnably - and attaches the logger after the server is already accepting connections, are - recorded in `system/.specify/memory/architecture.md` as the observation that - **osapi's own memory mentions neither**. That is a correct diagnosis filed in - the wrong repository, and the reading found it by needing the facts and not - finding them where a contributor would look. - - Owner: `osapi`. A transport subject document, in its own change. - -### What this feature does not do - -- **FR-016**: This specification MUST NOT write any of the five remaining - baselines. It states the shape; each baseline is its own feature in its own - project, which is what keeps a wrong shape from being discovered six times. - -### Key Entities - -- **Baseline**: A component project's first feature, whose deliverable is an - inventory of how that repository behaves today. Seven sections, in order. -- **Section**: One of the seven required parts. Required by default, omissible - only where the repository's nature makes it meaningless, and never omitted - silently. -- **Measurement**: A count paired with the command that reproduces it. The - pairing is what makes a baseline checkable rather than believable. -- **Gap**: A disagreement between a repository's prose and its code, recorded - with both sides named and an owner. Never corrected by the baseline itself. -- **Edge**: A dependency between two repositories, named from both ends so a - reader can trace it from either. - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: A reader who has read none of the baselines reads all of them and - can state, without opening a repository: what each of the six is for, and - every dependency edge between them. This is the outcome; "six baselines exist" - is not. -- **SC-002**: Every count in every baseline is paired with a command, and - running the commands reproduces the counts or shows precisely which have - drifted. -- **SC-003**: No baseline contains a transcribed call graph, an exhaustive list - of exported functions, or a file-by-file walkthrough. -- **SC-004**: Every baseline's section 7 is non-empty — every one has something - it deliberately leaves out, and says so. -- **SC-005**: The charter fragment exists and is composed into all six component - constitutions, so the shape binds rather than being advice. -- **SC-006**: gohai's baseline carries sections 2 and 3, added by its own - amendment rather than by this feature. -- **SC-007**: Every documentation page in every repository is classified as - user-facing or contributor-facing by that repository's baseline, and no page - is moved without its classification stating why. **440** pages across the five - repositories that have any — `osapi`'s 219 published pages, not the 221 that - includes the Docusaurus project's own files. **Corrected**: this read 221, - which is `osapi`'s own page count and coincidentally also the total for the - four repositories still needing a move — two different quantities sharing one - figure, which is why the error read as consistent. -- **SC-008**: After the moves, no contributor rule is stated in both a - repository and the corpus. This is the one-statement rule applied across - repositories rather than within one, and it is what the whole exercise is for. -- **SC-009**: `just test` passes in the specs repository. - -## Assumptions - -- The six repositories are those the repository list returns today: `gohai`, - `nats-client`, `nats-server`, `osapi`, `osapi-justfiles`, - `osapi-orchestrator`. The list itself comes from the command `system`'s own - first feature made authoritative, not from this file. -- The dependency graph as measured on 2026-09-29: `osapi` depends on - `nats-client` and `nats-server`; `osapi-orchestrator` depends on `osapi`; - `gohai` has no osapi-io Go dependencies and no osapi-io dependents; - `osapi-justfiles` is fetched by all six through a justfile recipe. It will - change, which is why FR-002 requires each baseline to state its own edges - rather than this file holding the graph. -- Every repository already defers to this one for design: their own `AGENTS.md` - and `CONTRIBUTING.md` point here rather than restating a workflow. So the - evergreen documents live **only** in this repository, in each project's - `.specify/memory/`, and are never copied into the repository they describe. A - copy there would be the second statement `global/documentation` forbids, and - it would be the copy that goes stale, because nothing regenerates it. -- **`osapi` gets a baseline like every other repository.** Its memory holds five - archived features, which is accumulated *feature* history rather than an - inventory: it says what each change did and nowhere says what the repository - is, where it sits, or what its architecture is. Under this shape it is the one - memory that does not conform — see FR-021. -- The work is **two operations, not one**: a baseline states what a repository - is, and a move relocates its contributor documentation into the corpus. gohai - has the first and needs the second; osapi has the second and needs the first. - That asymmetry is the thing being corrected, and it is why FR-026 fixes the - order rather than leaving it to whoever goes next. -- The seven sections are the minimum that answers SC-001. A baseline may say - more; it may not say less without stating the omission. -- One finding is recorded here and deliberately not acted on: `osapi-justfiles` - is fetched from `refs/heads/main` rather than a pinned ref, which the Tooling - principle's "a tool whose output is committed is pinned" speaks to directly. - It is osapi-justfiles' and each consumer's own change, not this feature's. diff --git a/history/system-002-baseline-shape/tasks.md b/history/system-002-baseline-shape/tasks.md deleted file mode 100644 index 3ea5728..0000000 --- a/history/system-002-baseline-shape/tasks.md +++ /dev/null @@ -1,561 +0,0 @@ -______________________________________________________________________ - -## description: "Task list for one shape for every component baseline" - -# Tasks: One shape for every component baseline - -**Input**: Design documents from `system/specs/002-baseline-shape/` - -**Prerequisites**: [spec.md](spec.md) merged (specs#163, amended by #164 and -#165), [plan.md](plan.md), [research.md](research.md), -[data-model.md](data-model.md), -[contracts/section-order.md](contracts/section-order.md), -[quickstart.md](quickstart.md) - -**Tests**: none. There is no code. What stands in for a test is `just test`, the -composition check, and two readings that no command can perform. - -## Read this before starting - -**This feature writes four things and obliges ten others.** It is easy to read -the task list as "the baseline programme" and start writing baselines; FR-016 -forbids that here, and the reason is that one wrong judgement would then -propagate into six documents before anybody reviewed it. - -| | | -| --------------------------------------- | ---------------------------------------------------------------------- | -| **Tasks this feature carries out** | T001–T012 — the fragment, the manifest entry, the composition, the map | -| **Tasks that only open other features** | T013–T016 — they create a branch and invoke a skill, and stop | -| **Not tasks at all** | The nine baselines and four moves. Each is its own feature's task list | - -T013 to T016 are deliberately thin. A task here that produced another project's -baseline would be that project's work done in the wrong place. - -______________________________________________________________________ - -## Phase 1: Setup - -- [x] T001 Read [contracts/section-order.md](contracts/section-order.md) before - writing anything, and confirm the seven sections it describes match FR-001's - order in [spec.md](spec.md). The contract is what nine later features are - written against; a disagreement between it and the requirement is cheap now - and expensive after four baselines exist. - -______________________________________________________________________ - -## Phase 2: Foundational (Blocking Prerequisites) - -**⚠️ CRITICAL**: T002 through T005 are the whole of this feature's enforceable -output. A fragment that is written but not composed is a file, not a rule. - -- [x] T002 Write `.charter/fragments/global/baseline.md` with the 14-line - wording fixed in [research.md](research.md) Decision 1. Do not reword it: the - wording was matched to the existing seven fragments' voice and length, and its - middle paragraph names a real event — osapi's 1,840 lines of memory with no - statement of purpose — because `global/correction` requires a requirement - written from evidence the repository carries. - -- [x] T003 Add `global/baseline` to `mandatory_fragments` in - `.charter/manifest.yml`. **This is a separate step from T002 and skipping it - is silent**: a fragment absent from the manifest is never composed, so it sits - in the repository looking authoritative and binds nothing. - -- [x] T004 Recompose the constitution for **all six** component projects by - invoking `speckit-charter-compose` in each: `components/gohai`, - `components/nats-client`, `components/nats-server`, `components/osapi`, - `components/osapi-justfiles`, `components/osapi-orchestrator`. Six, not five. - A project missed here is a repository bound by nothing, and nothing about its - next baseline would reveal the omission. - - **Done, and it was wider than this task assumed.** All six component - constitutions carried only **five** sections where the manifest declares - eight: `global/repositories` (added by system's first feature) and - `global/tracking` (added by specs#144) had never been composed into any of - them. Only `system` had seven. So the fragment written in T002 could not have - bound anything even once composed — the composition reads each project's - `state.yml`, not the manifest, and every project's listed five. - - Both were fixed as part of this task, because T005 is unsatisfiable otherwise: - each `state.yml` now lists all eight in manifest order, and every constitution - carries eight marked sections at version 1.2.0. - - **No hand edits were lost.** `snapshot-detect-modified.sh` reported four - sections modified in gohai, which was a false positive: comparing each section - against its snapshot with heading levels and blank lines normalised showed the - content identical, and the one remaining difference was a structural `---` - before the metadata footer. The extension's own - `constitution-validate-sections.sh` returns `VALID=true` for all six. - -- [x] T005 Run the SC-005 check from [quickstart.md](quickstart.md): - `grep -rl "^# Baseline" components/*/.specify/memory/constitution.md | wc -l` - returns **6**. This is the task that distinguishes a composed rule from a - written one. - - **Passes.** - `grep -rl "^## Baseline" components/*/.specify/memory/constitution.md | wc -l` - returns **6**. The three fragments that were missing are each now in 6 of 6. - **Checkpoint**: the shape binds. Every baseline written after this point is - written under a rule rather than under advice, which is the condition - [research.md](research.md) Decision 5 exists to create. - -______________________________________________________________________ - -## Phase 3: User Story 1 — the set reads as one thing (Priority: P1) 🎯 MVP - -**Goal**: the map exists, so a reader can see the whole graph without reading -six documents first. - -**Independent test**: the map's own commands reproduce its edges, and it agrees -with every baseline that exists. - -- [x] T006 [US1] Add `## The Repository Map` to `system/.specify/memory/plan.md` - with the table from [data-model.md](data-model.md) — six repositories, what - each depends on, what depends on it, and one line on what it is. Not to - `spec.md`: [research.md](research.md) Decision 2 gives the reason, which is - that a dependency graph is current state and `plan.md` is where current state - lives. - -- [x] T007 [US1] Add the commands that produce the map beside it — - `grep -oE "osapi-io/[a-z-]+" */go.mod` for the Go edges and - `grep -n justfiles */justfile` for the build edge. **FR-007 applies to this - feature's own artifacts**, not only to the baselines it governs, and a map - without its commands is the cached list `global/repositories` forbids. - -- [x] T008 [US1] State in the map section that the build edge — every repository - fetching `osapi-justfiles` — appears in no `go.mod`, which is why it is listed - separately rather than derived from the Go graph. A reader who ran only the - first command would conclude `osapi-justfiles` has no dependents. - - Stated: every repository fetches `osapi-justfiles` through a justfile recipe - rather than importing it, so it appears in no `go.mod`. A reader running only - the Go command would conclude it has no dependents. - -- [x] T009 [US1] Record in the map section that `osapi-justfiles` is fetched - from `refs/heads/main` rather than a pinned ref, and that `global/tooling` - requires a tool whose output is committed to be pinned. Owner: those - repositories. Recorded, not fixed — this feature changes no repository. - - Recorded, not fixed: the fetch takes `refs/heads/main` rather than a pinned - ref, against the Tooling principle. Owner named as `osapi-justfiles` and each - consumer. **Checkpoint**: the graph is readable in one place and re-derivable - from two commands. - -______________________________________________________________________ - -## Phase 4: User Story 2 — a baseline survives ordinary change (Priority: P1) - -**Goal**: the rules that keep a baseline evergreen are binding rather than -habitual. - -**Independent test**: the fragment states the counts-with-commands rule, and -`contracts/section-order.md` gives a reviewer the question that distinguishes -architecture from a transcribed call graph. - -- [x] T010 [US2] Confirm the fragment's third paragraph carries the - counts-with-commands rule. It is the one mechanically checkable part of the - shape, and [research.md](research.md) Decision 1 records it as deliberately a - second rule rather than more context — a fragment omitting it would leave the - most enforceable part as advice. - -- [x] T011 [US2] Confirm `contracts/section-order.md` states, under section 3, - the question a reviewer asks: *if a function were renamed tomorrow, would this - section become wrong, or merely cite a stale path?* Nothing automatable checks - the difference between architecture and a call graph, and a contract that did - not give the reviewer a question would leave FR-004 unenforceable. - -- [x] T012 [US2] Run `cd specs && mise exec -- just test` — SC-009. - - Green, and `constitution-validate-sections.sh` returns `VALID=true` for all - six projects. **Checkpoint**: this feature's own output is complete. - Everything below opens other work. - -______________________________________________________________________ - -## Phase 5: User Story 3 — opening the remaining units (Priority: P2) - -**Goal**: the ten obliged units are started in the order the plan fixes, and the -hardest-fitting repository is not left to last. - -**Independent test**: each task below creates a branch and invokes a skill -against the right project, and stops there. - -**These four tasks open features. They do not write them.** - -- [x] T013 [US3] Open `osapi`'s baseline: branch, then `speckit-specify` against - `components/osapi` for a seven-section baseline. **It must not restate the - 1,840 lines already in that memory** from five archived features — its section - 4 is mostly citation, because osapi's contract is already stated and restating - it is the second statement this whole programme forbids. First by FR-029, - because it is the dependency hub and every other baseline's edges are stated - against its section 2. - - **Merged as specs#169**, and it changed the programme's arithmetic. The - baseline found three contributor pages the corpus backfill never looked at — - `architecture/ui.md` at 264 lines, `development/ui-development.md` at 200, and - `sdk/guidelines.md` at 227 — of which the first two have no corpus counterpart - at all. So osapi needs a move after all: **twelve units, not eleven**, - recorded as FR-030, and a new task T023 below. - - It also corrected osapi's page count from 221 to **219**: the 221 counted the - Docusaurus project's own `README.md` and `SUPPORT.md` rather than published - pages. The programme's classification total is 440. - - It did **not** restate the 1,840 lines already in osapi's memory. Its section - 4 is mostly citation and says why, which is what FR-010 of that baseline - requires. - - **Archived 2026-09-30, and last of the seven rather than first.** It had no - `plan.md` for a day — stage 1 merged and stages 3 and 4 were skipped — and the - archival gate requires one, so six features reached osapi's memory before the - one that says what the repository is, including the embedded UI whose - specification depends on this baseline's classification. Plan and tasks at - specs#186, archival at specs#187. FR-113 through FR-133 sit **first** in that - memory's requirements, before FR-001, because `global/baseline` requires it. - -- [x] T014 [US3] **After T013's baseline merges**, and not before, run T004's - composition if it has not already run — see [research.md](research.md) - Decision 5. The fragment lands between the first baseline and the remaining - four: earlier binds six repositories to an untested shape, later leaves five - baselines written against a specification rather than a rule. - - **Already satisfied, and in the right order.** T004 composed the fragment into - all six constitutions and T005 confirmed it. osapi's baseline merged at - specs#169 before that, so the ordering Decision 5 requires held without a - second composition being needed. Verify with - `grep -c '^## Baseline' components/*/.specify/memory/constitution.md` — six - files, one each. - -- [x] T015 [US3] Open `gohai`'s baseline **amendment** — not a new feature. It - adds section 2, section 3, and the classification of its 68 documentation - pages. **Done.** The classification merged at specs#205 and sections 2 and 3 - at #209. Splitting it that way was not the plan and is worth recording: the - classification was self-contained and the two missing sections are not, so - holding the smaller half back would have delayed a finding for nothing. - gohai's memory already carries both subjects, written during the memory tree - work, so what the amendment owes is the baseline's own statement of them - rather than the knowledge. Section 3 is the one to note: gohai's FR-016 - excluded architecture *deliberately*, so the amendment reverses a judgement - rather than filling a blank. SC-006 is satisfied by that amendment, not by - this task: opening a feature is not carrying it out, and the check belongs to - the amendment's own task list. - -- [x] T016 [US3] Open `osapi-justfiles`' baseline early rather than last, even - though it is the smallest. It has no Go and no documentation pages, so it is - the one repository that tests FR-011 and FR-013 — whether a section may be - omitted and whether a contract can be stated for something that exposes no - code. **If the shape cannot be filled there, the shape is wrong**, and finding - that out after four conforming baselines is the expensive order. - - **Done, and the shape held.** Specified at specs#178, planned and tasked at - #180, archived 2026-09-30. **No section was omitted**, so FR-011's allowance - was never exercised — and that is the answer this unit existed for. FR-013 was - the one that needed work: "the contract" reads as though it presumes exported - symbols, and the baseline had to decide that 38 recipe names and twenty - variable names are one. They are, by every test that matters, and the bound - stated so the word does not become unbounded for the four baselines after it - is *something a consumer's build breaks on*. - - It also produced three amendments to this feature — FR-032, FR-033 and FR-034 - at specs#184 — and one finding it handed back rather than fixing: whether - `global/baseline` should say what a contract means for a repository exposing - no code, and whether a baseline should carry the shape finding at all, since - the SC-001 reading judged that the two deliverables in one document serve a - consumer worse. Both are this feature's to decide. - -**Checkpoint**: the programme is running under a fixed shape, with its riskiest -case tested early. - -______________________________________________________________________ - -## Phase 6: The programme's exit criteria - -**None of these six is a task this feature completes.** Every one needs -baselines that do not exist yet, so none can run until the other ten units have -merged. They are recorded here because a programme without a stated finish line -is one that gets abandoned rather than finished — and because four of them were -briefly filed as though this feature could run them, which would have meant -reporting a pass against zero baselines. - -- [x] T017 The SC-001 reading, from [quickstart.md](quickstart.md): a reader who - has seen none of the baselines reads all six and answers what each repository - is for, which depends on which and what would break, and where to look for how - one is built. Question 2 is the one that fails if the baselines are six - conforming documents that share a template rather than a set — each - individually correct, the graph still untraceable, because section 2 was - filled in as a formality. - - **Done 2026-09-30. Question 1 passes, question 2 passes, question 3 is - partial.** The reading answered the graph from the text, with every Go edge - stated from both ends and every build edge in `osapi-justfiles`' FR-004, and - it reconstructed what breaks on each edge including the delayed shape that - pinning by pseudo-version produces. Question 3 is partial by design: the - recipe contract, the variables, the fetch mechanism and the pinning situation - are all stated precisely, while CI workflows, recipe internals, binary - production and toolchain versions are excluded. The reader's own summary of - that: "I can tell you what `just test` resolves to and not what it runs." - - Its verdict on the set: **one set, converged onto rather than born as one.** - The evidence it gave was not the shared template, which it dismissed, but that - the documents report on their own propagation. `osapi-justfiles` records three - headings drifting and corrects them, `nats-client` says it kept them right by - copying the corrected file rather than re-deriving, and `nats-server` and - `osapi-orchestrator` count the hops. Four of the six also declare their own - reading order in their preambles, and all six file findings against each other - and against this feature with owners rather than fixing them in place. - - **Nine defects, all fixed at specs#212.** Ordered by what each cost a reader: - - | Defect | Where | - | ----------------------------------------------------------- | ------------------------ | - | A citation to `FR-115`, which does not exist, twice | `nats-client` | - | Two documents disagreeing about which repository is the hub | `osapi-justfiles` FR-003 | - | Five requirements filed inside Success Criteria | `gohai`, from specs#205 | - | A move size stale against an amendment one day older | `gohai` FR-020 | - | Nine counts whose command needs the row above it | three baselines | - | A fourth gap invisible to anyone reading section 6 | `nats-server`, from #207 | - | "five exclusions" where six are listed | `osapi` SC-004 | - | "three gaps" where four are stated | `osapi-justfiles` SC-005 | - | "three exported types" beside a table saying four | `nats-server` FR-012 | - - The first is the one worth keeping. Four documents claim their edges are - "verified from both ends", and at the single place where one of them links to - the other end, it links to a requirement that has never existed. Nobody - followed the link, including the reviews that merged it. A claim about - verification that is itself unverified is the exact failure this programme - exists to catch, and it took a reader restricted to the text to find it. - - Two of the nine were introduced by amendments **this programme** made in the - last two days, which is the honest cost of correcting merged documents: a - block appended before the wrong heading, and a gap filed in the section whose - count it corrects rather than the section that lists gaps. - - **What the reading could not get, and it is not a defect list.** Nothing in - the six says what the product is *for*. Six documents and roughly 3,400 lines - describe the machine, and osapi's FR-017 excludes what any domain or provider - does, so a reader finishes knowing the shape of osapi and not its purpose. The - reader's words: "I can describe the machine and not the job." Also absent: a - definition of "the corpus", which is the subject of nearly every requirement - in all six; and the programme's other units, since four of the six carry a - unit number while units 1 to 5 and 10 to 12 are named nowhere. - `osapi-justfiles`' FR-022a recorded that complaint about itself and assigned - it here. Owner: this feature, and it is the strongest argument yet that a - reader who needs an entry point should be given memory rather than the - baselines. - -- [x] T018 [P] SC-002: every count in every baseline is paired with the command - that reproduces it, and running the commands reproduces the counts or shows - precisely which have drifted. A count whose command no longer reproduces it - has **dated**, not failed; a count with no command beside it has failed. - - **Done 2026-09-30.** 35 commands extracted from the six baselines and run in - the repository each describes. Every one reproduces its count except one, - which had failed rather than dated. **The pass also missed a second class - entirely**, which T017's reading found: nine rows across three baselines gave - their command as "the same, plus ..." or "the same, with ...", inheriting the - row above. The extractor skipped those rather than reporting them, so "every - command reproduces" was true of the commands it ran and silent about nine it - did not. `just memory-check` has treated that form as a defect since it was - written, on the ground that a count whose command needs the row above it - cannot be checked alone, and the baselines were held to a weaker standard than - memory for no reason anybody had stated. All nine are now self-contained and - all nine reproduce, taking the baselines from 35 runnable commands to 44. - - The one that had failed outright: `osapi`'s SDK method count gave - `grep -cE ... pkg/sdk/client/*.go` followed by the word "summed" outside the - backticks. `grep -c` over many files prints a count per file, so the command - as written produces no number at all. The count itself, 117, is right. Fixed - to `grep -hE ... | wc -l`. - - The same pass found the count **hedged** in osapi's memory, as "roughly 110 - exported methods" where the figure is exact and reproducible. That is the - defect `osapi-justfiles`' FR-013b recorded in its own variable count, - appearing a second time, which is the argument for a check rather than a - convention. - - **The standing guarantee is memory's, not the baselines'.** A baseline records - what was true at a commit, so drift in it is expected and gating it would be - wrong. Memory is the current description, so `just memory-check` runs every - count in it on every test run. That check had two holes this task exposed: - - | Hole | Counts covered | - | --------------------------------------------- | -------------: | - | before | 53 | - | it only read measurement **tables** | 55 | - | its glob stopped at `memory/*.md`, not deeper | 62 | - - The second was the serious one. Every subject document lives in - `memory/architecture/`, one directory below the pattern, so the check had - never looked at a single one of them while reporting a total that read as - complete. - -- [x] T019 [P] SC-003: no baseline contains a transcribed call graph, an - exhaustive list of exported functions, or a file-by-file walkthrough. The - check is the question in - [contracts/section-order.md](contracts/section-order.md) under section 3, - applied by a reviewer, because nothing automatable separates architecture from - transcription. - - **Passes, 2026-09-30, with one thing worth naming.** No baseline transcribes a - call graph or walks files. The largest surfaces are stated as rules rather - than as lists: `osapi`'s 117 SDK methods become five naming rules derived from - them, and `osapi-orchestrator`'s 101 operations become one sentence plus a - page each. - - What comes closest to a list is an **enumerated vocabulary**: gohai's eight - registry functions, the orchestrator's eight guards and ten predicates. Each - is the complete set a consumer has to know, short enough to read, and closed - by constants in the code. The test that separates it from transcription is - whether a reader needs the whole set to use the thing. For a vocabulary they - do, and for 117 methods they do not. - -- [x] T020 [P] SC-004: every baseline's section 7 is non-empty. FR-004's - evergreen bound guarantees every baseline excludes something, so a zero means - the omission was not stated rather than that nothing was omitted. - - **Passes, 2026-09-30.** All six, at 6, 9, 16, 19, 23 and 65 non-blank lines - for gohai, osapi-orchestrator, osapi, nats-client, nats-server and - osapi-justfiles. The order is worth a glance: `osapi-justfiles` has no Go at - all and excludes the most, because a repository whose contract is 38 recipe - names has to say what a recipe does *not* promise. Size predicted nothing here - either. - - **Unit 8 done, 2026-09-30.** `nats-server` baselined at specs#191, amended at - #192, archived at #193. Its findings came from reading `Start()` in order - rather than from counting — a statement order, two literal arguments, and an - absent statement — and its SC-001 reading passed three of three, the first - clean reading in the programme. It also drew a **different** coherence verdict - from the two before it: one argument rather than a checklist with footnotes. - - **Unit 7 done, 2026-09-30.** `nats-client` baselined at specs#188, amended at - #189 and archived at #190. Its reading found something a command could not: an - acceptance scenario promising a behaviour the specification never stated. Two - counts were also wrong, both missing an exclusion. Remaining: `nats-server`, - `osapi-orchestrator`, gohai's amendment, and the moves. - -- [x] T021 [P] SC-007: every documentation page in every repository was - classified by that repository's baseline before any move touched it — 440 - pages across the five that have any. **Done.** osapi at specs#169, gohai at - #205, osapi-orchestrator at #206, and both nats repositories at #207. Three of - those four were amendments to baselines that predated FR-025, which is the - cost of fixing the shape after four baselines had merged. The results - contradict FR-027's prediction for three of six repositories and are recorded - as FR-035. - -- [x] T023 Open `osapi`'s **move** — the twelfth unit, which FR-027's table - originally said was unnecessary. It relocates `architecture/ui.md` and - `development/ui-development.md` into the corpus, and the part of - `sdk/guidelines.md` that is not a demonstration of rules the corpus already - states. Driven by the classification in specs#169, which is the ordering - FR-026 requires and the one osapi got wrong the first time. - - **Two of three pages done, and the box stays open until the third is.** - `007-the-embedded-ui` moved the two UI pages: specified at specs#172, planned - at specs#173, implemented at osapi#549, corrected at specs#175 and osapi#550. - It did **not** touch `sdk/guidelines.md`, which 006's FR-015 classifies as - *partly* moving — its rules are 005's FR-019 and FR-020 and the page rightly - demonstrates them, but the package structure and the response pattern have no - counterpart anywhere. That remainder is a feature of its own and it is not - open yet. Ticking this on the UI move alone would have recorded a third of a - page family as a whole one. - - **Done, 2026-09-30.** The UI half was already carried out: three statements of - 264, 200 and 263 lines are now 82, 52 and 9, the last a pointer whose own text - says it is a pointer. The `sdk/guidelines.md` remainder is in the corpus too: - the package layout and the `Response[T]` envelope are in - [the SDK document](../../../components/osapi/.specify/memory/architecture/sdk.md), - which previously cited the site for both and stated neither. - - What the corpus adds is the why, which the page did not have. The split - between `.go` and `_types.go` keeps the conversion from - generated types in one place per domain, so a service returning a generated - type directly has skipped that file. The envelope exists for one caller, the - CLI's `--json` mode, which has to print what the server said rather than what - the SDK parsed. - -- [ ] T022 The SC-008 search: every documentation page still present in a - repository is one its own baseline classified **user-facing**. Anything - classified contributor-facing and still there is a rule stated twice, which is - the state the programme exists to end and the only check that distinguishes a - finished one from six inventories written beside the documentation they were - meant to replace. - - **Blocked, and the residue is measured, 2026-09-30.** The search runs; it - cannot pass, because two moves have not happened. What is still in a - repository after its own baseline classified it contributor-facing: - - | Page | Lines | State | - | ---------------------------------- | ----: | ---------------------------------- | - | `gohai/docs/methodology.md` | 382 | untouched | - | `gohai/docs/adding-a-collector.md` | 280 | untouched | - | `gohai/docs/ocsf-validation.md` | 107 | untouched | - | `osapi`'s `sdk/guidelines.md` | 227 | the non-demonstration part is owed | - - ```sh - cd ~/git/osapi-io && wc -l gohai/docs/methodology.md \ - gohai/docs/adding-a-collector.md gohai/docs/ocsf-validation.md \ - osapi/docs/docs/sidebar/sdk/guidelines.md - ``` - - **osapi's UI move is done**, which this task list never recorded. Its three - statements were 264, 200 and 263 lines and are now 82, 52 and 9: the site page - reduced to the operator's half, the development page likewise, and - `ui/docs/architecture.md` a nine-line pointer whose own text says it is a - pointer and not a summary, and why. That is the shape FR-027 asks for, and it - is the only one of the moves carried out so far. - -______________________________________________________________________ - -## Dependencies & Execution Order - -### Phase dependencies - -- **Phase 1** has none. -- **Phase 2** blocks everything: T003 makes T002 binding, and T005 is what - proves it. -- **Phase 3** (US1) and **Phase 4** (US2) are independent of each other and both - depend on Phase 2. -- **Phase 5** (US3) depends on Phase 2, and T014 depends on T013 having merged. -- **Phase 6** depends on all ten obliged units, which are outside this feature. - -### What is genuinely parallel - -- T006 through T009 are one section of one file and are sequential in practice. -- T010 and T011 are reads of two different files. -- T013, T015 and T016 open three different projects and can run in any order or - at once; only T014 is ordered, and only relative to T013. - -### What only looks parallel - -T002 and T003 touch different files and **must not be split across pull -requests**. A fragment merged without its manifest entry is a rule that binds -nothing, and it reads as authoritative for exactly as long as nobody checks. - -______________________________________________________________________ - -## Implementation Strategy - -### MVP - -Phases 1, 2, 3 and 4 — one pull request. The fragment, the manifest entry, the -six recomposed constitutions, and the map. That is the whole of this feature's -own output and it is coherent alone: the shape binds and the graph is readable. - -### Then - -Phase 5 opens the remaining work in the fixed order, with `osapi` first because -it anchors everyone's edges and `osapi-justfiles` early because it is the case -most likely to prove the shape wrong. - -______________________________________________________________________ - -## Notes - -- No code changes in any repository. No documentation moves in this feature - either — FR-016 and FR-027 put every move in its own feature. -- Eleven units in total, enumerated in [plan.md](plan.md). One is this feature, - nine are new features, one is an amendment to gohai's merged baseline. -- **440** documentation pages will be classified by the baselines, none of them - here. Not 442, which used `osapi`'s pre-correction 221; see FR-031 and FR-038. - And not 221 — that is `osapi`'s own count, which the four other repositories - also totalled before the correction. The collision is what made the stale - figure findable, and correcting `osapi` to 219 is what broke it. diff --git a/history/system-003-rule-and-reasoning/checklists/requirements.md b/history/system-003-rule-and-reasoning/checklists/requirements.md deleted file mode 100644 index 16f847b..0000000 --- a/history/system-003-rule-and-reasoning/checklists/requirements.md +++ /dev/null @@ -1,61 +0,0 @@ -# Specification Quality Checklist: A rule and the reason for it live in different places - -**Purpose**: Validate specification completeness and quality before proceeding -to planning - -**Created**: 2026-09-30 - -**Feature**: [spec.md](../spec.md) - -## Content Quality - -- [x] No implementation details (languages, frameworks, APIs) -- [x] Focused on user value and business needs -- [x] Written for non-technical stakeholders -- [x] All mandatory sections completed - -## Requirement Completeness - -- [x] No [NEEDS CLARIFICATION] markers remain -- [x] Requirements are testable and unambiguous -- [x] Success criteria are measurable -- [x] Success criteria are technology-agnostic (no implementation details) -- [x] All acceptance scenarios are defined -- [x] Edge cases are identified -- [x] Scope is clearly bounded -- [x] Dependencies and assumptions identified - -## Feature Readiness - -- [x] All functional requirements have clear acceptance criteria -- [x] User scenarios cover primary flows -- [x] Feature meets measurable outcomes defined in Success Criteria -- [x] No implementation details leak into specification - -## Notes - -**Two items pass differently than their wording suggests**, which is how every -feature in this repository passes them. The requirements name files, fragments -and advisory identifiers, because `global/verification` requires evidence a -reader can re-measure and this feature's subject is where statements live. The -stakeholder is a contributor to any of the six repositories. - -**SC-003 is the criterion that matters and the weakest to write.** It asks a -reader given only the fragment to sort ten rules and say where each goes. That -tests whether the test in FR-007 is usable by somebody who was not present for -the argument, which is the whole point of writing it into a fragment rather than -a feature. It cannot be automated and it is the only check that would catch a -resolution that reads well and does not discriminate. - -**FR-017 records a gap this feature cannot close.** Nothing will check that a -rule in memory is also stated in its repository, because deciding which -statements are rules is the judgement FR-007 exists to make. The honest position -is in the requirement: this one may not be automatable, and saying so beats -inventing a checker that passes by counting something else. - -**One requirement is about a merged specification being wrong.** FR-013 says -osapi's 005 FR-026 required removing the only place a contributor could read -rules the corpus then held alone. The specification is not amended, because it -records what was decided; the repository gains the eight statements instead. -That distinction is deliberate and is the Correction principle rather than an -exception to it. diff --git a/history/system-003-rule-and-reasoning/data-model.md b/history/system-003-rule-and-reasoning/data-model.md deleted file mode 100644 index 450e5ae..0000000 --- a/history/system-003-rule-and-reasoning/data-model.md +++ /dev/null @@ -1,71 +0,0 @@ -# Rule, reasoning, and the test between them - -**Feature**: `003-rule-and-reasoning` | **Date**: 2026-09-30 - -## The two entities - -| Entity | Is | Lives in | Form | -| ------------- | --------------------------------------------------------- | ------------------------------- | ---------------------------- | -| **Rule** | A statement somebody must follow to avoid a wrong change | the repository | imperative, one or two lines | -| **Reasoning** | Why the rule exists, what it buys, what breaks without it | that repository's design record | prose, once | - -They are not two halves of one statement. Each is complete on its own: a rule -that needs its reasoning to be followed is not yet a rule, and reasoning that -restates the rule has made the second statement this forbids. - -## The test - -> Would somebody who cannot read this make a wrong change? - -Applied to one statement at a time, before deciding where it goes. - -| Answer | It is | It goes | -| ------------------------------------------------- | --------- | ----------------- | -| Yes, they would do the wrong thing | a rule | the repository | -| No, they would do the right thing not knowing why | reasoning | the design record | - -**What the test deliberately ignores**: whether the statement is written as an -imperative, whether it carries the word "must", whether its heading says -MANDATORY, and how long it is. All four are form, and the two failures this -feature exists to fix were both invisible to form. gohai has three MANDATORY -headings that are rules and one that is reasoning wearing an imperative; -`osapi`'s stdin rule is a paragraph of explanation whose omission costs a -vulnerability. - -## Worked examples, from FR-002's ten - -These are the evidence FR-010 keeps out of the fragment. They live here, and -`system`'s memory will carry the shorter version. - -| Statement | Test says | Where | -| ------------------------------------------------------------- | --------- | ------ | -| One endpoint never both creates and updates | rule | repo | -| Why a combined endpoint destroys 404's meaning | reasoning | design | -| A secret reaches a command through stdin, never an argument | rule | repo | -| That arguments are logged and appear in the process table | reasoning | design | -| Update when absent is an error, create when present is not | rule | repo | -| Why the asymmetry: the caller asserted a thing exists | reasoning | design | -| A permission absent from the role map reaches nobody | rule | repo | -| That `ResolvePermissions` returns early on direct permissions | reasoning | design | -| Ten minutes is a ceiling on any command | rule | repo | -| That a caller can ask for less and cannot ask for more | reasoning | design | - -The pattern in every row: the rule is what to do, the reasoning is what the -system does. A contributor needs the first to be correct and the second to be -confident. - -## What changes in each file - -| File | Change | -| --------------------------------------------- | ------------------------------------------------ | -| `.charter/fragments/global/documentation.md` | one paragraph, third position, 14 lines to 22 | -| seven `constitution.md` | recomposed, the paragraph appears in each | -| `system/.specify/memory/spec.md` | one agreement added: the rule, the test, and why | -| `system/specs/003-rule-and-reasoning/spec.md` | FR-016's constitution count, 6 to 7 | - -## State transitions - -None. A fragment has no lifecycle; it is composed or it is not. The one thing -worth naming is that composition is the transition: a fragment edited and not -composed binds nothing, and a constitution edited by hand is discarded on the -next compose. diff --git a/history/system-003-rule-and-reasoning/plan.md b/history/system-003-rule-and-reasoning/plan.md deleted file mode 100644 index 3a19a96..0000000 --- a/history/system-003-rule-and-reasoning/plan.md +++ /dev/null @@ -1,181 +0,0 @@ -# Implementation Plan: A rule and the reason for it live in different places - -**Branch**: `docs/003-plan-and-tasks` | **Date**: 2026-09-30 | **Spec**: -[spec.md](spec.md) - -## Summary - -Add a paragraph to `global/documentation` distinguishing a rule from its -reasoning, and recompose every constitution that composes that fragment. No -repository's documentation changes here. The fragment text is decided below, -verbatim. - -Planning found one defect in the merged spec: FR-016 counts six constitutions -and there are seven. - -## Technical Context - -**Language/Version**: none. Markdown and YAML. - -**Primary Dependencies**: the `specify` CLI pinned in the root justfile, -`speckit-charter-compose` per project, mdformat. - -**Storage**: N/A - -**Testing**: `just test`, which runs mdformat, just-fmt, skill-lint, -`memory-check` over 69 counts and `memory-docs` over 26 documents. - -**Target Platform**: N/A - -**Project Type**: charter - -**Performance Goals**: N/A - -**Constraints**: `global/documentation` stays under 25 lines. No em dashes, no -"MANDATORY", no evidence in the fragment. - -**Scale/Scope**: one fragment, **seven** composed constitutions, one memory -document in `system`. - -## Constitution Check - -| Principle | Verdict | Note | -| ------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Documentation | passes | This feature is that principle gaining a clause. The new paragraph does not weaken the existing three. | -| Verification | passes | Composition is verified by grep across all seven constitutions rather than by assuming the tool ran. | -| Tooling | passes | The `specify` CLI stays pinned. Nothing new is installed. | -| Correction | passes | The requirement is written from `osapi`'s existing practice, which is what this principle asks for. Planning invoked it once more, for the count below. | -| Workflow | passes | Spec merged before planning. Plan and tasks are one review unit, which is what the lifecycle asks and what gohai's 002 did not do. | -| Repositories | passes | The fragment and the design live here. The compliance work lives in `osapi`. | -| Tracking | passes | Two follow-on items have owners named in the spec and need no issue while this task list is live. | -| Baseline | passes | The new paragraph is compatible with the one-statement rule, by separating the two statements rather than permitting a second. | - -### The defect planning found - -FR-016 states "Constitutions to recompose | 6" with -`ls components/*/.specify/memory/constitution.md | wc -l`. That command is right -and the number is the wrong number for the task: `system` composes -`global/documentation` too, and its constitution carries the same marker. - -```sh -ls components/*/.specify/memory/constitution.md system/.specify/memory/constitution.md | wc -l # 7 -grep -c 'global/documentation SECTION' system/.specify/memory/constitution.md # 1 -``` - -Seven projects list the fragment in their own `state.yml`, which is what -composition reads: - -```sh -for d in components/*/ system/; do grep -c 'global/documentation' $d.specify/charter/state.yml; done # 1 seven times -``` - -Recomposing six would leave `system`'s own constitution stating a rule `system` -wrote and does not carry, which is the shape of defect this programme keeps -finding. The task list recomposes seven. FR-016's figure is amended in the same -change, because it is a count in a merged specification and correcting it is not -a change of intent. - -## The fragment text - -This is the deliverable. It goes into -`.charter/fragments/global/documentation.md` as the **third** paragraph, after -"the same words" and before the tool-configuration clause. - -```markdown -A rule and the reason for it are two statements, and they live in different -places. The rule is stated where somebody about to break it is standing, in the -repository, short enough to check against a diff. The reason is stated once -wherever that repository's design is recorded. Ask whether somebody who cannot -read this would make a wrong change: if they would, it is a rule and belongs in -the repository; if it only changes whether they understand why, it is reasoning -and belongs with the design. -``` - -Six lines, taking the fragment from 14 to 22. - -### Why that position - -A reader meets the paragraphs in order, and the first two establish that a -repository states its conventions and states them identically to its siblings. -The new paragraph narrows that: it says what a convention's statement contains -and what it does not. Putting it third leaves the tool-configuration clause -last, which is the strongest of the four and reads as the exception it is. - -Before the tool clause rather than after it, because the tool clause is a case -where the rule is not stated in prose at all, and a reader who has just been -told where a rule goes is the right reader for an exception to it. - -### Why it does not say "memory" - -"Wherever that repository's design is recorded" rather than "in memory". Three -reasons. - -A constitution is composed into repositories whose contributors do not use Spec -Kit, and `.specify/memory/` is a Spec Kit directory. `global/baseline` already -names memory, and it is the fragment about the corpus; this one is about -conventions. And a fragment that names a tool's directory is a fragment that has -to change if the tool does, which is the drift the tool-configuration clause two -paragraphs down exists to prevent. - -The cost is one indirection for a reader who does not know where design is -recorded. `global/baseline` tells them, in the same constitution. - -### Why the test is a question rather than a definition - -"Ask whether somebody who cannot read this would make a wrong change" is -imperative and applies to one statement at a time. A definition would invite -sorting by grammar, and the spec's FR-007 is explicit that grammar is the wrong -axis: "MANDATORY" in a heading does not make something a rule, and a paragraph -of explanation is not reasoning if omitting it lets somebody ship a -vulnerability. - -## Project Structure - -### Documentation (this feature) - -``` -system/specs/003-rule-and-reasoning/ -├── spec.md merged at specs#220 -├── plan.md this file, carrying the fragment text -├── research.md Phase 0: the count defect, and what was rejected -├── data-model.md Phase 1: rule, reasoning, and the test -└── tasks.md the work in order -``` - -### Source (what the implementation touches) - -``` -.charter/fragments/global/documentation.md the paragraph -components/*/.specify/memory/constitution.md six recomposed -system/.specify/memory/constitution.md the seventh -system/.specify/memory/spec.md the design, one agreement added -system/specs/003-rule-and-reasoning/spec.md FR-016's count corrected -``` - -## How SC-003 is run - -A fresh reader, given **only** the amended fragment and the list of ten rules -from FR-002 with no indication of which are which, sorts them and says where -each goes. - -They are given: the fragment's text, and the ten rules as one-line statements. -They are not given the spec, the corpus, or any repository. - -**A pass** is the ten sorted with a stated reason per item, and the reason -referring to consequence rather than to how the rule is worded. - -**A failure** is either sorting by wording, which means the test does not -discriminate and the paragraph needs rewriting, or asking for the reasoning -before sorting, which means the test cannot be applied to a rule in isolation -and is therefore not usable at the moment somebody needs it. - -This is the only check that the test works. Everything else in this feature -checks that a paragraph landed in seven files. - -## Complexity Tracking - -| Thing | Why it is not simpler | -| ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Seven recompositions rather than one edit | The constitutions are generated. Editing one is lost on the next compose, which `global/tooling` names. | -| A fragment paragraph rather than a rule in `system`'s memory alone | A rule that binds every repository is a fragment by the placement test in CONTRIBUTING, and memory holds the design. | -| Correcting FR-016 inside this change | It is a count in a merged specification and the number is wrong, not changed. Correcting intent would need its own pull request; correcting a miscount does not. | diff --git a/history/system-003-rule-and-reasoning/research.md b/history/system-003-rule-and-reasoning/research.md deleted file mode 100644 index d28c596..0000000 --- a/history/system-003-rule-and-reasoning/research.md +++ /dev/null @@ -1,82 +0,0 @@ -# Research: where a rule lives and where its reason lives - -**Feature**: `003-rule-and-reasoning` | **Date**: 2026-09-30 - -## 1. How many constitutions compose this fragment? - -**Decision**: seven, not six. FR-016's count is corrected in this change. - -**Rationale**: composition reads each project's own `.specify/charter/state.yml` -rather than `.charter/manifest.yml`, and all seven projects list -`global/documentation`: - -```sh -for d in components/*/ system/; do grep -c 'global/documentation' $d.specify/charter/state.yml; done -# 1, seven times -ls components/*/.specify/memory/constitution.md system/.specify/memory/constitution.md | wc -l # 7 -``` - -FR-016's command counts only `components/*/`, which is right about components -and wrong about the task. `system` is a project like any other, which its own -CONTRIBUTING says in as many words, and `system`'s constitution carries the -`global/documentation` marker. - -**Why it matters more than one file**: recomposing six would leave `system`'s -own constitution stating a rule `system` wrote and does not carry. -`osapi-justfiles`' baseline recorded the same shape of error, where "the six" -and "the repositories" were used interchangeably and produced a wrong consumer -count. This is that confusion in a different place, which is the argument for -the fragment being composed rather than described. - -## 2. Should the fragment say "memory"? - -**Decision**: no. It says "wherever that repository's design is recorded". - -**Rationale**: three things, in order of weight. - -A constitution is read by contributors who do not use Spec Kit. -`.specify/memory/` is a Spec Kit directory, and a rule binding every repository -should not depend on the tool the rule was written with. - -`global/baseline` already names memory and is the fragment whose subject is the -corpus. This paragraph's subject is conventions. Naming memory here puts the -same noun in two fragments with different jobs. - -And the tool-configuration clause two paragraphs down says prose describing a -tool's settings drifts while continuing to read as authoritative. A fragment -that names a tool's directory is subject to that clause. - -**Cost**: a reader who does not know where design is recorded has one -indirection. `global/baseline` answers it in the same constitution. - -**Alternatives considered**: "the corpus", rejected because it is this -organization's jargon and the spec's own reading found "the corpus" undefined in -all six baselines. "In `.specify/memory/`", rejected for the reasons above. - -## 3. Is the test a question or a definition? - -**Decision**: a question, stated imperatively, applying to one statement at a -time. - -**Rationale**: the spec's FR-007 says the axis is consequence rather than -grammar. A definition invites sorting by form, which is what produced the -problem: three of gohai's headings are marked MANDATORY and that is not what -makes them rules, while `osapi`'s stdin rule is a paragraph of explanation and -omitting it lets somebody ship the thing GHSA-6gc6-px2x-q95j describes. - -**Alternatives considered**: a two-column table of examples in the fragment. -Rejected under FR-010: the fragment carries the rule and the test, not the -evidence, and a table of examples is evidence that would need maintaining in -seven composed copies. - -## 4. What is not resolved - -The gap FR-017 records stands unchanged: nothing checks that a rule in memory is -also stated in its repository. Planning looked for a mechanism and found none -that does not require knowing which statements are rules, which is the judgement -the test exists to make. A checker that counted links, or headings, or the word -"must", would pass on documents that fail the actual rule, which is worse than -no checker because it would read as coverage. - -Recorded rather than solved, and the spec already says it may not be -automatable. diff --git a/history/system-003-rule-and-reasoning/spec.md b/history/system-003-rule-and-reasoning/spec.md deleted file mode 100644 index 288668b..0000000 --- a/history/system-003-rule-and-reasoning/spec.md +++ /dev/null @@ -1,364 +0,0 @@ -# Feature Specification: A rule and the reason for it live in different places - -**Feature Branch**: `docs/rule-and-reasoning` - -**Created**: 2026-09-30 - -**Status**: Closed 2026-09-30 without a charter change. The obligation it -arrived at is recorded in `system`'s memory, and gohai's move honoured it by -leaving the one-line rules where a contributor meets them. The two failed -attempts at a sorting test are kept in [tasks.md](tasks.md) because they are the -argument for the obligation being worded the way it is. - -**Input**: Resolve the conflict between `global/documentation` and -`global/baseline` that blocked gohai's move at specs#219, and state the -resolution as a charter fragment. - -## What this specification is, and what it is not - -It settles one question: **when a rule binds a contributor and its reasoning is -architecture, where does each live?** Until now the charter has said a -repository states its conventions in full, and separately that a rule is stated -once, without saying which governs a statement that is both. - -The answer is not invented here. `osapi` already practises it, twice, and nobody -wrote it down. This feature writes it down and names the compliance work that -follows. - -It changes no repository's documentation. It changes one fragment, which -recomposes into six constitutions, and it produces a list of eight rules `osapi` -owes. - -## User Scenarios & Testing *(mandatory)* - -### User Story 1 - A contributor with one checkout can comply (Priority: P1) - -Somebody clones `osapi`, writes a provider, and passes a password as a command -argument. The rule forbidding that exists, in the corpus, with a CVE behind it. -Its `CONTRIBUTING.md` does not state it, the site page that used to was reduced, -and they have no way to know. - -**Why this priority**: it is the harm `global/documentation` was written to -prevent, and it is currently happening to eight rules in one repository. - -**Independent Test**: for each of the eight rules, a reader given only the -`osapi` repository can state the rule. - -**Acceptance Scenarios**: - -1. **Given** only an `osapi` checkout, **When** a contributor asks how a secret - reaches a privileged command, **Then** the answer is stdin, from a file in - that checkout. -2. **Given** only an `osapi` checkout, **When** they ask whether one endpoint - may both create and update, **Then** the answer is no, from a file in that - checkout. - -### User Story 2 - A reader who wants the reason follows one link (Priority: P2) - -Somebody reads the rule, wants to know why it exists, and follows one link to a -document that explains it and does not repeat the rule's wording. - -**Why this priority**: it is what distinguishes this resolution from "state -everything twice", which is the outcome the one-statement rule exists to -prevent. - -**Independent Test**: no rule's reasoning appears in two places. - -### User Story 3 - The next move applies a test rather than a judgement (Priority: P2) - -Somebody planning a move for another repository has to decide, heading by -heading, what goes where. Today that is a judgement call; gohai's plan made it -three times and stopped. - -**Why this priority**: four repositories have moves or reductions ahead of them, -and a test applied four times beats a judgement made four times. - -### Edge Cases - -- A rule a tool already enforces. `global/documentation` already says the - configuration is the statement of record and prose is not. That clause wins, - and the resolution here must not contradict it. -- A rule with no interesting reason. Not every rule has architecture behind it; - the resolution must not require inventing reasoning to satisfy a shape. -- A rule that binds several repositories. `global/documentation` already says - each states it in the same words. This resolution must leave that intact. - -## Requirements *(mandatory)* - -### Section 1 — what the conflict is - -- **FR-001**: The corpus MUST state the conflict as two clauses that cannot both - be satisfied by a rule that is also architecture. `global/documentation`: a - repository states in full the conventions binding it, and a reference - elsewhere does not stand in place of stating them. `global/baseline`: memory - is the documentation and a fact is stated once. Neither says which governs. - -- **FR-002**: The corpus MUST state that the conflict is load-bearing rather - than theoretical, measured at `osapi` on 2026-09-30. Ten rules the corpus - states and a contributor must follow; two are stated in `osapi`'s own root - documents and eight are not. - - | Rule | In `osapi`'s own docs | - | ------------------------------------------------------------------- | --------------------- | - | A domain appears in every layer an existing one does | **yes** | - | Validation is declared via `x-oapi-codegen-extra-tags` | **yes** | - | One endpoint never both creates and updates | no | - | Create when it exists is not an error | no | - | Update when it is absent **is** an error | no | - | A secret reaches a command through stdin, never an argument | no | - | A caller's value beginning with a dash becomes a flag | no | - | Ten minutes is a ceiling on any command, not a fallback | no | - | Anything becoming a path or argument is revalidated in the provider | no | - | A permission absent from the role map reaches nobody | no | - - ```sh - cd ~/git/osapi-io/osapi - grep -ilE 'consistent across all layers' CONTRIBUTING.md AGENTS.md # found - grep -ilE 'upsert' CONTRIBUTING.md AGENTS.md # not found - ``` - -- **FR-003**: The corpus MUST state that **two of the eight carry advisories**, - GHSA-6gc6-px2x-q95j for the stdin rule and GHSA-7fjw-v3g9-326g for - revalidating in the provider. The rules this organization learned the hard way - are among those a contributor's checkout cannot show them, which is the - sharpest form the problem takes. - -- **FR-004**: The corpus MUST state which readers reach what, because the reader - list is the argument and `global/documentation` already names three of them. - - | Reader | Has | Reaches the eight | - | ------------------------------------------------------- | ---------------------------------------- | ----------------- | - | An agent working in this repository | the `add-a-domain` skill and the corpus | yes | - | A contributor with `osapi` checked out | `CONTRIBUTING.md` and the published site | **no** | - | A reviewer reading an `osapi` pull request in a browser | the diff and `osapi`'s files | **no** | - - The skill cites 22 requirement numbers across four feature specifications, so - the first reader is well served and the fragment's own three examples include - two who are not. - - ```sh - grep -rhoE 'FR-[0-9]+' .claude/skills/add-a-domain/ | sort -u | wc -l # 22 - ``` - -### Section 2 — the resolution - -- **FR-005**: The corpus MUST state the resolution as **an obligation about - reachability**, not as a taxonomy. - - **Corrected 2026-09-30, after SC-003 failed twice.** This required a - separation: the rule here, the reasoning there, sorted by a test. Two readings - showed the sorting cannot be codified, and the second showed why. The test - said "a rule says what somebody does", and **not one rule in this feature's - own worked examples says what somebody does**: "one endpoint never both - creates and updates", "updating something that does not exist is an error", - "ten minutes is the ceiling". All three describe the system. By the letter of - the test they were reasons, and the reader classified them as rules anyway and - said so: "I called it a rule because I know what it is for. The test did no - work." - - The taxonomy was never the thing that mattered. What matters is what FR-002 - measured: eight rules a contributor must follow that their checkout cannot - show them. So the obligation is **where the corpus states a rule a contributor - must follow, that contributor's repository states it too.** Nothing has to be - sorted, nothing has to be split, and nothing turns on whether a sentence names - an actor. - - The reasoning is left where it is. `global/baseline`'s one-statement rule - already governs duplication, and this obligation does not ask for a second - statement of anything: it asks that the rule be reachable from the repository - that it binds. - -- **FR-006**: The corpus MUST record that this is `osapi`'s existing practice - rather than a new rule, because `global/correction` requires a requirement - written from evidence the repository already carries. `osapi`'s - `CONTRIBUTING.md` does it twice: - - - Under "Adding a new API domain" it states the binding rule in full and in - bold, "pick an existing domain and `find`/`grep` for it across the codebase. - Your new domain should appear in all the same places", and links the site - guide for the nine steps. - - Under "Input validation" it states the mechanism and the tags, and leaves - the design reasoning elsewhere. - - Both are the shape this feature ratifies. What was missing is anybody saying - so, which is why eight other rules did not get the same treatment. - -- **FR-007**: The corpus MUST record that **the sorting test is abandoned**, and - why, because the attempt is the evidence for the obligation replacing it. - - Two readings, two failures, and the failures were different. The first sorted - by grammar and admitted it: every statement it called reasoning carried a - causal connective. The second described its real procedure, which was to - cluster the ten into topic pairs and label the directive member of each pair, - with the paragraph used "as a label-chooser rather than as a decision - procedure". It then found four things wrong with the test itself, of which two - are fatal: the stated definition classifies every one of this feature's own - rules as a reason, and six of ten verdicts were relational, so a test written - to apply to one sentence only works on a corpus. - - It also found the asymmetry nobody had argued for: a rule without its reason - was treated as fine and a reason without its rule as a defect. For a - convention binding several repositories the first paragraph puts the rule in - all of them and the separation puts the reason in one, so the offline - contributor the fragment is written for gets rules with no reasons. That is - the mirror of the failure the separation called fatal. - - What survives is the one thing the second reading would defend without - hedging: the existing clause that a rule a tool enforces is named rather than - restated, because the configuration is checkable and has a clear failure mode. - The obligation in FR-005 is written to the same standard. - -- **FR-008**: The corpus MUST state what the resolution does **not** license. It - does not license restating the reasoning in the repository, which is the - duplication `global/baseline` forbids. It does not license a rule stated only - in memory, which is the absence `global/documentation` forbids. And it leaves - the existing clause about tool configuration untouched: a rule a tool enforces - is still never restated as prose, and that clause wins where it applies. - -### Section 3 — the fragment - -- **FR-009**: `.charter/fragments/global/documentation.md` MUST gain the rule, - in the voice and at the length of the existing eight fragments, which run 11 - to 58 lines and average 23. The addition is a paragraph, not a section. - -- **FR-010**: The fragment MUST NOT carry the evidence. The ten rules, the - advisories, the reader table and `osapi`'s two worked examples belong in - `system`'s memory, which is where a reader goes for why. A fragment that - carried its own evidence would be the longest of the nine and would say in six - constitutions what belongs in one place. - -- **FR-011**: All six constitutions MUST be recomposed, because a fragment - changed and not composed binds nothing. This is `speckit-charter-compose` per - project and is implementation rather than specification. - -### Section 4 — what this makes other people owe - -- **FR-012**: `osapi` MUST state the eight rules in its own documentation. This - is **compliance work in `osapi`'s own pull request**, not a feature, per the - rule that once something binds every component bringing a repository into line - is ordinary work. It cites the fragment and this feature. - - Owner: `osapi`. This feature produces the list and does not do the work. - -- **FR-013**: The corpus MUST record what this means for `osapi`'s three merged - features, specifically rather than in general. They are **not wrong about - where the reasoning goes**, and 005's FR-026 is wrong about the rule: it - required "the contributor half of the site" removed, and removing it removed - the only place a contributor could read rules the corpus then held alone. The - specifications stay as they are, because they record what was decided; the - repository gains the eight statements under FR-012. - - This is the Correction principle working as intended rather than a failure of - it. Applying a rule a fourth time found what three applications did not. - -- **FR-014**: `gohai`'s 002 MUST be amended before its plan resumes, and this - feature MUST say so rather than leaving a blocked plan with no named next - step. Its FR-001 splits content three ways and needs a fourth axis: a rule's - one-line statement stays in `gohai` even when its reasoning moves. Under the - resolution, three of its nine contested headings each split rather than moving - whole. - - Owner: `gohai`, after this merges. - -- **FR-015**: The corpus MUST state that a **skill does not discharge the - obligation**. The `add-a-domain` skill lives in this repository, cites 22 - requirement numbers, and is unreachable from an `osapi` checkout. It serves - the first reader well and is not a substitute for the repository stating its - rules, because two of the three readers `global/documentation` names cannot - run it. No change to the skill; it is simply not the answer to this question. - -### Section 5 — measurements - -- **FR-016**: The corpus MUST carry these counts with their commands, measured - 2026-09-30: - - | Measurement | Value | Command | - | ----------------------------------- | ----: | ------------------------------------------------------------------------------------------------- | - | Charter fragments | 8 | `ls .charter/fragments/global/*.md \| wc -l` | - | Lines in `global/documentation` | 14 | `wc -l .charter/fragments/global/documentation.md` | - | Constitutions to recompose | 7 | `ls components/*/.specify/memory/constitution.md system/.specify/memory/constitution.md \| wc -l` | - | Requirement numbers the skill cites | 22 | `grep -rhoE 'FR-[0-9]+' .claude/skills/add-a-domain/ \| sort -u \| wc -l` | - -### Section 6 — gaps - -- **FR-017**: **Gap**: nothing checks that a rule in memory is stated in its - repository. This feature states the rule and creates no mechanism, so the - eight can become nine the next time a move happens. A checker would have to - know which statements in memory are rules, which is the judgement FR-007's - test exists to make and not something a script can make. Owner: `system`, - recorded rather than solved, and the honest position is that this one may not - be automatable. - -- **FR-018**: **Gap**: the five components other than `osapi` have not been - measured against FR-012. The ten-rule audit was done for `osapi` because that - is where the three moves happened. Owner: each component, when its own move - runs. - -### Section 7 — what this feature excludes - -- **FR-019**: The corpus MUST state what it leaves out: writing the eight - statements in `osapi`, amending `gohai`'s 002, `gohai`'s `collectors.md`, any - change to the `add-a-domain` skill, deciding 002's FR-040 about what a - citation points at, and any mechanism that would check FR-012 automatically. - -### Key Entities - -- **Rule**: a statement somebody must follow to avoid making a wrong change. - Lives in the repository, imperative. -- **Reasoning**: why the rule exists, what it buys, what breaks without it. - Lives in that repository's memory, once. -- **The test**: would somebody who cannot read this make a wrong change? - -## Success Criteria *(mandatory)* - -### Measurable Outcomes - -- **SC-001**: The fragment states the rule and the test in a paragraph, and - `global/documentation` stays under 25 lines. - -- **SC-002**: All **seven** constitutions carry the new text, verified by grep - for a phrase from it. **Corrected 2026-09-30, during planning**: this said six - and counted only `components/*/`. `system` composes `global/documentation` - like any other project and its constitution carries the marker, so recomposing - six would leave `system`'s own constitution stating a rule `system` wrote and - does not carry. That is the confusion between "the six" and "the repositories" - that `osapi-justfiles`' baseline recorded, in a different place. - - ```sh - grep -l 'about to break it is standing' \ - components/*/.specify/memory/constitution.md system/.specify/memory/constitution.md | wc -l - ``` - -- **SC-003**: A reader given the fragment alone **says, for each of the ten - rules of FR-002, whether a contributor with only that repository could follow - it**. No sorting, no taxonomy. The criterion is whether the obligation is - applicable by somebody who was not here, and the answer is a yes or a no per - rule rather than a classification. - - **Failed twice as written and was rewritten, 2026-09-30.** It used to ask a - reader to sort the ten into rule and reasoning. The first reading sorted by - grammar, the second sorted by topic pairing, and the second proved the test - contradicted this feature's own examples. A criterion a reader cannot satisfy - without importing knowledge the fragment does not give them is not measuring - the fragment. - -- **SC-004**: The eight rules `osapi` owes are listed by name, each with where - its reasoning already lives, so the compliance PR has no discovery to do. - -- **SC-005**: No rule's reasoning is stated twice after this feature, which is - unchanged from before it, because this feature moves nothing. - -- **SC-006**: `just test` passes in the specs repository. - -## Assumptions - -- Recomposing six constitutions is mechanical and produces no conflict. Each - project's `.specify/charter/state.yml` lists the fragments it composed, and - `global/documentation` is mandatory in `manifest.yml`, so all six take the - change. -- The ten rules of FR-002 are a sample rather than an audit. They were chosen - because the corpus states them and a contributor must follow them; a full - audit of `osapi`'s memory against `osapi`'s documentation is the compliance - PR's job under FR-012. -- "Imperative and short" is judged by a reader rather than measured. A rule that - takes a paragraph to state is a candidate for being two rules. diff --git a/history/system-003-rule-and-reasoning/tasks.md b/history/system-003-rule-and-reasoning/tasks.md deleted file mode 100644 index a5dfcce..0000000 --- a/history/system-003-rule-and-reasoning/tasks.md +++ /dev/null @@ -1,264 +0,0 @@ -# Tasks: A rule and the reason for it live in different places - -**Feature**: `003-rule-and-reasoning` | **Spec**: [spec.md](spec.md) | **Plan**: -[plan.md](plan.md) - -Twelve tasks in five phases. The fragment text is in [plan.md](plan.md) and is -not re-derived here; T002 pastes it. - -## Phase 1: correct the specification - -- [x] T001 Correct FR-016's constitution count from 6 to 7 in - [spec.md](spec.md), and say in the same requirement that `system` composes the - fragment like any other project. Research found this; see - [research.md](research.md) item 1. It is a miscount rather than a change of - intent, so it lands here rather than in its own pull request. - - Verify: - `ls components/*/.specify/memory/constitution.md system/.specify/memory/constitution.md | wc -l` - returns 7. - - **Done.** FR-016's count corrected to 7 with a command naming both globs, and - SC-002 corrected the same way: it also said six and greped only - `components/*/`. Two instances of one miscount in one specification. - -## Phase 2: the fragment - -- [ ] T002 Add the paragraph from [plan.md](plan.md) to - `.charter/fragments/global/documentation.md` as the **third** paragraph, - before the tool-configuration clause. Paste it verbatim; the wording was - decided in planning and re-deciding it here is how two versions of a rule - appear. - - **Done.** The fragment is 22 lines. - -- [ ] T003 Check the fragment against its own constraints, which is four greps - rather than a reading: - - ```sh - wc -l .charter/fragments/global/documentation.md # under 25 - grep -c '—' .charter/fragments/global/documentation.md # 0 - grep -ci 'mandatory' .charter/fragments/global/documentation.md # 0 - grep -ci 'memory\|\.specify' .charter/fragments/global/documentation.md # 0 - ``` - - The last is the one to watch. The paragraph says "wherever that repository's - design is recorded" precisely so that a fragment binding six repositories does - not name a Spec Kit directory, and the easiest way to lose that in an edit is - to make it concrete for clarity. - - **Done.** 22 lines, 0 em dashes, 0 "mandatory", 0 mentions of memory or - `.specify`. - -## Phase 3: compose - -- [ ] T004 Recompose **all seven** constitutions by invoking - `speckit-charter-compose` once per project, with `SPECIFY_INIT_DIR` set to - that project. Seven invocations, not one: composition resolves one project at - a time from its own `state.yml`. - - Order does not matter. Each is independent. - - **Done, all seven.** Composition is a deterministic concatenation: each `[F]` - marker is followed by that fragment's body with one heading level added. - Verified against the existing output before touching anything, then applied - identically. - -- [ ] T005 [P] Verify every constitution took the change, by a phrase from the - paragraph rather than by trusting that the tool ran: - - ```sh - grep -lc 'about to break it is standing' components/*/.specify/memory/constitution.md \ - system/.specify/memory/constitution.md | wc -l # 7 - ``` - - A count below seven names the project that did not compose. - `global/verification` is why this is a task rather than an assumption: running - something that would fail if the claim were false. - - **Done.** `grep -l 'about to break it is standing'` over all seven returns 7. - -- [ ] T006 [P] Verify nothing else moved in the seven files. - `speckit-charter-compose` rewrites a whole constitution, so a fragment that - changed upstream, or a marker that drifted, appears here as an unrelated diff. - - ```sh - git diff --stat -- components/*/.specify/memory/constitution.md system/.specify/memory/constitution.md - ``` - - Expect seven files, each with the same small insertion. Anything larger is - investigated before committing, not explained afterwards. - - **Done.** Seven files, one 9-line change each, nothing else moved. Each - section byte-equals the fragment body with the heading delta applied, checked - by comparing the two rather than by reading the diff. - -## Phase 4: the design - -- [ ] T007 Add the agreement to `system/.specify/memory/spec.md`: the rule, the - test, and the two failures that produced it. Keep it to what a reader needs, - which is the separation and the question. The ten worked examples stay in - [data-model.md](data-model.md); memory names the pattern rather than listing - ten rows. - - **Done.** One agreement added to `system`'s memory: the separation, the - question, the two failures that made the test about consequence rather than - form, and that this was `osapi`'s practice before it was anybody's rule. - -- [ ] T008 Add the counts to `system`'s memory with their commands, if the - agreement states any. Anything stated as a number in memory is run by - `just memory-check` on every test, and a number without a command fails it. - - **Done, nothing owed.** The agreement states no counts, so there is nothing - for `memory-check` to run. The ten worked examples stayed in - [data-model.md](data-model.md), the feature's artifact rather than memory. - -- [ ] T009 Run `unslop` over what T007 wrote, per AGENTS.md. The tells to expect - in this particular text are a bold label restating the line after it, and - commentary about the document, because the subject is documentation and it is - easy to slip into writing about writing. - - **Done.** One change: the opening was passive where the fragment's siblings - name the actor, so "the rule is stated in the repository" became "a repository - states the rule". - -## Phase 5: verify - -- [ ] T010 SC-003, the reading, from [plan.md](plan.md)'s procedure. A fresh - reader given the amended fragment and the ten rules as unlabelled one-liners - sorts them and states a reason each. A pass is ten sorted with reasons about - consequence. A failure is sorting by wording, or asking for the reasoning - before sorting. - - This is the only task that checks the test works rather than checking a - paragraph landed. If it fails, T002's wording is wrong and Phase 2 runs again. - - **RUN AND FAILED, 2026-09-30.** The reader sorted all ten and then said how: - "I sorted substantially on the presence of a causal connective. Every - statement I called REASONING carries one ... Every statement I called RULE is - a bare indicative with none. That is grammar detection, not the counterfactual - test." - - They proved it rather than asserting it. Rewrite statement 10 from "a - permission absent from the role map can be held by no token, **so** the - endpoint is unreachable" to "every permission an endpoint checks appears in - the role map", and it lands as a rule with nothing about the world having - changed. - - This is the failure this task exists to catch, and it caught it before the - wording bound seven constitutions. Phase 2 and Phase 3 are reset to unstarted - and re-run after the amendment below. - - Four defects in the paragraph, in order of how much they matter: - - 1. **The test is not a property of one statement.** It asks a counterfactual - about a sentence while the answer depends on what else the reader can see. - "A combined endpoint destroys the meaning of a 404" is reasoning *only - because* the rule forbidding the endpoint is also stated. Remove that rule - and the same words become the only thing standing between a contributor and - a broken contract. Every reasoning verdict is a claim about the set, and - the paragraph presents it as a claim about the sentence. - 2. **It does not rank itself against the clause below it.** The ten-minute - ceiling is a rule by this paragraph and forbidden prose by the next one, - which says a rule a tool enforces is never restated. The spec's FR-008 - already decided that the tool clause wins; the fragment does not say so. - 3. **No handling for a sentence that is both.** "A caller can ask for less and - cannot ask for more, because the wrapper applies its context - unconditionally" splits mid-sentence, and the paragraph says a rule and its - reason are two statements without saying to split one that is not. - 4. **"Short enough to check against a diff" fails on the most rule-like of the - ten.** Checking that a domain appears in every layer needs the list of - layers, which is not in the sentence. - - A fifth thing, which is a finding rather than a defect: a reason recorded - while the rule it explains is stated nowhere means **the rule is missing**, - not implied. The paragraph has no way to say that, and it is the most useful - thing the reading produced. - - **RUN AND FAILED A SECOND TIME, against the corrected test.** The paragraph - was rewritten to sort by what a statement is about rather than by what its - absence would cost, and the rerun asked the reader for their method as well as - their verdicts. Both were worse than the first time, and usefully so. - - The reader's actual procedure, in its words: cluster the ten into topic pairs, - label the directive member of each pair, and use the paragraph "as a - label-chooser rather than as a decision procedure". Grammar was load-bearing - in five of ten, and the absence of a connective drove the other five. - - **The defect that ends the approach**: the corrected test said "a rule says - what somebody does", and not one rule in this feature's own `data-model.md` - says what somebody does. "One endpoint never both creates and updates." - "Updating something that does not exist is an error." "Ten minutes is the - ceiling." All describe the system, so by the letter of the test all three are - reasons. The reader classified them as rules and said why: "I called it a rule - because I know what it is for. The test did no work." - - ```sh - grep -c 'what somebody does' .charter/fragments/global/documentation.md # the test - grep -E '^\| (One endpoint|Update when|Ten minutes)' \ - system/specs/003-rule-and-reasoning/data-model.md # all marked rule - ``` - - Three more, each real: - - - **Six of ten verdicts were relational.** A test written to apply to one - sentence only works against a corpus. "A combined endpoint destroys the - meaning of a 404" is acceptable reasoning only because the rule forbidding - the endpoint exists somewhere the reader can see. - - **The asymmetry was never argued.** A rule without its reason was fine and a - reason without its rule was a defect. For a convention binding several - repositories the first paragraph puts the rule in all of them and the - separation puts the reason in one, so the offline contributor gets rules - with no reasons, which is the mirror of the failure the separation called - fatal. - - **"The rule is stated nowhere" is unfalsifiable at scale.** Reaching it for - one statement took an exhaustive search of nine others. Against a repository - it needs a complete search of everything the repository states. - - The one thing the reader would defend without hedging is the clause already in - the fragment: a rule a tool enforces is named rather than restated, because - the configuration is checkable and the failure mode is clear. - - **So the taxonomy is abandoned rather than reworded a third time**, and FR-005 - now states an obligation about reachability instead. Phase 2 and Phase 3 stay - unstarted. T010 is rewritten with the criterion it is checking. - -- [x] T011 [P] `just test` in the specs repository. mdformat, just-fmt, - skill-lint, 69 counts and 26 documents. - - **Done.** `just test` passes: mdformat, just-fmt, skill-lint, 69 counts, 26 - documents. - -- [x] T012 Record what is owed, with owners, in this task list rather than in an - issue, because both items are live work with a named next step: - - | Owed | Owner | Blocked until | - | ---------------------------------------------------- | ------- | ------------- | - | State the eight rules in `osapi`'s own documentation | `osapi` | this merges | - | Amend `gohai`'s 002 for the fourth axis | `gohai` | this merges | - - **Done.** Both rows stand, and neither is in this feature. - -## Dependencies & Execution Order - -- **Phase 1** is independent and can run first or last. It corrects a count. -- **Phase 2** blocks Phase 3. Composition reads the fragment. -- **Phase 3** blocks nothing inside this feature and is what makes the rule - bind. -- **Phase 4** is independent of Phases 2 and 3: the design can be written before - the fragment composes, and is better written after T002 fixes the wording. -- **Phase 5** depends on everything. T010 specifically depends on T002 and on - nothing else, so it can run before composition and catch a wording failure - early. - -**The one ordering that matters**: T002 before T004. A fragment composed before -its text is final puts the wrong words in seven files, and the second compose -looks like a correction to a rule rather than a typo fix. - -## Notes - -- Nothing here changes a repository's documentation. The two things that will - are T012's rows, in their own repositories' pull requests. -- T005 and T006 are the tasks that exist because composition is a generator. - Everything generated needs a check that it generated what was intended, which - is the lesson `global/tooling` records about committed output. diff --git a/justfile b/justfile index b11b138..30f78b8 100644 --- a/justfile +++ b/justfile @@ -34,7 +34,7 @@ skill-lint: check-counts: python3 scripts/check-counts.py -# Hold the documentation contract the constitution states +# Links resolve, pages are linked, no em dashes [group('lint')] check-docs: python3 scripts/check-docs.py diff --git a/scripts/check-docs.py b/scripts/check-docs.py index 1ea3ee6..784e197 100644 --- a/scripts/check-docs.py +++ b/scripts/check-docs.py @@ -1,26 +1,15 @@ #!/usr/bin/env python3 -"""Hold the documentation contract the constitution states. +"""Three things a script can check about the docs, so a reader does not have to. -These documents are the design record, written as documentation. The constitution -says what that means, but an instruction is obeyed by whoever remembers it. These -checks fail instead. + 1. Every relative link resolves. Moving a page breaks links silently, and the + reorganization that flattened this repository broke ten of them. + 2. Every page is linked from its component's README. An unlinked page is one + nobody finds. + 3. No em dashes. The one rule in VOICE.md a script can enforce, and 32 of them + had accumulated in two skills nobody was checking. -What is enforced: - - 1. No specification scaffolding. A `MUST`, an `FR-` label, a user story, a - success criterion or an acceptance scenario in memory means an archival - copied the feature's form instead of converting it. - 2. No em dashes, which is the machine tell that survives every other pass. - 3. Every link resolves, so the tree is navigable rather than nominally linked. - 4. Every subject document is reachable from its component's entry point. An - unlinked document is one nobody will find. - 5. Every component has an entry point at all. - -What is not enforced, and cannot be: whether the prose is any good. A count that -moves fails `memory-check`; a paragraph that drifts from the code does not. Only a -reader catches that, and every reading so far has caught something. - -Exit 0 when the contract holds, 1 when it does not. +Everything else about the writing needs a reader. Exit 0 when these hold, 1 when +they do not. """ from __future__ import annotations @@ -32,39 +21,15 @@ SPECS = Path(__file__).resolve().parent.parent COMPONENTS = SPECS / "components" -# A changelog is an audit trail of what each archival did, so it keeps the -# feature identifiers it is recording. Everything else is documentation. -EXEMPT: set[str] = set() - -SCAFFOLDING = [ - (re.compile(r"\bMUST\b"), "normative MUST; memory states what is, not what is required"), - (re.compile(r"^\s*-?\s*\*\*FR-\d"), "an FR- label; memory carries no requirement identifiers"), - (re.compile(r"^#+\s*User Stor", re.I), "a user story; that belongs to the feature that was reviewed"), - (re.compile(r"^\s*-?\s*\*\*SC-\d"), "an SC- label; success criteria belong to the feature"), - (re.compile(r"^\s*-?\s*\*\*(Given|When|Then)\*\*"), "an acceptance scenario"), - (re.compile(r"^#+\s*(Success Criteria|Measurable Outcomes|Acceptance)", re.I), "a feature-review heading"), - (re.compile(r"\[Source: specs/"), "a per-paragraph source footer; memory names its feature once, at the end"), -] - LINK = re.compile(r"\[[^\]]+\]\(([^)#]+\.md)(?:#[^)]*)?\)") -INLINE_CODE = re.compile(r"`[^`]*`") - -def prose(line: str) -> str: - """The line with inline code removed. - A backticked `MUST` is being named, not used. The rule that forbids the word - has to be able to say the word, and so does a table describing these checks. - """ - return INLINE_CODE.sub("", line) - - -def memory_docs() -> list[Path]: - out = [] - for f in sorted(COMPONENTS.glob("*/*.md")) + [SPECS / "ARCHITECTURE.md"]: - if f.name not in EXEMPT and f.exists(): - out.append(f) - return out +def docs() -> list[Path]: + """Every markdown file somebody wrote: the pages, the skills, the root.""" + out = list(COMPONENTS.rglob("*.md")) + out += (SPECS / ".claude").rglob("*.md") + out += SPECS.glob("*.md") + return sorted(set(out)) def rel(f: Path) -> str: @@ -73,53 +38,38 @@ def rel(f: Path) -> str: def main() -> int: problems: list[str] = [] - docs = memory_docs() + files = docs() - for f in docs: + for f in files: text = f.read_text() - lines = text.splitlines() - - for pattern, why in SCAFFOLDING: - for n, line in enumerate(lines, 1): - if pattern.search(prose(line)): - problems.append(f"{rel(f)}:{n} holds {why}\n {line.strip()[:96]}") - break # one report per pattern per file is enough to act on - for n, line in enumerate(lines, 1): + for n, line in enumerate(text.splitlines(), 1): if "—" in line: - problems.append(f"{rel(f)}:{n} holds an em dash\n {line.strip()[:96]}") + problems.append(f"{rel(f)}:{n} has an em dash\n {line.strip()[:96]}") break for m in LINK.finditer(text): - target = (f.parent / m.group(1)).resolve() - if not target.exists(): - problems.append(f"{rel(f)} links to {m.group(1)}, which does not exist") - - # Every subject document reachable from its component's entry point. - # Every page reachable from its component's README. - for entry in sorted(COMPONENTS.glob("*/README.md")): - subjects = [p for p in sorted(entry.parent.glob("*.md")) if p.name != "README.md"] - if not subjects: - continue - linked = {m.group(1) for m in LINK.finditer(entry.read_text())} - for s in subjects: - if s.name not in linked: - problems.append(f"{rel(s)} is not linked from {rel(entry)}; nobody will find it") - - # Every component has an entry point. - for comp in sorted(COMPONENTS.iterdir()): - if not comp.is_dir(): - continue + if not (f.parent / m.group(1)).resolve().exists(): + problems.append(f"{rel(f)} links {m.group(1)}, which does not exist") + + for readme in sorted(COMPONENTS.glob("*/README.md")): + pages = [p for p in sorted(readme.parent.glob("*.md")) if p.name != "README.md"] + linked = {m.group(1) for m in LINK.finditer(readme.read_text())} + for p in pages: + if p.name not in linked: + problems.append(f"{rel(p)} is not linked from {rel(readme)}") + + for comp in sorted(d for d in COMPONENTS.iterdir() if d.is_dir()): if not (comp / "README.md").exists(): - problems.append(f"{comp.name} has no README.md; it has no index") + problems.append(f"{comp.name} has no README.md") for p in problems: print(f" {p}") print() - print(f"{len(docs)} documents checked, {len(problems)} problems") + print(f"{len(files)} files checked, {len(problems)} problems") if problems: - print("These are documentation. See CONSTITUTION.md.") + print("See VOICE.md.") return 1 return 0 From 102afbee1827e8f1a06810db05ca9be0eec6d263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D7=A0=CF=85=CE=B1=CE=B7=20=D7=A0=CF=85=CE=B1=CE=B7=D1=95?= =?UTF-8?q?=CF=83=CE=B7?= Date: Wed, 30 Sep 2026 21:52:23 -0700 Subject: [PATCH 2/3] docs: delete history and the scripts, keep the voice in the skill history/ is gone. The knowledge is in the pages, which is what archiving it was for, and 15 frozen specs nobody maintains were a second place to look and a thousand em dashes. The reorg had also left components/osapi-justfiles/specs/ behind, which the git mv into history missed. scripts/ is gone: check-counts, check-docs and validate-skills, plus the skill-lint workflow that called a recipe that no longer exists. What replaces them is the skill and a reader. just test is markdown and justfile formatting now. Fourteen page footers cited ../../history/ paths that no longer resolve. Nothing caught them because they are backticked rather than links, which is the kind of thing a script would not have caught either. Spec Kit vocabulary is out of the prose. "The corpus" was never defined anywhere and an onboarding reading said so; it is "these docs" now. ARCHITECTURE.md claimed osapi's memory mentions neither the reconnection nor the logging behaviour, which stopped being true when transport.md was written, so it cites that page instead. Hardcoded counts are out of the README: six repositories, twelve subject pages, fifteen specifications. Each was wrong the day something changed, and the skills section right below them explains why no skill hardcodes an inventory. The voice is a reference in the document skill, where the skill that writes the docs reads it. It carries the required unslop pass. Three passages fixed: osapi-justfiles announced its own honesty before saying six words, osapi-orchestrator argued with the codebase's vocabulary for five sentences before defining either term, and ARCHITECTURE.md had a heading about where facts are filed rather than about the system. --- .github/workflows/skill-lint.yml | 22 --- AGENTS.md | 11 +- ARCHITECTURE.md | 19 ++- CONSTITUTION.md | 6 +- README.md | 33 ++-- components/gohai/README.md | 3 +- components/gohai/collectors.md | 3 +- components/nats-client/README.md | 3 +- components/nats-server/README.md | 3 +- components/osapi-justfiles/README.md | 5 +- components/osapi-orchestrator/README.md | 3 +- components/osapi/README.md | 3 +- components/osapi/agent-identity.md | 3 +- components/osapi/domains.md | 3 +- components/osapi/job-system.md | 3 +- components/osapi/providers.md | 3 +- components/osapi/sdk.md | 3 +- components/osapi/ui.md | 3 +- justfile | 23 --- scripts/check-counts.py | 201 ------------------------ scripts/check-docs.py | 78 --------- scripts/validate-skills.py | 122 -------------- 22 files changed, 38 insertions(+), 518 deletions(-) delete mode 100644 .github/workflows/skill-lint.yml delete mode 100644 scripts/check-counts.py delete mode 100644 scripts/check-docs.py delete mode 100755 scripts/validate-skills.py diff --git a/.github/workflows/skill-lint.yml b/.github/workflows/skill-lint.yml deleted file mode 100644 index 91478a0..0000000 --- a/.github/workflows/skill-lint.yml +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Skill Lint - -on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - - name: Install uv - uses: astral-sh/setup-uv@v7 - - name: Install just - uses: extractions/setup-just@v4 - - name: Fetch justfiles - run: just fetch - - name: Validate skills - run: just skill-lint diff --git a/AGENTS.md b/AGENTS.md index f390370..d0f3980 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,7 +23,7 @@ that fails here and passes in continuous integration, on a file nobody edited. Read the component's page under `components/` and the subjects it links. That is the standing description of how the component behaves and it is more current -than any prose written about it elsewhere, including anything in `history/`. +than any prose written about it elsewhere. Read [ARCHITECTURE.md](ARCHITECTURE.md) when the work touches how repositories fit together. A component's page describes only its own behaviour, so an @@ -38,13 +38,8 @@ both a request and the constitution, say so rather than picking one silently. **Run `unslop` over anything you write.** It applies to prose here the way `mdformat` applies to formatting. Em dashes are the tell it catches most often. -**A page explains a system to somebody who has to work on it.** No requirement -identifiers, no "MUST", no user stories, no acceptance scenarios. -`just check-docs` fails on all of those. - -**Every count carries the command that produces it.** `just check-counts` runs -all of them against the repository each page describes, so a count without a -command, or with a command that needs the line above it, fails. +**Use `/document` rather than writing a page by hand.** It carries the voice, +places the subject, and runs `unslop`. **Place content in the subject it belongs to.** A component's README is an index: what the repository is, and a table linking its subjects. Something about diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 2cb85ab..7959bce 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -11,11 +11,11 @@ drives the design: work is queued so the API does not block on twenty machines, results come back per host, and an agent has to prove who it is before anything targets it. -Six components, one product. osapi is the thing an operator runs; everything -else either supports it or drives it. +One product. osapi is the thing an operator runs; everything else either +supports it or drives it. -A seventh repository, `specs`, holds this documentation and is not a component. -It matters once below, because it consumes `osapi-justfiles` like the others do, +The `specs` repository holds this documentation and is not a component. It +matters once below, because it consumes `osapi-justfiles` like the others do, which is why the blast radius of a justfiles change is seven and not six. ``` @@ -62,8 +62,8 @@ Two things about that pairing matter and are stated in neither wrapper alone. whatever the upstream library does by default and osapi is not notified. `nats-server` forces debug and trace logging on and attaches its logger after the server is already running, so osapi's own startup logging from the bus goes -somewhere else. **osapi's memory mentions neither**, and an operator debugging a -transport problem needs both. +somewhere else. Both are in [the message bus](components/osapi/transport.md), +which is where somebody debugging the transport will look. ### The SDK surface @@ -90,8 +90,8 @@ used, and `.just/` is gitignored everywhere. `md` reaches all seven consumers, `just` six, `go` five, and `react` and `docusaurus` one each. So **`md.just` is the widest change available in the -organization.** The specs repository, whose `just test` gates every corpus -change, is downstream of it. +organization.** The specs repository, whose `just test` gates every change to +these docs, is downstream of it. ## Facts that span repositories @@ -137,5 +137,4 @@ repositories rather than components. `specs` is where components are described; ______________________________________________________________________ -Written from every repository's `go.mod` and justfile. History: -`history/system-001-repository-inventory/`. +Written from every repository's `go.mod` and justfile. diff --git a/CONSTITUTION.md b/CONSTITUTION.md index 19775f4..10eac51 100644 --- a/CONSTITUTION.md +++ b/CONSTITUTION.md @@ -217,7 +217,7 @@ stale.** Ageing was never the failure mode; the writer's frame was. A number alone is a claim that was true when somebody typed it, and nothing marks the moment it stops being true. -`just check-counts` runs every command in every measurement table against the +A count is written with the command that produces it, so a reader can run the repository it describes. Getting there fixed seven counts nobody could have checked: six written as "the same, plus `-not -name '*_test.go'`", which is a shortcut for whoever wrote the table and cannot be run by anything. @@ -226,11 +226,11 @@ Each command has to stand alone for that reason. ### A rule a document states is reachable from the repository it binds -Where the corpus states a rule somebody must follow, the repository they are +Where these docs state a rule somebody must follow, the repository they are working in states it too. The reason lives here and stays here; what this asks is that the rule be reachable from one checkout. -The measurement that produced it, at `osapi` on 2026-09-30: ten rules the corpus +The measurement that produced it, at `osapi` on 2026-09-30: ten rules these docs states and a contributor must follow, two of them stated in `osapi`'s own root documents and eight not. Two of the eight carry advisories, so the rules this organization learned the hardest are among those a checkout cannot show you. diff --git a/README.md b/README.md index e8eb2fa..656e335 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ # specs -The design docs for [osapi-io]. How the six repositories are built and why, in -one place, kept current as they change. No product code here. +The design docs for [osapi-io]. How each repository is built and why, in one +place, kept current as they change. No product code here. ## Usage @@ -24,13 +24,12 @@ all come out of that one choice. ``` components/ one page per repository, plus a page per subject -ARCHITECTURE.md how the six fit together, and what breaks what +ARCHITECTURE.md how they fit together, and what breaks what CONSTITUTION.md the rules every repository follows -history/ superseded specs, kept for the record ``` -Read [osapi](components/osapi/README.md) first. Twelve subject pages hang off -it, and four of the other five repositories either feed it or consume it. +Read [osapi](components/osapi/README.md) first. Its subject pages hang off it, +and four of the other five repositories either feed it or consume it. ## Doc-driven development @@ -53,18 +52,10 @@ like. ### What keeps it honest -`just test` runs two scripts and fails the build on either. -[check-counts](scripts/check-counts.py) takes every count in every page and runs -the command written beside it, in the repository that page describes, so a -number that moved breaks CI rather than sitting there wrong. -[check-docs](scripts/check-docs.py) fails on a dead link, a page missing from -its index, or a page that reads like a specification instead of documentation. - -Neither catches prose that drifted from the code. A reader does: hand somebody -the page and nothing else, ask them the question it claims to answer, and fix -what they could not work out. That has found more than both scripts together, -including seven permissions where a page said one, thirteen struct fields where -it said fourteen, and a bucket TTL described backwards. +A reader. Hand somebody the page and nothing else, ask them the question it +claims to answer, and fix what they could not work out. That has found seven +permissions where a page said one, thirteen struct fields where it said +fourteen, and a bucket TTL described backwards. ## The design docs @@ -105,12 +96,6 @@ and wrong after the next change, with nothing marking the moment. Each follows the [Agent Skills] format: a slim `SKILL.md` that routes, with the detail in reference files an agent reads only when the question calls for them. -## history/ - -Fifteen specifications written under a workflow this repository no longer uses. -Kept because they record what was decided and when, not maintained, and where -one disagrees with a page under `components/` the page is right. - ## Contributing See the [Contributing](CONTRIBUTING.md) guide for prerequisites, the test for diff --git a/components/gohai/README.md b/components/gohai/README.md index 0b686af..9a9d62c 100644 --- a/components/gohai/README.md +++ b/components/gohai/README.md @@ -200,5 +200,4 @@ gohai's testing conventions, which are its own `CONTRIBUTING.md`'s. ______________________________________________________________________ -Written from the gohai repository. History: -`../../history/gohai-001-gohai-baseline/`. +Written from the gohai repository. diff --git a/components/gohai/collectors.md b/components/gohai/collectors.md index d26a7ec..eda0243 100644 --- a/components/gohai/collectors.md +++ b/components/gohai/collectors.md @@ -162,5 +162,4 @@ table counts. ______________________________________________________________________ -Written from `pkg/gohai/collectors/` and `schemas/field-mapping.md`. History: -`../../history/gohai-002-move-contributor-docs/`. +Written from `pkg/gohai/collectors/` and `schemas/field-mapping.md`. diff --git a/components/nats-client/README.md b/components/nats-client/README.md index 1fb476e..6d1b3e0 100644 --- a/components/nats-client/README.md +++ b/components/nats-client/README.md @@ -152,5 +152,4 @@ question. ______________________________________________________________________ -Written from `pkg/client/`. History: -`../../history/nats-client-001-nats-client-baseline/`. +Written from `pkg/client/`. diff --git a/components/nats-server/README.md b/components/nats-server/README.md index b7ffe79..7f9aec8 100644 --- a/components/nats-server/README.md +++ b/components/nats-server/README.md @@ -143,5 +143,4 @@ question. ______________________________________________________________________ -Written from `pkg/server/`. History: -`../../history/nats-server-001-nats-server-baseline/`. +Written from `pkg/server/`. diff --git a/components/osapi-justfiles/README.md b/components/osapi-justfiles/README.md index 2e035b8..39a3057 100644 --- a/components/osapi-justfiles/README.md +++ b/components/osapi-justfiles/README.md @@ -35,7 +35,7 @@ in the consuming repository. So `md` reaches all seven, `just` six, `go` five, and `react` and `docusaurus` one each. **`md` is the widest change available**, and `specs`, whose -`just test` gates every corpus change in the organization, is downstream of it. +`just test` gates every change to these docs, is downstream of it. Take the consumer list from `gh repo list osapi-io --no-archived --visibility public` and read each `fetch` @@ -190,5 +190,4 @@ a recipe does what its name suggests. ______________________________________________________________________ -Written from the osapi-justfiles repository. History: -`../../history/osapi-justfiles-001-justfiles-baseline/`. +Written from the osapi-justfiles repository. diff --git a/components/osapi-orchestrator/README.md b/components/osapi-orchestrator/README.md index 9dbaed4..7eaf896 100644 --- a/components/osapi-orchestrator/README.md +++ b/components/osapi-orchestrator/README.md @@ -191,5 +191,4 @@ Whether eight guards and ten predicates are the right eight and ten. ______________________________________________________________________ -Written from `pkg/orchestrator/` and `internal/engine/`. History: -`../../history/osapi-orchestrator-001-orchestrator-baseline/`. +Written from `pkg/orchestrator/` and `internal/engine/`. diff --git a/components/osapi/README.md b/components/osapi/README.md index d45d411..87b3c91 100644 --- a/components/osapi/README.md +++ b/components/osapi/README.md @@ -105,5 +105,4 @@ osapi's testing conventions, which are its own `CONTRIBUTING.md`'s. ______________________________________________________________________ -Written from the osapi repository. History: -`../../history/osapi-006-osapi-baseline/`. +Written from the osapi repository. diff --git a/components/osapi/agent-identity.md b/components/osapi/agent-identity.md index f7b73ee..99488c0 100644 --- a/components/osapi/agent-identity.md +++ b/components/osapi/agent-identity.md @@ -96,5 +96,4 @@ lifecycle, is [the job system](job-system.md). ______________________________________________________________________ -Written from `internal/agent/` and `internal/job/registration.go`. History: -`../../history/osapi-002-agent-key-store/`. +Written from `internal/agent/` and `internal/job/registration.go`. diff --git a/components/osapi/domains.md b/components/osapi/domains.md index 8a19cb1..d32682e 100644 --- a/components/osapi/domains.md +++ b/components/osapi/domains.md @@ -157,5 +157,4 @@ The generated client the combined specification also feeds is ______________________________________________________________________ -Written from `internal/controller/api/` and `cfg.yaml`. History: -`../../history/osapi-005-building-a-domain/`. +Written from `internal/controller/api/` and `cfg.yaml`. diff --git a/components/osapi/job-system.md b/components/osapi/job-system.md index c3ecb62..8d64b55 100644 --- a/components/osapi/job-system.md +++ b/components/osapi/job-system.md @@ -216,5 +216,4 @@ to call, is [building a domain](domains.md). ______________________________________________________________________ -Written from `internal/job/`, `internal/agent/` and `cmd/root.go`. History: -`../../history/osapi-004-job-system/`. +Written from `internal/job/`, `internal/agent/` and `cmd/root.go`. diff --git a/components/osapi/providers.md b/components/osapi/providers.md index 091ad52..258c43a 100644 --- a/components/osapi/providers.md +++ b/components/osapi/providers.md @@ -140,5 +140,4 @@ artifacts that come with it, is [building a domain](domains.md). ______________________________________________________________________ -Written from `internal/provider/`. History: -`../../history/osapi-001-provider-contract/`. +Written from `internal/provider/`. diff --git a/components/osapi/sdk.md b/components/osapi/sdk.md index ec46dd9..d9d0f88 100644 --- a/components/osapi/sdk.md +++ b/components/osapi/sdk.md @@ -131,5 +131,4 @@ different generator, and is [the embedded UI](ui.md). ______________________________________________________________________ -Written from `pkg/sdk/client/`. History: -`../../history/osapi-005-building-a-domain/`. +Written from `pkg/sdk/client/`. diff --git a/components/osapi/ui.md b/components/osapi/ui.md index b1dd623..10d6206 100644 --- a/components/osapi/ui.md +++ b/components/osapi/ui.md @@ -81,5 +81,4 @@ combined specification is invisible to it, is [building a domain](domains.md). ______________________________________________________________________ -Written from `ui/` and `internal/controller/api/ui/`. History: -`../../history/osapi-007-the-embedded-ui/`. +Written from `ui/` and `internal/controller/api/ui/`. diff --git a/justfile b/justfile index 30f78b8..c196cd0 100644 --- a/justfile +++ b/justfile @@ -22,37 +22,14 @@ fetch: curl -sSfL https://raw.githubusercontent.com/osapi-io/osapi-justfiles/refs/heads/main/md/md.just -o .just/remote/md.just curl -sSfL https://raw.githubusercontent.com/osapi-io/osapi-justfiles/refs/heads/main/just/just.just -o .just/remote/just.just -# --- Checks --- - -# Validate every SKILL.md against the Agent Skills specification -[group('lint')] -skill-lint: - uvx --with pyyaml python scripts/validate-skills.py - -# Run every count in components/ against the command stated beside it -[group('lint')] -check-counts: - python3 scripts/check-counts.py - -# Links resolve, pages are linked, no em dashes -[group('lint')] -check-docs: - python3 scripts/check-docs.py - # --- Top-level orchestration --- # Run all checks test: just md-fmt-check just just-fmt-check - just skill-lint - just check-counts - just check-docs # Format and lint before committing ready: just md-fmt just just-fmt - just skill-lint - just check-counts - just check-docs diff --git a/scripts/check-counts.py b/scripts/check-counts.py deleted file mode 100644 index 3ea2ebe..0000000 --- a/scripts/check-counts.py +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/bin/env python3 -"""Run every count in every component's memory against the command beside it. - -`global/baseline` says a count is written with the command that produces it, -because a number alone is a claim that was true when somebody typed it. This -turns that from a convention into a check: it finds the measurement tables in -`components/*/`, runs each command in the repository the -memory describes, and fails when a value has moved. - -A memory table row looks like this: - - | Go files | 12 | `find . -name '*.go' -not -path './.git/*' \\| wc -l` | - -Three columns, the middle one an integer, the third a command in backticks. -Rows whose command is prose ("the same, plus ...") inherit the previous row's -command and append their own difference, so they are resolved against the row -above. - -Exit codes: 0 all counts current, 1 a count has moved, 2 a command could not -run. A missing target repository is skipped with a note rather than failed, -because a contributor without every clone is not a broken corpus. -""" - -from __future__ import annotations - -import re -import subprocess -import sys -from pathlib import Path - -REPO_PARENT = Path.home() / "git" / "osapi-io" -SPECS = Path(__file__).resolve().parent.parent - -ROW = re.compile(r"^\|\s*(?P