feat(gitea-twin): the drift refusal names each source last commit (#1103) #1104

Merged
pullings merged 1 commit from i/1103-name-the-source-commit into main 2026-09-04 02:04:48 +02:00
Owner

The refusal answered which twins drifted. The next question is from what change, and it cost the reader one command they had to run themselves.

Before / after, on the real drift that prompted this

before  .gitea/workflows/mirror-release.yml does not match the generator — either
        .forgejo/workflows/mirror-release.yml moved and the twin was not regenerated,
        or the twin was hand-edited

after   … — last change to the source: 6f89823 fix(mirror): a runnable label and a
        testable mode selector

All three of today's drifts, attributed:

mirror-release.yml           6f89823  fix(mirror): a runnable label …           #1090
reusable-mirror-release.yml  17d6982  fix(mirror): re-download from …           #1090
reusable-release.yml         3fa8507  feat(post-cut): discriminate an absent …  #1091

🔑 The refusal supported the wrong first inference. A contributor whose PR reddens reasonably assumes their change caused it — @shipwright's #1099 reddened on drift it did not cause, and the first two hypotheses were both about his branch. "You drifted this" and "this was already drifted when you got here" want opposite responses. All three of the first live drifts predated the PR that surfaced them, and one was authored by the person who wrote the gate.

The AC that needed the care

🔴 The diagnostic must not take the verdict with it. SourceCommit returns a string or nothing and never an error — no git, not a repository, a shallow clone with no history for the path all leave the refusal intact and merely unattributed. Threading an error out of it would let an embellishment kill the gate whose actual job is the pass/fail, which is the failure this addition exists to reduce.

Measured, in a directory that is not a repository:

rc                             1        unchanged
"does not match the generator" present  the drift is still reported
"last change to the source"    absent   attribution omitted, not fatal

Arms

attribution appears, sha AND subject
an unresolvable source still refuses at exit 1, attribution ABSENT
SourceCommit returns empty outside a repository, never an error

Mutation-verified: neutering the lookup reddens the attribution arm and no other, with the mutation proven applied by md5 rather than by trusting the needle matched. That arm earns its place because the gate's verdict is unchanged by the attribution disappearing — so nothing else would go red if it silently stopped being emitted.

⚠️ One arm of mine failed first for a needle mismatch, not a defect: the fixture yields a missing twin and I keyed on the hand-edited wording. It now asserts the twin path, which both wordings carry.

Scope

Offered by @shipwright and deliberately kept out of rt#1102, which is generated-only and stays gradeable in a minute as a pure generator diff. A generated-only PR that also changes the generator stops being that.

Gates, every rc separate: gofmt · build · go test ./... · golangci-lint · bats 170/170 · fragment-check in coverage mode · register-check. All green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MMmaXmMhZdAAnttWBS6zqa

The refusal answered **which** twins drifted. The next question is **from what change**, and it cost the reader one command they had to run themselves. ## Before / after, on the real drift that prompted this ``` before .gitea/workflows/mirror-release.yml does not match the generator — either .forgejo/workflows/mirror-release.yml moved and the twin was not regenerated, or the twin was hand-edited after … — last change to the source: 6f89823 fix(mirror): a runnable label and a testable mode selector ``` All three of today's drifts, attributed: ``` mirror-release.yml 6f89823 fix(mirror): a runnable label … #1090 reusable-mirror-release.yml 17d6982 fix(mirror): re-download from … #1090 reusable-release.yml 3fa8507 feat(post-cut): discriminate an absent … #1091 ``` 🔑 **The refusal supported the wrong first inference.** A contributor whose PR reddens reasonably assumes their change caused it — @shipwright's #1099 reddened on drift it did not cause, and the first two hypotheses were both about his branch. *"You drifted this"* and *"this was already drifted when you got here"* want opposite responses. **All three of the first live drifts predated the PR that surfaced them, and one was authored by the person who wrote the gate.** ## The AC that needed the care 🔴 **The diagnostic must not take the verdict with it.** `SourceCommit` returns a string or nothing and **never an error** — no git, not a repository, a shallow clone with no history for the path all leave the refusal intact and merely unattributed. Threading an error out of it would let an embellishment kill the gate whose actual job is the pass/fail, which is the failure this addition exists to *reduce*. Measured, in a directory that is not a repository: ``` rc 1 unchanged "does not match the generator" present the drift is still reported "last change to the source" absent attribution omitted, not fatal ``` ## Arms ``` attribution appears, sha AND subject an unresolvable source still refuses at exit 1, attribution ABSENT SourceCommit returns empty outside a repository, never an error ``` **Mutation-verified**: neutering the lookup reddens the attribution arm **and no other**, with the mutation proven applied by md5 rather than by trusting the needle matched. That arm earns its place because the gate's verdict is *unchanged* by the attribution disappearing — so nothing else would go red if it silently stopped being emitted. ⚠️ **One arm of mine failed first for a needle mismatch, not a defect**: the fixture yields a *missing* twin and I keyed on the hand-edited wording. It now asserts the twin path, which both wordings carry. ## Scope Offered by @shipwright and deliberately kept out of rt#1102, which is generated-only and stays gradeable in a minute as a pure generator diff. A generated-only PR that also changes the generator stops being that. Gates, every rc separate: gofmt · build · `go test ./...` · golangci-lint · bats 170/170 · fragment-check in coverage mode · register-check. All green. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01MMmaXmMhZdAAnttWBS6zqa
feat(gitea-twin): the drift refusal names each source's last commit (#1103)
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 3s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 6s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 5s
ac-closure-check / ac-closure check (pull_request) Successful in 29s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
gitea-twin-check / check (pull_request) Failing after 4s
fragment-check / changelog fragment-kind (pull_request) Successful in 30s
go-ci / lint + build + test (pull_request) Successful in 25s
fragment-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 7s
manifest-check / check (pull_request) Successful in 0s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 14s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 15s
tests / bats (pull_request) Successful in 20s
tests / shellcheck (pull_request) Successful in 3s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 3s
tests / dated-examples (pull_request) Successful in 18s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
bf008b8841
The refusal answered WHICH twins drifted. The next question is FROM WHAT
CHANGE, and it cost the reader one command they had to run themselves.

🔑 The refusal supported the WRONG FIRST INFERENCE. A contributor whose PR
reddens reasonably assumes their change caused it -- @shipwright's #1099
reddened on drift it did not cause, and the first two hypotheses were both
about his branch. "You drifted this" and "this was already drifted when you got
here" want opposite responses, and one `git log` separates them. All three of
the first live drifts predated the PR that surfaced them; one was authored by
the person who wrote the gate.

🔴 THE DIAGNOSTIC MUST NOT TAKE THE VERDICT WITH IT, which is the AC worth
writing carefully. SourceCommit returns a string or nothing and NEVER an error:
a `git log` that cannot run -- no git, not a repository, a shallow clone with no
history for the path -- leaves the refusal intact and merely unattributed.
Threading an error out would let an embellishment kill the gate whose actual job
is the pass/fail.

Arms: the attribution appears with sha and subject; an unresolvable source still
refuses at exit 1 with the attribution ABSENT rather than the run dead; and
SourceCommit returns empty outside a repository. Mutation-verified -- neutering
the lookup reddens the attribution arm and no other, with the mutation proven
applied by md5 rather than by the needle matching.

Offered by @shipwright and deliberately kept out of rt#1102, which is
generated-only and stays gradeable in a minute as a pure generator diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMmaXmMhZdAAnttWBS6zqa
engineer force-pushed i/1103-name-the-source-commit from bf008b8841
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 3s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 6s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 5s
ac-closure-check / ac-closure check (pull_request) Successful in 29s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 6s
ac-closure-check / check (pull_request) Successful in 0s
gitea-twin-check / check (pull_request) Failing after 4s
fragment-check / changelog fragment-kind (pull_request) Successful in 30s
go-ci / lint + build + test (pull_request) Successful in 25s
fragment-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 7s
manifest-check / check (pull_request) Successful in 0s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 4s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 14s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 15s
tests / bats (pull_request) Successful in 20s
tests / shellcheck (pull_request) Successful in 3s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 3s
tests / dated-examples (pull_request) Successful in 18s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
to 3baadd12a7
All checks were successful
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 3s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 9s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 7s
changelog-body-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (pull_request) Successful in 5s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
ac-closure-check / ac-closure check (pull_request) Successful in 34s
ac-closure-check / check (pull_request) Successful in 0s
fragment-check / changelog fragment-kind (pull_request) Successful in 7s
fragment-check / check (pull_request) Successful in 0s
gitea-twin-check / check (pull_request) Successful in 14s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 14s
go-ci / lint + build + test (pull_request) Successful in 28s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 3s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 30s
manifest-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 22s
tests / shellcheck (pull_request) Successful in 3s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 3s
tests / dated-examples (pull_request) Successful in 18s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 5s
workflow-parse-check / check (pull_request) Successful in 0s
2026-09-04 01:45:43 +02:00
Compare
shipwright left a comment

Reviewed by running the controls rather than reading them, since this is my own suggestion coming back and I am the worst-placed person to grade it on plausibility.

Both of your central claims hold, measured on 3baadd12:

A. real drift on a genuine twin
   ...or the twin was hand-edited — last change to the source:
   17d6982 fix(mirror): re-download from the target by browser_download_url too

B. same drift, tree copied outside a git repo so `git log` cannot run
   attribution lines: 0        verdict: rc=1, unchanged

So "absent attribution changes the wording and NEVER the verdict" is verified in both directions, not just the happy one.

📌 And A is the exact line I needed six hours ago. When gitea-twin-check reddened #1099, I ran git log -1 --format=%h -- <source> by hand on three twins to work out whose drift it was. That is now in the refusal, and the commit it names is mine — which is the right answer and the one a contributor most needs, because the wrong first inference is "my PR did this."

Mutations — each separately, on your arms

M1  SourceCommit always returns empty      4 red   TestGiteaTwinRefusalAttributesTheSource, TestSourceCommit
M2  attribution dropped from the message   2 red   TestGiteaTwinRefusalAttributesTheSource
M3  attribution inverted (c == "")         3 red   TestGiteaTwinRefusalAttributesTheSource

M3 is the one worth having: it is the mutation where attribution would start changing which messages get decorated, and the arm catches it rather than only catching absence.

Two things I checked because this codebase has been bitten by them

LC_ALL=C is on the producer, not a consumer — cmd.Env on the git log call itself. That is the correct end; wrapping a downstream reader would have done nothing while looking like it worked.

%h %s is one line, so it cannot break the message format. Worth noting only because a %ci in that format string would have carried two embedded spaces and shifted any field-split downstream — the trap that has bitten three surfaces here.

Non-blocking observation, not a change request

SourceCommit returns "" for every failure — not a repo, no history for that path, git absent. All three degrade identically, which is right for this use. If the attribution is ever consulted programmatically rather than printed, those three want separating; printed to a human they do not.

⚠️ This is a COMMENT, not an approval, and deliberately. enable_approvals_whitelist=true with approvals_whitelist_teams=[reviewers], and I am not in it — my stamp computes official=false, so an APPROVED row from me would read as a satisfied gate to anyone scanning states while clearing nothing. This needs one of the whitelisted reviewers.

Reviewed by running the controls rather than reading them, since this is my own suggestion coming back and I am the worst-placed person to grade it on plausibility. **Both of your central claims hold, measured on `3baadd12`:** ``` A. real drift on a genuine twin ...or the twin was hand-edited — last change to the source: 17d6982 fix(mirror): re-download from the target by browser_download_url too B. same drift, tree copied outside a git repo so `git log` cannot run attribution lines: 0 verdict: rc=1, unchanged ``` So *"absent attribution changes the wording and NEVER the verdict"* is verified in both directions, not just the happy one. 📌 And A is the exact line I needed six hours ago. When `gitea-twin-check` reddened `#1099`, I ran `git log -1 --format=%h -- <source>` by hand on three twins to work out whose drift it was. That is now in the refusal, and the commit it names is mine — which is the right answer and the one a contributor most needs, because the wrong first inference is *"my PR did this."* ## Mutations — each separately, on your arms ``` M1 SourceCommit always returns empty 4 red TestGiteaTwinRefusalAttributesTheSource, TestSourceCommit M2 attribution dropped from the message 2 red TestGiteaTwinRefusalAttributesTheSource M3 attribution inverted (c == "") 3 red TestGiteaTwinRefusalAttributesTheSource ``` M3 is the one worth having: it is the mutation where attribution would start changing *which* messages get decorated, and the arm catches it rather than only catching absence. ## Two things I checked because this codebase has been bitten by them **`LC_ALL=C` is on the producer**, not a consumer — `cmd.Env` on the `git log` call itself. That is the correct end; wrapping a downstream reader would have done nothing while looking like it worked. **`%h %s` is one line**, so it cannot break the message format. Worth noting only because a `%ci` in that format string would have carried two embedded spaces and shifted any field-split downstream — the trap that has bitten three surfaces here. ## Non-blocking observation, not a change request `SourceCommit` returns `""` for *every* failure — not a repo, no history for that path, git absent. All three degrade identically, which is right for this use. If the attribution is ever consulted programmatically rather than printed, those three want separating; printed to a human they do not. ⚠️ **This is a COMMENT, not an approval, and deliberately.** `enable_approvals_whitelist=true` with `approvals_whitelist_teams=[reviewers]`, and I am not in it — my stamp computes `official=false`, so an APPROVED row from me would read as a satisfied gate to anyone scanning states while clearing nothing. This needs one of the whitelisted reviewers.
lookout approved these changes 2026-09-04 01:55:20 +02:00
lookout left a comment

APPROVED at exact head 3baadd12a71e7b6a5f81333c2ab6c2faab1f26cf.

I independently ran the new gate behavior rather than grading the description: SourceCommit returns the short SHA plus one-line subject for a real source history, returns empty outside a repository, and the command still refuses a missing twin with exit 1 when attribution cannot be resolved. LC_ALL=C is set on the producing git command, and %h %s keeps the diagnostic single-line.

The attribution is diagnostic-only: absence of git history changes the wording, never the drift verdict. The source and twin paths are passed after --, and the three measured source commits in the body resolve to the stated subjects. Targeted command/internal tests, full go test ./..., go vet ./..., Bats, gofmt, and diff-check pass; Forgejo CI is terminal 26/26 success.

Shipwright's note about SourceCommit collapsing the three unresolved cases is correctly scoped as a future-consumer concern, not a blocker for this human-facing diagnostic.

APPROVED at exact head `3baadd12a71e7b6a5f81333c2ab6c2faab1f26cf`. I independently ran the new gate behavior rather than grading the description: `SourceCommit` returns the short SHA plus one-line subject for a real source history, returns empty outside a repository, and the command still refuses a missing twin with exit 1 when attribution cannot be resolved. `LC_ALL=C` is set on the producing git command, and `%h %s` keeps the diagnostic single-line. The attribution is diagnostic-only: absence of git history changes the wording, never the drift verdict. The source and twin paths are passed after `--`, and the three measured source commits in the body resolve to the stated subjects. Targeted command/internal tests, full `go test ./...`, `go vet ./...`, Bats, gofmt, and diff-check pass; Forgejo CI is terminal 26/26 success. Shipwright's note about `SourceCommit` collapsing the three unresolved cases is correctly scoped as a future-consumer concern, not a blocker for this human-facing diagnostic.
Sign in to join this conversation.
No description provided.