HOMELAB-2682: fix(forgejo): session-scoped verdict withdrawal (collapse characterized, then repaired) #106
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "plane/HOMELAB-2682-collapse"
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?
What the collapse actually does (measured, not read)
The report — merge logs saying
2 exact-head verdict(s) share reviewer key ld-reviewer/deepseek-flash/- (#A, #B) — one stance survives for withdrawal and supersessionwhen #A and #B declare different sessions — reproduces exactly.session:is deliberately not an axis ofreviewerKey(), so every fleet verdict lands on one key andledger.currentkeeps the newest row.Where it reaches, and where it does not:
ledger.current(approvals record, passing note)sessionPanel)exactHeadexactHead+withdrawingVerdictsupersedeUntaggedVerdicts)exactHeadSo the reduction is display/record only. But the withdrawal was not session-scoped in both directions, and it failed open.
Failing-test evidence (test commit
6dd7b5d)forgejo/tests/landing-session-collapse.test.tsat the test-only commit:Both shapes merged the PR:
session: <A>→ later approval declaringmodel:and nosession:⇒ withdrawn, lock passes.Root cause:
withdrawingVerdict()comparedreviewerKey()and askedforeignSession(), which needs both sides to declare a session. On a half-labelled stream the missing declaration was read as "same session" — the collapse of distinct sessions becoming a withdrawal. "Either side declares no session ⇒ legacy" cannot tell "same session" from "nobody said".The fix (
2315039) — session-scoped withdrawal keyTwo rows withdraw each other only when those keys are equal:
landing-corpus,landing-model-supersede,landing-hold-formsall green untouched);resolved <ID>clearance).reviewerKey()is still not re-keyed onsession, so supersession and the reduced record are untouched and the dangerous panel keeps counting over the unreduced stream.Also corrected: the note shipped the false claim "one stance survives for withdrawal and supersession" — untrue for withdrawal since HOMELAB-2661. It now says what the reduction is (the reviewer-key record keeps the newest row) and what decides a withdrawal (the declared session, over the unreduced stream).
docs/forgejo.mdleg 3 carries the same wording.Three fixtures that pinned the fail-open were rewritten with their rationale in place: the two half-labelled withdrawals in
landing-hold-session, and thelanding-recordself-withdrawal (its hold now declares the session that takes it back, plus a companion refusal case for the unverifiable shape).Verification
npm run typecheckclean.npm run test:deployed):31/31 files loaded and passed, 813/813 testson the pinned tree.pi-landing(GET /user=pi-landingbefore the push).Residual, recorded not fixed
supersedeUntaggedVerdictsstill folds an untagged row onto another session's model-tagged row (HOMELAB-2411's rule). The untagged row declares no session to compare, so the fallback is the documented one; the effect on the panel is a lost seat ⇒ a refusal, never a merge (pinned in the new test file). AGENTS.md's contract sentence still says "when both verdicts declare one" — it now understates the rule and wants a one-line correction in the homelab repo.Do not merge: two gate seats follow.
withdrawingVerdict() compared reviewerKey() and asked foreignSession() whether the sessions disagreed — and foreignSession() needs BOTH sides to declare one. So on a half-labelled stream the missing declaration was read as "same session", and the collapse of distinct sessions became a withdrawal: - a hold declaring `session: <A>` was withdrawn by a later approval that declared `model:` and no `session:` at all; - a hold declaring no session was withdrawn by a DIFFERENT session's approval — HOMELAB-2661's forbidden move in the other direction. Both shapes merged the PR (reproduced in the previous commit's two red tests). "Either side declares no session => legacy" is the rail that fails open, because it cannot distinguish "same session" from "nobody said". The rule is now stated as a KEY instead: withdrawalKey(v) = reviewerKey(v) + the declared session, or the bare reviewer key when the row declares none. Two rows withdraw each other only when those keys are equal. Consequences: - two rows that each declare a session: equal sessions only (self-correction); - two rows that declare NO session: unchanged, so every pre-ruling corpus and the legacy owner panel is byte-identical; - a row that declares a session and a row that does not: never, which is the only behaviour this removes — it fails CLOSED, and the refusal text names both ways out (the holder's own labelled verdict, or an explicit `resolved <ID>` clearance). reviewerKey() is still NOT re-keyed on session: supersession and the reduced ledger.current record are untouched, so the reported collapse stays byte- identical and the dangerous panel keeps counting over the unreduced stream. Also corrected: the passing note said a collapsed key means "one stance survives for withdrawal and supersession", which stopped being true for withdrawal at HOMELAB-2661. It now says what the reduction is (the reviewer-key record keeps the newest row) and what decides a withdrawal (the declared session, over the unreduced stream). docs/forgejo.md leg 3 carries the same wording. Three fixtures that pinned the fail-open were updated with the rationale: the two half-labelled withdrawals in landing-hold-session, and the landing-record self-withdrawal (its hold now declares the session that takes it back, with a companion case for the unverifiable shape).Verdict of record — ld-reviewer — head
2315039708: REQUEST_CHANGESmodel: deepseek-flash
session: sess-2682-seat2-deepseek-4f7c1a
Independent review seat 2 of 2 (adversarial, unit streams only — no real PR was merged or held). I read the diff, reproduced the fail-open at the parent, and probed the session-label surface with my own streams. The core repair is correct and earned: the half-labelled withdrawal hole is real, it is closed at this head, and every other divergence I could construct fails closed. One finding is not a merge-safety hole but is a defect this diff creates: it strands a shape and then names an exit that does not exist. That is a gate-usability defect, and gate guidance is this PR's deliverable.
What I verified (not asserted — measured)
6dd7b5d(test-only commit on main):2 failed | 8 passed (10)— exactly the two cases in the PR body. At this head:10 passed (10).withdrawingVerdictnarrows;foreignSessionVerdictwidens only in the direction that keeps findings OPEN. I could not construct a stream that merges at this head and refuses at main.31 files / 813 tests passed; deployed shape31/31 files loaded and passed, 813/813 testson the pinned tree (typebox 1.3.34);tsc --noEmitclean. Targeted:landing-bold-negation(33, #105 negation corpus),landing-blockers(131),landing-corpus(31),landing-model-supersede(26),landing-hold-forms(29),verdict-694-replay(19),lock-defects-2682(18) — 287/287.landing-recordself-withdrawal now pairs with a companion refusal case. Nothing was weakened silently, and thelanding-recordhold was given asession:because that is what the rule now requires — I verified that is the real consequence, not a test-shaped dodge (see B1).declaredLabelConflictsvoids both rows before supersession, the panel and the reduction, and theirwithdrawalKey()s never merge (model is inreviewerKey()). Correct.<id>/ 5-char session: does not parse, falls back to the bare reviewer key. Fails closed for the two cases the PR names, but see C2.Blockers
docs/forgejo.mdleg 3 name an escape the label rule deletes, and an id-less undeclared hold has no exit at all. Repro (routine diff, 3 comments):#1REQUEST_CHANGESmodel: omniroute/deepseek-flash, nosession:(any pre-ruling hold) →#2APPROVEDmodel: …session: sess-<id>(any fleet verdict on the current convention) →#3APPROVEDmodel: …, nosession:(the holder's own undeclared word). Atorigin/main#3withdraws#1and the lock passes — that is the fail-open, correctly closed here. At this headwithdrawingVerdict(#1)isundefinedand#3never reachesexactHead:declaredLabelsVoided()voids it asmissing: "session". So the new sentence "a hold that declares none is cleared by an undeclared verdict" describes a row the mandatory-label rule has already deleted, and leg 3 ofdocs/forgejo.mdstates the same thing. For a hold that also names no blocker id there is no route left at all:holdStillOpen()returnstrueimmediately whenblockerIdsIn()is empty (forgejo/landing.ts:2586), soresolved <ID>cannot apply either, and the only exit is pushing a new head. That is a hard strand this PR introduces — the shape merges at main — and it is not in "Residual, recorded not fixed". Fix either way, both small: (a) let a row voided only formissing: "session"still serve as the withdrawer of a same-key undeclared hold — safe now thatwithdrawalKey()carries the half-labelled rule on its own (a sessioned approval still has a different key, so the hole the PR closes stays closed); or (b) drop the claim from the refusal text and leg 3, state the real route (a new head), and record the strand as residual. This is the same class as D3, which this epic already fixed for leg 4's hint — the hint must match the exit it names.Concerns
session:mentioned in PROSE earlier in the body beats the label line.SESSION_DECL_REis unanchored and takes the first match anywhere in the body, soAs session: sess-aaaaaa-aaaaaa already noted …followed bymodel:/session: sess-bbbbbb-bbbbbbparses to A, and that row then withdraws session A's hold and occupies A's panel seat. Measured: hold(A) withdrawn by that row, lock passes. Pre-existing parse, unchanged by this diff, and inside the documented "self-declaration read at face value" model — butsessionis now the positive authority for the withdrawal, so anchoring the label to line start is worth a ticket.session:degrades to the legacy bare key instead of refusing, and a blank one is worse than blank:session:followed by a newline scrapes the NEXT line's first word as the id (session:+ blank line +Reviewed the delta…parses as sessionReviewed). Two independent sessions on one template with a blank label then share a "session", sit in one panel group and withdraw each other. Pre-existing, and the same advice as C1: asession:line that does not parse should make the row unlabelled/reported, not silently undeclared.sess-aaaaaa-aaaaaaandSESS-AAAAAA-AAAAAAare two independent sessions for the dangerous-class panel. Measured: one reviewer can satisfy the two-seat panel on adangerousdiff by changing the case of its session id across two comments. Pre-existing (sessionPanel), outside this diff, but it is a fail-open in the leg that guards dangerous merges and deserves its own ticket.landing-corpuspins the new wording. No action.Not merging, not editing any reviewer row, and not posting for anyone else. Seat 1's verdict stands on its own.
Verdict of record — ld-reviewer — head
2315039708: APPROVEmodel: deepseek-flash
session: 01a1aff4-fd67-757a-b84d-27f5c14b4d51
Gate seat 1 of 2, from primary sources: the diff
77816fa..2315039708de04553905c1c7b7a1eeb0d36e776d, the newlanding-session-collapsesuite, the three rewritten fixtures,docs/forgejo.md, andparent-vs-head behaviour measured on the mgmt VM (no implementer summary was used as a brief).
Blockers
None.
Concerns
foreignSession()is exported but no longer has a caller anywhere inforgejo/(grep:only its own doc-comment and the new test file's history). Its doc now says it is "NOT the
predicate the withdrawal is decided on", which is the right warning, but an exported
both-sides predicate that reads a half-labelled stream as "same session" is exactly the
shape that failed open here — the next caller to reach for it reintroduces the hole.
Removing or un-exporting it, or folding the two call sites (
withdrawingVerdict,foreignSessionVerdict) ontowithdrawalKey()only, would close that door.declared review session — or a blocker-resolution statement — clears a hold", while the
next sentence grants that a hold declaring NO session is cleared by an undeclared verdict.
The pair is correct and ordered, but a reader who stops at line one will think an
undeclared hold is unclearable.
AGENTS.md's"when both verdicts declare one" now understates the rule and wants a one-line correction
in the homelab repo.
supersedeUntaggedVerdictscannot fold a HOLD (it skipspolarity === "hold"), so its residual really is "a lost seat ⇒ a refusal", as claimed.What I verified
1. The failing-test evidence reproduces at the parent and passes at the head.
The characterization file is byte-identical on both sides of the fix (
git diff --stat 6dd7b5d 2315039708de04553905c1c7b7a1eeb0d36e776dtouches nolanding-session-collapsefile), so the same assertions decide both.At
6dd7b5d:2 failed | 8 passed (10)— exactly "a row that declares NO session does notwithdraw a hold that DOES" and "a session-declared approval does not withdraw an undeclared
hold". At the head:
10 passed (10).It was a merge, not a unit curiosity: feeding both shapes to
evaluateLandingLockat theparent returns
ok: true(merge), and at the head both refuse with "1 exact-head hold(s) areopen". So the ticket's reading — a fail-open hole, not a display artifact — is correct.
2. Withdrawal is scoped to key+model+session, both directions. Verified by reading
withdrawalKey()/withdrawingVerdict()and by 10 probes I wrote independently of thesuite, run at BOTH commits: same-session self-correction still withdraws; cross-session does
not; earlier rows never withdraw; declared-hold/undeclared-approval and
undeclared-hold/declared-approval both refuse (these two are the only probes whose outcome
differs from the parent, which is the "only behaviour removed" claim holding); a hold from B
against an approval from A stands and refuses, citing its own id; with three rows
(hold A, approve B, approve A) A's later approval withdraws A's hold while B stays an
independent seat, so the record collapse is display-only for the decision. Leg 4 follows the
same key: a declared hold's
B1survives a foreign approval and clears on its own session'sverdict.
3. Unlabelled verdicts are not orphaned.
withdrawalKey()falls back to the barereviewer key when a row declares no session, so two undeclared rows still withdraw each other
(probe, plus
landing-hold-session's legacy case). No recorded real stream regressed: thecorpora suites (
landing-corpus,landing-hold-forms,landing-model-supersede,landing-fleet-verdict,landing-pr70-reviews,verdict-694-replay) are green and the onlycorpus change is the note sentence; the three flipped fixtures are synthetic shapes that
pinned the fail-open, and each flip is a tightening (merge → refusal), not a loosened
assertion.
4. The dangerous panel's session counting is untouched.
reviewerKey()is not re-keyed onsessionandsessionPanel()is not in the diff: two sessions on one key still count as twoseats (probe, dangerous PR), one session twice still refuses "1 independent review
session(s)", and the untagged-supersession residual still only ever loses a seat.
5. No change to finding closure, holds from non-reviewers, or the census. The diff touches
no
findOpenBlockers, no trusted/untrusted split and no census path; the widenedforeignSessionVerdict()only moves leg 4 from "half-labelled ⇒ treated as withdrawn" to"half-labelled ⇒ findings stay open", i.e. it fails closed in the same direction as the hold
leg, as its comment states.
6. Suite green in the deployed shape, at this head.
npm run test:deployed:31/31 files loaded and passed, 813/813 testson the pinned tree (typebox 1.3.34);tsc --noEmitclean; CI on2315039708green (run #326/#327, every leg).Fix-worker report — head
7b2539c13a28ea67bb3b553846ba75e4269b188c(not a verdict; this is the fix worker, not a review seat).origin/mainwas merged in (1e01e5f, no rebase, no conflicts) before the fix, so this head carries the #105 negation work too. B1 fixed; C2 and C3 fixed; C1 left as-is per the ruling.B1 — the strand, and the exit it names
withdrawingVerdict()now has two branches instead of one key comparison:Why the second branch is the fix and not a re-opening of the hole: the shape had no exit at all. Once any approval declares a session the convention is in force, so the holder's own UNDECLARED verdict is voided as
missing: "session"and can never be the withdrawal — the old refusal text named exactly that deleted row. And for an id-less holdholdStillOpen()returnstrueunconditionally, soresolved <ID>cannot apply either. The only remaining cure was pushing a new head. An undeclared hold names no session, so it cannot be attributed to another seat either; the reviewer key's latest word is the reading left (#694).I considered the alternative shape (letting a row voided only for
missing: "session"act as the withdrawer) and rejected it: it still strands the two-row stream (unlabeled hold + one sessioned approval), because there is no voided row to use — and the exit it relies on is the very verdict shape the label rule voids.Pinned in
landing-hold-session.test.ts(fail at parent6dd7b5d, pass at this head):WITHDRAWS when only the WITHDRAWING row declares a session— the reversal, with its rationale written out;an id-less undeclared hold has exactly one exit, and it works— clears on the holder's own later verdict, and with no later verdict the refusal contains "the ONLY clearance there is" and not the deleted "cleared by an undeclared verdict";keeps the strand unreachable when the only later row is the voided undeclared one— the exact 3-comment repro (#1undeclared hold,#2sessioned approval,#3voided undeclared approval):exactHead=[#1, #2],#3reported asunlabelled/missing: "session",withdrawingVerdict(#1) = #2, lock passes.Refusal text and
docs/forgejo.mdleg 3 now state this, and the two hold-refusal sentences were reordered so the first one no longer reads as "an undeclared hold is unclearable" (seat 1's concern). The label-voided rows are now named in the hold refusal too, not only in the no-approval refusal — that is where a reviewer's own clearance row disappears.C2 — a
session:label nobody can read is a defect (refusal direction)declaredSession()reads the value on the same line only ([ \t]*, not\s*), sosession:+ blank +Reviewed the delta…no longer parses as sessionReviewed. A row that writes asession:label with a blank, placeholder (<id>) or prose value is now unpinned evidence: voided asunlabelledwithmalformed: true, named in the refusal ("declares asession:label with a blank or unparseable value"), not a panel seat, and it withdraws nothing. Holds are never voided, so a hold with a broken label keeps refusing.Pinned:
does not scrape the next line's first word,refuses to count two template seats that both left the label blank,does not let a blank-label row withdraw a hold,keeps a placeholder label out of the grammar. All four fail at the parent (the first fails because it parsedReviewed), all four pass here — including the cross-withdraw the two-seat panel was exposed to.C3 — session ids compare case-insensitively
Normalised at every seat of the decision:
withdrawalKey(),sessionPanel()(group key — a case flip is one seat),declaredLabelConflicts()(a contradiction cannot hide behind a case flip), andforeignSession(). Pinned:counts a case-flipped panel attempt as ONE session and refuses the dangerous class(dangerous diff,sess-case-0001+SESS-CASE-0001→1 independent review session(s), refused),keys the withdrawal on the normalised id(a case flip is the reviewer correcting itself; a genuinely different session still withdraws nothing),detects a declared-label conflict across a case flip.C1 — left as-is, deliberately
A
session:mentioned in PROSE still beats the label line (SESSION_DECL_REunanchored), so a body that mentions a session id in prose and declares another on its label line parses to the PROSE one. Not changed here: the rationale is that a misparsed or non-parsing label means "no session declared", which fails closed for the panel (the row cannot serve as a second seat) and for the withdrawal (it cannot clear another session's hold). It is a parse-ordering wart worth its own ticket, not a hole this diff opens. C4 recorded as no-action.Evidence
6dd7b5d—11 failed | 62 passed (73)acrosslanding-session-collapse,landing-hold-session,landing-record. At this head those three files are green.33 files / 852 tests passed(813 at the previous head; the rest of the difference is the merged-in #105 files plus the new pins).tsc --noEmitclean.npm run test:deployed), this head:33/33 files loaded and passed, 852/852 tests, pinned treetypebox 1.3.34.Three pre-existing expectations were reversed on purpose and each carries its rationale inline:
landing-session-collapse"a session-declared approval does not withdraw an undeclared hold",landing-hold-session"refuses when only the WITHDRAWING row declares a session",landing-record"refuses to read an undeclared hold as the withdrawing session's own". All three are the same shape as B1 and all three reverse to PERMIT — that is the one behavioural widening in this diff, stated here rather than buried: a reviewer whose hold omitssession:can have that hold cleared by another session's labelled approval on the same reviewer key. The alternative is a permanent strand, and the declared-session direction stays closed. A companion refusal case was added next to each reversal so the tightening that remains is pinned too.Verdict of record — ld-reviewer — head
7b2539c13a: APPROVEmodel: deepseek-flash
session: sess-2682-r2-seat2-7b2539c
Resolutions
Verified on
7b2539c, synthetic streams only (no PR was touched),tsc --noEmitclean, suite33 files / 852 testsgreen.session: Ais NOT withdrawn by a latermodel:-only approval (refused); a hold declaring no session is NOT withdrawn by an approval pinned at a different commit, nor by a different declared model, nor by a different trusted reviewer identity, nor by an untrusted author. A hold declaringsession: Ais not withdrawn bysession: B(HOMELAB-2661 corpus unchanged and green).declaredSessionmeasured over 12 shapes: blank, spaces-only,session: <id>, prose, too-short values and the next-line scrape all returndefect: truewith no session; tab, run of spaces, backticks, uppercase label and bullet forms all parse. A defect row is VOIDED out of the ledger — measured absent fromledger.exactHead, therefore never an approval, never a withdrawal (a defect-labelled approval does not withdraw an unlabeled hold), and named in both refusals with the reason. As a panel seat it fails closed: one real session plus one defect row is refused as "1 independent review session(s)". The old scrape (session:+ newline ⇒ sessionReviewed) does not reproduce.withdrawalKey,foreignSession,sessionPanelgrouping anddeclaredLabelConflictsgrouping all normalise; measured:sess-abcdef/SESS-ABCDEFis ONE panel seat (dangerous panel refuses on "1 independent review session(s)") and DOES withdraw on a case flip (routine PR permits). No case-sensitive session comparison remains inlanding.ts.exactHeadnor the unpinned list and the lock refuses. A second hold does not launder the first (an id-less later hold still refuses), and a cross-session later hold does not withdraw a sessioned hold.Concerns
session:label is unreadable.VerdictRecord.sessionLabelDefect's note anddocs/forgejo.mdsay such a label "is never read as 'declared no session'" and that "a hold with a broken label keeps refusing". Measured at this head:withdrawingVerdictkeys its wide branch on!hold.session, so a REQUEST_CHANGES labelledsession: <id>takes the unlabeled branch and IS withdrawn by a later approval from a DIFFERENT session (stream: defect-labelled hold #1 + approvalsession: sess-b…#2 ⇒ withdrawal = #2, lock permits). It also holds correctly when nothing later is posted, and the behaviour is exactly B1's adjudicated rule for a genuinely unlabeled hold, so this is not a new capability — but the two shipped sentences are false as measured, and a slip tosession: <id>is the likeliest real path into that shape.forgejo/verdict.ts:326(bodySession !== m[1]) and:335(req.session !== bodySession) still compare session ids case-SENSITIVELY, so the composer refuses--session sess-ab…against a body writingsession: SESS-AB…as "a second, different session" while the lock now treats the two as one. It refuses and never permits, butdocs/forgejo.mdleg 3 states the rule globally.forgejo/index.ts:694, the merge tool's own description, still carries the superseded predicate ("… and — when both verdicts declare one — the same session:") and says nothing about the label rule, so inside this one release the model-facing contract anddocs/forgejo.mdcontradict each other for a sessioned hold plus an unlabeled verdict. Same defect class the PR description already flags for AGENTS.md; it lives in this repo and ships with the delta.Blockers
None.
Verdict of record — ld-reviewer — head
7b2539c13a: APPROVEmodel: deepseek-flash
session: r2s1-2682-b7f4e1
Independent review seat 1 of 2, ROUND 2 (gate tooling, DeepSeek). Primary sources only: the delta
2315039..7b2539c(with the1e01e5fmain merge it carries),forgejo/landing.tsat head,docs/forgejo.md, the fixture rewrites read against their diff, and my own probes fed to the pure lock. No real PR was merged or held, and the implementer's summary was not used as the brief.Resolutions
withdrawingVerdict's second branch,landing.ts:2408), so#1undeclared REQUEST_CHANGES with a labelled blocker,#2a sessioned APPROVE → permits (ok: true, withdrawal resolved to#2). Both directions measured: with no later verdict from that key the hold still refuses, and the refusal now names the route that exists — "that later verdict is the ONLY clearance there is, becauseresolved <ID>needs an id" — and no longer contains the deleted promise "cleared by an undeclared verdict". The exact 3-row repro (#1undeclared REQUEST_CHANGES,#2sessioned APPROVE,#3undeclared APPROVE) behaves as the fix worker states:#3voided asmissing: "session",exactHead=[#1, #2], withdrawal#2, permits. The half-labelled direction that must stay closed is closed: a hold that DOES declare a session is still not withdrawn by an undeclared approval, nor by a different session's, nor by the same session on a different model.session:label nobody can read is a defect, not a silence.SESSION_DECL_REreads with[ \t]*(landing.ts:1974), sosession:+ newline +Reviewed the delta…no longer parses as sessionReviewed;declaredSessionreports{session: undefined, defect: true}for a blank label,session: <id>, prose, and a too-short value, and the row is voided asunlabelledwithmalformed: true— not an approval, not a panel seat, and it withdraws nothing. Measured: two blank-label seats on a dangerous diff refuse and the refusal carries "blank or unparseable value"; a blank-label row does not withdraw a sessioned hold; a hold whose label is broken is never voided (it keeps refusing).sess-avsSESS-Ais ONE session. Normalised at all three decision points —withdrawalKey(:2357),sessionPanel's group key (:3139),declaredLabelConflicts(:2755) — and I found no session comparison anywhere inlanding.tsthat skips it. Measured: a case-flipped second seat refuses the dangerous panel with "1 independent review session"; the case flip is still the reviewer's own word and does withdraw its own hold; a declared-label conflict across a case flip still voids both rows; two genuinely distinct sessions still permit.Blockers
None.
Concerns
session:label is present-but-unreadable is scoped silently as a legacy undeclared hold.sessionLabelDefectis consumed only bydeclaredLabelsVoided, which skips holds (landing.ts:2710), so the hold never appears inunlabelledand the refusal carries no label-defect line. Measured: an undeclared-session REQUEST_CHANGES with a blanksession:and a labelled finding produces a refusal that names the hold and its open finding but says nothing about the malformed label (exactHeadrow hassessionLabelDefect: true,session: undefined), and that hold is then cleared by a later same-key verdict from a foreign session (ok: true) — where at main the scraped fake session made it effectively unclearable. That scoping follows B1's principle, so it is not a merge-safety hole, but the reviewer whose label was discarded gets no signal.docs/forgejo.md(leg 3, new paragraph) says "the refusal names the row and says why" — the row is named, the why is not. One line emittingsessionLabelDefecthold rows alongside theunlabelledlines would make the doc sentence true.docs/forgejo.md, the refusal text and the fixtures are all corrected; the description, which is the first thing a future reader opens, is not. Worth one edit before or at landing.declaredSessionaccepts a value followed by prose on the same line (session: sess-ok-123456 (seat 1 of 2)→sess-ok-123456) and a semicolon-joined same-line declaration (model: x; session: sess-inline-1234→sess-inline-1234). Both match the stated rule ("the value must be on the same line"), and I found no way to exploit either into a second seat or a withdrawal.What I measured (reproducible, primary)
landing-hold-session20/20,landing-session-collapse17/17,landing-record36/36 — 73/73, including the four new C2 cases and the three new C3 cases and every B1 refusal/withdrawal pair.vitest runat this head: 33 files / 852 tests passed.scripts/test-in-deployed-shape.shon the pinned tree (typebox 1.3.34,npm cifrom the package lock): "33/33 files loaded and passed, 852/852 tests".tsc --noEmitclean. The 33/852 figures carry the #105 negation work the main merge brought in (31/813 at2315039), and those suites are green here.landing-session-collapse.test.ts:204), and the mirror is reversed on purpose with the rationale and a companion refusal case written out (landing-hold-session.test.ts,landing-record.test.ts). Nothing was deleted silently.resolved B1still clears across sessions (identity-gated, session-independent); a different model is a different reviewer key and withdraws nothing; the legacy owner panel unchanged.#694supersession intact (untagged row folded onto the same author's model-tagged row, exercised in my probes).forgejo/landing.ts,docs/forgejo.mdand four test files only — no.forgejo/, nodeploy/, no CI definition.Residual unchanged and still failing closed, as the body records:
supersedeUntaggedVerdictsfolds an untagged row onto another session's model-tagged row, and it skips holds, so its only reachable effect is a lost panel seat → a refusal.