Filed by the repo:cloud seat per the cross-seat transfer protocol (file, do not dispatch). Unassigned, pm:queue only — routing left to the triage seat. Found while executing cloud's pin move onto 68feaadd (cloud#1197 / cloud#1219); measured empirically, not inferred.
The asymmetry
Two boot paths disagree about whether an application's declared default profile exists.
- CLI honours it —
packages/cli/src/commands/serve.ts:2008 reads appDefaultPermissionSetName(config.permissions) and passes it as new SecurityPlugin({ fallbackPermissionSet }).
bootStack does not — packages/verify/src/harness.ts defaults to a vanilla new SecurityPlugin() and never reads config.permissions.
So the profile an app declares is wired when a human runs objectstack dev, and silently absent when the app's own dogfood suite boots it.
Why it matters now, and not before
Before #5491 (9e9445ba, "the platform baseline becomes explicit-allow") the platform's member_default carried an object_permissions["*"] wildcard, so a member with no application profile still reached every object. The gap was invisible: the fallback profile was never load-bearing, because the floor underneath it granted everything anyway.
#5491 removed that floor deliberately (its own measurement: 21 of 21 create-DENIAL probes returned 201; public_read objects answered 200 with ALL rows to non-holders), and its Migration section prescribes exactly one consumer action:
Ship an app default profile. Mark a permission set isDefault: true… list the objects members legitimately touch, with the axes they need.
That prescription is not expressible through bootStack today. Measured in objectstack-ai/cloud@apps/ee-group-showcase while attempting the prescribed migration: adding definePermissionSet({ isDefault: true, … }) plus permissions: to the app config changed nothing — the same 6 acceptance cases still failed at the object gate. The attempt was reverted rather than worked around.
The only way for a dogfood suite to exercise its own declared baseline is to hand-construct a SecurityPlugin in each suite — which is itself the "custom security code" that several of these apps exist to prove is unnecessary (ADR-0105 Phase 1 acceptance, framework#3541).
Why this is a defect and not a preference
It is a declared ≠ enforced split inside the test harness that exists to catch that split. An app can declare a profile, ship it to users through the CLI path, and have every one of its own tests run against a boot that does not include it — green tests, different production behaviour. The class is the same one #5852 and framework#3695 came from: the context the tests feed is not the context the real seam produces.
Suggested shape (not a ruling — the harness is not this seat's to design)
Make bootStack resolve the app-declared default the same way serve.ts does, so the two boot paths agree by construction rather than by each caller remembering. If a suite deliberately wants the vanilla baseline, that should be the explicit opt-out, not the silent default. A conformance-style assertion that "the profile bootStack wires equals the profile serve wires for the same config" would keep the two from drifting apart again.
Blast radius
Every apps/*-showcase dogfood suite in objectstack-ai/cloud, and any framework-side suite booting through verify that relies on an app profile after #5491. Consumer context and the full three-axis analysis of the cloud-side half are on objectstack-ai/cloud#1219 (that card is needs-user-decision for a separate reason — which control-plane objects members may read — and this gap is one of the two sub-questions it names).
Dedup: searched open issues for bootStack / fallbackPermissionSet / verify harness SecurityPlugin isDefault — zero hits before filing.
Refs: objectstack-ai/cloud#1219, objectstack-ai/cloud#1197, #5491, #5492, #3541, ADR-0105.
Filed by the
repo:cloudseat per the cross-seat transfer protocol (file, do not dispatch). Unassigned,pm:queueonly — routing left to the triage seat. Found while executing cloud's pin move onto68feaadd(cloud#1197 / cloud#1219); measured empirically, not inferred.The asymmetry
Two boot paths disagree about whether an application's declared default profile exists.
packages/cli/src/commands/serve.ts:2008readsappDefaultPermissionSetName(config.permissions)and passes it asnew SecurityPlugin({ fallbackPermissionSet }).bootStackdoes not —packages/verify/src/harness.tsdefaults to a vanillanew SecurityPlugin()and never readsconfig.permissions.So the profile an app declares is wired when a human runs
objectstack dev, and silently absent when the app's own dogfood suite boots it.Why it matters now, and not before
Before #5491 (
9e9445ba, "the platform baseline becomes explicit-allow") the platform'smember_defaultcarried anobject_permissions["*"]wildcard, so a member with no application profile still reached every object. The gap was invisible: the fallback profile was never load-bearing, because the floor underneath it granted everything anyway.#5491 removed that floor deliberately (its own measurement: 21 of 21 create-DENIAL probes returned
201;public_readobjects answered200with ALL rows to non-holders), and its Migration section prescribes exactly one consumer action:That prescription is not expressible through
bootStacktoday. Measured inobjectstack-ai/cloud@apps/ee-group-showcasewhile attempting the prescribed migration: addingdefinePermissionSet({ isDefault: true, … })pluspermissions:to the app config changed nothing — the same 6 acceptance cases still failed at the object gate. The attempt was reverted rather than worked around.The only way for a dogfood suite to exercise its own declared baseline is to hand-construct a
SecurityPluginin each suite — which is itself the "custom security code" that several of these apps exist to prove is unnecessary (ADR-0105 Phase 1 acceptance, framework#3541).Why this is a defect and not a preference
It is a
declared ≠ enforcedsplit inside the test harness that exists to catch that split. An app can declare a profile, ship it to users through the CLI path, and have every one of its own tests run against a boot that does not include it — green tests, different production behaviour. The class is the same one #5852 and framework#3695 came from: the context the tests feed is not the context the real seam produces.Suggested shape (not a ruling — the harness is not this seat's to design)
Make
bootStackresolve the app-declared default the same wayserve.tsdoes, so the two boot paths agree by construction rather than by each caller remembering. If a suite deliberately wants the vanilla baseline, that should be the explicit opt-out, not the silent default. A conformance-style assertion that "the profilebootStackwires equals the profileservewires for the same config" would keep the two from drifting apart again.Blast radius
Every
apps/*-showcasedogfood suite inobjectstack-ai/cloud, and any framework-side suite booting throughverifythat relies on an app profile after #5491. Consumer context and the full three-axis analysis of the cloud-side half are on objectstack-ai/cloud#1219 (that card isneeds-user-decisionfor a separate reason — which control-plane objects members may read — and this gap is one of the two sub-questions it names).Dedup: searched open issues for
bootStack/fallbackPermissionSet/verify harness SecurityPlugin isDefault— zero hits before filing.Refs: objectstack-ai/cloud#1219, objectstack-ai/cloud#1197, #5491, #5492, #3541, ADR-0105.