Repository navigation
Commit 8eac321
fix(spec): the extension refusals for
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`):
```text
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 #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](https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK)_
Co-authored-by: Claude <noreply@anthropic.com>hooks and listViews say what defineStack() does with another package's object (#24496)1 parent ca426cb commit 8eac321
4 files changed
Lines changed: 129 additions & 6 deletions
File tree
- .changeset
- content/docs/data-modeling
- packages/spec/src
- data
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
83 | 83 | | |
84 | 84 | | |
85 | 85 | | |
86 | | - | |
87 | | - | |
| 86 | + | |
| 87 | + | |
88 | 88 | | |
89 | 89 | | |
90 | 90 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3996 | 3996 | | |
3997 | 3997 | | |
3998 | 3998 | | |
| 3999 | + | |
| 4000 | + | |
| 4001 | + | |
| 4002 | + | |
| 4003 | + | |
| 4004 | + | |
3999 | 4005 | | |
4000 | | - | |
4001 | | - | |
| 4006 | + | |
| 4007 | + | |
| 4008 | + | |
| 4009 | + | |
4002 | 4010 | | |
4003 | | - | |
4004 | | - | |
| 4011 | + | |
| 4012 | + | |
| 4013 | + | |
| 4014 | + | |
4005 | 4015 | | |
4006 | 4016 | | |
4007 | 4017 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
0 commit comments