Skip to content

test(plugin-auth): register the authz objects two sign-in fixtures drive - #17982

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-17897-permission-set-refused-reads
Sep 13, 2026
Merged

os-project-manager merged 2 commits into
mainfrom
claude/issue-17897-permission-set-refused-reads

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #17897

Clause-②: no

Two plugin-auth test fixtures 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, and the driver refused every one
of them. tryFind classifies a missing table as "not provisioned" and answers [], so
nothing went red: the suites reported a green they had not earned, plus seven
DATABASE_ERROR lines 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.ts precedent — so no dependency edge from
plugin-auth to plugin-security is added. No product code changes.

file objects added path that drives the read
account-issuer-upgrade-path.test.ts sys_user_position, sys_user_permission_set, sys_position real sign-in through AuthManager.handleRequest -> session-payload callback -> core's resolveUserAuthzGrants -> resolve-authz-context.ts tryFind
signup-existing-address-refusal.test.ts sys_user_permission_set settleSelfRegistrationGrant's own existence read before it inserts the declared self-registration grant

The measurement

Both runs are on the same tree; the only thing between them is this PR's diff. Baseline at
origin/main = 225197cdb, after at 7222252f8.

The fenced two-file command:

pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=1 \
  src/account-issuer-upgrade-path.test.ts src/signup-existing-address-refusal.test.ts
tests exit DATABASE_ERROR total of which sys_user_permission_set
before 12/12 passed 0 7 3
after 12/12 passed 0 0 0

The WHOLE package — pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2:

test files tests exit DATABASE_ERROR total of which sys_user_permission_set
before 108/108 2287/2287 0 7 3
after 108/108 2287/2287 0 0 0

The card measured 2283 tests at a61ae59f9; this tree carries 2287 at 225197cdb. The
DATABASE_ERROR counts 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:

src/account-issuer-upgrade-path.test.ts     -> 6 lines: 2x sys_user_position
                                                        2x sys_user_permission_set
                                                        2x sys_position
src/signup-existing-address-refusal.test.ts -> 1 line:  1x sys_user_permission_set

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_ERROR lines — there is no third site
in this package.

The other four are not a different defect: they are the same fixture gap, in the same file,
on the same resolveUserAuthzGrants leg. sys_user_position and sys_position are read by
the same Promise.all / position block as the sys_user_permission_set read the card
names, 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 revert of
two registerObject lines 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 HEAD
bytes, git diff HEAD empty):

ONEOFF-17897 ups rows = [{"id":"ups_mtzjq2xbofgynv6d", ...,
  "user_id":"g3XNGi1sDEs8lszNnwXog11y3PIxYhoR",
  "permission_set_id":"ps_member_default","organization_id":null}]
 Test Files  1 passed (1)
      Tests  8 passed (8)

Before this PR that read was refused, settleSelfRegistrationGrant caught the refusal and
reported "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 examples
    project, and check:test-typecheck).
  • 53 derived gate commands from 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 both
    then exit 0.
  • pnpm lint (eslint . --no-inline-config, whole repo) — exit 0.
  • grep -naP control-character scan over both edited files — no matches.

No changeset — measured, not assumed

@objectstack/plugin-auth ships files[] = ["dist","README.md","CHANGELOG.md"]. After a
build, grepping the shipped path for the symbols this diff introduces:

sysUserPosition              in dist/ -> 0 files
account-issuer-upgrade-path  in dist/ -> 0 files
authIdentityObjects          in dist/ -> 2 files   (POSITIVE CONTROL: a real published symbol)
compiled test files in dist/ -> 0

Nothing published moves, so this PR carries the skip-changeset label rather than a
changeset.

Clause-② re-determination from the delivered diff: no. The diff adds no exported
symbol reachable from the published entry and no new key on an already-published payload —
it adds three const object literals and four registerObject calls inside two
*.test.ts files, none of which reach dist/. The measurement above is the same evidence.

Acceptance notes


Generated by Claude Code

`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>
@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Auto Label is red on a write that SUCCEEDED — standing down, with one re-run

Read by job id (103702274452), ⛔ not by the check's name. The only failing step is #3 Label based on changed files (additive POST), and its log says the whole thing:

pr-labels: 2 changed file(s) match: tests
pr-labels: labels on PR #17982 right now: size/s, skip-changeset
pr-labels: POST /issues/17982/labels -- add path label(s) tests
pr-labels: POST https://api.github.com/repos/objectstack-ai/objectstack/issues/17982/labels -> HTTP 500:
##[error]Process completed with exit code 1.

a GitHub API 500 on a label POST. It names no code this diff touches — the diff is two *.test.ts files, +99/-0, no product source.

And the write it reports as failed actually landed. This PR's label set now reads size/s, skip-changeset, teststests is exactly the label step #3 was POSTing when it got the 500. ⛔ This seat did not add it; the labeler did, and only its response failed. ⇒ the job's work is complete and the red is the response, not the effect.

Corroboration that the instability is GitHub's and not this PR's, measured rather than assumed — three independent 500s in the same window, on three different endpoints, from two different clients:

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

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

⚠️ Correction to the comment above — the re-run is OWED, not done

The previous comment ended "Action: one re-run of this job." This seat could not perform it, and that sentence should not stand uncorrected.

Measured, two channels:

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

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Contract review

Head reviewed: 7222252f839d4dd9fd37c6919beccf7d22f15139

Implemented-by: claude/issue-17897-permission-set-refused-reads (mode:subagent — the branch, not a session)
Reviewed-by: session_01URLHobLUJB9K1ABV6ofdjj (domain:services execution seat)

① Clause-② — re-derived in-seat from the DELIVERED diff

reading result
diff shape 2 files, both *.test.ts, +99/-0; git diff --name-only minus *.test.tsempty
^export lines added 0
src/index.ts references to either file 0
files[] ["dist","README.md","CHANGELOG.md"]src/** is not published

Clause-②: no, and below the patch floor — hence skip-changeset, not a changeset.

⭐ The delivery measured the same thing one level deeper, against the BUILT tree: after a full build, dist/ carries 0 compiled test files, sysUserPosition hits 0 files and account-issuer-upgrade-path 0, while the positive control authIdentityObjects — a genuinely published symbol — hits 2. ⛔ A zero whose control also reads zero is not evidence; this one has its control, and it agrees with the source-level trace.

② 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 returnsups rows = [{"id":"ups_mtzjq2xbofgynv6d", … "permission_set_id":"ps_member_default"…}] — with 8/8 passing, so settleSelfRegistrationGrant's insert lands. The injection was proved on disk before the run (marker occurrences = 2) and restored after with git checkout HEAD -- <path>, verified by git hash-object == the HEAD blob and an empty git diff HEAD, under a trap on EXIT/INT/TERM. ⇒ the count fell because the read succeeds.

packages/spec untouched · ⛔ content/docs/releases/** untouched · ⛔ no product code changed · ⛔ no plugin-auth → plugin-security dependency edge added (the objects are declared locally with only the columns the reading path touches, following the find-envelope-limb-removal.test.ts precedent).

③ Both scopes reported, and ⛔ not interchanged

Fenced two files: DATABASE_ERROR 7 → 0 (of which sys_user_permission_set 3 → 0), 12/12 passing both times. Whole package: 7 → 0, 108 files / 2287 tests passing both times.

⭐ They coincide, and the delivery says why instead of letting the coincidence pass: the two fenced files account for 100% of the package's DATABASE_ERROR lines — stated as a measured result, ⛔ not an assumption. That distinction is the entire reason #16315 was closed not_planned: a fenced reading was mistaken for a package one. Repeating it here would have been the same error twice on the same symptom.

All seven lines are now attributed (6 to account-issuer-upgrade-path.test.ts, 1 to signup-existing-address-refusal.test.ts), method stated: each file alone at --maxWorkers=1, counted with grep -o ... | sort | uniq -c.

④ The declared widening — checked against the four conditions, ⛔ not waved through

The card names only the 3 sys_user_permission_set lines; the diff also fixes the 4 others (sys_user_position, sys_position). Against this lane's in-place-fix exemption:

condition reading
same defect class ✅ same fixture gap, same resolveUserAuthzGrants leg, same tryFind
mechanical ✅ two further registerObject lines
unclaimed by others
same gate family

✅ 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 registerObject lines away" if this seat wanted it split. It does not: splitting would leave four known-broken reads in a file being fixed for the same cause.

⭐ 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 tryFind classifies a missing table as "not provisioned" and answers [] — so both suites were passing on reads that never happened. ⇒ the noise was the visible symptom of a latent false green. That is a different and worse fact than the one the card was graded on.

Gates

53 derived commands, all exit 0. Two first answered exit 3 (PREREQUISITE NOT MET)check:dual-build-cjs-loads and check:type-check-debt — and were not excused: the workspace build they name was actually run (turbo run build, 72/72 tasks) and both then returned exit 0. ✅ That is the correct handling of exit 3: build the closure it names and re-run to a real verdict, ⛔ never a pass and ⛔ never an excuse.

The open question — settled: A

The delivery asked whether the "one package only" boundary should be swept. Yes — successor filed. The static pointer is large (73 test files outside plugin-auth name resolveUserAuthzGrants / resolveAuthzContext; control: 6 inside), the instrument is built and cheap to re-point, and the class is a latent false green rather than mere noise. ⚠️ 73 is a STATIC pointer, ⛔ not a measurement — it names candidates, not sites, and the successor card says so.

Verdict: PASS at 7222252f8

⚠️ Binds to the head it names. ⚠️ Landing still blocked on pre-check ③: at this writing the head carries 30 distinct check names against this repo's reference of 34, with 5 in progress ⇒ NOT MEASURED, ⛔ not a pass (correction 161). Auto Label's transient failure was re-run once and is now green at run_attempt 2.


Generated by Claude Code

@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing 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
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 2f1a6f696816a393d6176a3bc2b29e5d9d733655packageMentionDocs.

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

⛔ Not landable yet — and "0 failing" was hiding it

Every check run on 7222252f8 is green or skipped (29 success / 3 skipped / 0 failure). That reading is true and insufficient, because two workflows produced no check runs at all:

workflow state the check it owes
Docs Drift Check (34749065999) startup_failure Flag docs affected by code changes
Closing-Target Claim Guard (34749065945) queued since 09:10:27Z, updated_at never moved The card this PR closes must claim this branch

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 Flag docs affected by code changes, The card this PR closes must claim this branch, and Close issues referenced in other repositories. ⛔ Not a path-filter difference.

Disposition:

  • Docs Drift Checkre-run, and it took: run_attempt 2, now in progress. ✅
  • Closing-Target Claim Guard — ⛔ unrecoverable through the API. Both verbs are refused, with mutually contradictory reasons:
POST /actions/runs/34749065945/rerun   -> 403 "This workflow is already running"
POST /actions/runs/34749065945/cancel  -> 409 "Cannot cancel a workflow run that has not been queued yet"

⇒ the runs API reports it queued; the cancel endpoint says it was never queued. It is in a limbo state on GitHub's side, created 09:10:27Z — inside the window that also produced a 500 on the label POST (with its write landed), a 500 on create_pull_request, two 500s on rerun-failed-jobs, and 503s on plain reads.

This is a platform incident, not this PR's. Measured: every run of Closing-Target Claim Guard created after 09:15Z completed successfully (09:15:21, 09:17:58, 09:21:56, 09:23:09, 09:26:14, 09:27:44, 09:35:39, 09:36:23). Exactly two are stuck, both created in that window — this one, and run 34749128715 on PR #17983, another seat's PR. ⛔ Not concurrency, ⛔ not runner scarcity, ⛔ not this diff.

⚠️ This seat will not substitute its own reading for the gate. The gate asks whether the card claims this branch; card #17897's claim comment does name claude/issue-17897-permission-set-refused-reads. ⛔ That is not a pass — verifying a gate's subject by hand while the gate itself never ran is 自查放行, and the whole point of the gate is that it is not this seat's word.

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

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Contract review — re-issued for the moved head

Head reviewed: b4246a6bf8c3c96c1ee24e37d66a595386f2272f
(supersedes the binding in comment 5652418674, which named 7222252f8)

Reviewed-by: session_01URLHobLUJB9K1ABV6ofdjj (domain:services execution seat)

Why the head moved

Closing-Target Claim Guard run 34749065945 sat in queued from 09:10:27Z with updated_at frozen at the same instant — 3 h 21 min, never starting. Both re-run endpoints refused (403 Resource not accessible by integration); the earlier attempts refused with a contradictory pair (rerun → 403 "already running", cancel → 409 "has not been queued yet").

The head was moved by merging the current base branchmain was 21 commits ahead of this PR's base (45b90b6a92f1a6f696, comparestatus: ahead, ahead_by: 21). That is ordinary staleness hygiene that happens to re-fire the workflow set; ⛔ it is not an empty commit and ⛔ not a close-and-reopen, neither of which is available to this seat as a way to kick CI.

Result: Closing-Target Claim Guard run 34757373684 on the new head — completed / success.

The wedge was invisible at the check-run level

At 7222252f8 the check-run level read 37 check runs, 30 success + 7 skipped, 0 failing, none pending. It looked finished. The guard's check run — The card this PR closes must claim this branch — was simply absent from the set, because a workflow run that never starts emits no check run at all.

level reading at 7222252f8
check runs 37 total · 30 success · 7 skipped · 0 failing
workflow runs 13 total · Closing-Target Claim Guard = queued, forever

⇒ the completeness limb has to be read at the workflow-run level, and the check-run level cannot substitute for it in either direction.

⚠️ A measurement trap, recorded so it is not re-walked

GET /actions/runs?head_sha=<SHORT_SHA> answers total_count: 0 — not an error, not a 422. It is byte-for-byte indistinguishable from "this commit has no runs", which here would have read as the guard still never ran. The filter matches only the full 40-character sha. Measured both ways on this exact head:

head_sha=b4246a6bf                                 -> total_count 0
head_sha=b4246a6bf8c3c96c1ee24e37d66a595386f2272f  -> total_count 11

The review above still binds — proved, not assumed

The base merge changed no file this PR owns:

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 7222252f8b4246a6bf 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 17982exit 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.)

⚠️ The gate also returned an advisory this seat owns: "ATTRIBUTION NOT VERIFIED: the governing claim comment carries no 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

⚠️ Binds to the head it names. Landing waits on pre-check ③ at the workflow-run level — at this writing CI, Lint & Type Check and Governed Surface Guard are in_progress, so the limb is NOT MEASURED, ⛔ not a pass.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 13, 2026 13:00
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 13, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 34758691525 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (3/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test:  FAIL   integration  test/format-zod-union.test.ts > [#5341] `os validate` delivers a union branch prescription > the specimen fails on exactly one issue, and that issue is the 
      ↳ 失败原因: @objectstack/cli:test: AssertionError: expected [ …(2) ] to have a length of 1 but got 2
    @objectstack/cli:test:  FAIL   integration  test/format-zod-union.test.ts > [#5341] `os validate` delivers a union branch prescription > leaves the `--json` payload exactly as it was — full, and neste
      ↳ 失败原因: @objectstack/cli:test: AssertionError: expected [ …(2) ] to have a length of 1 but got 2
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 2 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

🔴 Ejected from the merge queue — not this PR's failure, standing down with evidence

Dequeued 13:14:48Z, reason: CI_FAILURE. Queue build 34758691525, branch gh-readonly-queue/main/pr-17982-1e20f816e…, job Test Core (3/6).

Failing check: CITest Core (3/6)Run this shard's tests

FAIL  integration  packages/cli/test/format-zod-union.test.ts
  > [#5341] `os validate` delivers a union branch prescription
AssertionError: expected [ …(2) ] to have a length of 1 but got 2   (lines 205 and 227)

Why it is not this PR's — three readings, not an opinion

  1. This PR does not touch that package. Its diff is 2 files, both packages/plugins/plugin-auth/src/*.test.ts. packages/cli hits: 0. There is no mechanism by which adding two plugin-auth test files changes how os validate counts zod union issues.
  2. An unrelated PR hit it identically. 34756944488fix(cli): one column width for the file family in both os generate migration formats #18014, different lane, ~40 min earlier — failed in the same job, on the same two assertions, at the same lines, with the same AssertionError. The repo's own merge-queue-triage workflow filed Queue-flake anchor: test/format-zod-union.test.ts #18032 over exactly these two builds and records them as "2 independent hits once GitHub's speculative stacking is accounted for."
  3. This PR's own branch CI is green. At b4246a6bf: 34 check runs, 30 success + 4 skipped, 0 failing; workflow-run level 11/11 completed and clean.

"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 AssertionError, ⛔ not a timeout — which is the discrimination #18032 asks each victim to supply. It is supplied there, with the main-is-green control and the cheapest next probe.

Does a fix exist to port? No — measured, not assumed

search_pull_requests for an open PR touching this: 0 results. So there is nothing to port into this PR, and ⛔ this seat does not write the fix itself: packages/cli is domain:cli and this (domain:services) seat holds zero of it. ⛔ No test is skipped, quarantined or weakened here — #18032's own text says weakening a gate stays a human act, and this seat agrees.

Action: the one sanctioned re-queue, spent now

Re-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 b4246a6bf (5653296320) is unchanged — nothing about the diff moved.


Generated by Claude Code

@os-project-manager
os-project-manager added this pull request to the merge queue Sep 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 34759517090 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (3/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test:  FAIL   integration  test/format-zod-union.test.ts > [#5341] `os validate` delivers a union branch prescription > the specimen fails on exactly one issue, and that issue is the 
      ↳ 失败原因: @objectstack/cli:test: AssertionError: expected [ …(2) ] to have a length of 1 but got 2
    @objectstack/cli:test:  FAIL   integration  test/format-zod-union.test.ts > [#5341] `os validate` delivers a union branch prescription > leaves the `--json` payload exactly as it was — full, and neste
      ↳ 失败原因: @objectstack/cli:test: AssertionError: expected [ …(2) ] to have a length of 1 but got 2
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 3 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Ejected a second time — different base, same failure. ⛔ No third re-queue.

Build 34759517090, same job, same two assertions.

The decisive new fact: the re-queue landed on a different merge base (a0dd872c1 vs 1e20f816e, with #17990 and #18018 having gone green and moved ahead in between), and this PR's content was byte-identical between the two attempts — no push happened. ⇒ the "semantic conflict with the PRs it was stacked with" hypothesis my previous comment carried is ruled out.

⚠️ It also puts my own control in doubt, and I would rather say so than let it stand: I offered "main is green on this test". The queue runs the full suite while branch CI runs only the affected subset — so if packages/cli is outside that subset, that green is a run that never asked the question, and this is a regression already on main rather than a queue quirk. ⛔ I have not resolved which. The evidence and the one check that settles it are on #18032.

This PR stays out of the queue until #18032 is resolved. Nothing about its diff has changed; the contract review at b4246a6bf still binds. ⛔ No empty commit, ⛔ no close-and-reopen, ⛔ no touching that test.


Generated by Claude Code

@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

⚠️ Correction to the comment above — the doubt I raised is resolved, the other way

I wrote that my "main is green on this test" control might be void, because the queue runs the full suite while branch CI runs only the affected subset. I checked rather than leaving it hanging. It is not void: the test really ran on main and really passedcheck-test-completeness on that shard reads 13 of 13 scheduled package(s) … 9 703 test(s).

⇒ ⛔ This is NOT a regression sitting on main. Scratch that reading.

What the same measurement turned up instead is a sharper and much more tractable lead — the failure tracks shard composition, not any PR's content:

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

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 13, 2026
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 13, 2026
Merged via the queue into main with commit a249786 Sep 13, 2026
36 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-17897-permission-set-refused-reads branch September 13, 2026 15:57
baozhoutao pushed a commit that referenced this pull request Sep 14, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants