Uh oh!
There was an error while loading. Please reload this page.
fix(objectql,metadata): derive a view container's object through the shared helper, so the row's own name is LAST at every SOURCE registrar (#14399) - #14665
Conversation
…shared helper, so the row's own `name` is LAST at every SOURCE registrar (#14399) The ObjectQL boot-loop registrar read a container's own `name` FIRST, while `deriveViewContainerObject` (@objectstack/metadata) and `expandRuntimeViewContainer` (@objectstack/metadata-protocol) read it LAST. A container written as `{ name: 'lead_views', object: 'crm_lead', list: { … } }` was therefore keyed `lead_views` by one SOURCE registrar and `crm_lead` by the others, expansion included — and `getViewsByObject()` / `GET /meta/view?object=` filter on the expanded items' `object`, so which registrar loaded the document decided whether the views were addressable under the object at all. The boot loop's container branch now calls `deriveViewContainerObject` by import; a fourth hand-copy of the chain was the defect, not the repair. `deriveViewContainerObject` moves onto `@objectstack/metadata`'s root entry to make that import legal without reaching into another package's `src/`. Only the CONTAINER branch moves, gated on `isAggregatedViewContainer`; the assembled `viewItems:` channel still keys by its own `name` first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
…st view-container sources are divergent) (#14399) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
…ew-container-name-order
… shift (#14399) Generated by `node scripts/check-system-context-census.mjs --fix` on the merged tree; 15 anchors rewritten, never hand-edited. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
…ew-container-name-order
📓 Docs Drift CheckThis PR changes 2 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 24 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin dc744ff995c04d7c29856663afb483fcf15c20f7 && git checkout dc744ff995c04d7c29856663afb483fcf15c20f7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2aa8456cf2d66ec3825d262686fe4218e57cfd27 a95087993bc3ec03604b95e0596b29250457170c && git checkout -B drift-repro 2aa8456cf2d66ec3825d262686fe4218e57cfd27 && git merge --no-ff a95087993bc3ec03604b95e0596b29250457170c
node scripts/docs-audit/affected-docs.mjs --json 2aa8456cf2d66ec3825d262686fe4218e57cfd27
|
os-musk
commented
Sep 2, 2026
Landing provenance (engine execution seat, session
Owed by the seat at MERGED: verify by content on Generated by Claude Code |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 33669892536 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 33671791799 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#14399
What was wrong
Three sites derive "which object does an aggregated
defineViewcontainer bind to". After #13407 / #13913 / #13912 all three read the container's own top-levelobjectbefore thelist.data.objectchain, but they still disagreed about the row's ownname:namepackages/objectql/src/engine.tsresolveMetadataItemName('views', item)— the boot-loop SOURCE registrarnamethenidthenobjectthenlist.data.objectthenform.data.objectderiveViewContainerObject(@objectstack/metadata) — the artifact/HMR SOURCE registrar andgetViewsByObject()objectthenlist.data.objectthenform.data.objectthennameexpandRuntimeViewContainer(@objectstack/metadata-protocol) — the runtime doorA container written as
{ name: 'lead_views', object: 'crm_lead', list: { … } }was therefore keyedlead_viewsby the boot loop andcrm_leadeverywhere else, expansion (OBJECT.KEYitems) included.getViewsByObject()andGET /meta/view?object=filter the expanded items by theirobject, so which registrar loaded the document decided whether the views were addressable under the object at all. No error, no diagnostic.The change
The boot loop's CONTAINER branch now calls
deriveViewContainerObject— by import, not by re-spelling. A fourth hand-copy of a chain that already existed three times was the defect, not the repair; the tail of the same function now calls the helper too, soengine.tsholds zero copies of the chain where it used to hold one.The direction is the 2026-08-07 meta-rule rather than taste: one operation with two inconsistent implementations, the side bound by a DECLARATION wins.
ViewSchema.object's own.describe()names its readers; the boot loop's order argued from item identity, which declares nothing about the binding. The two sites that already held the winning order are untouched.deriveViewContainerObjectwas module-local, so it is added to@objectstack/metadata's root entry (minorfor that package). Measured on this tree:packages/objectql/package.jsondeclares@objectstack/metadataasworkspace:*, and@objectstack/metadata's full transitive dependency closure is 6 packages with@objectstack/objectqlnot among them — no cycle, and no deep relative import into another package'ssrc/.Confinement. Only the container branch moves, gated on
isAggregatedViewContainer. The assembledviewItems:channel still keys by its ownnamefirst — every member ofAssembledViewArtifactSchemarequiresviewKind, soisAggregatedViewContaineris false for all of them, and there is a control asserting exactly that.item.idis untouched and cannot fire for a container:ViewSchemais astrictObjectdeclaringnameandobjectand noid.Bounded in-place fix, named per the standing clause: the docblock above
resolveMetadataItemNameasserted "per spec,ViewSchemadoes NOT have a top-levelnamefield", which was the stated premise for readingnamefirst. It is measurably false —ViewSchemadeclares an optionalnameatview.zod.ts— so the docblock now records what the field actually is and what the old sentence got wrong. Same defect class, same file, mechanically settled by the spec source, no new verification surface.MEASURED CORRECTION to the card
The card predicted the artifact/HMR registrar would "derive
crm_leadand mintcrm_lead.default, registering the container undercrm_lead" — two SILENT keys for one document. The second half is false, and the divergence is sharper than filed:lead_views, silently;crm_leadcorrectly and then refuses the whole artifact load, loudly —assertMetadataRegisterContract(MetadataFacadeanswers threeregister→getround-trip cases differently from every other shippedIMetadataService#7378 row 1),VALIDATION_ERROR/ 400 — because the document's owndata.namestill readslead_views.The boot loop reconciles that field (
toRegister = { ...item, name: itemName }) and the artifact door does not. That residual asymmetry is a separate defect at a separate site; #14399 owns the derivation, and the asymmetry is filed on its own card and pinned as an assertion here so it cannot drift unnoticed.The fixture is the deliverable
No fixture anywhere in the repo set a container
namethat differed from its bound object, so the divergence was un-rehearsed in BOTH directions — each registrar was individually green on every shape it had ever been shown, which is also why the correction above had gone unnoticed.packages/objectql/src/view-container-divergent-name-registrars.test.tsdrives both SOURCE registrars side by side on one document: the boot loop throughObjectQL.registerApp, the artifact door throughMetadataPlugin._parseAndRegisterArtifact, exactly as its own #13912 pin drives it. Four controls (name equals object; noobjectanywhere; the anonymous container both registrars accept; a standalone ViewItem) are green in both directions.Verification
Head at the time of the runs below:
a95087993.Reverse verification (ablation), direction predicted before running — the boot-loop legs go red, the artifact-door derivation leg stays green because it does not read
engine.ts:37ab4cb6to775ef0b1;./engine, a relative SOURCE specifier, so vitest reads the mutated file directly; the helper it calls comes from@objectstack/metadata'sdist, which the mutation does not touch;expected [ 'lead_views', …(2) ] to deeply equal [ Array(3) ]andexpected [ 'lead_views' ] to deeply equal [ 'crm_lead' ]. The "artifact/HMR registrar derives the SAME binding" case stayed green, as predicted;EXIT INT TERMtrap with absolute paths, proved by blob equality (37ab4cb6equals theHEADblob),git diff HEADempty, marker count 0.Suites and gates, all on
a95087993:pnpm --filter @objectstack/objectql exec vitest run— 264 files / 4556 tests passedpnpm --filter @objectstack/metadata exec vitest run— 43 files / 677 tests passedpnpm --filter @objectstack/objectql typecheck—check:test-typecheck: OK ... 44 file(s) / 242 error(s) / 69 pinned signature(s)(unmoved; the new test file compiles clean and adds no debt)pnpm lint(whole repo,eslint . --no-inline-config) — exit 0, no narrowing claimedpnpm check:nul-bytes—OK (scanned 7997 text file(s) ... no raw ASCII control bytes);pnpm check:error-status-conformance— exit 0node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(62 commands): 57 exit 0, includingcheck-system-context-census,check-cross-package-test-inputs,check:test-source-alias,check:published-files,check:type-check-coverage,check:type-source-resolution,check:undeclared-dep-imports,check:engine-split-ratiocheck-test-completenessandcheck:type-check-debtandcheck:dual-build-cjs-loads(exit 3 — each needs a saved turbo test log or a built package closure),check:pm/half-states(exit 3 — repo-scoped GitHub REST answers 403 in this container), andcheck:skill-examples(exit 1 on its prerequisite branch:packages/client-react/dist holds no .d.ts declarations — the package is not built). Building the closure to measure them was attempted twice and killed by this container's 10-minute foreground cap both times, so the narrowing is declared rather than hidden; CI builds the closure and runs all five.content/docs/permissions/system-context.mdxwas re-anchored bynode scripts/check-system-context-census.mjs --fixon the merged tree (15 anchors rewritten), never hand-edited; the gate is green on the final head.Migration
None. Measured on this tree: of the 54 non-test sources that author or carry view containers, zero declare a
namethat differs from the object they bind to, so every in-tree container derived identically at all three sites before this change and does after it. What moves is the latent shape only.Clause-2
yes — self-read from the final diff, against the seat's
no. The content limb is hit:packages/metadata/src/index.tsadds a new export to the package's public entry, which widens the published surface (theminorin the changeset is the same fact). No path-limb file is touched — the diff does not enterpackages/spec/src/**.needs:contract-reviewis hung on this PR accordingly.🤖 Generated with Claude Code
https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
Generated by Claude Code