Skip to content

fix(spec): the extension refusals for hooks and listViews say what defineStack() does with another package's object - #24496

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24471-extension-guidance-siblings
Oct 19, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24471-extension-guidance-siblings

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24471

Clause-②: no

Dispatched by the domain:spec seat 2 (claim 6261361740, branch claude/issue-24471-extension-guidance-siblings). This is an os-dev subagent of session session_019oFY6Hi35J7atvgVFnmoaK.

What this does

ObjectExtensionSchema refuses hooks and listViews on an objectExtensions entry. Both refusals told the author to declare a top-level hook, or a top-level view, "bound to the target object instead". defineStack() refuses both when the target is an object the stack does not define, and an extended object is one of those. So the route each sentence named was refused at build.

This PR re-trues both sentences to what the code does. The accept set does not change. packages/spec/src/stack.zod.ts is untouched: the hooks arm (:3830) and the views arm (:3846), behind the :3812 early return, accept and refuse exactly what they did.

The new sentences, verbatim (packages/spec/src/data/object.zod.ts:4005 and :4010):

hooks:
`hooks` cannot be contributed through an object extension — the merge does not carry them. A hook follows the ownership of its object: declare it as a top-level hook in the stack that defines the object. `defineStack()` refuses a top-level hook bound to an object the stack does not define, an extended one included.

listViews:
`listViews` cannot be contributed through an object extension — the merge does not carry them. Declare the views as a top-level `view` in the stack that defines the target object. `defineStack()` refuses a top-level `view` whose `list` or `form` data source is an object the stack does not define, an extended one included.
  • hooks. ADR-0130 §1.5 records this refusal as deliberate: "the split must follow hook ownership". The one route consistent with that line is a hook in the stack that defines the object, and that is the route the sentence names. It was measured as the same-stack control below.
  • listViews. No ADR governs it. The sentence states what is refused, and names the one route measured to work: a view in the stack that defines the object (the same-stack control). It does not name the container-object binding, either as a route or as refused. See P2 and the acceptance notes.
  • actions. The sentence beside them (re-trued by feat(spec,objectql)!: a top-level action bound to another package's object is listed on that object as served, and an unregistered target fails the boot #24458) is unchanged.
  • Docs mirror. content/docs/data-modeling/object-extensions.mdx:86–:87, the hooks and listViews rows of the redirect table, now say the same.
  • Changeset. .changeset/24471-extension-guidance-hooks-list-views.md: patch on @objectstack/spec, refusal text only, Clause-②: no.

The premises, each read on this branch

P1: holds. No producer needs a wider route. Measured with git grep over examples/** and packages/** (tests, fixtures and markdown excluded), with each hit read. Tree: this branch, merged with origin/main ae6bd1a69.

  • Top-level hooks:. There are three producers, and each hook names an object its own stack defines:
    • examples/app-crm: crm_opportunity;
    • examples/app-showcase: showcase_task (three hooks), showcase_project, showcase_inquiry;
    • examples/app-todo: todo_task.
    • No plugin, service or platform-objects manifest declares a metadata hooks collection. The other hooks: hits are runtime ports and types.
  • Top-level views:. There are five producers, and each container binding (object: or data.object) names an object its own stack defines:
    • examples/app-crm: crm_lead, crm_opportunity, crm_activity;
    • examples/app-showcase: six showcase_* objects;
    • examples/app-todo: todo_task;
    • packages/qa/downstream-contract: dc_account;
    • the scaffold template packages/create-objectstack/src/templates/blank/src/views/index.ts exports nothing.
    • The one sys_user hit in a view file is an action param lookup (examples/app-showcase/src/ui/views/project.view.ts:78), not a view binding.
  • strict: false. No stack outside the CLI's own config loader calls defineStack with it.
  • objectExtensions. There are three entries, and none carries hooks or listViews:
    • examples/app-showcase (showcase_account: fields, label, priority);
    • packages/plugins/plugin-auth (sys_sso_provider: fields);
    • packages/qa/downstream-contract (dc_account: fields, label, priority).

P2: holds as the card states. The views arm reads fewer bindings than "any name". A tsx probe ran against src on this branch, before and after the merge, identically. The stack defines one object, extender_note, and has objectExtensions: [{ extend: 'sys_organization', … }]:

input defineStack()
hook, object: 'sys_organization' refused, STACK_CROSS_REFERENCE_INVALID 422: ✗ Hook 'g3_hook' references object 'sys_organization' which is not defined in objects.
hook, object: 'extender_note' (control) accepted
view, list.data.object: 'sys_organization' refused, 422: ✗ View[0].list references object 'sys_organization' which is not defined in objects.
view, form.data.object: 'sys_organization' refused, 422: ✗ View[0].form references object 'sys_organization' which is not defined in objects.
view, list.data.object: 'extender_note' (control) accepted
view container, object: 'sys_organization' + listViews accepted
view container, object: 'nope_typo' + listViews accepted (typo control)
object-less stack, hook on sys_organization accepted (the :3812 early return)

The views arm judges list.data and form.data only. A container's own object, which is the binding getViewsByObject() reads, and its listViews / formViews entries are not judged. The typo passes too, so the acceptance comes from what the check reads, not from a design. os validate judges the container's object (packages/lint/src/validate-object-references.ts, the view-container leg). Hence the sentence's precise wording: it says what is refused and nothing broader. This is an open question in the report for the seat, not a change here.

P3: holds. ADR-0130 still reads as quoted. docs/adr/0130-release-artifact-as-co-ownership-boundary.md:155–:157 say: "a hook attached to a foreign object, are REJECTED … the split must follow hook ownership." :446 restates it under Consequences. Its two addenda (2026-09-02 and 2026-09-22) do not mention hooks. No other ADR cites ADR-0130. The code still fences it: DefineStackOptions.artifactObjects keeps hooks[].object out of its widening (stack.zod.ts:2154), and stack-artifact-crossref.test.ts:189 pins that.

Tests

On head a55182033, the merge of origin/main ae6bd1a69 through scripts/pm/os-regen-merge.sh. No regeneration was owed.

  • packages/spec/src/stack-extension-hook-view-foreign-object.test.ts (new, 7 cases):
    • a hook on an extended sys_organization is refused, with code + status + the located message;
    • the same-stack hook is accepted;
    • a view whose list reads the extended target is refused, and so is one whose form does;
    • the same-stack view is accepted;
    • each guidance sentence names the owning stack and the refusal of an extended target.
  • Full @objectstack/spec suite (vitest run --project local): 700 files, 21284 tests passed (1 todo), exit 0. @objectstack/spec typecheck (tsc --noEmit, check:scripts-typecheck, check:test-typecheck): exit 0, test layer unchanged at 52 files / 242 errors / 132 pinned signatures.
  • pnpm --filter @objectstack/spec check:generated after a rebuild: all 14 generated artifacts are current. The guidance strings feed no generated file. The only copies are object.zod.ts and the docs table.
  • Ablations, one per changed sentence, each landed by scripts/ablation-replace.mjs (anchor hit 1 → 0, blob moved). Each was restored to the HEAD blob 0f61b6835ad5 with git diff HEAD empty. The test imports src, so no dist/ is involved.
    • The hooks sentence put back to "declare a top-level hook bound to the target object instead": exactly the hooks guidance case went red, 1 of 7.
    • The listViews sentence cut back to "... target object instead.": exactly the listViews guidance case went red, 1 of 7.
    • The five refusal and control cases stay green under both ablations, as expected, because this diff does not change the behaviour they pin.

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) on head a55182033 derives 108 commands. Each was run with its exit code captured before any pipe, and --ran reconciles them: 108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN, and every one exited 0.

  • check:dev-prereqs (local only) passes.
  • The full farm is CI's.

Acceptance notes

  • The container-object binding is not judged by defineStack(). See P2. Whether the runtime serves a code-shipped container bound to another package's object as that object's list views was NOT MEASURED here. The listViews sentence therefore neither offers it nor refuses it. The report carries the options for the seat. No producer writes such a container (P1).
  • An object-less stack skips both arms (stack.zod.ts:3812). There, a hook or view on another package's object is accepted. That leniency is pinned for the artifact-scoped classes (stack-artifact-crossref.test.ts:306). Per the ruling, the sentences do not present it as a route. In that corner they over-state the refusal, which errs toward not prescribing a route.
  • Commits. They carry the model-free trailer pair. This PR squash-lands under its own title and body.

Generated by Claude Code

…fineStack() does with another package's object

Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

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 — 143 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 45bec43cd981684906a0b4c1a8e15d08ca9ec09f → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 45bec43cd981684906a0b4c1a8e15d08ca9ec09f

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 (seat check) · PR #24496 · domain:spec seat 2 · zhuangjianguo · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-19T08:16Z

ACCEPT at head a55182033fd6449841d0aaa4b27cd3e11712bd63 (claim 6261361740, os-dev subagent, report 6262776701 on #24471). A contract review at CONTRACT_REVIEW_TIER is owed (the schema face: packages/spec/src/data/object.zod.ts). It runs on this head, isolated from the seat. needs:contract-review is on the PR until its record is adopted.

item reading
Shape Draft, base main, assignee zhuangjianguo. Line 1 is Fixes #24471. Clause-②: no starts a line. 4 files, +129 / −6. Not governed. The changeset is patch on @objectstack/spec.
The change Only the guidance.hooks and guidance.listViews strings of ObjectExtensionSchema change, with a comment above them citing ADR-0130 §1.5, plus the two mirror rows in content/docs/data-modeling/object-extensions.mdx. packages/spec/src/stack.zod.ts is untouched, so the accept set does not move. Neither sentence names the object-less early return as a route.
Premises P1 true: every top-level hook and view producer in the tree is bound to its own stack's objects, and no objectExtensions entry carries hooks or listViews, so nothing pulls for a wider route. P2 true, with one precision: the views arm judges list / form data sources only, so the listViews sentence names exactly that. P3 true: ADR-0130 §1.5 stands unamended.
Pins New packages/spec/src/stack-extension-hook-view-foreign-object.test.ts, 7 cases. They cover the refusals for an extended foreign target, the same-stack controls, and each sentence's text. One ablation per sentence turns exactly its text case red.
CI On this head at this act: 38 runs, 19 success, 5 skipped, 14 in progress, 0 failure. Landing waits for green.
Open question (does the container's own object binding need more?) Seat answer: A, do nothing further in this PR. No producer writes such a container (P1), and the sentence is true under any later decision about the arm. B (a runtime probe of a cross-package container) and C (narrowing the views arm) would each need a decision the measured population does not ask for. Not escalated: wording-accuracy class, with no product-visible behaviour change.
Findings Two, both noted and not filed. Neither has a measured wrong answer at a door, so neither passes the filing gate: the object-less early return accepts a foreign hook or view (deliberate and pinned for the artifact-scoped classes), and the views arm does not judge a container's own object. Both are in the PR's Acceptance notes.
Accepted deviations (b) The dev built formula, sdui-parser and lint to measure three doc gates, which widens the local measurement. (d) main moved (#24451, disjoint) and was merged through scripts/pm/os-regen-merge.sh. (a) and (c) carry no contract effect.
Landing After a contract-review PASS and a green head: pr_ready then automerge_enable. Fixes #24471 closes the card.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: a55182033fd6449841d0aaa4b27cd3e11712bd63
Local-runs: none
Scope: packages/spec/src/data/object.zod.ts + packages/spec/src/stack.zod.ts (read, unchanged: validateCrossReferences :3785, the :3812 early return, the hooks arm :3822–:3835, the views arm :3838–:3860, extendedObjectNames :4030 and the actions arm :4062–:4071, the throw :4941–:4953), docs/adr/0130-release-artifact-as-co-ownership-boundary.md (read, unchanged: :155–:157, :446, the status header), packages/objectql/src/engine.ts (read, unchanged: the objectExtensions loop :8352–:8369, resolveMetadataItemName :3736, the views expansion :8700–:8713), packages/metadata/src/metadata-manager.ts getViewsByObject :1754 (read, unchanged), packages/lint/src/validate-object-references.ts :343–:380 (read, unchanged), packages/objectql/src/hook-binder.ts (read, unchanged), packages/spec/src/shared/strict-object.ts :317–:365 (read, unchanged), packages/spec/src/data/hook.zod.ts :295 (read, unchanged), packages/spec/src/ui/view.zod.ts :4963–:4968 (read, unchanged), packages/spec/src/stack-artifact-crossref.test.ts :189, :306 (read, unchanged), packages/spec/src/stack-extension-hook-view-foreign-object.test.ts, content/docs/data-modeling/object-extensions.mdx, .changeset/24471-extension-guidance-hooks-list-views.md

Inputs: card #24471 (body and comments 6261292197, 6261361740, 6261390012, 6262776701), PR #24496 (body, 4-file list, net diff against merge base ae6bd1a696 of 129+/6-), PR #24458's contract review 6260702525 item 8 (the measurement this card came from), and the 43 check-runs on this head. The seat check 6262813655 was not weighed.

① Derived judgments

1. The hooks sentence, clause by clause — true of the code, with one corner named.

  • "the merge does not carry them": right. The engine's objectExtensions loop (engine.ts :8359–:8367) builds the extension def from exactly name, fields, label, pluralLabel, description, validations, indexes; there is no hooks slot, and registerObject(…, 'extend', priority) is the only thing done with the entry.
  • "a hook follows the ownership of its object": consistent with the code (the arm judges against objectNames alone) and with ADR-0130 :157 and :446 as quoted. ObjectOwnershipEnum's own text says extend means "owned by another package", so an extended object is a foreign one.
  • "declare it as a top-level hook in the stack that defines the object": right — in a stack with objects the arm accepts exactly objectNames.has(obj) (:3826), which the same-stack control pins.
  • "defineStack() refuses a top-level hook bound to an object the stack does not define, an extended one included": right for every stack that defines at least one object. The hooks arm (:3822–:3835) tests objectNames.has(obj) and nothing else; extendedObjectNames (:4030) is built after both arms and read only by the actions arm (:4066); isPlatformProvidedObjectName is not consulted here either; artifactObjects does not widen it (pinned at stack-artifact-crossref.test.ts:189). The clause over-states in exactly one corner: a stack that defines NO objects returns at :3812 before both arms, so an object-less pure-extender stack (objectExtensions only) has a hook on its extended target ACCEPTED at build. Judged in ③.

2. The listViews sentence, clause by clause — true, and scoped to what the arm reads.

  • "the merge does not carry them": right, same loop.
  • "declare the views as a top-level view in the stack that defines the target object": right as the route named — accepted and pinned by the same-stack control. It is not the only container shape defineStack() accepts; see item 5.
  • "defineStack() refuses a top-level view whose list or form data source is an object the stack does not define, an extended one included": right and precisely worded. The views arm (:3838–:3860) reads view.list?.data and view.form?.data only, judges a provider === 'object' entry only, against objectNames only. The sentence names list / form data source and nothing wider, so it claims no reach the arm lacks. Same object-less corner as item 1.

3. No accept-set change — right. The net diff against the merge base touches four files; stack.zod.ts is not one of them, and every changed line in object.zod.ts is inside the two guidance string literals or the six-line comment above them: no schema shape, no export, no error code, no issue path moves. Check Changeset concluded success on both workflow runs for this head (job 117956459131: every step success; job 117958550565: every substantive step success, the post-checkout step still running at read). Type Check · source gates, consumer gates and debt ledger success; Type Check · workspace in progress at read.

4. The ADR citation — accurate quote, right lines, one shade of over-naming. ADR-0130 :155–:157: "a package's own app.navigation pointing at a foreign object, and a hook attached to a foreign object, are REJECTED. Those two rejections are the shape of the seam: navigation crosses only through contributions, and the split must follow hook ownership." :446 restates it under Consequences: "the split must follow hook ownership (§1.5 — a hook on a foreign object is rejected by defineStack)". The ADR is Accepted; neither addendum touches hooks. Precision: §1.5 is the ADR's "What the runtime already does" section — a measurement from #14122 that the ADR adopted as an authoring constraint and carried into Consequences; it is not one of D1–D7. The code comment's "a recorded decision" and the changeset's "the recorded decision of ADR-0130 §1.5" are a shade strong ("recorded constraint" is exact), but the citation lands on the right lines, the quotation is verbatim, and under Prime Directive #13 an Accepted ADR carrying the constraint in Consequences still binds, so the card's "do not widen the hooks arm" holds either way. Does it cover an EXTENDED object? "A foreign object" is any object the stack does not own; extension does not transfer ownership, and the code the ADR measured consulted no extension list — so yes, by the ADR's own wording and by the arm. Not blocking.

5. The container-object binding (the dev's P2) — the sentence is honest; the gap is defineStack()'s, and it is pre-recorded. The views arm never reads a container's own object, nor the data of its listViews / formViews entries, so views: [{ object: 'sys_organization', listViews: {…} }] passes build (the dev measured it, typo control included). The runtime keys a container by that object (resolveMetadataItemName :3736 → deriveViewContainerObject), expands it into ViewItems carrying viewKind + object (:8706–:8711), and getViewsByObject (:1754) matches viewKind && object === object with no ownership test — so IF a code-shipped cross-package container is served, it is served by key regardless of ownership; whether it is, nobody has measured. Read against that: the sentence states what IS refused (list/form data source), names the one route measured to work, and neither offers nor refuses the container binding. It does not say "the only route", and an author with a foreign target reads it as "no route from your package", which is exactly as narrow as the enforcement (Prime Directive #10: declared ≠ enforced, keep the claim as narrow as the enforcement). Not misleading. The gap itself is already recorded at the lint door: validate-object-references.ts:343–:348 says in so many words that validateCrossReferences "reads a container's list.data / form.data bindings, never the container's own key", and resolves it there (an unprefixed miss gates, a known platform name passes). Verdict on the open question in ③.

6. The pins — hold each claim, same-stack controls present, text cases pin real emitted output. Seven cases. The three refusals assert code: 'STACK_CROSS_REFERENCE_INVALID', status: 422 (the StackCrossReferenceError envelope, :4947–:4953) and the located message, which matches the arm's string byte for byte (Hook 'probe_hook' references object 'sys_organization', View[0].list references object '…', View[0].form …). The two controls bind the stack's own extender_note in a stack that ALSO extends sys_organization, so they isolate the ownership test from the extension. The two text cases parse { extend, hooks: [] } / { extend, listViews: {} } through ObjectExtensionSchema and read the guidance back from issues: strictObject hands guidance to strictUnknownKeyError (:355–:360), so the string in issues is the schema's own, not a copy. Each pins two substrings the old sentence did not carry, which is why the dev's ablation turns exactly one case per sentence red. Limits, none blocking: substring pins (a later sentence that keeps both substrings and adds a false clause passes); the "merge does not carry" clause is not pinned by text; the object-less corner is not pinned here (it is, for the artifact pass, at stack-artifact-crossref.test.ts:306). The file imports ./stack.zod and ./data/object.zod only — in-package, so no cross-package input spelling is owed. Test Core (1/6)–(6/6) in progress at read.

7. The docs mirror — matches in substance. Both rows carry the owning-stack route, the "an extended one included" refusal, the ownership rationale for hooks and the list / form scoping for views. The rows drop "the merge does not carry them", and the table's own lead sentence already says "The merge carries the seven keys above and nothing else", so nothing is lost.

8. P1, re-read on this head. Top-level hooks producers: app-crm crm_opportunity, app-todo todo_task, app-showcase showcase_task ×3, showcase_project, showcase_inquiry — each defined by its own stack. View containers: app-crm crm_lead / crm_opportunity / crm_activity (list.data and listViews.*.data), app-showcase six defineView containers (the two sys_user / showcase_account hits in project.view.ts are action-param lookups, not bindings), downstream-contract dc_account. objectExtensions entries: showcase showcase_account (fields, label), plugin-auth sys_sso_provider (fields, in authIdentityManifest, which defines authIdentityObjects beside it), downstream-contract dc_account (fields, label, priority). None carries hooks or listViews; no producer is an object-less extender, so the :3812 corner has no occupant in the tree. P1 holds.

9. Repo rules. Both runtime strings carry no tracker number; the code comment cites ADR-0130 §1.5, the permitted provenance. No governed path (Governed Surface Queue Guard success); content/docs/data-modeling is a hand-written tree; 135 changed lines; the card's claim names this branch (claim check success); draft, base main, head repo is the base repo. Both branch commits carry the model-free trailer pair; the PR body carries the session-URL footer.

② Semver level

The changeset declares @objectstack/spec patch with Clause-②: no, no breaking marker, and an "Unchanged" clause naming the accept set, every error code, status and issue path. That is what the diff publishes: two refusal strings on ObjectExtensionSchema's guidance and nothing else in a released package (the test file and the docs page publish nothing). Clause-②: no is right — no public shape, export or accept set moves (item 3) — so no arm is owed and patch is the floor a text fix in a released package takes. The PR body's Clause-②: no line agrees with the changeset. The Check Changeset gate's "declared-breaking" and "major" steps passed with nothing to grade.

Gate state on this head at read (43 check-runs, 0 failure): required contexts — Lint & Repo Gates success, Build Core success, Governed Surface Queue Guard success, Test Core (six shards) in progress, the Type Check lanes three success and workspace in progress. in_progress is not a pass; landing waits for green.

Semver level: right.

③ Boundary flags

Dev deviations (report 6262776701):

The object-less corner (out_of_scope_findings[0], and the PR's Acceptance notes): accepted as disclosed. Both "refuses" clauses are unconditional, and in a stack that defines no objects defineStack() accepts the foreign hook and the foreign list / form view (:3812). The ruling in the dispatch (6261390012) is that neither sentence presents the early return as a route, and the text obeys it; the over-statement errs toward refusing, so no author following the sentence is led into a refusal, and no producer in the tree occupies the corner (item 8). hook-binder.ts was read and no ownership condition was found in it, so if such a stack boots, the runtime would bind the hook — whether it does is unmeasured, as the dev said. Not a filing: no wrong answer at a public door was measured, and the leniency is deliberate and pinned for the artifact pass. Noted for the seat: an exact sentence would say "in a stack that defines objects"; the ruling chose not to hint at the corner, and that is a seat choice, not a contract defect.

Open question (the views arm judges a container's list / form only; a container's own object and its listViews / formViews entries are not judged) — A is acceptable, and nothing must be filed from this PR. Reasons: the sentence advertises no unmeasured route and refuses nothing the code accepts (item 5); the gap is defineStack()'s, already recorded in prose at the lint door (validate-object-references.ts:343) and resolved there for the typo class; C (narrowing the arm) is an accept-set change that first needs a ruling on whether a cross-package container is legitimate, which lint's deliberate pass for a known platform name suggests nobody has decided; B (boot a code-shipped { object: 'sys_organization', listViews } and read GET /meta/view?object=) is the honest next measurement if the seat wants the route on record, and only its result — served (a route to name) or dead (a (c) trap: accepted at build, silently never found) — passes the filing gate. Until then the dev's dedupe words in out_of_scope_findings[1] are the record.

PR state read: draft, no governed path, 135 changed lines, author objectstack-fleet[bot], head repo equals base repo.

Implemented-by: claude/issue-24471-extension-guidance-siblings
Reviewed-by: session_019oFY6Hi35J7atvgVFnmoaK

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review adopted · domain:spec seat 2 · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-19T08:48Z

  • Adopted: contract review 6262965489.
    • Served-tier: CONTRACT_REVIEW_TIER.
    • Head-sha a55182033fd6449841d0aaa4b27cd3e11712bd63 is the current head.
    • PASS, with no FAIL item.
    • needs:contract-review is removed in this act.
  • The review's notes, answered:
    • The object-less corner (the :3812 early return): accepted as disclosed. The ruling chose not to name it as a route, the text errs toward refusing, and no producer occupies the corner.
    • "A recorded decision" of ADR-0130 §1.5 is a shade strong; "recorded constraint" is exact. It is not blocking, because the Accepted ADR carries it under Consequences. It stays as is.
    • The dev's open question (the views arm does not judge a container's own object): the review agrees with A, so nothing is filed from this PR. If the route is wanted on record, the next honest measurement is a booted read of a code-shipped foreign container (option B). Only its result would pass the filing gate. The dev's dedupe words are the record.
  • Pre-landing on this head:
    • 45 check-runs: 37 success, 8 skipped. The required contexts are green.
    • check-expected-skips: OK.
    • check-governed-merges: not governed, +129/−6.
    • mergeable_state: clean.
  • Landing: pr_ready then automerge_enable, in this act. Fixes #24471 closes the card.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 19, 2026 08:50
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 19, 2026 08:50
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 19, 2026
Merged via the queue into main with commit 8eac321 Oct 19, 2026
49 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-24471-extension-guidance-siblings branch October 19, 2026 09:24
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 protocol:data size/m tests tooling

Projects

None yet

2 participants