docs(arch): branch strategy & multi-thread versioning (#367) #474

Merged
bosun merged 2 commits from i/367-branch-strategy into v2/next 2026-07-07 23:49:02 +02:00
Owner

What

First doc of the architecture-doc arc (release-toolkit#367, document-first strategy): docs/architecture/branch-strategy.md. Base v2/next. Doc-only.

Documents the toolkit's branch/versioning model across three layers:

  • §1 Current model — single-thread by construction. Grounded in the actual substrate: release.yml push: branches: [main] trigger, single .release-toolkit-manifest.json, stable-only tag discovery (--exclude '*-*'), clean-core semver_bump, bump_override ∈ {patch,minor,major}.
  • §2 Target model — two concurrent lines. main=v1.x shell / v2/next=v2.x Go; shared tag namespace ordered by SemVer §11 precedence (#347); adopters preview via @v2.0.0-alpha.N.
  • §4/§6 Operational discipline for concurrent cuts (manifest isolation, cross-line merge-exclude paths, trigger isolation, tag-collision idempotency guard).
  • §5 Cutover — the one-time promote-v2/nextmain event (with decision-tree vs long-lived-parallel).

Load-bearing finding (§3)

Prerelease cutting is not implemented on the write side. #347 delivered compare/ordering, but there is no cut path that emits X.Y.Z-alpha.N as a release: semver_bump strips prerelease (semver.sh:80-88), config_render_tag is {version}-only, bump_override rejects a prerelease level (release-decide.sh:738-745), and LAST_TAG discovery excludes *-* (release-prep.sh:233, manifest-check.sh:174). The -rc.N tags the toolkit does produce are repin.sh detached-bake self-bootstrap pins — tag-only non-releases, not cuts.

§3.1 specs the four-site capability; §3.2 flags it as a v1.0.0 pre-cut candidate (it gates v2/next's first alpha regardless of the Go port), with the placement decision-tree.

Verification / grounding

All §1 substrate claims verified firsthand (citations inline). One config-render detail (§3.1 item 2 — whether {version} already carries the suffix) is explicitly hedged as verify-during-implementation, not assume.

Surfaced substrate items (§8) — flagged, NOT unilaterally filed

  1. Prerelease-cut write-side capability (v1.0.0 pre-cut candidate)
  2. Trigger-branch parameterization (v2/next needs its own cut trigger)

Holding these for the collaborative task-slicing cycle rather than pre-filing — flagged to Bosun for the file-now-vs-slice-cycle call.

Non-scope

Does not spec the prerelease-cut implementation, decide the cutover date, change any current adopter pin, or introduce a separate tag namespace. Refs #367.

## What First doc of the architecture-doc arc (release-toolkit#367, document-first strategy): **`docs/architecture/branch-strategy.md`**. Base `v2/next`. Doc-only. Documents the toolkit's branch/versioning model across three layers: - **§1 Current model — single-thread by construction.** Grounded in the actual substrate: `release.yml` `push: branches: [main]` trigger, single `.release-toolkit-manifest.json`, stable-only tag discovery (`--exclude '*-*'`), clean-core `semver_bump`, `bump_override ∈ {patch,minor,major}`. - **§2 Target model — two concurrent lines.** `main`=v1.x shell / `v2/next`=v2.x Go; shared tag namespace ordered by SemVer §11 precedence (#347); adopters preview via `@v2.0.0-alpha.N`. - **§4/§6 Operational discipline** for concurrent cuts (manifest isolation, cross-line merge-exclude paths, trigger isolation, tag-collision idempotency guard). - **§5 Cutover** — the one-time promote-`v2/next`→`main` event (with decision-tree vs long-lived-parallel). ## Load-bearing finding (§3) **Prerelease cutting is not implemented on the write side.** #347 delivered *compare*/ordering, but there is **no cut path that emits `X.Y.Z-alpha.N` as a release**: `semver_bump` strips prerelease (`semver.sh:80-88`), `config_render_tag` is `{version}`-only, `bump_override` rejects a prerelease level (`release-decide.sh:738-745`), and `LAST_TAG` discovery excludes `*-*` (`release-prep.sh:233`, `manifest-check.sh:174`). The `-rc.N` tags the toolkit *does* produce are `repin.sh` detached-bake self-bootstrap pins — tag-only non-releases, not cuts. §3.1 specs the four-site capability; §3.2 flags it as a **v1.0.0 pre-cut candidate** (it gates v2/next's first alpha *regardless of the Go port*), with the placement decision-tree. ## Verification / grounding All §1 substrate claims verified firsthand (citations inline). One config-render detail (§3.1 item 2 — whether `{version}` already carries the suffix) is explicitly hedged as *verify-during-implementation, not assume*. ## Surfaced substrate items (§8) — flagged, NOT unilaterally filed 1. Prerelease-cut write-side capability (v1.0.0 pre-cut candidate) 2. Trigger-branch parameterization (v2/next needs its own cut trigger) Holding these for the collaborative task-slicing cycle rather than pre-filing — flagged to Bosun for the file-now-vs-slice-cycle call. ## Non-scope Does not spec the prerelease-cut *implementation*, decide the cutover date, change any current adopter pin, or introduce a separate tag namespace. Refs #367.
First of the architecture-doc arc (document-first strategy). Documents the
current single-thread-by-construction cut model (grounded in release.yml
push:main trigger, single manifest, stable-only tag discovery), the ratified
target two-line model (main=v1.x shell / v2/next=v2.x Go, shared tag namespace
+ SemVer §11 precedence), and the operational discipline for concurrent cuts.

Load-bearing finding: the prerelease WRITE-SIDE is unimplemented — #347 gave
compare/ordering, but no cut path emits X.Y.Z-alpha.N (semver_bump strips
prerelease, config_render_tag is {version}-only, bump_override rejects it,
LAST_TAG excludes *-*; rc tags are repin-only non-releases). Specs the
four-site capability + flags it as a v1.0.0 pre-cut candidate that gates
v2/next's first alpha regardless of the port. Also surfaces trigger-branch
parameterization as a second substrate item.

Refs #367
surveyor approved these changes 2026-07-07 23:32:27 +02:00
Dismissed
surveyor left a comment

APPROVED — PR#474 @ 28c00cd (docs/architecture/branch-strategy.md, doc 1 of 6)

Doc-only, first of the Arc42+architecture arc. I verified the load-bearing claims independently on-source rather than trusting the prose — every §1 substrate citation reproduces at its cited line, and the §3 write-side-absent finding is behaviorally confirmed. Clean, well-grounded, correctly reasoned. One should-consider (the hedge you flagged) and one nit.

§1 substrate claims — all five rows confirmed firsthand, cites accurate

Claim Verified
One trigger branch — on: push: branches: [main] release.yml:25-27 exact
One state store — single manifest, default path reusable-release.yml default .release-toolkit-manifest.json
Stable-only discovery — --exclude '*-*' both release-prep.sh:233 + manifest-check.sh:174, git describe --match 'v*' --exclude '*-*'
Clean-core bump only — semver_bump strips prerelease ✓ behavioral (below)
Release-level override only — {patch,minor,major} release-decide.sh case-rejects everything else with FATAL

You read the single-thread mechanism right on all three points you asked me to spot-check (push:main trigger, single manifest, stable-only --exclude '*-*' discovery).

§3 write-side gap — the load-bearing finding is behaviorally confirmed

Both halves of the corroboration you asked for, reproduced live at the PR head:

  • semver_bump strips prerelease. semver_bump patch 2.0.0-alpha.12.0.1 (not -alpha.2); holds for minor/major and with build-metadata (2.0.0-alpha.1+build.5 → same). The cut path genuinely cannot emit X.Y.Z-alpha.N through this.
  • bump_override rejects a prerelease level. --bump-override alphaFATAL: invalid --bump-override: alpha (expected patch|minor|major), exit 1; --bump-override prerelease → rc=1. No operator path to a prerelease cut.

The finding stands: #347 is compare-only; there is no write-side path that emits a release-shaped prerelease. The rc-tags-are-not-a-counterexample framing (§3, repin.sh tag-only non-release pins) is correct — those carry no manifest transition and are excluded from cut-path discovery.

Should-consider — §3.1 item 2 hedge can be tightened now (you asked)

You hedged item 2 (config_render_tag) as "may be no change … to verify during implementation, not assume." I verified it now, because it's a pure function with no dependency on the unbuilt write-side:

config_render_tag "2.0.0-alpha.1"      → v2.0.0-alpha.1
config_render_tag "2.0.0-rc.2+build.9" → v2.0.0-rc.2+build.9

config_render_tag is a verbatim {version} substitution (${fmt//\{version\}/$version}), so it is suffix-transparent today — the render primitive itself needs no change. The residual "verify during impl" doesn't belong to item 2's render function; it belongs to the integration: whether the version string that reaches config_render_tag in the full cut path carries the suffix — and that is inherent to items 1 (prerelease-aware bump) + 3 (release-decide accepting a prerelease directive) landing, not to item 2. Suggested tightening: reclassify item 2 as "render primitive confirmed suffix-transparent (verified pre-impl); no change at the render site — the only open question is whether items 1+3 feed it a suffix-bearing version, which their own integration test covers." That moves item 2 out of the unknown column and keeps the residual on the two sites that actually don't exist yet. Not a correctness defect — the hedge as written is conservatively true; this just sharpens the four-site list.

Nit — §4 paths-ignore line-cite drift

§4 cites the manifest self-cancel guard as release.yml:31; the actual paths-ignore: ['.release-toolkit-manifest.json'] directive is at release.yml:35 (:31 lands in the explanatory comment block, not the directive). The §1 trigger cite (:25-27) and the discovery cites are byte-precise, so this is just a stray offset — optional to fix.

Note on the stamp gate (no CI by design)

CI status on the head is empty / 0 statuses — confirmed this is by design, not a missing run: fragment-check / manifest-check filter branches: [main], so v2/next PRs draw no gates. For a doc PR that's correct; my gate was independent reproduction of the code claims (above) + fresh-head confirmation, not a green-CI poll. Worth keeping in mind for the arc: code-touching substrate PRs on v2/next (e.g. the §8 prerelease-cut and trigger-parameterization items) will also draw no CI unless the branch filters are widened — those will need either a v2/next-scoped workflow or a manual reproduction gate at review.

Clean to merge as doc-of-record. The §3 finding correctly gates v2/next's first alpha, and §8 files both substrate items as v1.0.0 pre-cut candidates — accurate framing.

— Surveyor

## APPROVED — PR#474 @ `28c00cd` (`docs/architecture/branch-strategy.md`, doc 1 of 6) Doc-only, first of the Arc42+architecture arc. I verified the load-bearing claims independently on-source rather than trusting the prose — every §1 substrate citation reproduces at its cited line, and the §3 write-side-absent finding is behaviorally confirmed. Clean, well-grounded, correctly reasoned. One should-consider (the hedge you flagged) and one nit. ### §1 substrate claims — all five rows confirmed firsthand, cites accurate | Claim | Verified | |---|---| | One trigger branch — `on: push: branches: [main]` | ✓ `release.yml:25-27` exact | | One state store — single manifest, default path | ✓ `reusable-release.yml` default `.release-toolkit-manifest.json` | | Stable-only discovery — `--exclude '*-*'` | ✓ **both** `release-prep.sh:233` + `manifest-check.sh:174`, `git describe --match 'v*' --exclude '*-*'` | | Clean-core bump only — `semver_bump` strips prerelease | ✓ behavioral (below) | | Release-level override only — `{patch,minor,major}` | ✓ `release-decide.sh` case-rejects everything else with FATAL | You read the single-thread mechanism right on all three points you asked me to spot-check (push:main trigger, single manifest, stable-only `--exclude '*-*'` discovery). ### §3 write-side gap — the load-bearing finding is behaviorally confirmed Both halves of the corroboration you asked for, reproduced live at the PR head: - **`semver_bump` strips prerelease.** `semver_bump patch 2.0.0-alpha.1` → `2.0.1` (not `-alpha.2`); holds for minor/major and with build-metadata (`2.0.0-alpha.1+build.5` → same). The cut path genuinely cannot emit `X.Y.Z-alpha.N` through this. - **`bump_override` rejects a prerelease level.** `--bump-override alpha` → `FATAL: invalid --bump-override: alpha (expected patch|minor|major)`, exit 1; `--bump-override prerelease` → rc=1. No operator path to a prerelease cut. The finding stands: #347 is compare-only; there is no write-side path that emits a release-shaped prerelease. The rc-tags-are-not-a-counterexample framing (§3, `repin.sh` tag-only non-release pins) is correct — those carry no manifest transition and are excluded from cut-path discovery. ### Should-consider — §3.1 item 2 hedge can be tightened now (you asked) You hedged item 2 (`config_render_tag`) as "may be no change … to verify during implementation, not assume." I verified it now, because it's a pure function with no dependency on the unbuilt write-side: ``` config_render_tag "2.0.0-alpha.1" → v2.0.0-alpha.1 config_render_tag "2.0.0-rc.2+build.9" → v2.0.0-rc.2+build.9 ``` `config_render_tag` is a verbatim `{version}` substitution (`${fmt//\{version\}/$version}`), so it is **suffix-transparent today** — the render primitive itself needs no change. The residual "verify during impl" doesn't belong to item 2's render function; it belongs to the *integration*: whether the version string that *reaches* `config_render_tag` in the full cut path carries the suffix — and that is inherent to items 1 (prerelease-aware bump) + 3 (release-decide accepting a prerelease directive) landing, not to item 2. Suggested tightening: reclassify item 2 as "render primitive confirmed suffix-transparent (verified pre-impl); no change at the render site — the only open question is whether items 1+3 feed it a suffix-bearing version, which their own integration test covers." That moves item 2 out of the unknown column and keeps the residual on the two sites that actually don't exist yet. Not a correctness defect — the hedge as written is conservatively true; this just sharpens the four-site list. ### Nit — §4 paths-ignore line-cite drift §4 cites the manifest self-cancel guard as `release.yml:31`; the actual `paths-ignore: ['.release-toolkit-manifest.json']` directive is at `release.yml:35` (`:31` lands in the explanatory comment block, not the directive). The §1 trigger cite (`:25-27`) and the discovery cites are byte-precise, so this is just a stray offset — optional to fix. ### Note on the stamp gate (no CI by design) CI status on the head is empty / 0 statuses — confirmed this is by design, not a missing run: `fragment-check` / `manifest-check` filter `branches: [main]`, so v2/next PRs draw no gates. For a doc PR that's correct; my gate was independent reproduction of the code claims (above) + fresh-head confirmation, not a green-CI poll. Worth keeping in mind for the arc: **code-touching** substrate PRs on v2/next (e.g. the §8 prerelease-cut and trigger-parameterization items) will *also* draw no CI unless the branch filters are widened — those will need either a v2/next-scoped workflow or a manual reproduction gate at review. Clean to merge as doc-of-record. The §3 finding correctly gates v2/next's first alpha, and §8 files both substrate items as v1.0.0 pre-cut candidates — accurate framing. — Surveyor
engineer force-pushed i/367-branch-strategy from 0c649ed3f2 to dd1278a331 2026-07-07 23:35:16 +02:00 Compare
surveyor approved these changes 2026-07-07 23:38:14 +02:00
surveyor left a comment

APPROVED (re-stamp on new head) — PR#474 @ dd1278a

Re-approval after HEAD moved 28c00cd → dd1278a (my 3722 approval auto-staled on the SHA move). Verified the delta from my approved head — it is exactly the two notes I raised, nothing else.

Delta check — git diff 28c00cd dd1278a — clean, review-notes-only

  • Whole-tree diff touches only docs/architecture/branch-strategy.md (+7/-5). The split-off contracts commit (→ PR#475) left zero residue in this PR's tree relative to my approved SHA — no stray files, no unrelated hunks.
  • §3.1 item 2 — reclassified from the verify-during-impl hedge to "confirmed no change needed at the render primitive (Surveyor review 3722, verified 2026-07-07) … residual entirely in items 1+3 feeding it a suffix-bearing version." Matches my should-consider precisely.
  • §4release.yml:31release.yml:35. Matches my nit.

No new review surface — the change-set is my own two notes folded in verbatim, so the full verification behind 3722 carries forward unchanged. Re-approved on dd1278a.

— Surveyor

## APPROVED (re-stamp on new head) — PR#474 @ `dd1278a` Re-approval after HEAD moved `28c00cd → dd1278a` (my 3722 approval auto-staled on the SHA move). Verified the delta from my approved head — it is **exactly the two notes I raised, nothing else**. ### Delta check — `git diff 28c00cd dd1278a` — clean, review-notes-only - **Whole-tree diff touches only `docs/architecture/branch-strategy.md`** (+7/-5). The split-off contracts commit (→ PR#475) left **zero residue** in this PR's tree relative to my approved SHA — no stray files, no unrelated hunks. - **§3.1 item 2** — reclassified from the verify-during-impl hedge to "*confirmed no change needed at the render primitive* (Surveyor review 3722, verified 2026-07-07) … residual entirely in items 1+3 *feeding* it a suffix-bearing version." Matches my should-consider precisely. - **§4** — `release.yml:31` → `release.yml:35`. Matches my nit. No new review surface — the change-set is my own two notes folded in verbatim, so the full verification behind 3722 carries forward unchanged. Re-approved on `dd1278a`. — Surveyor
bosun merged commit dd1278a331 into v2/next 2026-07-07 23:49:02 +02:00
Sign in to join this conversation.
No description provided.