Public roadmap: SpecSync 6.0, and what adoption is deciding next #339
0xLeif
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
SpecSync 6.0 is at
6.0.0-rc.9.5.2.0remains the latest stable release. This post replaces the "after 5.0" roadmap, which predates the 6.0 line entirely.The honest framing of this release: rc.1 through rc.9 were not planned, they were driven. Almost every fix below came from adopting the tool in anger on a real repository — increasingly, on somebody else's — and recording what broke, rather than from a feature list. The roadmap below is therefore mostly what adoption has already decided, plus a short list of questions it has raised and not yet answered.
Breaking, and the thing to read first
Windows is no longer a supported target as of 6.0. No Windows binary is published (#722, in
rc.8). 6.0 ships five artifacts:linux-x86_64,linux-x86_64-musl,linux-aarch64,macos-x86_64,macos-aarch64. Run SpecSync under WSL, or build from source —cargo install specsyncstill works on Windows. The packaged GitHub Action now refuses a Windows runner with that message instead of requesting an asset that no longer exists.Two facts decided it, and the second matters more — which is worth stating plainly, because the first on its own does not carry the argument. Across five weeks of
v5.2.0the Windows asset was downloaded once, against 462 formacos-aarch64and 447 forlinux-x86_64. Butlinux-aarch64was downloaded zero times over the same window and is deliberately retained, so a download count alone would prove too much. What separates them is the second fact: every job in ordinary CI runs onubuntu-latest, so the Windows executable was published without ever being exercised, whilelinux-aarch64is built from the same source the Linux lane exercises end to end.That is how #715 happened:
specsync viewfailed with "Cannot parse frontmatter" on every spec in the project, in any checkout withcore.autocrlf=true, and it survived for weeks on the one platform that shipped a binary nothing tested. It was found by an adopter, not by us.No Windows correctness was removed, and none may be. A teammate on Windows commits CRLF files and a colleague on Linux reads them; that case is fully supported and guarded. CRLF frontmatter tolerance, the single canonical
strip_frontmatter, the.gitattributeseol=lfpins, Windows-reserved and Windows-invalid filename guards,MAX_SLUG_BYTESand itsMAX_PATHjustification, junction and reparse-point rejection, and every#[cfg(windows)]block are unchanged. Requirement wording that had scoped those guarantees to "every platform SpecSync ships a binary for" is rebound to the platforms a repository may be checked out on — read literally, the old phrasing would have narrowed each guarantee the moment the shipped set narrowed, which is the opposite of the intent.The release-candidate qualification lane still runs Ubuntu, macOS and Windows. It is the only place the retained
#[cfg(windows)]code is compiled and run, and removing it would recreate exactly the condition that produced theviewdefect.What 6.0 changes structurally
CHG-NNNNordinals. Identity comes fromstate.json, never from the shape of a name (fix(6.0): identity must come from state.json, never from the shape of a name #658), succession is ordered by when a change happened rather than how it is named (refactor(6.0): succession must be ordered by when a change happened, not by how it is named #659), slugs are legal directory names on every platform a repository may be checked out on (fix(6.0): a slug must be a legal directory name on every platform we ship #661), and the ordinal is retired so the ledger stays readable forever (refactor(6.0): retire the ordinal and keep the ledger readable forever #665). This removes numbering collisions from the identity path. Nothing allocates into.specsync/change-sequence.jsonany more. It is still written, though:floor_sequence_ledger_to_committedraises a stale working-tree copy to the committed high-water mark before staging and mergesacknowledged_collisions(bug(change):change check --commitself-commits change-sequence.json backwards, regressing the high-water mark #533). An earlier version of this post said "nothing writes it" — that was wrong, and it came from taking an error message in the source literally rather than reading the function beside it.rc.6). What a change learned is folded back into the module's own context at proposal, at build, and at archive, so the next change to that module starts from the last one's dead ends rather than rediscovering them.shipnames the fold-back too (fix(lessons): ship must name the fold-back too, and fold #697's bundle #700, docs(lessons): say how the fold-back terminates, and that ship names it too #710).rc.7), and no later approval may withdraw that binding (a portable v5.0.1 approval downgrades the delta binding to "no claim" and lets a swapped delta through #719, onmain).What the release candidates fixed, and where each landed
rc.3rc.3reopennow admits commit unreachability as a staleness axis in its own right, instead of only content drift.rc.3initandchange newname the lifecycle you are on (#678). An upgraded 5.x repository silently stayed on workflow v1 and only found out atship.rc.4ADD COLUMNthat agrees with an existing column is a no-op.rc.4docs/ADOPTING.mdnames the remedy for path ownership, not just the trap:--specand--no-spec-changecoexist (#682).rc.5db_tablesdeclared without a configurableschema_diris a notice, not a strict-gating warning (#684). Gating a release on advice the reader cannot take is not drift detection.rc.6rc.6rc.6rc.7viewa single spec (#715). One canonical frontmatter reader, CRLF-tolerant, plus.gitattributeseol=lfpins.rc.7## MODIFIEDblock could be silently dropped.rc.7rc.8rc.8source_dirssurvives a manifest discovery failure, and an in-repo GradleincludeBuildis judged by its path rather than refused on its token prefix (#723).rc.9includeBuildconfiguration block is skipped, not refused (#725).rc.8andrc.9came from one adopter's two reports, days apart, on a real Gradle build. That is the most useful kind of contribution this project gets.The recurring shape, stated plainly because it should guide what comes next
Many of the above are the same defect wearing different clothes: a category is empty for want of input, and the code reads it as a verdict.
Where this recurs, the fix is not to relax the check. It is to distinguish cannot answer from answered no, and to say which one happened.
What is open, and what decides it
These are not commitments to a schedule. They are the questions currently blocking, ordered by how much they change what the tool promises.
1. What
verifiedmay depend on, given that squash-merge is the norm. This repository is squash-only; measured atd6f266a4, 21 of 198 archived changes have a verification commit reachable fromorigin/main, and 3 of 515 commits onmainare merges. Commit ancestry is not an occasionally-unavailable guarantee here, it is an unavailable one.rc.6(verification.commit ancestry is incompatible with squash-only repositories, and there is no available workaround #689):ship_status_reportwas the one caller still asking a history question that every other currency path had stopped asking. Squash, rebase, and merge-commit all reach ready to finalize..specsync/changes/and.specsync/archive/, exactly the regionworkspace_digestdeliberately excludes, so content digests cannot replace it. Measurement corrected the framing: of 107 archived reviews only 2 cite a commit that is an ancestor oforigin/main, and 0 of 107 would pass the walk at all — archiving relocates the workspace out from under the walk's own allowlist. The walk also never reads a blob, so it asserts history shape rather than content.AuthenticatedHistorycurrently means "I checked the evidence" on one path and "this went through the process" on the other, with no way for a reader to tell them apart.2. Line endings in the delta binding (#730), and it affects you if your team is mixed-OS. The digest binding a delta to its approval hashes raw bytes, while the code that applies a delta explicitly treats line-ending style as not part of the content. A change approved on Linux and checked out on Windows with
core.autocrlf=truetherefore fails its own gate with nothing edited. This repository is immune only because it pinseol=lf; we fixed our instance and shipped the class. Open and specified.3. Lifecycle dead ends, and telling the user which door is open. #685, #690, and #688 are one family: the tool names a verb it will then refuse, and the working alternative goes unmentioned. A deliberate constraint worth preserving in any fix:
reopenrequires--actorand--reason, so the only door out of a wedged state cannot be opened by an agent acting alone. The goal is to name that door, not to make it easier to walk through unattended.4. Gates that are weaker than they look. #708 —
change checkdoes not run clippy at all, and the clippy that CI does run omits--all-targets, so the ~2,800 tests in this repository have never been linted. #712 — the merge-before-finalize warning reaches only operators who run the verb that prints it, which the mistaken operator by definition does not. #714 — a folded lesson that is wrong is worse than no lesson, and nothing checks one.5. Records whose work has already shipped. #679:
auditreports an affirmative pass while changes sit inimplementingwith their deliverables live in the default branch. A record whoseaffected_pathsare all present in the comparison base is not stalled, it is finished-and-unrecorded, and those want different messages.6. Adoption ergonomics. #674 (a v1 change that merges during adoption), #554 (no way to withdraw a draft, and scope freezes at the interview), #541 (cancellation as a first-class terminal state that keeps its lessons).
7. Parser depth, driven by fixtures rather than by language checklists. The open half of #672 is embedded DDL: statements written as string literals without trailing terminators, which is valid for
db.Execand is how most Go and TypeScript projects carry their schema.70 issues are open. This is the subset that adoption made load-bearing, not the whole board.
How to influence this
The single most useful contribution right now is a measurement of your own repository, not a feature request. Counts by state, which archives are anchored, which paths are present on your default branch, what your merge strategy actually is. Three separate explanations of one coupling were wrong in a single day of work on this, and every one of them was killed by somebody running a command instead of reading source. That pattern has held: three issues in this release were filed with diagnoses that were directionally right and specifically wrong, and each was caught only when an implementer re-derived from the diff.
Concretely:
React or reply with the outcome you need rather than a feature name, so these decisions stay evidence-led.
All reactions