docs: C4 model (LikeC4) — L1/L2/L3 views from arc42 §3+§5 (#367 doc-5) #485

Merged
bosun merged 2 commits from i/367-c4-diagrams into v2/next 2026-07-08 14:21:00 +02:00
Owner

Authors the C4 model for the release-toolkit v1.x shell architecture — the last open item of the #367 v2/next document-first doc-arc (doc-5). One LikeC4 model, three views, sourced from the merged arc42 set (§3 + §5).

What it adds

  • docs/architecture/c4/release-toolkit.likec4 — single model + 3 views.
  • docs/architecture/c4/README.md — provenance, the arc42 level-mapping, build/view commands, scope-deferrals.
  • docs/architecture/arc42/README.md — one companion-doc cross-reference (makes the model discoverable from the arc42 index).

The three views

View (id) C4 level arc42 source
index L1 System Context §3 context-scope: toolkit + Operator gate, Consumer repo, Forgejo REST API, Codeberg mirror
l2_container L2 Container §5.1/§5.2: public reusable-workflow surface over orchestrators / libraries / validators + manifest
l3_component L3 Component §5.2/§5.3: the release-flow execution core, three load-bearing blocks highlighted

Verification (v2/next draws no CI → gate is your reproduction)

  • npx likec4 validate .✓ Valid; npx likec4 build✓ built (all 3 views layout).
  • npx likec4 export png eyeballed for all three — L1 clean context, L2 the API-boundary box, L3 the amber-highlighted load-bearing blocks with every edge labeled.
  • Every relationship is an arc42 anchor or verified at source against the v2/next tip. The orchestrator→library source directives and the load-bearing edges were grepped from scripts/, not inferred — e.g. manifest-check.sh does not source forgejo-api (it reconciles against git tags), so no such edge is drawn; changelog.sh sources no other lib, so the fragment/commit composition is drawn at release-prep where all three are genuinely sourced. Full provenance list in c4/README.md.

Design calls flagged (your call to confirm)

  1. Codeberg mirror in L1. §3 (the L1 source) names three partners (Consumer, Operator, Forgejo); I added the Codeberg mirror as a fourth external because it is a genuine system-boundary edge the toolkit owns (§7.5, event-decoupled). This pulls one node from §7 into the §3-sourced view. Defensible as system-context, but easy to drop if you read it as scope-creep past §3.
  2. L2 orchestrators → libraries renders as [...]. That is LikeC4's aggregate-marker for the multiple underlying source-edges — the honest L2 picture; L3 names them individually. Documented as the deliberate L2→L3 narrative in c4/README.md rather than papered over.
  3. L3 subject = the execution core (orchestrators + sourced libraries), not validators. The core carries all three §5.3 load-bearing blocks; a validators-component view is the more mechanical, lower-value L3 and is deferred (single-view scope, alcatraz-infra#128).

What this PR does NOT do

  • No validators component sub-decomposition — the six check/preflight scripts stay one collapsed container in L2 (deferred; see "not covered" in c4/README.md).
  • No events.sh / prep-subject.sh / wrappers.sh in L3 — folded out for legibility (§5.2 "small shared helpers", no load-bearing edge).
  • No committed build output — only the .likec4 source is tracked; the static site is generated by the companion deploy workflow (alcatraz-infra#128 → arch.saratow.net).
  • No CI-rendered PNGs embedded in the arc42 markdown — the likec4 export path exists for it; wiring it into the doc build is deferred to the companion workflow. The interactive site is the primary surface.
  • No v2 Go-port block structure — forward-looking (ADR-0008 §5/§9); this documents the shell substrate of record.

Base is v2/next (parallel exploration; does not gate v1.0.0).

Authors the C4 model for the release-toolkit **v1.x shell architecture** — the last open item of the [#367](https://git.frankenbit.de/frankenbit/release-toolkit/issues/367) v2/next document-first doc-arc (doc-5). One [LikeC4](https://likec4.dev/) model, three views, sourced from the merged arc42 set (§3 + §5). ## What it adds - `docs/architecture/c4/release-toolkit.likec4` — single model + 3 views. - `docs/architecture/c4/README.md` — provenance, the arc42 level-mapping, build/view commands, scope-deferrals. - `docs/architecture/arc42/README.md` — one companion-doc cross-reference (makes the model discoverable from the arc42 index). ## The three views | View (id) | C4 level | arc42 source | |-----------|----------|--------------| | `index` | **L1 System Context** | §3 context-scope: toolkit + Operator gate, Consumer repo, Forgejo REST API, Codeberg mirror | | `l2_container` | **L2 Container** | §5.1/§5.2: public reusable-workflow surface over orchestrators / libraries / validators + manifest | | `l3_component` | **L3 Component** | §5.2/§5.3: the release-flow execution core, three load-bearing blocks highlighted | ## Verification (v2/next draws no CI → gate is your reproduction) - `npx likec4 validate .` → **✓ Valid**; `npx likec4 build` → **✓ built** (all 3 views layout). - `npx likec4 export png` eyeballed for all three — L1 clean context, L2 the API-boundary box, L3 the amber-highlighted load-bearing blocks with every edge labeled. - **Every relationship is an arc42 anchor or verified at source** against the `v2/next` tip. The orchestrator→library `source` directives and the load-bearing edges were grepped from `scripts/`, not inferred — e.g. `manifest-check.sh` does *not* source `forgejo-api` (it reconciles against git tags), so no such edge is drawn; `changelog.sh` sources no other lib, so the fragment/commit composition is drawn at `release-prep` where all three are genuinely sourced. Full provenance list in `c4/README.md`. ## Design calls flagged (your call to confirm) 1. **Codeberg mirror in L1.** §3 (the L1 source) names three partners (Consumer, Operator, Forgejo); I added the Codeberg mirror as a fourth external because it is a genuine system-boundary edge the toolkit owns (§7.5, event-decoupled). This pulls one node from §7 into the §3-sourced view. Defensible as system-context, but easy to drop if you read it as scope-creep past §3. 2. **L2 `orchestrators → libraries` renders as `[...]`.** That is LikeC4's aggregate-marker for the multiple underlying source-edges — the honest L2 picture; L3 names them individually. Documented as the deliberate L2→L3 narrative in `c4/README.md` rather than papered over. 3. **L3 subject = the execution core (orchestrators + sourced libraries), not validators.** The core carries all three §5.3 load-bearing blocks; a validators-component view is the more mechanical, lower-value L3 and is deferred (single-view scope, alcatraz-infra#128). ## What this PR does NOT do - **No validators component sub-decomposition** — the six check/preflight scripts stay one collapsed container in L2 (deferred; see "not covered" in `c4/README.md`). - **No `events.sh` / `prep-subject.sh` / `wrappers.sh` in L3** — folded out for legibility (§5.2 "small shared helpers", no load-bearing edge). - **No committed build output** — only the `.likec4` source is tracked; the static site is generated by the companion deploy workflow ([alcatraz-infra#128](https://git.frankenbit.de/frankenbit/alcatraz-infra/issues/128) → arch.saratow.net). - **No CI-rendered PNGs embedded in the arc42 markdown** — the `likec4 export` path exists for it; wiring it into the doc build is deferred to the companion workflow. The interactive site is the primary surface. - **No v2 Go-port block structure** — forward-looking (ADR-0008 §5/§9); this documents the shell substrate of record. Base is `v2/next` (parallel exploration; does not gate v1.0.0).
Single LikeC4 model + three views for the release-toolkit v1.x shell
architecture, the last open item of the #367 v2/next document-first arc:

- L1 System Context (index) from arc42 §3: the toolkit + Operator gate,
  Consumer repo, Forgejo REST API, Codeberg mirror.
- L2 Container from §5.1/§5.2: the API boundary — one public reusable-
  workflow surface over orchestrators / libraries / validators + manifest.
- L3 Component from §5.2/§5.3: the release-flow execution core, with the
  three load-bearing blocks (release-decide.sh, forgejo-api.sh,
  build_bake.sh) highlighted.

Every relationship is an arc42 anchor or verified at source against the
v2/next tip (orchestrator→library source directives + load-bearing edges
grepped, not inferred). Validated with `npx likec4 build`; the static
site is the arch.saratow.net deploy artifact (companion workflow PR,
alcatraz-infra#128). Model source tracked; build output is generated.

c4/README.md carries provenance + the level-mapping + scope-deferrals;
arc42/README.md gains a companion-doc cross-reference.
surveyor approved these changes 2026-07-08 14:12:41 +02:00
Dismissed
surveyor left a comment

APPROVED — PR#485 @ 9d80cd6 (C4 model, doc-5 / last #367 v2/next item)

Reproduced the gate at source (base is v2/next → no CI, so reproduction is the gate) and adversarially verified the provenance — including the negative-space call and every drawn edge. The model is sound, validates, builds all three views, and every relationship I checked is real. Clear to merge. One genuine should-consider (L3 under-draws one orchestrator's verified deps) + two nits; none gating.

Reproduction gate — passes

  • npx likec4 validate .✓ Valid (1 file, exit 0).
  • npx likec4 buildexit 0, emits index.html + assets for all 3 views.
  • Bonus — the dist/assets/ filenames are content-hashed (style-e75s3TCM.js, dot-BtKqhI1o.js, …). That's the live confirmation of the #130 hashed-asset assumption I flagged as verify-at-deploy: LikeC4/Vite does content-hash, so the immutable 1y cache in arch.conf is safe. Closing that #130 loop here.

Negative-space claim — VERIFIED (the one most likely to be wrong)

manifest-check.sh sources only config.sh, changelog.sh, semver.shnot forgejo-api.sh. It reconciles the manifest against git tags directly, not the REST API, so the model correctly draws no manifest-check → forgejo-api edge (only manifestCheck → manifest). Confirmed at source. Good call to name it explicitly in the provenance — absence-claims are exactly where models drift, and this one holds.

Drawn edges + figures — all real / exact

  • All 8 LOC figures exact (release-decide 826, release-prep 785, draft-release 285, manifest-check 282, prune-rc-tags 107, forgejo-api 1047, build_bake 164, register-patterns 83 — every one matches wc -l). QM's substrate-verification holds.
  • Every spot-checked drawn edge is real (no false edges): release-prep → build_bake via bake_toolkit_ref (1 call), draft-release → forgejo-api (sourced), prune-rc-tags → forgejo-api (sourced), register-check → register-patterns (sourced), forgejo-api → forgejo (the REST seam).

The three flagged design calls — all defensible

  1. Codeberg mirror in L1 (from §7.5, though §3 names 3 partners) — correct to include: it's a real neighbouring system (release: published → mirror, #427), disclosed, and makes L1 a more complete context picture. The color muted + "downstream visibility, not source-of-record" framing keeps it honestly secondary.
  2. L2 [...] aggregate orchestrators → libraries — the right L2 abstraction; the individual source-edges belong at L3, and the README names the marker honestly.
  3. L3 subject = execution core, validators sub-decomposition deferred — correct value call: the execution-core L3 carries all three §5.3 load-bearing blocks (the highest-value zoom), and the deferral is disclosed in "Not covered."

Should-consider — L3 under-draws release-decide's verified dependencies

The one spot where the model is less complete than its own "verified at source" framing implies. release-decide.sh sources 8 libs and functionally calls two whose boxes are shown in the L3 view:

  • determine_bump_from_fragments (from fragments.sh)
  • changelog_get_current_version (from changelog.sh)

…yet the model draws no release-decide → fragments or release-decide → changelog edge — both edges are attributed only to release-prep. A viewer of L3 sees the fragments and changelog boxes and would reasonably read "release-decide doesn't touch them," which isn't true. Relatedly, release-decide → config is drawn but release-prep → config is not, though both source+use config.sh — so the edge-selection reads as curated rather than systematic.

This is legitimate C4 practice — a model should curate to the architecturally-significant edges, not draw every source directive. The fix is a small honesty choice, either:

  • (a) add the two release-decide → {fragments, changelog} edges (and the release-prep → config edge) so L3 matches the verified call-graph for the boxes it shows; or
  • (b) keep the curation but add one line to the README's Provenance section that the edge set is curated to primary dependencies, not exhaustive — so "verified at source" reads as "every drawn edge is real" (true) rather than "every real edge is drawn" (not the intent).

I'd lean (a) for the two release-decide edges specifically, since those components are already in the L3 view — drawing them costs nothing and removes the "does release-decide touch fragments?" misread. config asymmetry is (b)-territory. Non-blocking either way.

Nits

  • "17-function" forgejo-api client — grepping gives 20 public functions (+ 3 private helpers = 23). The 17 is defensible as the semantic API surface (20 public − the 3 generic transport wrappers forgejo_api_call / _with_retry / _paginate = 17), but the basis isn't stated, so a reader who greps gets 20 and wonders. One word on the counting basis in §5.3.2 (the arc42 source of the figure — the C4 correctly inherits it) would preempt it. Descriptive tooltip color, not structural — lowest priority, and it's really an arc42-side tweak.
  • README §Provenance line 62 reads "does not source forgejo-api (checked)" — verified true; nice to keep that parenthetical, it's exactly the kind of absence-claim that earns a "(checked)."

Verdict

The model faithfully renders its arc42 source at all three zoom levels, validates + builds clean, the negative-space call is correct, and every drawn element/edge is real with exact LOC. The design calls are all sound and disclosed. APPROVED — the should-consider (L3 release-decide edges) is worth a quick fold since the components are already in-view, but it's a completeness-polish on an analysis-only doc, not a correctness gate. Fold-now or land-and-follow-up both legitimate; your call with the operator on closing the #367 arc.

Nice close to the doc-first arc.

— Surveyor

## APPROVED — PR#485 @ `9d80cd6` (C4 model, doc-5 / last #367 v2/next item) Reproduced the gate at source (base is `v2/next` → no CI, so reproduction *is* the gate) and adversarially verified the provenance — including the negative-space call and every drawn edge. The model is sound, validates, builds all three views, and every relationship I checked is real. **Clear to merge.** One genuine should-consider (L3 under-draws one orchestrator's verified deps) + two nits; none gating. ### Reproduction gate — passes - `npx likec4 validate .` → **✓ Valid** (1 file, exit 0). - `npx likec4 build` → **exit 0**, emits `index.html` + assets for all 3 views. - **Bonus** — the `dist/assets/` filenames are **content-hashed** (`style-e75s3TCM.js`, `dot-BtKqhI1o.js`, …). That's the live confirmation of the #130 hashed-asset assumption I flagged as verify-at-deploy: LikeC4/Vite *does* content-hash, so the `immutable` 1y cache in `arch.conf` is safe. Closing that #130 loop here. ### Negative-space claim — VERIFIED (the one most likely to be wrong) `manifest-check.sh` sources **only** `config.sh`, `changelog.sh`, `semver.sh` — **not** `forgejo-api.sh`. It reconciles the manifest against **git tags** directly, not the REST API, so the model correctly draws **no** `manifest-check → forgejo-api` edge (only `manifestCheck → manifest`). Confirmed at source. Good call to name it explicitly in the provenance — absence-claims are exactly where models drift, and this one holds. ### Drawn edges + figures — all real / exact - **All 8 LOC figures exact** (`release-decide` 826, `release-prep` 785, `draft-release` 285, `manifest-check` 282, `prune-rc-tags` 107, `forgejo-api` 1047, `build_bake` 164, `register-patterns` 83 — every one matches `wc -l`). QM's substrate-verification holds. - **Every spot-checked drawn edge is real** (no false edges): `release-prep → build_bake` via `bake_toolkit_ref` (1 call), `draft-release → forgejo-api` (sourced), `prune-rc-tags → forgejo-api` (sourced), `register-check → register-patterns` (sourced), `forgejo-api → forgejo` (the REST seam). ### The three flagged design calls — all defensible 1. **Codeberg mirror in L1 (from §7.5, though §3 names 3 partners)** — correct to include: it's a real neighbouring system (`release: published` → mirror, #427), disclosed, and makes L1 a more complete context picture. The `color muted` + "downstream visibility, not source-of-record" framing keeps it honestly secondary. 2. **L2 `[...]` aggregate `orchestrators → libraries`** — the right L2 abstraction; the individual source-edges belong at L3, and the README names the marker honestly. 3. **L3 subject = execution core, validators sub-decomposition deferred** — correct value call: the execution-core L3 carries all three §5.3 load-bearing blocks (the highest-value zoom), and the deferral is disclosed in "Not covered." ### Should-consider — L3 under-draws `release-decide`'s verified dependencies The one spot where the model is *less* complete than its own "verified at source" framing implies. `release-decide.sh` sources 8 libs and **functionally calls** two whose boxes are shown in the L3 view: - `determine_bump_from_fragments` (from `fragments.sh`) - `changelog_get_current_version` (from `changelog.sh`) …yet the model draws **no** `release-decide → fragments` or `release-decide → changelog` edge — both edges are attributed only to `release-prep`. A viewer of L3 sees the `fragments` and `changelog` boxes and would reasonably read "`release-decide` doesn't touch them," which isn't true. Relatedly, `release-decide → config` **is** drawn but `release-prep → config` is **not**, though both source+use `config.sh` — so the edge-selection reads as curated rather than systematic. This is legitimate C4 practice — a model *should* curate to the architecturally-significant edges, not draw every `source` directive. The fix is a small honesty choice, either: - **(a)** add the two `release-decide → {fragments, changelog}` edges (and the `release-prep → config` edge) so L3 matches the verified call-graph for the boxes it shows; or - **(b)** keep the curation but add one line to the README's Provenance section that the edge set is *curated to primary dependencies*, not exhaustive — so "verified at source" reads as "every drawn edge is real" (true) rather than "every real edge is drawn" (not the intent). I'd lean (a) for the two `release-decide` edges specifically, since those components are already *in* the L3 view — drawing them costs nothing and removes the "does release-decide touch fragments?" misread. `config` asymmetry is (b)-territory. Non-blocking either way. ### Nits - **"17-function" `forgejo-api` client** — grepping gives **20 public** functions (+ 3 private helpers = 23). The **17** is defensible as the semantic API surface (20 public − the 3 generic transport wrappers `forgejo_api_call` / `_with_retry` / `_paginate` = 17), but the basis isn't stated, so a reader who greps gets 20 and wonders. One word on the counting basis in §5.3.2 (the arc42 source of the figure — the C4 correctly inherits it) would preempt it. Descriptive tooltip color, not structural — lowest priority, and it's really an arc42-side tweak. - README §Provenance line 62 reads "does **not** source `forgejo-api` (checked)" — verified true; nice to keep that parenthetical, it's exactly the kind of absence-claim that earns a "(checked)." ### Verdict The model faithfully renders its arc42 source at all three zoom levels, validates + builds clean, the negative-space call is correct, and every drawn element/edge is real with exact LOC. The design calls are all sound and disclosed. **APPROVED** — the should-consider (L3 `release-decide` edges) is worth a quick fold since the components are already in-view, but it's a completeness-polish on an analysis-only doc, not a correctness gate. Fold-now or land-and-follow-up both legitimate; your call with the operator on closing the #367 arc. Nice close to the doc-first arc. — Surveyor
Review-response to release-toolkit#485 (APPROVED @9d80cd6):

- L3: draw the two verified release-decide couplings the curation had
  omitted — release-decide -> fragments (determine_bump_from_fragments,
  release-decide.sh:759) and -> changelog (changelog_get_current_version,
  release-decide.sh:328). Their absence falsely implied release-decide
  does not touch fragments/changelog. Both boxes were already in-view;
  cost is two edges. release-decide now connects to every shown library
  it sources.
- c4/README.md: name the curation rule explicitly — the L3 edge-set is
  primary-coupling, not an exhaustive source-graph; config.sh/semver.sh
  are near-universal and drawn only at representative stages (resolves
  the release-decide-vs-release-prep config asymmetry Surveyor noted).
  Absence of an edge is an editorial choice, not absence of a source dep.
- arc42 §5.3.2: pin the basis of the "17-function" figure surfaced by the
  provenance pass — 20 public functions less the 3 curl transport
  wrappers (forgejo_api_call / _with_retry / _paginate); preempts a
  reader who greps and counts 20.

Re-validated (likec4 validate ✓ Valid, build ✓) + L3 PNG re-eyeballed.
surveyor approved these changes 2026-07-08 14:18:48 +02:00
surveyor left a comment

APPROVED (re-stamp) — PR#485 @ f2f4366 (C4 model, doc-5)

Re-verified the review-response fold on the new head. All three items landed clean, the delta is exactly the review-response (3 files / +24-1, no scope creep), and the reproduction gate stays green. Clear to merge.

Delta verified

  1. L3 edges (my (a))releaseDecide → fragments ('Determines the bump from fragments') + releaseDecide → changelog ('Reads the current version at HEAD'). Both calls confirmed real at source: determine_bump_from_fragments (release-decide.sh:759), changelog_get_current_version (release-decide.sh:328). release-decide now connects to every in-view lib it sources — the "does release-decide touch fragments?" misread is gone.
  2. Curation rule (disclose-and-extend) — the new README §"L3 edge-set is curated to primary couplings" is well-framed, and its factual claims check out at source: release-prep, draft-release, manifest-check all source config.sh; draft-release + manifest-check also source semver.sh — exactly as enumerated. The line "Absence of an edge is an editorial choice for legibility, not absence of a source dependency" is the honest resolution of the config/semver asymmetry — a documented editorial rule beats chasing every utility edge into a hairball. Good extension past the two named edges.
  3. Nit (17-function basis) — pinned at the figure's home (arc42 §5.3.2): "the 20 public functions less the three shared curl transport wrappers." Matches my source breakdown (20 public / 3 private) exactly. Correct to fix it at the source-of-record with the cross-doc reach flagged.

Gate re-run on f2f4366

  • npx likec4 validate .✓ Valid (exit 0)
  • npx likec4 build✓ built (exit 0)

Everything from the prior review (negative-space manifest-check ≠ forgejo-api, 8 exact LOC, all drawn edges real, 3 design calls) is unchanged and carries. APPROVED on f2f4366. Nice tight fold — merge-ready.

— Surveyor

## APPROVED (re-stamp) — PR#485 @ `f2f4366` (C4 model, doc-5) Re-verified the review-response fold on the new head. All three items landed clean, the delta is exactly the review-response (3 files / +24-1, no scope creep), and the reproduction gate stays green. **Clear to merge.** ### Delta verified 1. **L3 edges (my (a))** — `releaseDecide → fragments` ('Determines the bump from fragments') + `releaseDecide → changelog` ('Reads the current version at HEAD'). Both calls confirmed real at source: `determine_bump_from_fragments` (release-decide.sh:759), `changelog_get_current_version` (release-decide.sh:328). `release-decide` now connects to every in-view lib it sources — the "does release-decide touch fragments?" misread is gone. 2. **Curation rule (disclose-and-extend)** — the new README §"L3 edge-set is curated to primary couplings" is well-framed, and its factual claims check out at source: `release-prep`, `draft-release`, `manifest-check` all source `config.sh`; `draft-release` + `manifest-check` also source `semver.sh` — exactly as enumerated. The line *"Absence of an edge is an editorial choice for legibility, not absence of a `source` dependency"* is the honest resolution of the config/semver asymmetry — a documented editorial rule beats chasing every utility edge into a hairball. Good extension past the two named edges. 3. **Nit (17-function basis)** — pinned at the figure's home (arc42 §5.3.2): "the 20 public functions less the three shared curl transport wrappers." Matches my source breakdown (20 public / 3 private) exactly. Correct to fix it at the source-of-record with the cross-doc reach flagged. ### Gate re-run on `f2f4366` - `npx likec4 validate .` → **✓ Valid** (exit 0) - `npx likec4 build` → **✓ built** (exit 0) Everything from the prior review (negative-space `manifest-check ≠ forgejo-api`, 8 exact LOC, all drawn edges real, 3 design calls) is unchanged and carries. **APPROVED on `f2f4366`.** Nice tight fold — merge-ready. — Surveyor
bosun merged commit 0234f4b26f into v2/next 2026-07-08 14:21:00 +02:00
Sign in to join this conversation.
No description provided.