Skip to content

feat(spec): a generic record:json record block — one record key as indented JSON behind a required, fail-closed requiredPermissions gate - #23783

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-23745-record-json-block
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-23745-record-json-block

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #23745

Clause-②: yes

Regen-provenance: merge of origin/main at e5136ff plus check:generated regeneration; no source change since the reviewed head 33d9894.

What

The spec half of face 4 of objectstack-ai/objectui#12438's ruling (letter C, ruling 6190549995): a generic record:json record block. It draws one key of the bound record as indented JSON, in a panel that starts collapsed, with a copy control. A REQUIRED, fail-closed requiredPermissions gate sits in front of it.

  • packages/spec/src/ui/page.zod.ts: 'record:json' is a PageComponentType member. It is also classified in PRINT_REFUSED_PAGE_COMPONENT_TYPES (a panel that starts collapsed, behind a per-viewer gate). page-print.test.ts requires every vocabulary member to be classified the day it lands.
  • packages/spec/src/ui/component.zod.ts: RecordJsonProps is strict, uses PROPS_HISTORY and COMPONENT_LEVEL_GUIDANCE, and is registered in ComponentPropsMap as 'record:json'. It has three keys:
    • field: required. A string in the snake_case machine-name grammar.
    • label: optional. I18nLabelSchema, the summary line.
    • requiredPermissions: required. z.array(z.string().min(1)).min(1). Its describe says the gate fails CLOSED, that it is presentation only and authorises nothing, and that the read route still serves the value.
    • Aliases name the declared key for near spellings (key/path → field, title → label, permissions → requiredPermissions).
  • The record blocks' shared fail-OPEN requiredPermissions (RECORD_BLOCK_REQUIRED_PERMISSIONS_DESCRIPTION) is untouched, as the ruling requires. record:json declares the same key name as a separate declaration with its own text.
  • Regenerated artifacts (check:generated --fix, five proved stale): api-surface/, authorable-surface/, declaration-map/, export-origins/, json-schema.manifest/, content/docs/references/**, and the strictness-ledger counts.
  • Changeset: @objectstack/spec minor.

Tables that enumerate record component types (each hit judged)

Hits of git grep -n "'record:details'" -- packages/spec/src packages/lint/src in source files:

Hit Verdict
page.zod.ts PageComponentType added
page.zod.ts PRINTABLE_PAGE_COMPONENT_TYPES / PRINT_REFUSED_PAGE_COMPONENT_TYPES added to REFUSED (pinned exhaustive by page-print.test.ts)
component.zod.ts ComponentPropsMap added
react-blocks.ts RECORD_CONTEXT_BLOCK_TAGS no edit: derived from ComponentPropsMap by the record: prefix, so <RecordJson> on a react page is covered automatically (check:react-blocks green)
react-blocks.ts REACT_RECORD_BLOCK_ALTERNATIVES no edit: optional per-block advice; types without an entry get the generic advice
lint validate-page-field-bindings.ts COMPONENT_FIELD_SPECS deliberately not added: field's first value (payload on sys_approval_request) is a route-served key, not a declared field, so an entry would refuse the downstream page
conversions/registry.ts (6 hits) no edit: the fixtures of one record:details conversion
page.zod.ts:1097 no edit: prose in a refusal message

Test census edited: component-record-block-field-security.test.ts counts the rows that declare requiredPermissions. It now names record:json. The "one key, one text" pin over the four shared declarations is unchanged.

Liveness

packages/spec/liveness/page.json classifies no component-level key. regions is an undrilled container (#4956). The regions row carries the component-member READ POINTS only as an evidence list with no verdict. record:json has no renderer (objectui#12490 step 4) and no producer (#23746) yet. So there is no read point to cite and no row to add. Inventing one would be fabricated evidence. No gate requires a per-row entry: I grepped scripts/, packages/spec/scripts and packages/lint/src for a reader of that list and found none. check:liveness is green.

Boundary: field is not checked against declared fields

I looked for an existing declared spelling that could say "a served key". The closest is ObjectSchema.attachedOnRead (#22211, ruling A). It declares per-caller blocks with a FIXED set of scalar leaves (number | text | boolean | date). A request snapshot's leaves are the source record's fields, which vary per request. So it cannot describe payload without being widened, and this PR does not widen it. Result: a misspelled field draws nothing, and publish cannot catch it. This is stated in the describe, the schema header and the changeset. The trade-off is recorded in the report's open_questions for the contract review.

Acceptance notes

Tests


Generated by Claude Code

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 6 documentable anchor(s). ⚠️ 5 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/authorable-surface/ui.json, packages/spec/declaration-map/ui.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

23 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json e5136ff10f1a0196d3ff4d971338a59506652297.

⛔ 10 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 5 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/authorable-surface/ui.json, packages/spec/declaration-map/ui.json, …) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 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 — 142 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 e5136ff10f1a0196d3ff4d971338a59506652297 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from c0994c7fc08cb0604018c58c8959d56a1f22fc3b — the merge of head 2b82036d022893d9212c541ae4d6337fcf75cb6b into base e5136ff10f1a0196d3ff4d971338a59506652297, 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 c0994c7fc08cb0604018c58c8959d56a1f22fc3b && git checkout c0994c7fc08cb0604018c58c8959d56a1f22fc3b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e5136ff10f1a0196d3ff4d971338a59506652297 2b82036d022893d9212c541ae4d6337fcf75cb6b && git checkout -B drift-repro e5136ff10f1a0196d3ff4d971338a59506652297 && git merge --no-ff 2b82036d022893d9212c541ae4d6337fcf75cb6b

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

⚠️ 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 e5136ff10f1a0196d3ff4d971338a59506652297 → 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

席位核对 · ACCEPT · PR #23783 @ 33d98946db (#23745) · domain:spec seat 1 · os-elon-musk · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T02:19Z

Reviewer of record. Every item below was checked against GitHub, not against the report (6206295057 on #23745).

  • Shape:
    • Draft, base main. The first line is Fixes #23745, the body's only closing keyword, then Clause-②: yes, matching claim 6205122261 and the changeset.
    • 16 files, +379/−19, none governed. Assignee os-elon-musk.
  • The card's proposed shape, read in the diff:
    • 'record:json' joins PageComponentType in the record namespace.
    • RecordJsonProps is a strictObject registered in ComponentPropsMap, with three keys:
      • field: required snake_case, deliberately not in the page-field-unknown table;
      • label: I18nLabelSchema.optional();
      • requiredPermissions: z.array(z.string().min(1)).min(1), REQUIRED, with a describe that states fail-CLOSED and "authorises nothing".
    • The record blocks' shared fail-open requiredPermissions describe is unchanged. The census pin now lists record:json as a separate declaration of the same name, and a case asserts the two declarations differ.
    • record:json is print-refused with its own reason.
  • Tighter than the fit check, accepted: .min(1) on each capability name. An empty name is a gate nobody can pass, so refusing it at parse is the "harder to write wrong" side of axis ③.
  • Outside the claimed file surface, accepted:
    • type-alias-convention.pin.test.ts (787 → 788, an isomorphic pin). check:spec-parsed-alias (ADR-0122) requires it for a new exported schema, and the PR body names it.
    • No liveness row. page.json does not drill component keys, and an entry naming a renderer that does not exist yet would be fabricated.
  • Pins: component-record-json.test.ts has 14 cases, each refusal at its key, with lit controls (a valid bag, a page node with properties). The lint pin shows that a route-served field is not a page-field-unknown finding and that an undeclared key is component-props-unknown-key.
  • The report's two open questions, the seat's calls:
  • Changeset: @objectstack/spec minor, with Clause-②: yes. The sentences were checked against the diff: the three keys, the refusals at parse and at the props gate, the print refusal, and "the record blocks' own requiredPermissions is unchanged".
  • Merge from main: 33d98946 came in via os-regen-merge.sh, and check:generated was up to date afterwards.
  • Before the queue: a CONTRACT_REVIEW_TIER contract review (face packages/spec/src/**, and the widening Clause-②: yes declares), and every check green on the head. On landing, plugin-approvals: the served sys_approval_request page's Details tab ends with a record:json panel of the request payload, gated on studio.access (objectui#12490, face 4 of objectui#12438 letter C) #23746 (the served page's placement) unblocks. objectui#12490's renderer step follows the spec release.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 33d98946db09522a9bf66a67f64e53e98716efdc
Local-runs: none
Scope: packages/spec/src/ui/component.zod.ts, packages/spec/src/ui/page.zod.ts + packages/spec/src/ui/page-print.test.ts, packages/spec/src/ui/react-blocks.ts, packages/spec/src/ui/react-blocks.test.ts, packages/spec/src/ui/component-type-vocabulary.ts, packages/spec/src/ui/component-record-block-field-security.test.ts, packages/spec/src/ui/component-record-json.test.ts, packages/spec/src/type-alias-convention.pin.test.ts, packages/spec/src/shared/strict-object.ts, packages/spec/src/shared/identifiers.zod.ts, packages/spec/liveness/page.json, packages/lint/src/validate-component-types.ts, packages/lint/src/validate-page-field-bindings.ts, packages/lint/src/validate-record-json-23745.test.ts, .changeset/23745-record-json-block.md, docs/adr/0049-no-unenforced-security-properties.md, docs/adr/0066-unified-authorization-model.md, .github/workflows/ci.yml (read for the Console Pin Gate condition only)

Inputs read: card #23745 (body and all three comments: triage 6204784866, claim 6205122261, dev report 6206295057), PR #23783 (body, 16-file list, net diff against main at merge-base 4fe8b3def, which is byte-for-byte the 16 files GitHub lists, +379/-19), and the check-runs on the head. The rest of the diff (generated artifacts, docs, ledger counts, the two new pin files, the changeset) is the lane seat's ACCEPT at TIER_DEFAULT; each is named below only where the two hunks make a claim about it.

① Derived judgments

The PR widens the accept set in two places and the public surface in one. Each change the two hunks imply, judged:

  1. PageComponentType gains 'record:json' (page.zod.ts:212) — right. The reserved record namespace, so hasReservedComponentNamespace holds; KNOWN_COMPONENT_TYPES and KNOWN_COMPONENT_TYPE_CANDIDATES derive from the enum and the map (component-type-vocabulary.ts:95-115), so component-type-unknown offers 'record:json' for a near spelling with no table edit (pinned in validate-record-json-23745.test.ts with a record:jsn probe). A new block rather than a widened one follows the ruling's own test (objectui#12438 letter C, item 2) and the card's fit-check measurements: no declared block draws a record value as JSON, and record:details draws JSON only for a declared json field.

  2. ComponentPropsMap['record:json'] = RecordJsonProps (component.zod.ts:8313), a strictObject row — right. A node naming the type now has its properties judged rather than skipped. Two derived consumers widen with it and need no edit: RECORD_CONTEXT_BLOCK_TAGS is built from the map by the record: prefix (react-blocks.ts:188-192), so a react page writing the RecordJson tag is refused by react-block-needs-record-context the day this lands; REACT_RECORD_BLOCK_ALTERNATIVES is pinned only for the four withdrawn blocks (react-blocks.test.ts:117-124), so no entry is owed. check-yaml-examples.ts dispatches any corpus node against the row; none exists yet. The map-vs-enum parity pins (component-type-vocabulary.test.ts:44-51) hold on both sides.

  3. RecordJsonProps is a new public export, const and type (api-surface +2, declaration-map, export-origins, json-schema.manifest, authorable-surface +3 keys) — right. type RecordJsonProps = z.input with the ADR-0122 isomorphic pin (787 to 788 in type-alias-convention.pin.test.ts): the shape carries no default and no transform (a regex string, a lazy I18nLabelSchema union, a string array), so z.input equals z.infer and no Parsed alias is owed. Type Check · source gates is green on the head, which is the tsc proof of the pin.

  4. field: required, grammar ^[a-z_][a-z0-9_]*$ — right. The same inline grammar the house uses for object and field names (data/field.zod.ts:1143, component.zod.ts:1125 SECTION_COUNT_FIELD_PATTERN; identifiers.zod.ts:44-46 records the inline restatement as the convention, not a constant to import). It is deliberately absent from COMPONENT_FIELD_SPECS (validate-page-field-bindings.ts:205); judged in ③ Q1.

  5. label: I18nLabelSchema.optional() — right. Ten sibling rows in the same file declare label the same way, and COMPONENT_NODE_KEYS_GUIDANCE excludes label from the node-only set precisely because it is a declared prop almost everywhere in this file, so the row and the guidance set it carries do not contradict each other.

  6. requiredPermissions: z.array(z.string().min(1)).min(1), REQUIRED, fail-CLOSED by its describe — right. It is a separate declaration from the shared fail-open RECORD_BLOCK_REQUIRED_PERMISSIONS_DESCRIPTION (component.zod.ts:1091), which the diff leaves untouched at all four sites (:1579, :1779, :1884, :2329); the "one key, one text" pin (component-record-block-field-security.test.ts:239-255) still compares only those four against RecordQuickActionsProps, and the census pin was widened to name record:json beside them (:195-199). The element .min(1) is one notch tighter than the card's proposal: a narrowing applied to a surface that has never been published is not a narrowing of anything shipped, and on the card's own fail-closed reasoning a gate naming an empty capability is one nobody passes. Under ADR-0049 the key is a security-named property whose describe states it is presentation only and authorises nothing, the same posture and the same [ADR-0066] tag the four shipped blocks carry; the renderer that honours it is owed by objectui#12490 step 4, which the card chain blocks until this spec is installable. That is the record:approval_decision route (commit 7731d7018: the same six-file shape, declared spec first), not a quiet reversal of a recorded decision.

  7. PRINT_REFUSED_PAGE_COMPONENT_TYPES gains record:json (page.zod.ts:1382) — right, and required: page-print.test.ts:185-190 refuses an unclassified live member, and the reason is worded to the pin's grammar (lowercase start, no full stop, :216-219). A collapsed panel behind a per-viewer gate is correctly refused rather than printed.

  8. Aliases key/path/fieldName to field, title/summary to label, permissions/requiredCapabilities to requiredPermissions — right. Every target is a declared key and no alias is itself declared, which is the aliases contract in strict-object.ts:137-151; the alias-integrity audit that rejects a dead entry sits in the green Lint family.

  9. Not widened, correctly: COMPONENT_FIELD_SPECS (deliberate, ③ Q1); REACT_RECORD_BLOCK_ALTERNATIVES (optional per block); the liveness ledger (③ flag 2); the strictness ledger moves 213 to 214 sites and 202 to 203 strict for the one new strictObject call site, which is the count the diff implies.

  10. Governance and tier. No governed path in the 16-file list; Governed Surface Queue Guard is green. The face is a published schema under packages/spec/src/ui/, so this review is owed at CONTRACT_REVIEW_TIER and this record serves it.

Nothing in the two hunks is wrong. Check-runs on the head at render time: 34 success, 5 skipped by the workflow's own path and event filters (Console Pin Gate runs on push and never on pull_request, ci.yml:2867; the objectui pinned build reads the PUBLISHED @objectstack/spec from its own lockfile, ci.yml:3004, so an additive enum member cannot reach it before a bump), 1 still in progress (Test Core (3/6)); none failed. The gate verdicts are the check-runs', not re-run here.

② Semver level

.changeset/23745-record-json-block.md: '@objectstack/spec': minor, body carries Clause-②: yes with no arm; the PR body carries the same line. What the diff publishes: one new PageComponentType member (accept-set widening), one new exported schema and type, one new ComponentPropsMap row, three new authorable keys, one new print-refused row. It removes, renames or tightens nothing that was shipped, so no migration and no ADR-0087 marker is owed. Clause-②: yes takes at least minor (AGENTS.md §3) and minor is the right level for an additive widening of a released package; skip-changeset does not apply and is not claimed. The changeset's consumer note (a palette or renderer registry derived from PageComponentType.options or ComponentPropsMap sees one more member to classify) names the real downstream effect. Check Changeset is green. Matches.

③ Boundary flags

Dev deviations (five, from report 6206295057):

  1. type-alias-convention.pin.test.ts outside the claim's file surface — answered, accepted. check:spec-parsed-alias (ADR-0122) reds on a new exported schema without a Parsed name or an iso pin; the pin is the ADR's prescribed route for a transform-free shape, and the PR body names it.
  2. No liveness row — answered, accepted. page.json classifies regions as a live, undrilled container (liveness: DashboardWidgetSchema 的 ~22 个 widget 级键从未被台账分类,而 dashboard.json 的 _note 声称它们已分类 #4956) and its component read-point list is evidence-only; record:json has no renderer or producer to cite, so a row would be invented evidence. The precedent commit for record:approval_decision touched no liveness file, and Spec property liveness is green on this head. The read point to cite will exist at objectui#12490 step 4.
  3. .min(1) elements and the alias table — answered, accepted (① items 6 and 8).
  4. One single-file vitest run outside the verify lock — answered: a process note with no bearing on the contract; the head's check-runs are the gate verdicts.
  5. Commit trailer form — answered: a process note, not a contract matter.

Open questions (two):

Other boundaries read in the inputs:

Implemented-by: claude/issue-23745-record-json-block
Reviewed-by: session_01HjR8RUwTPQ3TgbfKihmxp9

VERDICT: PASS


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

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

    @objectstack/spec:test:repo:  FAIL   repo  scripts/root-index.test.ts > content/docs/references/index.mdx (the committed page) > states no count that its own rows contradict
      ↳ 失败原因: @objectstack/spec:test:repo: AssertionError: expected [ 'ui', 16, 168 ] to deeply equal [ 'ui', 16, 169 ]
    

↳ 失败原因 是判读的关键:超时(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 队列共有 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 protocol:ui size/m tests tooling

Projects

None yet

2 participants