@classytic/arc 2.28.0 → 2.31.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/LICENSE +1 -1
- package/README.md +7 -0
- package/TRADEMARK.md +22 -0
- package/dist/{BaseController-B65zbz9m.mjs → BaseController-De7GqHck.mjs} +25 -24
- package/dist/{EventTransport-QmY7A258.d.mts → EventTransport-2uVV34WU.d.mts} +1 -0
- package/dist/EventTransport-9mlTgqzE.mjs +104 -0
- package/dist/{HookSystem-C1vB_Gc9.mjs → HookSystem-BcmPyq5I.mjs} +16 -35
- package/dist/{QueryCache-DOMT_iq7.d.mts → QueryCache-DFA-QrU9.d.mts} +2 -1
- package/dist/{ResourceRegistry-y9Hnypus.mjs → ResourceRegistry-qq0mn2s9.mjs} +3 -2
- package/dist/{actionPermissions-CAphcmUG.mjs → actionPermissions-BE5NwaQk.mjs} +1 -0
- package/dist/audit/index.d.mts +3 -2
- package/dist/audit/index.mjs +3 -2
- package/dist/auth/audit.d.mts +1 -0
- package/dist/auth/audit.mjs +1 -0
- package/dist/auth/index.d.mts +5 -4
- package/dist/auth/index.mjs +6 -5
- package/dist/auth/redis-session.d.mts +2 -1
- package/dist/auth/redis-session.mjs +1 -0
- package/dist/authorizationDecision-ChvUQGG1.mjs +266 -0
- package/dist/{buildHandler-Dtwd8fdz.mjs → buildHandler-CDrCJzmB.mjs} +34 -7
- package/dist/cache/index.d.mts +4 -3
- package/dist/cache/index.mjs +4 -3
- package/dist/{caching-_NJeDd1_.mjs → caching-BFHA6Scf.mjs} +2 -1
- package/dist/cleanup/index.d.mts +4 -3
- package/dist/cleanup/index.mjs +9 -13
- package/dist/cli/commands/describe.d.mts +2 -1
- package/dist/cli/commands/describe.mjs +3 -2
- package/dist/cli/commands/docs.d.mts +1 -0
- package/dist/cli/commands/docs.mjs +3 -2
- package/dist/cli/commands/doctor.d.mts +1 -0
- package/dist/cli/commands/doctor.mjs +1 -0
- package/dist/cli/commands/generate.d.mts +2 -1
- package/dist/cli/commands/generate.mjs +2 -1
- package/dist/cli/commands/init.d.mts +2 -1
- package/dist/cli/commands/init.mjs +2 -1
- package/dist/cli/commands/introspect.d.mts +1 -0
- package/dist/cli/commands/introspect.mjs +2 -1
- package/dist/cli/index.d.mts +3 -2
- package/dist/cli/index.mjs +3 -2
- package/dist/{constants-DNre0wRg.mjs → constants-D3Dv4LAV.mjs} +1 -0
- package/dist/context/index.d.mts +1 -0
- package/dist/context/index.mjs +3 -2
- package/dist/core/index.d.mts +3 -2
- package/dist/core/index.mjs +7 -5
- package/dist/{core-BGa8TXJR.mjs → core-BGpIxINN.mjs} +4 -49
- package/dist/{createActionRouter-C-N7z2EL.mjs → createActionRouter-CDXEdasl.mjs} +8 -7
- package/dist/{createAggregationRouter-wzwFa_dW.mjs → createAggregationRouter-KrQDzx-k.mjs} +10 -22
- package/dist/{createApp-Du5G54TM.mjs → createApp-Df89BEni.mjs} +505 -209
- package/dist/defineAggregation-DbPPS8RI.mjs +48 -0
- package/dist/{defineResource-2zvVVpxg.mjs → defineResource-Bv6EtN1x.mjs} +90 -72
- package/dist/discovery/index.d.mts +1 -0
- package/dist/discovery/index.mjs +1 -0
- package/dist/docs/index.d.mts +3 -2
- package/dist/docs/index.mjs +3 -2
- package/dist/{elevation-C9s5hFP_.d.mts → elevation-BXCfVcNG.d.mts} +1 -0
- package/dist/{elevation-CIz1GjGm.mjs → elevation-D2l1mh4g.mjs} +4 -3
- package/dist/encryption/index.d.mts +2 -1
- package/dist/encryption/index.mjs +3 -2
- package/dist/{errorHandler-Bq2OeEQL.mjs → errorHandler-C5NeD6bT.mjs} +3 -2
- package/dist/{errorHandler-C6yv4ZTW.d.mts → errorHandler-CpYUJmPA.d.mts} +1 -0
- package/dist/{errors-DHcHVAgk.d.mts → errors-BGdVwP25.d.mts} +1 -0
- package/dist/{errors-DkO8Nk6-.mjs → errors-Cj42AntO.mjs} +1 -0
- package/dist/{eventPlugin-DrQ_FesK.mjs → eventPlugin-CkffEihm.mjs} +6 -106
- package/dist/{eventPlugin-Bg3cP2_u.d.mts → eventPlugin-GF5nnBIq.d.mts} +2 -1
- package/dist/events/index.d.mts +4 -3
- package/dist/events/index.mjs +17 -340
- package/dist/events/transports/redis-stream-entry.d.mts +2 -1
- package/dist/events/transports/redis-stream-entry.mjs +1 -0
- package/dist/events/transports/redis.d.mts +2 -1
- package/dist/events/transports/redis.mjs +1 -0
- package/dist/{externalPaths-BE6wFpYv.d.mts → externalPaths-CmGOxOtD.d.mts} +1 -0
- package/dist/factory/index.d.mts +9 -5
- package/dist/factory/index.mjs +7 -6
- package/dist/{fastify-augmentation-OzYGIiU1.d.mts → fastify-augmentation-zh0_AYHa.d.mts} +13 -4
- package/dist/{applyPermissionResult-BnyVE9eD.mjs → fields-CB5Cuzk9.mjs} +2 -107
- package/dist/{fields-D3lhgzhD.d.mts → fields-I_EeRWND.d.mts} +1 -0
- package/dist/{generate-BnsL9GBm.d.mts → generate-DCGuEbqI.d.mts} +1 -0
- package/dist/{generate-D0wZupBg.mjs → generate-EcA6ui4i.mjs} +2 -1
- package/dist/{headers-BvlQSDMB.mjs → headers-3zvE0rM3.mjs} +2 -1
- package/dist/{health-CB_m4S32.mjs → health-DjZYjM7H.mjs} +1 -0
- package/dist/hooks/index.d.mts +2 -1
- package/dist/hooks/index.mjs +2 -1
- package/dist/idempotency/index.d.mts +3 -2
- package/dist/idempotency/index.mjs +5 -4
- package/dist/idempotency/redis.d.mts +2 -1
- package/dist/idempotency/redis.mjs +1 -0
- package/dist/{index-CjVg-Epg.d.mts → index-Bc2ox8_U.d.mts} +6 -5
- package/dist/{index-CG4yqMDB.d.mts → index-C-481Zf2.d.mts} +74 -18
- package/dist/{index-BMnbzvtO.d.mts → index-CEJNZFtX.d.mts} +242 -24
- package/dist/index-CFThn1K9.d.mts +224 -0
- package/dist/{index-DPRH0Cme.d.mts → index-CpzxqePp.d.mts} +203 -97
- package/dist/index.d.mts +9 -8
- package/dist/index.mjs +11 -9
- package/dist/{init-tlRSt667.mjs → init-BEiNZBsJ.mjs} +19 -23
- package/dist/{init-BPEu8cs-.d.mts → init-Cscs4lab.d.mts} +1 -0
- package/dist/integrations/event-gateway.d.mts +3 -2
- package/dist/integrations/event-gateway.mjs +2 -1
- package/dist/integrations/index.d.mts +4 -3
- package/dist/integrations/index.mjs +1 -0
- package/dist/integrations/jobs/index.d.mts +2 -1
- package/dist/integrations/jobs/index.mjs +108 -32
- package/dist/integrations/mcp/index.d.mts +9 -9
- package/dist/integrations/mcp/index.mjs +2 -1
- package/dist/integrations/mcp/testing.d.mts +3 -2
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.d.mts +1 -0
- package/dist/integrations/streamline.mjs +3 -2
- package/dist/integrations/webhooks.d.mts +1 -0
- package/dist/integrations/webhooks.mjs +1 -0
- package/dist/integrations/websocket-pushref-redis.d.mts +2 -1
- package/dist/integrations/websocket-pushref-redis.mjs +1 -0
- package/dist/integrations/websocket-redis.d.mts +2 -1
- package/dist/integrations/websocket-redis.mjs +1 -0
- package/dist/integrations/websocket.d.mts +3 -2
- package/dist/integrations/websocket.mjs +1 -0
- package/dist/{interface-BGZCzOGL.d.mts → interface-BOk3YnTz.d.mts} +1 -0
- package/dist/{interface-Uk8ONJv7.d.mts → interface-DEWNOMza.d.mts} +1 -0
- package/dist/{interface-n5hpKg1d.mjs → interface-DYzM0q-u.mjs} +1 -0
- package/dist/{jsonBody-BlzAoGCe.mjs → jsonBody-BYbLDi5D.mjs} +2 -1
- package/dist/{keys-Cd316u_y.mjs → keys-C0_pa1tY.mjs} +1 -0
- package/dist/{loadResources-DpU8pfjV.mjs → loadResources-yawi9tAE.mjs} +2 -1
- package/dist/{loadResourcesFromEntry-kvGT6EcO.mjs → loadResourcesFromEntry-BAjYZonx.mjs} +2 -1
- package/dist/logger/index.d.mts +1 -0
- package/dist/logger/index.mjs +1 -0
- package/dist/{mcpPlugin-Cs6nRGCt.mjs → mcpPlugin-B_es0hiF.mjs} +52 -60
- package/dist/{memory-CmnzXE5Z.mjs → memory-DR6wpa9F.mjs} +2 -1
- package/dist/{metrics-fdxI5mn_.mjs → metrics-CUdw_wLB.mjs} +2 -1
- package/dist/middleware/index.d.mts +2 -1
- package/dist/middleware/index.mjs +3 -2
- package/dist/migrations/index.d.mts +1 -3
- package/dist/migrations/index.mjs +2 -4
- package/dist/module-0pFJA6YW.mjs +78 -0
- package/dist/{multipartBody-D0eBvkeq.mjs → multipartBody-wOgj_fnj.mjs} +1 -0
- package/dist/{openapi-BLV4OMXb.mjs → openapi-B2L4js1v.mjs} +6 -5
- package/dist/permissions/index.d.mts +5 -4
- package/dist/permissions/index.mjs +6 -4
- package/dist/{permissions-BrtiBKGs.mjs → permissions-L--QDAir.mjs} +518 -316
- package/dist/{pipe-BOLmGIxa.mjs → pipe-DRDNsI2T.mjs} +2 -1
- package/dist/pipeline/index.d.mts +2 -1
- package/dist/pipeline/index.mjs +2 -1
- package/dist/{pipeline-BGRnV-To.mjs → pipeline-UPQFatit.mjs} +7 -20
- package/dist/plugins/index.d.mts +8 -7
- package/dist/plugins/index.mjs +16 -15
- package/dist/plugins/response-cache.d.mts +1 -0
- package/dist/plugins/response-cache.mjs +2 -1
- package/dist/plugins/tracing-entry.d.mts +2 -1
- package/dist/plugins/tracing-entry.mjs +2 -1
- package/dist/{pluralize-BhrgwZb9.mjs → pluralize-O0FZgESl.mjs} +1 -0
- package/dist/{preloadResources-CM4uUaFv.d.mts → preloadResources-CyoJMSiO.d.mts} +117 -12
- package/dist/{preloadResources-DSY5HGpS.mjs → preloadResources-Dz6BjzM6.mjs} +1 -0
- package/dist/presets/filesUpload.d.mts +6 -5
- package/dist/presets/filesUpload.mjs +9 -11
- package/dist/presets/index.d.mts +15 -2
- package/dist/presets/index.mjs +2 -1
- package/dist/presets/multiTenant.d.mts +2 -1
- package/dist/presets/multiTenant.mjs +3 -2
- package/dist/presets/search.d.mts +3 -2
- package/dist/presets/search.mjs +2 -1
- package/dist/{presets-DwFDVKP0.mjs → presets-C4-g4_4P.mjs} +27 -10
- package/dist/projection-BHw6xlOQ.mjs +18 -0
- package/dist/{pushref-registry-BbcnpNTm.d.mts → pushref-registry-Dov9l4qE.d.mts} +1 -0
- package/dist/{queryCachePlugin-DSGuSVLW.mjs → queryCachePlugin-BZr8v46E.mjs} +5 -4
- package/dist/{queryCachePlugin-br4otiqF.d.mts → queryCachePlugin-BzqxconG.d.mts} +3 -2
- package/dist/{rateLimit-DrED-C4v.mjs → rateLimit-CMPY5Qms.mjs} +7 -3
- package/dist/{rateLimitKey-BoWRH3e7.mjs → rateLimitKey-Dn5JNmkj.mjs} +1 -0
- package/dist/{realtime-DEuDu9io.mjs → realtime-C5NoxWFY.mjs} +20 -8
- package/dist/{redis-UDtLaNj6.d.mts → redis-CduhKsJi.d.mts} +2 -1
- package/dist/{redis-stream-uIaauNpZ.d.mts → redis-stream-qN6A-RFv.d.mts} +2 -1
- package/dist/{registerRoute-DTp18Ah_.mjs → registerRoute-CBN84fc7.mjs} +5 -2
- package/dist/registry/index.d.mts +3 -2
- package/dist/registry/index.mjs +3 -2
- package/dist/{registry-9km_w1aP.mjs → registry-4213U4zb.mjs} +3 -2
- package/dist/{renewingLease-3cuQH7h8.mjs → renewingLease-Dll8mGgc.mjs} +1 -0
- package/dist/{replyHelpers-67tRawQZ.mjs → replyHelpers-DIiZKGUt.mjs} +2 -1
- package/dist/repositoryFilter-f9eAfq_l.mjs +45 -0
- package/dist/{requestContext-MrlJwECf.mjs → requestContext-2TxXOJ7T.mjs} +1 -0
- package/dist/{rolldown-runtime-8H4AJuhK.mjs → rolldown-runtime-CUsjNX9R.mjs} +1 -0
- package/dist/{schedules-CjOd2MG9.mjs → schedules-DdtlICTf.mjs} +3 -2
- package/dist/{schemaConverter-DJvodiHj.mjs → schemaConverter-D--wdJM-.mjs} +70 -17
- package/dist/{schemaIR-Ca7ySNPx.mjs → schemaIR-Bm2enbnh.mjs} +2 -1
- package/dist/schemas/index.d.mts +1 -0
- package/dist/schemas/index.mjs +1 -0
- package/dist/scim/index.d.mts +1 -0
- package/dist/scim/index.mjs +2 -1
- package/dist/scope/index.d.mts +3 -2
- package/dist/scope/index.mjs +5 -4
- package/dist/{scope-DfaPntpo.mjs → scope-DHx5Fous.mjs} +4 -3
- package/dist/{sessionManager-BHOM3N_0.d.mts → sessionManager-qimlKV0P.d.mts} +1 -0
- package/dist/{simpleEqualityMatcher-Cg05HPsn.mjs → simpleEqualityMatcher-DjQJ7FKA.mjs} +1 -0
- package/dist/{sse-BQ1w0dfl.mjs → sse-CUTo9y0X.mjs} +5 -4
- package/dist/{sseStream-BQ7Qaynp.mjs → sseStream-B_5XcVVd.mjs} +2 -1
- package/dist/{storage-DLUZ04kK.d.mts → storage-Cx9Sz3Ou.d.mts} +1 -0
- package/dist/{store-helpers-DY6xq4sM.mjs → store-helpers-D1MP_gRG.mjs} +1 -0
- package/dist/{streaming-BQcW6_Kq.mjs → streaming-PSZwi8JV.mjs} +1 -0
- package/dist/subscribe-helpers-C0Tb2aUp.mjs +340 -0
- package/dist/sync/changelog.d.mts +1 -0
- package/dist/sync/changelog.mjs +1 -0
- package/dist/testing/cleanupStoreContract.d.mts +2 -1
- package/dist/testing/cleanupStoreContract.mjs +1 -0
- package/dist/testing/index.d.mts +167 -12
- package/dist/testing/index.mjs +522 -205
- package/dist/testing/outboxStoreContract.d.mts +1 -0
- package/dist/testing/outboxStoreContract.mjs +1 -0
- package/dist/testing/storageContract.d.mts +2 -1
- package/dist/testing/storageContract.mjs +1 -0
- package/dist/{tracing-CsGKQJGg.d.mts → tracing-Dro5eH6j.d.mts} +1 -0
- package/dist/{transactionContext-PNdoyLLm.mjs → transactionContext-dAfBVUrk.mjs} +1 -0
- package/dist/{typeGuards-C9KXUly6.mjs → typeGuards-B0ryPo3I.mjs} +1 -0
- package/dist/types/index.d.mts +6 -5
- package/dist/types/index.mjs +1 -0
- package/dist/types/storage.d.mts +2 -1
- package/dist/types/storage.mjs +1 -0
- package/dist/{types-Dmw-6Qeo.d.mts → types-B7XeLEgK.d.mts} +127 -90
- package/dist/{types-Cc91nXuA.d.mts → types-ByHn9rEO.d.mts} +1 -0
- package/dist/{types-C_ZZRcD6.d.mts → types-C4QE8qnm.d.mts} +1 -0
- package/dist/{types-Cu6vVJmh.d.mts → types-CBgvkp8O.d.mts} +82 -12
- package/dist/{types-DjC-KUCz.d.mts → types-ChuShpCP.d.mts} +1 -0
- package/dist/{types-QFGc3Cjv.d.mts → types-CwcUQ610.d.mts} +2 -6
- package/dist/{types-CSyCxl2V.mjs → types-Cys0ko9Z.mjs} +4 -3
- package/dist/usage/index.d.mts +3 -2
- package/dist/usage/index.mjs +0 -0
- package/dist/{userHelpers-Cg_y3fBz.mjs → userHelpers-ChYJ6CdK.mjs} +1 -0
- package/dist/utils/index.d.mts +16 -10
- package/dist/utils/index.mjs +10 -9
- package/dist/{utils-taUVD4p3.mjs → utils-vL8BrDE2.mjs} +3 -2
- package/dist/{validate-Bk44F2yT.mjs → validate-_KbifpHF.mjs} +2 -1
- package/dist/{versioning-gKWyryT3.mjs → versioning-CHDTor3P.mjs} +3 -2
- package/dist/{versioning-DXgY5BUm.d.mts → versioning-D_rhHfZm.d.mts} +5 -2
- package/dist/{websocket-_Pd0CgR0.d.mts → websocket-BOXVggMf.d.mts} +2 -1
- package/package.json +15 -7
- package/skills/arc/SKILL.md +22 -8
- package/skills/arc/references/api-reference.md +4 -0
- package/skills/arc/references/auth.md +3 -0
- package/skills/arc/references/integrations.md +33 -0
- package/skills/arc/references/mcp.md +6 -7
- package/skills/arc/references/production.md +24 -25
- package/skills/arc-code-review/references/anti-patterns.md +3 -3
- package/dist/index-CZY4EGga.d.mts +0 -107
- package/dist/module-_-M7C5ES.mjs +0 -29
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import { i as
|
|
1
|
+
|
|
2
|
+
import { i as createIdempotencyResult, n as IdempotencyResult, r as IdempotencyStore, t as IdempotencyLock } from "../interface-DEWNOMza.mjs";
|
|
3
|
+
import { i as RedisIdempotencyStoreOptions, n as RedisClient } from "../redis-CduhKsJi.mjs";
|
|
3
4
|
import { FastifyPluginAsync } from "fastify";
|
|
4
5
|
import { RepositoryLike } from "@classytic/repo-core/adapter";
|
|
5
6
|
//#region src/idempotency/idempotencyPlugin.d.ts
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
import { n as
|
|
3
|
-
import { t as
|
|
1
|
+
/*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
|
|
2
|
+
import { n as requireSingleHeaderValue } from "../headers-3zvE0rM3.mjs";
|
|
3
|
+
import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-D1MP_gRG.mjs";
|
|
4
|
+
import { t as createIdempotencyResult } from "../interface-DYzM0q-u.mjs";
|
|
5
|
+
import { and, eq, exists, gt, lt, or, startsWith } from "@classytic/repo-core/filter";
|
|
4
6
|
import { createHash } from "node:crypto";
|
|
5
7
|
import fp from "fastify-plugin";
|
|
6
|
-
import { and, eq, exists, gt, lt, or, startsWith } from "@classytic/repo-core/filter";
|
|
7
8
|
import { update } from "@classytic/repo-core/update";
|
|
8
9
|
//#region src/idempotency/repository-idempotency-adapter.ts
|
|
9
10
|
function repositoryAsIdempotencyStore(repository, defaultTtlMs) {
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
import { a as UpstashRedisLike, i as RedisIdempotencyStoreOptions, n as RedisClient, o as ioredisAsIdempotencyClient, r as RedisIdempotencyStore, s as upstashAsIdempotencyClient, t as IoredisLike } from "../redis-CduhKsJi.mjs";
|
|
2
3
|
export { type IoredisLike, type RedisClient, RedisIdempotencyStore, type RedisIdempotencyStoreOptions, type UpstashRedisLike, ioredisAsIdempotencyClient, upstashAsIdempotencyClient };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import "./
|
|
1
|
+
|
|
2
|
+
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-CpzxqePp.mjs";
|
|
3
|
+
import { i as RequestScope } from "./types-ChuShpCP.mjs";
|
|
4
|
+
import { l as AnyRecord, r as PermissionCheck } from "./types-B7XeLEgK.mjs";
|
|
5
|
+
import "./fastify-augmentation-zh0_AYHa.mjs";
|
|
5
6
|
import { FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
|
|
6
7
|
import { z } from "zod";
|
|
7
8
|
import { DataAdapter } from "@classytic/repo-core/adapter";
|
|
@@ -170,7 +171,7 @@ declare function defineAction<TSchema extends z.ZodTypeAny | undefined = undefin
|
|
|
170
171
|
//#endregion
|
|
171
172
|
//#region src/core/defineResource/seams.d.ts
|
|
172
173
|
/**
|
|
173
|
-
* Structural adapter boundary — the `ControllerLike` pattern
|
|
174
|
+
* Structural adapter boundary — the `ControllerLike` pattern
|
|
174
175
|
* applied to adapters. `DataAdapter<T>` is invariant in `T`, so a
|
|
175
176
|
* `DataAdapter<HydratedDocument<IProduct>>` built by the HOST does not
|
|
176
177
|
* assign to a module's `DataAdapter<AnyRecord>` slot; before this type,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
|
|
2
|
+
import { r as PermissionCheck } from "./types-B7XeLEgK.mjs";
|
|
1
3
|
import { FastifyPluginAsync } from "fastify";
|
|
2
4
|
//#region src/integrations/jobs/execution.d.ts
|
|
3
5
|
/**
|
|
@@ -43,18 +45,6 @@ interface ExecuteTimedHandlerOptions<T> {
|
|
|
43
45
|
declare function executeTimedHandler<T>(options: ExecuteTimedHandlerOptions<T>): Promise<T>;
|
|
44
46
|
//#endregion
|
|
45
47
|
//#region src/integrations/jobs/types.d.ts
|
|
46
|
-
/**
|
|
47
|
-
* Job queue integration — public contract (definitions, meta, dispatcher).
|
|
48
|
-
*
|
|
49
|
-
* Directory map (mechanical split of the former single file):
|
|
50
|
-
* types.ts — this contract + `defineJob`
|
|
51
|
-
* execution.ts — `executeTimedHandler` (timeout / cancellation / bulkhead)
|
|
52
|
-
* repeat.ts — repeatable-schedule reconciliation (boot hygiene)
|
|
53
|
-
* plugin.ts — BullMQ wiring (queues, workers, DLQ, routes, shutdown)
|
|
54
|
-
* index.ts — public barrel; `src/integrations/jobs.ts` re-exports it
|
|
55
|
-
* so the package subpath is unchanged
|
|
56
|
-
*/
|
|
57
|
-
/** Repeat schedule — cron pattern or fixed interval. Explicit timezone is required. */
|
|
58
48
|
interface JobRepeatOptions {
|
|
59
49
|
/** Cron pattern (e.g. '0 9 * * *' = every day 09:00). Mutually exclusive with `every`. */
|
|
60
50
|
pattern?: string;
|
|
@@ -177,6 +167,61 @@ interface JobsPluginOptions {
|
|
|
177
167
|
jobs: JobDefinition[];
|
|
178
168
|
/** URL prefix for job management endpoints (default: '/jobs') */
|
|
179
169
|
prefix?: string;
|
|
170
|
+
/**
|
|
171
|
+
* Job management HTTP endpoints (`GET <prefix>/stats`,
|
|
172
|
+
* `GET <prefix>/:queue/:id/status`).
|
|
173
|
+
*
|
|
174
|
+
* **Disabled by default.** A job status carries `returnValue` and
|
|
175
|
+
* `failedReason` — the handler's own output and error text, which routinely
|
|
176
|
+
* hold export URLs, billing results, model output, recipient addresses, or
|
|
177
|
+
* internal stack detail. Serving that unauthenticated turns a job id into a
|
|
178
|
+
* read primitive over other tenants' work.
|
|
179
|
+
*
|
|
180
|
+
* Enable it deliberately, with a permission check:
|
|
181
|
+
*
|
|
182
|
+
* ```ts
|
|
183
|
+
* managementRoutes: { operatorPermission: requireRoles(["platform-ops"]) }
|
|
184
|
+
* ```
|
|
185
|
+
*
|
|
186
|
+
* Queue-level counts are also available through the metrics/health
|
|
187
|
+
* integration, which does not expose individual jobs.
|
|
188
|
+
*/
|
|
189
|
+
managementRoutes?: false | {
|
|
190
|
+
/**
|
|
191
|
+
* REQUIRED — arc will not mount an unguarded management surface.
|
|
192
|
+
*
|
|
193
|
+
* **This is an OPERATOR surface, not a tenant-facing one.** A BullMQ job
|
|
194
|
+
* carries no framework-level tenant identity, and `getStatus` fetches
|
|
195
|
+
* straight by queue + id, so a row-level `policy` on the decision has
|
|
196
|
+
* nothing to filter: `requireOrgRole("manager")` would let a manager in
|
|
197
|
+
* org A read a known job handle from org B. `/stats` is inherently
|
|
198
|
+
* global for the same reason.
|
|
199
|
+
*
|
|
200
|
+
* Gate it on an operator role — `requireRoles(["platform-ops"])` — and
|
|
201
|
+
* keep tenant-facing job status in a resource that owns its own row
|
|
202
|
+
* policy. Arc REFUSES a request whose decision carries a `policy`
|
|
203
|
+
* rather than silently ignoring it.
|
|
204
|
+
*/
|
|
205
|
+
operatorPermission: PermissionCheck;
|
|
206
|
+
/**
|
|
207
|
+
* Accept an `operatorPermission` that arc cannot prove is platform-only.
|
|
208
|
+
*
|
|
209
|
+
* By default the gate must carry `_platformOnly` (i.e. come from
|
|
210
|
+
* `requirePlatformRole`), because that property is unprovable from
|
|
211
|
+
* outside: `requireOrgRole("manager")` and the default
|
|
212
|
+
* `requireRoles(["ops"])` both return a bare allow with no policy, so a
|
|
213
|
+
* manager in org A passes and reads a known handle from org B. Boot fails
|
|
214
|
+
* rather than serving a surface whose guard reads as tenant-scoped.
|
|
215
|
+
*
|
|
216
|
+
* Set this only for a custom check you have verified consults platform
|
|
217
|
+
* identity alone.
|
|
218
|
+
*/
|
|
219
|
+
allowUnverifiedOperatorPermission?: boolean;
|
|
220
|
+
/** Include the handler's return value in a status response. Default `false`. */
|
|
221
|
+
exposeResult?: boolean;
|
|
222
|
+
/** Include `failedReason` in a status response. Default `false`. */
|
|
223
|
+
exposeFailureReason?: boolean;
|
|
224
|
+
};
|
|
180
225
|
/** Bridge job events to Arc's event bus (default: true) */
|
|
181
226
|
bridgeEvents?: boolean;
|
|
182
227
|
/** Default job options applied to all jobs */
|
|
@@ -192,19 +237,30 @@ interface JobsPluginOptions {
|
|
|
192
237
|
};
|
|
193
238
|
}
|
|
194
239
|
interface JobDispatcher {
|
|
195
|
-
dispatch<TData = unknown>(name: string, data: TData, options?: JobDispatchOptions): Promise<
|
|
196
|
-
jobId: string;
|
|
197
|
-
}>;
|
|
240
|
+
dispatch<TData = unknown>(name: string, data: TData, options?: JobDispatchOptions): Promise<JobHandle>;
|
|
198
241
|
getQueue(name: string): unknown | null;
|
|
199
242
|
getStats(): Promise<Record<string, QueueStats>>;
|
|
200
243
|
/**
|
|
201
244
|
* Fetch a point-in-time status snapshot for a job by its ID.
|
|
202
|
-
*
|
|
203
|
-
*
|
|
245
|
+
* QUEUE-QUALIFIED: BullMQ job ids are local to their queue, so an id alone
|
|
246
|
+
* cannot identify a job. `dispatch()` returns the queue name alongside the id
|
|
247
|
+
* for exactly this reason.
|
|
248
|
+
*
|
|
249
|
+
* Returns `null` if the queue is not registered, or the job doesn't exist /
|
|
250
|
+
* has been removed by retention policy.
|
|
204
251
|
*/
|
|
205
|
-
getStatus(jobId: string): Promise<JobStatus | null>;
|
|
252
|
+
getStatus(queue: string, jobId: string): Promise<JobStatus | null>;
|
|
206
253
|
close(): Promise<void>;
|
|
207
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* A dispatched job's address. The queue is part of the identity because BullMQ
|
|
257
|
+
* ids are queue-local — two queues can both hold `"123"`.
|
|
258
|
+
*/
|
|
259
|
+
interface JobHandle {
|
|
260
|
+
/** Registered job name (= queue name). */
|
|
261
|
+
queue: string;
|
|
262
|
+
jobId: string;
|
|
263
|
+
}
|
|
208
264
|
interface QueueStats {
|
|
209
265
|
waiting: number;
|
|
210
266
|
active: number;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
|
|
2
|
+
import { r as CacheStore, t as CacheLogger } from "./interface-BOk3YnTz.mjs";
|
|
3
|
+
import { i as RequestScope, n as Mandate } from "./types-ChuShpCP.mjs";
|
|
4
|
+
import { i as PermissionContext, r as PermissionCheck, t as AuthorizationDecision } from "./types-B7XeLEgK.mjs";
|
|
4
5
|
import { FastifyReply, FastifyRequest } from "fastify";
|
|
5
6
|
//#region src/permissions/agent.d.ts
|
|
6
7
|
/**
|
|
@@ -162,13 +163,37 @@ interface RequireAgentScopeOptions<TDoc = Record<string, unknown>> extends Requi
|
|
|
162
163
|
*/
|
|
163
164
|
declare function requireAgentScope<TDoc = Record<string, unknown>>(opts: RequireAgentScopeOptions<TDoc>): PermissionCheck<TDoc>;
|
|
164
165
|
//#endregion
|
|
165
|
-
//#region src/permissions/
|
|
166
|
+
//#region src/permissions/authorizationDecision.d.ts
|
|
166
167
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
168
|
+
* A permission check returns either a bare boolean (terse allow/deny sugar) or
|
|
169
|
+
* a full {@link AuthorizationDecision}. There is no legacy `PermissionResult` —
|
|
170
|
+
* `AuthorizationDecision` is arc's single authorization contract.
|
|
170
171
|
*/
|
|
171
|
-
|
|
172
|
+
type PermissionCheckReturn = boolean | AuthorizationDecision;
|
|
173
|
+
/**
|
|
174
|
+
* Normalize a check return into the canonical {@link AuthorizationDecision}.
|
|
175
|
+
* A boolean promotes to `{ effect: "allow" | "deny" }`; a decision passes
|
|
176
|
+
* through. This is the ONE place booleans are promoted, so the rest of the
|
|
177
|
+
* pipeline only ever handles decisions.
|
|
178
|
+
*/
|
|
179
|
+
declare function normalizeToDecision(result: PermissionCheckReturn): AuthorizationDecision;
|
|
180
|
+
/**
|
|
181
|
+
* Run a permission check and return a canonical {@link AuthorizationDecision} —
|
|
182
|
+
* the transport-neutral PDP (HTTP, aggregation, MCP, and any future transport).
|
|
183
|
+
* A thin mapper over {@link evaluatePermissionOutcome}:
|
|
184
|
+
*
|
|
185
|
+
* - a structured `ArcError` is RE-THROWN so the transport surfaces it verbatim
|
|
186
|
+
* (e.g. a tier gate throwing `arc.tier_required` with machine-readable meta);
|
|
187
|
+
* - any OTHER throw is fail-closed to a `deny` decision (a broken check must
|
|
188
|
+
* never fall open to a 500 that skips authorization).
|
|
189
|
+
*
|
|
190
|
+
* Pure w.r.t. the request — it neither reads a reply nor mutates the request.
|
|
191
|
+
* Callers translate the returned decision into their own response mechanism and
|
|
192
|
+
* apply side-effects via {@link applyAuthorizationDecision}. HTTP additionally
|
|
193
|
+
* hardens a *thrown* check to 403-even-when-anonymous — that transport-specific
|
|
194
|
+
* nuance lives in {@link evaluateAndApplyPermission}, not here.
|
|
195
|
+
*/
|
|
196
|
+
declare function evaluatePermissionDecision(check: PermissionCheck, context: PermissionContext): Promise<AuthorizationDecision>;
|
|
172
197
|
/**
|
|
173
198
|
* Minimal shape of a Fastify request that can receive permission side-effects.
|
|
174
199
|
* We avoid depending on the full augmented `FastifyRequest` type here because
|
|
@@ -180,8 +205,9 @@ type RequestSink = FastifyRequest & {
|
|
|
180
205
|
scope?: RequestScope;
|
|
181
206
|
};
|
|
182
207
|
/**
|
|
183
|
-
* Apply
|
|
184
|
-
*
|
|
208
|
+
* Apply an allowed {@link AuthorizationDecision} to a Fastify request — conjoins
|
|
209
|
+
* the decision's row-level `policy` into `_policyFilters` and conditionally
|
|
210
|
+
* installs the `scope`.
|
|
185
211
|
*
|
|
186
212
|
* **Scope install rule:** only writes `scope` when the current request scope
|
|
187
213
|
* is absent or `public`. This prevents downgrading an already-authenticated
|
|
@@ -189,13 +215,41 @@ type RequestSink = FastifyRequest & {
|
|
|
189
215
|
* narrower `service` scope — the original `member` wins because it came from
|
|
190
216
|
* a more authoritative source).
|
|
191
217
|
*
|
|
192
|
-
* Safe to call with a
|
|
193
|
-
*
|
|
194
|
-
*
|
|
218
|
+
* Safe to call with a denied decision — it simply no-ops. Callers should still
|
|
219
|
+
* check `decision.effect` and send an error response before reaching here, but
|
|
220
|
+
* this function tolerates the misuse defensively.
|
|
221
|
+
*
|
|
222
|
+
* This is the enforcement point (PEP) — the ONE place scope + data policy are
|
|
223
|
+
* installed (and the obligation seam lives).
|
|
224
|
+
*/
|
|
225
|
+
declare function applyAuthorizationDecision(decision: AuthorizationDecision, request: RequestSink): void;
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/permissions/context.d.ts
|
|
228
|
+
/**
|
|
229
|
+
* Resolve the request scope for a permission check. Prefers the context's
|
|
230
|
+
* first-class `scope`; falls back to the raw HTTP `request.scope` when present,
|
|
231
|
+
* else `PUBLIC_SCOPE`. Primitives MUST use this instead of touching
|
|
232
|
+
* `ctx.request` so scope threads through combinators and non-HTTP transports
|
|
233
|
+
* (MCP, jobs) — which have no request — resolve identity correctly.
|
|
195
234
|
*/
|
|
196
|
-
declare function
|
|
235
|
+
declare function scopeOf(ctx: PermissionContext): RequestScope;
|
|
197
236
|
//#endregion
|
|
198
237
|
//#region src/permissions/core.d.ts
|
|
238
|
+
/**
|
|
239
|
+
* Build an `allow` {@link AuthorizationDecision}, optionally attaching a row-level
|
|
240
|
+
* `policy` and/or a `scope` to install. The idiomatic "grant" return for arc
|
|
241
|
+
* 2.30 checks (parallels Cedar's `permit`).
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* return allow(); // simple grant
|
|
245
|
+
* return allow({ policy: { ownerId: userId } }); // grant + row policy
|
|
246
|
+
*/
|
|
247
|
+
declare function allow(extra?: Omit<AuthorizationDecision, "effect">): AuthorizationDecision;
|
|
248
|
+
/**
|
|
249
|
+
* Build a `deny` {@link AuthorizationDecision} with an optional human-readable
|
|
250
|
+
* reason (parallels Cedar's `forbid`). The idiomatic "refuse" return.
|
|
251
|
+
*/
|
|
252
|
+
declare function deny(reason?: string): AuthorizationDecision;
|
|
199
253
|
/**
|
|
200
254
|
* Allow public access (no authentication required).
|
|
201
255
|
*
|
|
@@ -248,6 +302,24 @@ declare function requireRoles(roles: readonly string[], options?: {
|
|
|
248
302
|
*/
|
|
249
303
|
includeOrgRoles?: boolean;
|
|
250
304
|
}): PermissionCheck;
|
|
305
|
+
/**
|
|
306
|
+
* Require a PLATFORM role — global roles only. An organization role can never
|
|
307
|
+
* satisfy this gate.
|
|
308
|
+
*
|
|
309
|
+
* `requireRoles(["ops"])` also accepts an ORG role named `ops` (that is its
|
|
310
|
+
* documented default), and `requireOrgRole("manager")` grants ANY organization's
|
|
311
|
+
* manager. Both return a bare allow with no policy, so a surface that is global
|
|
312
|
+
* by construction — a queue dashboard, a cluster-wide operations endpoint —
|
|
313
|
+
* cannot distinguish them from a real operator gate by looking at the decision.
|
|
314
|
+
* That makes "platform-only" a property the check has to DECLARE, which is what
|
|
315
|
+
* `_platformOnly` records and what such a surface can then require.
|
|
316
|
+
*
|
|
317
|
+
* Elevated scope passes: platform admin is the identity this gate is for.
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* permissions: { list: requirePlatformRole("platform-ops") }
|
|
321
|
+
*/
|
|
322
|
+
declare function requirePlatformRole(...args: string[] | [readonly string[]]): PermissionCheck;
|
|
251
323
|
/**
|
|
252
324
|
* Short-form alias of `requireRoles()`. Identical behavior — checks both
|
|
253
325
|
* platform roles AND org roles. Prefer `requireRoles` for new code; this
|
|
@@ -272,11 +344,18 @@ declare function requireOwnership<TDoc = Record<string, unknown>>(ownerField?: E
|
|
|
272
344
|
/**
|
|
273
345
|
* Combine multiple checks — ALL must pass (AND logic).
|
|
274
346
|
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
347
|
+
* Evaluation is PURE — nothing is written to the request. Each child runs
|
|
348
|
+
* against the accumulated state of previous children, threaded through a fresh
|
|
349
|
+
* child context:
|
|
350
|
+
* - `policy` from earlier children is CONJOINED (logical AND) — a later child
|
|
351
|
+
* can add or narrow restrictions but never silently replaces an earlier
|
|
352
|
+
* child's constraint on the same key (conflicting values are preserved under
|
|
353
|
+
* `$and`; see `conjoinPolicyFilters`)
|
|
354
|
+
* - `scope` installed by an earlier child is visible to the next through
|
|
355
|
+
* `scopeOf(ctx)`, and never downgrades an already-authoritative scope
|
|
278
356
|
*
|
|
279
|
-
* The
|
|
357
|
+
* The returned decision carries the merged `policy` + `scope`; the enforcement
|
|
358
|
+
* point (`applyAuthorizationDecision`) applies them to the request once.
|
|
280
359
|
*
|
|
281
360
|
* @example
|
|
282
361
|
* ```typescript
|
|
@@ -449,6 +528,145 @@ declare function createOrgPermissions(config: {
|
|
|
449
528
|
*/
|
|
450
529
|
declare function createDynamicPermissionMatrix(config: DynamicPermissionMatrixConfig): DynamicPermissionMatrix;
|
|
451
530
|
//#endregion
|
|
531
|
+
//#region src/permissions/explain.d.ts
|
|
532
|
+
/**
|
|
533
|
+
* The statically-known requirement of a permission check.
|
|
534
|
+
*
|
|
535
|
+
* - `public` — `allowPublic()`; anyone passes.
|
|
536
|
+
* - `roles` — grants if the caller holds ANY of these (platform OR org roles,
|
|
537
|
+
* unioned — matches how `requireRoles`/`requireOrgRole`/`anyOf` compose).
|
|
538
|
+
* - `scoped` — grants only within a caller SCOPE dimension (`_scopeContext`,
|
|
539
|
+
* e.g. `{ branchRole: "head_office" }`), optionally combined with `roles`. The
|
|
540
|
+
* scope dimensions are known statically even though their per-request VALUES
|
|
541
|
+
* aren't, so a consumer that holds the caller's scope (a FE that knows the
|
|
542
|
+
* active branch's role) can decide it client-side — the piece a flat `roles`
|
|
543
|
+
* requirement could not express.
|
|
544
|
+
* - `authenticated` — the check reads request-time facts (custom auth, service
|
|
545
|
+
* scope, ownership, grants) that no static analysis can decide; the definitive
|
|
546
|
+
* answer is computed per request. Treat as "server decides".
|
|
547
|
+
*/
|
|
548
|
+
type PermissionRequirement = {
|
|
549
|
+
readonly kind: "public";
|
|
550
|
+
} | {
|
|
551
|
+
readonly kind: "roles";
|
|
552
|
+
readonly roles: readonly string[];
|
|
553
|
+
/**
|
|
554
|
+
* `true` when these roles are NECESSARY but not SUFFICIENT — the gate is an
|
|
555
|
+
* `allOf(...)` that also carries an opaque runtime branch (ownership /
|
|
556
|
+
* custom / dynamic / flow-mode …). A role MATCH is then `conditional`, not a
|
|
557
|
+
* definitive `allow`; a MISMATCH is still a definitive `deny`.
|
|
558
|
+
*/
|
|
559
|
+
readonly conditional?: boolean;
|
|
560
|
+
} | {
|
|
561
|
+
readonly kind: "scoped";
|
|
562
|
+
/** Required scope dimensions: `value` string = must equal; `undefined` = must be present. */
|
|
563
|
+
readonly scope: Readonly<Record<string, string | undefined>>;
|
|
564
|
+
/** Roles ALSO required alongside the scope (union of platform + org roles), if any. */
|
|
565
|
+
readonly roles?: readonly string[];
|
|
566
|
+
/** See `roles.conditional` — the composite also has an opaque/contradictory branch. */
|
|
567
|
+
readonly conditional?: boolean;
|
|
568
|
+
} | {
|
|
569
|
+
readonly kind: "authenticated";
|
|
570
|
+
};
|
|
571
|
+
/**
|
|
572
|
+
* Extract a check's static requirement from its `PermissionCheckMeta`. Public
|
|
573
|
+
* wins; then a scope-context gate (optionally with roles); then the union of
|
|
574
|
+
* platform + org roles a role gate declares; otherwise "authenticated"
|
|
575
|
+
* (auth-required / custom / request-time checks). An `allOf` that couldn't be
|
|
576
|
+
* fully captured statically carries `conditional: true` (see `_conditional`).
|
|
577
|
+
*/
|
|
578
|
+
declare function describePermission(check: PermissionCheck): PermissionRequirement;
|
|
579
|
+
/** Result of {@link explainAccess}. */
|
|
580
|
+
interface AccessExplanation {
|
|
581
|
+
/**
|
|
582
|
+
* `allow` / `deny` are DEFINITIVE from static metadata (public, or a role
|
|
583
|
+
* match against the supplied principal). `conditional` means the gate reads
|
|
584
|
+
* request-time state, so the real decision is made per request — this is not a
|
|
585
|
+
* denial, it is "cannot be determined statically".
|
|
586
|
+
*/
|
|
587
|
+
decision: "allow" | "deny" | "conditional";
|
|
588
|
+
/** Human-readable rationale, safe to surface in a UI or an audit report. */
|
|
589
|
+
reason: string;
|
|
590
|
+
/** The check's static requirement (the same value {@link describePermission} returns). */
|
|
591
|
+
requirement: PermissionRequirement;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Explain whether a principal (by roles) passes a permission check, from static
|
|
595
|
+
* metadata alone — for permission-matrix UIs, docs, and audits. Omit `principal`
|
|
596
|
+
* to get the requirement-only view (roles → `conditional`).
|
|
597
|
+
*
|
|
598
|
+
* @example
|
|
599
|
+
* explainAccess(requireRoles("admin"), { roles: ["editor"] })
|
|
600
|
+
* // → { decision: "deny", reason: "requires one of: admin", requirement: { kind: "roles", roles: ["admin"] } }
|
|
601
|
+
*/
|
|
602
|
+
declare function explainAccess(check: PermissionCheck, principal?: {
|
|
603
|
+
roles?: readonly string[];
|
|
604
|
+
orgRoles?: readonly string[];
|
|
605
|
+
/** The caller's scope-context values (e.g. `{ branchRole: "head_office" }`) — supply to decide a `scoped` gate. */
|
|
606
|
+
scope?: Readonly<Record<string, string | undefined>>;
|
|
607
|
+
}): AccessExplanation;
|
|
608
|
+
/**
|
|
609
|
+
* Introspect a whole permission map (`{ list, get, create, ... }` or any
|
|
610
|
+
* action→check record) into `action → requirement`. The reusable core of a
|
|
611
|
+
* `/permissions/matrix` endpoint or an access audit — framework-owned, so a host
|
|
612
|
+
* never hand-rolls `(check as any)._roles` peeking. Undefined slots (a CRUD op
|
|
613
|
+
* with no gate = public-by-omission) surface as `public` so the inventory is
|
|
614
|
+
* honest about what's exposed.
|
|
615
|
+
*/
|
|
616
|
+
declare function describePermissionMap(permissions: Readonly<Record<string, PermissionCheck | undefined>>): Record<string, PermissionRequirement>;
|
|
617
|
+
/**
|
|
618
|
+
* The public attack surface of a permission map — the actions reachable with NO
|
|
619
|
+
* authentication (explicit `allowPublic()` or public-by-omission). Empty is the
|
|
620
|
+
* healthy default for a protected resource; a non-empty list is what a
|
|
621
|
+
* "what's exposed?" audit report should show.
|
|
622
|
+
*/
|
|
623
|
+
declare function collectPublicSurface(permissions: Readonly<Record<string, PermissionCheck | undefined>>): string[];
|
|
624
|
+
//#endregion
|
|
625
|
+
//#region src/permissions/filter-merge.d.ts
|
|
626
|
+
/**
|
|
627
|
+
* Policy-filter composition — logical AND, never last-writer-wins.
|
|
628
|
+
*
|
|
629
|
+
* Row-level policy filters can arrive from several INDEPENDENT sources for one
|
|
630
|
+
* request: a tenant preset, an `ownedByUser` grant, an `allOf(...)` branch, a
|
|
631
|
+
* host permission check. Every one of them is a RESTRICTION the row must
|
|
632
|
+
* satisfy, so composing them is logical AND.
|
|
633
|
+
*
|
|
634
|
+
* The historical `{ ...base, ...incoming }` shallow spread did NOT implement AND:
|
|
635
|
+
* when two sources constrained the SAME key with different values, the later one
|
|
636
|
+
* silently REPLACED the earlier one — widening (or erasing) a restriction another
|
|
637
|
+
* layer deliberately imposed. That is a defense-in-depth defect, not a
|
|
638
|
+
* refinement. "Later can refine" is only sound when the refinement is provably
|
|
639
|
+
* narrower, and a bare overwrite proves nothing.
|
|
640
|
+
*
|
|
641
|
+
* {@link conjoinPolicyFilters} composes with AND semantics while preserving the
|
|
642
|
+
* common case byte-for-byte:
|
|
643
|
+
*
|
|
644
|
+
* - key present on only one side → carried through flat. Non-overlapping
|
|
645
|
+
* filters (the ~99% case) produce exactly the same object the old spread did,
|
|
646
|
+
* so there is ZERO behavior change for them.
|
|
647
|
+
* - same key, deep-equal value → kept once (idempotent — e.g. two checks
|
|
648
|
+
* both pinning the same `organizationId`).
|
|
649
|
+
* - same key, DIFFERENT value → BOTH constraints are preserved under an
|
|
650
|
+
* `$and`, so a row must satisfy both. For flat equality that conjunction is
|
|
651
|
+
* unsatisfiable, so the query returns nothing — the safe, correct outcome for
|
|
652
|
+
* contradictory policies (deny), and the earlier restriction is never dropped.
|
|
653
|
+
*
|
|
654
|
+
* Adapter safety: mongokit / sqlitekit compile `$and` natively at the DB layer,
|
|
655
|
+
* and arc's in-memory `simpleEqualityMatcher` (AccessControl.checkPolicyFilters)
|
|
656
|
+
* already fail-closes on operator-shaped filters. So a produced `$and` is either
|
|
657
|
+
* evaluated correctly by the DB or rejected fail-closed in memory — both safe.
|
|
658
|
+
*
|
|
659
|
+
* This is the pragmatic, adapter-neutral form of the AND semantics. A canonical
|
|
660
|
+
* filter IR in `repo-core` (nested and/or/not nodes each adapter compiles) is the
|
|
661
|
+
* fuller design, but this closes the security defect without one.
|
|
662
|
+
*/
|
|
663
|
+
/**
|
|
664
|
+
* Conjoin two policy-filter maps with AND semantics. Never silently overwrites a
|
|
665
|
+
* same-key constraint with a different value — conflicting keys are preserved
|
|
666
|
+
* under `$and`. See the module doc for the full contract.
|
|
667
|
+
*/
|
|
668
|
+
declare function conjoinPolicyFilters(base: Record<string, unknown> | undefined, incoming: Record<string, unknown> | undefined): Record<string, unknown>;
|
|
669
|
+
//#endregion
|
|
452
670
|
//#region src/permissions/grants.d.ts
|
|
453
671
|
/**
|
|
454
672
|
* The grant-mode lattice, weakest → strongest. Holding a mode satisfies
|
|
@@ -474,7 +692,7 @@ declare function modeSatisfies(held: string | undefined, required: GrantMode): b
|
|
|
474
692
|
* - `{ mode }` — the strongest mode the caller holds on `ctx.resourceId`;
|
|
475
693
|
* the combinator applies the lattice against the required mode.
|
|
476
694
|
* - `{ filters }` — list-shaped resolution: row filters that scope the
|
|
477
|
-
* query to visible records (flows through `
|
|
695
|
+
* query to visible records (flows through the decision's `policy` →
|
|
478
696
|
* `_policyFilters` → the repository query). Grants access with the
|
|
479
697
|
* filters attached.
|
|
480
698
|
* - `{ mode, filters }` — mode gates; filters ride along when it passes.
|
|
@@ -655,8 +873,8 @@ declare function requireScopeContext<TDoc = Record<string, unknown>>(keyOrMap: s
|
|
|
655
873
|
*
|
|
656
874
|
* **Two call shapes:**
|
|
657
875
|
* ```typescript
|
|
658
|
-
* requireOrgInScope('acme-holding')
|
|
659
|
-
* requireOrgInScope((ctx) => ctx.
|
|
876
|
+
* requireOrgInScope('acme-holding') // static
|
|
877
|
+
* requireOrgInScope((ctx) => ctx.params?.orgId) // dynamic — from ctx facts, not the raw request
|
|
660
878
|
* ```
|
|
661
879
|
*
|
|
662
880
|
* `elevated` scope grants unconditionally (cross-org bypass).
|
|
@@ -664,9 +882,9 @@ declare function requireScopeContext<TDoc = Record<string, unknown>>(keyOrMap: s
|
|
|
664
882
|
* @example
|
|
665
883
|
* ```typescript
|
|
666
884
|
* permissions: {
|
|
667
|
-
* list: requireOrgInScope((ctx) => ctx.
|
|
885
|
+
* list: requireOrgInScope((ctx) => ctx.params?.orgId),
|
|
668
886
|
* create: allOf(
|
|
669
|
-
* requireOrgInScope((ctx) => ctx.
|
|
887
|
+
* requireOrgInScope((ctx) => (ctx.data as { organizationId?: string })?.organizationId),
|
|
670
888
|
* requireOrgRole('admin'),
|
|
671
889
|
* ),
|
|
672
890
|
* }
|
|
@@ -772,4 +990,4 @@ declare function fullPublic<TDoc = Record<string, unknown>>(overrides?: Permissi
|
|
|
772
990
|
*/
|
|
773
991
|
declare function readOnly<TDoc = Record<string, unknown>>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
|
|
774
992
|
//#endregion
|
|
775
|
-
export {
|
|
993
|
+
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, roles as J, requirePlatformRole as K, allOf as L, DynamicPermissionMatrix as M, DynamicPermissionMatrixConfig as N, describePermission as O, PermissionEventBus as P, applyAuthorizationDecision as Q, allow as R, modeSatisfies as S, AccessExplanation as T, not as U, deny as V, requireAuth as W, scopeOf as X, when as Y, PermissionCheckReturn 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, requireRoles 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 };
|