OpenSpec v1.12.0 and v1.13.0 shipped in quick succession (September 2026), neither with breaking changes. The theme of v1.13.0 fits in one sentence: archive and the delta parser no longer quietly rewrite or drop what you wrote. This article walks the post-upgrade workflow (Explore → Propose → Apply → Archive) through a real change — “order list query” (order-list-query) — and records the changes in both releases that matter in practice, including validate --report findings catching 3 real issues in this repository on its very first run.
openspec validate --report findings — a focused report showing only errors, warnings, and informational findings while preserving totals and exit codes; a natural CI hook.* or + bullets now take effect — previously only - was recognized: validate passed, archive reported success, and the requirement stayed exactly as it was.## ADDED Requirements sections used to have one silently dropped before archiving (documenting OpenSpec’s own syntax inside a fenced example triggers this naturally).skip_specs: true), aligning with validate’s zero-delta rejection.openspec list --specs: plain openspec list shows only in-flight changes, never the project’s durable capabilities.openspec context --json resolves the authoritative root before reading the context field from config.yaml; without an OpenSpec root it stops and asks instead of silently initializing.Following the v1.13 explore template, the inventory step is now openspec list --specs (7 capabilities with requirement counts), cross-checked against the route tables of both implementations. Four gaps remained: order list query (small-medium), cart clear endpoint (tiny), payment implementation (large), product deletion (medium-large, stock references).
We picked order list query: GET /api/orders?userId= returning a user’s order history — the natural next gap in the order loop; the repo layer already had traversal; a pure ADDED requirement.
Reading the code during explore surfaced a dual-implementation divergence: the Python Order model has no user_id field, while Node’s order objects have always carried userId. Filtering by user therefore first required completing the model — a finding that directly shaped the design.
Per the new propose template’s step 2, openspec context --json runs first (returning the authoritative root path), and only then is the context field of config.yaml read as a planning constraint. Four artifacts:
order-management, ADDED “订单列表查询”userId returns 400 + MISSING_USER_IDOrderRepo gains findAll(), aligning with ProductRepo); an explicit 400 for a missing parameter instead of returning everyone’s orders (admin semantics leak other users’ data); Python’s Order model gains user_id (alias userId), aligning the order JSON across implementations — a purely additive fieldBoth implementations landed in ~20 lines each. The Node integration test failed on first run: the order assertion expected 201, got 400 (CART_EMPTY) — the exact pit PR #11 fixed in the performance test: the dev cart endpoint is pinned to user_dev, but the test ordered as a different user. Fixed with relative assertions from user_dev’s perspective (record the pre-existing order count, assert +2); multi-user isolation assertions moved to the Python side — Python’s cart requests carry userId, so multi-user E2E is natural there.
The repeat itself is worth recording: the dev server’s mock identity model (fixed user_dev) is in structural tension with tests that want multiple users. Unit tests cover isolation (direct service calls), integration covers a single-user loop, and Python covers multi-user — a reasonable division under the current architecture.
Results: Node 18/18, Python 6/6 green.
During wrap-up we ran the new report mode against the main specs:
$ openspec validate --report findings --specs
spec/cart-management
[WARNING] overview: Purpose section is too brief (less than 50 characters)
spec/payment
[WARNING] overview: Purpose section is too brief (less than 50 characters)
spec/product-query
[WARNING] overview: Purpose section is still a placeholder rather than
a Purpose anyone wrote ...
Totals: 7 passed, 0 failed (7 items)
All three warnings were real: cart/payment Purposes were indeed under 50 characters, and product-query’s Purpose was still the TBD - created by archiving change ... placeholder the CLI wrote during the v1.5.0 practice archive, never filled in. After fixing each one, findings came back empty. This neatly validated the evolution loop across v1.9.0 (Purpose format) → v1.11.0 (placeholder warning) → v1.12.0 (findings report): guards added version by version surface real, accumulated debt version by version.
A single openspec archive merged the delta (+1 added) and archived the change to openspec/changes/archive/2026-09-10-order-list-query/. Post-archive validation: no active changes, all 7 specs pass, findings empty.
The three parser failure modes (*/+ bullets ignored, duplicate delta sections half-applied, fenced blank lines rewritten) shared one property: the toolchain reported success while the result was wrong. That is more dangerous than a crash — it corrupts the “single source of truth” itself. v1.13.0 turns all of them into “either takes effect, or errors out.”
From v1.9.0 through v1.13.0, five upgrades kept reinforcing the same thing: spec content quality. Purpose migration → placeholder warning → findings report — each guard has paid off in a later practice. The value of an SDD toolchain is not just “faster generation” but “drift is discovered earlier.”
user_dev mock identity has now interfered with integration tests twice; if a future practice is user-scoped (order ownership, payment), it’s worth making the dev identity parameterizable.GET /api/orders/{id} — the spec’s “订单查询” requirement (2 scenarios) is currently implemented in Node only — a good candidate for a small follow-up practice.This article is based on the order-list-query practice in the OpenSpec Practise repository (2026-09-10); full artifacts live under openspec/changes/archive/2026-09-10-order-list-query/.