HOMELAB-2682: fix(forgejo): refuse expected_identity declaring the operator (scoped) #115
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "plane/HOMELAB-2682-operator-refuse"
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 changed
forgejo_comment_on_prnow refuses a declaration that names the operator.The rule it enforces (El Director's standing order, quoted in the commit and the
code comment): "NEVER declare expected_identity: aaron — in this session,
every worker, every seat brief, no exception for agents; no writes as Aaron."
assertWriteIdentityalready refused a write when nothing was declared and thetoken turned out to be the operator's (the branch that landed earlier today). It
still accepted an explicit declaration of
aaron— so the one spelling thatsays out loud "this is the operator's write" was the one spelling that switched
the guard off. That is now inverted: the refusal happens at resolution, on the
resolved expectation (parameter or
FORGEJO_EXPECTED_IDENTITY), beforeGET /user, so the call makes zero requests and zero writes.The refusal message states plainly that declaring the operator's login is
refused and that a different identity must be declared (or none at all, which
keeps the existing no-declaration semantics).
What refused before and posts now — the only case
One case, exactly:
forgejo_comment_on_prwithexpected_identity: "aaron", or withFORGEJO_EXPECTED_IDENTITY=aaronin force, posted the comment asaaron.Two tests pinned that as intended behaviour; they were the ones that changed.
Aaron,AARON," aaron "andAaRoNrefuseidentically (the comparison is
isOperatorIdentity, already case- andspace-insensitive).
Nothing else moved:
token still refuses, and an undeclared write on a non-operator token still posts;
forgejo_comment_asis untouched — it already refuses the operator by namebefore
assertWriteIdentityis reached;path.
Red, then green
Tests first. Red was measured with the source untouched:
5 failed | 1168 passed (1173); the two touched files5 failed | 33 passed (38). Every red failure was the measured defect itself:3 requests were made (
GET /user,POST, read-back) — the comment posted.The assertions are at the request layer (
h.callslength 0, noGET, nomutating verb, nothing recorded as posted), not on the message text alone.
45 files / 1173 tests passed;tsc --noEmitclean(exit 0).
The five new/changed tests: param-declared operator refuses with zero requests;
env-declared operator refuses with zero requests; a blank parameter does not
switch the env refusal off; every case/spacing variant refuses; a non-operator
declaration still posts. Plus the two historical pins, rewritten to the new
intended behaviour (one in
comment-identity-hardening.test.ts, which keeps itsoriginal purpose — explicit arg beats env — on a non-operator login).
Files
forgejo/api.ts— the refusal, one branch inassertWriteIdentityat resolution.forgejo/tests/comment-operator-refuse-2682.test.ts— new section (e); the twoaaron-posts pins replaced; the file header states the new rule.
forgejo/tests/comment-identity-hardening.test.ts— the explicit-wins-over-envpin moved to a non-operator login, plus a red test that declaring the operator
is refused before any request.
forgejo/index.ts— the tool description and theexpected_identityparameterdescription say the operator's login may not be declared.
docs/forgejo.md— one sentence in the comment-write steps.Untouched
The five historical comments written as
aaron(20294, 20295, 20368, 21032,20685) are untouched: this change adds no request, and nothing here edits, deletes
or resolves a comment. No verdict is asserted or implied here — review is the two
seats' job.
Measured on
The suite's fake Forgejo API (no network). No live write was made while building
this branch.
Verdict of record — ld-reviewer — head
7435cbe330: APPROVEmodel: omniroute/deepseek-flash
session: 01a1b368-9704-70d7-89c1-cbeff3e60fcd
Blockers
None.
Change surface — is anything changed beyond the operator-declaration leg?
No.
git diff 96437d4a..7435cbe3is 5 files, +158/−27, and every hunk belongs to theone leg:
forgejo/api.ts— oneif (expected && isOperatorIdentity(expected)) { return {ok:false,…} }branch inside
assertWriteIdentity, plus two prose lines on its doc comment and thebranch's own comment. No other function in the file is touched.
forgejo/index.ts— two DESCRIPTION strings only (forgejo_comment_on_prdescriptionand its
expected_identityparameter description). No code.docs/forgejo.md— one added sentence in the comment-write steps.aaron-posts pinsreplaced.
forgejo/comment-as.ts,verdict.ts,landing.tsare byte-identical base↔head(sha256
43805c03…,84546148…,1f7c6a03…), soforgejo_comment_as, the verdicthelper and the landing lock are unchanged by construction, not by assertion. The no-declaration
branch, the mismatch branch, the unreadable-identity branch and the reviewer-verdict branch
are all untouched and re-pinned by tests that still pass.
Refusal ordering (measured in the head source, not inferred): the operator-declaration
returnis atforgejo/api.ts:437; the first request on the whole path,await getAuthenticatedUser(cfg), is atforgejo/api.ts:453. Nothing can be sentbefore the refusal.
Differential — deployed (= base
96437d4a) → head7435cbe3Eighteen cases run against BOTH trees with the same fake-API harness
(
tests/fake.tsmockFetch, which records everyfetchbefore routing, so the requestcount is requests actually attempted). One JSON row per case, produced twice and diffed.
96437d4a7435cbe3expected_identity: aaron, token = operatorexpected_identity: aaron, token = workerexpected_identity: Aaron, token = operatorexpected_identity: " aaron ", token = operatorFORGEJO_EXPECTED_IDENTITY=aaron, no paramFORGEJO_EXPECTED_IDENTITY=aaron, blank paramFORGEJO_EXPECTED_IDENTITY=ld-reviewer, no paramexpected_identity: aaron+ verdict body, token = operatorGET /userunreadableCounts: 12 same · 6 PERMITS→REFUSES · 0 REFUSES→PERMITS. Every permitted→refused row
declares the operator (parameter or environment, incl. case/space variants, a blank
parameter falling through to an operator env, and a verdict body); no other row changes
verdict, and the two rows whose reason changed (C08, C18) still refuse.
comment_asis not in the matrix because its file is byte-identical: its own suite is 28/28 in both
trees, and its step-2 guard (
comment-as.ts:564,isOperatorIdentity(declared)) refusesan operator
identitybefore theassertWriteIdentitycall atcomment-as.ts:644isreached — the new branch is unreachable there, so its behaviour cannot have moved.
My own suite results
Run by me in
/tmp/rv2682/{base,head}, clones ofHomelab/pi-extensionsat exactly96437d4aand7435cbe3,node_modulessymlinked from the mgmt checkout, tests offline:2 files passed · 38 tests passed.45 files passed · 1173 tests passed;tsc --noEmitexit 0.45 files · 1169 tests passed.source, source untouched →
2 files failed · 5 failed | 33 passed (38), and all fivefailures are the section-(e) operator-declaration cases.
comment-identity-as.test.ts: 28/28 at base and 28/28 at head.Exposure — what I personally read and ran
Read in full: the base..head diff of all five files; the whole
assertWriteIdentityathead (
api.ts:290-520); both call sites (index.ts:1156,comment-as.ts:644) and thecomment-as.ts:564operator guard;tests/fake.tsmockFetch; the diff of both testfiles. Ran: the suites and the 18-case differential listed above, all offline.
Deployed-drop provenance is proven by hash, not assumed:
~/.pi/agent/extensions/forgejois a source drop with no
node_modules; itsapi.ts,index.ts,comment-as.ts,verdict.ts,landing.tssha256 values equal those files at base96437d4aand are NOTequal to head (
api.ts=118b7504…at deployed AND at base,50c67355…at head). Sothe table above is measured deployed-behaviour → head-behaviour.
I made no write with
FORGEJO_API_KEY, noexpected_identitydeclaring the operatoranywhere, and exactly one write in this review: this verdict comment.
Non-blocking observations (not blockers, post-merge tidy)
forgejo/api.ts:297-299— prose left in the file this PR edits still says "Where theidentity is DECLARED the operator is a legitimate login". On the comment-write path the
new branch makes that false, and the new branch's own comment ~140 lines below says the
opposite. Doc drift inside the touched file.
forgejo/tests/comment-operator-refuse-2682.test.ts:25-27— the file header stillsays
"Zero requests" here means zero WRITES. One authenticated GET /user is unavoidable, while section (e) in the same file - and the docs sentence this PR adds -now prove zero requests. Stale claim in a file this PR changes.
Verdict of record — ld-reviewer — head
7435cbe330: APPROVEmodel: omniroute/deepseek-flash
session: 01a1b36e-4bdb-70d7-89c1-cbf5738dc9d8
Blockers
None.
Scope measured (not read from the description)
git diff 96437d4a 7435cbe3 --stat= 5 files, 158 insertions, 27 deletions, and theserved
.diffapplies cleanly to a96437d4aexport producing a tree byte-identical tothe
7435cbe3export:The only functional change is one branch at the top of
assertWriteIdentity(api.ts:437) plus its comments.
resolveExpectedIdentity(363-371),expectedIdentitySource(374-380),isOperatorIdentity(304-307),sameLogin, theno-declaration operator branch (486), the verdict-without-declaration branch (465) and
the mismatch branch (505) are all unchanged;
comment-as.ts,landing.ts,verdict.ts,verdict-cli.ts,types.tsandpackage.jsonhash identically at baseand head.
assertWriteIdentityhas exactly two callers in the tree (index.ts:1156,comment-as.ts:644) and both are measured below.
Differential table — base
96437d4avs head7435cbe3Same driver file, both trees, real registered tools, request-layer fake
fetch(
h.callscounts HTTP calls; request counts are per call).96437d4a7435cbe3Counts: 13 cases · 8 unchanged · 5 changed · permits→refuses 5 · refuses→permits 0.
Every changed row is a call that DECLARES the operator's login; no declaration, no
non-operator declaration, no mismatch refusal and no
comment_aspath moved.Ordering of the refusal
In
assertWriteIdentitythe new branch returns at api.ts:437 before the first networkcall in the function,
getAuthenticatedUser(api.ts:456 →GET /user). The commenttool's only work before that call is
parseRepoRef(index.ts:232, pure) andloadConfig(api.ts:109, pure env read) — measured: every refusal row above made0 requests, not merely 0 writes.
The zero-request assertions are at the request layer, not a mocked refusal: the suite
stubs global
fetchwithmockFetch(tests/fake.ts:76) and assertsh.calls,callsOf(h.calls,"GET"),writes(h.calls)andh.postedare empty. Nothing stubsassertWriteIdentityor the refusal message.Suite result (my run, head
7435cbe3)tests/comment-operator-refuse-2682.test.ts+comment-identity-hardening.test.ts→ 2 files, 38 tests passed.96437d4asource → 5 failed | 33 passed (38), failures showingexpected 0, received 3requests — the defect itself, not a message mismatch.tsc --noEmit: exit 0.Exposure
Read: full served
.diffandgit diff 96437d4a..7435cbe3;api.tsaroundisOperatorIdentity/resolveExpectedIdentity/assertWriteIdentity; the comment andcomment_astool handlers; both touched test files andtests/fake.ts. Ran: the twotouched files and the whole package at head; the head tests against base source; the
13-case matrix on both trees;
tsc --noEmit. Deployed-drop provenance: the mgmt drop at~/.pi/agent/extensions/forgejois sha256-identical to the96437d4aexport for all 8code files (api.ts, comment-as.ts, index.ts, landing.ts, types.ts, verdict.ts,
verdict-cli.ts, package.json), so the differential baseline IS the deployed lock. No
live write was made; the matrix ran only against the fake API.