@@ -135,6 +135,32 @@ export const TenancyConfigSchema = lazySchema(() => z.object({
135135 crossTenantAccess : z . boolean ( ) . default ( false ) . describe ( 'Allow cross-tenant data access (with explicit permission)' ) ,
136136} ) ) ;
137137
138+ /**
139+ * [ADR-0066 D2] Secure-by-default object posture.
140+ *
141+ * Declares whether the object participates in blanket wildcard permission
142+ * grants — a data-model posture like {@link TenancyConfigSchema}, NOT an
143+ * assignment (it names no principal).
144+ *
145+ * - `public` (default) — covered by a permission set's `'*'` wildcard object
146+ * grant; today's allow-by-default behaviour.
147+ * - `private` — NOT covered by the `'*'` wildcard grant; access requires an
148+ * EXPLICIT per-object grant (Salesforce "new object = no access until
149+ * granted"). A `private` object is ALSO exempt from wildcard RLS
150+ * (`tenant_isolation`, owner scoping): the posture-gated superuser bypass
151+ * (`viewAllRecords`/`modifyAllRecords`) short-circuits RLS, so a platform
152+ * admin — incl. one who is also an org admin whose `tenant_isolation` would
153+ * otherwise narrow the result — sees all rows, while non-admins without an
154+ * explicit grant see none.
155+ *
156+ * Pair with the object's `requiredPermissions` (D3) to additionally gate access
157+ * on holding a capability.
158+ */
159+ export const ObjectAccessConfigSchema = lazySchema ( ( ) => z . object ( {
160+ default : z . enum ( [ 'public' , 'private' ] ) . default ( 'public' )
161+ . describe ( 'Default exposure posture: public (covered by wildcard grants) | private (needs explicit grant; exempt from wildcard RLS).' ) ,
162+ } ) ) ;
163+
138164/**
139165 * Soft Delete Configuration Schema
140166 * Implements recycle bin / trash functionality
@@ -487,6 +513,25 @@ const ObjectSchemaBase = z.object({
487513
488514 // Multi-tenancy configuration
489515 tenancy : TenancyConfigSchema . optional ( ) . describe ( 'Multi-tenancy configuration for SaaS applications' ) ,
516+
517+ /**
518+ * [ADR-0066 D2] Secure-by-default object posture. `access.default: 'private'`
519+ * opts the object OUT of blanket wildcard (`'*'`) permission grants (access
520+ * then needs an explicit per-object grant) and exempts it from wildcard RLS
521+ * via the posture-gated superuser bypass. Absent ⇒ `public` (today's
522+ * allow-by-default behaviour; no migration for existing objects).
523+ */
524+ access : ObjectAccessConfigSchema . optional ( ) . describe ( '[ADR-0066 D2] Object exposure posture (public-by-default vs private secure-by-default).' ) ,
525+
526+ /**
527+ * [ADR-0066 D3] Capability contract — capability name(s) (permission-set
528+ * `systemPermissions`; D1 records) a caller MUST hold to access this object at
529+ * all. Mirrors `App.requiredPermissions`. Enforced by plugin-security as an
530+ * AND-gate: checked IN ADDITION to permission-set CRUD grants — a caller
531+ * missing any listed capability is denied regardless of grants. Absent/empty
532+ * ⇒ no capability gate.
533+ */
534+ requiredPermissions : z . array ( z . string ( ) ) . optional ( ) . describe ( '[ADR-0066 D3] Capabilities required to access this object (AND-gate, checked alongside CRUD grants).' ) ,
490535
491536 // Soft delete configuration
492537 softDelete : SoftDeleteConfigSchema . optional ( ) . describe ( 'Soft delete (trash/recycle bin) configuration' ) ,
@@ -867,6 +912,7 @@ export type ServiceObjectInput = z.input<typeof ObjectSchemaBase>;
867912export type ObjectCapabilities = z . infer < typeof ObjectCapabilities > ;
868913export type ObjectIndex = z . infer < typeof IndexSchema > ;
869914export type TenancyConfig = z . infer < typeof TenancyConfigSchema > ;
915+ export type ObjectAccessConfig = z . infer < typeof ObjectAccessConfigSchema > ;
870916export type SoftDeleteConfig = z . infer < typeof SoftDeleteConfigSchema > ;
871917export type VersioningConfig = z . infer < typeof VersioningConfigSchema > ;
872918export type PartitioningConfig = z . infer < typeof PartitioningConfigSchema > ;
0 commit comments