@cullet/erp-core 1.5.0 → 2.0.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 (306) hide show
  1. package/KIT_CONTEXT.md +2 -2
  2. package/dist/abac/index.d.cts +2 -2
  3. package/dist/abac/index.d.ts +2 -2
  4. package/dist/aggregate-version.cjs +14 -0
  5. package/dist/aggregate-version.cjs.map +1 -0
  6. package/dist/aggregate-version.js +9 -0
  7. package/dist/aggregate-version.js.map +1 -0
  8. package/dist/app-error.cjs +8 -1
  9. package/dist/app-error.cjs.map +1 -1
  10. package/dist/app-error.js +8 -1
  11. package/dist/app-error.js.map +1 -1
  12. package/dist/application/index.cjs +8 -4
  13. package/dist/application/index.d.cts +2 -2
  14. package/dist/application/index.d.ts +2 -2
  15. package/dist/application/index.js +1 -2
  16. package/dist/authorization-error.cjs +67 -17
  17. package/dist/authorization-error.cjs.map +1 -1
  18. package/dist/authorization-error.d.cts +6 -2
  19. package/dist/authorization-error.d.ts +6 -2
  20. package/dist/authorization-error.js +50 -18
  21. package/dist/authorization-error.js.map +1 -1
  22. package/dist/authorizer.port.d.cts +18 -3
  23. package/dist/authorizer.port.d.ts +18 -3
  24. package/dist/composite-authorizer.d.cts +63 -10
  25. package/dist/composite-authorizer.d.ts +63 -10
  26. package/dist/condition-evaluator.cjs +115 -184
  27. package/dist/condition-evaluator.cjs.map +1 -1
  28. package/dist/condition-evaluator.js +116 -179
  29. package/dist/condition-evaluator.js.map +1 -1
  30. package/dist/core-config.d.cts +53 -6
  31. package/dist/core-config.d.ts +53 -6
  32. package/dist/decorate.cjs +14 -0
  33. package/dist/decorate.js +9 -0
  34. package/dist/domain/index.cjs +107 -2
  35. package/dist/domain/index.cjs.map +1 -0
  36. package/dist/domain/index.d.cts +25 -1
  37. package/dist/domain/index.d.ts +25 -1
  38. package/dist/domain/index.js +99 -3
  39. package/dist/domain/index.js.map +1 -0
  40. package/dist/domain-event-contracts.cjs +12 -8
  41. package/dist/domain-event-contracts.cjs.map +1 -1
  42. package/dist/domain-event-contracts.d.cts +29 -4
  43. package/dist/domain-event-contracts.d.ts +29 -4
  44. package/dist/domain-event-contracts.js +11 -7
  45. package/dist/domain-event-contracts.js.map +1 -1
  46. package/dist/domain-exception.cjs +2 -1
  47. package/dist/domain-exception.cjs.map +1 -1
  48. package/dist/domain-exception.js +2 -1
  49. package/dist/domain-exception.js.map +1 -1
  50. package/dist/errors/index.cjs +1 -1
  51. package/dist/errors/index.d.cts +1 -1
  52. package/dist/errors/index.d.ts +1 -1
  53. package/dist/errors/index.js +2 -2
  54. package/dist/exceptions/index.cjs +2 -2
  55. package/dist/exceptions/index.d.cts +1 -2
  56. package/dist/exceptions/index.d.ts +1 -2
  57. package/dist/exceptions/index.js +2 -2
  58. package/dist/gate-engine-registry.cjs.map +1 -1
  59. package/dist/gate-engine-registry.d.cts +1 -1
  60. package/dist/gate-engine-registry.d.ts +1 -1
  61. package/dist/gate-engine-registry.js.map +1 -1
  62. package/dist/gate-v1-payload.schema.cjs +11 -6
  63. package/dist/gate-v1-payload.schema.cjs.map +1 -1
  64. package/dist/gate-v1-payload.schema.js +11 -6
  65. package/dist/gate-v1-payload.schema.js.map +1 -1
  66. package/dist/hashing.cjs +1 -1
  67. package/dist/hashing.cjs.map +1 -1
  68. package/dist/hashing.js +2 -2
  69. package/dist/hashing.js.map +1 -1
  70. package/dist/immutable.cjs +25 -12
  71. package/dist/immutable.cjs.map +1 -1
  72. package/dist/immutable.js +24 -11
  73. package/dist/immutable.js.map +1 -1
  74. package/dist/index.cjs +13 -10
  75. package/dist/index.cjs.map +1 -1
  76. package/dist/index.d.cts +9 -10
  77. package/dist/index.d.ts +9 -10
  78. package/dist/index.js +7 -9
  79. package/dist/index.js.map +1 -1
  80. package/dist/invalid-state-transition-exception.cjs +6 -6
  81. package/dist/invalid-state-transition-exception.cjs.map +1 -1
  82. package/dist/invalid-state-transition-exception.js +6 -6
  83. package/dist/invalid-state-transition-exception.js.map +1 -1
  84. package/dist/invariant-violation-exception.cjs +2 -2
  85. package/dist/invariant-violation-exception.cjs.map +1 -1
  86. package/dist/invariant-violation-exception.js +2 -2
  87. package/dist/invariant-violation-exception.js.map +1 -1
  88. package/dist/not-found-error.cjs +6 -4
  89. package/dist/not-found-error.cjs.map +1 -1
  90. package/dist/not-found-error.js +6 -4
  91. package/dist/not-found-error.js.map +1 -1
  92. package/dist/outcome.cjs +5 -5
  93. package/dist/outcome.cjs.map +1 -1
  94. package/dist/outcome.js +5 -5
  95. package/dist/outcome.js.map +1 -1
  96. package/dist/parse-gate-payload.d.cts +1 -1
  97. package/dist/parse-gate-payload.d.ts +1 -1
  98. package/dist/path.d.cts +2 -2
  99. package/dist/path.d.ts +2 -2
  100. package/dist/plugin.cjs +23 -13
  101. package/dist/plugin.cjs.map +1 -1
  102. package/dist/plugin.d.cts +22 -8
  103. package/dist/plugin.d.ts +22 -8
  104. package/dist/plugin.js +23 -13
  105. package/dist/plugin.js.map +1 -1
  106. package/dist/policies/engines/index.d.cts +1 -1
  107. package/dist/policies/engines/index.d.ts +1 -1
  108. package/dist/policies/engines/v1/gate/index.d.cts +3 -15
  109. package/dist/policies/engines/v1/gate/index.d.ts +3 -15
  110. package/dist/policies/index.d.cts +1 -1
  111. package/dist/policies/index.d.ts +1 -1
  112. package/dist/policy-bridge.cjs +30 -2
  113. package/dist/policy-bridge.cjs.map +1 -1
  114. package/dist/policy-bridge.d.cts +18 -0
  115. package/dist/policy-bridge.d.ts +18 -0
  116. package/dist/policy-bridge.js +30 -2
  117. package/dist/policy-bridge.js.map +1 -1
  118. package/dist/policy-service.cjs +40 -46
  119. package/dist/policy-service.cjs.map +1 -1
  120. package/dist/policy-service.d.cts +72 -9
  121. package/dist/policy-service.d.ts +72 -9
  122. package/dist/policy-service.js +42 -48
  123. package/dist/policy-service.js.map +1 -1
  124. package/dist/requested-by.cjs +1 -1
  125. package/dist/requested-by.cjs.map +1 -1
  126. package/dist/requested-by.js +1 -1
  127. package/dist/requested-by.js.map +1 -1
  128. package/dist/result.cjs +29 -1
  129. package/dist/result.cjs.map +1 -1
  130. package/dist/result.d.cts +12 -0
  131. package/dist/result.d.ts +12 -0
  132. package/dist/result.js +29 -1
  133. package/dist/result.js.map +1 -1
  134. package/dist/rule.cjs +94 -24
  135. package/dist/rule.cjs.map +1 -1
  136. package/dist/rule.js +94 -24
  137. package/dist/rule.js.map +1 -1
  138. package/dist/ruleset-registry.cjs +19 -0
  139. package/dist/ruleset-registry.cjs.map +1 -1
  140. package/dist/ruleset-registry.js +19 -0
  141. package/dist/ruleset-registry.js.map +1 -1
  142. package/dist/stable-stringify.cjs +43 -3
  143. package/dist/stable-stringify.cjs.map +1 -1
  144. package/dist/stable-stringify.js +38 -4
  145. package/dist/stable-stringify.js.map +1 -1
  146. package/dist/temporal-snapshot.d.cts +87 -0
  147. package/dist/temporal-snapshot.d.ts +87 -0
  148. package/dist/temporal-use-case.cjs +169 -16
  149. package/dist/temporal-use-case.cjs.map +1 -1
  150. package/dist/temporal-use-case.d.cts +76 -43
  151. package/dist/temporal-use-case.d.ts +76 -43
  152. package/dist/temporal-use-case.js +162 -15
  153. package/dist/temporal-use-case.js.map +1 -1
  154. package/dist/unexpected-error.cjs +4 -2
  155. package/dist/unexpected-error.cjs.map +1 -1
  156. package/dist/unexpected-error.js +4 -2
  157. package/dist/unexpected-error.js.map +1 -1
  158. package/dist/uuid-identifier.cjs +140 -2
  159. package/dist/uuid-identifier.cjs.map +1 -1
  160. package/dist/uuid-identifier.d.cts +26 -7
  161. package/dist/uuid-identifier.d.ts +26 -7
  162. package/dist/uuid-identifier.js +135 -3
  163. package/dist/uuid-identifier.js.map +1 -1
  164. package/dist/uuid.cjs +11 -6
  165. package/dist/uuid.cjs.map +1 -1
  166. package/dist/uuid.js +11 -6
  167. package/dist/uuid.js.map +1 -1
  168. package/dist/validation-code.cjs +9 -0
  169. package/dist/validation-code.cjs.map +1 -1
  170. package/dist/validation-code.d.cts +3 -0
  171. package/dist/validation-code.d.ts +3 -0
  172. package/dist/validation-code.js +9 -0
  173. package/dist/validation-code.js.map +1 -1
  174. package/dist/validation-error.cjs +42 -67
  175. package/dist/validation-error.cjs.map +1 -1
  176. package/dist/validation-error.d.cts +24 -8
  177. package/dist/validation-error.d.ts +24 -8
  178. package/dist/validation-error.js +41 -66
  179. package/dist/validation-error.js.map +1 -1
  180. package/dist/validation-exception.cjs +17 -6
  181. package/dist/validation-exception.cjs.map +1 -1
  182. package/dist/validation-exception.d.cts +33 -9
  183. package/dist/validation-exception.d.ts +33 -9
  184. package/dist/validation-exception.js +17 -6
  185. package/dist/validation-exception.js.map +1 -1
  186. package/dist/validation-field.cjs +3 -0
  187. package/dist/validation-field.cjs.map +1 -1
  188. package/dist/validation-field.d.cts +1 -0
  189. package/dist/validation-field.d.ts +1 -0
  190. package/dist/validation-field.js +3 -0
  191. package/dist/validation-field.js.map +1 -1
  192. package/dist/value-object-ruleset.contracts.d.cts +10 -0
  193. package/dist/value-object-ruleset.contracts.d.ts +10 -0
  194. package/dist/value-object.cjs +12 -2
  195. package/dist/value-object.cjs.map +1 -1
  196. package/dist/value-object.d.cts +16 -0
  197. package/dist/value-object.d.ts +16 -0
  198. package/dist/value-object.js +13 -3
  199. package/dist/value-object.js.map +1 -1
  200. package/dist/version.d.cts +27 -0
  201. package/dist/version.d.ts +27 -0
  202. package/dist/versioning/index.d.cts +2 -2
  203. package/dist/versioning/index.d.ts +2 -2
  204. package/meta.json +3 -2
  205. package/package.json +1 -1
  206. package/src/core/abac/authorizer.ts +60 -10
  207. package/src/core/abac/domain/policy-set.ts +52 -5
  208. package/src/core/abac/domain/rule.ts +28 -16
  209. package/src/core/abac/index.ts +7 -1
  210. package/src/core/application/commands/command.ts +14 -1
  211. package/src/core/application/commands/requested-by.ts +12 -3
  212. package/src/core/application/index.ts +2 -1
  213. package/src/core/application/policy-error-mapper.ts +6 -0
  214. package/src/core/application/ports/index.ts +7 -0
  215. package/src/core/application/ports/temporal-repository.port.ts +35 -2
  216. package/src/core/application/queries/index.ts +1 -1
  217. package/src/core/application/queries/query.ts +19 -2
  218. package/src/core/application/temporal/temporal-use-case.ts +31 -4
  219. package/src/core/application/use-case.ts +44 -7
  220. package/src/core/config/core-config.ts +46 -25
  221. package/src/core/config/index.ts +1 -0
  222. package/src/core/config/policy-reporter.ts +32 -1
  223. package/src/core/domain/entity.ts +50 -7
  224. package/src/core/domain/rulesets/entity-ruleset.contracts.ts +0 -2
  225. package/src/core/domain/rulesets/ruleset-registry.ts +24 -0
  226. package/src/core/domain/rulesets/value-object-ruleset.contracts.ts +1 -7
  227. package/src/core/domain/temporal/half-open-interval.ts +34 -0
  228. package/src/core/domain/temporal/temporal-snapshot.ts +13 -2
  229. package/src/core/domain/temporal/transaction-time.ts +19 -15
  230. package/src/core/domain/temporal/valid-time.ts +20 -15
  231. package/src/core/domain/uuid-identifier.ts +5 -3
  232. package/src/core/domain/value-object.ts +17 -0
  233. package/src/core/errors/authentication-error.ts +5 -31
  234. package/src/core/errors/authorization-error.ts +12 -20
  235. package/src/core/errors/business-rule-violation-error.ts +4 -1
  236. package/src/core/errors/idempotency-error.ts +5 -2
  237. package/src/core/errors/index.ts +4 -0
  238. package/src/core/errors/integration-error.ts +4 -24
  239. package/src/core/errors/legacy-incompatible-error.ts +1 -0
  240. package/src/core/errors/not-found-error.ts +4 -1
  241. package/src/core/errors/temporal-error.ts +22 -20
  242. package/src/core/errors/unexpected-error.ts +5 -1
  243. package/src/core/errors/utils/factory-helpers.ts +70 -0
  244. package/src/core/errors/utils/index.ts +5 -0
  245. package/src/core/errors/utils/json-safe.ts +15 -1
  246. package/src/core/errors/validation-error.ts +4 -1
  247. package/src/core/exceptions/business-rule-violation-exception.ts +2 -1
  248. package/src/core/exceptions/domain-exception.ts +9 -1
  249. package/src/core/exceptions/entity-not-found-exception.ts +5 -1
  250. package/src/core/exceptions/invalid-state-transition-exception.ts +5 -2
  251. package/src/core/exceptions/invariant-violation-exception.ts +2 -2
  252. package/src/core/exceptions/validation-code.ts +14 -0
  253. package/src/core/exceptions/validation-exception.ts +44 -5
  254. package/src/core/exceptions/validation-field.ts +11 -1
  255. package/src/core/plugins/plugin.ts +25 -15
  256. package/src/core/plugins/types.ts +4 -2
  257. package/src/core/policies/asof/asof.ts +12 -0
  258. package/src/core/policies/catalog/policy-catalog-entry.ts +3 -1
  259. package/src/core/policies/catalog/policy-catalog.ts +5 -1
  260. package/src/core/policies/context/context-builder.ts +20 -0
  261. package/src/core/policies/context/context-resolver.ts +5 -0
  262. package/src/core/policies/context/context-seed.ts +11 -0
  263. package/src/core/policies/defs/in-memory-policy-definition-repo.ts +0 -2
  264. package/src/core/policies/engines/parse-gate-payload.ts +9 -0
  265. package/src/core/policies/engines/v1/condition-date-operands.ts +112 -0
  266. package/src/core/policies/engines/v1/condition-evaluator.ts +99 -325
  267. package/src/core/policies/engines/v1/condition-schema.ts +23 -19
  268. package/src/core/policies/engines/v1/condition-types.ts +18 -11
  269. package/src/core/policies/index.ts +4 -6
  270. package/src/core/policies/service/policy-service.ts +46 -35
  271. package/src/core/policies/utils/hash.ts +5 -69
  272. package/src/core/policies/utils/result.ts +1 -1
  273. package/src/core/rbac/access-request.ts +12 -2
  274. package/src/core/rbac/authorizer.ts +8 -1
  275. package/src/core/rbac/domain/role.ts +20 -0
  276. package/src/core/rbac/domain/scope.ts +6 -1
  277. package/src/core/result/index.ts +5 -0
  278. package/src/core/result/outcome.ts +5 -7
  279. package/src/core/result/result.ts +34 -1
  280. package/src/core/shared/hashing.ts +6 -3
  281. package/src/core/shared/immutable.ts +22 -4
  282. package/src/core/shared/stable-stringify.ts +91 -4
  283. package/src/core/shared/uuid.ts +11 -6
  284. package/src/core/versioning/domain-event-contracts.ts +43 -15
  285. package/src/core/versioning/index.ts +1 -0
  286. package/src/core/versioning/version.ts +30 -1
  287. package/src/domain/index.ts +5 -0
  288. package/src/examples/rulesets/entity/order-creation-rules-v1.ts +1 -1
  289. package/src/examples/rulesets/entity/order-invariants-v1.ts +2 -4
  290. package/src/examples/rulesets/entity/order-invariants-v2.ts +2 -4
  291. package/src/examples/rulesets/value-object/cpf-rules-v1.ts +2 -4
  292. package/src/examples/rulesets/value-object/cpf-rules-v2.ts +2 -4
  293. package/src/examples/rulesets/value-object/person-name-rules-v1.ts +2 -4
  294. package/src/examples/rulesets/value-object/person-name-rules-v2.ts +2 -4
  295. package/src/result/index.ts +7 -2
  296. package/src/version.ts +1 -1
  297. package/dist/domain-exception.d.cts +0 -7
  298. package/dist/domain-exception.d.ts +0 -7
  299. package/dist/entity.cjs +0 -122
  300. package/dist/entity.cjs.map +0 -1
  301. package/dist/entity.js +0 -111
  302. package/dist/entity.js.map +0 -1
  303. package/dist/use-case.cjs +0 -92
  304. package/dist/use-case.cjs.map +0 -1
  305. package/dist/use-case.js +0 -87
  306. package/dist/use-case.js.map +0 -1
@@ -1,4 +1,48 @@
1
1
  import { r as ErrorCodes, t as AppError } from "./app-error.js";
2
+ //#region src/core/errors/utils/factory-helpers.ts
3
+ /** The envelope fields AppError understands; everything else is metadata. */
4
+ const APP_ERROR_OPTION_KEYS = [
5
+ "cause",
6
+ "type",
7
+ "severity",
8
+ "correlationId",
9
+ "requestId",
10
+ "commandId",
11
+ "createdAtIso",
12
+ "publicMessage"
13
+ ];
14
+ /**
15
+ * Picks the AppError envelope fields out of a flat factory input, so they can
16
+ * be forwarded to the constructor as options instead of leaking into metadata.
17
+ */
18
+ function pickAppErrorOptions(input) {
19
+ return {
20
+ cause: input?.cause,
21
+ type: input?.type,
22
+ severity: input?.severity,
23
+ correlationId: input?.correlationId,
24
+ requestId: input?.requestId,
25
+ commandId: input?.commandId,
26
+ createdAtIso: input?.createdAtIso,
27
+ publicMessage: input?.publicMessage
28
+ };
29
+ }
30
+ /**
31
+ * The complement of {@link pickAppErrorOptions}: everything that is *not* an
32
+ * AppError envelope field, i.e. the caller-supplied metadata. Keeps the cause
33
+ * and the public message out of the internal data block.
34
+ */
35
+ function stripAppErrorOptions(input) {
36
+ const result = { ...input };
37
+ for (const key of APP_ERROR_OPTION_KEYS) delete result[key];
38
+ return result;
39
+ }
40
+ /** Drops keys whose value is `undefined` (mirrors how JSON.stringify treats them). */
41
+ function compactMetadata(input) {
42
+ if (!input) return void 0;
43
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
44
+ }
45
+ //#endregion
2
46
  //#region src/core/errors/authorization-error.ts
3
47
  /**
4
48
  * Raised when an authenticated actor is not allowed to perform a business
@@ -44,7 +88,7 @@ var AuthorizationError = class AuthorizationError extends AppError {
44
88
  reason: "forbidden",
45
89
  ...extractMetadataOnly(input)
46
90
  },
47
- ...extractAppErrorOptions(input)
91
+ ...pickAppErrorOptions(input)
48
92
  });
49
93
  }
50
94
  /**
@@ -66,7 +110,7 @@ var AuthorizationError = class AuthorizationError extends AppError {
66
110
  ...extractMetadataOnly(input),
67
111
  decision: "deny"
68
112
  },
69
- ...extractAppErrorOptions(input)
113
+ ...pickAppErrorOptions(input)
70
114
  });
71
115
  }
72
116
  /**
@@ -87,7 +131,7 @@ var AuthorizationError = class AuthorizationError extends AppError {
87
131
  reason: "missing_role",
88
132
  ...extractMetadataOnly(input)
89
133
  },
90
- ...extractAppErrorOptions(input)
134
+ ...pickAppErrorOptions(input)
91
135
  });
92
136
  }
93
137
  /**
@@ -106,7 +150,7 @@ var AuthorizationError = class AuthorizationError extends AppError {
106
150
  reason: "missing_capability",
107
151
  ...extractMetadataOnly(input)
108
152
  },
109
- ...extractAppErrorOptions(input)
153
+ ...pickAppErrorOptions(input)
110
154
  });
111
155
  }
112
156
  /**
@@ -126,27 +170,15 @@ var AuthorizationError = class AuthorizationError extends AppError {
126
170
  reason: "out_of_scope",
127
171
  ...extractMetadataOnly(input)
128
172
  },
129
- ...extractAppErrorOptions(input)
173
+ ...pickAppErrorOptions(input)
130
174
  });
131
175
  }
132
176
  };
133
- function extractAppErrorOptions(options) {
134
- return {
135
- cause: options?.cause,
136
- type: options?.type,
137
- severity: options?.severity,
138
- correlationId: options?.correlationId,
139
- requestId: options?.requestId,
140
- commandId: options?.commandId,
141
- createdAtIso: options?.createdAtIso,
142
- publicMessage: options?.publicMessage
143
- };
144
- }
145
177
  function extractMetadataOnly(input) {
146
178
  const { cause, type, severity, correlationId, requestId, commandId, createdAtIso, publicMessage, ...metadata } = input;
147
179
  return metadata;
148
180
  }
149
181
  //#endregion
150
- export { AuthorizationError as t };
182
+ export { stripAppErrorOptions as i, compactMetadata as n, pickAppErrorOptions as r, AuthorizationError as t };
151
183
 
152
184
  //# sourceMappingURL=authorization-error.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"authorization-error.js","names":[],"sources":["../src/core/errors/authorization-error.ts"],"sourcesContent":["import { AppError } from \"./app-error.js\";\nimport { ErrorCodes } from \"./error-codes.js\";\nimport type { AppErrorOptions, ErrorSeverity } from \"./types.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype AuthorizationErrorReason =\n | \"forbidden\"\n | \"missing_role\"\n | \"missing_capability\"\n | \"out_of_scope\"\n | \"outside_time_window\"\n | \"policy_denied\"\n | \"unknown\";\n\ntype AuthorizationRequirement = {\n role?: string;\n capability?: string;\n scope?: string; // e.g.: \"school:{id}\" or \"class:{id}\"\n};\n\ntype AuthorizationErrorMetadata = {\n reason: AuthorizationErrorReason;\n\n /** Stable action you evaluated (not an HTTP route — a \"business action\") */\n action: string;\n\n /** Resource identification (without leaking the full payload) */\n resource?: { type: string; id?: string };\n\n /** Optional: actor (do not include sensitive data) */\n actor?: { userId: string; role?: string };\n\n /** Optional: expected requirement */\n required?: AuthorizationRequirement;\n\n /** Optional: policy (when you already have PolicyEvaluation or similar) */\n policyId?: string;\n policyVersion?: number;\n evaluatedAtIso?: string;\n decision?: \"allow\" | \"deny\";\n reasonCode?: string;\n\n /** Optional: additional non-sensitive info */\n details?: string;\n};\n\n/**\n * Options for AuthorizationError factory methods.\n * Combines metadata fields (except 'reason') with common AppError options.\n */\ntype AuthorizationErrorFactoryOptions = Omit<\n AuthorizationErrorMetadata,\n \"reason\"\n> &\n Omit<AppErrorOptions, \"metadata\">;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// AuthorizationError\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Raised when an authenticated actor is not allowed to perform a business\n * action — the \"you may not\" error, distinct from authentication (\"who are\n * you\"). Maps to an HTTP 403 at the edge.\n *\n * The discriminating {@link reason} records *why* access was denied (a flat\n * forbid, a missing role/capability, an out-of-scope target, or a policy\n * decision) so the boundary can shape the response without re-deriving it. The\n * metadata is deliberately built around a stable business `action` and a\n * type/id resource reference rather than an HTTP route or full payload, keeping\n * sensitive data out of logs. Instances are frozen. Construct through the\n * static factories, never directly.\n */\nclass AuthorizationError extends AppError {\n public readonly reason: AuthorizationErrorReason;\n\n private constructor(params: {\n message: string;\n code: string;\n reason: AuthorizationErrorReason;\n metadata: AuthorizationErrorMetadata;\n cause?: unknown;\n type?: string;\n severity?: ErrorSeverity;\n correlationId?: string;\n requestId?: string;\n commandId?: string;\n createdAtIso?: string;\n publicMessage?: string;\n }) {\n super(params.message, params.code, {\n cause: params.cause,\n metadata: params.metadata,\n type: params.type,\n severity: params.severity,\n correlationId: params.correlationId,\n requestId: params.requestId,\n commandId: params.commandId,\n createdAtIso: params.createdAtIso,\n publicMessage: params.publicMessage,\n });\n\n this.reason = params.reason;\n Object.freeze(this);\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Factories\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * A flat denial with no more specific reason — the actor simply may not\n * perform this action. Reach for a more precise factory\n * ({@link AuthorizationError.missingCapability}, {@link AuthorizationError.outOfScope},\n * {@link AuthorizationError.policyDenied}) when the cause is known.\n */\n static forbidden(\n input: AuthorizationErrorFactoryOptions,\n ): AuthorizationError {\n return new AuthorizationError({\n message: \"Action not allowed\",\n code: ErrorCodes.authorization.forbidden,\n reason: \"forbidden\",\n metadata: { reason: \"forbidden\", ...extractMetadataOnly(input) },\n ...extractAppErrorOptions(input),\n });\n }\n\n /**\n * The action was denied by an evaluated policy. Captures the deciding\n * policy's id, version, and evaluation instant into the metadata (with\n * `decision: \"deny\"`) so the denial is auditable back to the exact policy\n * that produced it.\n *\n * @param input - Factory options plus the required `policyId`,\n * `policyVersion`, and `evaluatedAtIso` of the deciding policy.\n */\n static policyDenied(\n input: AuthorizationErrorFactoryOptions & {\n policyId: string;\n policyVersion: number;\n evaluatedAtIso: string;\n },\n ): AuthorizationError {\n return new AuthorizationError({\n message: \"Action denied by policy\",\n code: ErrorCodes.authorization.policyDenied,\n reason: \"policy_denied\",\n metadata: {\n reason: \"policy_denied\",\n ...extractMetadataOnly(input),\n decision: \"deny\",\n },\n ...extractAppErrorOptions(input),\n });\n }\n\n /**\n * The actor holds no role at all that bears on the action — the most basic\n * denial, distinct from {@link AuthorizationError.missingCapability} (which\n * means the actor *has* roles, just none granting the required capability).\n * The expected {@link AuthorizationRequirement} is recorded so the boundary\n * can tell the caller what grant is missing.\n *\n * @param input - Factory options plus the `required` role/capability/scope.\n */\n static missingRole(\n input: AuthorizationErrorFactoryOptions & {\n required: AuthorizationRequirement;\n },\n ): AuthorizationError {\n return new AuthorizationError({\n message: \"Missing required role to perform the action\",\n code: ErrorCodes.authorization.missingRole,\n reason: \"missing_role\",\n metadata: {\n reason: \"missing_role\",\n ...extractMetadataOnly(input),\n },\n ...extractAppErrorOptions(input),\n });\n }\n\n /**\n * The actor lacks a required role or capability. The expected\n * {@link AuthorizationRequirement} is recorded so the boundary can tell the\n * caller precisely what grant is missing.\n *\n * @param input - Factory options plus the `required` role/capability/scope.\n */\n static missingCapability(\n input: AuthorizationErrorFactoryOptions & {\n required: AuthorizationRequirement;\n },\n ): AuthorizationError {\n return new AuthorizationError({\n message: \"Insufficient capability to perform the action\",\n code: ErrorCodes.authorization.missingCapability,\n reason: \"missing_capability\",\n metadata: {\n reason: \"missing_capability\",\n ...extractMetadataOnly(input),\n },\n ...extractAppErrorOptions(input),\n });\n }\n\n /**\n * The actor may perform the action in general, but not on *this* target —\n * the resource falls outside the actor's permitted scope (e.g. a different\n * school or tenant than the one they are bound to).\n *\n * @param input - Factory options plus the optional `required` scope that the\n * target failed to satisfy.\n */\n static outOfScope(\n input: AuthorizationErrorFactoryOptions & {\n required?: AuthorizationRequirement;\n },\n ): AuthorizationError {\n return new AuthorizationError({\n message: \"Action outside the allowed scope\",\n code: ErrorCodes.authorization.outOfScope,\n reason: \"out_of_scope\",\n metadata: { reason: \"out_of_scope\", ...extractMetadataOnly(input) },\n ...extractAppErrorOptions(input),\n });\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction extractAppErrorOptions(options?: Partial<AppErrorOptions>) {\n return {\n cause: options?.cause,\n type: options?.type,\n severity: options?.severity,\n correlationId: options?.correlationId,\n requestId: options?.requestId,\n commandId: options?.commandId,\n createdAtIso: options?.createdAtIso,\n publicMessage: options?.publicMessage,\n };\n}\n\nfunction extractMetadataOnly(\n input: AuthorizationErrorFactoryOptions,\n): Omit<AuthorizationErrorMetadata, \"reason\"> {\n const {\n cause,\n type,\n severity,\n correlationId,\n requestId,\n commandId,\n createdAtIso,\n publicMessage,\n ...metadata\n } = input;\n return metadata;\n}\n\nexport { AuthorizationError };\nexport type {\n AuthorizationErrorMetadata,\n AuthorizationErrorReason,\n AuthorizationRequirement,\n};\n"],"mappings":";;;;;;;;;;;;;;;AA4EA,IAAM,qBAAN,MAAM,2BAA2B,SAAS;CAGtC,YAAoB,QAajB;EACC,MAAM,OAAO,SAAS,OAAO,MAAM;GAC/B,OAAO,OAAO;GACd,UAAU,OAAO;GACjB,MAAM,OAAO;GACb,UAAU,OAAO;GACjB,eAAe,OAAO;GACtB,WAAW,OAAO;GAClB,WAAW,OAAO;GAClB,cAAc,OAAO;GACrB,eAAe,OAAO;EAC1B,CAAC;EAED,KAAK,SAAS,OAAO;EACrB,OAAO,OAAO,IAAI;CACtB;;;;;;;CAYA,OAAO,UACH,OACkB;EAClB,OAAO,IAAI,mBAAmB;GAC1B,SAAS;GACT,MAAM,WAAW,cAAc;GAC/B,QAAQ;GACR,UAAU;IAAE,QAAQ;IAAa,GAAG,oBAAoB,KAAK;GAAE;GAC/D,GAAG,uBAAuB,KAAK;EACnC,CAAC;CACL;;;;;;;;;;CAWA,OAAO,aACH,OAKkB;EAClB,OAAO,IAAI,mBAAmB;GAC1B,SAAS;GACT,MAAM,WAAW,cAAc;GAC/B,QAAQ;GACR,UAAU;IACN,QAAQ;IACR,GAAG,oBAAoB,KAAK;IAC5B,UAAU;GACd;GACA,GAAG,uBAAuB,KAAK;EACnC,CAAC;CACL;;;;;;;;;;CAWA,OAAO,YACH,OAGkB;EAClB,OAAO,IAAI,mBAAmB;GAC1B,SAAS;GACT,MAAM,WAAW,cAAc;GAC/B,QAAQ;GACR,UAAU;IACN,QAAQ;IACR,GAAG,oBAAoB,KAAK;GAChC;GACA,GAAG,uBAAuB,KAAK;EACnC,CAAC;CACL;;;;;;;;CASA,OAAO,kBACH,OAGkB;EAClB,OAAO,IAAI,mBAAmB;GAC1B,SAAS;GACT,MAAM,WAAW,cAAc;GAC/B,QAAQ;GACR,UAAU;IACN,QAAQ;IACR,GAAG,oBAAoB,KAAK;GAChC;GACA,GAAG,uBAAuB,KAAK;EACnC,CAAC;CACL;;;;;;;;;CAUA,OAAO,WACH,OAGkB;EAClB,OAAO,IAAI,mBAAmB;GAC1B,SAAS;GACT,MAAM,WAAW,cAAc;GAC/B,QAAQ;GACR,UAAU;IAAE,QAAQ;IAAgB,GAAG,oBAAoB,KAAK;GAAE;GAClE,GAAG,uBAAuB,KAAK;EACnC,CAAC;CACL;AACJ;AAMA,SAAS,uBAAuB,SAAoC;CAChE,OAAO;EACH,OAAO,SAAS;EAChB,MAAM,SAAS;EACf,UAAU,SAAS;EACnB,eAAe,SAAS;EACxB,WAAW,SAAS;EACpB,WAAW,SAAS;EACpB,cAAc,SAAS;EACvB,eAAe,SAAS;CAC5B;AACJ;AAEA,SAAS,oBACL,OAC0C;CAC1C,MAAM,EACF,OACA,MACA,UACA,eACA,WACA,WACA,cACA,eACA,GAAG,aACH;CACJ,OAAO;AACX"}
1
+ {"version":3,"file":"authorization-error.js","names":[],"sources":["../src/core/errors/utils/factory-helpers.ts","../src/core/errors/authorization-error.ts"],"sourcesContent":["// Shared helpers for the error factories: separating the AppError envelope\n// options from the free-form metadata, and dropping undefined-valued keys.\n// Previously each of authentication/authorization/integration carried its own\n// near-identical copy of these.\n\nimport type { AppErrorOptions } from \"../types.js\";\n\n/** The envelope fields AppError understands; everything else is metadata. */\nconst APP_ERROR_OPTION_KEYS = [\n \"cause\",\n \"type\",\n \"severity\",\n \"correlationId\",\n \"requestId\",\n \"commandId\",\n \"createdAtIso\",\n \"publicMessage\",\n] as const;\n\ntype AppErrorEnvelope = Pick<\n AppErrorOptions,\n (typeof APP_ERROR_OPTION_KEYS)[number]\n>;\n\n/**\n * Picks the AppError envelope fields out of a flat factory input, so they can\n * be forwarded to the constructor as options instead of leaking into metadata.\n */\nfunction pickAppErrorOptions(\n input?: Partial<AppErrorOptions>,\n): AppErrorEnvelope {\n return {\n cause: input?.cause,\n type: input?.type,\n severity: input?.severity,\n correlationId: input?.correlationId,\n requestId: input?.requestId,\n commandId: input?.commandId,\n createdAtIso: input?.createdAtIso,\n publicMessage: input?.publicMessage,\n };\n}\n\n/**\n * The complement of {@link pickAppErrorOptions}: everything that is *not* an\n * AppError envelope field, i.e. the caller-supplied metadata. Keeps the cause\n * and the public message out of the internal data block.\n */\nfunction stripAppErrorOptions<T extends Record<string, unknown>>(\n input: T,\n): Omit<T, (typeof APP_ERROR_OPTION_KEYS)[number]> {\n const result = { ...input };\n for (const key of APP_ERROR_OPTION_KEYS) {\n delete result[key];\n }\n return result;\n}\n\n/** Drops keys whose value is `undefined` (mirrors how JSON.stringify treats them). */\nfunction compactMetadata<T extends Record<string, unknown>>(\n input?: T,\n): T | undefined {\n if (!input) return undefined;\n\n return Object.fromEntries(\n Object.entries(input).filter(([, value]) => value !== undefined),\n ) as T;\n}\n\nexport { compactMetadata, pickAppErrorOptions, stripAppErrorOptions };\n","import { AppError } from \"./app-error.js\";\nimport { ErrorCodes } from \"./error-codes.js\";\nimport type { AppErrorOptions, ErrorSeverity } from \"./types.js\";\nimport { pickAppErrorOptions } from \"./utils/index.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype AuthorizationErrorReason =\n | \"forbidden\"\n | \"missing_role\"\n | \"missing_capability\"\n | \"out_of_scope\"\n | \"outside_time_window\"\n | \"policy_denied\"\n | \"unknown\";\n\ntype AuthorizationRequirement = {\n role?: string;\n capability?: string;\n scope?: string; // e.g.: \"school:{id}\" or \"class:{id}\"\n};\n\ntype AuthorizationErrorMetadata = {\n reason: AuthorizationErrorReason;\n\n /** Stable action you evaluated (not an HTTP route — a \"business action\") */\n action: string;\n\n /** Resource identification (without leaking the full payload) */\n resource?: { type: string; id?: string };\n\n /**\n * Optional: the acting principal (do not include sensitive data). `actorId`\n * is the raw `RequestedBy` value — a user UUID *or* a `\"system:<job>\"`\n * identity — so it must not be assumed to be a human user.\n */\n actor?: { actorId: string; role?: string };\n\n /** Optional: expected requirement */\n required?: AuthorizationRequirement;\n\n /** Optional: policy (when you already have PolicyEvaluation or similar) */\n policyId?: string;\n policyVersion?: number;\n evaluatedAtIso?: string;\n decision?: \"allow\" | \"deny\";\n reasonCode?: string;\n\n /** Optional: additional non-sensitive info */\n details?: string;\n};\n\n/**\n * Options for AuthorizationError factory methods.\n * Combines metadata fields (except 'reason') with common AppError options.\n */\ntype AuthorizationErrorFactoryOptions = Omit<\n AuthorizationErrorMetadata,\n \"reason\"\n> &\n Omit<AppErrorOptions, \"metadata\">;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// AuthorizationError\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Raised when an authenticated actor is not allowed to perform a business\n * action — the \"you may not\" error, distinct from authentication (\"who are\n * you\"). Maps to an HTTP 403 at the edge.\n *\n * The discriminating {@link reason} records *why* access was denied (a flat\n * forbid, a missing role/capability, an out-of-scope target, or a policy\n * decision) so the boundary can shape the response without re-deriving it. The\n * metadata is deliberately built around a stable business `action` and a\n * type/id resource reference rather than an HTTP route or full payload, keeping\n * sensitive data out of logs. Instances are frozen. Construct through the\n * static factories, never directly.\n */\nclass AuthorizationError extends AppError {\n public readonly reason: AuthorizationErrorReason;\n\n private constructor(params: {\n message: string;\n code: string;\n reason: AuthorizationErrorReason;\n metadata: AuthorizationErrorMetadata;\n cause?: unknown;\n type?: string;\n severity?: ErrorSeverity;\n correlationId?: string;\n requestId?: string;\n commandId?: string;\n createdAtIso?: string;\n publicMessage?: string;\n }) {\n super(params.message, params.code, {\n cause: params.cause,\n metadata: params.metadata,\n type: params.type,\n severity: params.severity,\n correlationId: params.correlationId,\n requestId: params.requestId,\n commandId: params.commandId,\n createdAtIso: params.createdAtIso,\n publicMessage: params.publicMessage,\n });\n\n this.reason = params.reason;\n Object.freeze(this);\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Factories\n // ─────────────────────────────────────────────────────────────────────────\n\n /**\n * A flat denial with no more specific reason — the actor simply may not\n * perform this action. Reach for a more precise factory\n * ({@link AuthorizationError.missingCapability}, {@link AuthorizationError.outOfScope},\n * {@link AuthorizationError.policyDenied}) when the cause is known.\n */\n static forbidden(\n input: AuthorizationErrorFactoryOptions,\n ): AuthorizationError {\n return new AuthorizationError({\n message: \"Action not allowed\",\n code: ErrorCodes.authorization.forbidden,\n reason: \"forbidden\",\n metadata: { reason: \"forbidden\", ...extractMetadataOnly(input) },\n ...pickAppErrorOptions(input),\n });\n }\n\n /**\n * The action was denied by an evaluated policy. Captures the deciding\n * policy's id, version, and evaluation instant into the metadata (with\n * `decision: \"deny\"`) so the denial is auditable back to the exact policy\n * that produced it.\n *\n * @param input - Factory options plus the required `policyId`,\n * `policyVersion`, and `evaluatedAtIso` of the deciding policy.\n */\n static policyDenied(\n input: AuthorizationErrorFactoryOptions & {\n policyId: string;\n policyVersion: number;\n evaluatedAtIso: string;\n },\n ): AuthorizationError {\n return new AuthorizationError({\n message: \"Action denied by policy\",\n code: ErrorCodes.authorization.policyDenied,\n reason: \"policy_denied\",\n metadata: {\n reason: \"policy_denied\",\n ...extractMetadataOnly(input),\n decision: \"deny\",\n },\n ...pickAppErrorOptions(input),\n });\n }\n\n /**\n * The actor holds no role at all that bears on the action — the most basic\n * denial, distinct from {@link AuthorizationError.missingCapability} (which\n * means the actor *has* roles, just none granting the required capability).\n * The expected {@link AuthorizationRequirement} is recorded so the boundary\n * can tell the caller what grant is missing.\n *\n * @param input - Factory options plus the `required` role/capability/scope.\n */\n static missingRole(\n input: AuthorizationErrorFactoryOptions & {\n required: AuthorizationRequirement;\n },\n ): AuthorizationError {\n return new AuthorizationError({\n message: \"Missing required role to perform the action\",\n code: ErrorCodes.authorization.missingRole,\n reason: \"missing_role\",\n metadata: {\n reason: \"missing_role\",\n ...extractMetadataOnly(input),\n },\n ...pickAppErrorOptions(input),\n });\n }\n\n /**\n * The actor lacks a required role or capability. The expected\n * {@link AuthorizationRequirement} is recorded so the boundary can tell the\n * caller precisely what grant is missing.\n *\n * @param input - Factory options plus the `required` role/capability/scope.\n */\n static missingCapability(\n input: AuthorizationErrorFactoryOptions & {\n required: AuthorizationRequirement;\n },\n ): AuthorizationError {\n return new AuthorizationError({\n message: \"Insufficient capability to perform the action\",\n code: ErrorCodes.authorization.missingCapability,\n reason: \"missing_capability\",\n metadata: {\n reason: \"missing_capability\",\n ...extractMetadataOnly(input),\n },\n ...pickAppErrorOptions(input),\n });\n }\n\n /**\n * The actor may perform the action in general, but not on *this* target —\n * the resource falls outside the actor's permitted scope (e.g. a different\n * school or tenant than the one they are bound to).\n *\n * @param input - Factory options plus the optional `required` scope that the\n * target failed to satisfy.\n */\n static outOfScope(\n input: AuthorizationErrorFactoryOptions & {\n required?: AuthorizationRequirement;\n },\n ): AuthorizationError {\n return new AuthorizationError({\n message: \"Action outside the allowed scope\",\n code: ErrorCodes.authorization.outOfScope,\n reason: \"out_of_scope\",\n metadata: { reason: \"out_of_scope\", ...extractMetadataOnly(input) },\n ...pickAppErrorOptions(input),\n });\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction extractMetadataOnly(\n input: AuthorizationErrorFactoryOptions,\n): Omit<AuthorizationErrorMetadata, \"reason\"> {\n const {\n cause,\n type,\n severity,\n correlationId,\n requestId,\n commandId,\n createdAtIso,\n publicMessage,\n ...metadata\n } = input;\n return metadata;\n}\n\nexport { AuthorizationError };\nexport type {\n AuthorizationErrorMetadata,\n AuthorizationErrorReason,\n AuthorizationRequirement,\n};\n"],"mappings":";;;AAQA,MAAM,wBAAwB;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;;;;AAWA,SAAS,oBACL,OACgB;CAChB,OAAO;EACH,OAAO,OAAO;EACd,MAAM,OAAO;EACb,UAAU,OAAO;EACjB,eAAe,OAAO;EACtB,WAAW,OAAO;EAClB,WAAW,OAAO;EAClB,cAAc,OAAO;EACrB,eAAe,OAAO;CAC1B;AACJ;;;;;;AAOA,SAAS,qBACL,OAC+C;CAC/C,MAAM,SAAS,EAAE,GAAG,MAAM;CAC1B,KAAK,MAAM,OAAO,uBACd,OAAO,OAAO;CAElB,OAAO;AACX;;AAGA,SAAS,gBACL,OACa;CACb,IAAI,CAAC,OAAO,OAAO,KAAA;CAEnB,OAAO,OAAO,YACV,OAAO,QAAQ,KAAK,EAAE,QAAQ,GAAG,WAAW,UAAU,KAAA,CAAS,CACnE;AACJ;;;;;;;;;;;;;;;;ACcA,IAAM,qBAAN,MAAM,2BAA2B,SAAS;CAGtC,YAAoB,QAajB;EACC,MAAM,OAAO,SAAS,OAAO,MAAM;GAC/B,OAAO,OAAO;GACd,UAAU,OAAO;GACjB,MAAM,OAAO;GACb,UAAU,OAAO;GACjB,eAAe,OAAO;GACtB,WAAW,OAAO;GAClB,WAAW,OAAO;GAClB,cAAc,OAAO;GACrB,eAAe,OAAO;EAC1B,CAAC;EAED,KAAK,SAAS,OAAO;EACrB,OAAO,OAAO,IAAI;CACtB;;;;;;;CAYA,OAAO,UACH,OACkB;EAClB,OAAO,IAAI,mBAAmB;GAC1B,SAAS;GACT,MAAM,WAAW,cAAc;GAC/B,QAAQ;GACR,UAAU;IAAE,QAAQ;IAAa,GAAG,oBAAoB,KAAK;GAAE;GAC/D,GAAG,oBAAoB,KAAK;EAChC,CAAC;CACL;;;;;;;;;;CAWA,OAAO,aACH,OAKkB;EAClB,OAAO,IAAI,mBAAmB;GAC1B,SAAS;GACT,MAAM,WAAW,cAAc;GAC/B,QAAQ;GACR,UAAU;IACN,QAAQ;IACR,GAAG,oBAAoB,KAAK;IAC5B,UAAU;GACd;GACA,GAAG,oBAAoB,KAAK;EAChC,CAAC;CACL;;;;;;;;;;CAWA,OAAO,YACH,OAGkB;EAClB,OAAO,IAAI,mBAAmB;GAC1B,SAAS;GACT,MAAM,WAAW,cAAc;GAC/B,QAAQ;GACR,UAAU;IACN,QAAQ;IACR,GAAG,oBAAoB,KAAK;GAChC;GACA,GAAG,oBAAoB,KAAK;EAChC,CAAC;CACL;;;;;;;;CASA,OAAO,kBACH,OAGkB;EAClB,OAAO,IAAI,mBAAmB;GAC1B,SAAS;GACT,MAAM,WAAW,cAAc;GAC/B,QAAQ;GACR,UAAU;IACN,QAAQ;IACR,GAAG,oBAAoB,KAAK;GAChC;GACA,GAAG,oBAAoB,KAAK;EAChC,CAAC;CACL;;;;;;;;;CAUA,OAAO,WACH,OAGkB;EAClB,OAAO,IAAI,mBAAmB;GAC1B,SAAS;GACT,MAAM,WAAW,cAAc;GAC/B,QAAQ;GACR,UAAU;IAAE,QAAQ;IAAgB,GAAG,oBAAoB,KAAK;GAAE;GAClE,GAAG,oBAAoB,KAAK;EAChC,CAAC;CACL;AACJ;AAMA,SAAS,oBACL,OAC0C;CAC1C,MAAM,EACF,OACA,MACA,UACA,eACA,WACA,WACA,cACA,eACA,GAAG,aACH;CACJ,OAAO;AACX"}
@@ -68,7 +68,12 @@ type ScopeProps = {
68
68
  */
69
69
  declare class Scope extends ValueObject<ScopeProps, string> {
70
70
  private constructor();
71
- /** Validates `"type:id"` or the global `"*"`, throwing on anything else. */
71
+ /**
72
+ * Validates `"type:id"` or the global `"*"`, throwing on anything else.
73
+ * `Scope.of("*")` is accepted and is equivalent to {@link Scope.global} —
74
+ * prefer `global()` at call-sites for intent, `of("*")` when rehydrating a
75
+ * stored string.
76
+ */
72
77
  static of(raw: string): Scope;
73
78
  /** The global scope `"*"`, which {@link includes} every other scope. */
74
79
  static global(): Scope;
@@ -101,12 +106,22 @@ interface AccessRequest {
101
106
  * Never a wildcard — only the *granted* permissions on roles may wildcard.
102
107
  */
103
108
  readonly required: Permission;
104
- /** The target resource, identified without leaking its payload. */
109
+ /**
110
+ * The target resource, identified without leaking its payload. Carried for
111
+ * audit metadata only — the RBAC decisor does **not** check it. Per-resource
112
+ * ownership (this actor may cancel *this* order) is an ABAC/policy concern,
113
+ * not a role/permission one.
114
+ */
105
115
  readonly resource?: {
106
116
  readonly type: string;
107
117
  readonly id?: string;
108
118
  };
109
- /** The scope the resource lives in, e.g. `Scope.of("school:123")`. */
119
+ /**
120
+ * The scope the resource lives in, e.g. `Scope.of("school:123")`. Asking in
121
+ * the global scope (`Scope.global()`) means "may I do this *everywhere*?" and
122
+ * so requires a globally-scoped grant — it is **not** "in any scope I happen
123
+ * to hold". Ask about a concrete scope to check a bounded grant.
124
+ */
110
125
  readonly scope: Scope;
111
126
  }
112
127
  //#endregion
@@ -68,7 +68,12 @@ type ScopeProps = {
68
68
  */
69
69
  declare class Scope extends ValueObject<ScopeProps, string> {
70
70
  private constructor();
71
- /** Validates `"type:id"` or the global `"*"`, throwing on anything else. */
71
+ /**
72
+ * Validates `"type:id"` or the global `"*"`, throwing on anything else.
73
+ * `Scope.of("*")` is accepted and is equivalent to {@link Scope.global} —
74
+ * prefer `global()` at call-sites for intent, `of("*")` when rehydrating a
75
+ * stored string.
76
+ */
72
77
  static of(raw: string): Scope;
73
78
  /** The global scope `"*"`, which {@link includes} every other scope. */
74
79
  static global(): Scope;
@@ -101,12 +106,22 @@ interface AccessRequest {
101
106
  * Never a wildcard — only the *granted* permissions on roles may wildcard.
102
107
  */
103
108
  readonly required: Permission;
104
- /** The target resource, identified without leaking its payload. */
109
+ /**
110
+ * The target resource, identified without leaking its payload. Carried for
111
+ * audit metadata only — the RBAC decisor does **not** check it. Per-resource
112
+ * ownership (this actor may cancel *this* order) is an ABAC/policy concern,
113
+ * not a role/permission one.
114
+ */
105
115
  readonly resource?: {
106
116
  readonly type: string;
107
117
  readonly id?: string;
108
118
  };
109
- /** The scope the resource lives in, e.g. `Scope.of("school:123")`. */
119
+ /**
120
+ * The scope the resource lives in, e.g. `Scope.of("school:123")`. Asking in
121
+ * the global scope (`Scope.global()`) means "may I do this *everywhere*?" and
122
+ * so requires a globally-scoped grant — it is **not** "in any scope I happen
123
+ * to hold". Ask about a concrete scope to check a bounded grant.
124
+ */
110
125
  readonly scope: Scope;
111
126
  }
112
127
  //#endregion
@@ -1,7 +1,7 @@
1
1
  import { t as RequestedBy } from "./requested-by.cjs";
2
2
  import { t as AuthorizationError } from "./authorization-error.cjs";
3
3
  import { r as Result } from "./result.cjs";
4
- import { T as PolicyContext, j as ConditionNode, t as CoreConfig } from "./core-config.cjs";
4
+ import { A as ConditionNode, t as CoreConfig, w as PolicyContext } from "./core-config.cjs";
5
5
  import { t as ValueObject } from "./value-object.cjs";
6
6
  import { n as AccessRequest, t as AuthorizerPort } from "./authorizer.port.cjs";
7
7
 
@@ -93,6 +93,26 @@ type AbacRuleProps = {
93
93
  * authored in TypeScript — trusted data, not untrusted JSON — the structural
94
94
  * check is a hand-rolled walk of the condition tree rather than a schema parse.
95
95
  * Build one through {@link of}; the constructor is private.
96
+ *
97
+ * **Attribute semantics a rule author must know (the condition is evaluated by
98
+ * the shared gate/compute engine, whose runtime rules apply here too):**
99
+ * - **A referenced attribute that is *absent* from the request is a technical
100
+ * evaluation error, not a non-match.** By default (`onEvaluationError:
101
+ * "fail-closed"` on the {@link AbacPolicySet}) that error fails the *entire*
102
+ * decision closed — so adding a rule that reads an attribute a given call site
103
+ * does not yet populate makes that call site start returning `forbidden`,
104
+ * *even for requests the older rules used to permit*. Choose
105
+ * `onEvaluationError: "skip-rule"` on the set to skip an unevaluable rule
106
+ * instead of failing the whole set.
107
+ * - **`isNull` / `isNotNull` test for an explicit `null`/`undefined` *value*, not
108
+ * for a missing key.** A key that is absent from the bag never reaches the
109
+ * operator — it short-circuits to the missing-attribute error above. To match
110
+ * "no deletedAt", the consumer must put `deletedAt: null` in the bag, not omit
111
+ * it. Pass `allowNull: true` on a relational leaf to treat a present `null` as
112
+ * a non-match instead of an error.
113
+ * - **Date comparison operands must be strict ISO-8601 UTC**
114
+ * (`YYYY-MM-DDTHH:mm:ss.sssZ`); a bare `"2026-07-09"` or an offset like
115
+ * `+00:00` is rejected as an invalid operand and fails closed.
96
116
  */
97
117
  declare class AbacRule extends ValueObject<AbacRuleProps, AbacRuleProps> {
98
118
  private constructor();
@@ -128,28 +148,51 @@ type CombiningAlgorithm = "deny-overrides" | "permit-overrides" | "first-applica
128
148
  /** The resolved decision plus the rule (if any) that produced it. */
129
149
  //#endregion
130
150
  //#region src/core/abac/domain/policy-set.d.ts
151
+ /**
152
+ * How the {@link AbacAuthorizer} reacts when a rule's condition cannot be
153
+ * evaluated (a referenced attribute is absent, an operand is wrong-typed).
154
+ *
155
+ * - `fail-closed` (default, secure): the whole decision fails closed as
156
+ * `forbidden`. Safe, but it means adding a rule that reads an attribute a call
157
+ * site does not populate makes that call site deny everything until it does.
158
+ * - `skip-rule`: the unevaluable rule is dropped from the applicable set and the
159
+ * remaining rules decide — the escape valve for evolving rules without
160
+ * trailing every call site. The evaluator still reports the anomaly.
161
+ */
162
+ type OnEvaluationError = "fail-closed" | "skip-rule";
131
163
  /**
132
164
  * An ordered collection of {@link AbacRule}s plus how to combine them
133
- * ({@link CombiningAlgorithm}) and what to decide when no rule applies
134
- * ({@link defaultEffect}). This is where ABAC goes beyond a single gate
165
+ * ({@link CombiningAlgorithm}), what to decide when no rule applies
166
+ * ({@link defaultEffect}), and how to react to an unevaluable rule
167
+ * ({@link onEvaluationError}). This is where ABAC goes beyond a single gate
135
168
  * condition: several PERMIT/DENY rules resolved by an explicit algorithm.
136
169
  *
137
- * Closed by default — the combining algorithm defaults to `"deny-overrides"` and
138
- * the default effect to `"DENY"`, so a request matching nothing is denied. A
139
- * plain holder (not a value object); build through {@link of}.
170
+ * Closed by default — the combining algorithm defaults to `"deny-overrides"`,
171
+ * the default effect to `"DENY"`, and evaluation errors to `"fail-closed"`, so a
172
+ * request matching nothing (or hitting a technical error) is denied. A plain
173
+ * holder (not a value object); build through {@link of}.
140
174
  */
141
175
  declare class AbacPolicySet {
142
176
  private readonly _rules;
143
177
  private readonly _algorithm;
144
178
  private readonly _defaultEffect;
179
+ private readonly _onEvaluationError;
145
180
  private constructor();
181
+ /**
182
+ * Builds a policy set. Rejects duplicate rule ids with
183
+ * {@link InvalidValueException} so a denial's `policyId` attribution is
184
+ * unambiguous.
185
+ */
146
186
  static of(rules: readonly AbacRule[], options?: {
147
187
  readonly algorithm?: CombiningAlgorithm;
148
188
  readonly defaultEffect?: RuleEffect;
189
+ readonly onEvaluationError?: OnEvaluationError;
149
190
  }): AbacPolicySet;
191
+ private static assertUniqueIds;
150
192
  get rules(): readonly AbacRule[];
151
193
  get algorithm(): CombiningAlgorithm;
152
194
  get defaultEffect(): RuleEffect;
195
+ get onEvaluationError(): OnEvaluationError;
153
196
  }
154
197
  //#endregion
155
198
  //#region src/core/abac/authorizer.d.ts
@@ -173,17 +216,27 @@ interface AbacDecision {
173
216
  * "errors as values" contract of `RbacAuthorizer`/`GateEngineV1`.
174
217
  *
175
218
  * A denial maps to {@link AuthorizationError.policyDenied}, attributed to the
176
- * deciding rule's `id`/`version`; a condition-evaluation failure (a missing
219
+ * deciding rule's `id`/`version`. A condition-evaluation failure (a missing
177
220
  * attribute, a wrong-typed operand) fails closed as
178
- * {@link AuthorizationError.forbidden}. Loading the dynamic attributes is the
179
- * consumer's job (behind an `AbacAuthorizerPort` adapter); this class only decides.
221
+ * {@link AuthorizationError.forbidden} also attributed to the culprit rule's
222
+ * `id`/`version` unless the set opts into `onEvaluationError: "skip-rule"`, in
223
+ * which case the unevaluable rule is skipped and the rest decide. Every resolved
224
+ * decision (PERMIT and DENY) is emitted best-effort through the configured
225
+ * reporter as an `abac-decision` event (silent by default). Timestamps come from
226
+ * an injectable `clock` (default `() => new Date()`), so the decisor is pure
227
+ * given one. Loading the dynamic attributes is the consumer's job (behind an
228
+ * `AbacAuthorizerPort` adapter); this class only decides.
180
229
  */
181
230
  declare class AbacAuthorizer {
182
231
  private readonly coreConfig;
232
+ private readonly clock;
183
233
  constructor(params?: {
184
234
  readonly coreConfig?: CoreConfig;
235
+ /** Injectable clock for deterministic timestamps; defaults to `() => new Date()`. */
236
+ readonly clock?: () => Date;
185
237
  });
186
238
  authorize(request: AbacRequest, policies: AbacPolicySet): Result<AbacDecision, AuthorizationError>;
239
+ private reportDecision;
187
240
  }
188
241
  //#endregion
189
242
  //#region src/core/abac/composite-authorizer.d.ts
@@ -212,5 +265,5 @@ declare class CompositeAuthorizer {
212
265
  authorize(request: CompositeAccessRequest): Promise<Result<void, AuthorizationError>>;
213
266
  }
214
267
  //#endregion
215
- export { AbacPolicySet as a, AbacRuleProps as c, AbacAuthorizerPort as d, AbacAttributes as f, AbacDecision as i, RuleEffect as l, CompositeAuthorizer as n, CombiningAlgorithm as o, AbacRequest as p, AbacAuthorizer as r, AbacRule as s, CompositeAccessRequest as t, abacContext as u };
268
+ export { AbacPolicySet as a, AbacRule as c, abacContext as d, AbacAuthorizerPort as f, AbacDecision as i, AbacRuleProps as l, AbacRequest as m, CompositeAuthorizer as n, OnEvaluationError as o, AbacAttributes as p, AbacAuthorizer as r, CombiningAlgorithm as s, CompositeAccessRequest as t, RuleEffect as u };
216
269
  //# sourceMappingURL=composite-authorizer.d.cts.map
@@ -1,7 +1,7 @@
1
1
  import { t as RequestedBy } from "./requested-by.js";
2
2
  import { t as AuthorizationError } from "./authorization-error.js";
3
3
  import { r as Result } from "./result.js";
4
- import { T as PolicyContext, j as ConditionNode, t as CoreConfig } from "./core-config.js";
4
+ import { A as ConditionNode, t as CoreConfig, w as PolicyContext } from "./core-config.js";
5
5
  import { t as ValueObject } from "./value-object.js";
6
6
  import { n as AccessRequest, t as AuthorizerPort } from "./authorizer.port.js";
7
7
 
@@ -93,6 +93,26 @@ type AbacRuleProps = {
93
93
  * authored in TypeScript — trusted data, not untrusted JSON — the structural
94
94
  * check is a hand-rolled walk of the condition tree rather than a schema parse.
95
95
  * Build one through {@link of}; the constructor is private.
96
+ *
97
+ * **Attribute semantics a rule author must know (the condition is evaluated by
98
+ * the shared gate/compute engine, whose runtime rules apply here too):**
99
+ * - **A referenced attribute that is *absent* from the request is a technical
100
+ * evaluation error, not a non-match.** By default (`onEvaluationError:
101
+ * "fail-closed"` on the {@link AbacPolicySet}) that error fails the *entire*
102
+ * decision closed — so adding a rule that reads an attribute a given call site
103
+ * does not yet populate makes that call site start returning `forbidden`,
104
+ * *even for requests the older rules used to permit*. Choose
105
+ * `onEvaluationError: "skip-rule"` on the set to skip an unevaluable rule
106
+ * instead of failing the whole set.
107
+ * - **`isNull` / `isNotNull` test for an explicit `null`/`undefined` *value*, not
108
+ * for a missing key.** A key that is absent from the bag never reaches the
109
+ * operator — it short-circuits to the missing-attribute error above. To match
110
+ * "no deletedAt", the consumer must put `deletedAt: null` in the bag, not omit
111
+ * it. Pass `allowNull: true` on a relational leaf to treat a present `null` as
112
+ * a non-match instead of an error.
113
+ * - **Date comparison operands must be strict ISO-8601 UTC**
114
+ * (`YYYY-MM-DDTHH:mm:ss.sssZ`); a bare `"2026-07-09"` or an offset like
115
+ * `+00:00` is rejected as an invalid operand and fails closed.
96
116
  */
97
117
  declare class AbacRule extends ValueObject<AbacRuleProps, AbacRuleProps> {
98
118
  private constructor();
@@ -128,28 +148,51 @@ type CombiningAlgorithm = "deny-overrides" | "permit-overrides" | "first-applica
128
148
  /** The resolved decision plus the rule (if any) that produced it. */
129
149
  //#endregion
130
150
  //#region src/core/abac/domain/policy-set.d.ts
151
+ /**
152
+ * How the {@link AbacAuthorizer} reacts when a rule's condition cannot be
153
+ * evaluated (a referenced attribute is absent, an operand is wrong-typed).
154
+ *
155
+ * - `fail-closed` (default, secure): the whole decision fails closed as
156
+ * `forbidden`. Safe, but it means adding a rule that reads an attribute a call
157
+ * site does not populate makes that call site deny everything until it does.
158
+ * - `skip-rule`: the unevaluable rule is dropped from the applicable set and the
159
+ * remaining rules decide — the escape valve for evolving rules without
160
+ * trailing every call site. The evaluator still reports the anomaly.
161
+ */
162
+ type OnEvaluationError = "fail-closed" | "skip-rule";
131
163
  /**
132
164
  * An ordered collection of {@link AbacRule}s plus how to combine them
133
- * ({@link CombiningAlgorithm}) and what to decide when no rule applies
134
- * ({@link defaultEffect}). This is where ABAC goes beyond a single gate
165
+ * ({@link CombiningAlgorithm}), what to decide when no rule applies
166
+ * ({@link defaultEffect}), and how to react to an unevaluable rule
167
+ * ({@link onEvaluationError}). This is where ABAC goes beyond a single gate
135
168
  * condition: several PERMIT/DENY rules resolved by an explicit algorithm.
136
169
  *
137
- * Closed by default — the combining algorithm defaults to `"deny-overrides"` and
138
- * the default effect to `"DENY"`, so a request matching nothing is denied. A
139
- * plain holder (not a value object); build through {@link of}.
170
+ * Closed by default — the combining algorithm defaults to `"deny-overrides"`,
171
+ * the default effect to `"DENY"`, and evaluation errors to `"fail-closed"`, so a
172
+ * request matching nothing (or hitting a technical error) is denied. A plain
173
+ * holder (not a value object); build through {@link of}.
140
174
  */
141
175
  declare class AbacPolicySet {
142
176
  private readonly _rules;
143
177
  private readonly _algorithm;
144
178
  private readonly _defaultEffect;
179
+ private readonly _onEvaluationError;
145
180
  private constructor();
181
+ /**
182
+ * Builds a policy set. Rejects duplicate rule ids with
183
+ * {@link InvalidValueException} so a denial's `policyId` attribution is
184
+ * unambiguous.
185
+ */
146
186
  static of(rules: readonly AbacRule[], options?: {
147
187
  readonly algorithm?: CombiningAlgorithm;
148
188
  readonly defaultEffect?: RuleEffect;
189
+ readonly onEvaluationError?: OnEvaluationError;
149
190
  }): AbacPolicySet;
191
+ private static assertUniqueIds;
150
192
  get rules(): readonly AbacRule[];
151
193
  get algorithm(): CombiningAlgorithm;
152
194
  get defaultEffect(): RuleEffect;
195
+ get onEvaluationError(): OnEvaluationError;
153
196
  }
154
197
  //#endregion
155
198
  //#region src/core/abac/authorizer.d.ts
@@ -173,17 +216,27 @@ interface AbacDecision {
173
216
  * "errors as values" contract of `RbacAuthorizer`/`GateEngineV1`.
174
217
  *
175
218
  * A denial maps to {@link AuthorizationError.policyDenied}, attributed to the
176
- * deciding rule's `id`/`version`; a condition-evaluation failure (a missing
219
+ * deciding rule's `id`/`version`. A condition-evaluation failure (a missing
177
220
  * attribute, a wrong-typed operand) fails closed as
178
- * {@link AuthorizationError.forbidden}. Loading the dynamic attributes is the
179
- * consumer's job (behind an `AbacAuthorizerPort` adapter); this class only decides.
221
+ * {@link AuthorizationError.forbidden} also attributed to the culprit rule's
222
+ * `id`/`version` unless the set opts into `onEvaluationError: "skip-rule"`, in
223
+ * which case the unevaluable rule is skipped and the rest decide. Every resolved
224
+ * decision (PERMIT and DENY) is emitted best-effort through the configured
225
+ * reporter as an `abac-decision` event (silent by default). Timestamps come from
226
+ * an injectable `clock` (default `() => new Date()`), so the decisor is pure
227
+ * given one. Loading the dynamic attributes is the consumer's job (behind an
228
+ * `AbacAuthorizerPort` adapter); this class only decides.
180
229
  */
181
230
  declare class AbacAuthorizer {
182
231
  private readonly coreConfig;
232
+ private readonly clock;
183
233
  constructor(params?: {
184
234
  readonly coreConfig?: CoreConfig;
235
+ /** Injectable clock for deterministic timestamps; defaults to `() => new Date()`. */
236
+ readonly clock?: () => Date;
185
237
  });
186
238
  authorize(request: AbacRequest, policies: AbacPolicySet): Result<AbacDecision, AuthorizationError>;
239
+ private reportDecision;
187
240
  }
188
241
  //#endregion
189
242
  //#region src/core/abac/composite-authorizer.d.ts
@@ -212,5 +265,5 @@ declare class CompositeAuthorizer {
212
265
  authorize(request: CompositeAccessRequest): Promise<Result<void, AuthorizationError>>;
213
266
  }
214
267
  //#endregion
215
- export { AbacPolicySet as a, AbacRuleProps as c, AbacAuthorizerPort as d, AbacAttributes as f, AbacDecision as i, RuleEffect as l, CompositeAuthorizer as n, CombiningAlgorithm as o, AbacRequest as p, AbacAuthorizer as r, AbacRule as s, CompositeAccessRequest as t, abacContext as u };
268
+ export { AbacPolicySet as a, AbacRule as c, abacContext as d, AbacAuthorizerPort as f, AbacDecision as i, AbacRuleProps as l, AbacRequest as m, CompositeAuthorizer as n, OnEvaluationError as o, AbacAttributes as p, AbacAuthorizer as r, CombiningAlgorithm as s, CompositeAccessRequest as t, RuleEffect as u };
216
269
  //# sourceMappingURL=composite-authorizer.d.ts.map