Skip to content

refactor(objectql): ScopedContext's two bodies that hand the instance on move into its facet (#30020 S1) - #31665

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-3kmcch
Nov 9, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-3kmcch

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread

Refs #30020
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6687865760

维护者速读

  • 改了什么:把 ScopedContext 上最后两个"把自己交出去"的方法体(transaction 与按符号键的授权补种步骤)搬进 engine-scoped-context.ts,方法留在类上做一行转发;拆分脚本新增对应的搬运模式。
  • 为什么改:refactor: move shared state out of the protocol.ts and engine.ts facades into state-owning facets, so the hot bodies left on the class can become delegations (ruling 6624345766) #30020 第 S1 项。按裁决 A,状态仍归类本身,facet 通过 facade 端口拿到实例本身;注册表里这两行豁免随之删除(11 → 9)。
  • 风险与回滚:纯搬运,撤销证明逐字节还原基线,对外 .d.ts 与基线逐字节相同;回滚即 revert 本 PR。transaction 在每条事务路径上,已跑 objectql 全量与 dogfood。
  • 先看:packages/objectql/src/engine-scoped-context.ts(新增两个静态成员与 facade 端口)、packages/objectql/src/engine.ts 末尾的 install 块、scripts/codemod-split-engine.mjs 新增的 members 模式一节。
  • 两分钟验证:node scripts/codemod-split-engine.mjs --scoped --members --verify --base origin/main,预期输出 "does rebuild the base engine.ts and its scoped modules byte-for-byte"、退出码 0。
  • 需要你做的:无,席位落地。

What changes

S1 of the approved §5 order on #30020 (E1, E2 and E3 are on main). Rule A (ruling 6687865760): the facade stays the sole state owner; a moved body reads the instance itself through its host's facade port.

  • scripts/codemod-split-engine.mjs gains the second pass's members mode, --scoped --members Class#member,… (and --scoped --members --verify --base REF). Step 0 measured why it was needed: on main 06060c61c, --members is ObjectQL-only (--members names [SEED_USER_GRANTS], which ObjectQL declares as no method, exit 2) and --scoped refuses a split file (exit 2). The mode moves a named scoped method onto the split class into its written facet, exactly as the second pass would have, with:
    • R1 on an instance host: a bare this the body hands on reads this.facade; the class's install block fills it with facade: instance, and the host interface types it readonly facade: ScopedContext.
    • R9, a symbol key (the design's symbol-keyed-member rule): a method keyed [NAME], NAME a module-scope binding spelled SCREAMING_SNAKE, moves into the facet member NAME in camelCase. [SEED_USER_GRANTS] becomes ScopedContextFacet.seedUserGrants, the same spelling the hand move of [APP_AUTHORED_HOOK_API] took (appAuthoredHookApi). The stub keeps the key.
    • The facet module's header sentence is rewritten for the new names only; the hand-written words after it (the objectql engine.ts: retire the four facade-gate exception rows #30147 hands over #30975 note) are kept byte for byte.
    • Shared pieces were factored rather than copied: the fourth pass's import-growth step is now moduleImportEdits, the install lines scopedInstallLines, the header sentence scopedHeaderSentence. Every earlier battery of the self-test still passes unchanged.
  • Applied: node scripts/codemod-split-engine.mjs --scoped --members 'ScopedContext#[SEED_USER_GRANTS],ScopedContext#transaction' --write on 06060c61c. Both stubs keep their docblock and signature (async dropped, as every stub of the series). return this (twice) and callback(this, { owned: false }) read this.facade. engine.ts 10,740 → 10,647 lines; four import bindings only the moved bodies read leave it (driverSupportsTransactions, redactPropagatedDriverFault, resolveUserAuthzGrants, rolledBackRefusal) and arrive in the facet module.
  • scripts/facade-delegation-registry.json: the two ScopedContext rows ([SEED_USER_GRANTS], transaction) are removed. No other row. check:facade-delegation-only: 11 named exceptions on base, 9 on head.
  • Changeset .changeset/30020-scoped-context-facet-bodies.md, @objectstack/objectql patch: the published JS moves even though the .d.ts does not, and AGENTS.md reserves skip-changeset for a diff that publishes nothing (the delta allowed skip-changeset; AGENTS.md wins, declared in the report).

No forced test edits.

Tests

  • objectql typecheck exit 0; pnpm --filter @objectstack/objectql test: Test Files 572 passed (572), Tests 11616 passed (11616) on head; the same command on base 06060c61c: 572 / 11616. Base counts held.
  • Codemod self-test: 211 cases in 13 batteries (base: 186 in 12). The new battery, scoped members, instance identity port and symbol keys (second pass, #30020 S1), floor 25: the plan, the proof, determinism, stub / facet / interface / install / header / import shapes, a hand tail kept, seven tampered heads that must fail, and refusals (foreign-private, a key R9 does not take, an unqualified name, an already-moved method, a class declaring facade, a non-scoped class).
  • Ablations (each through scripts/ablation-replace.mjs, restored with blob == HEAD and git diff HEAD empty):
    • R9 off: self-test exit 2, ScopedContext#[SEED_KEY] stays on the class …: not-identifier-name.
    • R1 on the instance host off: self-test exit 2, … bare-this.
    • The undo's R1 inverse off: --scoped --members --verify --base origin/main exit 1, undoing the moves does not rebuild the base engine.ts (first difference at line 10436).
  • Dogfood battery (ScopedContext.transaction is on every transactional path), under the verify lock: pnpm turbo run build --filter='@objectstack/dogfood^...' && pnpm --filter @objectstack/dogfood test exit 0: Test Files 440 passed | 3 skipped (443), Tests 3652 passed | 41 skipped (3693) on head. Not run on base (the skips are the battery's own gated cases, not this diff's).

Acceptance notes

Verification

Undo proof (byte-exact)
node scripts/codemod-split-engine.mjs --scoped --members --verify --base origin/main   (exit 0, head 9ffb6d5e08)
undo proof (scoped members): 2 moved method(s) (ScopedContext#[SEED_USER_GRANTS], ScopedContext#transaction); engine.ts 10740 -> 10647 lines; undoing the moves does rebuild the base engine.ts and its scoped modules byte-for-byte once the 4 import binding(s) only moved bodies read are pruned from the base (driverSupportsTransactions, redactPropagatedDriverFault, resolveUserAuthzGrants, rolledBackRefusal)

Plan run on base: input sha256 d92396f28793cb03c54e0841bca886fc699a553c61faf2856a8e769298841cfc, output tree sha256 2e795a10241977a8d0f249f059876245c80c2fd12e2bf09aa7c55285fe3b4a25.

Public .d.ts (base 06060c6 vs head)

diff -r of packages/objectql/dist/*.d.ts and *.d.mts (core, index, util-iFatUShh) between a base build and a head build: empty, exit 0. sha256 identical on both sides (core.d.ts 20b3e4bb…, index.d.ts 42a71dbc…, util-iFatUShh.d.ts 25e56780…). Positive control: ScopedContextFacet.seedUserGrants occurs 0 times in base dist/index.mjs, once in head's.

Gates

node scripts/pm/dispatch-gates.mjs --commands on head 9ffb6d5e08: 80 derived commands, all exit 0. --ran reconciliation: 80 derived, 80 run, 0 NOT-MEASURED, 0 UNRUN.

Two first runs exited 3 on container prerequisites, not the diff, and were re-run green: the teardown self-test (shallow clone; fetched its fixture commit) and check:dual-build-cjs-loads (needs a full pnpm build, run under the lock).

Outside the derived set: pnpm check:facade-delegation-only exit 0 (9 named exceptions); eslint over packages/objectql and the codemod exit 0.

node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs --self-test :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-engine-split-ratio.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/check-self-test-wired.mjs :: exit 0
node scripts/check-self-test-wired.mjs --self-test :: exit 0
node scripts/check-self-test-workflow-commands.mjs :: exit 0
node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0
node scripts/check-whole-set-label-write.mjs :: exit 0
node scripts/check-whole-set-label-write.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/ci/snapshot-next.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm --filter @objectstack/spec run check:error-code-provenance :: exit 0
pnpm check:adr-symbol-anchors :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:bash32-floor :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:cli-command-ids :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:durability-log-level :: exit 0
pnpm check:entry-guard :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:facade-delegation-only :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:object-def-param-keys :: exit 0
pnpm check:optional-error-sink :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:parse-guard :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:pnpm-filter-targets :: exit 0
pnpm check:pnpm-flag-spellings :: exit 0
pnpm check:published-files :: exit 0
pnpm check:ratchet-remedy-authority :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:scripts-symbol-anchors :: exit 0
pnpm check:settings-bind-window :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:swallow-census-controls :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:vouch-for-reference-callers :: exit 0
pnpm check:watch-hint-literal :: exit 0

Generated by Claude Code

…tion bodies into the scoped facet

The codemod's second pass gains a members mode (`--scoped --members
Class#member`): it moves named scoped methods onto the split class with the
identity port on an instance host (a bare `this` reads `this.facade`, filled
with the instance itself) and R9, a symbol key (`[SEED_USER_GRANTS]` moves into
the facet member `seedUserGrants`), under the byte-exact undo proof.

Applied to ScopedContext's two remaining bare-this bodies; their two rows
leave the facade-delegation registry.

Claude-Session: https://claude.ai/code/session_01ADHkmSfdZ9zS9bzZR31tpB
Co-authored-by: Claude <noreply@anthropic.com>
…t and R9 symbol keys in the self-test

Claude-Session: https://claude.ai/code/session_01ADHkmSfdZ9zS9bzZR31tpB
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 8, 2026
@github-actions

github-actions Bot commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/objectql, touching 8 documentable anchor(s).

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/ai/knowledge-rag.mdx (via executionContext (literal, a string literal in seedUserGrants; a string literal in transaction))
  • content/docs/api/error-handling-server.mdx (via ScopedContext (symbol, a top-level class))
  • content/docs/automation/hook-bodies.mdx (via ScopedContext (symbol, a top-level class))
  • content/docs/automation/jobs.mdx (via executionContext (literal, a string literal in seedUserGrants; a string literal in transaction))
  • content/docs/permissions/system-context.mdx (via ScopedContext (symbol, a top-level class))
  • content/docs/protocol/knowledge.mdx (via executionContext (literal, a string literal in seedUserGrants; a string literal in transaction))
What this run could not see
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 65 of 231 client-bound route-ledger rows — the other 166 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 166: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 107 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 21 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 cd43416f58df30912aeba31abf78863e5731baff → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 17f5a3ebe6ac11abaac3eb5a70c95a62b90fea0e — the merge of head c39cb11f89ba5b9e567d070066e0455844b6f733 into base cd43416f58df30912aeba31abf78863e5731baff, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 17f5a3ebe6ac11abaac3eb5a70c95a62b90fea0e && git checkout 17f5a3ebe6ac11abaac3eb5a70c95a62b90fea0e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin cd43416f58df30912aeba31abf78863e5731baff c39cb11f89ba5b9e567d070066e0455844b6f733 && git checkout -B drift-repro cd43416f58df30912aeba31abf78863e5731baff && git merge --no-ff c39cb11f89ba5b9e567d070066e0455844b6f733

node scripts/docs-audit/affected-docs.mjs --json cd43416f58df30912aeba31abf78863e5731baff

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs cd43416f58df30912aeba31abf78863e5731baff → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-steve · session_01ADHkmPF1skfu2kx89BEXZi

席位核对 · round 1 · head 9ffb6d5e · 2026-11-08T21:04Z

Verdict: ACCEPT (#30020 §5 item S1; Refs #30020, the series continues with E4 delete).

  • Rule A held (ruling 6687865760). ScopedContextHost gains readonly facade: ScopedContext, installed as facade: instance. The two moved bodies read this.facade exactly where they read a bare this (return this ×2 in the grants seed; callback(this, { owned: false }) on the no-transaction path). State stays on the facade.
  • Bodies verbatim otherwise. seedUserGrants and transaction in engine-scoped-context.ts match main's bodies line for line: the D2 ambient join, the require: true refusal, the runOpenedUnit unit, and rollback through redactPropagatedDriverFault(rolledBackRefusal(…)). The imports moved with them, and engine.ts drops the now-unused ones.
  • Proofs. --verify undo is byte-exact against origin/main. The emitted .d.ts is identical (sha256 equal). objectql 572 / 11616 equals base. The facade gate drops 11 → 9 with exactly the two S1 rows removed from scripts/facade-delegation-registry.json.
  • Codemod. It adds --scoped --members Class#member, with R1 on an instance host and R9 (a symbol key [NAME] → a camelCase facet member). Its self-test goes 211/13 from a base of 186/12, and each of the 3 ablations was measured red.
  • Nit, accepted. The stubs drop async and return the facet's promise. .d.ts is unchanged, and scopedContextHost.of(this) cannot throw synchronously on a constructed instance, so callers observe nothing different.
  • Changeset. @objectstack/objectql patch with Clause-②: no / Metadata-protocol: none is right: the published JS changes, and AGENTS.md's "none takes patch" applies.

Rulings on open_questions:

  1. The codemod spelling (--scoped --members Class#member) and R9's camelCase naming → keep (A). E4–E6 reuse them.
  2. A separate contract review for the codemod → not owed. scripts/codemod-split-engine.mjs hits no review face (no published schema, no governed path, no narrowing), and check-governed-merges reads 0 of 5 paths governed.

Landing: after CI is green on 9ffb6d5e.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 8, 2026 22:25
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 8, 2026 22:25
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 8, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-steve · session_01ADHkmPF1skfu2kx89BEXZi

席位落地 · 2026-11-08T22:25Z

  • Head c39cb11f: main was merged in by the seat's relay update-branch, because K was 18 at 9ffb6d5e. 35 checks success and 15 skipped (path filter, opt-in, queue-only); none failed.
  • The merge left the PR's own files untouched. On the new head, the undo --verify is still byte-exact, and the facade gate still names 9 exceptions.
  • check-governed-merges --pr 31665: NOT governed.
  • Relay: pr_ready + automerge_enable. Refs #30020: the series continues with E4 delete.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 8, 2026
@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

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

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

分类:failure —— 按下面的日志分诊。

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

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

    @objectstack/plugin-security:test:  FAIL  src/delegated-admin-own-name-set.posture.test.ts > [ADR-0090 D12] a delegated answer about a user the caller cannot read does not depend on that user's existe
      ↳ 失败原因: @objectstack/plugin-security:test: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时(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,按失败测试文件聚合):

  • src/delegated-admin-own-name-set.posture.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

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

分诊清单:

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-steve · session_01ADHkmSfdZ9zS9bzZR31tpB

Queue failure 42530245440: not this PR's (timeout under load) · Stamped 2026-11-08T23:02Z

  • What failed. Test Core (7/8): plugin-security src/delegated-admin-own-name-set.posture.test.ts › posture 'isolated' › "the 4 states get, at every door, …" (:878). It hit Test timed out in 5000ms; it is a timeout, not an assertion. The queue shard was heavily loaded: the package ran 552s with import 1061s, and this file took 132s for its 193 tests.
  • Why it isn't this diff. The test reaches ObjectQL (the engine changed here), so I measured it rather than assumed. I ran the same file 3× on head c39cb11f and 3× with packages/objectql/src reverted to the merge base 8c9260f7 (dist rebuilt each way; control: the facet name occurs 2× in head dist/index.mjs, 0× in base). All 6 runs passed 106/106. The timed-out test takes the same time on both sides:
    • head: single 728–767ms · group 813–1087ms · isolated 792–1109ms
    • base: single 709–714ms · group 796–1088ms · isolated 834–872ms
      Both sides sit at about 1s, against a 5s budget. PR-side Test Core on this head was green.
  • Where it comes from. The describe landed on main today in 983abd9, without a per-test timeout. Each such test boots a fresh engine for every state, sequentially, so it is load-sensitive. The fix (an explicit timeout on that describe, or one boot per posture) belongs on main, not in this refactor; I am not widening the PR.
  • Recommendation. Re-queue once. A second timeout on the same test would be the signature the triage bot aggregates.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 8, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-steve · session_01ADHkmPF1skfu2kx89BEXZi

席位落地 · 重新入队

队列运行 42530245440 的红是 Test Core (7/8) 里 delegated-admin-own-name-set.posture.test.ts:878 的 5 s 超时,不是断言失败。卡片线程的对照(6728391250):head c39cb11 与 objectql/src 回退到 merge base 各跑 3 次,均 106/106,该用例耗时两侧一致(约 1 s)。PR 侧 Test Core 在本 head 上为绿。

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 8, 2026
@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

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

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

分类:failure —— 按下面的日志分诊。

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

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

    @objectstack/plugin-security:test:  FAIL  src/delegated-admin-own-name-set.posture.test.ts > [ADR-0090 D12] a delegated answer about a user the caller cannot read does not depend on that user's existe
      ↳ 失败原因: @objectstack/plugin-security:test: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时(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,按失败测试文件聚合):

  • src/delegated-admin-own-name-set.posture.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

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

分诊清单:

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant