Repository navigation
fix(spec): the extension refusals for hooks and listViews say what defineStack() does with another package's object - #24496
Conversation
…fineStack() does with another package's object Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
…tension-guidance-siblings
📓 Docs Drift Check1 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
Coarse fallback — 143 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # 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 |
席位核对 (seat check) · PR #24496 ·
|
| 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
Contract reviewServed-tier: Inputs: card #24471 (body and comments 6261292197, 6261361740, 6261390012, 6262776701), PR #24496 (body, 4-file list, net diff against merge base ① Derived judgments1. The
2. The
3. No accept-set change — right. The net diff against the merge base touches four files; 4. The ADR citation — accurate quote, right lines, one shade of over-naming. ADR-0130 :155–:157: "a package's own 5. The container- 6. The pins — hold each claim, same-stack controls present, text cases pin real emitted output. Seven cases. The three refusals assert 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 8. P1, re-read on this head. Top-level 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); ② Semver levelThe changeset declares Gate state on this head at read (43 check-runs, 0 failure): required contexts — Semver level: right. ③ Boundary flagsDev 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 Open question (the views arm judges a container's PR state read: draft, no governed path, 135 changed lines, author Implemented-by: VERDICT: PASS Generated by Claude Code |
|
Contract review adopted ·
Generated by Claude Code |
Fixes #24471
Clause-②: no
Dispatched by the
domain:specseat 2 (claim 6261361740, branchclaude/issue-24471-extension-guidance-siblings). This is anos-devsubagent of sessionsession_019oFY6Hi35J7atvgVFnmoaK.What this does
ObjectExtensionSchemarefuseshooksandlistViewson anobjectExtensionsentry. Both refusals told the author to declare a top-level hook, or a top-levelview, "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.tsis untouched: the hooks arm (:3830) and the views arm (:3846), behind the:3812early return, accept and refuse exactly what they did.The new sentences, verbatim (
packages/spec/src/data/object.zod.ts:4005and:4010):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-objectbinding, 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.content/docs/data-modeling/object-extensions.mdx:86–:87, thehooksandlistViewsrows of the redirect table, now say the same..changeset/24471-extension-guidance-hooks-list-views.md:patchon@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 grepoverexamples/**andpackages/**(tests, fixtures and markdown excluded), with each hit read. Tree: this branch, merged withorigin/mainae6bd1a69.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.platform-objectsmanifest declares a metadatahookscollection. The otherhooks:hits are runtime ports and types.views:. There are five producers, and each container binding (object:ordata.object) names an object its own stack defines:examples/app-crm:crm_lead,crm_opportunity,crm_activity;examples/app-showcase: sixshowcase_*objects;examples/app-todo:todo_task;packages/qa/downstream-contract:dc_account;packages/create-objectstack/src/templates/blank/src/views/index.tsexports nothing.sys_userhit 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 callsdefineStackwith it.objectExtensions. There are three entries, and none carrieshooksorlistViews: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
tsxprobe ran againstsrcon this branch, before and after the merge, identically. The stack defines one object,extender_note, and hasobjectExtensions: [{ extend: 'sys_organization', … }]:defineStack()object: 'sys_organization'STACK_CROSS_REFERENCE_INVALID422:✗ Hook 'g3_hook' references object 'sys_organization' which is not defined in objects.object: 'extender_note'(control)list.data.object: 'sys_organization'✗ View[0].list references object 'sys_organization' which is not defined in objects.form.data.object: 'sys_organization'✗ View[0].form references object 'sys_organization' which is not defined in objects.list.data.object: 'extender_note'(control)object: 'sys_organization'+listViewsobject: 'nope_typo'+listViewssys_organization:3812early return)The views arm judges
list.dataandform.dataonly. A container's ownobject, which is the bindinggetViewsByObject()reads, and itslistViews/formViewsentries are not judged. The typo passes too, so the acceptance comes from what the check reads, not from a design.os validatejudges the container'sobject(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–:157say: "a hook attached to a foreign object, are REJECTED … the split must follow hook ownership.":446restates 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.artifactObjectskeepshooks[].objectout of its widening (stack.zod.ts:2154), andstack-artifact-crossref.test.ts:189pins that.Tests
On head
a55182033, the merge oforigin/mainae6bd1a69throughscripts/pm/os-regen-merge.sh. No regeneration was owed.packages/spec/src/stack-extension-hook-view-foreign-object.test.ts(new, 7 cases):sys_organizationis refused, withcode+status+ the located message;listreads the extended target is refused, and so is one whoseformdoes;@objectstack/specsuite (vitest run --project local): 700 files, 21284 tests passed (1 todo), exit 0.@objectstack/spectypecheck (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:generatedafter a rebuild: all 14 generated artifacts are current. The guidance strings feed no generated file. The only copies areobject.zod.tsand the docs table.scripts/ablation-replace.mjs(anchor hit 1 → 0, blob moved). Each was restored to the HEAD blob0f61b6835ad5withgit diff HEADempty. The test importssrc, so nodist/is involved.hookssentence put back to "declare a top-level hook bound to the target object instead": exactly thehooksguidance case went red, 1 of 7.listViewssentence cut back to "... target object instead.": exactly thelistViewsguidance case went red, 1 of 7.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths) on heada55182033derives 108 commands. Each was run with its exit code captured before any pipe, and--ranreconciles them: 108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN, and every one exited 0.check:dev-prereqs(local only) passes.Acceptance notes
objectbinding is not judged bydefineStack(). 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. ThelistViewssentence therefore neither offers it nor refuses it. The report carries the options for the seat. No producer writes such a container (P1).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.Generated by Claude Code