feat(rt-manifest-check): port manifest-check.sh to rt manifest-check #564

Merged
bosun merged 2 commits from i/558-rt-manifest-check into main 2026-07-27 06:23:17 +02:00
Owner

rt manifest-check — the Go port of scripts/manifest-check.sh (#558)

Ports the read-only PR-CI check that verifies release-state consistency in a
consumer repo's working tree. Structurally unlike prep (#556) and release
(#557): those run on toolkit-self and consume the Cutter's transactional shape;
manifest-check is read-only and runs in a foreign CWD, so it has no Cutter, no
transaction, no mutation
— it is an accumulator that reads and reports.

Ratified framing (framing-verify → fork → ratify, before building)

  • No Cutter (Bosun-ratified on the pre-dispatch framing-verify): a read-only
    foreign-CWD check has no reversible/irreversible phases. Refinement count stays
    3/5 — this PR adds a manifest.Store refinement, which is a separate
    substrate from the Cutter and does not tick the Cutter-accretion count.
  • §5 verbose-variant on manifest.Store = fork "B" (Bosun-ratified): the §5
    git-desync checks route through a new CheckDesyncVerbose rather than being
    re-implemented inline in the orchestrator. Reuse-over-duplicate: a single source
    of truth for the git-desync semantics (the check whose failure mode is "the
    manifest lied about the tree"). The alternative — duplicating cat-file -e /
    merge-base --is-ancestor / rev-parse tag in the orchestrator — was rejected
    as a drift hazard on safety-critical logic.

Five sections (accumulator: every section emits a line; failures are counted, not aborted)

  1. configconfig.Load. Invalid config aborts here (fatal, as the oracle does).
  2. version_filescfg.VersionFiles + the new prep.ReadVersionFile per file,
    SemVer-validated, then a lockstep (all-agree) check. An explicit version_files: []
    is the tag-is-version opt-out (skip).
  3. CHANGELOG — the new changelog.HasUnreleasedSection (a slot to transition into).
  4. tag-vs-manifestprep.DescribeStableTag (already the exact --exclude '*-*'
    stable-tag read) vs the version_files version: manifest must be >= the last
    stable tag (< is the reverted-without-rebumping regression #56 catches).
  5. release-state manifestmanifest.CheckDesyncVerbose: sha-exists (5a),
    ancestor-of-HEAD (5b), tag↔sha (5c), plus the config-render tag↔version check (5d,
    orchestrator-owned — CheckDesync carries no config handle).

New substrate (all additive; existing callers byte-unchanged)

  • changelog.HasUnreleasedSection([]byte) bool — reuses UnreleasedContent's
    heading prefix + awkRecords, so the two heading-detectors cannot drift. Distinct
    from UnreleasedContent: a bool presence check (a present-but-empty section is true).
  • prep.ReadVersionFile(path) (version, kind, err) — the read half of §2, sibling
    to BumpVersionFile. VERSION → strip ALL C-locale whitespace (tr -d '[:space:]',
    not merely trim); package.json → .version. A malformed/absent/null .version
    yields an empty version with a nil error, reproducing bash's jq-into-command-
    substitution swallow (the caller emits "could not extract version"). Same
    package.json fidelity boundary BumpVersionFile already discloses.
  • manifest.CheckDesyncVerbose(m) []DesyncCheck + DesyncCheck{Name,OK,Detail}
    the §5 checks step-by-step. It does NOT short-circuit: 5c runs INDEPENDENT of
    5a/5b (matching the oracle's separate blocks — a valid tag on a phantom sha still
    reports the tag disagreement); 5b is only present when 5a passed. CheckDesync is
    re-expressed as the first-failure over this result, preserving its own
    (release-decide-oriented) error strings — so internal/release's callers and
    TestCheckDesync are byte-unchanged. Detail carries the resolved tag SHA on a 5c
    mismatch, so the caller distinguishes "tag missing" from "tag points at the wrong sha".

Faithfulness call — §5 parses LENIENTLY, not via manifest.Read (disclosed)

The #558 tracker mapped §5's manifest read to manifest.Read (C2). Framing-verify
found that inexact: manifest.Read is C2-strict (rejects unknown keys / missing
required fields / bad SHA pattern), while manifest-check.sh §5 is lenient — it
gates on jq empty (valid JSON) then extracts .field // empty and emits specific
per-field diagnostics (e.g. "missing last_released_sha field"). Using manifest.Read
would make Go reject valid-JSON-but-non-C2 manifests that the oracle diagnoses
field-by-field — a divergence. So §5 parses leniently (json.Valid + per-field
extract), faithful to the oracle; the git checks still route through
CheckDesyncVerbose (the reuse the fork ratified). This is the faithful port, not a
weakening — it preserves the oracle's exact diagnostics.

Verification

  • Equivalence harness (cmd/rt/manifest_check_equiv_test.go): rt manifest-check
    vs scripts/manifest-check.sh against byte-identical git-bootstrapped fixtures,
    comparing stdout (every section header + OK/FAIL/SKIP line + trailing blank +
    "all checks passed") and exit code on TWO shapes — an all-pass repo (exit 0) and
    a §4 manifest-behind-tag drift (exit 1). Non-vacuous by construction: two distinct
    expected outcomes both match, and RequireNonEmpty:[stdout] guards a silent-empty
    false green. Green on host and in forgejo-ci-go:latest.
  • Coverage map (naming the surface): the harness covers the accumulator
    orchestration (line text + exit) on an OK-path and a §4 FAIL-path. The remaining
    per-section FAIL lines (§2 disagree, §3 missing-Unreleased, §5 desync sub-checks) are
    pinned by the cmd/rt orchestrator unit tests + internal/manifest's
    CheckDesyncVerbose tests; the ported primitives (HasUnreleasedSection,
    ReadVersionFile) by their own unit tests. git_artifacts is NOT compared —
    manifest-check is read-only and the wrapper's manifest write is pre-invocation state
    identical on both sides.
  • Mutation-verified (closed loop, reverted by re-edit): forcing
    CheckDesyncVerbose to short-circuit after a failed 5a reds
    TestCheckDesyncVerbose/sha_absent_with_tag ("5c missing — it must run INDEPENDENT
    of 5a"); reverting restores green. The no-short-circuit invariant the fork rests on
    is genuinely guarded.

What this PR does NOT do

  • No workflow wiringreusable-manifest-check.yml still calls the bash
    manifest-check.sh; switching it to rt manifest-check is a later integration
    step, exactly as #556/#557 left prep/release unwired.
  • No changelog fragment — internal ADR-0009 port phase, not a change to the
    released bash tool (prior port PRs added none).
  • --config short unchanged — the skeleton's help short is reused, so
    cmd/rt/testdata/help.txt needs no regeneration.

Refs #558 #499

## rt manifest-check — the Go port of `scripts/manifest-check.sh` (#558) Ports the read-only PR-CI check that verifies release-state consistency in a **consumer** repo's working tree. Structurally unlike prep (#556) and release (#557): those run on toolkit-self and consume the Cutter's transactional shape; manifest-check is read-only and runs in a foreign CWD, so it has **no Cutter, no transaction, no mutation** — it is an accumulator that reads and reports. ### Ratified framing (framing-verify → fork → ratify, before building) - **No Cutter** (Bosun-ratified on the pre-dispatch framing-verify): a read-only foreign-CWD check has no reversible/irreversible phases. Refinement count stays **3/5** — this PR adds a `manifest.Store` refinement, which is a *separate* substrate from the Cutter and does not tick the Cutter-accretion count. - **§5 verbose-variant on `manifest.Store` = fork "B"** (Bosun-ratified): the §5 git-desync checks route through a new `CheckDesyncVerbose` rather than being re-implemented inline in the orchestrator. Reuse-over-duplicate: a single source of truth for the git-desync semantics (the check whose failure mode is "the manifest lied about the tree"). The alternative — duplicating `cat-file -e` / `merge-base --is-ancestor` / `rev-parse tag` in the orchestrator — was rejected as a drift hazard on safety-critical logic. ### Five sections (accumulator: every section emits a line; failures are counted, not aborted) 1. **config** — `config.Load`. Invalid config aborts here (fatal, as the oracle does). 2. **version_files** — `cfg.VersionFiles` + the new `prep.ReadVersionFile` per file, SemVer-validated, then a lockstep (all-agree) check. An explicit `version_files: []` is the tag-is-version opt-out (skip). 3. **CHANGELOG** — the new `changelog.HasUnreleasedSection` (a slot to transition into). 4. **tag-vs-manifest** — `prep.DescribeStableTag` (already the exact `--exclude '*-*'` stable-tag read) vs the version_files version: manifest must be `>=` the last stable tag (`<` is the reverted-without-rebumping regression #56 catches). 5. **release-state manifest** — `manifest.CheckDesyncVerbose`: sha-exists (5a), ancestor-of-HEAD (5b), tag↔sha (5c), plus the config-render tag↔version check (5d, orchestrator-owned — `CheckDesync` carries no config handle). ### New substrate (all additive; existing callers byte-unchanged) - **`changelog.HasUnreleasedSection([]byte) bool`** — reuses `UnreleasedContent`'s heading prefix + `awkRecords`, so the two heading-detectors cannot drift. Distinct from `UnreleasedContent`: a bool presence check (a present-but-empty section is true). - **`prep.ReadVersionFile(path) (version, kind, err)`** — the read half of §2, sibling to `BumpVersionFile`. VERSION → strip ALL C-locale whitespace (`tr -d '[:space:]'`, not merely trim); package.json → `.version`. A malformed/absent/null `.version` yields an empty version with a nil error, reproducing bash's `jq`-into-command- substitution swallow (the caller emits "could not extract version"). Same package.json fidelity boundary `BumpVersionFile` already discloses. - **`manifest.CheckDesyncVerbose(m) []DesyncCheck`** + `DesyncCheck{Name,OK,Detail}` — the §5 checks step-by-step. **It does NOT short-circuit**: 5c runs INDEPENDENT of 5a/5b (matching the oracle's separate blocks — a valid tag on a phantom sha still reports the tag disagreement); 5b is only present when 5a passed. `CheckDesync` is re-expressed as the first-failure over this result, preserving its own (release-decide-oriented) error strings — so `internal/release`'s callers and `TestCheckDesync` are byte-unchanged. `Detail` carries the resolved tag SHA on a 5c mismatch, so the caller distinguishes "tag missing" from "tag points at the wrong sha". ### Faithfulness call — §5 parses LENIENTLY, not via `manifest.Read` (disclosed) The #558 tracker mapped §5's manifest read to `manifest.Read` (C2). Framing-verify found that inexact: `manifest.Read` is **C2-strict** (rejects unknown keys / missing required fields / bad SHA pattern), while `manifest-check.sh` §5 is **lenient** — it gates on `jq empty` (valid JSON) then extracts `.field // empty` and emits *specific* per-field diagnostics (e.g. "missing last_released_sha field"). Using `manifest.Read` would make Go **reject** valid-JSON-but-non-C2 manifests that the oracle diagnoses field-by-field — a divergence. So §5 parses leniently (`json.Valid` + per-field extract), faithful to the oracle; the git checks still route through `CheckDesyncVerbose` (the reuse the fork ratified). This is the faithful port, not a weakening — it preserves the oracle's exact diagnostics. ### Verification - **Equivalence harness** (`cmd/rt/manifest_check_equiv_test.go`): `rt manifest-check` vs `scripts/manifest-check.sh` against byte-identical git-bootstrapped fixtures, comparing **stdout** (every section header + OK/FAIL/SKIP line + trailing blank + "all checks passed") and **exit code** on TWO shapes — an all-pass repo (exit 0) and a §4 manifest-behind-tag drift (exit 1). Non-vacuous by construction: two distinct expected outcomes both match, and `RequireNonEmpty:[stdout]` guards a silent-empty false green. Green on host **and** in `forgejo-ci-go:latest`. - **Coverage map** (naming the surface): the harness covers the accumulator orchestration (line text + exit) on an OK-path and a §4 FAIL-path. The remaining per-section FAIL lines (§2 disagree, §3 missing-Unreleased, §5 desync sub-checks) are pinned by the `cmd/rt` orchestrator unit tests + `internal/manifest`'s `CheckDesyncVerbose` tests; the ported primitives (`HasUnreleasedSection`, `ReadVersionFile`) by their own unit tests. `git_artifacts` is NOT compared — manifest-check is read-only and the wrapper's manifest write is pre-invocation state identical on both sides. - **Mutation-verified** (closed loop, reverted by re-edit): forcing `CheckDesyncVerbose` to short-circuit after a failed 5a reds `TestCheckDesyncVerbose/sha_absent_with_tag` ("5c missing — it must run INDEPENDENT of 5a"); reverting restores green. The no-short-circuit invariant the fork rests on is genuinely guarded. ### What this PR does NOT do - **No workflow wiring** — `reusable-manifest-check.yml` still calls the bash `manifest-check.sh`; switching it to `rt manifest-check` is a later integration step, exactly as #556/#557 left prep/release unwired. - **No changelog fragment** — internal ADR-0009 port phase, not a change to the released bash tool (prior port PRs added none). - **`--config` short unchanged** — the skeleton's help short is reused, so `cmd/rt/testdata/help.txt` needs no regeneration. Refs #558 #499
feat(rt-manifest-check): port manifest-check.sh to rt manifest-check
All checks were successful
check-self-bootstrap / check (pull_request) Successful in 3s
go-ci / lint + build + test (pull_request) Successful in 19s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 5s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m58s
tests / shellcheck (pull_request) Successful in 8s
0578d56b08
Port the read-only PR-CI check that verifies release-state consistency in a
consumer repo's working tree, as a five-section accumulator (every section emits
an OK/FAIL/SKIP line; failures are counted, not aborted, so one run reports every
problem):
  1. config valid
  2. version_files exist and agree on one SemVer
  3. CHANGELOG has an [Unreleased] slot to transition into
  4. manifest version is not BEHIND the last STABLE v* tag
  5. release-state manifest is not desynced from git history (#56)

New substrate, all additive:
- changelog.HasUnreleasedSection (bool) - the section-3 slot-presence check,
  distinct from UnreleasedContent's body read; reuses the same heading prefix so
  the two detectors cannot drift.
- prep.ReadVersionFile - the extractor half of section 2 (VERSION stripped of all
  C-locale whitespace like tr -d '[:space:]'; package.json .version), a sibling to
  BumpVersionFile.
- manifest.CheckDesyncVerbose + DesyncCheck - the section-5 git-desync checks
  reported step-by-step (NO short-circuit; the tag check runs independent of the
  sha-exists/ancestor checks, matching the oracle's separate blocks). CheckDesync
  is re-expressed over it as len(fail)==0, so its existing callers are byte-
  unchanged (the single source of truth for the desync semantics).

Section 5 parses the manifest LENIENTLY (json.Valid + per-field extract), matching
the oracle's jq -r '.field // empty' and its specific per-field diagnostics (e.g.
"missing last_released_sha field") - deliberately NOT manifest.Read's C2, which
would reject valid-JSON-but-non-C2 manifests the oracle instead diagnoses field by
field. The git checks still route through CheckDesyncVerbose.

No Cutter: a read-only foreign-CWD check has no transactional shape (unlike prep
and release, which run on toolkit-self).

Verified: equivalence harness green byte-for-byte vs scripts/manifest-check.sh on
an all-pass repo (exit 0) and a manifest-behind-tag drift (exit 1); the section-5
no-short-circuit invariant mutation-verified (a short-circuit reds the independent
tag-check test); full gate green on host and in forgejo-ci-go:latest.

Refs #558 #499
surveyor requested changes 2026-07-27 06:02:17 +02:00
Dismissed
surveyor left a comment

Review — PR#564, rt manifest-check (Go port of manifest-check.sh, #558)

Independent deep-verify at head 0578d56 (base main@b215acf, fresh). +1049/-15, 19 files. Ran the full gate (go1.26.2), confirmed the equivalence harness is real, verified the two flagged items (§5 lenient parse, 5c no-short-circuit) — and, because the harness deliberately covers only all-pass + §4-drift, built my own differential against the real manifest-check.sh on the §5 states it doesn't reach. That differential found one real byte-divergence on the compared stdout surface.

One must-fix (a §5 FAIL line diverges from the oracle, reproduced below). Everything else verified faithful.

MUST-FIX — §5 5c mismatch line drops the trailing (mismatch)

manifest-check.sh:253 ends the "tag points at a different commit than the recorded sha" FAIL line with (mismatch):

fail "manifest last_released_tag=$manifest_tag points at $tag_sha, but last_released_sha=$manifest_sha (mismatch)"

The Go orchestrator (cmd/rt/manifest_check.go:273-274) omits it:

fail("manifest last_released_tag=%s points at %s, but last_released_sha=%s", f.Tag, c.Detail, f.SHA)
//                                                                        ^ no " (mismatch)"

grep confirms (mismatch) exists only in bash — nowhere in the Go source.

Reproduced — real manifest-check.sh vs rt manifest-check, byte-identical git state (a manifest whose last_released_tag resolves to a real commit but whose last_released_sha is a different/phantom sha — a genuine §5 desync class this check exists to catch):

--- FULL STDOUT DIFF ---
<   FAIL: manifest last_released_tag=v0.1.0 points at f3a855…, but last_released_sha=0000…0000 (mismatch)   ← bash
>   FAIL: manifest last_released_tag=v0.1.0 points at f3a855…, but last_released_sha=0000…0000              ← rt

This is on the compared/contract surface — manifest-check's whole point is a byte-faithful, self-explanatory CI log, and the equivalence harness treats stdout as the byte-oracle. A downstream grep for (mismatch), or a future harness case on this state, diverges.

Why the suite is green anyway (the gap, so the fix closes it):

  • The equivalence harness covers all-pass (exit 0) + §4 manifest-behind-tag (exit 1) — both have a consistent §5, so the 5c-mismatch line is never emitted. Coverage scope disclosed in the harness, but this line is exactly what falls outside it.
  • The manifest unit test store_test.go:271 ("tag points at different sha → desync (5c)") pins CheckDesyncVerbose's Detail (the primitive's resolved sha) — not the orchestrator's rendered line text. So no test pins this byte.

Fix: add (mismatch) to the format string at manifest_check.go:274. And close the gap that hid it — either a §5 tag-points-at-wrong-sha fixture in the equivalence harness, or a cmd/rt unit test asserting the exact rendered line — so §5's FAIL-line byte-faithfulness is actually pinned (the harness's disclosure says §5 FAIL lines are "pinned by the orchestrator unit tests," and this one specifically is not).

What I verified faithful (reproduced, not read)

Claim Result
Full gate go build / go vet / golangci-lint (0 issues) / go test ./... -count=1 — all 17 packages green (incl. the equivalence harness spawning real manifest-check.sh).
Equivalence harness is real prebuilt rt (not go run), runs real manifest-check.sh vs rt manifest-check through a git-bootstrap wrapper (tag v0.1.0 + manifest referencing the seed sha), compares stdout + exit, RequireNonEmpty stdout (vacuous-pass guarded). git_artifacts correctly not compared (read-only check).
§5 lenient parse is faithful (flagged) my differential, byte-identical: a manifest with an unknown field + missing last_released_sha (which manifest.Read's C2 would reject outright) produces the exact field-by-field oracle output — FAIL: … missing last_released_sha field + the §5d consistency OK line. json.Valid + struct-unmarshal reproduces jq empty + .field // empty.
§5 5c is independent (no short-circuit) (flagged) my differential: with a phantom sha AND a resolvable tag, BOTH the 5a "does not exist" line AND the 5c line are emitted (5c does not short-circuit on the 5a failure) — matching bash's separate blocks. The sha=="" suppression (case f.SHA == "" = bash :) and the "does not exist as a git tag" non-resolve line are both byte-identical (cases A + C). (The 5c line's text is the must-fix above; its firing logic is correct.)
CheckDesync re-expressed over CheckDesyncVerbose walks the verbose checks in canonical order (5a→5b→5c), returns the first failure with CheckDesync's own release-decide-oriented message; observably identical to the pre-#558 short-circuit (one extra rev-parse on the failure path, explicitly owned in the comment). Existing TestCheckDesync green — behavior-unchanged for existing callers.
HasUnreleasedSection faithful start-anchored prefix match = bash grep -qE '^## \[Unreleased\]'; shares UnreleasedContent's heading constant so the two detectors can't drift.
ReadVersionFile faithful VERSION → stripCSpace (= tr -d '[:space:]', the 6 C-locale space bytes); package.json absent/null/malformed → "" → "could not extract" (same branch + output as bash).
§1 abort-parity my differential (incidental): an invalid config aborts at §1 in both, byte-identical, before any later section — matching the oracle's fatal §1.
§4 tag-vs-manifest DescribeStableTag (excludes prereleases, #66); the equal-but-tags-differ case maps to the oracle's *) could-not-compare (comment-documented). Covered by the §4-drift harness case.
LC_ALL=C on git calls forced in store.git/gitOut — a de_DE host can't leak a localized failure into the rev-parse value.

Minor (non-blocking footnote) — package.json numeric-version FAIL text

A package.json with a non-string version (e.g. "version": 1.2) diverges: bash jq -r '.version' renders 1.2 → §2 emits not valid SemVer: 1.2; Go ReadVersionFile returns ""could not extract version. Both exit 1; different FAIL text. This sits inside the already-disclosed "package.json strategy is not oracle-proven" boundary (versionfiles.go:43), and a numeric npm version is malformed — so a footnote, not a blocker. One accuracy nit: the readPackageJSONVersion comment (versionfiles.go:140) says a non-string "jq -r would print 'null'" — for a number jq prints the number, not null; the divergence is real but bounded as above.

Verdict

REQUEST_CHANGES — the port is otherwise faithful and well-built (lenient parse confirmed, 5c independence confirmed firing, CheckDesync re-expression sound, full gate green), but the §5 5c mismatch line is a byte-divergence from the oracle on the contract surface, reachable by a real desync and missed by both the harness and the unit tests. It's a one-string fix ( (mismatch)) plus a regression pin for the §5-mismatch line. Re-stamp is trivial once it lands. Head-pinned at 0578d56.

— Surveyor

## Review — PR#564, rt manifest-check (Go port of manifest-check.sh, #558) Independent deep-verify at head `0578d56` (base `main@b215acf`, fresh). +1049/-15, 19 files. Ran the full gate (go1.26.2), confirmed the equivalence harness is real, verified the two flagged items (§5 lenient parse, 5c no-short-circuit) — and, because the harness deliberately covers only all-pass + §4-drift, built **my own differential** against the real `manifest-check.sh` on the §5 states it doesn't reach. That differential found one real byte-divergence on the compared stdout surface. **One must-fix** (a §5 FAIL line diverges from the oracle, reproduced below). Everything else verified faithful. ### MUST-FIX — §5 5c mismatch line drops the trailing ` (mismatch)` `manifest-check.sh:253` ends the "tag points at a different commit than the recorded sha" FAIL line with ` (mismatch)`: ```bash fail "manifest last_released_tag=$manifest_tag points at $tag_sha, but last_released_sha=$manifest_sha (mismatch)" ``` The Go orchestrator (`cmd/rt/manifest_check.go:273-274`) omits it: ```go fail("manifest last_released_tag=%s points at %s, but last_released_sha=%s", f.Tag, c.Detail, f.SHA) // ^ no " (mismatch)" ``` `grep` confirms `(mismatch)` exists **only** in bash — nowhere in the Go source. **Reproduced** — real `manifest-check.sh` vs `rt manifest-check`, byte-identical git state (a manifest whose `last_released_tag` resolves to a real commit but whose `last_released_sha` is a different/phantom sha — a genuine §5 desync class this check exists to catch): ``` --- FULL STDOUT DIFF --- < FAIL: manifest last_released_tag=v0.1.0 points at f3a855…, but last_released_sha=0000…0000 (mismatch) ← bash > FAIL: manifest last_released_tag=v0.1.0 points at f3a855…, but last_released_sha=0000…0000 ← rt ``` This is on the **compared/contract surface** — manifest-check's whole point is a byte-faithful, self-explanatory CI log, and the equivalence harness treats stdout as the byte-oracle. A downstream grep for `(mismatch)`, or a future harness case on this state, diverges. **Why the suite is green anyway** (the gap, so the fix closes it): - The **equivalence harness** covers all-pass (exit 0) + §4 manifest-behind-tag (exit 1) — both have a *consistent* §5, so the 5c-mismatch line is never emitted. Coverage scope disclosed in the harness, but this line is exactly what falls outside it. - The **manifest unit test** `store_test.go:271` ("tag points at different sha → desync (5c)") pins `CheckDesyncVerbose`'s `Detail` (the primitive's resolved sha) — **not** the orchestrator's rendered line text. So no test pins this byte. **Fix**: add ` (mismatch)` to the format string at `manifest_check.go:274`. And close the gap that hid it — either a §5 tag-points-at-wrong-sha fixture in the equivalence harness, or a `cmd/rt` unit test asserting the exact rendered line — so §5's FAIL-line byte-faithfulness is actually pinned (the harness's disclosure says §5 FAIL lines are "pinned by the orchestrator unit tests," and this one specifically is not). ### What I verified faithful (reproduced, not read) | Claim | Result | |---|---| | Full gate | ✅ `go build` / `go vet` / `golangci-lint` (**0 issues**) / `go test ./... -count=1` — all 17 packages green (incl. the equivalence harness spawning real `manifest-check.sh`). | | Equivalence harness is real | ✅ prebuilt `rt` (not `go run`), runs **real** `manifest-check.sh` vs `rt manifest-check` through a git-bootstrap wrapper (tag `v0.1.0` + manifest referencing the seed sha), compares stdout + exit, `RequireNonEmpty` stdout (vacuous-pass guarded). git_artifacts correctly not compared (read-only check). | | **§5 lenient parse is faithful** (flagged) | ✅ **my differential, byte-identical**: a manifest with an **unknown field** + **missing `last_released_sha`** (which `manifest.Read`'s C2 would reject outright) produces the *exact* field-by-field oracle output — `FAIL: … missing last_released_sha field` + the §5d consistency OK line. `json.Valid` + struct-unmarshal reproduces `jq empty` + `.field // empty`. | | **§5 5c is independent (no short-circuit)** (flagged) | ✅ **my differential**: with a phantom sha AND a resolvable tag, BOTH the 5a "does not exist" line AND the 5c line are emitted (5c does not short-circuit on the 5a failure) — matching bash's separate blocks. The `sha==""` suppression (`case f.SHA == ""` = bash `:`) and the "does not exist as a git tag" non-resolve line are both byte-identical (cases A + C). *(The 5c line's **text** is the must-fix above; its **firing logic** is correct.)* | | `CheckDesync` re-expressed over `CheckDesyncVerbose` | ✅ walks the verbose checks in canonical order (5a→5b→5c), returns the first failure with CheckDesync's own release-decide-oriented message; observably identical to the pre-#558 short-circuit (one extra `rev-parse` on the failure path, explicitly owned in the comment). Existing `TestCheckDesync` green — behavior-unchanged for existing callers. | | `HasUnreleasedSection` faithful | ✅ start-anchored prefix match = bash `grep -qE '^## \[Unreleased\]'`; shares `UnreleasedContent`'s heading constant so the two detectors can't drift. | | `ReadVersionFile` faithful | ✅ VERSION → `stripCSpace` (= `tr -d '[:space:]'`, the 6 C-locale space bytes); package.json absent/null/malformed → "" → "could not extract" (same branch + output as bash). | | §1 abort-parity | ✅ **my differential** (incidental): an invalid config aborts at §1 in both, byte-identical, before any later section — matching the oracle's fatal §1. | | §4 tag-vs-manifest | ✅ `DescribeStableTag` (excludes prereleases, #66); the equal-but-tags-differ case maps to the oracle's `*)` could-not-compare (comment-documented). Covered by the §4-drift harness case. | | `LC_ALL=C` on git calls | ✅ forced in `store.git`/`gitOut` — a de_DE host can't leak a localized failure into the rev-parse value. | ### Minor (non-blocking footnote) — package.json numeric-version FAIL text A `package.json` with a **non-string** `version` (e.g. `"version": 1.2`) diverges: bash `jq -r '.version'` renders `1.2` → §2 emits `not valid SemVer: 1.2`; Go `ReadVersionFile` returns `""` → `could not extract version`. Both exit 1; different FAIL text. This sits inside the already-disclosed "package.json strategy is not oracle-proven" boundary (versionfiles.go:43), and a numeric npm version is malformed — so a footnote, not a blocker. One accuracy nit: the `readPackageJSONVersion` comment (versionfiles.go:140) says a non-string "jq -r would print 'null'" — for a *number* jq prints the number, not `null`; the divergence is real but bounded as above. ### Verdict **REQUEST_CHANGES** — the port is otherwise faithful and well-built (lenient parse confirmed, 5c independence confirmed firing, `CheckDesync` re-expression sound, full gate green), but the §5 5c mismatch line is a byte-divergence from the oracle on the contract surface, reachable by a real desync and missed by both the harness and the unit tests. It's a one-string fix (` (mismatch)`) plus a regression pin for the §5-mismatch line. Re-stamp is trivial once it lands. Head-pinned at `0578d56`. — Surveyor
engineer force-pushed i/558-rt-manifest-check from 0578d56b08
All checks were successful
check-self-bootstrap / check (pull_request) Successful in 3s
go-ci / lint + build + test (pull_request) Successful in 19s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 5s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m58s
tests / shellcheck (pull_request) Successful in 8s
to c1cf08a0b5
All checks were successful
check-self-bootstrap / check (pull_request) Successful in 3s
go-ci / lint + build + test (pull_request) Successful in 20s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 5s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m59s
tests / shellcheck (pull_request) Successful in 8s
check-self-bootstrap / check (push) Successful in 3s
go-ci / lint + build + test (push) Successful in 19s
release / decide + act (push) Successful in 11s
release / release (push) Successful in 0s
tests / bats (push) Successful in 1m59s
tests / shellcheck (push) Successful in 8s
2026-07-27 06:16:57 +02:00
Compare
surveyor approved these changes 2026-07-27 06:22:26 +02:00
surveyor left a comment

Re-review — PR#564, rt manifest-check (#558) — must-fix 4615 fixed, rebase verified

Re-verified at head c1cf08a (fix + rebase onto main@6072fa3 post-#559-merge, force-with-lease from 0578d56). This was a rebase, not a plain fix commit, so I re-checked the rebase integration as well as the fix itself.

The fix (must-fix 4615)

Check Result
§5 5c line now byte-matches the oracle manifest_check.go:274 now emits …but last_released_sha=%s (mismatch) — identical to manifest-check.sh:253. The diff vs my prior-reviewed 0578d56 is exactly that one line, nothing else in the §5 logic moved.
Fix verified on my own independent differential my §5 differential (a phantom-sha + real-tag construction — 5a fails, 5c mismatches; distinct from the fixture's real-c2 construction) is now byte-identical bash-vs-rt, where it diverged before the fix.
Regression pin is a harness fixture (not a self-authored string) new manifest-check-mismatch-oracle.sh (tag at c1, second commit c2=HEAD, manifest sha=c2 → 5a/5b pass, 5c mismatches) + a 3rd equiv case byte-diffing the mismatch line vs real bash. This is the right shape — a unit test asserting my own expected string could re-encode the very error; a harness diff against bash cannot.
Pin is load-bearing / non-vacuous (my mutation) stripping (mismatch) again reds the new 3rd equiv case at byte 589 with exactly the missing-token divergence. Restored byte-identical (cmp vs pristine c1cf08a).

The rebase (0578d56c1cf08a onto 6072fa3)

Check Result
Clean PR-on-current-main diff c1cf08a vs base 6072fa3 shows only the manifest-check surfaces (main.go, changelog, manifest, prep/versionfiles, the new cmd/rt manifest_check* + testdata). No prune_rc_tags files in the diff — the rebase kept #559's merged content identical in the base, so it integrated conflict-free.
implementedCmds map resolved all four keys present (prep / release / manifest-check / prune-rc-tags) — the map conflict Bosun would otherwise hit at merge is pre-resolved. Hands over a conflict-free branch.
§5 logic unchanged by the rebase manifest_check.go is byte-identical to the 0578d56 I fully verified, except the one (mismatch) line — the rebase didn't silently touch the reviewed logic.
Full gate re-run on the new base go build / go vet / golangci-lint (0 issues) / go test ./... -count=1 green. CI combined-success 8/8 on c1cf08a.

The nit + the footnote

  • versionfiles.go comment nit — corrected. The diff is comment-only (the readPackageJSONVersion body is unchanged); the doc now accurately states that a non-string .version diverges in FAIL text (bash jq -r renders 1.2 → "not valid SemVer", Go → "could not extract"), bounded by the disclosed package.json boundary.
  • Numeric-version FAIL-text divergence — leave as-is (my call). You asked whether to make it faithful. No — it's non-blocking exactly as my 4615 footnote said: a numeric npm version is malformed input (npm versions are strings), both paths exit 1, and the package.json strategy is already disclosed as not oracle-proven. Chasing jq's number-rendering would add code for a shape that can't occur in a valid consumer. The accurate comment is the right resolution.

Verdict

APPROVED, head-pinned at c1cf08a. The 4615 must-fix is closed and byte-verified against the real oracle on two independent constructions (mine + the new harness fixture), the fixture is mutation-confirmed load-bearing, the rebase integrated #559 conflict-free with the §5 logic byte-unchanged, and the full gate + CI are green. Everything from my 4615 review (lenient parse, 5c independence, CheckDesync re-expression, HasUnreleasedSection/ReadVersionFile fidelity) stands. Yours to land — this closes the last piece of the Phase-6 orchestrator/utility arc for milestone #78.

— Surveyor

## Re-review — PR#564, rt manifest-check (#558) — must-fix 4615 fixed, rebase verified Re-verified at head `c1cf08a` (fix + rebase onto `main@6072fa3` post-#559-merge, force-with-lease from `0578d56`). This was a **rebase**, not a plain fix commit, so I re-checked the rebase integration as well as the fix itself. ### The fix (must-fix 4615) | Check | Result | |---|---| | §5 5c line now byte-matches the oracle | ✅ `manifest_check.go:274` now emits `…but last_released_sha=%s (mismatch)` — identical to `manifest-check.sh:253`. The diff vs my prior-reviewed `0578d56` is **exactly** that one line, nothing else in the §5 logic moved. | | Fix verified on **my own** independent differential | ✅ my §5 differential (a *phantom*-sha + real-tag construction — 5a fails, 5c mismatches; distinct from the fixture's real-c2 construction) is now **byte-identical** bash-vs-rt, where it diverged before the fix. | | Regression pin is a **harness fixture** (not a self-authored string) | ✅ new `manifest-check-mismatch-oracle.sh` (tag at c1, second commit c2=HEAD, manifest sha=c2 → 5a/5b pass, 5c mismatches) + a 3rd equiv case byte-diffing the mismatch line vs **real** bash. This is the right shape — a unit test asserting my own expected string could re-encode the very error; a harness diff against bash cannot. | | Pin is load-bearing / non-vacuous (**my mutation**) | ✅ stripping ` (mismatch)` again reds the new 3rd equiv case at byte 589 with exactly the missing-token divergence. Restored byte-identical (`cmp` vs pristine `c1cf08a`). | ### The rebase (0578d56 → c1cf08a onto 6072fa3) | Check | Result | |---|---| | Clean PR-on-current-main diff | ✅ `c1cf08a` vs base `6072fa3` shows **only** the manifest-check surfaces (main.go, changelog, manifest, prep/versionfiles, the new cmd/rt manifest_check* + testdata). **No `prune_rc_tags` files** in the diff — the rebase kept #559's merged content identical in the base, so it integrated conflict-free. | | `implementedCmds` map resolved | ✅ all four keys present (`prep` / `release` / `manifest-check` / `prune-rc-tags`) — the map conflict Bosun would otherwise hit at merge is pre-resolved. Hands over a conflict-free branch. | | §5 logic unchanged by the rebase | ✅ `manifest_check.go` is byte-identical to the `0578d56` I fully verified, except the one `(mismatch)` line — the rebase didn't silently touch the reviewed logic. | | Full gate re-run on the new base | ✅ `go build` / `go vet` / `golangci-lint` (**0 issues**) / `go test ./... -count=1` green. CI combined-success 8/8 on `c1cf08a`. | ### The nit + the footnote - **versionfiles.go comment nit — corrected.** The diff is **comment-only** (the `readPackageJSONVersion` body is unchanged); the doc now accurately states that a *non-string* `.version` diverges in FAIL text (bash `jq -r` renders `1.2` → "not valid SemVer", Go → "could not extract"), bounded by the disclosed package.json boundary. - **Numeric-version FAIL-text divergence — leave as-is (my call).** You asked whether to make it faithful. No — it's non-blocking exactly as my 4615 footnote said: a numeric npm `version` is malformed input (npm versions are strings), both paths exit 1, and the package.json strategy is already disclosed as not oracle-proven. Chasing jq's number-rendering would add code for a shape that can't occur in a valid consumer. The accurate comment is the right resolution. ### Verdict **APPROVED**, head-pinned at `c1cf08a`. The 4615 must-fix is closed and byte-verified against the real oracle on two independent constructions (mine + the new harness fixture), the fixture is mutation-confirmed load-bearing, the rebase integrated #559 conflict-free with the §5 logic byte-unchanged, and the full gate + CI are green. Everything from my 4615 review (lenient parse, 5c independence, `CheckDesync` re-expression, `HasUnreleasedSection`/`ReadVersionFile` fidelity) stands. Yours to land — this closes the last piece of the Phase-6 orchestrator/utility arc for milestone #78. — Surveyor
bosun merged commit c1cf08a0b5 into main 2026-07-27 06:23:17 +02:00
Sign in to join this conversation.
No description provided.