@classytic/arc 2.25.0 → 2.30.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.
Files changed (92) hide show
  1. package/README.md +2 -0
  2. package/dist/{BaseController--M8EMzt0.mjs → BaseController-B5DwufbB.mjs} +15 -11
  3. package/dist/EventTransport-C6YA8xY9.mjs +103 -0
  4. package/dist/{HookSystem-C1vB_Gc9.mjs → HookSystem-WkIougo6.mjs} +15 -35
  5. package/dist/applyPermissionResult-Cf6zUCvb.mjs +257 -0
  6. package/dist/audit/index.d.mts +2 -2
  7. package/dist/audit/index.mjs +1 -1
  8. package/dist/auth/index.d.mts +2 -2
  9. package/dist/auth/index.mjs +1 -1
  10. package/dist/{buildHandler-Dtwd8fdz.mjs → buildHandler-BYS3Vkfz.mjs} +32 -6
  11. package/dist/cleanup/index.d.mts +313 -0
  12. package/dist/cleanup/index.mjs +1267 -0
  13. package/dist/cli/commands/describe.d.mts +1 -1
  14. package/dist/cli/commands/docs.mjs +1 -1
  15. package/dist/core/index.d.mts +2 -2
  16. package/dist/core/index.mjs +5 -3
  17. package/dist/core-poalJLvp.mjs +175 -0
  18. package/dist/{createActionRouter-B6GvAuAX.mjs → createActionRouter-BxuywVRB.mjs} +2 -2
  19. package/dist/{createAggregationRouter-wzwFa_dW.mjs → createAggregationRouter-BiY-lGdY.mjs} +7 -20
  20. package/dist/{preloadResources-DfyngIN9.mjs → createApp-CXrKFciH.mjs} +25 -76
  21. package/dist/defineAggregation-ufWjUwml.mjs +47 -0
  22. package/dist/{core-EUNu0u07.mjs → defineResource-CxWqDCAy.mjs} +36 -261
  23. package/dist/docs/index.d.mts +1 -1
  24. package/dist/docs/index.mjs +1 -1
  25. package/dist/{eventPlugin-DrQ_FesK.mjs → eventPlugin-D2NNR4py.mjs} +2 -103
  26. package/dist/events/index.d.mts +28 -2
  27. package/dist/events/index.mjs +24 -343
  28. package/dist/factory/index.d.mts +4 -3
  29. package/dist/factory/index.mjs +3 -1
  30. package/dist/{fastify-augmentation-dPNBVeIj.d.mts → fastify-augmentation-0WhPnUH7.d.mts} +3 -2
  31. package/dist/{applyPermissionResult-Tw4iXtlf.mjs → fields-CQ_7KQWw.mjs} +1 -97
  32. package/dist/hooks/index.d.mts +1 -1
  33. package/dist/hooks/index.mjs +1 -1
  34. package/dist/idempotency/index.mjs +1 -1
  35. package/dist/index-CCzVR-N3.d.mts +107 -0
  36. package/dist/{index-BMnbzvtO.d.mts → index-Ch16aAah.d.mts} +201 -21
  37. package/dist/{index-f4PTrk3B.d.mts → index-Cx7bro4R.d.mts} +4 -4
  38. package/dist/{index-DPRH0Cme.d.mts → index-Q5gzEIWY.d.mts} +97 -66
  39. package/dist/index.d.mts +6 -6
  40. package/dist/index.mjs +8 -6
  41. package/dist/integrations/index.d.mts +1 -1
  42. package/dist/integrations/mcp/index.d.mts +6 -6
  43. package/dist/integrations/mcp/index.mjs +1 -1
  44. package/dist/integrations/mcp/testing.d.mts +1 -1
  45. package/dist/integrations/mcp/testing.mjs +1 -1
  46. package/dist/{mcpPlugin-Cb7olw0j.mjs → mcpPlugin-nRzm5g2s.mjs} +36 -43
  47. package/dist/middleware/index.d.mts +1 -1
  48. package/dist/migrations/index.d.mts +0 -3
  49. package/dist/migrations/index.mjs +0 -3
  50. package/dist/module-_-M7C5ES.mjs +29 -0
  51. package/dist/{openapi-DrLtog_B.mjs → openapi-B8Vkbxbn.mjs} +1 -1
  52. package/dist/permissions/index.d.mts +3 -3
  53. package/dist/permissions/index.mjs +4 -3
  54. package/dist/{permissions-BrtiBKGs.mjs → permissions-Bfu_8y1U.mjs} +318 -289
  55. package/dist/pipeline/index.d.mts +1 -1
  56. package/dist/{pipeline-BN5G05rd.mjs → pipeline-BJnpkY9i.mjs} +4 -18
  57. package/dist/plugins/index.d.mts +4 -4
  58. package/dist/plugins/index.mjs +2 -2
  59. package/dist/plugins/tracing-entry.mjs +1 -1
  60. package/dist/{preloadResources-0oe-JSSS.d.mts → preloadResources-DCfa2egz.d.mts} +3 -2
  61. package/dist/preloadResources-DSY5HGpS.mjs +45 -0
  62. package/dist/presets/filesUpload.d.mts +2 -2
  63. package/dist/presets/filesUpload.mjs +1 -1
  64. package/dist/presets/index.d.mts +14 -2
  65. package/dist/presets/index.mjs +1 -1
  66. package/dist/presets/multiTenant.d.mts +1 -1
  67. package/dist/presets/search.d.mts +2 -2
  68. package/dist/presets/search.mjs +1 -1
  69. package/dist/{presets-DwFDVKP0.mjs → presets-CJOEXLuk.mjs} +25 -9
  70. package/dist/projection-UBDgfMwu.mjs +17 -0
  71. package/dist/{realtime-Cuw_deA-.mjs → realtime-B9nmIY5V.mjs} +2 -1
  72. package/dist/{registerRoute-v0Wb6Plt.mjs → registerRoute-JkxIEBJq.mjs} +3 -1
  73. package/dist/registry/index.d.mts +2 -2
  74. package/dist/registry/index.mjs +1 -1
  75. package/dist/{registry-vcOCtPij.mjs → registry-BYXUUg21.mjs} +16 -1
  76. package/dist/repositoryFilter-Dz8k3S4P.mjs +44 -0
  77. package/dist/subscribe-helpers-DfzsivM6.mjs +339 -0
  78. package/dist/testing/cleanupStoreContract.d.mts +10 -0
  79. package/dist/testing/cleanupStoreContract.mjs +375 -0
  80. package/dist/testing/index.d.mts +34 -5
  81. package/dist/testing/index.mjs +316 -211
  82. package/dist/testing/outboxStoreContract.d.mts +13 -0
  83. package/dist/testing/outboxStoreContract.mjs +247 -0
  84. package/dist/types/index.d.mts +3 -3
  85. package/dist/{types-QFGc3Cjv.d.mts → types-Bmmn-fO0.d.mts} +1 -1
  86. package/dist/types-C_ZZRcD6.d.mts +450 -0
  87. package/dist/{types-Dmw-6Qeo.d.mts → types-ChvvVNP8.d.mts} +93 -88
  88. package/dist/{index-KUoxjIq1.d.mts → types-D_n8L6VY.d.mts} +33 -106
  89. package/dist/usage/index.d.mts +1 -1
  90. package/dist/usage/index.mjs +0 -0
  91. package/dist/utils/index.d.mts +2 -5
  92. package/package.json +539 -524
@@ -0,0 +1,107 @@
1
+ import { a as ModuleContribution, h as HealthCheck, n as ArcModuleInput, r as ArcModuleRegistry, t as ArcModule, u as ScheduleDefinition } from "./types-D_n8L6VY.mjs";
2
+ import { FastifyInstance } from "fastify";
3
+ //#region src/factory/module/contributions.d.ts
4
+ /**
5
+ * Collect every module's `healthChecks` in the caller's order. Fails at boot
6
+ * on a duplicate check `name` across modules, naming BOTH owning modules.
7
+ * The host's app-level checks are appended AFTER this (modules-first,
8
+ * host-last) by the caller.
9
+ */
10
+ declare function collectModuleHealthChecks(modules: readonly ArcModule[]): HealthCheck[];
11
+ /**
12
+ * Collect every module's `workflows`. Resolves factory contributions (so a
13
+ * factory can close over a container the caller has already decorated on
14
+ * `fastify`) and flattens to a single opaque array. Arc core deliberately
15
+ * does NOT validate or interpret the values — the streamline integration
16
+ * owns name/shape checks and registration. Called by the integration at init
17
+ * time, not by arc's boot pipeline (workflows depend on the integration's
18
+ * container).
19
+ */
20
+ declare function collectModuleWorkflows(fastify: FastifyInstance, modules: readonly ArcModule[]): Promise<unknown[]>;
21
+ /**
22
+ * Collect every module's `scheduledJobs`. Resolves factory contributions
23
+ * (after bootstraps) and fails at boot on a duplicate job `name` across
24
+ * modules. The caller passes the returned definitions to the canonical
25
+ * schedules plugin; factories must therefore be resolved only once.
26
+ */
27
+ declare function collectModuleScheduledJobs(fastify: FastifyInstance, modules: readonly ArcModule[]): Promise<ScheduleDefinition[]>;
28
+ //#endregion
29
+ //#region src/factory/module/lifecycle.d.ts
30
+ /**
31
+ * Subscribe every module's `eventHandlers` in dependency order (pass the
32
+ * `orderModules`-sorted list). Returns the ordered list of unsubscribe
33
+ * functions so the caller can tear them down at shutdown BEFORE module
34
+ * `onClose` (while module deps are still alive). Fails at boot on:
35
+ * - a duplicate NAMED handler across modules (attributing both owners);
36
+ * - a module declaring handlers while the event subsystem is unavailable.
37
+ * Resolves factory contributions (after bootstraps) so a handler can close over
38
+ * booted engines rather than a global getter.
39
+ */
40
+ declare function subscribeModuleEventHandlers(fastify: FastifyInstance, modules: readonly ArcModule[]): Promise<Array<() => void | Promise<void>>>;
41
+ //#endregion
42
+ //#region src/factory/module/order.d.ts
43
+ /**
44
+ * Order modules for composition by their `dependsOn` edges — a STABLE
45
+ * topological sort. Called once at the start of the bootstrap phase; every
46
+ * subsequent phase (bootstrap, resources, afterResources, and reverse-order
47
+ * onClose) iterates the returned list, so a module's declared dependencies are
48
+ * always composed before it.
49
+ *
50
+ * "Stable" = modules with no edge between them keep their original list order,
51
+ * so declaring `dependsOn` on one module never silently reorders an unrelated
52
+ * one. Backward compatible: a `modules` array with NO `dependsOn` anywhere is
53
+ * returned unchanged.
54
+ *
55
+ * Fail-fast — throws (never reorders past a broken contract) on:
56
+ * - duplicate module names (the name is the graph key)
57
+ * - a `dependsOn` name not present in the composed set
58
+ * - a self-reference (`dependsOn` includes the module's own name)
59
+ * - a dependency cycle (reports the concrete `a → b → … → a` path)
60
+ */
61
+ declare function orderModules(modules: readonly ArcModule[]): ArcModule[];
62
+ //#endregion
63
+ //#region src/factory/module/resolve.d.ts
64
+ /**
65
+ * Resolve any module-input form to a concrete `ArcModule`. A thunk is invoked
66
+ * (this is where a dynamic `import()` fires); a promise (or plain module) is
67
+ * awaited. Called once per module at the start of the bootstrap phase.
68
+ */
69
+ declare function resolveModule(input: ArcModuleInput): Promise<ArcModule>;
70
+ /** Resolve a `ModuleContribution` to a concrete array (array passthrough or factory call). */
71
+ declare function resolveContribution<T>(contribution: ModuleContribution<T> | undefined, fastify: FastifyInstance): Promise<readonly T[]>;
72
+ //#endregion
73
+ //#region src/factory/module/index.d.ts
74
+ /**
75
+ * Identity helper for authoring a typed module — mirrors `defineResource`.
76
+ * Gives inference + a single obvious construction site; does no work.
77
+ *
78
+ * `TExports` is inferred from the `bootstrap` return value, so a module
79
+ * author gets a typed public export for free:
80
+ *
81
+ * ```ts
82
+ * export const accountingModule = (deps: Deps) =>
83
+ * defineModule({
84
+ * name: "accounting",
85
+ * bootstrap: async () => createAccountingEngine(deps), // TExports inferred
86
+ * });
87
+ * ```
88
+ */
89
+ declare function defineModule<TExports = unknown>(module: ArcModule<TExports>): ArcModule<TExports>;
90
+ /**
91
+ * Typed accessor for a module's public export (its `bootstrap` return value),
92
+ * recorded at `fastify.arc.modules[name]`.
93
+ *
94
+ * Throwing, in line with the fail-fast contract (and `requireOrgId`-style
95
+ * accessors): a missing entry means the module either isn't composed, is
96
+ * composed AFTER the caller (list order = init order), or returned nothing —
97
+ * all wiring bugs that must surface at boot, not as `undefined` downstream.
98
+ *
99
+ * The type parameter is an assertion, not a proof. Two ways to type it:
100
+ * - augment `ArcModuleRegistry` once — then the name alone infers the type:
101
+ * `getModuleExports(f, "accounting")` → `AccountingEngine`.
102
+ * - or pass it inline: `getModuleExports<AccountingEngine>(f, "accounting")`.
103
+ */
104
+ declare function getModuleExports<K extends keyof ArcModuleRegistry>(fastify: FastifyInstance, name: K): ArcModuleRegistry[K];
105
+ declare function getModuleExports<TExports = unknown>(fastify: FastifyInstance, name: string): TExports;
106
+ //#endregion
107
+ export { orderModules as a, collectModuleScheduledJobs as c, resolveModule as i, collectModuleWorkflows as l, getModuleExports as n, subscribeModuleEventHandlers as o, resolveContribution as r, collectModuleHealthChecks as s, defineModule as t };
@@ -1,6 +1,6 @@
1
1
  import { r as CacheStore, t as CacheLogger } from "./interface-BGZCzOGL.mjs";
2
2
  import { i as RequestScope, n as Mandate } from "./types-DjC-KUCz.mjs";
3
- import { n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "./types-Dmw-6Qeo.mjs";
3
+ import { i as PermissionContext, r as PermissionCheck, t as AuthorizationDecision } from "./types-ChvvVNP8.mjs";
4
4
  import { FastifyReply, FastifyRequest } from "fastify";
5
5
  //#region src/permissions/agent.d.ts
6
6
  /**
@@ -164,11 +164,35 @@ declare function requireAgentScope<TDoc = Record<string, unknown>>(opts: Require
164
164
  //#endregion
165
165
  //#region src/permissions/applyPermissionResult.d.ts
166
166
  /**
167
- * Normalize a permission check return value (`boolean | PermissionResult`)
168
- * into a concrete `PermissionResult`. This is the only place in Arc that
169
- * promotes booleans to results — keeps the type narrowing honest everywhere.
167
+ * A permission check returns either a bare boolean (terse allow/deny sugar) or
168
+ * a full {@link AuthorizationDecision}. There is no legacy `PermissionResult` —
169
+ * `AuthorizationDecision` is arc's single authorization contract.
170
170
  */
171
- declare function normalizePermissionResult(result: boolean | PermissionResult): PermissionResult;
171
+ type PermissionCheckReturn = boolean | AuthorizationDecision;
172
+ /**
173
+ * Normalize a check return into the canonical {@link AuthorizationDecision}.
174
+ * A boolean promotes to `{ effect: "allow" | "deny" }`; a decision passes
175
+ * through. This is the ONE place booleans are promoted, so the rest of the
176
+ * pipeline only ever handles decisions.
177
+ */
178
+ declare function normalizeToDecision(result: PermissionCheckReturn): AuthorizationDecision;
179
+ /**
180
+ * Run a permission check and return a canonical {@link AuthorizationDecision} —
181
+ * the transport-neutral PDP (HTTP, aggregation, MCP, and any future transport).
182
+ * A thin mapper over {@link evaluatePermissionOutcome}:
183
+ *
184
+ * - a structured `ArcError` is RE-THROWN so the transport surfaces it verbatim
185
+ * (e.g. a tier gate throwing `arc.tier_required` with machine-readable meta);
186
+ * - any OTHER throw is fail-closed to a `deny` decision (a broken check must
187
+ * never fall open to a 500 that skips authorization).
188
+ *
189
+ * Pure w.r.t. the request — it neither reads a reply nor mutates the request.
190
+ * Callers translate the returned decision into their own response mechanism and
191
+ * apply side-effects via {@link applyAuthorizationDecision}. HTTP additionally
192
+ * hardens a *thrown* check to 403-even-when-anonymous — that transport-specific
193
+ * nuance lives in {@link evaluateAndApplyPermission}, not here.
194
+ */
195
+ declare function evaluatePermissionDecision(check: PermissionCheck, context: PermissionContext): Promise<AuthorizationDecision>;
172
196
  /**
173
197
  * Minimal shape of a Fastify request that can receive permission side-effects.
174
198
  * We avoid depending on the full augmented `FastifyRequest` type here because
@@ -180,8 +204,9 @@ type RequestSink = FastifyRequest & {
180
204
  scope?: RequestScope;
181
205
  };
182
206
  /**
183
- * Apply a granted `PermissionResult` to a Fastify request — merges row-level
184
- * filters into `_policyFilters` and conditionally installs the scope.
207
+ * Apply an allowed {@link AuthorizationDecision} to a Fastify request — conjoins
208
+ * the decision's row-level `policy` into `_policyFilters` and conditionally
209
+ * installs the `scope`.
185
210
  *
186
211
  * **Scope install rule:** only writes `scope` when the current request scope
187
212
  * is absent or `public`. This prevents downgrading an already-authenticated
@@ -189,13 +214,47 @@ type RequestSink = FastifyRequest & {
189
214
  * narrower `service` scope — the original `member` wins because it came from
190
215
  * a more authoritative source).
191
216
  *
192
- * Safe to call with a non-granted result — it simply no-ops. Callers should
193
- * still check `result.granted` and send an error response before reaching here,
194
- * but this function tolerates the misuse defensively.
217
+ * Safe to call with a denied decision — it simply no-ops. Callers should still
218
+ * check `decision.effect` and send an error response before reaching here, but
219
+ * this function tolerates the misuse defensively.
220
+ *
221
+ * This is the enforcement point (PEP) — the ONE place scope + data policy are
222
+ * installed (and the obligation seam lives).
223
+ */
224
+ declare function applyAuthorizationDecision(decision: AuthorizationDecision, request: RequestSink): void;
225
+ /**
226
+ * @deprecated Renamed to {@link applyAuthorizationDecision} — the contract is an
227
+ * `AuthorizationDecision`, not a `PermissionResult`. This alias is kept for
228
+ * back-compat and will be removed in a future major.
195
229
  */
196
- declare function applyPermissionResult(result: PermissionResult, request: RequestSink): void;
230
+ declare const applyPermissionResult: typeof applyAuthorizationDecision;
231
+ //#endregion
232
+ //#region src/permissions/context.d.ts
233
+ /**
234
+ * Resolve the request scope for a permission check. Prefers the context's
235
+ * first-class `scope`; falls back to the raw HTTP `request.scope` when present,
236
+ * else `PUBLIC_SCOPE`. Primitives MUST use this instead of touching
237
+ * `ctx.request` so scope threads through combinators and non-HTTP transports
238
+ * (MCP, jobs) — which have no request — resolve identity correctly.
239
+ */
240
+ declare function scopeOf(ctx: PermissionContext): RequestScope;
197
241
  //#endregion
198
242
  //#region src/permissions/core.d.ts
243
+ /**
244
+ * Build an `allow` {@link AuthorizationDecision}, optionally attaching a row-level
245
+ * `policy` and/or a `scope` to install. The idiomatic "grant" return for arc
246
+ * 2.30 checks (parallels Cedar's `permit`).
247
+ *
248
+ * @example
249
+ * return allow(); // simple grant
250
+ * return allow({ policy: { ownerId: userId } }); // grant + row policy
251
+ */
252
+ declare function allow(extra?: Omit<AuthorizationDecision, "effect">): AuthorizationDecision;
253
+ /**
254
+ * Build a `deny` {@link AuthorizationDecision} with an optional human-readable
255
+ * reason (parallels Cedar's `forbid`). The idiomatic "refuse" return.
256
+ */
257
+ declare function deny(reason?: string): AuthorizationDecision;
199
258
  /**
200
259
  * Allow public access (no authentication required).
201
260
  *
@@ -272,11 +331,18 @@ declare function requireOwnership<TDoc = Record<string, unknown>>(ownerField?: E
272
331
  /**
273
332
  * Combine multiple checks — ALL must pass (AND logic).
274
333
  *
275
- * Each child runs against the **accumulated** state of previous children:
276
- * - `filters` from earlier children merge into the next child's `_policyFilters`
277
- * - `scope` from earlier children installs on the request before the next child runs
334
+ * Evaluation is PURE — nothing is written to the request. Each child runs
335
+ * against the accumulated state of previous children, threaded through a fresh
336
+ * child context:
337
+ * - `policy` from earlier children is CONJOINED (logical AND) — a later child
338
+ * can add or narrow restrictions but never silently replaces an earlier
339
+ * child's constraint on the same key (conflicting values are preserved under
340
+ * `$and`; see `conjoinPolicyFilters`)
341
+ * - `scope` installed by an earlier child is visible to the next through
342
+ * `scopeOf(ctx)`, and never downgrades an already-authoritative scope
278
343
  *
279
- * The final result carries both merged `filters` and merged `scope`.
344
+ * The returned decision carries the merged `policy` + `scope`; the enforcement
345
+ * point (`applyAuthorizationDecision`) applies them to the request once.
280
346
  *
281
347
  * @example
282
348
  * ```typescript
@@ -449,6 +515,120 @@ declare function createOrgPermissions(config: {
449
515
  */
450
516
  declare function createDynamicPermissionMatrix(config: DynamicPermissionMatrixConfig): DynamicPermissionMatrix;
451
517
  //#endregion
518
+ //#region src/permissions/explain.d.ts
519
+ /**
520
+ * The statically-known requirement of a permission check.
521
+ *
522
+ * - `public` — `allowPublic()`; anyone passes.
523
+ * - `roles` — grants if the caller holds ANY of these (platform OR org roles,
524
+ * unioned — matches how `requireRoles`/`requireOrgRole`/`anyOf` compose).
525
+ * - `authenticated` — the check reads request-time facts (custom auth, service
526
+ * scope, ownership, scope-context, grants) that no static analysis can decide;
527
+ * the definitive answer is computed per request. Treat as "server decides".
528
+ */
529
+ type PermissionRequirement = {
530
+ readonly kind: "public";
531
+ } | {
532
+ readonly kind: "roles";
533
+ readonly roles: readonly string[];
534
+ } | {
535
+ readonly kind: "authenticated";
536
+ };
537
+ /**
538
+ * Extract a check's static requirement from its `PermissionCheckMeta`. Public
539
+ * wins; then the union of platform + org roles a role gate declares; otherwise
540
+ * "authenticated" (auth-required / custom / request-time checks).
541
+ */
542
+ declare function describePermission(check: PermissionCheck): PermissionRequirement;
543
+ /** Result of {@link explainAccess}. */
544
+ interface AccessExplanation {
545
+ /**
546
+ * `allow` / `deny` are DEFINITIVE from static metadata (public, or a role
547
+ * match against the supplied principal). `conditional` means the gate reads
548
+ * request-time state, so the real decision is made per request — this is not a
549
+ * denial, it is "cannot be determined statically".
550
+ */
551
+ decision: "allow" | "deny" | "conditional";
552
+ /** Human-readable rationale, safe to surface in a UI or an audit report. */
553
+ reason: string;
554
+ /** The check's static requirement (the same value {@link describePermission} returns). */
555
+ requirement: PermissionRequirement;
556
+ }
557
+ /**
558
+ * Explain whether a principal (by roles) passes a permission check, from static
559
+ * metadata alone — for permission-matrix UIs, docs, and audits. Omit `principal`
560
+ * to get the requirement-only view (roles → `conditional`).
561
+ *
562
+ * @example
563
+ * explainAccess(requireRoles("admin"), { roles: ["editor"] })
564
+ * // → { decision: "deny", reason: "requires one of: admin", requirement: { kind: "roles", roles: ["admin"] } }
565
+ */
566
+ declare function explainAccess(check: PermissionCheck, principal?: {
567
+ roles?: readonly string[];
568
+ orgRoles?: readonly string[];
569
+ }): AccessExplanation;
570
+ /**
571
+ * Introspect a whole permission map (`{ list, get, create, ... }` or any
572
+ * action→check record) into `action → requirement`. The reusable core of a
573
+ * `/permissions/matrix` endpoint or an access audit — framework-owned, so a host
574
+ * never hand-rolls `(check as any)._roles` peeking. Undefined slots (a CRUD op
575
+ * with no gate = public-by-omission) surface as `public` so the inventory is
576
+ * honest about what's exposed.
577
+ */
578
+ declare function describePermissionMap(permissions: Readonly<Record<string, PermissionCheck | undefined>>): Record<string, PermissionRequirement>;
579
+ /**
580
+ * The public attack surface of a permission map — the actions reachable with NO
581
+ * authentication (explicit `allowPublic()` or public-by-omission). Empty is the
582
+ * healthy default for a protected resource; a non-empty list is what a
583
+ * "what's exposed?" audit report should show.
584
+ */
585
+ declare function collectPublicSurface(permissions: Readonly<Record<string, PermissionCheck | undefined>>): string[];
586
+ //#endregion
587
+ //#region src/permissions/filter-merge.d.ts
588
+ /**
589
+ * Policy-filter composition — logical AND, never last-writer-wins.
590
+ *
591
+ * Row-level policy filters can arrive from several INDEPENDENT sources for one
592
+ * request: a tenant preset, an `ownedByUser` grant, an `allOf(...)` branch, a
593
+ * host permission check. Every one of them is a RESTRICTION the row must
594
+ * satisfy, so composing them is logical AND.
595
+ *
596
+ * The historical `{ ...base, ...incoming }` shallow spread did NOT implement AND:
597
+ * when two sources constrained the SAME key with different values, the later one
598
+ * silently REPLACED the earlier one — widening (or erasing) a restriction another
599
+ * layer deliberately imposed. That is a defense-in-depth defect, not a
600
+ * refinement. "Later can refine" is only sound when the refinement is provably
601
+ * narrower, and a bare overwrite proves nothing.
602
+ *
603
+ * {@link conjoinPolicyFilters} composes with AND semantics while preserving the
604
+ * common case byte-for-byte:
605
+ *
606
+ * - key present on only one side → carried through flat. Non-overlapping
607
+ * filters (the ~99% case) produce exactly the same object the old spread did,
608
+ * so there is ZERO behavior change for them.
609
+ * - same key, deep-equal value → kept once (idempotent — e.g. two checks
610
+ * both pinning the same `organizationId`).
611
+ * - same key, DIFFERENT value → BOTH constraints are preserved under an
612
+ * `$and`, so a row must satisfy both. For flat equality that conjunction is
613
+ * unsatisfiable, so the query returns nothing — the safe, correct outcome for
614
+ * contradictory policies (deny), and the earlier restriction is never dropped.
615
+ *
616
+ * Adapter safety: mongokit / sqlitekit compile `$and` natively at the DB layer,
617
+ * and arc's in-memory `simpleEqualityMatcher` (AccessControl.checkPolicyFilters)
618
+ * already fail-closes on operator-shaped filters. So a produced `$and` is either
619
+ * evaluated correctly by the DB or rejected fail-closed in memory — both safe.
620
+ *
621
+ * This is the pragmatic, adapter-neutral form of the AND semantics. A canonical
622
+ * filter IR in `repo-core` (nested and/or/not nodes each adapter compiles) is the
623
+ * fuller design, but this closes the security defect without one.
624
+ */
625
+ /**
626
+ * Conjoin two policy-filter maps with AND semantics. Never silently overwrites a
627
+ * same-key constraint with a different value — conflicting keys are preserved
628
+ * under `$and`. See the module doc for the full contract.
629
+ */
630
+ declare function conjoinPolicyFilters(base: Record<string, unknown> | undefined, incoming: Record<string, unknown> | undefined): Record<string, unknown>;
631
+ //#endregion
452
632
  //#region src/permissions/grants.d.ts
453
633
  /**
454
634
  * The grant-mode lattice, weakest → strongest. Holding a mode satisfies
@@ -474,7 +654,7 @@ declare function modeSatisfies(held: string | undefined, required: GrantMode): b
474
654
  * - `{ mode }` — the strongest mode the caller holds on `ctx.resourceId`;
475
655
  * the combinator applies the lattice against the required mode.
476
656
  * - `{ filters }` — list-shaped resolution: row filters that scope the
477
- * query to visible records (flows through `PermissionResult.filters` →
657
+ * query to visible records (flows through the decision's `policy` →
478
658
  * `_policyFilters` → the repository query). Grants access with the
479
659
  * filters attached.
480
660
  * - `{ mode, filters }` — mode gates; filters ride along when it passes.
@@ -655,8 +835,8 @@ declare function requireScopeContext<TDoc = Record<string, unknown>>(keyOrMap: s
655
835
  *
656
836
  * **Two call shapes:**
657
837
  * ```typescript
658
- * requireOrgInScope('acme-holding') // static
659
- * requireOrgInScope((ctx) => ctx.request.params.orgId) // dynamic
838
+ * requireOrgInScope('acme-holding') // static
839
+ * requireOrgInScope((ctx) => ctx.params?.orgId) // dynamic — from ctx facts, not the raw request
660
840
  * ```
661
841
  *
662
842
  * `elevated` scope grants unconditionally (cross-org bypass).
@@ -664,9 +844,9 @@ declare function requireScopeContext<TDoc = Record<string, unknown>>(keyOrMap: s
664
844
  * @example
665
845
  * ```typescript
666
846
  * permissions: {
667
- * list: requireOrgInScope((ctx) => ctx.request.params.orgId),
847
+ * list: requireOrgInScope((ctx) => ctx.params?.orgId),
668
848
  * create: allOf(
669
- * requireOrgInScope((ctx) => ctx.request.body?.organizationId),
849
+ * requireOrgInScope((ctx) => (ctx.data as { organizationId?: string })?.organizationId),
670
850
  * requireOrgRole('admin'),
671
851
  * ),
672
852
  * }
@@ -772,4 +952,4 @@ declare function fullPublic<TDoc = Record<string, unknown>>(overrides?: Permissi
772
952
  */
773
953
  declare function readOnly<TDoc = Record<string, unknown>>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
774
954
  //#endregion
775
- export { allOf as A, applyPermissionResult as B, requireGrant as C, PermissionEventBus as D, DynamicPermissionMatrixConfig as E, requireAuth as F, requireDPoP as G, RequireAgentScopeOptions as H, requireOwnership as I, requireMandate as K, requireRoles as L, anyOf as M, denyAll as N, createDynamicPermissionMatrix as O, not as P, roles as R, modeSatisfies as S, DynamicPermissionMatrix as T, RequireMandateOptions as U, normalizePermissionResult as V, requireAgentScope as W, createRoleHierarchy as _, permissionMatrix as a, GrantResolution as b, publicReadAdminWrite as c, requireOrgMembership as d, requireOrgRole as f, RoleHierarchy as g, requireTeamMembership as h, ownerWithAdminBypass as i, allowPublic as j, createOrgPermissions as k, readOnly as l, requireServiceScope as m, authenticated as n, presets_d_exports as o, requireScopeContext as p, fullPublic as r, publicRead as s, adminOnly as t, requireOrgInScope as u, GRANT_MODES as v, ConnectEventsOptions as w, RequireGrantOptions as x, GrantMode as y, when as z };
955
+ export { evaluatePermissionDecision as $, explainAccess as A, anyOf as B, requireGrant as C, collectPublicSurface as D, PermissionRequirement as E, createDynamicPermissionMatrix as F, requireOwnership as G, denyAll as H, createOrgPermissions as I, when as J, requireRoles as K, allOf as L, DynamicPermissionMatrix as M, DynamicPermissionMatrixConfig as N, describePermission as O, PermissionEventBus as P, applyPermissionResult as Q, allow as R, modeSatisfies as S, AccessExplanation as T, not as U, deny as V, requireAuth as W, PermissionCheckReturn as X, scopeOf as Y, applyAuthorizationDecision as Z, createRoleHierarchy as _, permissionMatrix as a, requireMandate as at, GrantResolution as b, publicReadAdminWrite as c, requireOrgMembership as d, normalizeToDecision as et, requireOrgRole as f, RoleHierarchy as g, requireTeamMembership as h, ownerWithAdminBypass as i, requireDPoP as it, ConnectEventsOptions as j, describePermissionMap as k, readOnly as l, requireServiceScope as m, authenticated as n, RequireMandateOptions as nt, presets_d_exports as o, requireScopeContext as p, roles as q, fullPublic as r, requireAgentScope as rt, publicRead as s, adminOnly as t, RequireAgentScopeOptions as tt, requireOrgInScope as u, GRANT_MODES as v, conjoinPolicyFilters as w, RequireGrantOptions as x, GrantMode as y, allowPublic as z };
@@ -1,7 +1,7 @@
1
- import { C as RequestContext, Et as ActionDefinition, F as FastifyWithDecorators, H as CrudController, L as RequestWithExtras, Lt as IController, Rt as IControllerResponse, T as CrudRouterOptions, U as ResourceConfig, V as ResourceDefinition, et as RouteSchemaOptions, jt as RouteMcpConfig, tt as ArcFieldRule, ut as AggregationConfig, zt as IRequestContext } from "./index-DPRH0Cme.mjs";
1
+ import { C as RequestContext, Et as ActionDefinition, F as FastifyWithDecorators, H as CrudController, L as RequestWithExtras, Lt as IController, Rt as IControllerResponse, T as CrudRouterOptions, U as ResourceConfig, V as ResourceDefinition, et as RouteSchemaOptions, jt as RouteMcpConfig, tt as ArcFieldRule, ut as AggregationConfig, zt as IRequestContext } from "./index-Q5gzEIWY.mjs";
2
2
  import { i as RequestScope } from "./types-DjC-KUCz.mjs";
3
- import { c as AnyRecord, t as PermissionCheck } from "./types-Dmw-6Qeo.mjs";
4
- import "./fastify-augmentation-dPNBVeIj.mjs";
3
+ import { l as AnyRecord, r as PermissionCheck } from "./types-ChvvVNP8.mjs";
4
+ import "./fastify-augmentation-0WhPnUH7.mjs";
5
5
  import { FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
6
6
  import { z } from "zod";
7
7
  import { DataAdapter } from "@classytic/repo-core/adapter";
@@ -170,7 +170,7 @@ declare function defineAction<TSchema extends z.ZodTypeAny | undefined = undefin
170
170
  //#endregion
171
171
  //#region src/core/defineResource/seams.d.ts
172
172
  /**
173
- * Structural adapter boundary — the `ControllerLike` pattern (v2.10.6)
173
+ * Structural adapter boundary — the `ControllerLike` pattern
174
174
  * applied to adapters. `DataAdapter<T>` is invariant in `T`, so a
175
175
  * `DataAdapter<HydratedDocument<IProduct>>` built by the HOST does not
176
176
  * assign to a module's `DataAdapter<AnyRecord>` slot; before this type,