Repository navigation
spec(object-extension): the extension refusal tells an author to declare a top-level hook or a top-level view bound to the target object, but defineStack() refuses both for an object outside the stack (the hook refusal by ADR-0130 §1.5's design) #24471
Description
Blocked-by: #24400
Filing gate ①, class (b): wrong text at a public door, with the wrong answer measured.
reach:the public doordefineStack(and soos compile/os validate). The spec(object-extension): the extension refusal saysdefineStack()attaches a top-level action withobjectName: '<target>', but for another package's object it is never attached and the served object lacks it #24400 dev measured it on PR 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's built spec (head0027ae7fe0), in a stack with one object of its own plusobjectExtensions: [{ extend: 'sys_organization', … }]:- a top-level hook bound to
sys_organizationis refused:✗ Hook 'g3_hook' references object 'sys_organization' which is not defined in objects. - a top-level view whose
list.data.objectissys_organizationis refused:✗ View[0].list references object 'sys_organization' which is not defined in objects.
- a top-level hook bound to
- The contract review on PR 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 (
6260702525, item 8) confirmed both by reading: each arm refuses any name outside the stack's objects. Only an object-less stack gets through.
Filed by domain:spec seat 2 (#18549) · zhuangjianguo · session session_019oFY6Hi35J7atvgVFnmoaK, from the #24400 dev's out_of_scope_findings[0] and [1] (report 6259877793, dispatch item G3). One card for the family: the two sibling sentences of the one PR #24458 re-trues. ⛔ Not a claim. Lane and grade follow the filer exception; triage may change either.
Measured on origin/main 31ce45c071
- The sentences.
packages/spec/src/data/object.zod.ts:3988–:3993,ObjectExtensionSchema's guidance:hooks: "declare a top-level hook bound to the target object instead";listViews: "declare a top-levelviewbound to the target object instead".- The hand-written mirror is
content/docs/data-modeling/object-extensions.mdx:86–:87.
- The refusals. In
packages/spec/src/stack.zod.ts, the hooks arm is at:3830and the views arm at:3846. TheobjectNames.size === 0early return (:3812) is the only way through. - A recorded decision for hooks. ADR-0130 §1.5 (
docs/adr/0130-release-artifact-as-co-ownership-boundary.md:157) records "a hook attached to a foreign object" as REJECTED, and concludes that "the split must follow hook ownership". So the hooks sentence contradicts a recorded decision, not just the code. - PR 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 (spec(object-extension): the extension refusal says
defineStack()attaches a top-level action withobjectName: '<target>', but for another package's object it is never attached and the served object lacks it #24400) re-trues only theactionssentence beside these two.
The work
One PR on packages/spec, plus the docs mirror rows.
-
hooks: re-true the sentence to the route that works. ADR-0130 §1.5 decides that the refusal is deliberate, so ⛔ do not widen the hooks arm in this card. Name what an author does instead (the hook lives with the package that owns the object), or name no route if none exists. -
listViews: no recorded decision was found. Two honest answers:- widen the views arm the way PR 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 widens the action arm (an extended or named platform target), with the served view reaching the object;
- or re-true the sentence to the route that works.
The dispatching seat picks one with four-axis reasons. If the axes split, a decision goes to the box first.
-
Pins: each sentence's claim is pinned at
defineStack, with a same-stack control.
Reader who acts: the domain:spec seat, in one PR, after PR #24458 lands. It edits the same ObjectExtensionSchema guidance and the same docs table.
Grade
p2 when both answers are text. If listViews widens the views arm, the fix changes what defineStack accepts, so it becomes protocol class (p1 + target:v18). Triage may regrade.
Dedupe
- A semantic search of this repository's issues, "ObjectExtension guidance hooks listViews top-level hook view bound to another package object refused defineStack", returned 4 hits.
- One is open: spec(object-extension): the extension refusal says
defineStack()attaches a top-level action withobjectName: '<target>', but for another package's object it is never attached and the served object lacks it #24400, this family'sactionssentence. - Three are closed. Two stored view containers of one object still displace each other's views: a container bound elsewhere or unbound under a sibling's expanded name, and a second container's bare list taking the first's
<object>.default, are accepted with no diagnostic #21639 is about view containers displacing each other, Package export emitsviews:entries the stack manifest vocabulary refuses — the export→import round trip works only through the runtime loop's undeclared wider acceptance #8070 about the package-export view vocabulary, and showcase:ContactViewsis exported but never registered inobjectstack.config.ts— the docs' canonical "create form ≠ edit form" example does not run #5420 about a showcase registration.
- One is open: spec(object-extension): the extension refusal says
- None names these two sentences.
Dedupe words: extension guidance hooks top-level hook another package object refused; ObjectExtension listViews guidance view data.object cross-package.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 19, 2026
Unblocked: #24400 landed as
fb0d85b7fb·pm:blocked→pm:queue·domain:specseat 2 ·zhuangjianguo· sessionsession_019oFY6Hi35J7atvgVFnmoaK· 2026-10-19T06:38Z⛔ Not a claim.
fb0d85b7fb, and spec(object-extension): the extension refusal saysdefineStack()attaches a top-level action withobjectName: '<target>', but for another package's object it is never attached and the served object lacks it #24400 closedcompleted(landing record6261268238). That commit is onorigin/main.origin/maine11ae4b6b0, with no new blocker.packages/spec/src/data/object.zod.ts:3991–:3996(the body cites:3988–:3993from before the merge).stack.zod.ts:3830for hooks and:3846for views, behind the:3812early return.content/docs/data-modeling/object-extensions.mdx:86–:87.actionssentence beside them is now the re-trued one.documentation,domain:spec. TheBlocked-by:line stays as history.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Claim: PM loop round 1 · 2026-10-19T06:43Z · the extension refusal's
hooksandlistViewssentences are re-trued to whatdefineStack()does with a top-level hook or view bound to an object outside the stack (refused at build; ADR-0130 §1.5 for hooks), with no accept-set changeSession:
session_019oFY6Hi35J7atvgVFnmoaK(the seat; the dev is anos-devsubagent of this session)Account:
zhuangjianguo(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24471-extension-guidance-siblingsWorktree:
objectstack-issue-24471Domain:
domain:specSeat:
domain:spec#2(seat post #18549)File surface (paths on
origin/maine11ae4b6b0or later; stop on a breach and explain it in the report):packages/spec/src/data/object.zod.ts:ObjectExtensionSchema'sguidance.hooksandguidance.listViewsonly (about:3991–:3996);content/docs/data-modeling/object-extensions.mdx: thehooksandlistViewsrows of the redirect table only (about:86–:87);defineStack(refused for an extended foreign target in a stack with objects; a same-stack control accepted);.changeset/24471-*.md(patchon@objectstack/spec).packages/spec/src/stack.zod.ts(the hooks and views arms keep their accept sets). Not ADR-0130. Notcontent/docs/releases/.Container & model:
S,mode:subagent,model: default tierfor the draft (dispatch-gates --tier: no path-derived mandate). The review before the queue runs atCONTRACT_REVIEW_TIER(the schema face:packages/spec/src/data/object.zod.ts). The seat runs atbatch 1, serial (「当前任务处理完,后续改为串行派发。」): this is the one slot.Clause-②: no
Responsibility: n/a — not a defect card (refusal guidance text that names a route
defineStackrefuses)Thread-read: 6261292197
Serial constraints cleared:
defineStack()attaches a top-level action withobjectName: '<target>', but for another package's object it is never attached and the served object lacks it #24400 (the sameObjectExtensionSchemaguidance block) landed asfb0d85b7fb(record6261268238).collectRLSPoliciesapplies an object's declared default row scope when no held set carries a policy for it — the platform identity objects stop depending on every set copying the same literals (#24164 ruling E′) #24340,domain:services, in the merge queue) touchespackages/spec/src/data/object.zod.ts, indefaultRowScope's docblock (about:2973). That hunk is disjoint from this one. If both regenerate one artefact, the dev mergesmainafter it lands, throughscripts/pm/os-regen-merge.sh.dataSourcewhosevisiblereadsdata.*is refused at parse (BREAKING, ADR-0087), question 2 of #24332's ruling #24450, lint: RLS_PREDICATE_UNENFORCEABLE walks only permissions[].rowLevelSecurity, so an ObjectSchema.defaultRowScope whose using cannot lower is caught only at runtime #24452, lint: the measure-aggregate-field-type-refused hint for a formula total says "aggregate it with count", where the spec's own remedy is "store the value as a number field" (cloud#2861 ruling B item 4) #24426, lint: a locale pack'spicklists.<name>translations are never checked — an option key that is not in the list, or a list no package declares, passesos lint --strictand is dropped silently at runtime #23566) namepage.zod.ts,component.zod.ts,app.zod.tsandpackages/lint/**, none of which this card touches.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Dispatched ·
domain:specseat 2 · sessionsession_019oFY6Hi35J7atvgVFnmoaK· 2026-10-19T06:45Z · for claim6261361740.mode:subagent: the dev is anos-devsubagent of this session, on branchclaude/issue-24471-extension-guidance-siblings, at the default tier.The ruling in the brief is premise-bound. Both sentences are re-trued to what
defineStack()does, with no accept-set change. The hooks sentence follows ADR-0130 §1.5. Neither sentence names the object-less early return as a route. P1 says no producer needs a wider route. If P1 is false, the dev stops and reportsneeds_decision(widen the arm, or re-true the text), with the four axes for each option.It reports here as an
os-dev-reportcomment and opens a draft PR. The seat subscribes to that PR the moment it exists.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 24471, "status": "done", "branch": "claude/issue-24471-extension-guidance-siblings", "pr": "https://github.com/objectstack-ai/objectstack/pull/24496", "session": "session_019oFY6Hi35J7atvgVFnmoaK (subagent, so this is the parent seat's id; the transcript's Claude-Session line names it)", "premise_still_valid": true, "summary": "Re-trued ObjectExtensionSchema's guidance.hooks and guidance.listViews (packages/spec/src/data/object.zod.ts:4005 and :4010 on head a55182033) to what defineStack() does, with no accept-set change. stack.zod.ts is untouched: the hooks arm :3830 and the views arm :3846, behind the :3812 early return. hooks now reads: '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.' That is the one route consistent with ADR-0130 §1.5, measured as the same-stack control. listViews now reads: '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.' The actions sentence is unchanged. The two rows at content/docs/data-modeling/object-extensions.mdx:86-:87 mirror the new sentences. P1 holds: 3 top-level hooks producers and 5 views producers, every binding same-stack; 3 objectExtensions entries, none carrying hooks or listViews; no platform package declares either collection. P2 holds as the card states. The views arm reads list.data and form.data only, so a container's own `object` (and its listViews entries) is accepted even when it is a typo; hence the narrow wording and open_questions[0]. P3 holds: ADR-0130 :155-:157 and :446 read as quoted, and neither addendum touches hooks. files_changed: packages/spec/src/data/object.zod.ts; content/docs/data-modeling/object-extensions.mdx; packages/spec/src/stack-extension-hook-view-foreign-object.test.ts (new); .changeset/24471-extension-guidance-hooks-list-views.md (new, patch @objectstack/spec, Clause-②: no). deviations: (a) the commits carry the AGENTS.md model-free trailer pair, and the PR body the AGENTS.md session-URL footer, rather than the harness reminder's model-named trailer and footer; AGENTS.md takes precedence. (b) to measure three doc gates that refused PREREQUISITE NOT MET, I built @objectstack/formula, @objectstack/sdui-parser and @objectstack/lint, which is outside the empty ① closure. This widens the local measurement; it does not narrow it. (c) test:repo was not run locally: the diff edits none of its tests and the derivation does not name it; it is CI's (Test Core). (d) origin/main moved during the work (#24451 landed in object.zod.ts, disjoint). It was merged before the PR opened, through scripts/pm/os-regen-merge.sh; no regeneration was owed, and the gates and suites were re-run on the merge head.", "tests": "Head a55182033 (the merge of origin/main ae6bd1a69). New test packages/spec/src/stack-extension-hook-view-foreign-object.test.ts, 7 cases: a hook on an extended sys_organization is refused (STACK_CROSS_REFERENCE_INVALID, 422, located message); the same-stack hook is accepted; a view whose list or form reads the extended target is refused (2 cases); the same-stack view is accepted; each guidance sentence names the owning stack and the extended-target refusal (2 cases). Targeted run `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2` on the new file plus stack-bound-action-foreign-object.test.ts and stack-artifact-crossref.test.ts: 'Test Files 3 passed (3) / Tests 43 passed (43)', VERDICT command-exit 0. Full `vitest run --project local --maxWorkers=2` for @objectstack/spec: 'Test Files 700 passed (700) / Tests 21284 passed | 1 todo (21285)'. `pnpm --filter @objectstack/spec typecheck`: 'check:test-typecheck: OK — 52 file(s) / 242 error(s) / 132 pinned signature(s)'. The two were joined with &&, VERDICT command-exit 0. `pnpm --filter @objectstack/spec build && check:generated`: 'All 14 generated artifacts are up to date'; the guidance strings feed no generated file. Ablations, one per changed sentence, through scripts/ablation-replace.mjs: anchor 1->0, blob 0f61b6835ad5 -> e235b3742389 (hooks) and -> f91837a5e346 (listViews). Hooks sentence restored to the old 'declare a top-level hook bound to the target object instead': 'Tests 1 failed | 6 passed (7)', exactly the hooks guidance case. listViews sentence cut to '...target object instead.': 'Tests 1 failed | 6 passed (7)', exactly the listViews guidance case. Both restored: 'blob == HEAD (0f61b6835ad5) and git diff HEAD is empty'. No dist is involved, because the test imports src. The 5 refusal and control cases stay green under both ablations, because the diff does not change the behaviour they pin. P2 probe (tsx against src, the same before and after the merge): hook on sys_organization refused '✗ Hook 'g3_hook' references object 'sys_organization' which is not defined in objects.'; list view refused '✗ View[0].list references object ...'; form refused; both same-stack controls accepted; container {object:'sys_organization'|'nope_typo', listViews} accepted; object-less stack with a hook on sys_organization accepted.", "gates": "Local: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths) on head a55182033 derives 108 commands, the same list as on f9854b68d before the merge. Each was run with its exit code captured before any pipe. `--ran` reconciles them: 'Run reconciliation — 108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN', and all 108 exited 0 (RECON_EXIT=0). On the pre-merge head f9854b68d it was 108 derived, 105 run, 3 NOT-MEASURED: check:skill-examples, check:dual-build-cjs-loads and check:lean-entry-closure, PREREQUISITE NOT MET; all three exit 0 on the final head. Outside the derivation: `pnpm check:dev-prereqs` exit 0 (local-only family; spec was built); `pnpm --filter @objectstack/spec check:generated` exit 0 after a rebuild; a control-byte grep over the changed files found 0 hits. CI, the PR #24496 check-runs on a55182033f at report time (38 in all). success (15): Auto Label, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Governed Surface Queue Guard, Lint · PM dispatch-gates, Lint · scoped · tooling self-tests, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Spec property liveness, The card this PR closes must claim this branch, filter. skipped (5): Console Pin Gate, the Dogfood Regression Gate shards, Dogfood Verify CLI, Packed-tarball smoke (opt-in), Temporal Conformance (live PG + MySQL). in_progress (18): Build Core, Build Docs, Lint · CI, release & engine gates, Lint · ESLint, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Test Core (1/6) through (6/6), Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace. Not converged, and CI convergence is the PM's.", "mcp_calls": "0 — no MCP GitHub tool was called", "api_writes": "2 relay strokes through scripts/pm/fleet-write/dispatch.mjs, as objectstack-fleet[bot], each one POST /repos/objectstack-ai/objectstack/dispatches: (1) pr_create, which executed POST /repos/objectstack-ai/objectstack/pulls (draft, #24496) and POST /repos/objectstack-ai/objectstack/issues/24496/assignees (zhuangjianguo); relay read-back: 9764 bytes sent, 9764 stored, identical; (2) the os-dev-report comment, POST /repos/objectstack-ai/objectstack/issues/24471/comments. Plus 3 `git push` of the branch (not REST). All reads were unauthenticated REST GETs.", "open_questions": [ { "question": "P2's difference: defineStack()'s views arm (stack.zod.ts:3838-:3860) judges a container's list.data and form.data only. A container's own `object` (the binding getViewsByObject() reads) and its listViews/formViews entries' data are not judged. views:[{object:'sys_organization', listViews:{...}}] is accepted in a stack with objects, and so is the same with object:'nope_typo'. os validate judges the container `object` (lint object-reference-unknown: an error on an unprefixed miss, a pass for a known platform name). Whether the runtime serves a code-shipped container bound to another package's object as that object's list views is NOT MEASURED. This PR's listViews sentence therefore states only the list/form refusal and the same-stack route; it neither offers nor refuses the container binding. Should anything more be done?", "options": [ "A. Keep this PR's text and do nothing further. Business need: no producer writes such a container (P1), so there is no measured pull. Long-term fit: the text stays true under any later decision about the arm. AI-error axis: the text advertises no unmeasured route, and os validate already catches the container-object typo class (though defineStack stays silent, and listViews entries' data.object is judged nowhere I found). Startup focus: zero new surface, zero new gate. Cost: none.", "B. Measure the container route at runtime: boot with a code-shipped {object:'sys_organization', listViews}, then read GET /api/v1/meta/view?object=sys_organization and the Console list-view switcher. If it is served, name it in the sentence (text only); if not, file a (c) trap card. Business need: none measured, so it is speculative. Long-term fit: it puts a cross-package views route on record without a decision about cross-package container slots (the containers-of-one-object displacement family). AI-error axis: a proven route helps authors. Startup focus: a runtime probe plus a pin, about half a day.", "C. Make the views arm judge the container's own `object` and its listViews/formViews entries' data the way it judges list/form. Business need: breaks no producer (P1), but nothing pulls for it. Long-term fit: one rule for every binding, loud at build (contract-first), but it contradicts lint's deliberate pass for a platform name on container `object` and needs a ruling on whether a cross-package container is legitimate. AI-error axis: the strongest structural guard. Startup focus: an accept-set narrowing (protocol class, Clause-② yes (narrowing)), a ruling and a breaking changeset." ], "recommendation": "A. There is no producer pull on any axis. The text is true under every later outcome. B and C each need a decision (a cross-package view route, or its refusal) that the measured population does not ask for. C is the right shape only if a ruling first decides that a cross-package container is illegitimate." } ], "out_of_scope_findings": [ "carrier: 承接者:无 · noted, not filed, and in the PR's Acceptance notes. The object-less early return (stack.zod.ts:3812) skips both arms, so in an object-less stack defineStack() ACCEPTS a top-level hook (and a list/form view) bound to another package's object. Measured: an object-less stack with objectExtensions sys_organization and hooks:[{object:'sys_organization'}] is accepted. The new sentences are unconditional, so in that corner they over-state the refusal; they err toward prescribing no route. Per the ruling the sentences do not present it as a route. The leniency is deliberate and pinned for the artifact-scoped classes (stack-artifact-crossref.test.ts:306; stack.zod.ts:6307, 'references may be served by a plugin that is not in this composition'). No producer uses it for hooks or views (P1). Dedupe words: object-less stack early return hook foreign object accepted; validateCrossReferences objectNames.size === 0 hooks views; defineStack no objects cross-reference skipped.", "carrier: the domain:spec seat, through open_questions[0] · noted, not filed. The views arm does not judge a container's own `object` or its listViews/formViews entries' data.object; the measurements and options are in open_questions[0]. No reach to a wrong runtime answer was measured, so it is not a filable class (a). Dedupe words: views arm container object not judged defineStack; listViews data.object cross-reference unchecked; view container object foreign package accepted." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Landed · PR #24496 →
8eac321996·domain:specseat 2 ·zhuangjianguo· sessionsession_019oFY6Hi35J7atvgVFnmoaK· 2026-10-19T09:25Z6262965489, adopted in6263370014(ready and auto-merge). It was added to the merge queue at 2026-10-19T08:52Z and merged out of it at 2026-10-19T09:23Z, as merge commit8eac3219963d5da43f7a3223eb274d4697bff9b6on parentca426cb65d.origin/maingit merge-base --is-ancestor 8eac321996 origin/main→ exit 0git diff ca426cb65d 8eac321996and the reviewed heada55182033f, measured against its merge baseae6bd1a696, sharegit patch-id --stablea8480f38. 4 files, +129 / −6.gh-readonly-queue/main/pr-24496-*ref remainsFixes #24471. Two other issues closed in the same queue batch: #24448 (PR #24495) and #24375 (PR #24500). Both PRs are other lanes' merges, not this one.What now holds on
main:ObjectExtensionSchema'sguidance.hookssays a hook follows the ownership of its object. The author declares it as a top-level hook in the stack that defines the object, anddefineStack()refuses one bound to an object the stack does not define, an extended one included (ADR-0130 §1.5).guidance.listViewssays to declare the views as a top-levelviewin the stack that defines the target object.defineStack()refuses a view whoselistorformdata source is an object the stack does not define.content/docs/data-modeling/object-extensions.mdxmirror both sentences.patchon@objectstack/spec,Clause-②: no.Noted, not filed (carried in the PR's Acceptance notes):
validateCrossReferences;object.Neither reaches a measured wrong answer at a door. A booted read of a code-shipped foreign container is the next honest measurement, if the route is wanted on record.
State: the merge closed this card
completed.pm:dispatchedis removed in this act. Claim6261361740ends here.Generated by Claude Code