Repository navigation
finding(metadata-protocol): a stored view row named exactly like a container expansion is shadowed in the object door by the expansion, while the by-name read answers the stored row #21510
Description
Activity
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·priority:p2·domain:engine·area:records·pm:blocked. The stored row wins, on both doorsTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-03T01:55Z. ⛔ Not a claim, ⛔ not a dispatch.Why p2. Two read doors answer differently for one name, and the input is reachable through a public write, since the save door accepts a write by an expanded name. If an edit is saved by its expanded name, the object door (the view switcher) keeps showing the container's version.
Ruling: the stored row wins on both doors.
- ADR-0005 overlays are name-keyed. A row stored under exactly that name is the sanctioned override for it. An expansion is derived from its container, so it fills only names that have no row of their own.
- This is the rule metadata-protocol: a view that a runtime view container expands is listed by GET /meta/view?object= but answers nothing by name on an environment-scoped kernel or for an org-scoped container — the by-name read expands no container #21442 (PR fix(metadata-protocol): a view a stored container expands answers by name what the object door lists, on every kernel and container scope (#21442) #21508) already gives the by-name read: a row-less expanded name answers the expansion, and a name with its own row answers that row.
- The object door's list read (
readFlattenedMetaItems,packages/metadata-protocol/src/protocol.ts) adopts it. The expansion pass never displaces a stored row of the same name. ⛔ Not a second rule: one predicate, read by both doors. - That keeps metadata: a view container with a bare list on another package's object silently replaces that object's packaged default view on GET /meta/view?object= — while the by-name read still serves the original #21334's ruling ("the object door and the by-name read answer the same row").
Pin: the dev's setup, where both doors answer the stored row, plus a row-less expanded name as the control.
Why blocked: it changes the same list-read region that PR #21508 changes for #21442. It is ruled against the merged shape.
Blocked-by: #21442
Generated by Claude Code
- addedarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Oct 3, 2026 objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsUnlock from
domain:engineseat 1 (seat post #6367) ·session_01DDZNkDVwPQnevTFcYE47H3· 2026-10-03T02:16Z. ⛔ Not a claim.- Blocker closed:
Blocked-by: #21442. metadata-protocol: a view that a runtime view container expands is listed by GET /meta/view?object= but answers nothing by name on an environment-scoped kernel or for an org-scoped container — the by-name read expands no container #21442 closedcompletedwhen PR fix(metadata-protocol): a view a stored container expands answers by name what the object door lists, on every kernel and container scope (#21442) #21508 landed ase9dec3dabonmain, and the shape this card is ruled against is onmain. - State:
pm:blocked→pm:queuein this act. Triage's ruling on this card is unchanged. - Serial, not parallel: finding(metadata-protocol): the runtime save door accepts any metadata body whose
namediffers from its row name, and registers it under the body name (the every-type half of #21412) #21470 (the save door, every type), finding(metadata-protocol): a stored view row named exactly like a container expansion is shadowed in the object door by the expansion, while the by-name read answers the stored row #21510 (the list read's expansion upsert) and finding(metadata-protocol): on an unscoped kernel a stored container's hydrated expansions carry no tenant marker, so an expanded view readsresettable: trueand its layeredcodeis the hydrated expansion #21511 (hydrateExpandedViewItems' tenant marker) all editpackages/metadata-protocol/src/protocol.ts. Fold or serial: serial. The fold's first gate fails, because they are three mechanisms with three fixes. The order is the lane's: same grade, then card age, so finding(metadata-protocol): the runtime save door accepts any metadata body whosenamediffers from its row name, and registers it under the body name (the every-type half of #21412) #21470, then finding(metadata-protocol): a stored view row named exactly like a container expansion is shadowed in the object door by the expansion, while the by-name read answers the stored row #21510, then finding(metadata-protocol): on an unscoped kernel a stored container's hydrated expansions carry no tenant marker, so an expanded view readsresettable: trueand its layeredcodeis the hydrated expansion #21511. Each is claimed after the one before it lands, against the merged file.
Generated by Claude Code
- Blocker closed:
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsClaim: PM loop round 26 · 2026-10-03T05:39Z
Session:session_01DDZNkDVwPQnevTFcYE47H3
Account:os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21510-stored-row-wins-list
Worktree:objectstack-issue-21510
Domain:domain:engine
Seat:domain:engine#1(seat post #6367)
File surface: per triage 5964342087 ("the stored row wins on both doors").- The list read:
readFlattenedMetaItemsinpackages/metadata-protocol/src/protocol.ts(about:8127atorigin/main24dc7c1134). Its expansion pass (byName.set, about:8358and:8361) never displaces a stored row of the same name. It reads the one predicate the by-name read already applies since metadata-protocol: a view that a runtime view container expands is listed by GET /meta/view?object= but answers nothing by name on an environment-scoped kernel or for an org-scoped container — the by-name read expands no container #21442 (PR fix(metadata-protocol): a view a stored container expands answers by name what the object door lists, on every kernel and container scope (#21442) #21508): a row-less expanded name answers the expansion, and a name with its own row answers that row. - ⛔ Not a second rule: if that predicate is not one callable today, the dev factors it out of the by-name read's selection (fix(metadata-protocol): a view a stored container expands answers by name what the object door lists, on every kernel and container scope (#21442) #21508's
readActiveOverlayRows/storedOverlayEntries/expandStoredViewContainersregion) and both doors call it. That region joins the surface only for that move. - The pins' test files in
metadata-protocol: the dev's setup, where both doors answer the stored row, and a row-less expanded name as the control.
⛔ Not
hydrateExpandedViewItems(#21511, next in serial), ⛔ not the save door, ⛔ not PR #21545's existence-gate region (about:10237). Stop on breach; explain in the report.
Container & model:B(a bounded fix),mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate forprotocol.ts, floor sonnet · default opus · ceiling fable)
Clause-②: no
Thread-read: 5964512256
Serial constraints cleared: read at 2026-10-03T05:39Z againstorigin/main24dc7c1134. #21470 landed (44defd4c94), which cleared this card's serial. #21511 is serialized behind this card. Open PRs carryingprotocol.ts: #21545 (#21516, this seat, the data door's existence gate about:10237) and #21473 (#21468,domain:services, the save path and draft promotion). Neither touches the list read. Whichever lands second mergesmain.Clause-②: no: the list read changes which of two existing rows it answers for one name, the by-name read's rule; no accept set or published surface moves.
Generated by Claude Code
- The list read:
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 21510, "status": "done", "branch": "claude/issue-21510-stored-row-wins-list", "pr": "https://github.com/objectstack-ai/objectstack/pull/21557", "session": "session_01DDZNkDVwPQnevTFcYE47H3 (mode:subagent, the PM's session; the newest Claim: on the card, 5966031600, names this branch)", "premise_still_valid": true, "summary": "Implemented triage's ruling 5964342087 (the stored row wins on both doors). The premise was measured first at BASE 24dc7c1134 with the dev's setup: a stored overlay of showcase_task's own container (list 'FromContainer', listViews.in_progress) plus a stored row showcase_task.default ('ByNameRow') written through the save door. On both kernels, both scopes and both write orders, the object door listed 'FromContainer' and the by-name read answered 'ByNameRow'. PM assumption 1 held: in readFlattenedMetaItems the first byName.set seats the merged items (registry plus stored rows), and the second byName.set is the expansion pass, which ran after it over every name. Assumption 2: the one predicate was the inline test records.some(record?.name === request.name) in resolveRowlessExpandedView. It is factored out unchanged as the private namesWithOwnStoredRow(records), a Set of the row names in the caller's readActiveOverlayRows selection. The by-name read asks it in place of the inline test (same answer for every input), and the list read asks it over its own records, skipping an expansion whose name has a stored row of its own. A row-less expanded name still replaces a packaged view of that name. Assumptions 3 and 4 held: both kernels are pinned; the save door accepts the write by the expanded name (body name equals the save name) and stores the row in every cell. Edge reported, not fixed (surface): a container saved under one of its own expanded names (showcase_task.default) is now listed under no name on the object door, where before it listed its self-expansion. The by-name read answers the raw container before and after; see out_of_scope_findings[0] and open_questions[0].", "tests": "(1) Premise at BASE 24dc7c1134, through a throwaway test that was deleted: 8 cells (2 kernels x 2 scopes x 2 write orders) gave object door 'FromContainer' / by-name 'ByNameRow'; the control showcase_task.in_progress was the expansion on both doors. The new pins before the fix: 10 failed, expected 'FromContainer' to be 'ByNameRow'. (2) Builds: os-verify-lock.sh -c \"pnpm exec turbo run build --filter='@objectstack/metadata-protocol^...' --concurrency=2\" gives VERDICT command-exit 0 (12 tasks). Later --filter='@objectstack/metadata-protocol...' --filter='@objectstack/objectql^...' --filter='@objectstack/rest^...' gives VERDICT command-exit 0 (24 tasks); dist/index.js carries namesWithOwnStoredRow. (3) At 6a41000f1e: pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2 gives Test Files 206 passed | 3 skipped (209), Tests 3173 passed | 19 skipped (3192), VERDICT command-exit 0. typecheck (tsc --noEmit) gives VERDICT command-exit 0, and tsc --listFiles includes view-container-runtime-expansion.test.ts. 10 new pins, file 119 to 129. (4) Reverse verification from committed 6a41000f1e through scripts/ablation-replace.mjs in a script with an EXIT/INT/TERM trap. Each restore was proven blob f1622d5bdde2 == HEAD with git diff HEAD empty, and the final git status was clean. The subject resolves via relative ./index.js, so no dist leg applies. A1, the list read's call off: 10 failed / 119 passed. A2, the predicate body off: 10 failed / 119 passed. A3, only the by-name call off: 8 failed / 121 passed (history 'every event names the row'). Direction red, as predicted. (5) Downstream sample against the rebuilt dist: objectql 5 files / 71 tests and rest 1 file / 24 tests (the tests that read the view object door through the real protocol), all pass. The rest is CI's. (6) Gates: dispatch-gates --commands --repo objectstack-ai/objectstack at final head d12a8f6256 derived 64 families: the PM lead's 56 plus 8 from the changeset (check-adr-0087-registration x2, check-empty-changeset x2, release-rehearsal-clone --self-test, release-pending-publish --self-test, check:objectui-changeset, check:pm-changeset-deadline-census). All 64 exited 0 after the final commit; --ran gives 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN. In the first union run at 6a41000f1e, check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET, 44 entry points with no dist); at d12a8f6256 the dists were present and it measured 106 entry points / 66 packages. (7) Lint, narrowed: eslint --no-inline-config --format json over the 2 changed .ts files gives 2 files, 0 errors, 0 warnings. There is no type-aware linting (eslint.config.mjs:328), so untouched files' verdicts cannot move; repo-wide pnpm lint is CI's. (8) d12a8f6256 changes only the changeset, so the readings in (3)-(5) cover identical protocol.ts and test bytes. (9) CI on #21557: in_progress at report time (one read: 10 completed, success or skipped; 20 in_progress).", "mcp_calls": "0", "api_writes": "3 REST writes, each one repository_dispatch to the fleet-write relay, executed as objectstack-fleet[bot]: (1) pr_create gives POST /repos/objectstack-ai/objectstack/pulls (draft #21557; read-back 9958 bytes sent = stored) via scripts/pm/fleet-write/dispatch.mjs; (2) label-write gives POST /repos/objectstack-ai/objectstack/issues/21557/assignees (os-elon-musk; read-back matches) via scripts/pm/label-write.mjs; (3) this os-dev-report gives POST /repos/objectstack-ai/objectstack/issues/21510/comments via scripts/pm/post-stamped.mjs. git push is not REST. Reads were REST GETs only (card, comments, #21442 thread, #21334 ruling comment, PR #21508, PR #21557 state and check runs).", "gates": "64 derived at d12a8f6256; 64 exit 0; 0 NOT MEASURED; CI in_progress at report time", "files_changed": [ ".changeset/21510-list-read-stored-row-wins.md", "packages/metadata-protocol/src/protocol.ts", "packages/metadata-protocol/src/view-container-runtime-expansion.test.ts" ], "line_budget": "n/a: no skills/** or governed surface touched; diff +176 / -2 over 3 files vs BASE 24dc7c1134", "deviations": [ "Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude). The harness reminder asked for a model-named Co-Authored-By, which the pre-push hook refuses. The PR body footer is AGENTS.md's session-URL form, not the harness's robot-emoji form.", "origin/main moved to 72af58c621 during the run (2 commits; one adds packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts and neither touches protocol.ts). No merge was made; there is no textual or region overlap, and CI plus the queue validate the merge." ], "open_questions": [ { "question": "The ruling's predicate, applied literally, gives this. A container saved under one of its own expanded names (the save door accepts {name: 'showcase_task.default', object: 'showcase_task', list}) is its own stored row for that name, so its expansion no longer fills it. The object door, which never enumerates a container (ADR-0017 canonical shape), now lists nothing under showcase_task.default. Before, it listed the self-expansion. The by-name read answers the raw container before and after. Is that outcome accepted for this PR?", "options": [ "A: accept it as ruled (the name behaves like every container's own name), and have the seat file the save-door half: refuse a container whose save name is one of its own expanded names", "B: exempt a row from the predicate when it is the very container being expanded (keeps the pre-PR listing, but the doors still disagree, and it is a second test of 'has its own row', which the ruling forbids)" ], "recommendation": "A, because it keeps one predicate (the ruling's explicit bar). B restores a listing whose by-name answer still disagrees, so it fixes nothing on the contract (#21334's same-row ruling), and it widens the place an AI can author a container under a ViewItem name. The real defect is the save door accepting that name, which is outside this card's surface." } ], "out_of_scope_findings": [ "class: c · reach: the save door's method, in-process on both kernels: saveMetaItem({type:'view', name:'showcase_task.default', item:{name:'showcase_task.default', object:'showcase_task', list:{label:'SelfNamed', type:'grid', columns:[{field:'title'}]}}}) is accepted (the same method PUT /api/v1/meta/view/NAME calls); not measured over REST · evidence: the container's bare list expands to its own save name. After PR #21557 the object door lists nothing for showcase_task.default (the container row is the name's own row and containers are never enumerated); before (the list read's call off = origin/main) it listed the self-expansion 'SelfNamed'. getMetaItem answers the raw container (isAggregatedViewContainer true) before and after, so no door answers a ViewItem for that name, and nothing tells the author why. Producer: the runtime save door. Seam: spec:view container contract (ADR-0017 §3.2, the container is named after its object) → runtime:saveMetaItem view save door | readFlattenedMetaItems canonical-shape filter · dedupe words: container saved under own expansion name · self-named view container object door · container name equals expanded view name · save door container named like its member", "carrier: PR #21557 Acceptance notes · noted, not filed — dist/index.d.ts gains one private member line (private namesWithOwnStoredRow;); no public member or exported type changes" ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsACCEPT — PR #21557 at head
d12a8f6256domain:engine#1·session_01DDZNkDVwPQnevTFcYE47H3· read at 2026-10-03T06:57Z. Judged against GitHub; the os-dev report is 5966513829.- Shape: draft, base
main. The first body lines areFixes #21510andClause-②: no; no other card number stands beside a closing keyword.check-governed-merges.mjs --pr 21557: NOT governed; 174 changed lines (+172/-2). There are 3 files:protocol.ts,view-container-runtime-expansion.test.ts(+10 pins), and the changeset (metadata-protocolpatch,Clause-②: no). - No isolated contract review is owed (
references/contract-review.md): nopackages/specpath, noClause-② yes, no governed surface. The one.d.tsline the dev names is aprivatemember, which is no published accept set. - The seat's own read of the diff: one predicate, triage 5964342087's "⛔ Not a second rule".
namesWithOwnStoredRow(records)is the set of row names in the caller'sreadActiveOverlayRowsselection.- The by-name read (
resolveRowlessExpandedView) now calls it in place of the inlinerecords.some(...)test it replaces, so its answer is unchanged. - The list read's expansion pass skips any name in that set.
- A registry or package item of a name is still replaced by the expansion, as before.
- The changeset prose, checked sentence by sentence against the diff:
- "What changed": the object door lists the stored row where an expansion shares its name.
- "The rule": one test, over each caller's own selection.
- "A container stored under one of its own expanded names": the edge, stated plainly.
- "What does not change": row-less expanded names, the packaged-view replacement, the save door, no response key.
patchfits a read fix with no surface change.
- The dev's open question → A, accepted as ruled. Under one predicate, a container stored under one of its own expanded names is that name's own row. The object door never lists a container, so it lists nothing there, and the by-name read answers the container as before. B, exempting the container's own row, is a second own-row test, which the ruling rules out. The save-door side is filed as finding(metadata-protocol): the runtime save door accepts a view container saved under one of its own expanded names, and after #21510's rule no door answers a view item for that name #21558 for triage.
- Verification (dev):
- the premise at the base: 8 of 8 cells disagreed (2 kernels × 2 scopes × 2 write orders);
- the new pins: 10 of 10 red before the fix;
metadata-protocol: 206 files / 3173 tests; typecheck exits 0;- ablations A1 to A3 each went red as predicted, and every restore was proven;
- downstream
objectqlandrestpass; - gates: 64 derived, 64 run, 0 not measured.
- CI on this head: in progress at this read. ⛔ Not ready until every check is
successor a roster skip. - Unlocks: finding(metadata-protocol): on an unscoped kernel a stored container's hydrated expansions carry no tenant marker, so an expanded view reads
resettable: trueand its layeredcodeis the hydrated expansion #21511 (hydrateExpandedViewItems) is next inprotocol.ts's serial. This seat is going off shift on the maintainer's order, so finding(metadata-protocol): on an unscoped kernel a stored container's hydrated expansions carry no tenant marker, so an expanded view readsresettable: trueand its layeredcodeis the hydrated expansion #21511 stays inpm:queuefor the next seat.
Next: once every check on this head is green,
pr_readyand thenautomerge_enable, as relay acts.Fixes #21510closes the card on merge.
Generated by Claude Code
- Shape: draft, base
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsLanded: PR #21557 →
5dbcee8a6donmain, verified at 2026-10-03T07:46Z.domain:engine#1·session_01DDZNkDVwPQnevTFcYE47H3.- The squash is on
origin/main, with one parent (f83d0669d7). Its diffstat matches the PR: 3 files, +172/-2. - On
origin/main,protocol.tscarries the one predicatenamesWithOwnStoredRow, called by both the list read and the by-name read. - The card closed
completedthroughFixes #21510. The PR body names no other card beside a closing keyword, so nothing else was closed.pm:dispatchedis removed in the same act. - Unlocked by this landing: finding(metadata-protocol): on an unscoped kernel a stored container's hydrated expansions carry no tenant marker, so an expanded view reads
resettable: trueand its layeredcodeis the hydrated expansion #21511 (hydrateExpandedViewItems), next inprotocol.ts's serial. It stays inpm:queuefor the next seat; this seat is going off shift on the maintainer's order. - Filed from this card: finding(metadata-protocol): the runtime save door accepts a view container saved under one of its own expanded names, and after #21510's rule no door answers a view item for that name #21558, on whether the save door accepts a container saved under one of its own expanded names. It is for triage.
Generated by Claude Code
- The squash is on
- added 6 commits that reference this issue
on Oct 7, 2026
Filing gate: ① a defect, class (b): two read doors disagree against a declared contract.
reach:measured in-process at the protocol methods the REST doors call (getMetaItemsforGET /meta/view?object=,getMetaItemfor the by-name read), on both kernels, atorigin/main5555047117and at PR #21508's head3558ad6e70. The input is reachable through a public write: the runtime save door ACCEPTS a write by an expanded view name (probe recorded in PR #21508). It was not measured over REST.Filed by
domain:engineseat 1 (seat post #6367,session_01DDZNkDVwPQnevTFcYE47H3), from #21442's os-dev report 5964086824 (out_of_scope_findings[0]). Reader who acts: triage grades and routes; which row wins is a ruling. ⛔ Not a claim.Measured (the dev's reading)
showcase_taskwith a list member, plus a stored row namedshowcase_task.default.showcase_task.default(labelFromContainer).ByNameRow).byName.setinreadFlattenedMetaItems,packages/metadata-protocol/src/protocol.ts) over every item, a stored row of that exact name included.Governing text
What triage decides (⛔ not a ruling)
Which one answers for that name on both doors: the stored row (a sanctioned name-keyed override) or the container's expansion. Then one rule, applied by both doors.
Relation
Dedupe
REST list of the 1,000 most recently updated issues and PRs here (down to #2714), grepped for
byName.setorupsert … displaces(3 hits: PR #21508, PR #21430, #20956, none this defect) andstored row … same name(3: PR #21508, #21377, #20913, none this defect). The control,view container, hits 17. The search endpoint was refused by the egress proxy in this session, so this is the list-and-grep fallback.Dedupe words:
expansion upsert displaces stored row·object door by-name disagree same name·write by expanded view name·container expansion over stored overrideGenerated by Claude Code