Skip to content

fix(metadata-protocol): the save door refuses a view container saved under a name another stored container of the same object expands to (#21620) - #21637

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-21620-container-named-after-object
Oct 3, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-21620-container-named-after-object

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Fixes #21620
Clause-②: no (narrowing)

The runtime save door's accept set narrows: a view container saved under a name that another stored container of the same object expands to is refused with VALIDATION_ERROR / 400. Nothing widens. Dispatched by domain:engine seat 1 under claim 5973165628 (branch claude/issue-21620-container-named-after-object).

What changed

saveMetaItem (packages/metadata-protocol/src/protocol.ts), the method behind PUT /api/v1/meta/view/NAME and the dispatcher's metadata save, gains one check: containerSiblingExpansionNameRefusal. It runs right after #21558's containerOwnExpansionNameRefusal and before the view identity stamp (normalizeViewMetadata). This is triage's pre-named fallback, the narrower check, not the broad one. The census below hit, so the broad check ("a container's name must equal its object's") was not written.

  • The predicate: "another stored container of the same object expands to the save name". It is answered by the readers' own pieces, never a copy of them:
    • Rows: the active view rows that readActiveOverlayRows selects for this caller, through the readers' gate organizationIdForMetaRead and with no package filter. That is environment-wide rows plus the caller's organization's.
    • Parse and expansion: each row is parsed by storedOverlayEntries and expanded by expandStoredViewContainers with its own package binding. So every member kind (a bare or named list, listViews, form, formViews), the expander's de-duplication, and 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 own-name arm on another package's object are judged where the readers place them.
    • "Another": the row stored under the save name itself is left out, because it is the row this save replaces.
    • "Of the same object": the expanded view's object against deriveViewContainerObject of the body. That is the one derivation the source registrars file a container under, newly imported from @objectstack/metadata/view-container.
  • The body judged is the authored one, with the door's own name stamp applied first (H3), before the identity patch. On an unscoped kernel the sibling's expansion is registered under the name, and a form-only container would otherwise take its viewKind and reach the schema as a malformed view item.
  • The envelope: VALIDATION_ERROR / 400, the same as the two name checks beside it. No new code, and the error-code ledger is untouched.
  • The words: "Invalid view container: it is saved under 'crm_lead.pipeline', which is a name the stored container 'crm_lead' expands (its list view on 'crm_lead'). An expanded view fills only a name that has no stored row of its own, and this container would be that row, so that view would no longer be served and no read would answer a view under 'crm_lead.pipeline'. Add the view as a member of the container 'crm_lead' (its list, listViews, form or formViews), or save a view item (name, object, viewKind and config) under 'crm_lead.pipeline'." The prescription names the stored container that expands the name: add the view as a member of it, or save a view item under the name. It never prescribes a save under a name another stored container holds (patch round 1, REWORK 5973617844). The text carries no tracker number.
  • The read doors are not changed, and no stored row is re-saved. File surface as claimed: saveMetaItem's view save door only.

The census, taken first (the ruling's stop condition)

The ruling, as the seat reads it: if any writer or packaged container names a container other than after its object, do not write the broad check; record the hit; implement the narrower check. The census hit, decidably.

Readings at BASE 045b946256, with objectui at its pin 89cad75d55:

Census input Evidence Names a container other than after its object?
The platform checklist's live view-authoring item, studio-authoring.view-authoring-live (P1, active, revision 2 of 2026-10-02) Step 1 is PUT /api/v1/meta/view/qa_repair_asset_views?mode=draft with { object: 'repair_asset', list, form }, on a runtime-authored object. Yes. It is the documented live authoring path.
#13407, found on a live EE deployment (QA-source #13404, the same item) PUT /api/v1/meta/view/NAME of a container bound to note under another name. #13407's repair taught the readers the container's own object so that this shape serves. Yes, a live writer.
#21334, steps 3 and 8 (the 17.6.0 run in #21330, the same item) os_qa_shadow_probe saved on showcase_task. Ruling 5946423948 allowed "expands under the container's own name". Seat answer 5955628428 rejected refusal at save because it "blocks a legitimate 'add views to a shipped object' path". PR #21430 pins it (46 cases plus a REST dogfood pin). Yes, and a ruled arm.
#21412's seat answer 5961930912 It rejected judging the save door against the container's binding because that "refuses P2b, the body the door itself stores for the #13407 shape". The save door's own comment at BASE: "this door keeps a container saved under a name other than its object (#13407, #21334)". Pinned as P2 and P2b here and in packages/metadata/src/view-container-name.test.ts. Yes, a ruled arm.
Studio (objectui at its pin) Creators write view items: ObjectView through buildViewConfigSaveBody and viewEnvelope, ObjectDataPage through createRuntimeMetadata, the metadata-admin createBuildBody, and the flat configs of data-objectstack setViewConfig and createView. Re-savers: the metadata-admin ResourceEditPage saves a body under the name it carries, and data-objectstack updateView's draft path merges onto the stored draft without reducing a container to its list. Both re-save a container stored under a non-object name, under that name. No creator. Both re-savers would be refused by the broad check.
The in-repo AI author The MCP tools in packages/mcp/src/mcp-http-tools.ts are object, record and action tools: no metadata write. The published skills/objectstack-ui teaches defineView containers in source, with no top-level name. No. The cloud AI author is outside this repository: NOT MEASURED. The census is decided by the rows above either way.
Packaged containers (H4) An AST scan finds 13 defineView( call sites with an object-literal argument, outside packages/spec/src and tests, and 0 with a top-level name. The source registrars refuse a set name that disagrees with the derived object: the boot loop (engine.ts:7024), the artifact/HMR loader (plugin.ts:1192) and os validate (view-container-names.ts:101). No. H4 holds.
Stored rows The example apps seed no sys_metadata view rows; the one sys_metadata mention is a comment in the showcase connectors. Hosted tenants: NOT MEASURED. No seeded rows. Rows of the legitimate "named other than its object" shape exist wherever the item above ran.

H2, measured: what the broad check would have broken. A throwaway, trap-guarded probe planted the broad predicate at the save door at BASE, ran the full metadata-protocol suite, and was restored (blob 8a8053c40c94 equal to HEAD, git diff HEAD empty). It gave 127 of 3342 tests red, every one carrying the probe's own message:

PR #21430's REST dogfood pin was not run under the probe (NOT MEASURED). With this PR it passes, 4 of 4. The pins that encode a ruled arm (#13407, #21334, #21412) are evidence for the stop condition, alongside the writers above. The probe's first attempt was refused by ablation-replace because its replacement re-contained the anchor. Nothing ran, the tool restored, and the probe was re-spelled.

Every accept-set change at saveMetaItem, type view

Input Before After
A container saved under a name that another active stored container of the same object, in the caller's selection, expands to. Publish and draft mode, both scopes, both kernels. Accepted: stored, and registered on an unscoped kernel. The sibling's view under that name was then served by neither door. Refused VALIDATION_ERROR / 400. Nothing is stored or registered.
The same container with no body name (the door stamps the save name). Accepted. Refused, with the same envelope.
A form-only container under a sibling's form-expanded name, on an unscoped kernel with an environment-wide sibling (the registry holds the sibling's expanded item under the name). Refused INVALID_METADATA / 422: the identity stamp copied that item's viewKind onto the body. Refused VALIDATION_ERROR / 400 by this check, which now runs first.
Everything else. Unchanged. Unchanged.

What still saves (pinned on both kernels and in both scopes):

Rows already stored in this shape keep their bytes and read as they do today. Measured with the check ablated, on both kernels and in both scopes:

  • the object door lists nothing under the name;
  • the by-name read answers the raw second container;
  • the second container's own expansion takes crm_lead.default on both doors.

A new save of such a row is refused, and delete stays open. migrateStoredMetadata and duplicatePackage re-save stored rows through this door inside a try whose catch records the row: outcome: 'failed' with the refusal's text, or a failed[] entry. So they report such a row and never re-save it (H5, by construction: the check is not gated on source or writeFace).

The PM's mechanism hypotheses

The foreseen follow-up: a container saved under the name of a view item a package ships

This was measured in-process on both kernels with a throwaway test, deleted afterwards. It is a different mechanism, so it is reported to the seat as a finding and not changed here. The save was { name: 'showcase_task.in_progress', object: 'showcase_task', list } as showcase_task.in_progress: package-less and environment-wide, package-less and organization-scoped, and in a writable package.

  • Accepted before and after this change. Afterwards the object door lists nothing under showcase_task.in_progress, and the by-name read answers the raw container. showcase_task.form reads the same. showcase_task.default reads the same from a writable package; package-less, 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's check refuses it.
  • Package-less, it also replaces the packaged default. The row's package is read from the artifact of the same name, here a shipped view item, so the container is not judged cross-package. Its bare list then expands to showcase_task.default and replaces the packaged default on both doors, stamped _packageId: com.example.showcase.
  • Why it is a different mechanism: the displaced view is a packaged artifact, not a stored container's expansion, so this check, which reads stored rows, cannot see it. The paths are the readers' name-keyed overlay of a shipped item by a stored container row, and the package attribution in runtimeViewContainerPackage.

Tests

  • Premise, measured at the fix's own pins with the new throw ablated (the measurement half of the reverse verification below), and confirmed by the throwaway door probe on both kernels and both scopes:

    • the card's pair is accepted;
    • the object door answers nothing for crm_lead.pipeline;
    • the by-name read answers the raw container;
    • crm_lead.default answers the second container's list on both doors.

    With the fix: refused VALIDATION_ERROR / 400, and both doors answer the first container's views.

  • New pins: 50, in a #21620 block in view-container-runtime-expansion.test.ts, inside 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 faithful-registry harness.

    • Per kernel (env_local and unscoped) and per scope (environment-wide and organization-scoped):
    • Per kernel: an environment-wide sibling refuses an organization caller's save, and a control where another organization's container is not this caller's sibling.
    • Per kernel (patch round 1): the prescription names the sibling container, and never a save under its name.
    • Each refusal asserts the ADR-0112 envelope (code and status) and the two subjects it names. It also asserts that no row or draft is stored, that no container is registered under the name, and that the sibling's view still answers on both doors as the same item.
  • The file: 231 passed (181 pre-existing plus 50), at 13736a50f6.

  • The package, at 13736a50f6 (patch round 1; round 0 read 3371 passed at 5573152989):

    • pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2: Test Files 209 passed, 3 skipped (212); Tests 3373 passed, 19 skipped (3392); exit 0.
    • typecheck: exit 0. tsc --noEmit --listFiles includes the test file.
  • Downstream sample. Direction: consumers of @objectstack/metadata-protocol, against dist/ rebuilt by turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2 (71 of 71 tasks). All pass; the rest of the downstream run is CI's.

  • The merge of origin/main (719644794c) moves no byte under packages/metadata-protocol or its dependency closure. So the suite, typecheck and ablation readings taken at 5573152989 read the same bytes.

Reverse verification

The fix was committed first (5573152989). A trap-guarded script then ran scripts/ablation-replace.mjs --delete on the anchor if (siblingExpansionRefusal) throw siblingExpansionRefusal;.

  • Mutation. The anchor went from 1 occurrence to 0, and the blob from 771b82e97372 to 1cb3741ebb3a.
  • Predicted direction: red.
  • Result. The file gave 34 failed and 195 passed.
    • All 34 are this block's refusal pins. 33 failed with expected null to be an instance of Error (the save accepted). 1 answered INVALID_METADATA instead of VALIDATION_ERROR (the form-only cell on the unscoped environment-wide kernel, the identity-stamp row in the table).
    • All 14 of this block's controls stayed green, and no other test moved.
  • Restore. git checkout HEAD -- ABS_PATH brought the blob back to 771b82e97372, equal to HEAD, and git diff HEAD was empty. Both the tool and the script's own trap proved this.
  • Patch round 1, from committed 13736a50f6, two legs:
    • (a) The prescription reverted to the old object-name arm: 2 failed and 229 passed, exactly the two new pins.
    • (b) The throw deleted: 36 failed and 195 passed. All 36 are the block's refusal pins, and the 14 controls stayed green.
    • Restore: blob 56bc12dce760, equal to HEAD, with git diff HEAD empty.
  • No dist leg. The subject is imported through ./index.js, the source.

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, with no paths, derived 64 families at 719644794c. That is the dispatch lead's 56 plus the 8 the changeset adds: check-adr-0087-registration ×2, check-empty-changeset ×2, release-rehearsal-clone --self-test, release-pending-publish --self-test, check:objectui-changeset and check:pm-changeset-deadline-census.

  • All 64 exited 0. pnpm check:lean-entry-closure first exited 3 (PREREQUISITE NOT MET: objectql's dist/ was absent). It exited 0 after the workspace build. check:dual-build-cjs-loads and check:type-check-debt ran through the verify lock, after the build.
  • --ran: 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN.
  • Patch round 1, at 13736a50f6: the same 64 families, all exit 0; --ran 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN.
  • check-adr-0087-registration accepts the changeset's not-required (no-migration-prescription) disposition.
  • Lint, narrowed. eslint --no-inline-config --format json over the 2 changed .ts files reports 2 files, 0 errors and 0 warnings (no "file ignored" message). Type-aware linting is never enabled (eslint.config.mjs:327-328; --print-config shows parserOptions.project and projectService both null), so files this diff does not touch cannot change verdict. Repo-wide pnpm lint is CI's.
  • Over REST, the refusal (PUT /api/v1/meta/view/NAME on a booted stack) is NOT MEASURED. The pins are in-process at the method that door calls, on both kernels, and the envelope is the one the same door's name refusals already carry through REST.

Acceptance notes

  • The ruled predicate's "same object", read literally, leaves two measured shapes open. Both are accepted on both kernels and in both scopes, and the sibling's view is gone from both doors:

    • a container bound to another object under a sibling's expanded name ({ object: 'crm_account', list } saved as crm_lead.pipeline);
    • an unbound container under it ({ list }, whose derived object is its own name).

    Both are refused if the check drops "of the same object" and keys on the name alone. No writer in the census saves either shape, so that change would refuse nothing legitimate. It is raised to the seat as an open question, not taken here, because the ruling's words name the same object.

  • A second container under a free name still takes a sibling's expanded name. Two containers of one object whose expansions share a name (both bare lists give crm_lead.default) are both accepted, and the later one's view answers that name on both doors. This is the card's step-3 symptom without this save, measured on both kernels and in both scopes. It is reported to the seat as a finding.

  • Scope and order:

    • An environment-wide save under the expanded name of an organization-scoped sibling is accepted, and for that organization the sibling's view is gone. Reading every organization's rows at an environment-wide save would be a cross-tenant read at a write door.
    • A second container stored first, before its sibling gains the member, keeps the name: the sibling's save is judged by its own name, which is its object's.

    Both are measured, and both are noted rather than filed.

  • Restore and publish doors. rollbackMetaItem, revertCommit and the draft promotion do not run this check, as for 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. A draft or version stored before this change, or a draft stored before its sibling existed, can still be written back in this shape. Kept to the claimed surface.

  • Cost. A save of a view container (only a container) reads the caller's active view rows once more, through the same overlay row cache the readers use. A view item pays nothing.

  • Declaration bytes. dist/index.d.ts gains one private member line. No public member or exported type changes.

  • .changeset/21620-container-sibling-expansion-name.md: '@objectstack/metadata-protocol': minor, Clause-②: no (narrowing), the BREAKING banner, and the ADR-0087 disposition not-required (no-migration-prescription).


Generated by Claude Code

claude added 3 commits October 3, 2026 20:47
…under a name another stored container of the same object expands to

A second container stored under a name a sibling container expands to
became that name's own row, so the sibling's expansion no longer filled it:
the object door listed nothing under the name and the by-name read answered
the raw container. The save door now refuses that save with
VALIDATION_ERROR / 400, judged by the readers' own row selection and
expansion, before the identity stamp.

Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
Co-authored-by: Claude <noreply@anthropic.com>
…pansion name refusal

Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol, touching 6 documentable anchor(s).

27 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 5b5e83f446bde0bf6e13db304b9f07f115635704.

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

What this run could not see
  • 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 — 11 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 5b5e83f446bde0bf6e13db304b9f07f115635704 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 5b5e83f446bde0bf6e13db304b9f07f115635704

⚠️ 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 5b5e83f446bde0bf6e13db304b9f07f115635704 → 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

REWORK — PR #21637 at head 719644794c: one item, a patch round to the same dev

domain:engine#1 · session_017ErfyP2Rx7XWHJA27QjyUi · read at 2026-10-03T21:22Z. Everything else in the diff passes the seat's read. The census hit and the fallback match Partition 1's stop condition and the ruling's fallback. This is the one item that blocks.

The refusal's prescription can destroy the view it protects. containerSiblingExpansionNameRefusal tells the author: "Save the container under its object's name, 'OBJECT', or save a view item … under 'NAME'."

  • The measured case: the card's own pair, where the sibling is { name: 'crm_lead', object: 'crm_lead', listViews: { pipeline } }. Here the object's name already holds the sibling container.
  • Following the first arm literally saves the refused body under crm_lead. That replaces the stored sibling row and drops listViews.pipeline, the view this refusal exists to keep serving.
  • An AI author follows a prescription literally, so this is the "防 AI 写错" axis.

The ruling does not fix this wording for the fallback. Ruling 5972387356 attaches "the same prescription: save the container under its object's name, or save a view item under the expanded name" to the broad check, where the refused container is the one misnamed. The fallback's prescription is the seat's to set.

Asked:

  1. The sibling refusal's prescription names the stored container that expands the name (hit.container.name). It says to add the view as a member of that container, or to save a view item (name, object, viewKind, config) under NAME.
    • It never prescribes saving under a name that a different stored container already holds.
    • When hit.container.name is the object's name, the message must not tell the author to save the refused body there.
  2. One pin per kernel asserts the prescription names the sibling container and does not name a save under it. The existing envelope pins stay.
  3. Changeset: "The fix." carries the same two arms. 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's own-expansion message is out of this PR's surface, so leave it as it is.
  4. Merge origin/main if mergeable_state reads dirty. Re-run the changed file, the package suite, typecheck and the dispatch-gates union at the new head, and reconcile with --ran.

The open question (drop "of the same object", option B): not taken in this PR. The ruling names the same object. The seat files the two residual shapes, with the dev's measured options and recommendation, for triage to rule.


Generated by Claude Code

…sibling container, never a save under its name

The refusal told the author to save the container under its object's name.
In the card's own pair that name holds the sibling container, so following
the arm literally replaced the sibling's row and dropped the view the
refusal keeps serving. It now names the stored container that expands the
name and prescribes adding the view as a member of it, or saving a view
item under the expanded name. One pin per kernel; the changeset's fix
carries the same two arms.

Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
Co-authored-by: Claude <noreply@anthropic.com>
…sibling's name appears

The first spelling only asserted the message never says "under 'crm_lead'",
which the old wording ("under its object's name, 'crm_lead'") also
satisfied. Every occurrence of the sibling's name must now name it as the
container, or as the object a view binds to.

Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

ACCEPT — PR #21637 at head 13736a50f6 (after patch round 1)

domain:engine#1 · session_017ErfyP2Rx7XWHJA27QjyUi · read at 2026-10-03T21:56Z. The os-dev reports are on #21620: round 0 is 5973588845 and patch round 1 is 5973830952. Judged against GitHub and the branch, not against the reports.

Out-of-scope, filed:


Generated by Claude Code

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 size/m tests tooling

Projects

None yet

2 participants