Found during the post-landing verification of #14439 (PR #14513) on main 7085f9053, booting examples/app-multi-package with objectstack dev --seed-admin. Verification-only finding — nothing fixed here.
The claim the fixture makes
examples/app-multi-package/src/packages/orders/index.ts (and the README table) says the module package deliberately carries no scope key, and that this is load-bearing:
ManifestSchema.scope defaults to 'project', so a scope-less module is the row that separates the server's writability verdict from a client-side scope !== 'project' heuristic (ADR-0070 D2 / ADR-0130 Consequences row 6).
objectui#7331 (the Studio switcher fix for #14375) was motivated by the same premise: "a type: module sub-package of a multi-package artifact is served with no scope key, because the schema default is applied at parse time while the artifact load path hands the raw manifest body to registerApp".
What is actually served
The property holds in the source and nowhere after it. defineStack parses the manifest through ManifestSchema, whose scope is z.enum([...]).default('project') (packages/spec/src/kernel/manifest.zod.ts:263), so the default is written into the compiled artifact and into every served row:
dist/objectstack.json (what os dev booted — the boot log says so): .packages[].manifest → {"id":"com.example.multi.orders","type":"module","has_scope":true,"scope":"project",…} and {"id":"com.example.multi.core","type":"app","has_scope":true,"scope":"project",…}.
GET /api/v1/packages (row trimmed to the manifest):
{"manifest":{"id":"com.example.multi.orders","namespace":"crm","defaultDatasource":"default","version":"1.0.0","type":"module","scope":"project","name":"Multi-Package Orders","description":"The Module half of a two-package release artifact (ADR-0130 D4)","dependencies":{"com.example.multi.core":"^1.0.0"},"engines":{"protocol":"^17"}},"status":"installed","enabled":true,"writable":false}
GET /api/v1/meta/package → same two rows, scope: "project" on both, writable: false on both.
Why it matters
The server verdict is right (writable: false on both rows — #14375/#14430 work as designed), but the fixture does not deliver the discriminating property it documents: with scope: "project" served, the pre-#7331 client heuristic scope !== 'project' also answers "read-only", so a console that ignores writable entirely renders this fixture identically to one that honours it. Measured: the vendored console at the pinned objectui d8ec8d6d (56 commits before objectui#7331) and objectui main 7dedec6f (contains #7331) show the same two "Read-only" badges on this fixture — see the verification comment on #14439. A fixture that is supposed to catch a regression to the heuristic cannot catch it.
This is Prime Directive #10's "declared ≠ delivered" shape, one layer down: the comment documents a row shape the producer never emits.
What to decide (not deciding here)
Repro: pnpm exec turbo run build --filter='./examples/app-multi-package...', then in examples/app-multi-package: ./node_modules/.bin/objectstack dev --seed-admin -p 4310 -d file:/tmp/mp/data.db; sign in as admin@objectos.ai/admin123; GET /api/v1/packages and jq '.packages[].manifest | {id, has_scope: has("scope"), scope}' dist/objectstack.json.
Found during the post-landing verification of #14439 (PR #14513) on
main7085f9053, bootingexamples/app-multi-packagewithobjectstack dev --seed-admin. Verification-only finding — nothing fixed here.The claim the fixture makes
examples/app-multi-package/src/packages/orders/index.ts(and the README table) says the module package deliberately carries noscopekey, and that this is load-bearing:objectui#7331 (the Studio switcher fix for #14375) was motivated by the same premise: "a
type: modulesub-package of a multi-package artifact is served with noscopekey, because the schema default is applied at parse time while the artifact load path hands the raw manifest body toregisterApp".What is actually served
The property holds in the source and nowhere after it.
defineStackparses the manifest throughManifestSchema, whosescopeisz.enum([...]).default('project')(packages/spec/src/kernel/manifest.zod.ts:263), so the default is written into the compiled artifact and into every served row:dist/objectstack.json(whatos devbooted — the boot log says so):.packages[].manifest→{"id":"com.example.multi.orders","type":"module","has_scope":true,"scope":"project",…}and{"id":"com.example.multi.core","type":"app","has_scope":true,"scope":"project",…}.GET /api/v1/packages(row trimmed to the manifest):{"manifest":{"id":"com.example.multi.orders","namespace":"crm","defaultDatasource":"default","version":"1.0.0","type":"module","scope":"project","name":"Multi-Package Orders","description":"The Module half of a two-package release artifact (ADR-0130 D4)","dependencies":{"com.example.multi.core":"^1.0.0"},"engines":{"protocol":"^17"}},"status":"installed","enabled":true,"writable":false}GET /api/v1/meta/package→ same two rows,scope: "project"on both,writable: falseon both.Why it matters
The server verdict is right (
writable: falseon both rows — #14375/#14430 work as designed), but the fixture does not deliver the discriminating property it documents: withscope: "project"served, the pre-#7331 client heuristicscope !== 'project'also answers "read-only", so a console that ignoreswritableentirely renders this fixture identically to one that honours it. Measured: the vendored console at the pinned objectuid8ec8d6d(56 commits before objectui#7331) and objectuimain7dedec6f(contains #7331) show the same two "Read-only" badges on this fixture — see the verification comment on #14439. A fixture that is supposed to catch a regression to the heuristic cannot catch it.This is Prime Directive #10's "declared ≠ delivered" shape, one layer down: the comment documents a row shape the producer never emits.
What to decide (not deciding here)
defineStack?), the fixture should exercise that path, or the claim should move to where it is true.scopekey" claim from the fixture source + README, and let feat(runtime):GET /packages与GET /packages/:id每行携带服务端自己的可写判定writable(isWritablePackage),Studio 不再用scope启发式反推 #14375'swritablefield stand on its own test (thepackage-list-writable-carrypin), so the docs stop promising a discriminator the artifact does not carry.Repro:
pnpm exec turbo run build --filter='./examples/app-multi-package...', then inexamples/app-multi-package:./node_modules/.bin/objectstack dev --seed-admin -p 4310 -d file:/tmp/mp/data.db; sign in asadmin@objectos.ai/admin123;GET /api/v1/packagesandjq '.packages[].manifest | {id, has_scope: has("scope"), scope}' dist/objectstack.json.