HOMELAB-2566: fix(forgejo): submit formal exact-head verdicts #81
No reviewers
Labels
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Homelab/pi-extensions!81
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "plane/HOMELAB-2566-formal-verdicts"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes HOMELAB-2566 (epic HOMELAB-2518).
Problem
The verdict helper composed the landing-lock grammar correctly and then wrote it
as a general comment (
POST /issues/{n}/comments). The landing lock readscomment bodies, so the verdict was counted — and branch protection, which reads
formal review state, saw nothing. PR #126 measured exactly that refusal. A
verdict that is not a formal review on the exact head is not an approval, however
well-formed its text.
Contract implemented
--postnow submits one formal review:GET /usernames the token's own login;--authoris an expectation that must equal it. Unreadable identity or adifferent login ⇒ zero writes. No proxy identities, no
--force.--head-shamust be the PR's live head (refused beforewriting otherwise), and that head is sent explicitly as
commit_id.## Verdict: <event>,model: <id>,head: <40-hex>— plustier: premium|worker|cheapon line 4when the body supplies a standalone tier line (or
--tier); a contradictingtier is refused. The resolution sentence and prose still sit after the blank
line, so the statement block stays exactly the grammar.
state, the token's own login as author,commit_idequal to the exact head(branch protection would otherwise read a review of another commit), and the
contiguous prefix (
tier:included) as the row's opening bytes. Then thelock's parser re-runs on the live stream against the real row
(
asItem— same kind, same id), soCOUNTEDis about what Forgejo stored.exit 2,nothing written); another 4xx is a plain refusal (nothing written); a 5xx, an
in-flight failure or a 2xx without an id is a write that may exist and is
reported as NOT verified with an explicit do not repost.
--compat-general-comment "<reason>"is the only wayto also write a comment: it requires a written reason (>= 20 chars), it runs
only after the review is verified, and it never fires on failure.
reviewer key's current word at this head is refused, not written twice. A later
hold or a new head changes "current", so a genuine re-verdict still works.
landing.tsand the merge implementation are untouched.Red first (fresh
origin/main9747346, implementation untouched)The recorded defect: the helper's only write is
POST .../issues/{n}/comments,so
eventisundefined, nocommit_idexists, and no formal review iscreated. Already green were the three pre-existing refusals (head mismatch,
--authormismatch, unreadable identity) and a dry run.The fake is a new route table (
tests/formal-review-fake.ts) because theshared
fake.tscannot answerPOST /pulls/N/reviewsat all — a fake thatcannot model the contract cannot catch a helper that ignores it. It models a
stored review plus every read-back failure mode: list without the row, unreadable
list, wrong state, wrong
commit_id, wrong author, lost prefix, POST-without-id,4xx and 5xx.
Green
tests/verdict-formal-review.test.ts: 22 passed (22) — APPROVED,REQUEST_CHANGES, tier lift, dry run posts nothing, head mismatch, identity
mismatch, unreadable identity, duplicate refusal, six read-back failures, POST
without an id, 5xx, 4xx, and the three compatibility-comment cases.
tsc --noEmit: clean;git diff --check: cleanPre-existing harnesses were updated to the new write path — their invariants
(one POST, stored-row-only authorship, NOT verified + do not repost) are
unchanged, only the endpoint moved:
verdict-694-replay.test.ts,comment-identity.test.ts,comment-identity-hardening.test.ts.Files
forgejo/verdict-cli.ts,forgejo/verdict.ts,docs/forgejo.md,forgejo/tests/verdict-formal-review.test.ts(new),forgejo/tests/formal-review-fake.ts(new), the three harness updates above.Report-only audit (no revert, no history rewrite)
docs/forgejo.md§ Pre-amendment landings records:2026-11-03T03:18:10Zasfe7058b8aaba16c240d38f7421fac4e52b295200on head
0f0b980c…, before this contract, on plain-comment verdicts #19875(
omniroute/deepseek-flash) and #19883 (omniroute/k3.1-pro,tier: premium)with zero formal reviews. Overseer ruling: stands; no revert.
omniroute/k3.1-pro,REQUEST_CHANGES at
c127f4a3…, naming H2550-K1) was never a formal review. Itis historical: the fix round moved the head to
b2b3d70e…, and both modelsthen submitted formal APPROVED reviews there before the merge. Neither comment
is edited or deleted; under this contract the same hold would be a formal
REQUEST_CHANGES.
Collision pre-check: #80 touches
landing.ts+ model-supersede/record tests,#68 is
pi-swarm-suite/**;docs/forgejo.mdwas held by #78, merged at9747346.No overlap with this diff.
No self-review, no verdicts, no merge, no Done: the panel reviews this head
normally.
Verdict: APPROVED
model: omniroute/deepseek-flash
head:
9f343ce83eIndependent GENERAL review on the exact head (live API head == this sha, base
origin/main9747346). Read-only worktree.worktrees/HOMELAB-2566-formal-verdicts; no edits, no merge, no other PR, noagents. Findings:
working-files/homelab-2518/HOMELAB-2566-review-deepseek.md.What I verified myself
tests/verdict-formal-review.test.ts→ 22 passed; full forgejo suite → 15files / 394 passed;
tsc --noEmitclean;git diff --checkclean.9747346versions of
verdict.ts+verdict-cli.ts→ 17 failed | 5 passed. The workerlog records 17/4 of 21 (one test added after that capture) — same 17 failures.
landing.ts,api.ts,index.tsuntouched: the lock, the policy and the mergepath are unmodified, and no merge call exists anywhere in the diff. The only
writes are
POST /pulls/{n}/reviewsand the opt-in compatibility comment.The amendment's legs, attacked
eventand an explicitcommit_idequal to the verified live head; the tests assert the POST path, theevent, thecommit_idand the three contiguous opening lines, and assert no/issues/write on the same run.GET /useris readon every run;
tokenIdentity = authenticated ?? authoris only reachable afterthe unreadable-identity refusal, so a proxy identity cannot reach a write.
id,
state, author == this token,commit_id== exact head, and the contiguousprefix bytes including
tier:. Six failure knobs each produce NOT verified +do-not-repost with exactly one POST and no comment fallback; a 2xx without
an id and a 5xx are handled as may-exist; a 4xx is a plain refusal.
key's current word at this head is refused with no write, and
asItemreplaces(never appends) the real row in the live re-parse, so the same review id cannot
be double-counted.
tier:is lifted to line 4 and dropped from prose;--tiercontradicting the body, and an unknown tier, are refused; the prefixproof covers it.
stateremains non-evidence for the landing lock — the lockstill reads body text only.
2026-11-03T03:18:10Zasfe7058b8…, head0f0b980c…, 0 formal reviews,verdict comments #19875 (
omniroute/deepseek-flash) and #19883(
omniroute/k3.1-pro,tier: premium) still present.fe7058b8is still anancestor of this head, so nothing was reverted or rewritten. #19854 (PR #78's
comment-form hold) is untouched. The record matches the snapshot I re-read.
Observations, not blockers
--compat-general-commentis the one path that re-creates the duplicate shape(comment + review, same reviewer key, same head). It is reason-gated, runs only
after verification, and the ledger keys by reviewer key with newest-wins, so it
is benign — but the default must stay the review alone.
--tier premiumis self-declared with no model cross-check;TIER_DECL_REalready carried that semantics in
landing.ts, so this adds a route, not a hole.the do-not-repost text can create a second review. The helper itself never
retries.
collectStateskips bodyless reviews, so such a row fails closed as NOTverified — the safe direction.
No blocker. APPROVED on
9f343ce83e92e1d86695068ae9f8f877ade59296.Verdict: REQUEST_CHANGES
model: omniroute/k3.1-pro
head:
9f343ce83etier: premium
Independent premium review of the complete eight-file diff. Targeted 22/22, full 394/394, tsc and CI #189 14/14 are green, but two post-write verification races fail open.
Blocker — H2566-K1: head movement during submission returns successful verification
BLOCKER OPEN: H2566-K1
After the POST, the helper re-reads the PR but does not refuse when its head differs from the pinned head. It also unconditionally returns exit 0 when the stored-row parse is not COUNTED. Reproduced with a fake API: the post-write head changed, the helper printed parse IGNORED and still returned
postedplusverified: branch protection reads...with exit 0. Any non-COUNTED verification must usereviewMayExist(NOT verified / do not repost); add APPROVED and REQUEST_CHANGES head-race tests.Blocker — H2566-K2: stored-row re-parse substitutes verification time for submitted_at
BLOCKER OPEN: H2566-K2
asItempreserves only kind/id;evaluateCompositionreconstructs the row withat: nowIso, composed body and author. Reproduced with an APPROVED review stored at 04:00, a later same-key REQUEST_CHANGES at 05:00, and verification at 06:00: the older approval was re-dated to 06:00 and returned COUNTED/current with exit 0. Re-parse the actual stored row, including submitted_at, body and author, and add later-same-key race coverage.Full findings:
working-files/homelab-2518/HOMELAB-2566-review-kimi.md.9f343ce83e84649d440eRound 2 — fix for premium review 1664
Head:
84649d440ee691df302fbdccabf99be3dcc7b444(single new head; previous9f343ce, rebased ontoorigin/main e2b6f6e).H2566-K1 — a post-write head move is never a success
The head is now part of the evidence. After the POST the helper re-reads the PR and refuses when the live head is not the pinned head (
the PR head moved from <pinned> to <live> …), because the stored review'scommit_idis then a commit the PR no longer has. Beyond that specific check, the general rule is enforced: a re-parse that is notCOUNTEDis not a success. AnIGNORED/REFUSEDstored-row parse now goes through the existingreviewMayExistpath — nonzero exit,NOT verified, explicitdo not repost,postedleft empty — instead of returning 0 withverified: branch protection reads a formal … review on the exact head this run judged. The separate full-lock preview remains informational (an unrelated leg may legitimately still refuse).H2566-K2 — the re-parse reads the stored row, never the clock
Composition.asItemcarried onlykind/id, soevaluateCompositionrebuilt the row withat: nowIso, the composed body and the composed author.asItemnow carries the stored row's ownsubmitted_at, body and author (all required — a row with no usable timestamp is refused rather than dated by the clock), and the live stream's row for that id must be byte-equal to the row the read-back proved (body, author,submitted_at). An older approval can no longer supersede a later same-key hold.Red first (rebased, implementation untouched)
The 7: head moved after the APPROVED review / after the REQUEST_CHANGES review (exit 0,
postedpopulated,verified:printed over an IGNORED parse); same-key later hold at 05:00Z verified at 06:00Z (counted,current = true, exit 0 with the older approval); the fabricated-timestamp variant; and the changed-row cases (live rowbody/author/submitted_atdiffering from the read-back row while the re-parse substituted the composed fields and still exited 0). Two more new cases are green before fix and kept as regression coverage: a row missing from the live stream (existing guard) and the honest success path for both events.New fake knobs in
forgejo/tests/formal-review-fake.ts:headAfterPost,afterPostReviews,hideReviewAfterPost,mutateReviewOnLiveStream. The reviews LIST answers the immediate read-back proof on the first post-POST read and the live stream (with those knobs) on later reads — the exact pair of reads the helper makes.Green
tests/verdict-formal-review.test.ts: 33 passed (33)tsc --noEmit: clean;git diff --check: clean84649d4(in progress at comment time; per-job status on the PR)Files:
forgejo/verdict-cli.ts,forgejo/verdict.ts,docs/forgejo.md,forgejo/tests/verdict-formal-review.test.ts,forgejo/tests/formal-review-fake.ts. The formal-review contract, one-write/no-retry discipline, identity assertions, duplicate refusal and the report-only PR #74 audit are unchanged;landing.tsand the merge implementation are untouched.Evidence log kept in
working-files/homelab-2518/HOMELAB-2566-round2-red.logandHOMELAB-2566-worker.md; reproduced on HOMELAB-2566. Fresh formal panel please — no self-review, no verdict, no merge, no Done from this job.Verdict: APPROVED
model: omniroute/deepseek-flash
head:
84649d440eIndependent general review at round-2 head
84649d4— APPROVED.Both prior blockers are closed, and I reproduced each independently against a fake Forgejo
(no repo edits, my own harness).
H2566-K1 (head move reported as success): the helper now re-reads the live stream after the single
POST, refuses on any head change ("the PR head moved … it is not a verdict on the head this run
judged"), and requires parse COUNTED before it may say verified. My reproduction (pinned head read,
POST stored, post-write read returns another head) exits 1 with posted=undefined, NOT verified, do
not repost, and no success line.
H2566-K2 (substituted clock): Composition.asItem.at is now required and carries the row's own
submitted_at; evaluateComposition rebuilds the row from the STORED body/author/time and replaces
rather than appends it. My reproduction — same-key REQUEST_CHANGES stored at 05:00 while the
approval under verification is stored at 04:00 and verification runs at 06:00 — now exits 1 with
"IGNORED: a later verdict of the same reviewer key replaced this one". An older approval can no
longer outrank a later hold.
Changed and missing rows fail closed: row absent from the read-back list, row absent from the live
stream, live body/author/submitted_at diverging from the proved row, and a stored row with no usable
timestamp each exit 1 through reviewMayExist. 4xx is a plain refusal, 5xx and a 2xx-without-id are
"may exist", and only one POST is ever made.
Successful path verified: one write to /pulls/81/reviews with event and commit_id equal to the exact
pinned head, body opening with the three contiguous grammar lines from byte zero, tier lifting to
line 4 when declared, --compat-general-comment writing only after the review is verified.
Local reruns: verdict-formal-review 33/33, full forgejo suite 15 files 423/423, tsc --noEmit clean,
git diff --check clean. Round-2 red log shows 7 failed / 26 passed with the implementation reverted.
CI #192 on this exact head: 14/14 jobs successful. landing.ts untouched; the amendment contract and
the PR #74 report-only audit (no revert, no rewrite) are preserved.
Two non-blocking findings, recorded in the review document and raised for the panel rather than as
holds:
H2566-R2-1: the duplicate guard matches ledger.current without regard to itemKind, so a
byte-identical verdict whose current record is a plain COMMENT (the pre-2566 helper produced the
same bytes) is refused instead of converted into the formal review that branch protection reads —
the one upgrade this ticket exists to enable, at an unchanged head. A one-line narrowing to
itemKind === "review" would fix it; a prose change is a workaround, so this costs rather than blocks.
H2566-R2-2: --tier premium is not checked against the model's own class, so premium evidence can be
declared for a model the policy does not class as premium. Not a new class of spoofing (a prose
line always worked), but the flag makes it one keystroke.
Nothing was merged, edited or deleted; no prior verdict was touched.
Verdict: REQUEST_CHANGES
model: omniroute/k3.1-pro
head:
84649d440etier: premium
Independent premium final review of exact head
84649d4. The K1/K2 fixes and 423-test suite are green, but one post-write verification gap remains.Blocker — H2566-K3: read-back accepts reviews branch protection does not count
BLOCKER OPEN: H2566-K3
formalReviewProblemsignores the storedofficial,dismissed, andstaleflags. Forgejo preserves APPROVED/REQUEST_CHANGES state when dismissed and serializes these flags separately; branch protection requires official, non-dismissed reviews and can exclude stale approvals. My independent no-network probe changed only one flag per run:dismissed=true,official=false, andstale=trueeach returned exit 0 withpostedId=1601and the falseverified: branch protection reads a formal APPROVED reviewsuccess line. Require the validity legs at read-back and in the live-row comparison, with refusal tests.R2-1 duplicate itemKind narrowing and R2-2 premium model-class validation remain non-blocking follow-ups. Evidence:
working-files/homelab-2518/HOMELAB-2566-review-kimi-final.md.Round 3 — fix for premium review #1671 (H2566-K3)
Head:
0af8dfed899f716fc85cb834ee20a8f2e271d078(single new head; previous84649d4, based on currentorigin/maine2b6f6e).Blocker H2566-K3 resolved —
formalReviewProblemsand the post-write live-row comparison now reject a stored review whoseofficialis false,dismissedis true, orstaleis true, so the helper can no longer printverified: branch protection reads a formal APPROVED reviewfor a row branch protection does not count.K1/K2 remain resolved (post-write head move and stored-row re-parse), unchanged by this round.
H2566-K3 — the stored row must be one branch protection COUNTS
Forgejo maps a review's
statefrom its review TYPE alone and serializesofficial,dismissed,staleseparately, so a dismissed/unofficial/stale review keeps reading APPROVED or REQUEST_CHANGES; branch protection requiresofficial = true AND dismissed = falseand drops stale approvals where the branch opts in.reviewValidityProblems()is applied in the read-back proof (formalReviewProblems) AND on the raw live row.LockState.itemsdrops the flags, socollectStatenow also returns the rawreviewRowsit read; the live leg finds that id there and re-checks it, refusing when the row degraded after a proved read-back.reviewMayExistpath: nonzero exit,NOT verified, explicitdo not repost,postedleft empty, exactly one POST, no success line, no comment fallback.officialis not a refusal: Forgejo always serializes the flag (measured on this instance — a formal review readsofficial: true, a general COMMENT review readsofficial: false), so absence only appears in fixtures. What is refused is an explicitfalse.flags,flagsAtReadBackandmutateReviewOnLiveStream: official|dismissed|stalekeep the two legs independently testable.Red first (pre-fix helper, new tests kept)
The 12: each of
official=false/dismissed=true/stale=trueon APPROVED and on REQUEST_CHANGES (6), the three read-back-only cases, and the three live-row-degradation cases — all exiting 0 withpostedid 1601 and the falseverified: branch protection …line. The 13th new test is the positive control (a counted row still verifies for both events) and passes before and after.Leg independence, each leg disabled alone: read-back leg off ⇒ 3 failed (its own cases only); live-row leg off ⇒ 3 failed (its own cases only); restored ⇒ 46/46.
Green
tests/verdict-formal-review.test.ts: 46 passed (46) (43 round-2 + 13 K3)tsc --noEmit: clean;git diff --check: clean0af8dfe: 14/14 jobs successfulFiles:
forgejo/verdict-cli.ts,forgejo/tests/verdict-formal-review.test.ts,forgejo/tests/formal-review-fake.ts,docs/forgejo.md. The K1/K2 behaviour, exact id/state/head/body/author/submitted_atlegs, duplicate refusal and compatibility-comment discipline are unchanged;landing.ts, the merge implementation, PR #82/#83 files and~/.pi/agent/extensionsare untouched. R2-1 (duplicate-guarditemKindnarrowing) and R2-2 (premium tier vs model class) remain follow-ups.Evidence:
working-files/homelab-2518/HOMELAB-2566-round3-worker.mdandHOMELAB-2566-round3-red.log.This new head invalidates every prior review — fresh DeepSeek + premium formal panel please. No self-review, no verdict, no merge, no Done from this job.
Verdict: APPROVED
model: omniroute/deepseek-flash
head:
0af8dfed89Independent GENERAL review, round 3, exact head. Read-only worktree
/home/aaron/repos/pi-extensions/.worktrees/HOMELAB-2566-formal-verdicts; no edits,no merge, no deployment, no other PR, no
~/.pi/agent/extensionschange. Findings:working-files/homelab-2518/HOMELAB-2566-review-deepseek-round3.md.H2566-K3 — reproduced independently, both legs, not taken from the worker report
reviewValidityProblems()rejects
official === false,dismissed === true,stale === true; it is called fromformalReviewProblems()(the read-back of the reviews LIST) and again on the raw liverow through
collectState().reviewRows— needed becauseLockState.itemsdrops theflags. Every refusal routes through
reviewMayExist: exit 1 (nonzero),postedundefined,
NOT verified,do not repost, noverified: branch protectionline,exactly one POST, no comment fallback.
leg off ⇒ 3 failed | 43 passed (its own read-back-only cases); live-row leg off ⇒ 3 failed
| 43 passed (its own live-degradation cases); both off ⇒ 12 failed | 34 passed (46). Each
leg fails only its own cases, so neither leg is covered by the other, and the 12 reproduce
the recorded red numbers.
official=true, dismissed=false, stale=false)still verifies for both events, with the success line and exactly one POST.
The blocker was concrete on this instance
Read-only
GET /pulls/81/reviews: review #1666 reads APPROVED withofficial=false, dismissed=true, stale=trueonce #1671 was submitted — a reviewer'ssuperseded review keeps its formal state while losing exactly the legs branch protection
filters on. That is the row the pre-fix helper would have announced as "branch protection
reads a formal APPROVED review".
K1 / K2 / exact-leg regressions
the PR head moved from …), including the variantwhere the lock preview still passes; one POST, no retry.
submitted_at, body and author; the live row must equalthe proved row (body/author/
submitted_at); a row with no usable timestamp refuses; alater same-key hold makes the older row IGNORED and therefore a refusal.
commit_idequal to the pinnedhead, author equal to the token's own login, contiguous opening bytes including the
optional tier line. Six read-back failure knobs, POST-without-id, 5xx and 4xx all keep
NOT verified / do-not-repost (or a plain 4xx refusal), with exactly one POST.
Checks run on this head
Focused
verdict-formal-review46/46; full forgejo suite 15 files 436/436;tsc --noEmitexit 0;
git diff --checkclean; worktree clean. CI run #197 on0af8dfed899f716fc85cb834ee20a8f2e271d078: 14/14 jobs success. Diff is 8 files;landing.tsand the merge path are untouched.Non-blocking observations
officialis treated as counted (only an explicitfalserefuses). All four rowson this instance serialize all three flags, so today this is fixture-only — but it is the
one direction that would fail open if a Forgejo upgrade stopped serializing the field.
submitted_atbutnot
commit_id, whichLockState.itemsdoes not carry; a re-pin after the read-back istherefore not re-proven. Immutable in practice, and outside K3's scope.
itemKind === "review") and H2566-R2-2(self-declared premium tier is not cross-checked against model class) remain non-blocking
follow-ups.
No blocker open. APPROVED on
0af8dfed899f716fc85cb834ee20a8f2e271d078.Verdict: APPROVED
model: omniroute/k3.1-pro
head:
0af8dfed89tier: premium
Independent PREMIUM round-3 review of the exact head. Evidence:
working-files/homelab-2518/HOMELAB-2566-review-kimi-round3.md.H2566-K3 verified independently
reviewValidityProblems()rejects explicitofficial=false,dismissed=true, andstale=truein the immediate reviews-LIST read-back and independently on the raw live row returned bycollectState().reviewRows; the fake isolates those legs so neither can cover for the other.The focused suite passed 46/46, including every wrong flag on both formal events, three read-back-only failures, three live-row degradations after a valid read-back, and valid-row success for both events.
Every invalid-row case asserts nonzero refusal,
NOT verified,do not repost, no branch-protection success line, exactly one POST, one stored review, and no comment fallback; the valid control exits 0 withCOUNTEDand the success line.Live API evidence confirms the defect class: superseded rows retain APPROVED or REQUEST_CHANGES while reading
official=false,dismissed=true, andstale=true; current general review #1675 reads official, non-dismissed, and non-stale on the exact head.K1/K2 and checks
K1 post-write head movement, including the lock-still-passes variant, and K2 stored
submitted_at, later-same-key-hold, and live body/author/timestamp equality regressions remain green with one-POST refusal semantics.Full Forgejo suite passed 15 files and 436/436 tests;
tsc --noEmitexited 0;git diff --checkwas clean; CI #197 has 14/14 successful jobs on this head;landing.tsand merge code are outside the diff.Non-blocking observations
R2-1 duplicate-guard narrowing, R2-2 premium-tier/model cross-checking, absent-
officialfail-open hardening, and live-rowcommit_idre-proof are explicitly owned by follow-up HOMELAB-2592 and are not blockers on the current Forgejo behavior and threat model.No blocker open.