test(plugin-auth): register the authz objects two sign-in fixtures drive - #17982
Conversation
`account-issuer-upgrade-path.test.ts` and `signup-existing-address-refusal.test.ts`
boot a real ObjectQL over a real SqlDriver and register only plugin-auth's own
`authIdentityObjects`. Both then drive reads against plugin-security-owned
tables that were never provisioned:
- real sign-ins reach core's `resolveUserAuthzGrants`, whose `tryFind` reads
`sys_user_position`, `sys_user_permission_set` and `sys_position`;
- `settleSelfRegistrationGrant` reads `sys_user_permission_set` before
inserting the declared self-registration grant.
The driver refused every one of them. `tryFind` classifies a missing table as
"not provisioned" and answers `[]`, so nothing went red — the resolver leg and
the grant-write leg simply went unexercised while the suites reported green.
Declares the missing objects locally with only the columns those paths read,
following the `find-envelope-limb-removal` precedent, so no dependency edge
from plugin-auth to plugin-security is added. No product code changes; the
refused reads now succeed.
Claude-Session: https://claude.ai/code/session_01URLHobLUJB9K1ABV6ofdjj
Co-authored-by: Claude <noreply@anthropic.com>
|
| when | who | call | result |
|---|---|---|---|
| 09:12:56Z | the labeler (CI) | POST /issues/17982/labels |
500 — yet the label landed |
| earlier | the implementing dev | create_pull_request |
500, created nothing (it retried; #17982 is the retry) |
| 09:1xZ | this seat | GET /issues/17982 |
500, then succeeded on retry with backoff |
⇒ not a flake hypothesis — a measured window of API instability that this PR's diff cannot reach.
Action: one re-run of this job, which is the single re-run this failure is allowed. ⛔ No empty commit, ⛔ no close-and-reopen, ⛔ nothing skipped, disabled or quarantined. If it fails a second time, that is real and will be root-caused as this PR's.
Generated by Claude Code
|
| channel | result |
|---|---|
POST /actions/jobs/103702274452/rerun (repo-scoped REST) |
403 — that token has no actions: write |
POST /actions/runs/34749065948/rerun-failed-jobs (MCP) |
500, twice |
⇒ the second channel has the permission; GitHub's Actions API is simply still answering 500 in this window — the same window that 500'd the label POST, the dev's create_pull_request, and a plain GET /issues/17982 from this seat.
⭐ And the re-run genuinely did not land: run_attempt on run 34749065948 still reads 1, status completed / failure. ⛔ That check matters, because the label POST in this same window returned 500 while its write succeeded — so a 500 here could have meant a re-run was already queued, and stacking a second one would have been the wrong move. It was verified before retrying, and again after.
Nothing about the standing-down analysis changes: the failing step is a GitHub API 500 on a label POST, it names no code this diff touches, and the label it was writing (tests) is on this PR. The job is red for work that completed.
⇒ the one re-run stays owed and scheduled, ⛔ not spent and ⛔ not abandoned. This PR stays watched until it is green and merged, or until a second failure proves the reading wrong. ⛔ No empty commit, ⛔ no close-and-reopen, ⛔ nothing skipped or disabled.
Generated by Claude Code
Contract reviewHead reviewed: Implemented-by: ① Clause-② — re-derived in-seat from the DELIVERED diff
⇒ Clause-②: ⭐ The delivery measured the same thing one level deeper, against the BUILT tree: after a full build, ② The fences — held, and one of them proved POSITIVELY⛔ The log line was not silenced, filtered, or re-levelled. This is the fence that mattered, because "make the count reach zero" has an illegitimate solution, and the delivery ruled it out by positive proof rather than by assertion: a one-off, non-committed assertion injected into the fixture printed the rows the previously-refused read now returns — ⛔ ③ Both scopes reported, and ⛔ not interchangedFenced two files: ⭐ They coincide, and the delivery says why instead of letting the coincidence pass: the two fenced files account for 100% of the package's All seven lines are now attributed (6 to ④ The declared widening — checked against the four conditions, ⛔ not waved throughThe card names only the 3
✅ And it was declared in the PR body, ⛔ not done quietly — which is the part the exemption actually turns on. The delivery even notes it is "one revert of two ⭐ The finding is larger than the card, and the card should say so#17897 was filed about 3 lines of log noise. What the delivery establishes is that Gates53 derived commands, all exit 0. Two first answered exit 3 (PREREQUISITE NOT MET) — The open question — settled: AThe delivery asked whether the "one package only" boundary should be swept. Yes — successor filed. The static pointer is large (73 test files outside Verdict: PASS at
|
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
⛔ Not landable yet — and "0 failing" was hiding itEvery check run on
⭐ A workflow that never starts emits no failing check. So a check-runs-only view reports a clean board while two gates are simply absent — including a governance gate. Caught by diffing this head's check-name set against two landed PRs (#17941 and #17937, the second also test-only): both ran all three of Disposition:
⇒ the runs API reports it ⭐ This is a platform incident, not this PR's. Measured: every run of
⇒ the PR stays open, unlanded and watched until the guard reports or is recoverable. ⛔ No empty commit, ⛔ no close-and-reopen, ⛔ no landing around a gate that did not run. Generated by Claude Code |
Contract review — re-issued for the moved headHead reviewed: Reviewed-by: Why the head moved
The head was moved by merging the current base branch — Result: The wedge was invisible at the check-run levelAt
⇒ the completeness limb has to be read at the workflow-run level, and the check-run level cannot substitute for it in either direction.
|
| reading | result |
|---|---|
git diff --name-only <merge-base> <head> old vs new |
identical file set (2 files) |
account-issuer-upgrade-path.test.ts blob |
git rev-parse 7222252f8:<path> == b4246a6bf:<path> |
signup-existing-address-refusal.test.ts blob |
==, same method |
whole content diff 7222252f8 → b4246a6bf minus the merge |
empty |
So ①–④ and the settled open question in comment 5652418674 carry unchanged; only the head binding is re-issued.
Carrier gate, re-read after the head move
node scripts/pm/check-clause2-carriers.mjs --pair 17982 → exit 0, captured before any pipe. (Correction 160: a review re-issued for a moved head is necessary and not sufficient — the gate reads the label event stream, so it is re-read, not inferred.)
Session: line". That is a defect in this seat's claim template, not in the delivery — SKILL.md 〈模板与表〉 requires it. Every claim this seat writes from here carries the line; the already-posted claims are not edited, per the same mechanism that forbids filling a declaration in on a seat's behalf.
Verdict: PASS at b4246a6bf
CI, Lint & Type Check and Governed Surface Guard are in_progress, so the limb is NOT MEASURED, ⛔ not a pass.
Generated by Claude Code
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 34758691525 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
🔴 Ejected from the merge queue — not this PR's failure, standing down with evidenceDequeued Failing check: Why it is not this PR's — three readings, not an opinion
⛔ "Flake" is not the diagnosis either, and I did not reach for it. Two independent reproductions is the opposite of a flake, and the reason line is an Does a fix exist to port? No — measured, not assumed
Action: the one sanctioned re-queue, spent nowRe-queued once. If the cause is a semantic conflict between stacked PRs, a different stack may carry it; if it ejects again, that is a second independent reproduction and the evidence goes to #18032, ⛔ not into another attempt. ⛔ No empty commit, ⛔ no close-and-reopen. This PR stays watched until it is MERGED or closed. The contract review at Generated by Claude Code |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 34759517090 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Ejected a second time — different base, same failure. ⛔ No third re-queue.Build The decisive new fact: the re-queue landed on a different merge base (
This PR stays out of the queue until #18032 is resolved. Nothing about its diff has changed; the contract review at Generated by Claude Code |
|
| build | packages in shard 3/6 | result |
|---|---|---|
main push CI |
13 | passed |
| queue, this PR | 5 | failed |
| queue, #18014 | 1 — @objectstack/cli alone |
failed |
⇒ the package fails when it runs alone or near-alone in a shard and passes when it runs among twelve others, which makes it reproducible locally on plain origin/main with no PR involved: pnpm --filter @objectstack/cli test --shard=1/2. Full evidence and the probe are on #18032.
Nothing else changes for this PR: it stays out of the queue until #18032 is resolved, its diff is untouched, and the contract review at b4246a6bf still binds.
Generated by Claude Code
…he response `Auto Label` went red on PR #17982 for work it had COMPLETED: the `--paths` step POSTed `tests`, the API answered HTTP 500, the script exited 1 — and the PR's label set read `size/s, skip-changeset, tests` immediately afterwards. A 500 is not evidence the write failed. The job's red said "the response failed"; every reader takes it to mean "the label is missing". Those are different facts, and that gap — not a missing retry — is the defect. A bounded 5xx retry with exponential backoff has been in this file since #10777 and did not close it. Failures are now classified: 5xx and a thrown fetch are INDETERMINATE (the server may have acted before the answer was lost) and are settled by re-reading the PR's labels and judging the step's post-condition; 4xx including 429 stays DETERMINATE, fatal and loud, even when the board happens to satisfy the post-condition — a 403 is a broken token and a 422 is a label that does not exist in the repo. A settling re-read that itself fails settles nothing: the write is reported UNVERIFIED and the original error is raised. `failureIsIndeterminate`, `postconditionOf` and `settleWriteFailure` are pure and pinned by a new 16-case `--self-test` battery covering both directions. Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU Co-authored-by: Claude <noreply@anthropic.com>
Fixes #17897
Clause-②: no
Two
plugin-authtest fixtures boot a realObjectQLover a realSqlDriverand registeronly plugin-auth's own
authIdentityObjects. Both then drive reads againstplugin-security-owned tables that were never provisioned, and the driver refused every one
of them.
tryFindclassifies a missing table as "not provisioned" and answers[], sonothing went red: the suites reported a green they had not earned, plus seven
DATABASE_ERRORlines of noise per package run.This registers the missing objects locally, with only the columns those paths read,
following the
find-envelope-limb-removal.test.tsprecedent — so no dependency edge fromplugin-auth to plugin-security is added. No product code changes.
account-issuer-upgrade-path.test.tssys_user_position,sys_user_permission_set,sys_positionAuthManager.handleRequest-> session-payload callback -> core'sresolveUserAuthzGrants->resolve-authz-context.tstryFindsignup-existing-address-refusal.test.tssys_user_permission_setsettleSelfRegistrationGrant's own existence read before it inserts the declared self-registration grantThe measurement
Both runs are on the same tree; the only thing between them is this PR's diff. Baseline at
origin/main=225197cdb, after at7222252f8.The fenced two-file command:
DATABASE_ERRORtotalsys_user_permission_setThe WHOLE package —
pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2:DATABASE_ERRORtotalsys_user_permission_setThe card measured 2283 tests at
a61ae59f9; this tree carries 2287 at225197cdb. TheDATABASE_ERRORcounts are unchanged from the card's at both scopes.The other four lines: same root cause, same two files — declared, not chased quietly
The card flagged 4 of the 7 package-wide lines as unattributed and explicitly NOT MEASURED.
They are now measured, per file:
7 of 7. The two-file run and the whole-package run produce the same multiset, so these two
files account for 100% of the package's
DATABASE_ERRORlines — there is no third sitein this package.
The other four are not a different defect: they are the same fixture gap, in the same file,
on the same
resolveUserAuthzGrantsleg.sys_user_positionandsys_positionare read bythe same
Promise.all/ position block as thesys_user_permission_setread the cardnames, twice each for the suite's two sign-in cases. Registering only the card's three
occurrences would have left the same fixture half-provisioned and the same resolver leg
half-exercised, so all three objects are registered together. This is stated here rather
than done quietly: if the PM wants the extra four split out, they are one
git revertoftwo
registerObjectlines away.Why the count falls because the read SUCCEEDS
No log line is silenced, filtered or re-levelled — the diff is two test files, +99 lines,
zero product code. The positive proof is that the previously-refused read now completes and
its follow-on write lands. A one-off, non-committed assertion on case ③ of
signup-existing-address-refusal.test.ts(injected, run, restored to byte-identical HEADbytes,
git diff HEADempty):Before this PR that read was refused,
settleSelfRegistrationGrantcaught the refusal andreported "admitted but NOT granted" — so the admitted-registrant control was passing over a
grant path that never completed. It completes now.
Checks
pnpm --filter @objectstack/plugin-auth typecheck— exit 0 (tsc --noEmit, the examplesproject, and
check:test-typecheck).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack— all exit 0. Two first answered exit 3 (PREREQUISITE NOT MET —
check:dual-build-cjs-loads,check:type-check-debt); the workspace build they name was run(
turbo run build --filter='./packages/*' --filter='./packages/*/*', 72/72 tasks) and boththen exit 0.
pnpm lint(eslint . --no-inline-config, whole repo) — exit 0.grep -naPcontrol-character scan over both edited files — no matches.No changeset — measured, not assumed
@objectstack/plugin-authshipsfiles[] = ["dist","README.md","CHANGELOG.md"]. After abuild, grepping the shipped path for the symbols this diff introduces:
Nothing published moves, so this PR carries the
skip-changesetlabel rather than achangeset.
Clause-② re-determination from the delivered diff:
no. The diff adds no exportedsymbol reachable from the published entry and no new key on an already-published payload —
it adds three
constobject literals and fourregisterObjectcalls inside two*.test.tsfiles, none of which reachdist/. The measurement above is the same evidence.Acceptance notes
packages drive the same resolver against a narrower object set was never swept, for this
card or for its predecessor [finding]
find-envelope-limb-removal.test.tsdrives a read onsys_user_permission_setagainst an engine that never registered it — the last refused read left in plugin-auth after #14846 #16315. A static pointer only, not a measurement: 73 testfiles outside
plugin-authnameresolveUserAuthzGrants/resolveAuthzContext(control:the same grep finds 6 inside
plugin-auth), and 4 constructAuthManager. Sweeping themmeans running those packages' suites; that is the PM's call, not this card's.
probe triage suggested — re-run the two-file command at
519a76915— cannot be run fromthis checkout: that object does not resolve here at all (
git cat-file -t 519a76915->fatal: Not a valid object name). The control leg proves the reading vacuous rather thaninformative:
manifest.ts, a file certainly present at every ref, also reads ABSENT at519a76915, while both target test files read PRESENT ata61ae59f9and225197cdb.Nothing here implies an answer in either direction.
sys_position_permission_setandsys_permission_setare deliberately NOT registered inaccount-issuer-upgrade-path.test.ts: the resolver reaches them only once asys_positionrow resolves and a permission-set id is collected, and these cases seed neither. Recorded
in the fixture comment so the next author does not read the omission as an oversight.
valid_from/valid_until) are absent from the newobjects for the same reason
sys_memberlacks them today:isGrantActivereads an absentbound as unbounded.
back-channel logout planning failed … no such table: sys_oauth_access_token) #14615 — deciding that a read of an absent table is a benigndiscriminated case and therefore no longer worth logging — is untouched here, as it was
untouched by plugin-auth sso-register harness never registers sys_position / sys_user_position, so the platform-admin standing resolver logs 8 DATABASE_ERROR lines on every green run #14846 and by the predecessor card.
Generated by Claude Code