@classytic/arc 2.11.3 → 2.13.1

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 (185) hide show
  1. package/README.md +27 -18
  2. package/dist/{BaseController-swXruJ2_.mjs → BaseController-DX_T-bDB.mjs} +388 -423
  3. package/dist/EventTransport-CT_52aWU.d.mts +34 -0
  4. package/dist/EventTransport-DLWoUMHy.mjs +103 -0
  5. package/dist/{QueryCache-DOBNHBE0.d.mts → QueryCache-D41bfdBB.d.mts} +1 -1
  6. package/dist/{ResourceRegistry-DkAeAuTX.mjs → ResourceRegistry-CTERg_2x.mjs} +139 -66
  7. package/dist/audit/index.d.mts +2 -2
  8. package/dist/audit/index.mjs +1 -1
  9. package/dist/auth/audit.d.mts +199 -0
  10. package/dist/auth/audit.mjs +288 -0
  11. package/dist/auth/index.d.mts +5 -5
  12. package/dist/auth/index.mjs +117 -191
  13. package/dist/auth/redis-session.d.mts +1 -1
  14. package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
  15. package/dist/buildHandler-olo-gt94.mjs +610 -0
  16. package/dist/cache/index.d.mts +3 -3
  17. package/dist/cache/index.mjs +3 -3
  18. package/dist/cli/commands/describe.d.mts +89 -13
  19. package/dist/cli/commands/describe.mjs +56 -2
  20. package/dist/cli/commands/docs.mjs +2 -2
  21. package/dist/cli/commands/generate.mjs +147 -48
  22. package/dist/cli/commands/init.d.mts +13 -0
  23. package/dist/cli/commands/init.mjs +237 -112
  24. package/dist/cli/commands/introspect.mjs +8 -1
  25. package/dist/context/index.mjs +1 -1
  26. package/dist/core/index.d.mts +3 -3
  27. package/dist/core/index.mjs +5 -5
  28. package/dist/core-D72ia0EH.mjs +1399 -0
  29. package/dist/{createActionRouter-u3ql2EDo.mjs → createActionRouter-CEvzKcy8.mjs} +7 -20
  30. package/dist/createAggregationRouter-CyecOxnO.mjs +114 -0
  31. package/dist/{createApp-BFxtdKy6.mjs → createApp-XX2-N0Yd.mjs} +31 -27
  32. package/dist/defineEvent-D5h7EvAx.mjs +188 -0
  33. package/dist/docs/index.d.mts +2 -2
  34. package/dist/docs/index.mjs +2 -2
  35. package/dist/{elevation-DOFoxoDs.mjs → elevation-DgoeTyfX.mjs} +1 -1
  36. package/dist/errorHandler-Bk-AGhkU.mjs +174 -0
  37. package/dist/errorHandler-DFr45ZG4.d.mts +45 -0
  38. package/dist/errors-j4aJm1Wg.mjs +184 -0
  39. package/dist/{eventPlugin-KrFIQ097.mjs → eventPlugin-CaKTYkYM.mjs} +35 -137
  40. package/dist/{eventPlugin-CUNjYYRY.d.mts → eventPlugin-qXpqTebY.d.mts} +57 -7
  41. package/dist/events/index.d.mts +164 -5
  42. package/dist/events/index.mjs +133 -209
  43. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  44. package/dist/events/transports/redis-stream-entry.mjs +204 -31
  45. package/dist/events/transports/redis.d.mts +1 -1
  46. package/dist/factory/index.d.mts +2 -2
  47. package/dist/factory/index.mjs +2 -2
  48. package/dist/{fields-C8Y0XLAu.d.mts → fields-COhcH3fk.d.mts} +23 -2
  49. package/dist/hooks/index.d.mts +1 -1
  50. package/dist/hooks/index.mjs +1 -1
  51. package/dist/idempotency/index.d.mts +3 -3
  52. package/dist/idempotency/index.mjs +1 -20
  53. package/dist/idempotency/redis.d.mts +1 -1
  54. package/dist/idempotency/redis.mjs +1 -1
  55. package/dist/{index-BYCqHCVu.d.mts → index-BTqLEvhu.d.mts} +164 -4
  56. package/dist/{index-6u4_Gg6G.d.mts → index-BtW7qYwa.d.mts} +661 -281
  57. package/dist/{index-BdXnTPRj.d.mts → index-Ds61mrJE.d.mts} +50 -4
  58. package/dist/{index-DdQ3O9Pg.d.mts → index-Dz5IKsrE.d.mts} +360 -219
  59. package/dist/index.d.mts +6 -7
  60. package/dist/index.mjs +9 -10
  61. package/dist/integrations/event-gateway.d.mts +2 -2
  62. package/dist/integrations/event-gateway.mjs +1 -1
  63. package/dist/integrations/index.d.mts +2 -2
  64. package/dist/integrations/mcp/index.d.mts +2 -2
  65. package/dist/integrations/mcp/index.mjs +1 -1
  66. package/dist/integrations/mcp/testing.d.mts +1 -1
  67. package/dist/integrations/mcp/testing.mjs +1 -1
  68. package/dist/integrations/streamline.d.mts +60 -11
  69. package/dist/integrations/streamline.mjs +75 -85
  70. package/dist/integrations/websocket-redis.d.mts +1 -1
  71. package/dist/integrations/websocket.d.mts +1 -1
  72. package/dist/integrations/websocket.mjs +2 -8
  73. package/dist/middleware/index.d.mts +1 -1
  74. package/dist/middleware/index.mjs +2 -2
  75. package/dist/migrations/index.d.mts +23 -3
  76. package/dist/migrations/index.mjs +0 -7
  77. package/dist/{multipartBody-CvTR1Un6.mjs → multipartBody-BOvVSVCD.mjs} +11 -8
  78. package/dist/{openapi-BGUn7Ki1.mjs → openapi-CiOMVW1p.mjs} +143 -13
  79. package/dist/org/index.d.mts +2 -2
  80. package/dist/org/index.mjs +1 -1
  81. package/dist/permissions/index.d.mts +3 -3
  82. package/dist/permissions/index.mjs +3 -3
  83. package/dist/{permissions-gd_aUWrR.mjs → permissions-ohQyv50e.mjs} +404 -176
  84. package/dist/{pipe-DVoIheVC.mjs → pipe-Zr0KXjQe.mjs} +1 -1
  85. package/dist/pipeline/index.d.mts +1 -1
  86. package/dist/pipeline/index.mjs +1 -1
  87. package/dist/plugins/index.d.mts +18 -33
  88. package/dist/plugins/index.mjs +33 -13
  89. package/dist/plugins/response-cache.mjs +1 -1
  90. package/dist/plugins/tracing-entry.d.mts +1 -1
  91. package/dist/plugins/tracing-entry.mjs +1 -1
  92. package/dist/presets/filesUpload.d.mts +5 -5
  93. package/dist/presets/filesUpload.mjs +6 -9
  94. package/dist/presets/index.d.mts +1 -1
  95. package/dist/presets/index.mjs +1 -1
  96. package/dist/presets/multiTenant.d.mts +1 -1
  97. package/dist/presets/multiTenant.mjs +2 -2
  98. package/dist/presets/search.d.mts +2 -2
  99. package/dist/presets/search.mjs +6 -8
  100. package/dist/{presets-Z7P5w4gF.mjs → presets-BbkjdPeH.mjs} +6 -28
  101. package/dist/{queryCachePlugin-BUXBSm4F.d.mts → queryCachePlugin-CqMdLI2-.d.mts} +2 -2
  102. package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
  103. package/dist/{redis-Cm1gnRDf.d.mts → redis-DiMkdHEl.d.mts} +1 -1
  104. package/dist/redis-stream-D6HzR1Z_.d.mts +232 -0
  105. package/dist/registry/index.d.mts +1 -1
  106. package/dist/registry/index.mjs +2 -2
  107. package/dist/{replyHelpers-ByllIXXV.mjs → replyHelpers-CK-FNO8E.mjs} +3 -21
  108. package/dist/{resourceToTools-ByZpgjeH.mjs → resourceToTools-C5coh64w.mjs} +224 -71
  109. package/dist/{routerShared-BqLRb5l7.mjs → routerShared-D6_fEGHh.mjs} +40 -36
  110. package/dist/{schemaIR-BlG9bY7v.mjs → schemaIR-7Vl611Qs.mjs} +1 -1
  111. package/dist/schemas/index.d.mts +100 -30
  112. package/dist/schemas/index.mjs +86 -29
  113. package/dist/scim/index.d.mts +264 -0
  114. package/dist/scim/index.mjs +963 -0
  115. package/dist/scope/index.d.mts +3 -3
  116. package/dist/scope/index.mjs +4 -4
  117. package/dist/{sse-V7aXc3bW.mjs → sse-Bz-5ZeTt.mjs} +1 -1
  118. package/dist/{store-helpers-BhrzxvyQ.mjs → store-helpers-BkIN9-vu.mjs} +1 -1
  119. package/dist/testing/index.d.mts +2 -8
  120. package/dist/testing/index.mjs +16 -24
  121. package/dist/testing/storageContract.d.mts +1 -1
  122. package/dist/types/index.d.mts +4 -4
  123. package/dist/types/storage.d.mts +1 -1
  124. package/dist/{types-BH7dEGvU.d.mts → types-BvqwCCSx.d.mts} +77 -29
  125. package/dist/{types-tgR4Pt8F.d.mts → types-CTYvcwHe.d.mts} +195 -1
  126. package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
  127. package/dist/{types-9beEMe25.d.mts → types-DQHFc8PM.d.mts} +1 -1
  128. package/dist/utils/index.d.mts +2 -2
  129. package/dist/utils/index.mjs +5 -5
  130. package/dist/{utils-CcYTj09l.mjs → utils-_h9B3c57.mjs} +1269 -1334
  131. package/dist/{versioning-M9lNLhO8.d.mts → versioning-DTTvc80y.d.mts} +1 -1
  132. package/package.json +24 -34
  133. package/skills/arc/SKILL.md +521 -785
  134. package/skills/arc/references/agent-auth.md +238 -0
  135. package/skills/arc/references/api-reference.md +187 -0
  136. package/skills/arc/references/auth.md +354 -7
  137. package/skills/arc/references/enterprise-auth.md +94 -0
  138. package/skills/arc/references/events.md +8 -6
  139. package/skills/arc/references/mcp.md +2 -2
  140. package/skills/arc/references/multi-tenancy.md +11 -2
  141. package/skills/arc/references/production.md +10 -9
  142. package/skills/arc/references/scim.md +247 -0
  143. package/skills/arc/references/testing.md +1 -1
  144. package/skills/arc-code-review/SKILL.md +141 -0
  145. package/skills/arc-code-review/references/anti-patterns.md +911 -0
  146. package/skills/arc-code-review/references/arc-cheatsheet.md +380 -0
  147. package/skills/arc-code-review/references/migration-recipes.md +700 -0
  148. package/skills/arc-code-review/references/mongokit-migration.md +386 -0
  149. package/skills/arc-code-review/references/scaffolding.md +230 -0
  150. package/skills/arc-code-review/references/severity.md +127 -0
  151. package/dist/EventTransport-CfVEGaEl.d.mts +0 -293
  152. package/dist/adapters/index.d.mts +0 -3
  153. package/dist/adapters/index.mjs +0 -2
  154. package/dist/adapters-D0tT2Tyo.mjs +0 -949
  155. package/dist/auth/mongoose.d.mts +0 -191
  156. package/dist/auth/mongoose.mjs +0 -73
  157. package/dist/core-DnUsRpuX.mjs +0 -1049
  158. package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
  159. package/dist/errorHandler-Co3lnVmJ.d.mts +0 -114
  160. package/dist/errors-D5c-5BJL.mjs +0 -232
  161. package/dist/index-BbMrcvGp.d.mts +0 -362
  162. package/dist/redis-stream-CM8TXTix.d.mts +0 -110
  163. /package/dist/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
  164. /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
  165. /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
  166. /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
  167. /package/dist/{elevation-s5ykdNHr.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
  168. /package/dist/{externalPaths-Bapitwvd.d.mts → externalPaths-BD5nw6St.d.mts} +0 -0
  169. /package/dist/{interface-CkkWm5uR.d.mts → interface-DfLGcus7.d.mts} +0 -0
  170. /package/dist/{interface-Da0r7Lna.d.mts → interface-beEtJyWM.d.mts} +0 -0
  171. /package/dist/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
  172. /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
  173. /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
  174. /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
  175. /package/dist/{pluralize-BneOJkpi.mjs → pluralize-DQgqgifU.mjs} +0 -0
  176. /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
  177. /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
  178. /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.mjs} +0 -0
  179. /package/dist/{sessionManager-D-oNWHz3.d.mts → sessionManager-C4Le_UB3.d.mts} +0 -0
  180. /package/dist/{storage-BwGQXUpd.d.mts → storage-Dfzt4VTl.d.mts} +0 -0
  181. /package/dist/{tracing-DokiEsuz.d.mts → tracing-QJVprktp.d.mts} +0 -0
  182. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
  183. /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
  184. /package/dist/{versioning-CGPjkqAg.mjs → versioning-BUrT5aP4.mjs} +0 -0
  185. /package/dist/{websocket-CyJ1VIFI.d.mts → websocket-ChC2rqe1.d.mts} +0 -0
@@ -13,10 +13,34 @@ var RedisStreamTransport = class {
13
13
  maxLen;
14
14
  maxPayloadBytes;
15
15
  logger;
16
+ /** Tracks the lifecycle policy — set in constructor, read in close(). */
17
+ externalLifecycle;
18
+ closeTimeoutMs;
16
19
  handlers = /* @__PURE__ */ new Map();
17
20
  running = false;
18
21
  pollPromise = null;
22
+ /**
23
+ * Monotonic counter bumped every time the poll loop should stop —
24
+ * `unsubscribe` (last handler removed) and `close()` increment it. Each
25
+ * `pollLoop` instance captures its generation at start and exits when
26
+ * `this.generation` no longer matches. Prevents the
27
+ * subscribe → unsubscribe → fast-resubscribe race where the old loop
28
+ * would still be in `XREADGROUP BLOCK` while a new loop started, leading
29
+ * to two concurrent poll loops on the same consumer name.
30
+ */
31
+ generation = 0;
19
32
  groupCreated = false;
33
+ /**
34
+ * Last-seen failure context per message id, populated when an in-process
35
+ * handler throws in {@link processEntry}. Consumed (and cleared) by
36
+ * {@link moveToDlq} so the dead-letter envelope carries the actual error
37
+ * message instead of opaque "reclaimed without context". Bounded by
38
+ * `maxRetries × consumer-throughput` — entries are deleted on ack and
39
+ * on DLQ write, so the map naturally drains.
40
+ */
41
+ failureContext = /* @__PURE__ */ new Map();
42
+ /** One-shot guard so the "client lacks xrange" warning fires once per process. */
43
+ xrangeWarningEmitted = false;
20
44
  constructor(redis, options = {}) {
21
45
  this.redis = redis;
22
46
  this.stream = options.stream ?? "arc:events";
@@ -29,6 +53,8 @@ var RedisStreamTransport = class {
29
53
  this.deadLetterStream = options.deadLetterStream ?? "arc:events:dlq";
30
54
  this.maxLen = options.maxLen ?? 1e4;
31
55
  this.maxPayloadBytes = options.maxPayloadBytes ?? 1e6;
56
+ this.externalLifecycle = options.externalLifecycle ?? false;
57
+ this.closeTimeoutMs = options.closeTimeoutMs ?? 1e3;
32
58
  this.logger = options.logger ?? console;
33
59
  }
34
60
  async publish(event) {
@@ -55,9 +81,10 @@ var RedisStreamTransport = class {
55
81
  if (!this.running) {
56
82
  await this.ensureGroup();
57
83
  this.running = true;
58
- this.pollPromise = this.pollLoop().catch((err) => {
84
+ const myGen = ++this.generation;
85
+ this.pollPromise = this.pollLoop(myGen).catch((err) => {
59
86
  this.logger.error("[RedisStreamTransport] Poll loop crashed:", err);
60
- this.running = false;
87
+ if (this.generation === myGen) this.running = false;
61
88
  });
62
89
  }
63
90
  return () => {
@@ -66,16 +93,59 @@ var RedisStreamTransport = class {
66
93
  set.delete(handler);
67
94
  if (set.size === 0) this.handlers.delete(pattern);
68
95
  }
69
- if (this.handlers.size === 0 && this.running) this.running = false;
96
+ if (this.handlers.size === 0 && this.running) {
97
+ this.running = false;
98
+ this.generation++;
99
+ }
70
100
  };
71
101
  }
102
+ /**
103
+ * Stop polling and release transport state.
104
+ *
105
+ * **Two close contracts** — pick the one that matches your deployment:
106
+ *
107
+ * 1. **Default (`externalLifecycle: false`) — strict bounded close.**
108
+ * `close()` waits up to `closeTimeoutMs` for the in-flight
109
+ * `XREADGROUP BLOCK` to drain. On timeout it calls `redis.disconnect()`
110
+ * (or `quit()` if the client lacks `disconnect`) to break the BLOCK
111
+ * immediately, then awaits the loop's exit. After `close()` returns
112
+ * the transport is fully closed and the connection is released.
113
+ *
114
+ * 2. **`externalLifecycle: true` — bounded RETURN, background drain.**
115
+ * Arc must NOT touch a connection it doesn't own. `close()` returns
116
+ * within `closeTimeoutMs`, but the poll loop is left to drain on its
117
+ * own when its outstanding `XREADGROUP BLOCK` returns (up to
118
+ * `blockTimeMs`). Arc silently absorbs the loop's eventual completion
119
+ * so the host doesn't see unhandled rejections / log spam against a
120
+ * transport it considers closed. The host's own `redis.quit()` /
121
+ * process exit is what ultimately tears the connection down.
122
+ *
123
+ * Practical implication: under `externalLifecycle: true`, set
124
+ * `blockTimeMs` low (e.g. 500ms) so the background drain window is
125
+ * short. The transport is "closed enough" to stop dispatching to
126
+ * handlers (handlers map is cleared and generation is bumped) but is
127
+ * not "fully closed" in the connection-lifecycle sense until the host
128
+ * closes the underlying client.
129
+ *
130
+ * In both modes the generation counter is bumped, so a follow-up
131
+ * `subscribe()` spawns a fresh poll loop with a new generation — the
132
+ * stale loop exits on its next iteration and never overlaps the new one.
133
+ */
72
134
  async close() {
73
135
  this.running = false;
136
+ this.generation++;
74
137
  this.handlers.clear();
75
138
  if (this.pollPromise) {
76
- await this.pollPromise;
139
+ if (await Promise.race([this.pollPromise.then(() => "drained"), this.sleep(this.closeTimeoutMs).then(() => "timeout")]) === "timeout") if (!this.externalLifecycle) {
140
+ if (typeof this.redis.disconnect === "function") this.redis.disconnect();
141
+ else await this.redis.quit().catch((err) => {
142
+ this.logger.error("[RedisStreamTransport] quit() during close raced:", err);
143
+ });
144
+ await this.pollPromise.catch(() => void 0);
145
+ } else this.pollPromise.catch(() => void 0);
77
146
  this.pollPromise = null;
78
147
  }
148
+ if (!this.externalLifecycle) await this.redis.quit().catch(() => void 0);
79
149
  }
80
150
  async ensureGroup() {
81
151
  if (this.groupCreated) return;
@@ -86,12 +156,12 @@ var RedisStreamTransport = class {
86
156
  }
87
157
  this.groupCreated = true;
88
158
  }
89
- async pollLoop() {
90
- while (this.running) try {
159
+ async pollLoop(myGen) {
160
+ while (this.running && this.generation === myGen) try {
91
161
  await this.claimPending();
92
162
  await this.readNewMessages();
93
163
  } catch (err) {
94
- if (this.running) {
164
+ if (this.running && this.generation === myGen) {
95
165
  this.logger.error("[RedisStreamTransport] Poll error:", err);
96
166
  await this.sleep(1e3);
97
167
  }
@@ -123,39 +193,38 @@ var RedisStreamTransport = class {
123
193
  }
124
194
  }
125
195
  async processEntry(messageId, fields) {
126
- const fieldMap = /* @__PURE__ */ new Map();
127
- for (let i = 0; i < fields.length; i += 2) fieldMap.set(fields[i], fields[i + 1]);
128
- const eventType = fieldMap.get("type");
129
- const rawData = fieldMap.get("data");
130
- if (!eventType || !rawData) {
131
- await this.redis.xack(this.stream, this.group, messageId);
132
- return;
133
- }
134
- let event;
135
- try {
136
- const parsed = JSON.parse(rawData, (key, value) => {
137
- if (key === "timestamp" && typeof value === "string") return new Date(value);
138
- return value;
139
- });
140
- if (!parsed || typeof parsed !== "object" || typeof parsed.type !== "string" || !parsed.meta?.id) {
141
- this.logger.warn("[RedisStreamTransport] Malformed event — missing type or meta.id, acking and skipping");
142
- await this.redis.xack(this.stream, this.group, messageId);
143
- return;
144
- }
145
- event = parsed;
146
- } catch {
196
+ const event = parseStreamFields(fields);
197
+ if (!event) {
198
+ this.logger.warn(`[RedisStreamTransport] Malformed entry ${messageId} — missing type/data or invalid JSON, acking and skipping`);
147
199
  await this.redis.xack(this.stream, this.group, messageId);
148
200
  return;
149
201
  }
150
202
  const matchingHandlers = this.getMatchingHandlers(event.type);
151
203
  let allSucceeded = true;
204
+ let lastError;
205
+ let lastHandlerName;
152
206
  for (const handler of matchingHandlers) try {
153
207
  await handler(event);
154
208
  } catch (err) {
155
209
  allSucceeded = false;
210
+ lastError = err instanceof Error ? err : new Error(String(err));
211
+ lastHandlerName = handler.name || lastHandlerName;
156
212
  this.logger.error(`[RedisStreamTransport] Handler error for ${event.type}:`, err);
157
213
  }
158
- if (allSucceeded) await this.redis.xack(this.stream, this.group, messageId);
214
+ if (allSucceeded) {
215
+ await this.redis.xack(this.stream, this.group, messageId);
216
+ this.failureContext.delete(messageId);
217
+ return;
218
+ }
219
+ const now = /* @__PURE__ */ new Date();
220
+ const prior = this.failureContext.get(messageId);
221
+ this.failureContext.set(messageId, {
222
+ error: lastError ? toErrorRecord(lastError) : { message: "handler returned without acking — no error captured" },
223
+ firstFailedAt: prior?.firstFailedAt ?? now,
224
+ lastFailedAt: now,
225
+ attempts: (prior?.attempts ?? 0) + 1,
226
+ handlerName: lastHandlerName ?? prior?.handlerName
227
+ });
159
228
  }
160
229
  getMatchingHandlers(eventType) {
161
230
  const matched = [];
@@ -173,19 +242,123 @@ var RedisStreamTransport = class {
173
242
  }
174
243
  async moveToDlq(ids) {
175
244
  if (this.deadLetterStream === false) {
176
- for (const id of ids) await this.redis.xack(this.stream, this.group, id);
245
+ for (const id of ids) {
246
+ await this.redis.xack(this.stream, this.group, id);
247
+ this.failureContext.delete(id);
248
+ }
177
249
  return;
178
250
  }
179
251
  for (const id of ids) try {
180
- await this.redis.xadd(this.deadLetterStream, "*", "originalStream", this.stream, "originalId", id, "group", this.group, "failedAt", (/* @__PURE__ */ new Date()).toISOString());
252
+ const envelope = await this.buildDlqEnvelope(id);
253
+ if (!envelope) {
254
+ this.logger.error(`[RedisStreamTransport] DLQ for ${id}: source entry missing AND no failure context — acking to drop`);
255
+ await this.redis.xack(this.stream, this.group, id);
256
+ this.failureContext.delete(id);
257
+ continue;
258
+ }
259
+ await this.redis.xadd(this.deadLetterStream, "*", "type", envelope.event.type, "originalStream", this.stream, "originalId", id, "group", this.group, "data", JSON.stringify(envelope));
181
260
  await this.redis.xack(this.stream, this.group, id);
261
+ this.failureContext.delete(id);
182
262
  } catch (err) {
183
263
  this.logger.error(`[RedisStreamTransport] DLQ write failed for ${id}:`, err);
184
264
  }
185
265
  }
266
+ /**
267
+ * Reconstruct a `DeadLetteredEvent` for a message id. Reads the original
268
+ * entry via `xrange` (when the client supports it) and merges in any
269
+ * in-process failure context. Returns `null` only when BOTH sources are
270
+ * missing — callers ack-and-drop rather than re-queuing a ghost.
271
+ *
272
+ * Graceful degradation paths:
273
+ * - Client lacks `xrange` (older custom wrappers) → log once, build the
274
+ * envelope from `failureContext` alone. Payload is absent but the
275
+ * error reason + attempt accounting still survive.
276
+ * - `xrange` throws (network blip, ACL) → same fallback.
277
+ * - Source entry trimmed before DLQ write → same fallback.
278
+ */
279
+ async buildDlqEnvelope(id) {
280
+ const ctx = this.failureContext.get(id);
281
+ let event = null;
282
+ if (typeof this.redis.xrange === "function") try {
283
+ const fields = (await this.redis.xrange(this.stream, id, id))[0]?.[1];
284
+ if (fields) {
285
+ const parsed = parseStreamFields(fields);
286
+ if (parsed) event = parsed;
287
+ }
288
+ } catch (err) {
289
+ this.logger.error(`[RedisStreamTransport] xrange for DLQ source ${id} failed:`, err);
290
+ }
291
+ else if (!this.xrangeWarningEmitted) {
292
+ this.xrangeWarningEmitted = true;
293
+ this.logger.warn("[RedisStreamTransport] Redis client lacks xrange() — DLQ envelopes will not include the original event payload. Upgrade your client (ioredis ≥4 supports it) or use a wrapper that proxies xrange to enable replay.");
294
+ }
295
+ if (!event && !ctx) return null;
296
+ const fallbackTime = /* @__PURE__ */ new Date();
297
+ return {
298
+ event: event ?? {
299
+ type: "<unknown>",
300
+ payload: null,
301
+ meta: {
302
+ id,
303
+ timestamp: fallbackTime
304
+ }
305
+ },
306
+ error: ctx?.error ?? { message: "exhausted retries — failure occurred on a different consumer; error context not preserved across consumer-group failover" },
307
+ attempts: ctx?.attempts ?? this.maxRetries,
308
+ firstFailedAt: ctx?.firstFailedAt ?? fallbackTime,
309
+ lastFailedAt: ctx?.lastFailedAt ?? fallbackTime,
310
+ ...ctx?.handlerName ? { handlerName: ctx.handlerName } : {}
311
+ };
312
+ }
186
313
  sleep(ms) {
187
314
  return new Promise((resolve) => setTimeout(resolve, ms));
188
315
  }
189
316
  };
317
+ /**
318
+ * Convert a thrown value into the `DeadLetteredEvent.error` shape — message
319
+ * always present, optional `code` (string only) and `stack`. Centralised so
320
+ * the failure-context tracker and the DLQ envelope writer agree.
321
+ */
322
+ function toErrorRecord(err) {
323
+ const e = err instanceof Error ? err : new Error(String(err));
324
+ const code = e.code;
325
+ return {
326
+ message: e.message,
327
+ ...typeof code === "string" ? { code } : {},
328
+ ...e.stack ? { stack: e.stack } : {}
329
+ };
330
+ }
331
+ /**
332
+ * Parse a Redis Stream entry's flat `[key, value, key, value, ...]` field
333
+ * array into a typed `DomainEvent`, or `null` when the entry is malformed
334
+ * (missing `type` / `data`, unparseable JSON, or missing required event
335
+ * structure).
336
+ *
337
+ * Pure on purpose — used by both `processEntry` (the live consumer path)
338
+ * and `buildDlqEnvelope` (the dead-letter writer). Keeping the parse logic
339
+ * in one place avoids the silent drift class that produced the original
340
+ * "DLQ has no payload" bug.
341
+ */
342
+ function parseStreamFields(fields) {
343
+ let eventType;
344
+ let rawData;
345
+ for (let i = 0; i < fields.length; i += 2) {
346
+ const key = fields[i];
347
+ const value = fields[i + 1];
348
+ if (key === "type") eventType = value;
349
+ else if (key === "data") rawData = value;
350
+ }
351
+ if (!eventType || !rawData) return null;
352
+ try {
353
+ const parsed = JSON.parse(rawData, (key, value) => {
354
+ if (key === "timestamp" && typeof value === "string") return new Date(value);
355
+ return value;
356
+ });
357
+ if (!parsed || typeof parsed !== "object" || typeof parsed.type !== "string" || !parsed.meta?.id) return null;
358
+ return parsed;
359
+ } catch {
360
+ return null;
361
+ }
362
+ }
190
363
  //#endregion
191
364
  export { RedisStreamTransport };
@@ -1,4 +1,4 @@
1
- import { i as EventLogger, n as DomainEvent, o as EventTransport, r as EventHandler } from "../../EventTransport-CfVEGaEl.mjs";
1
+ import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "../../EventTransport-CT_52aWU.mjs";
2
2
 
3
3
  //#region src/events/transports/redis.d.ts
4
4
  interface RedisLike {
@@ -1,4 +1,4 @@
1
- import { a as CustomPluginAuthOption, c as RawBodyOptions, d as ResourceLike, f as ResourceModule, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, p as loadResources, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as LoadResourcesOptions } from "../types-BH7dEGvU.mjs";
1
+ import { a as CustomPluginAuthOption, c as RawBodyOptions, d as ResourceLike, f as ResourceModule, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, p as loadResources, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as LoadResourcesOptions } from "../types-BvqwCCSx.mjs";
2
2
  import { FastifyInstance } from "fastify";
3
3
 
4
4
  //#region src/factory/createApp.d.ts
@@ -14,7 +14,7 @@ import { FastifyInstance } from "fastify";
14
14
  * 4. Arc core (fastify.arc, events)
15
15
  * 5. Arc plugins (requestId, health, caching, SSE, metrics, versioning)
16
16
  * 6. Auth (scope decoration, auth strategy, elevation, error handler)
17
- * 7. plugins() — user infra (DB, docs, webhooks)
17
+ * 7. plugins() — user infra (DB, data, webhooks)
18
18
  * 8. bootstrap[] — domain init (singletons, event handlers)
19
19
  * 9. resources[] — auto-discovered routes (prefix + skipGlobalPrefix)
20
20
  * 10. afterResources() — post-registration wiring
@@ -1,5 +1,5 @@
1
- import { a as edgePreset, c as testingPreset, i as developmentPreset, n as createApp, o as getPreset, s as productionPreset, t as ArcFactory } from "../createApp-BFxtdKy6.mjs";
2
- import { t as loadResources } from "../loadResources-CPpkyKfM.mjs";
1
+ import { a as edgePreset, c as testingPreset, i as developmentPreset, n as createApp, o as getPreset, s as productionPreset, t as ArcFactory } from "../createApp-XX2-N0Yd.mjs";
2
+ import { t as loadResources } from "../loadResources-DBMQg_Aj.mjs";
3
3
  //#region src/factory/edge.ts
4
4
  /**
5
5
  * Convert a Fastify app into a Web Standards fetch handler.
@@ -1,4 +1,4 @@
1
- import { r as RequestScope } from "./types-tgR4Pt8F.mjs";
1
+ import { i as RequestScope } from "./types-CTYvcwHe.mjs";
2
2
  import { FastifyRequest } from "fastify";
3
3
 
4
4
  //#region src/permissions/types.d.ts
@@ -134,7 +134,7 @@ interface PermissionResult {
134
134
  type PermissionCheck<TDoc = Record<string, unknown>> = ((context: PermissionContext<TDoc>) => boolean | PermissionResult | Promise<boolean | PermissionResult>) & PermissionCheckMeta;
135
135
  /**
136
136
  * Optional metadata attached to permission check functions.
137
- * Used for OpenAPI docs, introspection, and route-level auth decisions.
137
+ * Used for OpenAPI data, introspection, and route-level auth decisions.
138
138
  *
139
139
  * Each helper from `permissions/index.ts` writes its own discriminating tag
140
140
  * so downstream tooling (OpenAPI generator, MCP resource builder, route
@@ -173,6 +173,27 @@ interface PermissionCheckMeta {
173
173
  * (e.g. from route params).
174
174
  */
175
175
  _orgInScopeTarget?: string | ((ctx: PermissionContext) => string | undefined);
176
+ /**
177
+ * Set by requireDPoP() — the inbound credential must be sender-constrained
178
+ * via DPoP (RFC 9449), with `scope.dpopJkt` set by the authenticate
179
+ * function after a successful proof verification.
180
+ */
181
+ _dpopRequired?: boolean;
182
+ /**
183
+ * Set by requireMandate() — the capability string the mandate on
184
+ * `scope.mandate` must authorize (e.g. `payment.charge`, `data.export`).
185
+ */
186
+ _mandateCapability?: string;
187
+ /**
188
+ * Set by requireAgentScope() — composite descriptor for AI-agent flows.
189
+ * Tools (audit, OpenAPI, MCP) can render the full agent-auth requirement
190
+ * in one read instead of unpacking three separate metadata fields.
191
+ */
192
+ _agentScope?: {
193
+ capability: string;
194
+ scopes?: readonly string[];
195
+ dpop: boolean;
196
+ };
176
197
  }
177
198
  //#endregion
178
199
  //#region src/permissions/fields.d.ts
@@ -1,2 +1,2 @@
1
- import { Cn as beforeUpdate, Sn as beforeDelete, Tn as defineHook, _n as HookSystemOptions, bn as afterUpdate, dn as HookContext, fn as HookHandler, gn as HookSystem, hn as HookRegistration, mn as HookPhase, pn as HookOperation, un as DefineHookOptions, vn as afterCreate, wn as createHookSystem, xn as beforeCreate, yn as afterDelete } from "../index-6u4_Gg6G.mjs";
1
+ import { An as afterUpdate, Cn as HookOperation, Dn as HookSystemOptions, En as HookSystem, Fn as defineHook, Mn as beforeDelete, Nn as beforeUpdate, On as afterCreate, Pn as createHookSystem, Sn as HookHandler, Tn as HookRegistration, bn as DefineHookOptions, jn as beforeCreate, kn as afterDelete, wn as HookPhase, xn as HookContext } from "../index-BtW7qYwa.mjs";
2
2
  export { type DefineHookOptions, type HookContext, type HookHandler, type HookOperation, type HookPhase, type HookRegistration, HookSystem, type HookSystemOptions, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
@@ -1,2 +1,2 @@
1
- import { a as beforeCreate, c as createHookSystem, i as afterUpdate, l as defineHook, n as afterCreate, o as beforeDelete, r as afterDelete, s as beforeUpdate, t as HookSystem } from "../HookSystem-CGsMd6oK.mjs";
1
+ import { a as beforeCreate, c as createHookSystem, i as afterUpdate, l as defineHook, n as afterCreate, o as beforeDelete, r as afterDelete, s as beforeUpdate, t as HookSystem } from "../HookSystem-Iiebom92.mjs";
2
2
  export { HookSystem, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
@@ -1,7 +1,7 @@
1
- import { jn as RepositoryLike } from "../index-6u4_Gg6G.mjs";
2
- import { i as createIdempotencyResult, n as IdempotencyResult, r as IdempotencyStore, t as IdempotencyLock } from "../interface-CkkWm5uR.mjs";
3
- import { i as RedisIdempotencyStoreOptions, n as RedisClient } from "../redis-Cm1gnRDf.mjs";
1
+ import { i as createIdempotencyResult, n as IdempotencyResult, r as IdempotencyStore, t as IdempotencyLock } from "../interface-DfLGcus7.mjs";
2
+ import { i as RedisIdempotencyStoreOptions, n as RedisClient } from "../redis-DiMkdHEl.mjs";
4
3
  import { FastifyPluginAsync } from "fastify";
4
+ import { RepositoryLike } from "@classytic/repo-core/adapter";
5
5
 
6
6
  //#region src/idempotency/idempotencyPlugin.d.ts
7
7
  interface IdempotencyPluginOptions {
@@ -1,28 +1,9 @@
1
- import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-BhrzxvyQ.mjs";
1
+ import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-BkIN9-vu.mjs";
2
2
  import { createHash } from "node:crypto";
3
3
  import fp from "fastify-plugin";
4
4
  import { and, eq, exists, gt, lt, or, startsWith } from "@classytic/repo-core/filter";
5
5
  import { update } from "@classytic/repo-core/update";
6
6
  //#region src/idempotency/repository-idempotency-adapter.ts
7
- /**
8
- * RepositoryLike → IdempotencyStore adapter.
9
- *
10
- * Maps the idempotency store's verbs (get / set / tryLock / unlock / delete /
11
- * deleteByPrefix / findByPrefix) onto arc's canonical repository primitives
12
- * (`getOne` / `deleteMany` / `findOneAndUpdate`). `idempotencyPlugin` wraps
13
- * a passed repository with this helper when you use the `{ repository }`
14
- * option; the function is also re-exported from `@classytic/arc/idempotency`
15
- * so consumers can build and decorate the store (metrics, tracing, key
16
- * namespacing) before passing it via `store:`.
17
- *
18
- * Portability: filters compose via `@classytic/repo-core/filter` builders
19
- * (`and` / `or` / `eq` / `gt` / `lt` / `exists` / `startsWith`) and updates
20
- * via `@classytic/repo-core/update` (`update({ set, unset, setOnInsert })`).
21
- * Both IRs compile to Mongo operators on mongokit, SQL predicates on
22
- * sqlitekit / pgkit, and `WhereInput` / `update` on prismakit. The store
23
- * therefore runs identically on every backend that implements the
24
- * `StandardRepo.findOneAndUpdate` + `getOne` + `deleteMany` surface.
25
- */
26
7
  function repositoryAsIdempotencyStore(repository, defaultTtlMs) {
27
8
  const missing = [];
28
9
  if (typeof repository.getOne !== "function") missing.push("getOne");
@@ -1,2 +1,2 @@
1
- import { a as UpstashRedisLike, i as RedisIdempotencyStoreOptions, n as RedisClient, o as ioredisAsIdempotencyClient, r as RedisIdempotencyStore, s as upstashAsIdempotencyClient, t as IoredisLike } from "../redis-Cm1gnRDf.mjs";
1
+ import { a as UpstashRedisLike, i as RedisIdempotencyStoreOptions, n as RedisClient, o as ioredisAsIdempotencyClient, r as RedisIdempotencyStore, s as upstashAsIdempotencyClient, t as IoredisLike } from "../redis-DiMkdHEl.mjs";
2
2
  export { type IoredisLike, type RedisClient, RedisIdempotencyStore, type RedisIdempotencyStoreOptions, type UpstashRedisLike, ioredisAsIdempotencyClient, upstashAsIdempotencyClient };
@@ -218,7 +218,7 @@ function upstashAsIdempotencyClient(client) {
218
218
  const keyCount = _numKeys;
219
219
  const keys = args.slice(0, keyCount).map(String);
220
220
  const rest = args.slice(keyCount);
221
- return client.eval(script, keys, rest);
221
+ return client.eval?.(script, keys, rest);
222
222
  } : void 0,
223
223
  async quit() {
224
224
  return "OK";
@@ -1,8 +1,168 @@
1
- import { r as CacheStore, t as CacheLogger } from "./interface-Da0r7Lna.mjs";
2
- import { r as RequestScope } from "./types-tgR4Pt8F.mjs";
3
- import { c as PermissionCheck, l as PermissionContext, u as PermissionResult } from "./fields-C8Y0XLAu.mjs";
1
+ import { r as CacheStore, t as CacheLogger } from "./interface-beEtJyWM.mjs";
2
+ import { i as RequestScope, n as Mandate } from "./types-CTYvcwHe.mjs";
3
+ import { c as PermissionCheck, l as PermissionContext, u as PermissionResult } from "./fields-COhcH3fk.mjs";
4
4
  import { FastifyReply, FastifyRequest } from "fastify";
5
5
 
6
+ //#region src/permissions/agent.d.ts
7
+ /**
8
+ * Require a sender-constrained credential — the inbound token MUST carry a
9
+ * DPoP proof (RFC 9449) bound to a known key. Arc reads `scope.dpopJkt` (the
10
+ * JWK SHA-256 thumbprint per RFC 7638); your `authenticate` function performs
11
+ * the cryptographic `jose.dpop.verify(...)` and sets the field on success.
12
+ *
13
+ * **Pass behavior:**
14
+ * - `service` scope where `dpopJkt` is set → grant
15
+ * - `elevated` scope → grant (platform admin bypass)
16
+ * - Anything else → deny with a clear reason
17
+ *
18
+ * Use for high-value endpoints where bearer-token replay must be impossible:
19
+ * payment charges, data exports, account-takeover-class admin actions.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * permissions: { charge: allOf(requireServiceScope('payment.write'), requireDPoP()) }
24
+ * ```
25
+ */
26
+ declare function requireDPoP<TDoc = Record<string, unknown>>(): PermissionCheck<TDoc>;
27
+ /**
28
+ * Options for `requireMandate(capability, opts)`.
29
+ */
30
+ interface RequireMandateOptions<TDoc = Record<string, unknown>> {
31
+ /**
32
+ * Custom validator for the mandate's numeric ceiling against the inbound
33
+ * request. Arc passes the request body / params; you decide whether the
34
+ * action stays within the mandate's `cap`.
35
+ *
36
+ * Return `true` to accept, `false` (or a string reason) to deny. When
37
+ * omitted, arc skips amount validation — useful for boolean-capability
38
+ * mandates where presence of the mandate IS the authorization (no cap).
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * validateAmount: (ctx, mandate) => {
43
+ * const amount = (ctx.data as { amount?: number })?.amount ?? 0;
44
+ * if (amount <= (mandate.cap ?? 0)) return true;
45
+ * return `Amount ${amount} exceeds mandate cap ${mandate.cap}`;
46
+ * }
47
+ * ```
48
+ */
49
+ validateAmount?: (ctx: PermissionContext<TDoc>, mandate: Readonly<Mandate>) => boolean | string;
50
+ /**
51
+ * Resource the mandate must be bound to (`Mandate.audience`). Pass a
52
+ * static value or a function that derives it from the request (typically
53
+ * `ctx.params.id`). When set and the mandate's `audience` doesn't match,
54
+ * the request is denied — prevents a payment-mandate for invoice A being
55
+ * replayed against invoice B.
56
+ */
57
+ audience?: string | ((ctx: PermissionContext<TDoc>) => string | undefined);
58
+ /**
59
+ * Clock-skew tolerance for `Mandate.expiresAt`, in milliseconds.
60
+ * Default `30_000` (30s).
61
+ */
62
+ ttlGraceMs?: number;
63
+ /**
64
+ * When `true`, `elevated` scope is NOT allowed to bypass the mandate check.
65
+ * Defaults to `false` — platform admins normally bypass everything.
66
+ * Set when you genuinely want "even an admin needs a mandate" semantics
67
+ * (audited break-glass actions, regulated payment flows).
68
+ */
69
+ noElevatedBypass?: boolean;
70
+ }
71
+ /**
72
+ * Require a capability mandate (AP2 / x402 / MCP authorization) that
73
+ * authorizes the action being attempted.
74
+ *
75
+ * The mandate is set on `request.scope.mandate` by your authenticate function
76
+ * after verifying the inbound mandate JWT/VC. This check validates that the
77
+ * presented mandate covers the requested capability, hasn't expired, is bound
78
+ * to the right resource (when `audience` opt is set), and respects the
79
+ * mandate's numeric ceiling (when `validateAmount` opt is set).
80
+ *
81
+ * **Pass behavior:**
82
+ * - `elevated` scope → grant unless `noElevatedBypass: true`
83
+ * - `service` scope with mandate matching `capability`, not expired, and
84
+ * passing `validateAmount` + `audience` checks → grant
85
+ * - Anything else → deny with a precise reason
86
+ *
87
+ * Pair with `requireDPoP()` for replay-resistance, or use the bundled
88
+ * `requireAgentScope(...)` to declare both at once.
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * // Single payment charge — amount must fit the mandate's cap
93
+ * permissions: {
94
+ * pay: requireMandate('payment.charge', {
95
+ * validateAmount: (ctx, m) => (ctx.data as { amount: number }).amount <= (m.cap ?? 0),
96
+ * audience: (ctx) => `invoice:${ctx.params?.id}`,
97
+ * }),
98
+ * }
99
+ *
100
+ * // Boolean capability — presence of mandate is the gate
101
+ * permissions: {
102
+ * exportData: requireMandate('data.export'),
103
+ * }
104
+ * ```
105
+ */
106
+ declare function requireMandate<TDoc = Record<string, unknown>>(capability: string, opts?: RequireMandateOptions<TDoc>): PermissionCheck<TDoc>;
107
+ /**
108
+ * Options for `requireAgentScope(opts)`.
109
+ */
110
+ interface RequireAgentScopeOptions<TDoc = Record<string, unknown>> extends RequireMandateOptions<TDoc> {
111
+ /**
112
+ * Capability the mandate must authorize (e.g., `payment.charge`,
113
+ * `inbox.send`). Required.
114
+ */
115
+ capability: string;
116
+ /**
117
+ * When `true`, the inbound credential must also be DPoP-bound (RFC 9449).
118
+ * Defaults to `true` — sender-constrained credentials are the standard
119
+ * for high-value agent flows. Set `false` only when you intentionally
120
+ * accept bearer tokens (rare; usually a regression).
121
+ */
122
+ requireDPoP?: boolean;
123
+ /**
124
+ * Optional OAuth-style scope strings the service identity must hold in
125
+ * addition to the mandate (e.g., `['payment.write']`). Pairs with the
126
+ * mandate's narrower per-request authorization — scopes answer "ever
127
+ * allowed?", mandate answers "right now?".
128
+ */
129
+ scopes?: readonly string[];
130
+ }
131
+ /**
132
+ * Composite gate for AI-agent / M2M flows on protected resources.
133
+ *
134
+ * Bundles the three things every high-value agent endpoint needs:
135
+ * 1. **Service identity** — `scope.kind === 'service'` with `clientId`
136
+ * 2. **Capability mandate** — narrows what *this request* may do
137
+ * 3. **DPoP binding** — credential cannot be replayed from a different key
138
+ *
139
+ * Use this instead of hand-composing `allOf(requireServiceScope(...),
140
+ * requireMandate(...), requireDPoP())` — fewer ways to misconfigure, one
141
+ * meta-tag downstream tools (audit, MCP, OpenAPI) can read.
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * import { requireAgentScope } from '@classytic/arc/permissions';
146
+ *
147
+ * defineResource({
148
+ * name: 'invoice',
149
+ * actions: {
150
+ * pay: {
151
+ * handler: payInvoice,
152
+ * permissions: requireAgentScope({
153
+ * capability: 'payment.charge',
154
+ * scopes: ['payment.write'],
155
+ * requireDPoP: true,
156
+ * audience: (ctx) => `invoice:${ctx.params?.id}`,
157
+ * validateAmount: (ctx, m) => (ctx.data as { amount: number }).amount <= (m.cap ?? 0),
158
+ * }),
159
+ * },
160
+ * },
161
+ * });
162
+ * ```
163
+ */
164
+ declare function requireAgentScope<TDoc = Record<string, unknown>>(opts: RequireAgentScopeOptions<TDoc>): PermissionCheck<TDoc>;
165
+ //#endregion
6
166
  //#region src/permissions/applyPermissionResult.d.ts
7
167
  /**
8
168
  * Normalize a permission check return value (`boolean | PermissionResult`)
@@ -512,4 +672,4 @@ declare function fullPublic<TDoc = any>(overrides?: PermissionOverrides<TDoc>):
512
672
  */
513
673
  declare function readOnly<TDoc = any>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
514
674
  //#endregion
515
- export { requireRoles as A, allOf as C, not as D, denyAll as E, when as M, applyPermissionResult as N, requireAuth as O, normalizePermissionResult as P, createOrgPermissions as S, anyOf as T, ConnectEventsOptions as _, presets_d_exports as a, PermissionEventBus as b, readOnly as c, requireOrgRole as d, requireScopeContext as f, createRoleHierarchy as g, RoleHierarchy as h, ownerWithAdminBypass as i, roles as j, requireOwnership as k, requireOrgInScope as l, requireTeamMembership as m, authenticated as n, publicRead as o, requireServiceScope as p, fullPublic as r, publicReadAdminWrite as s, adminOnly as t, requireOrgMembership as u, DynamicPermissionMatrix as v, allowPublic as w, createDynamicPermissionMatrix as x, DynamicPermissionMatrixConfig as y };
675
+ export { requireRoles as A, allOf as C, not as D, denyAll as E, RequireAgentScopeOptions as F, RequireMandateOptions as I, requireAgentScope as L, when as M, applyPermissionResult as N, requireAuth as O, normalizePermissionResult as P, requireDPoP as R, createOrgPermissions as S, anyOf as T, ConnectEventsOptions as _, presets_d_exports as a, PermissionEventBus as b, readOnly as c, requireOrgRole as d, requireScopeContext as f, createRoleHierarchy as g, RoleHierarchy as h, ownerWithAdminBypass as i, roles as j, requireOwnership as k, requireOrgInScope as l, requireTeamMembership as m, authenticated as n, publicRead as o, requireServiceScope as p, fullPublic as r, publicReadAdminWrite as s, adminOnly as t, requireOrgMembership as u, DynamicPermissionMatrix as v, allowPublic as w, createDynamicPermissionMatrix as x, DynamicPermissionMatrixConfig as y, requireMandate as z };