diff --git a/docs/apply-archive.md b/docs/apply-archive.md index 6f5fc12..e963c9f 100644 --- a/docs/apply-archive.md +++ b/docs/apply-archive.md @@ -2,130 +2,31 @@ These two commands are the enforcement surface. `apply` is the gate an agent must clear before writing code; `archive` is the verified ship step. Both are -deterministic and both are documented here as a user-facing contract — the -generated skills only ever tell the agent to run the command and obey its exit -code. - -## Exit codes - -| code | meaning | -| ---- | ----------------------------------------------------------------------------------------------- | -| `0` | success (including "nothing to do") | -| `1` | failure — validation errors, verification failure, unknown item, parse error, drift/usage error | -| `2` | blocked (`apply` only) — missing required artifacts **or** unchecked hard blockers | -| `3` | soft-blocked (`apply` only) — unconfirmed soft blockers; re-run with `--allow-soft` | - -## `cospec apply [--allow-soft] [--json]` - -1. Resolve the change (unknown → exit 1 with a fuzzy suggestion) and its schema. - A legacy schema delegates `openspec instructions apply --json` verbatim with - no gate and an INFO note. -2. Run full validation in fast mode. Errors → exit 1 with the report. -3. Compute missing artifacts from `schema.apply.requires`. If any are missing → - print them, set JSON `gate.reason: "missing-artifacts"`, exit 2. -4. **The blocker gate** (deterministic): - - Parse `blocking-changes.md` (a parse error → exit 1). - - Build the archive index from `openspec/changes/archive/` dirs matching - `^(\d{4}-\d{2}-\d{2})-(.+)$` (duplicate slug → keep the latest date, warn). - - **Self-heal**: for each unchecked entry whose slug is in the archive index, - rewrite `[ ]` → `[x]`, append `*(archived )*` if absent, normalize - the separator to an em-dash, and record it in `gate.synced`. Atomic write. - - Remaining unchecked **Blocked by** entries are hard blockers → print each - (noting whether the slug is an active change), set - `gate.reason: "hard-blockers"`, exit 2. - - Remaining unchecked **Soft-blocked by** entries: without `--allow-soft`, - print each and exit 3; with `--allow-soft`, record `gate.softAcknowledged`. -5. Fetch `openspec instructions apply --json` (exit and JSON shape checked). -6. Emit merged output and exit 0: - -```json -{ - "change": "add-widget", - "type": "feat", - "gate": { - "state": "clear", - "hardBlockers": [], - "softAcknowledged": [], - "synced": [] - }, - "apply": { - "state": "...", - "contextFiles": [], - "progress": {}, - "tasks": [], - "instruction": "..." - } -} -``` - -The gate is enforced twice: here as an exit code an agent cannot rationalize -past, and in schema prose that says only "run this command and obey its exit -code." Dangling slugs cannot false-pass — `blockers/dangling-ref` fails -validation in step 2. - -## `cospec archive [-y] [--skip-specs] [--force-incomplete] [--json]` - -### Pre-flight - -1. Resolve the change and schema (a legacy schema still runs steps 5–11; step 2 - delegates validation). -2. Run **full** validation, including the archive-precondition family unless - `--skip-specs`. Errors → exit 1. This makes step 9's abort detection a - should-never-fire invariant, not the primary defense. -3. **Tasks gate**: parse `tasks.md`. Unchecked tasks and no `--force-incomplete` - → exit 1 listing them. This is stricter than OpenSpec by design: `-y` alone - does not waive incomplete tasks, so automation passing `-y` cannot skip work. -4. **Self-blocker sanity**: unchecked hard blockers in this change's own file → - a WARNING (not fatal — aborted or superseded work gets archived too). -5. **Collision pre-check**: an existing `archive/` dir matching - `^\d{4}-\d{2}-\d{2}-$` with today's date → exit 1 before delegating. -6. **Skip-specs decision**: pass `--skip-specs` to OpenSpec when the user passed - it, the schema declares no specs artifact, or no `specs/**/spec.md` files - exist. Light types therefore never enter the delta-merge path. -7. **Snapshot**: the archive dir basenames and, if merging, the parsed delta ops - per capability. - -### Execute - -8. Spawn `openspec archive -y [--skip-specs] --no-color`; capture stdout, - stderr, and exit code. - -### Verify (the archive verifier) - -9. Compute the new archive dirs. The target is the unique new dir matching - `/^\d{4}-\d{2}-\d{2}-$/` — **date-agnostic**, so it survives a midnight - rollover. Success requires all of: exit code 0; stdout matching neither - `Aborted` nor `Archive cancelled`; the source change directory gone; and the - target plus its `.openspec.yaml` present. On failure: - - clean abort (source not moved, no new dirs) → print the honest message plus - OpenSpec's captured output verbatim, indented, and the `--skip-specs` - remedy; - - half-state (moved without target, or target without moved) → print exactly - which invariant broke and instruct manual inspection. - - exit 1. -10. **Post-merge spot-check** (skipped when `--skip-specs` / no deltas): for - each snapshotted op, verify the living spec — ADDED present, REMOVED absent, - RENAMED to-name present and from-name absent, MODIFIED present. Any miss → - exit 1 with a "spec merge verification failed" message naming the misses. - -### Post - -11. Run `sync-blockers` in fix mode across all remaining active changes, - collecting which entries were checked and which changes became fully - unblocked. -12. Print the flywheel summary and exit 0: - -``` -Archived: add-widget (feat) → openspec/changes/archive/2026-07-03-add-widget/ -Specs: +2 ~1 -0 →0 applied and verified -Blockers: checked off in 1 change(s): add-dashboard -Now unblocked: add-dashboard → next: cospec apply add-dashboard -``` +deterministic — the generated skills only ever tell the agent to run the command +and obey its exit code. + +The full user-facing contract — the exit-code table, the step-by-step order +`apply` and `archive` run in, the `--json` shapes, and the two hard archive +gates — is owned by the site: +[Apply and archive](https://cospec.aligned.team/concepts/apply-and-archive). +Read that page for "what happens when I run this command"; this page covers what +isn't there. + +## Why the gate is enforced twice + +The `apply` gate is enforced in two places that never get out of sync by +construction: as an exit code an agent cannot rationalize past, and in the +schema instruction prose, which says only "run this command and obey its exit +code" — never a paraphrase of the gate logic an agent could talk itself past. +Dangling blocker slugs cannot false-pass either: `blockers/dangling-ref` fails +validation before the gate is even evaluated (see +[validation.md](validation.md)). ## Blocker sync `cospec sync-blockers [--check] [--change ] [--json]` is the standalone form -of archive step 11 and is wired into the pre-commit hook (a fix/check pair). See -[blocking-changes.md](blocking-changes.md) for the STALE / DANGLING / -MANUAL-CHECK / FORMAT classes and the exit rules. `fix` is the default mode; fix -idempotence (`fix(fix(x)) == fix(x)`) is a tested property. +of `archive`'s final step, and is wired into the pre-commit hook as a fix/check +pair. The single parser both `apply`'s blocker gate and `sync-blockers` share +lives in `core/blockers.ts` — see [blocking-changes.md](blocking-changes.md) for +its grammar. `fix` is the default mode; fix idempotence +(`fix(fix(x)) == fix(x)`) is a tested property. diff --git a/docs/architecture.md b/docs/architecture.md index 80c0bbc..7d149c2 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -99,32 +99,16 @@ commands use. ## The failure modes cospec defends against cospec exists because three OpenSpec behaviors are hazardous when an agent is -driving. - -### 1. `openspec validate` false-errors on schemas without deltas - -OpenSpec has a hardcoded `CHANGE_NO_DELTAS` rule: a change with no spec deltas -fails validation. But a `ci` or `docs` change legitimately has no deltas. cospec -runs its own rule families over every change and only delegates to -`openspec validate` for changes whose schema declares a `specs` artifact and -that actually have delta files — so the one suppressed check is suppressed only -where satisfying it is definitionally wrong. See [validation.md](validation.md). - -### 2. `openspec archive` exits 0 but silently aborts - -When a delta cannot merge (a MODIFIED target that does not exist, a zero-op -delta), OpenSpec prints `Aborted` and **exits 0 without moving the change**. An -agent trusting the exit code would believe the change shipped. cospec's archive -verifier (see [apply-archive.md](apply-archive.md)) checks the filesystem -directly — the change directory must be gone and a dated archive entry must -exist — and reports the abort honestly. Archive preconditions are also checked -at validate time, moving the failure left. - -`openspec archive` also exits 0 while silently **thinning** a spec: a MODIFIED -delta that drops `#### Scenario:` entries merges cleanly with no complaint. -cospec closes this with `archive/scenario-preservation` (below). - -### 2a. The archive gate ordering (two hard pre-delegation steps) +driving — `openspec validate` false-erroring on schemas without deltas, +`openspec archive` exiting 0 while silently aborting or thinning a spec, and +OpenSpec's generated files referencing skills it never generates. The +user-facing account of all three, and why each matters, is owned by the site: +[How cospec relates to OpenSpec](https://cospec.aligned.team/concepts/how-it-relates-to-openspec). +What follows is the implementation detail behind the two archive-time defenses — +the exact gate ordering and the dangling-reference guard — which the site +intentionally doesn't carry. + +### The archive gate ordering (two hard pre-delegation steps) `cospec archive`'s steps are, in order: fast-validate → tasks gate → **`archive/verification-incomplete`** → self-blocker sanity warning → collision diff --git a/docs/blocking-changes.md b/docs/blocking-changes.md index 7321e72..99a4182 100644 --- a/docs/blocking-changes.md +++ b/docs/blocking-changes.md @@ -5,29 +5,12 @@ must ship before this one. It is machine-parsed: the `apply` gate reads it, and `sync-blockers` keeps it current as dependencies archive. One parser (`core/blockers.ts`) serves validate, apply, archive, and sync. -## The template - -Every type ships the same template (the instructions differ by weight, the -format does not): - -```markdown -# Dependencies - -## Blocked by - - - - - -None. - -## Soft-blocked by - - - - -None. -``` +The user-facing account — the template, hard vs. soft sections, how `apply` +gates on it, and the STALE/DANGLING/MANUAL-CHECK/FORMAT sync diagnostics — is +owned by the site: +[Blocking changes](https://cospec.aligned.team/concepts/blocking-changes). This +page keeps the exact machine grammar `core/blockers.ts` implements, since the +site describes it in prose rather than as a parseable spec. ## The grammar @@ -57,36 +40,15 @@ Two sections are machine-gated. Extra sections (`## Phase Gates`, - **Outside** the two gated sections everything is ignored by the gate, but backticked-slug bullets missing a checkbox are linted (WARNING). -Examples: +## Sync internals -```markdown -## Blocked by - -- [ ] `add-auth` — the session token this endpoint reads -- [x] `add-db-pool` — the connection pool _(archived 2026-06-30)_ - -## Soft-blocked by - -None. -``` - -## Sync semantics - -`cospec sync-blockers [--check] [--change ] [--json]` — `fix` is the -default. +`cospec sync-blockers [--check] [--change ] [--json]` (`fix` is the +default): 1. Build the archive index (`archive/` dirs → slug → date; duplicate slug → latest date + warning; non-matching dirs → warning, ignored). 2. Build the active index (dirs under `changes/` except `archive/`). -3. For each active change with a `blocking-changes.md`, classify each entry: - - **STALE** — unchecked, target archived. `fix`: rewrite to the canonical - checked form with `*(archived )*`, atomic write. `--check`: report. - - **DANGLING** — unchecked, target neither archived nor active. Error; never - auto-fixed. - - **MANUAL-CHECK** — checked but target not archived. Warning (manual - check-off is allowed, just surfaced). - - **FORMAT** — a backticked-slug bullet without a checkbox in a gated - section. Error. Non-canonical separators are normalized in fix mode. +3. Classify each entry per the site's diagnostic classes, then fix or report. 4. Report changes that are now fully unblocked (all Blocked-by checked, or `None.`). 5. Exit: `--check` → 1 if any STALE / DANGLING / FORMAT; `fix` → 1 only if @@ -94,12 +56,3 @@ default. This runs standalone, as `cospec archive`'s post step across all remaining changes, and in the pre-commit hook as a fix/check pair. - -## How apply uses it - -`cospec apply` self-heals the ledger before gating: any unchecked entry whose -slug is already archived is checked off in place (and reported in -`gate.synced`), so a just-shipped dependency never blocks the next change on a -stale box. What remains unchecked under **Blocked by** is a hard block (exit 2); -what remains under **Soft-blocked by** is a soft block (exit 3 without -`--allow-soft`). See [apply-archive.md](apply-archive.md). diff --git a/docs/harness-integration.md b/docs/harness-integration.md index 4fce23c..cded24e 100644 --- a/docs/harness-integration.md +++ b/docs/harness-integration.md @@ -17,24 +17,11 @@ in `canon/workflows/*.md` and rendered per harness. This is cospec's full opsx below). cospec has no core/custom profile split: it always emits the full eleven to every configured harness. -``` -Claude Code: - .claude/commands/cospec/{propose,new,continue,ff,apply,verify,archive,bulk-archive,sync-specs,explore,onboard}.md - .claude/skills/cospec-{propose,new-change,continue-change,ff-change,apply-change,verify-change,archive-change,bulk-archive-change,sync-specs,explore,onboard}/SKILL.md - .claude/settings.json # additive permissions merge - -Codex (project-level only): - .codex/skills/cospec-{same 11}/SKILL.md - .codex/rules/cospec.rules # pre-approves read-only + gate cospec calls - -OpenCode: - .opencode/commands/cospec-{eleven}.md # /cospec-propose … — FULL bodies, work with .claude absent - .opencode/skills/cospec-{same 11}/SKILL.md -``` - -Slash syntax is substituted per harness (`/cospec:x` ↔ `/cospec-x`). Every body -calls **only** `cospec` commands — never bare `openspec` — so one permission -entry covers the whole loop. +The exact per-harness file tree each `cospec init` writes, the slash-syntax +substitution, the restart/reload notes, and the smoke-test checklist are owned +by the site: [Harness setup](https://cospec.aligned.team/guide/harness-setup). +This page covers what each generated workflow body actually does and the canon +internals behind it — content the site intentionally keeps at a higher level. ## What each workflow does @@ -165,12 +152,7 @@ merged entry. If it does not parse, cospec prints the snippet and skips. ## Per-harness smoke checklist Codex and OpenCode project-level skill loading is inferred from real repos, not -vendor docs. All enforcement lives in the CLI, so a half-loaded skill still -cannot bypass a gate — but confirm loading manually after `init`: - -1. **Claude Code** — restart; `/cospec:propose` appears in the command list; - `Bash(cospec *)` is in `.claude/settings.json`. -2. **Codex** — start a session; the `cospec-*` skills are listed; a read-only - `cospec status` runs without an approval prompt (`archive` still prompts). -3. **OpenCode** — reload; `/cospec-propose` runs and drives the loop even with - `.claude/` absent (OpenCode bodies are full, not pointers). +vendor docs — that inference is why this note exists here rather than only on +the site. All enforcement lives in the CLI, so a half-loaded skill still cannot +bypass a gate. The actual checklist to run after `init` is owned by the site: +[Harness setup](https://cospec.aligned.team/guide/harness-setup#smoke-checks). diff --git a/docs/schemas.md b/docs/schemas.md index 7835d08..1a64de1 100644 --- a/docs/schemas.md +++ b/docs/schemas.md @@ -4,146 +4,31 @@ cospec ships eleven schemas — one per conventional-commit type. A change's typ is its schema, recorded as `schema: ` in `.openspec.yaml` and written by `cospec new`. There are no name prefixes and no parallel metadata. -## The six canonical artifacts - -Every schema is composed from the same six hand-authored artifacts. Types differ -only in which artifacts they declare and require, and in which instruction -variant they use. - -| artifact | generates | role | -| ------------------ | --------------------- | --------------------------------------------------------- | -| `proposal` | `proposal.md` | Why / What Changes / (Capabilities) / (Surfaces) / Impact | -| `blocking-changes` | `blocking-changes.md` | cross-change dependency ledger | -| `specs` | `specs/**/*.md` | delta specs (ADDED/MODIFIED/REMOVED/RENAMED) | -| `design` | `design.md` | Context / Goals / Decisions / Risks | -| `verification` | `verification.md` | per-behavior acceptance-evidence ledger (machine-parsed) | -| `tasks` | `tasks.md` | numbered checkbox groups tracked during apply | +The user-facing facts — the six canonical artifacts, the full artifact matrix +(R/O/O(trig)/F per type), the `## Surfaces` block and its token vocabulary, and +the per-type rationale for why each type requires what it requires — are owned +by the site: +[Types and artifacts](https://cospec.aligned.team/concepts/types-and-artifacts). +`verification`'s row grammar and per-type required-row rules are owned by +[Verification](https://cospec.aligned.team/concepts/verification). This page +covers the canon internals behind those pages: where the matrix is authored, how +it's kept from drifting, and the schema-forking escape hatch's mechanics. `quality.md` and `rollout.md` were considered and rejected as vendor-coupled or duplicative of `verification`/`design`. Teams that want more use `openspec/config.yaml` (`context`/`rules`) or fork a schema with `cospec schema fork` (see [Customization tiers](#customization-tiers)). -### `verification` — the acceptance-evidence ledger - -`verification` closes the "green CI, broken behavior" gap: every observable -behavior gets a group, and every row names the **layer** it must be exercised -at, **who** runs it, and the **recorded evidence**. It reuses the checkbox lexer -(`## N. Group` / `- [ ] N.M …`), the same house grammar as `tasks.md`, rather -than introducing YAML frontmatter. - -``` -## 1. [critical] -- [ ] 1.1 @e2e (agent) drive the real flow end to end -> -- [~] 1.2 @manual (human) exercise the interactive surface -> defer: -``` - -- **Group** `## N. ` — one observable behavior; optional trailing - `[critical]` marks a behavior whose breakage ships a broken product. -- **Row state** `[ ]` planned, `[x]` verified-with-evidence, `[~]` deferred - (requires `defer: `). -- **`@`** — a closed vocabulary: - `@unit @integration @e2e @manual @runtime @regression @equivalence @benchmark @eval`. - Extend per project via `openspec/config.yaml` (`verification.layers`); an - unknown token is fail-closed (`verification/layer-unknown`). -- **`()`** — optional, `(agent)` or `(human)`. Default: `@manual` ⇒ - `(human)`, everything else ⇒ `(agent)`. A `(human)`/`@manual` row is flagged - in reports as CI-uncatchable. -- **` -> `** is required on every row; a checked (`[x]`) row's result - must be non-empty (`verification/evidence-required`) — that is what makes - "recorded evidence before archive" mechanical. - -Each type that requires `verification` has a distinct required-row fact — `feat` -needs a non-`@unit` row on every `[critical]` group, `fix` needs an -`@regression` row, `perf` needs both `@benchmark` and `@equivalence`, `refactor` -needs `@equivalence`. See [validation.md](validation.md) for the full rule -table. - -### `proposal` → `## Surfaces` - -Every type except `chore`/`docs`/`style`/`test` gains a closed, all-optional -`## Surfaces` checkbox block in its proposal template: - -``` -## Surfaces -- [ ] interactive — a user-visible/interactive surface (UI, TUI, CLI UX) -- [ ] deploy — deploy/runtime/CI-execution topology -- [ ] integration — a third-party/external contract -- [ ] agent-behavior — prompts, tools, model routing, agent output shape -``` - -These are the only four tokens the block accepts (`proposal/surfaces-vocab`). -Checking a flag never hard-requires anything — it soft-promotes a `verification` -row or a `design` section (see [validation.md](validation.md) -`meta/surface-unmet`, `design/*`). An unchecked block changes nothing; the four -no-surface light types omit the block entirely. +## Where the matrix is authored, and how drift is caught -## The artifact matrix - -Each type declares each artifact in one of four states: - -- **R** (required) — declared and in `apply.requires`; mechanically gated. -- **O** (optional) — declared, not in `apply.requires`; instructions and - templates are available and `status` shows it, but `apply` does not gate on - it. -- **O(trig)** (optional, soft-promoted) — declared, not in `apply.requires`, but - a checked `## Surfaces` flag turns its absence into an exit-3 soft blocker at - `apply` (`--allow-soft` clears it) and a WARNING at `validate` (ERROR under - `--strict`). Never a hard require — the static matrix never changes shape at - gate time. -- **F** (forbidden) — not declared; `cospec validate` raises - `meta/forbidden-artifact` if the file exists. - -| type | proposal | blocking-changes | specs | design | **verification** | tasks | `apply.requires` | -| ---------- | -------- | ---------------- | ----- | ------ | ---------------- | ----- | ------------------------------------------------------- | -| `feat` | R (full) | R (full) | **R** | O | **R** | R | proposal, blocking-changes, specs, verification, tasks | -| `fix` | R (full) | R (full) | O | O | **R** | R | proposal, blocking-changes, verification, tasks | -| `perf` | R (full) | R (full) | O | O | **R** | R | proposal, blocking-changes, verification, tasks | -| `refactor` | R (full) | R (full) | O | **R** | **R** | R | proposal, blocking-changes, design, verification, tasks | -| `revert` | R (full) | R (full) | O | F | O(trig) | R | proposal, blocking-changes, tasks | -| `build` | R (lite) | R (lite) | F | F | O(trig) | R | proposal, blocking-changes, tasks | -| `ci` | R (lite) | R (lite) | F | F | O(trig) | R | proposal, blocking-changes, tasks | -| `chore` | R (lite) | R (lite) | F | F | F | R | proposal, blocking-changes, tasks | -| `docs` | R (lite) | R (lite) | F | F | F | R | proposal, blocking-changes, tasks | -| `style` | R (lite) | R (lite) | F | F | F | R | proposal, blocking-changes, tasks | -| `test` | R (lite) | R (lite) | F | F | F | R | proposal, blocking-changes, tasks | - -Only the **verification** column and the four `apply.requires` rows for -feat/fix/perf/refactor changed from the five-artifact matrix. Every other cell -is unchanged. `TYPE_ARTIFACTS` in `apps/cli/src/core/rules/type-facts.ts` -mirrors this table by hand; a matrix-parity test -(`apps/cli/test/unit/schemas/matrix-parity.test.ts`) asserts it is -byte-identical to what `schema-compose.ts` composes from canon, across all 11 -types × 6 artifacts — closing the "two hand-mirrored matrices, no cross-check" -risk this expansion doubled down on. - -## Per-type rationale - -- **feat is gated on specs and on verification's `critical-real-layer` rule.** A - new feature that changes no capability's spec is almost always mis-typed. A - `[critical]` behavior group whose only row is `@unit` is exactly the "verified - the wrong layer" failure mode (mocked auth, unhydrated islands, orphaned - wiring) this rule exists to catch. -- **fix specs are optional; verification's `reproduces-bug` rule is not.** A fix - whose defect the suite never actually exercised is worthless — the required - `@regression` row forces failing-before/passing-after evidence. Create a spec - delta only when the bug means the living spec itself was wrong. -- **perf keeps behavior identical and now proves it twice.** The proposal still - needs `## Benchmarks`; verification's `equivalence` rule additionally requires - both a `@benchmark` row (ties to the benchmark) and a separate `@equivalence` - row (the speedup did not change behavior). -- **refactor requires design, allows only RENAMED specs, and proves the - invariant.** `design.md` is required and gains a mandatory `## Seam ownership` - section; verification's `invariant` rule requires an `@equivalence` row (the - existing suite passes unchanged) — the tokenmania seam-drift failure mode. -- **revert / build / ci soft-promote verification, never require it.** These - stay light by covenant; checking `## Surfaces` `deploy` (build/ci) or any flag - (revert) nudges verification into the soft-blocker set at apply, but a - flag-free change stays a two-minute, verification-free ceremony. -- **chore / docs / style / test forbid verification entirely.** These types have - no runtime behavior surface to exercise — a `test` change is its own - verification, `style` is "no semantic diff." Forbidding (not "optional") is - what protects the covenant; these four also omit `## Surfaces`. +`TYPE_ARTIFACTS` in `apps/cli/src/core/rules/type-facts.ts` mirrors the matrix +on the site by hand — it is a second, independent hand-authored surface from +`apps/cli/src/canon/types/*.yaml` (composed into `schema.yaml` by +`schema-compose.ts`). A matrix-parity test +(`apps/cli/test/unit/schemas/matrix-parity.test.ts`) asserts the two are +byte-identical — `declared` and `apply.requires` — across all 11 types × 6 +artifacts, closing the "two hand-mirrored matrices, no cross-check" risk that +adding `verification` as a dimension doubled down on. ## The requires graph @@ -162,61 +47,43 @@ Optional artifacts are never in another artifact's `requires` — that would deadlock the next-artifact computation. Every schema sets `apply.tracks: tasks.md`. -## Schema versioning and grandfathering +## Schema versioning internals The composer stamps `version: 2` into every schema (bumped from `1` when -`verification` was introduced). `cospec new` stamps the change's -`.openspec.yaml` with `schemaVersion: 2`; a change created before this expansion -has no stamp and is treated as `schemaVersion: 1`. - -`apply` and `archive` both compute `enforcedApplyRequires(type, schemaVersion)` -— the static `apply.requires` set above, filtered to artifacts whose -`introducedAt(artifact, type)` is `<= schemaVersion`. `verification` is -`introducedAt = 2` for feat/fix/perf/refactor and `1` for everything else, so a -`schemaVersion: 1` change is never gated on `verification` — no in-flight change -is retroactively blocked by this expansion. `cospec validate` emits a -non-blocking `meta/schema-outdated` INFO naming `cospec migrate` for any change -still on `schemaVersion: 1`. `cospec migrate ` scaffolds a deferred -`verification.md` (`[~] defer: pre-v2 change, verified out-of-band` on every -row) and bumps the stamp to 2; `cospec doctor` lists changes still on -`schemaVersion: 1`. Nothing runs this automatically — migration is opt-in, one -change at a time. +`verification` was introduced). `apply` and `archive` both compute +`enforcedApplyRequires(type, schemaVersion)` — the static `apply.requires` set, +filtered to artifacts whose `introducedAt(artifact, type)` is +`<= schemaVersion`. `verification` is `introducedAt = 2` for +feat/fix/perf/refactor and `1` for everything else, so a `schemaVersion: 1` +change is never gated on `verification` — no in-flight change is retroactively +blocked by a schema expansion. See the site's +[grandfathering section](https://cospec.aligned.team/concepts/types-and-artifacts#schema-versioning-and-grandfathering) +for the user-facing behavior (`cospec migrate`, the `meta/schema-outdated` +INFO). ## Customization tiers -Three strictly separated tiers: - -1. **cospec-managed** — `openspec/schemas/**`, harness files, and gate files. - Regenerated by `cospec update` and protected by the managed-file protocol - (see [harness-integration.md](harness-integration.md)). Never hand-edit; - header comments point you to the right customization channel. -2. **user-owned** — `openspec/config.yaml` (`context` plus per-artifact `rules`, - which OpenSpec injects into instructions natively) and all change and spec - content. Never rewritten by cospec. -3. **escape hatch** — `cospec schema fork [name]` (defaults the - destination to `-custom`) and `cospec schema init ` create a - project-local schema under `openspec/schemas//`. Both are disciplined - passthroughs to the wrapped binary, guarded by one cospec-side check: a - fork/init whose destination name is one of the 11 canon types is refused with - exit 1 before the binary is ever spawned, so a fork can never overwrite a - canon-managed `schema.yaml`. cospec never regenerates or manages a forked - schema — it is yours to edit. - - Run a change on a fork with `cospec new ` (or per-change - `--schema`). cospec resolves any non-canon schema as **legacy** and routes it - down the legacy lane: no cospec `schemaVersion` stamp and no typed - artifact-plan, and `cospec new` prints a "reduced cospec guarantees" note. - The legacy lane still keeps every **schema-agnostic** hard gate — the archive - tasks gate, scenario-preservation, filesystem-move verification, and blocker - fan-out — and delegates structural validation to OpenSpec's own check of the - fork's declared artifact graph (`cospec validate` emits a - `meta/legacy-schema` INFO and no typed `proposal/*`, `verification/*`, - `design/*`, or `specs/*` rule). The one cospec gate a fork does **not** get - is the verification-evidence ledger, which is scoped to canon types. - `cospec doctor` reports a change on a fork as an INFO, not a warning. - -**Documented limitation:** `config.yaml` `context`/`rules` are additive prose -only, keyed by artifact id repo-wide — they cannot vary per type, override a -template, or replace an instruction. Per-type guidance lives only in the schema -instruction prose. If you need genuinely different rules or templates per type, -fork the schema. +Three strictly separated tiers — see the site's +[Configuration reference](https://cospec.aligned.team/reference/configuration) +for the full user-facing account of all three, including the managed-file +protocol and the `config.yaml` key reference. The mechanics behind tier 3 +(schema forking) worth knowing as a contributor: + +`cospec schema fork [name]` (defaults the destination to `-custom`) +and `cospec schema init ` are disciplined passthroughs to the wrapped +binary, guarded by one cospec-side check: a fork/init whose destination name is +one of the 11 canon types is refused with exit 1 before the binary is ever +spawned, so a fork can never overwrite a canon-managed `schema.yaml`. cospec +never regenerates or manages a forked schema — it is yours to edit. + +A change run against a fork (`cospec new ` or per-change +`--schema`) resolves as **legacy** and routes down the legacy lane: no cospec +`schemaVersion` stamp, no typed artifact-plan, and a "reduced cospec guarantees" +note from `cospec new`. The legacy lane still keeps every **schema-agnostic** +hard gate — the archive tasks gate, scenario-preservation, filesystem-move +verification, and blocker fan-out — and delegates structural validation to +OpenSpec's own check of the fork's declared artifact graph (`cospec validate` +emits a `meta/legacy-schema` INFO and no typed `proposal/*`, `verification/*`, +`design/*`, or `specs/*` rule). The one cospec gate a fork does **not** get is +the verification-evidence ledger, which is scoped to canon types. +`cospec doctor` reports a change on a fork as an INFO, not a warning. diff --git a/docs/stores.md b/docs/stores.md index 5429521..16b329d 100644 --- a/docs/stores.md +++ b/docs/stores.md @@ -2,109 +2,30 @@ An OpenSpec **store** (openspec 1.5.0) is a standalone, registered planning repo: its own `openspec/` tree of specs and changes, versioned and shared over -git like any repo. Stores exist for cross-repo work — one plan that several code -repos implement against, or requirements one team owns and others consume. - -cospec is **store-aware**: every change-lifecycle command takes a `--store ` -global flag and runs its full typed workflow — schemas, the `apply` gate, the -verified `archive`, the blocking-changes ledger — against that store instead of -the local repo. Nothing about the workflow changes; only the operating root -does. +git like any repo. cospec is **store-aware** — every change-lifecycle command +takes a `--store ` global flag and runs its full typed workflow against that +store instead of the local repo, and the store lifecycle itself +(`cospec store setup|register|unregister|remove|list|doctor`, plus +`cospec context` and `cospec workset`) is a first-class cospec command, never a +bare `openspec` call. + +The full user-facing account — setup, the config.yaml `store:` pointer, the +resolution order, `cospec context`/`workset`, and what cospec owns vs. what +OpenSpec owns — is owned by the site: +[Stores](https://cospec.aligned.team/concepts/stores). ```sh cospec new feat cross-repo-epic --store platform # authored in the store -cospec validate cross-repo-epic --store platform --strict cospec apply cross-repo-epic --store platform # the gate, over the store cospec archive cross-repo-epic --store platform # verified move, in the store -cospec status --store platform # list/status against the store -``` - -## What cospec owns vs. what OpenSpec owns - -cospec wraps OpenSpec; it does not reimplement the store registry or its -resolution semantics. But every store-management, cross-repo-context, and -workset surface is now a **first-class cospec command** — you never drop out to -bare `openspec` for an everyday operation. - -- **cospec** owns everything a user runs: - - the change lifecycle — `new`, `validate`, `apply`, `archive`, `status`, - `list`, `instructions`, `sync-blockers`, `migrate` — each with `--store`; - - the store lifecycle — `cospec store setup|register|unregister|remove|list` - (`ls`)`|doctor`, a first-class wrap that verifies each mutation on disk (it - trusts the filesystem/registry, never the wrapped exit code) and, on a - successful `setup`/`register`, **auto-runs - `cospec init --harness none`** so a new or newly-registered store - gets cospec's 11 typed schemas in one command (opt out with - `--no-cospec-init`); - - the read-only cross-repo brief — `cospec context` (with `--json` and - `--code-workspace`/`--force`); - - personal working views — `cospec workset create|list|remove|open`. -- **OpenSpec** still owns the underlying machine registry, the on-disk store - format, and the `references:` config key (upstream specs surfaced into a code - repo's `instructions`). cospec spawns the pinned binary for all of it under - the [wrapped-call discipline](architecture.md); nothing about the registry - format is reimplemented. - -`store`, `context`, and `workset` are disciplined passthroughs (or, for `store`, -first-class wraps): read-only or personal, they carry no cospec gate — they add -wrapped-call rigor (declared exit codes, a stdout deny-list, an observable -post-condition) and, where the root is store-backed, `--store` threading. - -## Setup - -Creating a store is now a single command — `cospec store setup` registers the -root **and** stamps it with cospec's typed schemas in one step (the auto -`cospec init` that used to be a documented manual second step): - -```sh -# 1. Create + register the store, and give it cospec's typed schemas in one go. -# (Auto-runs `cospec init --harness none` on success — a store is -# planning-only, so no harness. Pass --no-cospec-init to skip that step.) -cospec store setup platform --path ./platform-store --remote git@github.com:acme/platform-store.git - -# 2. Work the store from anywhere by id. -cospec new feat some-epic --store platform -``` - -To adopt an already-existing OpenSpec root, `cospec store register ` -registers it and runs the same auto `cospec init`. `cospec store ls` lists the -registered stores, `cospec store doctor [id]` reports per-store health (git -facts, metadata, root completeness), and `cospec store unregister`/`remove` -inherit OpenSpec's `--yes`/confirmation contract (`unregister` forgets the -registry entry and leaves files on disk; `remove` also deletes the folder). - -A code repo can also point at a store by default instead of passing `--store` -every time, via its own `openspec/config.yaml`: - -```yaml -schema: feat -store: platform # cospec + openspec resolve commands against this store -references: - - platform # read-only upstream specs surfaced in instructions ``` -## Resolution order - -Each command resolves exactly one operating root, mirroring OpenSpec's own -precedence: - -1. an explicit `--store ` flag; -2. else a `store:` pointer in the local `openspec/config.yaml`; -3. else the local repo at the current directory. - -`references:` is read-only context, never a root override — it does not change -where a change is created or gated. - -An unregistered `--store` id fails loudly (naming the registered stores) rather -than silently falling back to the local repo, so a typo can never write a change -to the wrong place. - -## How it works +## How it works (internals) -Under the hood a resolved `Root` carries three things: the store's on-disk -`base` (cospec's filesystem readers key on it — a store's layout is identical to -a repo's, `/openspec/…`), the `cwd` wrapped calls spawn in, and the -`--store ` args every wrapped `openspec` call appends. The store's path is -resolved from the machine registry via `openspec store ls --json`, wrapped with -the same expected-exit / post-condition discipline as every other wrapped call -(see [architecture.md](architecture.md)). +A resolved `Root` carries three things: the store's on-disk `base` (cospec's +filesystem readers key on it — a store's layout is identical to a repo's, +`/openspec/…`), the `cwd` wrapped calls spawn in, and the `--store ` +args every wrapped `openspec` call appends. The store's path is resolved from +the machine registry via `openspec store ls --json`, wrapped with the same +expected-exit / post-condition discipline as every other wrapped call — see +[architecture.md](architecture.md). diff --git a/docs/validation.md b/docs/validation.md index 6511877..547eae6 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -9,15 +9,12 @@ they win the report. cospec validate [name] [--all|--changes|--specs] [--strict] [--json] [--fast] ``` -- No arguments (or `--all`) validates **both** active changes and living specs. - `--changes` restricts to changes, `--specs` to living specs; a bare `[name]` - validates that single change (or spec). Bulk runs exit 1 if any item fails. -- `--strict` promotes every WARNING to blocking (hooks and CI use it). -- `--fast` skips the archive-precondition checks (used internally by `apply`). -- Exit 1 if there are errors (or warnings under `--strict`); otherwise 0. - -To enumerate living specs without validating them, use `cospec list --specs` -(delegates to `openspec list --specs`, renders a spec/requirement-count table). +The full rule registry — every stable rule ID, grouped by family, with its level +and what it checks — is owned by the site: +[Validation rule registry](https://cospec.aligned.team/reference/validation-rules). +Rule IDs are stable public API: script against them, grep for them in CI logs, +ignore them by ID if you need to. This page covers how the rule families are +wired together, which is implementation detail the site doesn't need. ## Composition @@ -41,182 +38,9 @@ For each change, cospec resolves the schema from `.openspec.yaml`, then: Living specs (`--specs`) always delegate to `openspec validate --specs` (sound and schema-independent), with cospec's `specs/purpose-tbd` on top. -## Rule registry - -Rule IDs are stable public API. Levels: **E** = ERROR, **W** = WARNING (blocking -under `--strict`), **I** = INFO. Every issue carries a one-line hint. - -### `meta/` - -| ID | Level | Check | -| ------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `meta/openspec-yaml` | E | `.openspec.yaml` present, parseable, `schema:` non-empty; `created:` is `YYYY-MM-DD` when present | -| `meta/schema-unknown` | E | schema is not one of the eleven and not resolvable anywhere | -| `meta/legacy-schema` | I | schema resolvable but not a cospec type — legacy mode engaged | -| `meta/name-kebab` | E | change dir is kebab-case with no `YYYY-MM-DD-` prefix (collides with archive naming) | -| `meta/forbidden-artifact` | E | a file exists for an artifact the schema does not declare (e.g. `specs/` in `ci`) | -| `meta/unexpected-file` | W | a file matches no declared artifact glob — excludes `README.md`, `.openspec.yaml`, `.refine/` | -| `meta/empty-change` | I | change has `.openspec.yaml` but zero artifacts — reported as "in progress", never as an unknown item | -| `meta/surface-unmet` | W (E-strict) | a checked `## Surfaces` flag's consequence is absent, for a type whose target is not Forbidden (an O(trig) type — revert/build/ci — whose `verification.md` does not exist at all; a missing row on an _existing_ file is owned by `verification/*` instead, never double-reported) | -| `meta/schema-outdated` | I | change is on `schemaVersion 1` (absent ⇒ 1) — some artifacts (e.g. `verification`) are grandfathered out until `cospec migrate`; never blocks | -| `change/artifact-missing` | I / E-strict | an `apply.requires` artifact file does not exist yet (`verification` is excluded here — `verification/missing` owns it) | - -### `proposal/` - -| ID | Level | Check | -| -------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `proposal/missing` | W (E-strict when other artifacts exist) | `proposal.md` absent | -| `proposal/sections` | E | required H2s per variant: full → `## Why`, `## What Changes`, `## Impact` (+ `## Capabilities` for feat, and fix when `specs/` exists); lite → `## Why`, `## What Changes`, `## Impact`; plus `## Surfaces` for every surfaces:true type (all but chore/docs/style/test), fail-closed even when the block is empty | -| `proposal/why-substantive` | W | full-variant types: `## Why` body is at least 50 characters | -| `proposal/benchmarks` | E | perf only: `## Benchmarks` present with at least one before/after row | -| `proposal/revert-citation` | E | revert only: `## Reverts` with a backticked slug in `archive/` and/or a 7–40-hex sha | -| `proposal/surfaces-vocab` | E | every `## Surfaces` checkbox item's token is one of `interactive`, `deploy`, `integration`, `agent-behavior` (fail-closed on any other token) | - -### `blockers/` - -Grammar is specified in [blocking-changes.md](blocking-changes.md). - -| ID | Level | Check | -| ---------------------------- | ------------ | --------------------------------------------------------------------------------------------- | -| `blockers/sections` | E | both gated headings present with exact text; near-miss detection prints the corrected heading | -| `blockers/entry-grammar` | E | every line in a gated section is legal; the corrected line is printed | -| `blockers/dangling-ref` | E | an entry slug is neither an active change nor an archive suffix | -| `blockers/stale-unchecked` | W (E-strict) | an unchecked entry whose target is archived — auto-fixable by `sync-blockers` | -| `blockers/premature-checked` | W | a checked entry whose target is not archived (allowed, but surfaced) | -| `blockers/none-conflict` | E | `None.` coexisting with entries in the same section | - -### `tasks/` - -| ID | Level | Check | -| ------------------------ | ----- | -------------------------------------------------------------------------------------------------------- | -| `tasks/has-tasks` | E | at least one parseable `- [ ]` / `- [x]` item (when `tasks.md` exists) | -| `tasks/checkbox-grammar` | E | checkbox-like lines OpenSpec's tracker won't parse (`-[ ]`, `* [ ]`, `- [X ]`) — the fixed line is shown | -| `tasks/group-numbering` | W | `## N.` groups non-sequential or `N.M` prefixes inconsistent | - -### `verification/` - -Runs only when the schema declares `verification` -(`schema.declared.has('verification')`). Grammar in [schemas.md](schemas.md). -The `missing`/`structure`/`row-grammar`/ -`layer-unknown`/`owner-unknown`/`evidence-required`/`deferred-reason` rules are -fail-closed ERRORs regardless of type. The per-type required-row rules are ERROR -when `verification` is in `schema.applyRequires` for this change's type -(feat/fix/perf/refactor); the surface-driven and build/ci `deploy-real-layer` -rules are soft (WARNING, ERROR under `--strict`) because a `## Surfaces` flag -only ever soft-promotes. - -| ID | Level | Check | -| ----------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `verification/missing` | I / E-strict | `verification.md` is required by `apply.requires` (post-grandfathering) and absent | -| `verification/structure` | E | at least one `## N. behavior` group; every group has at least one row | -| `verification/row-grammar` | E | a checkbox-like line does not parse as `- [] N.M @ [()] -> ` | -| `verification/layer-unknown` | E | `@` is outside the closed vocabulary (`@unit @integration @e2e @manual @runtime @regression @equivalence @benchmark @eval`) and not extended via `openspec/config.yaml` `verification.layers` | -| `verification/owner-unknown` | E | `()` is present and is neither `(agent)` nor `(human)` | -| `verification/evidence-required` | E | a `[x]` row's ` -> ` is empty | -| `verification/deferred-reason` | E | a `[~]` row has no non-empty `defer: ` | -| `verification/critical-real-layer` | E | feat only: a `[critical]` group has no row whose layer is not `@unit` | -| `verification/reproduces-bug` | E | fix only: no `@regression` row anywhere in the ledger | -| `verification/equivalence` | E | perf only: missing a `@benchmark` row, an `@equivalence` row, or both | -| `verification/invariant` | E | refactor only: no `@equivalence` row | -| `verification/deploy-real-layer` | W (E-strict) | build/ci only, `deploy` surface checked: no `@runtime` row | -| `verification/interactive-required` | W (E-strict) | `interactive` surface checked: no `@manual` or `@e2e` row | -| `verification/eval-check` | W (E-strict) | `agent-behavior` surface checked: no `@eval` row | -| `verification/integration-check` | W (E-strict) | `integration` surface checked: no `@integration` row | - -### `design/` - -Fires only when the matching `## Surfaces` flag is checked in `proposal.md`, -except `seam-ownership`, which fires unconditionally for `refactor`. All soft -(WARNING, ERROR under `--strict`) — design's own matrix placement (O for -feat/fix/perf, R for refactor, F for revert + light types) is unchanged. - -| ID | Level | Check | -| ----------------------------- | ------------ | ----------------------------------------------------------------------------------------- | -| `design/operational-surface` | W (E-strict) | feat/fix/refactor, `interactive` or `deploy` checked: no `## Operational surface` section | -| `design/integration-contract` | W (E-strict) | feat/fix/refactor, `integration` checked: no `## Integration contract` section | -| `design/seam-ownership` | W (E-strict) | refactor, always: no `## Seam ownership` section | - -### `deltas/` - -Spec-bearing changes only; these run before delegation so cospec's diagnostics -win. - -| ID | Level | Check | -| -------------------------- | ----- | -------------------------------------------------------------------------------------------------- | -| `deltas/scenario-depth` | E | a `### Scenario:` heading uses three hashtags; it must be `#### Scenario:` | -| `deltas/header-present` | E | each `specs/*/spec.md` has at least one `## ADDED\|MODIFIED\|REMOVED\|RENAMED Requirements` header | -| `deltas/requirement-shape` | E | ADDED/MODIFIED requirements have SHALL/MUST plus at least one `#### Scenario:` | -| `deltas/capability-kebab` | E | capability dirs are kebab-case; the delta file is `specs//spec.md` | - -### `archive/` - -The archive-precondition family — set-membership checks against living-spec -requirement names, mirroring OpenSpec's own preconditions. Run at validate time -(skipped by `--fast`) to move the archive failure left. See -[apply-archive.md](apply-archive.md) for the runtime verifier that backs them. - -| ID | Level | Check | -| ------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `archive/no-ops` | E | a delta file parses to zero operations | -| `archive/target-missing` | E | a MODIFIED / RENAMED-FROM / REMOVED target is absent from the living spec | -| `archive/new-spec-non-added` | E | a capability with no living spec has MODIFIED/RENAMED/REMOVED ops | -| `archive/added-exists` | E | an ADDED target already exists, or a RENAMED-TO collides with an existing/ADDED name | -| `archive/target-invalid` | E | the target living spec is structurally invalid (missing `## Purpose`/`## Requirements`, or contains delta headers) | -| `archive/scenario-preservation` | W (E-strict) | advisory mirror of the hard `cospec archive` command step: a MODIFIED requirement drops `#### Scenario:` count with no `Scenario removed: ` note or matching REMOVED op | - -cospec may be strictly more conservative than OpenSpec here. A false PASS -(cospec ok, archive aborts) is a release blocker — and the runtime archive -verifier still catches it, so the user is never lied to. Parity is enforced by -contract tests, never trusted. - -`cospec archive` additionally runs two hard gates as explicit command steps, -before delegating to `openspec archive` — not folded into the -archive-precondition family above, because a real breach there must not report -as a clean archive. Both return `EXIT.failure` (1), the refusal code the tasks -gate already uses, and there is no `--force` for either: - -- **`archive/verification-incomplete`** (after the tasks gate, independent of - specs — fires for a specs-less `fix` too): whenever `verification` is enforced - for this change's type and stamped `schemaVersion` (`enforcedApplyRequires`), - every row must be `[x]` with non-empty evidence or `[~]` with a reason. Any - bare `[ ]` row refuses the archive. -- **`archive/scenario-preservation`** (before `openspec archive` executes, - specs-bearing changes only): for each `## MODIFIED Requirements` delta, - compares `#### Scenario:` counts against the living spec. A delta that drops - scenarios without a matching removal note refuses the archive — this catches - the class of thinning that `openspec archive` merges at exit 0. A mirror - advisory rule runs in the archive-precondition family above so - `cospec validate --strict` surfaces the same risk before archive time; the - hard block is the command step, verified against the real pinned openspec - 1.5.0 binary by a contract test. - -### `specs/` - -| ID | Level | Check | -| ------------------- | ----- | ------------------------------------------------------------------------------------ | -| `specs/purpose-tbd` | W | `## Purpose` contains the archive-generated `TBD - created by archiving` placeholder | -| (delegated) | \* | every `openspec validate --specs --strict` issue, re-rendered | - -## Output - -Human output groups by change, keeps the rule ID greppable, and always prints a -hint: - -``` -cospec validate — 2 changes, 5 specs - -✗ add-widget (feat) - ERROR specs/widgets/spec.md:14 deltas/scenario-depth scenario heading uses 3 hashtags; must be `#### Scenario:` - ERROR blocking-changes.md:7 blockers/dangling-ref `add-auth` is not an active or archived change - hint: `cospec list` shows active changes; fix the slug or remove the entry - WARNING proposal.md proposal/why-substantive ## Why is shorter than 50 characters -✓ fix-null-crash (fix) -✓ specs: 5/5 valid - -2 errors, 1 warning — validation failed -``` - -`--json` emits -`{ version, items: [{ id, kind, type, valid, issues: [...] }], summary: { errors, warnings, byRule } }`, -where each issue has `level`, `rule`, `path`, optional `line`, `message`, -optional `hint`, and `fixable`. +cospec may be strictly more conservative than OpenSpec in the archive- +precondition family. A false PASS (cospec ok, `openspec archive` aborts) is a +release blocker — and the runtime archive verifier still catches it, so the user +is never lied to. Parity is enforced by contract tests, never trusted. See +[apply-archive.md](apply-archive.md) for the runtime verifier that backs these +preconditions. diff --git a/openspec/changes/archive/2026-07-16-flat-docs-ownership/.openspec.yaml b/openspec/changes/archive/2026-07-16-flat-docs-ownership/.openspec.yaml new file mode 100644 index 0000000..93f90c8 --- /dev/null +++ b/openspec/changes/archive/2026-07-16-flat-docs-ownership/.openspec.yaml @@ -0,0 +1,3 @@ +schema: docs +created: 2026-07-16 +schemaVersion: 2 diff --git a/openspec/changes/archive/2026-07-16-flat-docs-ownership/blocking-changes.md b/openspec/changes/archive/2026-07-16-flat-docs-ownership/blocking-changes.md new file mode 100644 index 0000000..3f5463e --- /dev/null +++ b/openspec/changes/archive/2026-07-16-flat-docs-ownership/blocking-changes.md @@ -0,0 +1,16 @@ +# Dependencies + +## Blocked by + + + + + +None. + +## Soft-blocked by + + + + +None. diff --git a/openspec/changes/archive/2026-07-16-flat-docs-ownership/proposal.md b/openspec/changes/archive/2026-07-16-flat-docs-ownership/proposal.md new file mode 100644 index 0000000..84b883a --- /dev/null +++ b/openspec/changes/archive/2026-07-16-flat-docs-ownership/proposal.md @@ -0,0 +1,51 @@ +## Why + +The flat topic docs in `docs/` (validation.md, apply-archive.md, schemas.md, +stores.md, blocking-changes.md, harness-integration.md, architecture.md) heavily +restate facts now owned by pages on the public docs site (`apps/docs`, published +at https://cospec.aligned.team) — rule tables, exit-code tables, artifact +matrices, and command semantics. Per the repo's one-owner convention, each fact +should live on exactly one page; everywhere else links to it. This duplication +is a drift risk: the site and the flat docs will diverge the next time either is +updated in isolation. + +## What Changes + +- `docs/apply-archive.md` — replace the duplicated exit-code table, step-by-step + gate walkthrough, and JSON shape with a short summary and a link to + `/concepts/apply-and-archive` on the site; keep the "gate enforced twice" + rationale and the `sync-blockers` pointer to `core/blockers.ts`. +- `docs/validation.md` — replace the full rule registry tables with a link to + `/reference/validation-rules`; keep the composition/delegation logic + (`runChangeRules()` wiring) as repo-internal detail. +- `docs/schemas.md` — replace the artifact matrix, `## Surfaces` block, and + per-type rationale with a link to `/concepts/types-and-artifacts` and + `/concepts/verification`; keep canon pointers + (`apps/cli/src/canon/types/*.yaml`, `type-facts.ts`, the matrix-parity test) + and the customization-tier escape-hatch mechanics not covered at that depth on + the site. +- `docs/stores.md` — replace the near-verbatim duplicate of `/concepts/stores` + with a short summary and a link. +- `docs/blocking-changes.md` — replace the template/example walkthrough with a + link to `/concepts/blocking-changes`; keep the entry-grammar regex and the + `core/blockers.ts` single-parser pointer, which aren't on the site. +- `docs/harness-integration.md` — replace the "what gets written" file tree and + the smoke-test checklist with a link to `/guide/harness-setup`; keep the + per-workflow behavior descriptions (canon `workflows/*.md` semantics) and the + managed-file `writeManaged` pseudocode, which are contributor-facing detail + the site doesn't carry. +- `docs/architecture.md` — trim the introductory failure-mode explanations that + duplicate `/concepts/how-it-relates-to-openspec`, replacing them with a link; + keep the wrapped-call discipline mechanics, the static-matrix invariant, and + the module map, none of which are on the site. +- `docs/eval.md`, `docs/self-hosting.md`, `docs/release.md` — audited, no + site-owned facts found; left unchanged. +- Check `.agents/shared.md` / `CLAUDE.md` for references to the flat docs whose + meaning would change; none of the current cross-references break. + +## Impact + +Contributors reading `docs/*.md` for entry-point orientation; no code changes. +`apps/docs` content is untouched. Cross-links from `docs/*.md` into `apps/docs` +pages are new; existing inbound references from CLAUDE.md and +`.agents/shared.md` to these files remain valid. diff --git a/openspec/changes/archive/2026-07-16-flat-docs-ownership/tasks.md b/openspec/changes/archive/2026-07-16-flat-docs-ownership/tasks.md new file mode 100644 index 0000000..304c1cb --- /dev/null +++ b/openspec/changes/archive/2026-07-16-flat-docs-ownership/tasks.md @@ -0,0 +1,31 @@ +## 1. Restructure flat docs to link out to site-owned facts + +- [x] 1.1 `docs/apply-archive.md` — trim exit-code table/step walkthrough/JSON + shape; add summary + link to `/concepts/apply-and-archive`; keep the "gate + enforced twice" rationale and the `sync-blockers` pointer +- [x] 1.2 `docs/validation.md` — trim rule registry tables; add link to + `/reference/validation-rules`; keep composition/delegation prose +- [x] 1.3 `docs/schemas.md` — trim artifact matrix / Surfaces block / per-type + rationale; add links to `/concepts/types-and-artifacts` and + `/concepts/verification`; keep canon pointers and customization-tier + escape-hatch mechanics +- [x] 1.4 `docs/stores.md` — trim near-verbatim duplicate content; add summary + + link to `/concepts/stores` +- [x] 1.5 `docs/blocking-changes.md` — trim template/example walkthrough; add + link to `/concepts/blocking-changes`; keep entry-grammar regex and + `core/blockers.ts` pointer +- [x] 1.6 `docs/harness-integration.md` — trim "what gets written" file tree and + smoke checklist; add link to `/guide/harness-setup`; keep per-workflow + behavior descriptions and the managed-file pseudocode +- [x] 1.7 `docs/architecture.md` — trim duplicated failure-mode intro; add link + to `/concepts/how-it-relates-to-openspec`; keep wrapped-call discipline, + static-matrix invariant, and module map +- [x] 1.8 Audit `docs/eval.md`, `docs/self-hosting.md`, `docs/release.md` for + site-owned duplication; confirm none found, leave unchanged + +## 2. Verify cross-references stay valid + +- [x] 2.1 Grep `CLAUDE.md` and `.agents/shared.md` for references to the edited + flat docs; confirm none are invalidated by the restructure +- [x] 2.2 Spot-check every new/edited in-repo relative link (`docs/*.md` links + to other `docs/*.md` files) resolves to an existing file