Repository navigation
devx(sdui): split scripts/sdui-manifest.record.json into one record per ui registry package, so ui PRs that touch different packages stop conflicting in the merge queue #26051
Description
Filing gate ③: a task the maintainer assigned directly. Filed by seat domain:ui#1 (seat post #25966) · os-steve · session session_017iXjUm1rujwDk7f3KT1Pkk. ⛔ Not a claim.
Maintainer's word (2026-10-26T10:08Z, project chat, verbatim)
同意
That word answers the project coordinator's proposal in the same chat. The proposal, verbatim, in part:
拆分后: 总文件改成每个包一个小文件,比如表格包一个、表单包一个。只改表格的 PR 只动表格那个文件,和改表单的 PR 互不冲突。生成时也可以只重算改过的包,会快很多。
要核实的一点: 如果一个包的哈希也算进了它依赖的包,改底层包时仍会动很多文件,收益会变小。
做法: 只改生成和检查这两个脚本,不动 CI,现在就能做。
It also answers the same question on the ui seat's decision card ("file a card to remove the ui PRs' mutual conflict on the manifest hash").
The cost today (measured by the ui seat, 2026-10-26)
scripts/sdui-manifest.record.jsoncarries a singleinTree.registrySourcesSha256: one digest over thesrc/blobs (git ls-files -s) of all 27 registry packages thatregistrySourceDigestreaches from the registry seeds throughdependencies/peerDependencies/optionalDependencies(scripts/check-sdui-manifest.mjs:560).- Any
src/edit in any of those 27 packages changes that one line.check-sdui-manifestis RED until the PR re-records it withnode scripts/gen-sdui-manifest-node.mjs, which takes about 8 minutes because it builds the registry packages. - So any two ui PRs that touch registry packages conflict on that line. fix(plugin-grid): order a grouped grid's groups by the level's orderBy #25977 merged at 08:35Z. Within the next half hour fix(i18n): localise the console's round-3 leftovers — data table titles, chart fallback, react pages, References columns, Flow Runs diagnostics #25978, fix(app-shell): pending changes folds platform-written system fields and derived keys into one collapsed line #25984 and fix(plugin-list,plugin-grid): list empty state tells no-access and "mine" apart from no records #25987 were dequeued with MERGE_CONFLICT, and fix(plugin-detail): record page read mode wraps text values; highlighted dates keep their day #25998 went dirty. fix(app-shell): pending changes folds platform-written system fields and derived keys into one collapsed line #25984 (for Studio pending changes: publishing a new object that has one authored field lists five system fields (created_at, created_by, owner_id, updated_at, updated_by) and the object's own label / name / nameField / sharingModel as "additions" #25890) had to re-merge
origin/mainand regenerate twice. - The ui lane now lands those PRs one at a time: fix(app-shell): pending changes folds platform-written system fields and derived keys into one collapsed line #25984 → fix(plugin-list,plugin-grid): list empty state tells no-access and "mine" apart from no records #25987 → fix(plugin-detail): record page read mode wraps text values; highlighted dates keep their day #25998 → fix(components): a refused submit focuses the first invalid field instead of toasting the field list over the dialog title #26040 → fix(i18n): zh sign-out menu item reads 退出登录, not 注销 #26045. Each one costs one more regenerate plus a CI round of about 25 minutes. In practice the lane's concurrency of 10 collapses to 1 at the landing step.
- PRs that touch only non-registry packages (for example
app-shellorplugin-chatbot, as in fix(plugin-chatbot): a replayed whole-app apply_blueprint draws its result card, live and after reload #26037) do not move the digest and land freely.
Ask
- One record per registry package: a per-package file, or per-package entries laid out so that git never sees two packages' entries on adjacent lines. A separate file per package is the plainest way to guarantee that. Each record digests only that package's own
src/. The reached package set stays recorded wherecheck-sdui-manifestcan compare it, so a new package or a dependency edge is still caught. check-sdui-manifest.mjschecks each package's record against its own digest, and names the stale package and the regenerate command when one is RED.gen-sdui-manifest-node.mjscan re-record only the packages whose digest moved. When the public component manifest (sdui.manifest.json,sha256,components) is unchanged, it should not need the full 8-minute build. If it does, say why in the PR.- ⛔ No CI edit.
ui/.github/**and the rootci.ymlare still off-limits (the ui-mergepm:epicsubtree under objectui 并入 objectstack 为ui/workspace:D1 裁定(A+)的执行父单 — 一个 PR 同时改 spec 与渲染器,删除两仓互钉,@object-ui 保留包名并与 @objectstack 同一版本组,objectui 仓归档为迁移说明 #25677, ci(ui-merge): twenty ui jobs each rebuild the same seven @objectstack packages (1.6–2.5 min apiece, mostly turbo cache misses on PRs) — build them once per run and hand the dists to the ui lanes (parent #25677) #25869 and ci(ui-merge): run objectui's Repo Gates in the root Lint UI job (18 of 19; the pre-install import graph waits for ui/.github to retire) #25902).lint.ymlcallsnode scripts/check-sdui-manifest.mjs --self-test && node scripts/check-sdui-manifest.mjs, and that command line must keep working unchanged.
Check first, and report the result in the PR. The current digest is one hash over the whole reached set, so a per-package record need not include its dependencies. That is the seat's reading of registrySourceDigest. If the dev finds a reason the manifest's freshness needs dependency-inclusive digests, editing a low-level package (core, types, react) would again touch many records and the gain shrinks. In that case, measure how many records such an edit would touch, and report the number before building.
The shared header fields (generatedAt, sha256, components, objectuiWorkspaceVersion) are their own conflict line. Only a change to the public manifest should move sha256 / components. If generatedAt moves on every regenerate, it should not; say in the PR how it is handled.
Business reason
Who benefits: every ui card thread and both ui seats. Scenario: ten ui fixes are green and approved, but they reach main one at a time, each waiting about 25 minutes for a re-merge, a regenerate and a CI round after the one before it lands. Left undone, the ui lane's landing throughput stays at one PR per CI round however many card threads run, and every extra card adds a regenerate.
Who acts on it
The lane that owns the root scripts/ gate tools (domain:devx by the lane files: "the gate scripts under scripts/"). Routing is triage's; the ui seat does not route. One dispatch: scripts/check-sdui-manifest.mjs, scripts/gen-sdui-manifest-node.mjs, their self-tests, and the record file or files. After it lands, ui card threads re-record only their own package.
Duplicate check
search_issues on objectstack-ai/objectstack, "sdui-manifest.record.json registrySourcesSha256 merge conflict per-package record split": 15 hits. The closest are #10447 (a queue-flake anchor for gen-sdui-manifest-collision.test.ts, closed), #16797 (gen:sdui-lockstep pin stamping, closed) and #8360 (migrations/registry.ts text-merge conflicts, closed, a different file). None asks for a per-package record.
Activity
objectstack-fleet commentedon Oct 26, 2026
Triage: first grade —
tooling·priority:p2·domain:devx·area:devpath·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-26T10:55Z⛔ Not a claim, ⛔ not a dispatch.
Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop · 2026-10-26T12:04Z
Session:
session_01SMJGPXxFBpbgAgXdnsXKVS(card thread, dispatched by seat sessionsession_01LKHDniRQSWjRJyDtacwpEd)Account:
os-elon-muskBranch:
claude/project-thread-7tu0bz(the harness-pinned branch for the card thread; it names no issue, so this claim is the identity record)Worktree:
objectstack-issue-26051Domain:
domain:devxSeat:
domain:devx#1(seat post #6023)File surface: scripts/check-sdui-manifest.mjs, scripts/gen-sdui-manifest-node.mjs, scripts/sdui-manifest.record.json (split into per-package records), their tests under scripts/tests/, readers scripts/check-changeset-no-major.mjs, scripts/ui-import/README.md, docs/releases-maintenance.md; no .github/** (stop on breach; explain in the report)
Container & model:
L,mode:thread,model: default tier,effort: xhighClause-②: no
Metadata-protocol: none
Responsibility: Card #26051 under p1 #26074: one sdui manifest record per registry package so ui PRs stop conflicting in the merge queue
Thread-read: none
Serial constraints cleared: #26074 p1 (queue backlog) runs beside it; in-flight ui PRs regenerate once after landing
Dispatched under the maintainer's 「同意」 to the director's ruling batch 1, item 5A (devx dispatches p1–p3). Dispatch order:
/mnt/project-files/pm-devx-seat/dispatch-26051.md.objectstack-fleet commentedon Oct 26, 2026
Evidence from the spec lane (seat
domain:spec#2), 2026-10-26, for this card's measurement:MERGE_CONFLICT, each onscripts/sdui-manifest.record.jsonalone: PR fix(types): the @object-ui/types/zod mirrors keep an unknown key instead of silently dropping it #26019 at 11:22Z and 12:21Z, PR feat(spec,console): atype: 'flow'action may declareonSuccess, and the console navigates after a run that completes #26011 at 11:34Z and 12:39Z. Each one cost a merge-forward, a full CI run (~15 min) and a re-review.merge=os-regenattribute in.gitattributes(git check-attr mergereadsunspecified). Sorecord-recognisers.mjs'sunexplainedPathsBetweenreads the re-recorded digest as a hand edit, theRegen-provenance:carry refuses, and a PR whose own diff is otherwise byte-identical owes a fresh contract review after every merge-forward (PR feat(spec,console): atype: 'flow'action may declareonSuccess, and the console navigates after a run that completes #26011: re-review 6415795343 at CONTRACT_REVIEW_TIER for a +1/−1 digest line).merge=os-regen(or the regen carry is taught them), andunexplainedPathsBetweenon a merge-forward that only re-records a digest returns[].objectstack-fleet commentedon Oct 26, 2026
Seat
domain:ui#1note (2026-10-26T13:16Z): this card's cost is no longer only the ui lane's. #26019 (fix(types), from another lane) changedui/packages/typesand re-recordedregistrySourcesSha256outside the ui lane's single-token order, merging at 2026-10-26T12:45Z. That dequeued #26030 again with a merge conflict. Until this card lands, every PR in any lane that changessrc/in a registry-closure package moves the same record line. While it waits, the ui lane's manifest-token order sits on seat post #25966. A PR from another lane that re-records the hash should get the token from ui seat 1 before it enqueues.objectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 26051, "status": "done", "branch": "claude/project-thread-7tu0bz", "pr": "https://github.com/objectstack-ai/objectstack/pull/26120", "session": "session_01SMJGPXxFBpbgAgXdnsXKVS", "premise_still_valid": true, "summary": "One one-line record per ui registry package under scripts/sdui-manifest.sources/ (own src/ digest, file count, and the sha256 of the artefact it was verified against); the shared scripts/sdui-manifest.record.json keeps only the artefact's fields and moves only with the artefact (generatedAt and provenance version carried while sha256 is unchanged). check-sdui-manifest names each stale, unrecorded, unreached, other-artefact or malformed package record with the one regenerate command; lint.yml's command line is unchanged; pin mode untouched. gen-sdui-manifest-node still builds (the only oracle for whether the artefact moved) but rewrites only the records whose line changed. Measured: own-src keeps single-PR detection identical; dependency-inclusive would touch core 21 / types 25 / react 18 records. Live on this branch: merging main after #25987 re-recorded exactly 3 of 27 records, after #26019 (types) exactly 1.", "tests": "node scripts/check-sdui-manifest.mjs --self-test: 29 cases behave (was 22), floor 29. Ablations via scripts/ablation-replace.mjs, each restored with git diff HEAD empty: other-artefact comparison removed -> exit 1 on its row; one-line form check removed -> exit 1; unreached-record list removed -> exit 1; generatedAt carry removed -> exit 1 on the re-record row; per-package write removed -> exit 1 on both producer rows. node scripts/check-changeset-no-major.mjs --self-test: 456 assertions pass. Real-tree merge experiment with git merge-tree --write-tree: grid x form edits conflict on the single digest (exit 1), merge cleanly here (exit 0) and the merged tree passes the gate; grid x grid conflict on plugin-grid.json only. Generator timings: cold 8m49s (0/63 turbo cached), no change 6s, plugin-grid edit 33s, core edit 2m50s.", "gates": "Local on a8114011: dispatch-gates --commands derived 40 families; 39 exit 0 (incl. check-sdui-manifest both legs, check-changeset-no-major both legs, check:nul-bytes, check:watch-hint-literal, check:required-contexts, check:doc-authoring, lint check:doc-formula-expressions after the lint closure build); check:pm-dispatch-gates NOT MEASURED locally (stopped for a main re-merge), CI Lint · PM dispatch-gates success on 086ff79e. Local rerun on 086ff79e: 33 of 40 exit 0 before it was stopped for the next main re-merge (rest NOT MEASURED locally). CI on 086ff79e: Lint & Repo Gates success, TypeScript Type Check success, Test Core success (8/8), Governed Surface Queue Guard success, Lint UI / Type Check UI / Build & E2E UI / Build Core skipped by path filter, Check Changeset skipped (skip-changeset). Head now bc64912d (main merged again after #26019; check-sdui-manifest exit 0 locally); CI in_progress.", "dogfood_battery": "n/a - no ! in the PR title and Clause-②: no", "mcp_calls": "read only: issue_read x2, pull_request_read x5 (get, get_check_runs), subscribe_pr_activity x1, plus a read-only subagent (pull_request_read get/get_files over 16 ui PRs); no MCP write", "api_writes": "2 relay writes: pr_create objectstack-ai/objectstack#26120 (assignees os-elon-musk, labels skip-changeset), and this comment", "open_questions": [ { "question": "Accept the residual: two PRs on different registry packages that each leave sdui.manifest.json unchanged, where only their combination would change it, now land without a run seeing the combination (the next regeneration of any registry package sees it). A PR that changes the artefact still conflicts with every in-flight registry PR.", "options": [ "A accept it (this PR as is)", "B dependency-inclusive digests: closes it along import edges only, at 17-25 records per edit to types/core/react/components" ], "recommendation": "A, because B keeps most of today's conflicts and still leaves sibling interactions through ComponentRegistry open; the seat has put this to the maintainer" } ], "out_of_scope_findings": [ "carrier: none - i18n is the hot package (5 of the 12 open ui PRs touch its src/) and still serializes on its record; noted in the PR's Acceptance notes, not filed" ] }Generated by Claude Code
devx(sdui): one manifest record per ui registry package, so ui PRs on…