Spec workflow¶
New behavioural work in Kollect goes through OpenSpec,
pinned to one version in hack/tooling/openspec/package.json. This page is the contract. The
OpenSpec configuration in openspec/config.yaml repeats the rules an author needs while writing a
change.
| Location | Holds |
|---|---|
openspec/specs/<capability>/spec.md |
Current requirements and scenarios, one file per durable capability |
openspec/changes/<change>/ |
One change in flight: proposal, requirement deltas, design (when needed), tasks with verification |
openspec/changes/archive/ |
Completed changes, moved there by openspec archive |
specs/001-* |
Historical bundles, read-only. See specs/README.md |
docs/adr/ |
Durable architecture decisions. Changes link to them rather than copy them |
Small fixes do not need a change: a typo, a dependency bump, a CI tweak or a documentation correction goes straight to a PR. Open a change when observable behaviour changes.
Lifecycle¶
- Frame. In
proposal.md, write the outcome, the production entry point, non-goals and the assumptions the change depends on. Back each load-bearing assumption about a tool or API with a source or a probe. - Specify. Write requirements with stable IDs, plus scenarios for both what must happen and
what must not happen. Write the verification table in
tasks.mdbefore touching production code. - Red. Write the behavioural test and watch it fail on the assertion you care about. A compile error, a missing Docker daemon or a skipped test is not a red.
- Green. Make the smallest coherent change and run the relevant gates.
- Review. An independent reviewer (not the author) checks the exact revision against the requirements and the verification table.
- Land. After the review and green CI, archive the change (
openspec archive, which also creates or updates the living spec) as the last commit of the PR, so the archived record does not keepnot-runrows. The reviewer re-checks that commit (the moved change and the living spec), and the review record names both revisions. Then merge on green required CI (rebase merge).
These are separate states: implemented, tested, reviewed, merged, archived. Archiving is history, not proof. If a change is archived before its PR merges, the work stays open until the merge is verified.
Verification scales with risk¶
The gates apply to every PR. On top of them:
| Change | Additional verification |
|---|---|
| Prose only | Content, source and link checks. No invented behavioural test |
| Bounded local behaviour | Positive and negative test, with a red→green record |
| Stateful export or configuration | A test through the production path, the relevant state transitions, and the race detector on the changed packages (go test -race, or task coverage:race) |
| Deletion, tenancy, credentials, migrations | An isolation or ownership invariant, adversarial cases, and a mutation or negative control that shows the test catches the defect |
Identity, persisted metadata, compatibility and migration changes need a design.md.
Verification table¶
tasks.md ends with a table that maps each requirement to its evidence:
## Verification
| Req | Check | Expected | Status | Evidence |
| --- | --- | --- | --- | --- |
| IEE-1 | `TestRequireAll_missingTest` | run fails, names the missing test | pass | rev `abc1234`, `go test ./hack/...`, CI run link |
| IEE-4 | mutation: drop the exit-status check | the test above fails | pass | rev `abc1234`, local, Go 1.26.6 |
| IEE-5 | real Docker integration run | expected = executed, 0 skipped | not-run | needs CI |
Status is one of pass, fail, blocked, not-run, post-merge or N/A (with a reason).
post-merge is only for evidence that cannot exist before the merge, such as a workflow that only
fires from the default branch, a bot that reads its config from main, or "N days green". The row
links an open follow-up issue that owns it. Such a change merges without the archive commit; a
small follow-up evidence PR fills the rows and archives the change. Evidence names the
revision, the command, what ran and what was skipped, the tool versions and the known limitations.
Link CI runs instead of pasting logs. When a test found a failing input (a seed or a fuzz corpus
entry), keep that input as a regression fixture.
Evidence that ran nothing is not evidence:
- A red or a green counts only if the run shows the named test running (
go test -vprints=== RUN <name>). A-runpattern that matches nothing exits zero with "no tests to run". - Judge defect controls (mutations) by the test command's exit status, not by grepping its output, and include one no-op control that must survive.
- A single pass of a race-detector or otherwise flaky check is not evidence; repeat it
(
-count=N) and record N.
Commands¶
task spec:list # changes in flight; add -- --specs for capabilities
task spec:validate # openspec validate --all --strict, then check OpenSpec loaded config.yaml
task spec:cli -- new change <name>
task spec:cli -- archive <name>
The tasks install the pinned CLI with npm ci and set DO_NOT_TRACK=1 and
OPENSPEC_NO_UPDATE_CHECK=1, so they make no telemetry or update-check calls.