@objectstack/core 14.8.0 → 15.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -8,6 +8,7 @@ import { ConflictResolutionStrategy, ApiRegistryEntryInput, ApiRegistryEntry, Ap
8
8
  import * as QA from '@objectstack/spec/qa';
9
9
  import { KeyObject } from 'node:crypto';
10
10
  import { PluginCapability, PluginPermissions as PluginPermissions$1, PluginPermissionSet, ResourceType, PermissionAction, PluginPermission, SandboxConfig, KernelSecurityScanResult, KernelSecurityVulnerability, PluginHealthCheck, PluginHealthStatus as PluginHealthStatus$1, PluginHealthReport, HotReloadConfig, VersionConstraint, DependencyConflict, SemanticVersion, CompatibilityLevel } from '@objectstack/spec/kernel';
11
+ import { AuthzPosture } from '@objectstack/spec/security';
11
12
 
12
13
  /**
13
14
  * Service Lifecycle Types
@@ -1739,6 +1740,16 @@ interface ResolvedAuthzContext {
1739
1740
  tabPermissions?: Record<string, 'visible' | 'hidden' | 'default_on' | 'default_off'>;
1740
1741
  /** Fellow-org user IDs for RLS scoping of identity tables (`id IN (...)`). */
1741
1742
  org_user_ids: string[];
1743
+ /**
1744
+ * [ADR-0095 D2/D3] The monotonic posture rung this principal resolves to,
1745
+ * DERIVED once here from held capability grants (never a better-auth role):
1746
+ * `PLATFORM_ADMIN` (unscoped `admin_full_access`) > `TENANT_ADMIN`
1747
+ * (`organization_admin`) > `MEMBER` (the authenticated floor). `EXTERNAL` is
1748
+ * defined/test-locked but never resolved yet (no external principal type —
1749
+ * see `posture-ladder.ts`). Present only for an authenticated principal;
1750
+ * anonymous requests carry no rung.
1751
+ */
1752
+ posture?: AuthzPosture;
1742
1753
  }
1743
1754
  interface ResolveAuthzInput {
1744
1755
  /** Data engine (ObjectQL) exposing `find(object, { where, limit, context })`. */
@@ -1778,6 +1789,111 @@ declare function resolveLocalizationContext(input: ResolveLocalizationInput): Pr
1778
1789
  currency?: string;
1779
1790
  }>;
1780
1791
 
1792
+ /**
1793
+ * ── The monotonic posture ladder (ADR-0095 D2/D3) ───────────────────────────
1794
+ *
1795
+ * The principal-tiering enum resolved ONCE in `resolveAuthzContext`
1796
+ * (`PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > EXTERNAL`). This module owns two
1797
+ * things and deliberately nothing more:
1798
+ *
1799
+ * 1. **Derivation (D3).** {@link derivePosture} maps held *capability grants*
1800
+ * — never a better-auth role — to a rung. `PLATFORM_ADMIN` derives from the
1801
+ * unscoped `admin_full_access` grant (the `viewAllRecords`/`modifyAllRecords`
1802
+ * evidence the superuser bypass already trusts); `TENANT_ADMIN` from the
1803
+ * `organization_admin` grant. The better-auth `role='admin'` is upstream a
1804
+ * *provisioning source* of those grants (`auto-org-admin-grant.ts`), so it
1805
+ * never re-enters adjudication here — the #2836 dual-track class is closed
1806
+ * by construction.
1807
+ *
1808
+ * 2. **The rung → injection-rule mapping + its tested invariants (D2).** Each
1809
+ * rung maps to EXACTLY ONE row-visibility injection rule
1810
+ * ({@link POSTURE_INJECTION_RULE}). {@link postureVisibleRows} is the
1811
+ * REFERENCE MODEL of those rules over a synthetic row-set — it locks the two
1812
+ * properties the ADR requires as invariants: strict nesting (rung n's
1813
+ * visible set ⊇ rung n−1's) and the EXTERNAL deny-by-default semantics
1814
+ * (explicit shares only, OWD never widens it).
1815
+ *
1816
+ * This module is NOT the enforcement path. The effective read/write filter is
1817
+ * `Layer0(tenant) AND Layer1(business RLS)`, computed in `@objectstack/plugin-
1818
+ * security` (`tenant-layer.ts` + `security-plugin.ts`), and the real behavior
1819
+ * guard is the `authz-matrix-gate` unit snapshot + the dogfood conformance
1820
+ * matrix. The reference model here exists so the ladder's *mathematical*
1821
+ * properties can be asserted at the unit layer without an enforcement boot, and
1822
+ * so the EXTERNAL rung — which has no enforcement path yet — cannot be
1823
+ * reinvented differently when portal/external membership arrives.
1824
+ */
1825
+
1826
+ /**
1827
+ * The rung ordering, high privilege → low, matching the spec enum's numeric
1828
+ * values (`PLATFORM_ADMIN=3 … EXTERNAL=0`). Visibility grows monotonically UP
1829
+ * this ladder (see {@link postureVisibleRows}).
1830
+ */
1831
+ declare const POSTURE_LADDER: readonly ["PLATFORM_ADMIN", "TENANT_ADMIN", "MEMBER", "EXTERNAL"];
1832
+ /** Numeric rank per rung (mirrors the spec `AuthzPosture` enum values). */
1833
+ declare const POSTURE_RANK: Record<AuthzPosture, number>;
1834
+ /**
1835
+ * The ONE row-visibility injection rule each rung maps to (ADR-0095 D2). Prose,
1836
+ * because the machine artifacts live in enforcement (Layer 0 + the per-rung
1837
+ * Layer 1 rule); this is the enumerable contract the explain track reports and
1838
+ * {@link postureVisibleRows} models.
1839
+ */
1840
+ declare const POSTURE_INJECTION_RULE: Record<AuthzPosture, string>;
1841
+ /** Capability-grant evidence the posture derivation consumes (ADR-0095 D3). */
1842
+ interface PostureEvidence {
1843
+ /**
1844
+ * Holds the UNSCOPED platform-admin capability grant (`admin_full_access` →
1845
+ * `viewAllRecords`/`modifyAllRecords`) — the same evidence the superuser
1846
+ * bypass trusts. NOT a better-auth role.
1847
+ */
1848
+ isPlatformAdmin: boolean;
1849
+ /**
1850
+ * Holds the org-admin capability grant (`organization_admin`, tenant-scoped
1851
+ * `viewAllRecords`/`modifyAllRecords`). Provisioned from the better-auth
1852
+ * owner/admin role upstream, consumed here only as a held capability.
1853
+ */
1854
+ isTenantAdmin: boolean;
1855
+ }
1856
+ /**
1857
+ * Resolve the principal's posture rung from held capability grants (ADR-0095 D3).
1858
+ *
1859
+ * Returns `PLATFORM_ADMIN` | `TENANT_ADMIN` | `MEMBER`. It NEVER returns
1860
+ * `EXTERNAL`: no external principal type exists yet (the sharing chain has no
1861
+ * portal/guest-share concept — ADR-0095 W4). The `EXTERNAL` rung, its injection
1862
+ * rule, and its semantics are defined and test-locked ({@link postureVisibleRows},
1863
+ * {@link POSTURE_INJECTION_RULE}) so that when portal/external membership lands
1864
+ * (ADR-0093) the derivation gains an EXTERNAL branch HERE without the rung being
1865
+ * reinvented. `MEMBER` is the authenticated-principal floor.
1866
+ */
1867
+ declare function derivePosture(evidence: PostureEvidence): AuthzPosture;
1868
+ /** A synthetic record for the ladder reference model. */
1869
+ interface LadderRow {
1870
+ id: string;
1871
+ /** The row's tenant. `undefined` = a non-tenant (platform-global) row. */
1872
+ organization_id?: string;
1873
+ /** The row's owner (drives the MEMBER ownership disjunct). */
1874
+ owner_id?: string;
1875
+ /**
1876
+ * Whether an OWD-derived source would admit this row for a member (public
1877
+ * baseline / criteria sharing). EXTERNAL deliberately ignores this field.
1878
+ */
1879
+ owdVisible?: boolean;
1880
+ /** User ids this row is EXPLICITLY shared to (the only EXTERNAL source). */
1881
+ sharedTo?: readonly string[];
1882
+ }
1883
+ /** The principal the reference model evaluates a rung for. */
1884
+ interface LadderPrincipal {
1885
+ userId: string;
1886
+ /** The principal's active organization (undefined for an unscoped principal). */
1887
+ organizationId?: string;
1888
+ }
1889
+ /**
1890
+ * Reference model of the per-rung injection rule: the visible-row set a rung
1891
+ * would resolve to over `rows` for `principal`. Used to lock the ADR-0095 D2
1892
+ * invariants (strict nesting + EXTERNAL deny-by-default). NOT an enforcement
1893
+ * path — see the module header.
1894
+ */
1895
+ declare function postureVisibleRows(posture: AuthzPosture, rows: readonly LadderRow[], principal: LadderPrincipal): LadderRow[];
1896
+
1781
1897
  /**
1782
1898
  * ADR-0069 — authentication-policy session gate.
1783
1899
  *
@@ -1808,6 +1924,40 @@ declare function isAuthGateAllowlisted(rawPath: string | undefined | null): bool
1808
1924
  */
1809
1925
  declare function evaluateAuthGate(sessionUser: any, path: string): AuthGate | null;
1810
1926
 
1927
+ /** HTTP status every seam returns for an anonymous-denied request. */
1928
+ declare const ANONYMOUS_DENY_STATUS: 401;
1929
+ /** Stable machine code (mirrors the REST `enforceAuth` seam). */
1930
+ declare const ANONYMOUS_DENY_CODE: "unauthenticated";
1931
+ /** Human-facing message. */
1932
+ declare const ANONYMOUS_DENY_MESSAGE = "Authentication is required to access this endpoint.";
1933
+ /** The single 401 body shape every seam returns: `{ error, message }`. */
1934
+ declare const ANONYMOUS_DENY_BODY: {
1935
+ readonly error: "unauthenticated";
1936
+ readonly message: "Authentication is required to access this endpoint.";
1937
+ };
1938
+ interface AnonymousDenyInput {
1939
+ /** The `requireAuth` posture. Falsy ⇒ no-op (demo / single-tenant). */
1940
+ requireAuth: boolean | undefined;
1941
+ /** Resolved caller id, if any. */
1942
+ userId?: string | null;
1943
+ /** Internal system context (never set on inbound HTTP; cannot be forged). */
1944
+ isSystem?: boolean;
1945
+ /** HTTP method — `OPTIONS` (CORS preflight) always passes. */
1946
+ method?: string | null;
1947
+ /**
1948
+ * OPTIONAL request path. When a NON-EMPTY string, a control-plane path
1949
+ * (auth / health / ready / discovery — see {@link isAuthGateAllowlisted}) is
1950
+ * exempt. Body-routed seams (GraphQL) have no meaningful path and pass
1951
+ * `undefined`; see the guard below for why that is load-bearing.
1952
+ */
1953
+ path?: string | null;
1954
+ }
1955
+ /**
1956
+ * True when the request MUST be rejected with 401. The one decision every HTTP
1957
+ * seam shares.
1958
+ */
1959
+ declare function shouldDenyAnonymous(input: AnonymousDenyInput): boolean;
1960
+
1811
1961
  /** The validity-window shape shared by both user-grant tables (ADR-0091 D1). */
1812
1962
  interface GrantValidityWindow {
1813
1963
  valid_from?: unknown;
@@ -2394,4 +2544,4 @@ declare class NamespaceResolver {
2394
2544
  private suggestAlternative;
2395
2545
  }
2396
2546
 
2397
- export { API_KEY_PREFIX, type ApiKeyPrincipal, ApiRegistry, type ApiRegistryPluginConfig, type AuthGate, type BulkWriteOptions, type BulkWriteRowResult, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, type GeneratedApiKey, type GrantValidityWindow, HotReloadManager, type KernelState, type KeyInput, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolvedAuthzContext, type ResourceUsage, type RetryOptions, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, buildPermissionsFromGrants, bulkWrite, calendarPartsInTz, calendarPartsInTzOrUtc, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, deepMerge, defaultIsTransientError, evaluateAuthGate, extractApiKey, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, isAuthGateAllowlisted, isExpired, isGrantActive, isGrantExpired, isNode, parseScopes, parseSignature, readAuthoredTranslationLayer, resolveApiKeyPrincipal, resolveAuthzContext, resolveLocale, resolveLocalizationContext, safeExit, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature, wireAuthoredTranslationSync, withTransientRetry };
2547
+ export { ANONYMOUS_DENY_BODY, ANONYMOUS_DENY_CODE, ANONYMOUS_DENY_MESSAGE, ANONYMOUS_DENY_STATUS, API_KEY_PREFIX, type AnonymousDenyInput, type ApiKeyPrincipal, ApiRegistry, type ApiRegistryPluginConfig, type AuthGate, type BulkWriteOptions, type BulkWriteRowResult, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, type GeneratedApiKey, type GrantValidityWindow, HotReloadManager, type KernelState, type KeyInput, type LadderPrincipal, type LadderRow, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, POSTURE_INJECTION_RULE, POSTURE_LADDER, POSTURE_RANK, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PostureEvidence, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolvedAuthzContext, type ResourceUsage, type RetryOptions, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, buildPermissionsFromGrants, bulkWrite, calendarPartsInTz, calendarPartsInTzOrUtc, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, deepMerge, defaultIsTransientError, derivePosture, evaluateAuthGate, extractApiKey, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, isAuthGateAllowlisted, isExpired, isGrantActive, isGrantExpired, isNode, parseScopes, parseSignature, postureVisibleRows, readAuthoredTranslationLayer, resolveApiKeyPrincipal, resolveAuthzContext, resolveLocale, resolveLocalizationContext, safeExit, shouldDenyAnonymous, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature, wireAuthoredTranslationSync, withTransientRetry };
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ import { ConflictResolutionStrategy, ApiRegistryEntryInput, ApiRegistryEntry, Ap
8
8
  import * as QA from '@objectstack/spec/qa';
9
9
  import { KeyObject } from 'node:crypto';
10
10
  import { PluginCapability, PluginPermissions as PluginPermissions$1, PluginPermissionSet, ResourceType, PermissionAction, PluginPermission, SandboxConfig, KernelSecurityScanResult, KernelSecurityVulnerability, PluginHealthCheck, PluginHealthStatus as PluginHealthStatus$1, PluginHealthReport, HotReloadConfig, VersionConstraint, DependencyConflict, SemanticVersion, CompatibilityLevel } from '@objectstack/spec/kernel';
11
+ import { AuthzPosture } from '@objectstack/spec/security';
11
12
 
12
13
  /**
13
14
  * Service Lifecycle Types
@@ -1739,6 +1740,16 @@ interface ResolvedAuthzContext {
1739
1740
  tabPermissions?: Record<string, 'visible' | 'hidden' | 'default_on' | 'default_off'>;
1740
1741
  /** Fellow-org user IDs for RLS scoping of identity tables (`id IN (...)`). */
1741
1742
  org_user_ids: string[];
1743
+ /**
1744
+ * [ADR-0095 D2/D3] The monotonic posture rung this principal resolves to,
1745
+ * DERIVED once here from held capability grants (never a better-auth role):
1746
+ * `PLATFORM_ADMIN` (unscoped `admin_full_access`) > `TENANT_ADMIN`
1747
+ * (`organization_admin`) > `MEMBER` (the authenticated floor). `EXTERNAL` is
1748
+ * defined/test-locked but never resolved yet (no external principal type —
1749
+ * see `posture-ladder.ts`). Present only for an authenticated principal;
1750
+ * anonymous requests carry no rung.
1751
+ */
1752
+ posture?: AuthzPosture;
1742
1753
  }
1743
1754
  interface ResolveAuthzInput {
1744
1755
  /** Data engine (ObjectQL) exposing `find(object, { where, limit, context })`. */
@@ -1778,6 +1789,111 @@ declare function resolveLocalizationContext(input: ResolveLocalizationInput): Pr
1778
1789
  currency?: string;
1779
1790
  }>;
1780
1791
 
1792
+ /**
1793
+ * ── The monotonic posture ladder (ADR-0095 D2/D3) ───────────────────────────
1794
+ *
1795
+ * The principal-tiering enum resolved ONCE in `resolveAuthzContext`
1796
+ * (`PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > EXTERNAL`). This module owns two
1797
+ * things and deliberately nothing more:
1798
+ *
1799
+ * 1. **Derivation (D3).** {@link derivePosture} maps held *capability grants*
1800
+ * — never a better-auth role — to a rung. `PLATFORM_ADMIN` derives from the
1801
+ * unscoped `admin_full_access` grant (the `viewAllRecords`/`modifyAllRecords`
1802
+ * evidence the superuser bypass already trusts); `TENANT_ADMIN` from the
1803
+ * `organization_admin` grant. The better-auth `role='admin'` is upstream a
1804
+ * *provisioning source* of those grants (`auto-org-admin-grant.ts`), so it
1805
+ * never re-enters adjudication here — the #2836 dual-track class is closed
1806
+ * by construction.
1807
+ *
1808
+ * 2. **The rung → injection-rule mapping + its tested invariants (D2).** Each
1809
+ * rung maps to EXACTLY ONE row-visibility injection rule
1810
+ * ({@link POSTURE_INJECTION_RULE}). {@link postureVisibleRows} is the
1811
+ * REFERENCE MODEL of those rules over a synthetic row-set — it locks the two
1812
+ * properties the ADR requires as invariants: strict nesting (rung n's
1813
+ * visible set ⊇ rung n−1's) and the EXTERNAL deny-by-default semantics
1814
+ * (explicit shares only, OWD never widens it).
1815
+ *
1816
+ * This module is NOT the enforcement path. The effective read/write filter is
1817
+ * `Layer0(tenant) AND Layer1(business RLS)`, computed in `@objectstack/plugin-
1818
+ * security` (`tenant-layer.ts` + `security-plugin.ts`), and the real behavior
1819
+ * guard is the `authz-matrix-gate` unit snapshot + the dogfood conformance
1820
+ * matrix. The reference model here exists so the ladder's *mathematical*
1821
+ * properties can be asserted at the unit layer without an enforcement boot, and
1822
+ * so the EXTERNAL rung — which has no enforcement path yet — cannot be
1823
+ * reinvented differently when portal/external membership arrives.
1824
+ */
1825
+
1826
+ /**
1827
+ * The rung ordering, high privilege → low, matching the spec enum's numeric
1828
+ * values (`PLATFORM_ADMIN=3 … EXTERNAL=0`). Visibility grows monotonically UP
1829
+ * this ladder (see {@link postureVisibleRows}).
1830
+ */
1831
+ declare const POSTURE_LADDER: readonly ["PLATFORM_ADMIN", "TENANT_ADMIN", "MEMBER", "EXTERNAL"];
1832
+ /** Numeric rank per rung (mirrors the spec `AuthzPosture` enum values). */
1833
+ declare const POSTURE_RANK: Record<AuthzPosture, number>;
1834
+ /**
1835
+ * The ONE row-visibility injection rule each rung maps to (ADR-0095 D2). Prose,
1836
+ * because the machine artifacts live in enforcement (Layer 0 + the per-rung
1837
+ * Layer 1 rule); this is the enumerable contract the explain track reports and
1838
+ * {@link postureVisibleRows} models.
1839
+ */
1840
+ declare const POSTURE_INJECTION_RULE: Record<AuthzPosture, string>;
1841
+ /** Capability-grant evidence the posture derivation consumes (ADR-0095 D3). */
1842
+ interface PostureEvidence {
1843
+ /**
1844
+ * Holds the UNSCOPED platform-admin capability grant (`admin_full_access` →
1845
+ * `viewAllRecords`/`modifyAllRecords`) — the same evidence the superuser
1846
+ * bypass trusts. NOT a better-auth role.
1847
+ */
1848
+ isPlatformAdmin: boolean;
1849
+ /**
1850
+ * Holds the org-admin capability grant (`organization_admin`, tenant-scoped
1851
+ * `viewAllRecords`/`modifyAllRecords`). Provisioned from the better-auth
1852
+ * owner/admin role upstream, consumed here only as a held capability.
1853
+ */
1854
+ isTenantAdmin: boolean;
1855
+ }
1856
+ /**
1857
+ * Resolve the principal's posture rung from held capability grants (ADR-0095 D3).
1858
+ *
1859
+ * Returns `PLATFORM_ADMIN` | `TENANT_ADMIN` | `MEMBER`. It NEVER returns
1860
+ * `EXTERNAL`: no external principal type exists yet (the sharing chain has no
1861
+ * portal/guest-share concept — ADR-0095 W4). The `EXTERNAL` rung, its injection
1862
+ * rule, and its semantics are defined and test-locked ({@link postureVisibleRows},
1863
+ * {@link POSTURE_INJECTION_RULE}) so that when portal/external membership lands
1864
+ * (ADR-0093) the derivation gains an EXTERNAL branch HERE without the rung being
1865
+ * reinvented. `MEMBER` is the authenticated-principal floor.
1866
+ */
1867
+ declare function derivePosture(evidence: PostureEvidence): AuthzPosture;
1868
+ /** A synthetic record for the ladder reference model. */
1869
+ interface LadderRow {
1870
+ id: string;
1871
+ /** The row's tenant. `undefined` = a non-tenant (platform-global) row. */
1872
+ organization_id?: string;
1873
+ /** The row's owner (drives the MEMBER ownership disjunct). */
1874
+ owner_id?: string;
1875
+ /**
1876
+ * Whether an OWD-derived source would admit this row for a member (public
1877
+ * baseline / criteria sharing). EXTERNAL deliberately ignores this field.
1878
+ */
1879
+ owdVisible?: boolean;
1880
+ /** User ids this row is EXPLICITLY shared to (the only EXTERNAL source). */
1881
+ sharedTo?: readonly string[];
1882
+ }
1883
+ /** The principal the reference model evaluates a rung for. */
1884
+ interface LadderPrincipal {
1885
+ userId: string;
1886
+ /** The principal's active organization (undefined for an unscoped principal). */
1887
+ organizationId?: string;
1888
+ }
1889
+ /**
1890
+ * Reference model of the per-rung injection rule: the visible-row set a rung
1891
+ * would resolve to over `rows` for `principal`. Used to lock the ADR-0095 D2
1892
+ * invariants (strict nesting + EXTERNAL deny-by-default). NOT an enforcement
1893
+ * path — see the module header.
1894
+ */
1895
+ declare function postureVisibleRows(posture: AuthzPosture, rows: readonly LadderRow[], principal: LadderPrincipal): LadderRow[];
1896
+
1781
1897
  /**
1782
1898
  * ADR-0069 — authentication-policy session gate.
1783
1899
  *
@@ -1808,6 +1924,40 @@ declare function isAuthGateAllowlisted(rawPath: string | undefined | null): bool
1808
1924
  */
1809
1925
  declare function evaluateAuthGate(sessionUser: any, path: string): AuthGate | null;
1810
1926
 
1927
+ /** HTTP status every seam returns for an anonymous-denied request. */
1928
+ declare const ANONYMOUS_DENY_STATUS: 401;
1929
+ /** Stable machine code (mirrors the REST `enforceAuth` seam). */
1930
+ declare const ANONYMOUS_DENY_CODE: "unauthenticated";
1931
+ /** Human-facing message. */
1932
+ declare const ANONYMOUS_DENY_MESSAGE = "Authentication is required to access this endpoint.";
1933
+ /** The single 401 body shape every seam returns: `{ error, message }`. */
1934
+ declare const ANONYMOUS_DENY_BODY: {
1935
+ readonly error: "unauthenticated";
1936
+ readonly message: "Authentication is required to access this endpoint.";
1937
+ };
1938
+ interface AnonymousDenyInput {
1939
+ /** The `requireAuth` posture. Falsy ⇒ no-op (demo / single-tenant). */
1940
+ requireAuth: boolean | undefined;
1941
+ /** Resolved caller id, if any. */
1942
+ userId?: string | null;
1943
+ /** Internal system context (never set on inbound HTTP; cannot be forged). */
1944
+ isSystem?: boolean;
1945
+ /** HTTP method — `OPTIONS` (CORS preflight) always passes. */
1946
+ method?: string | null;
1947
+ /**
1948
+ * OPTIONAL request path. When a NON-EMPTY string, a control-plane path
1949
+ * (auth / health / ready / discovery — see {@link isAuthGateAllowlisted}) is
1950
+ * exempt. Body-routed seams (GraphQL) have no meaningful path and pass
1951
+ * `undefined`; see the guard below for why that is load-bearing.
1952
+ */
1953
+ path?: string | null;
1954
+ }
1955
+ /**
1956
+ * True when the request MUST be rejected with 401. The one decision every HTTP
1957
+ * seam shares.
1958
+ */
1959
+ declare function shouldDenyAnonymous(input: AnonymousDenyInput): boolean;
1960
+
1811
1961
  /** The validity-window shape shared by both user-grant tables (ADR-0091 D1). */
1812
1962
  interface GrantValidityWindow {
1813
1963
  valid_from?: unknown;
@@ -2394,4 +2544,4 @@ declare class NamespaceResolver {
2394
2544
  private suggestAlternative;
2395
2545
  }
2396
2546
 
2397
- export { API_KEY_PREFIX, type ApiKeyPrincipal, ApiRegistry, type ApiRegistryPluginConfig, type AuthGate, type BulkWriteOptions, type BulkWriteRowResult, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, type GeneratedApiKey, type GrantValidityWindow, HotReloadManager, type KernelState, type KeyInput, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolvedAuthzContext, type ResourceUsage, type RetryOptions, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, buildPermissionsFromGrants, bulkWrite, calendarPartsInTz, calendarPartsInTzOrUtc, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, deepMerge, defaultIsTransientError, evaluateAuthGate, extractApiKey, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, isAuthGateAllowlisted, isExpired, isGrantActive, isGrantExpired, isNode, parseScopes, parseSignature, readAuthoredTranslationLayer, resolveApiKeyPrincipal, resolveAuthzContext, resolveLocale, resolveLocalizationContext, safeExit, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature, wireAuthoredTranslationSync, withTransientRetry };
2547
+ export { ANONYMOUS_DENY_BODY, ANONYMOUS_DENY_CODE, ANONYMOUS_DENY_MESSAGE, ANONYMOUS_DENY_STATUS, API_KEY_PREFIX, type AnonymousDenyInput, type ApiKeyPrincipal, ApiRegistry, type ApiRegistryPluginConfig, type AuthGate, type BulkWriteOptions, type BulkWriteRowResult, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, type GeneratedApiKey, type GrantValidityWindow, HotReloadManager, type KernelState, type KeyInput, type LadderPrincipal, type LadderRow, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, POSTURE_INJECTION_RULE, POSTURE_LADDER, POSTURE_RANK, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PostureEvidence, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolvedAuthzContext, type ResourceUsage, type RetryOptions, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, buildPermissionsFromGrants, bulkWrite, calendarPartsInTz, calendarPartsInTzOrUtc, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, deepMerge, defaultIsTransientError, derivePosture, evaluateAuthGate, extractApiKey, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, isAuthGateAllowlisted, isExpired, isGrantActive, isGrantExpired, isNode, parseScopes, parseSignature, postureVisibleRows, readAuthoredTranslationLayer, resolveApiKeyPrincipal, resolveAuthzContext, resolveLocale, resolveLocalizationContext, safeExit, shouldDenyAnonymous, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature, wireAuthoredTranslationSync, withTransientRetry };
package/dist/index.js CHANGED
@@ -1554,6 +1554,8 @@ var ObjectKernel = class {
1554
1554
  this.validateSystemRequirements();
1555
1555
  this.logger.debug("Triggering kernel:ready hook");
1556
1556
  await this.context.trigger("kernel:ready");
1557
+ this.logger.debug("Triggering kernel:bootstrapped hook");
1558
+ await this.context.trigger("kernel:bootstrapped");
1557
1559
  this.logger.debug("Triggering kernel:listening hook");
1558
1560
  await this.context.trigger("kernel:listening");
1559
1561
  this.logger.info("\u2705 Bootstrap complete");
@@ -1837,6 +1839,7 @@ var LiteKernel = class extends ObjectKernelBase {
1837
1839
  await this.runPluginStart(plugin);
1838
1840
  }
1839
1841
  await this.triggerHook("kernel:ready");
1842
+ await this.triggerHook("kernel:bootstrapped");
1840
1843
  await this.triggerHook("kernel:listening");
1841
1844
  this.logger.info("\u2705 Bootstrap complete", {
1842
1845
  pluginCount: this.plugins.size
@@ -4136,7 +4139,8 @@ function safeJsonParse(s, fallback) {
4136
4139
  import {
4137
4140
  mapMembershipRole,
4138
4141
  BUILTIN_IDENTITY_PLATFORM_ADMIN,
4139
- ADMIN_FULL_ACCESS
4142
+ ADMIN_FULL_ACCESS,
4143
+ ORGANIZATION_ADMIN
4140
4144
  } from "@objectstack/spec";
4141
4145
 
4142
4146
  // src/security/grant-validity.ts
@@ -4164,6 +4168,62 @@ function isGrantExpired(row, nowMs) {
4164
4168
  return !(nowMs < until);
4165
4169
  }
4166
4170
 
4171
+ // src/security/posture-ladder.ts
4172
+ var POSTURE_LADDER = [
4173
+ "PLATFORM_ADMIN",
4174
+ "TENANT_ADMIN",
4175
+ "MEMBER",
4176
+ "EXTERNAL"
4177
+ ];
4178
+ var POSTURE_RANK = {
4179
+ PLATFORM_ADMIN: 3,
4180
+ TENANT_ADMIN: 2,
4181
+ MEMBER: 1,
4182
+ EXTERNAL: 0
4183
+ };
4184
+ var POSTURE_INJECTION_RULE = {
4185
+ PLATFORM_ADMIN: "Layer 0 exemption where the object posture permits (private / platform-global / better-auth-managed) \u2014 crosses the tenant wall; org-scoped like TENANT_ADMIN on ordinary tenant business objects.",
4186
+ TENANT_ADMIN: "All rows within the active organization (organization_id == ctx.tenantId); no ownership / depth / sharing narrowing.",
4187
+ MEMBER: "Business RLS within the organization \u2014 ownership (owner / unit depth), the OWD baseline, and explicit sharing.",
4188
+ EXTERNAL: "Explicitly shared rows ONLY \u2014 OWD baselines and sharing rules never apply; a misconfiguration can only shrink visibility, never widen it."
4189
+ };
4190
+ function derivePosture(evidence) {
4191
+ if (evidence.isPlatformAdmin) return "PLATFORM_ADMIN";
4192
+ if (evidence.isTenantAdmin) return "TENANT_ADMIN";
4193
+ return "MEMBER";
4194
+ }
4195
+ function isSharedTo(row, userId) {
4196
+ return (row.sharedTo ?? []).includes(userId);
4197
+ }
4198
+ function externalVisible(rows, p) {
4199
+ return rows.filter((r) => isSharedTo(r, p.userId));
4200
+ }
4201
+ function memberVisible(rows, p) {
4202
+ const shared = new Set(externalVisible(rows, p));
4203
+ return rows.filter(
4204
+ (r) => shared.has(r) || r.organization_id === p.organizationId && (r.owner_id === p.userId || r.owdVisible === true)
4205
+ );
4206
+ }
4207
+ function tenantAdminVisible(rows, p) {
4208
+ const member = new Set(memberVisible(rows, p));
4209
+ return rows.filter((r) => member.has(r) || r.organization_id === p.organizationId);
4210
+ }
4211
+ function platformAdminVisible(rows) {
4212
+ return [...rows];
4213
+ }
4214
+ function postureVisibleRows(posture, rows, principal) {
4215
+ switch (posture) {
4216
+ case "PLATFORM_ADMIN":
4217
+ return platformAdminVisible(rows);
4218
+ case "TENANT_ADMIN":
4219
+ return tenantAdminVisible(rows, principal);
4220
+ case "MEMBER":
4221
+ return memberVisible(rows, principal);
4222
+ case "EXTERNAL":
4223
+ return externalVisible(rows, principal);
4224
+ }
4225
+ }
4226
+
4167
4227
  // src/security/resolve-authz-context.ts
4168
4228
  function safeJsonParse2(s, fallback) {
4169
4229
  try {
@@ -4310,6 +4370,10 @@ async function resolveAuthzContext(input) {
4310
4370
  if (hasPlatformAdminGrant && !ctx.positions.includes(BUILTIN_IDENTITY_PLATFORM_ADMIN)) {
4311
4371
  ctx.positions.unshift(BUILTIN_IDENTITY_PLATFORM_ADMIN);
4312
4372
  }
4373
+ ctx.posture = derivePosture({
4374
+ isPlatformAdmin: hasPlatformAdminGrant,
4375
+ isTenantAdmin: ctx.permissions.includes(ORGANIZATION_ADMIN)
4376
+ });
4313
4377
  if (!ctx.permissions.includes("ai_seat")) {
4314
4378
  const aiAccess = (await getUserRow())?.ai_access;
4315
4379
  if (aiAccess === true || aiAccess === 1 || aiAccess === "1") ctx.permissions.push("ai_seat");
@@ -4395,6 +4459,26 @@ function evaluateAuthGate(sessionUser, path) {
4395
4459
  };
4396
4460
  }
4397
4461
 
4462
+ // src/security/anonymous-deny.ts
4463
+ var ANONYMOUS_DENY_STATUS = 401;
4464
+ var ANONYMOUS_DENY_CODE = "unauthenticated";
4465
+ var ANONYMOUS_DENY_MESSAGE = "Authentication is required to access this endpoint.";
4466
+ var ANONYMOUS_DENY_BODY = {
4467
+ error: ANONYMOUS_DENY_CODE,
4468
+ message: ANONYMOUS_DENY_MESSAGE
4469
+ };
4470
+ function shouldDenyAnonymous(input) {
4471
+ if (!input.requireAuth) return false;
4472
+ if (typeof input.method === "string" && input.method.toUpperCase() === "OPTIONS") {
4473
+ return false;
4474
+ }
4475
+ if (input.userId || input.isSystem) return false;
4476
+ if (typeof input.path === "string" && input.path.length > 0 && isAuthGateAllowlisted(input.path)) {
4477
+ return false;
4478
+ }
4479
+ return true;
4480
+ }
4481
+
4398
4482
  // src/utils/datetime.ts
4399
4483
  function calendarPartsInTz(d, tz) {
4400
4484
  const parts = new Intl.DateTimeFormat("en-US", {
@@ -5415,6 +5499,10 @@ var NamespaceResolver = class {
5415
5499
  }
5416
5500
  };
5417
5501
  export {
5502
+ ANONYMOUS_DENY_BODY,
5503
+ ANONYMOUS_DENY_CODE,
5504
+ ANONYMOUS_DENY_MESSAGE,
5505
+ ANONYMOUS_DENY_STATUS,
5418
5506
  API_KEY_PREFIX,
5419
5507
  ApiRegistry,
5420
5508
  CORE_FALLBACK_FACTORIES,
@@ -5425,6 +5513,9 @@ export {
5425
5513
  ObjectKernel,
5426
5514
  ObjectKernelBase,
5427
5515
  ObjectLogger,
5516
+ POSTURE_INJECTION_RULE,
5517
+ POSTURE_LADDER,
5518
+ POSTURE_RANK,
5428
5519
  PluginConfigValidator,
5429
5520
  PluginHealthMonitor,
5430
5521
  PluginLoader,
@@ -5454,6 +5545,7 @@ export {
5454
5545
  createPluginPermissionEnforcer,
5455
5546
  deepMerge,
5456
5547
  defaultIsTransientError,
5548
+ derivePosture,
5457
5549
  evaluateAuthGate,
5458
5550
  extractApiKey,
5459
5551
  generateApiKey,
@@ -5468,12 +5560,14 @@ export {
5468
5560
  isNode,
5469
5561
  parseScopes,
5470
5562
  parseSignature,
5563
+ postureVisibleRows,
5471
5564
  readAuthoredTranslationLayer,
5472
5565
  resolveApiKeyPrincipal,
5473
5566
  resolveAuthzContext,
5474
5567
  resolveLocale,
5475
5568
  resolveLocalizationContext,
5476
5569
  safeExit,
5570
+ shouldDenyAnonymous,
5477
5571
  signPayload,
5478
5572
  verifyPayload,
5479
5573
  verifyPlatformSignature,