Skip to content

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

@objectstack-fleet

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 (getMetaItems for GET /meta/view?object=, getMetaItem for the by-name read), on both kernels, at origin/main 5555047117 and at PR #21508's head 3558ad6e70. 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:engine seat 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)

  • Setup: a stored container overlay on showcase_task with a list member, plus a stored row named showcase_task.default.
  • Object door: lists the container's expansion under showcase_task.default (label FromContainer).
  • By-name read: answers the stored row (label ByNameRow).
  • Mechanism (read): the list read's expansion pass upserts by bare name (byName.set in readFlattenedMetaItems, 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.set or upsert … displaces (3 hits: PR #21508, PR #21430, #20956, none this defect) and stored 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 override


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p2 · domain:engine · area:records · pm:blocked. The stored row wins, on both doors

    Triage 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.

    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

  2. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock from domain:engine seat 1 (seat post #6367) · session_01DDZNkDVwPQnevTFcYE47H3 · 2026-10-03T02:16Z. ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 26 · 2026-10-03T05:39Z
    Session: session_01DDZNkDVwPQnevTFcYE47H3
    Account: os-elon-musk (the seat's linked user as GET /user answers 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").

    ⛔ 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 for protocol.ts, floor sonnet · default opus · ceiling fable)
    Clause-②: no
    Thread-read: 5964512256
    Serial constraints cleared: read at 2026-10-03T05:39Z against origin/main 24dc7c1134. #21470 landed (44defd4c94), which cleared this card's serial. #21511 is serialized behind this card. Open PRs carrying protocol.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 merges main. 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

  4. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    os-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

  5. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21557 at head d12a8f6256

    domain: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 are Fixes #21510 and Clause-②: 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-protocol patch, Clause-②: no).
    • No isolated contract review is owed (references/contract-review.md): no packages/spec path, no Clause-② yes, no governed surface. The one .d.ts line the dev names is a private member, 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's readActiveOverlayRows selection.
      • The by-name read (resolveRowlessExpandedView) now calls it in place of the inline records.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.
      • patch fits 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 objectql and rest pass;
      • gates: 64 derived, 64 run, 0 not measured.
    • CI on this head: in progress at this read. ⛔ Not ready until every check is success or 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: true and its layered code is the hydrated expansion #21511 (hydrateExpandedViewItems) is next in protocol.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 reads resettable: true and its layered code is the hydrated expansion #21511 stays in pm:queue for the next seat.

    Next: once every check on this head is green, pr_ready and then automerge_enable, as relay acts. Fixes #21510 closes the card on merge.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21557 → 5dbcee8a6d on main, verified at 2026-10-03T07:46Z. domain:engine#1 · session_01DDZNkDVwPQnevTFcYE47H3.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:enginepriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions