apcore-js 0.18.0 → 0.20.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 (241) hide show
  1. package/README.md +112 -9
  2. package/dist/acl-handlers.d.ts +14 -0
  3. package/dist/acl-handlers.d.ts.map +1 -1
  4. package/dist/acl-handlers.js +37 -4
  5. package/dist/acl-handlers.js.map +1 -1
  6. package/dist/acl.d.ts +22 -1
  7. package/dist/acl.d.ts.map +1 -1
  8. package/dist/acl.js +90 -34
  9. package/dist/acl.js.map +1 -1
  10. package/dist/async-task.d.ts +70 -16
  11. package/dist/async-task.d.ts.map +1 -1
  12. package/dist/async-task.js +212 -72
  13. package/dist/async-task.js.map +1 -1
  14. package/dist/bindings.d.ts.map +1 -1
  15. package/dist/bindings.js +113 -11
  16. package/dist/bindings.js.map +1 -1
  17. package/dist/builtin-steps.d.ts +33 -8
  18. package/dist/builtin-steps.d.ts.map +1 -1
  19. package/dist/builtin-steps.js +119 -47
  20. package/dist/builtin-steps.js.map +1 -1
  21. package/dist/client.d.ts +1 -0
  22. package/dist/client.d.ts.map +1 -1
  23. package/dist/client.js.map +1 -1
  24. package/dist/config.d.ts +38 -0
  25. package/dist/config.d.ts.map +1 -1
  26. package/dist/config.js +163 -33
  27. package/dist/config.js.map +1 -1
  28. package/dist/context.d.ts +34 -7
  29. package/dist/context.d.ts.map +1 -1
  30. package/dist/context.js +108 -40
  31. package/dist/context.js.map +1 -1
  32. package/dist/decorator.d.ts +3 -0
  33. package/dist/decorator.d.ts.map +1 -1
  34. package/dist/decorator.js +3 -0
  35. package/dist/decorator.js.map +1 -1
  36. package/dist/errors.d.ts +88 -2
  37. package/dist/errors.d.ts.map +1 -1
  38. package/dist/errors.js +231 -56
  39. package/dist/errors.js.map +1 -1
  40. package/dist/events/circuit-breaker.d.ts +45 -0
  41. package/dist/events/circuit-breaker.d.ts.map +1 -0
  42. package/dist/events/circuit-breaker.js +115 -0
  43. package/dist/events/circuit-breaker.js.map +1 -0
  44. package/dist/events/emitter.d.ts +24 -1
  45. package/dist/events/emitter.d.ts.map +1 -1
  46. package/dist/events/emitter.js +86 -12
  47. package/dist/events/emitter.js.map +1 -1
  48. package/dist/events/index.d.ts +4 -2
  49. package/dist/events/index.d.ts.map +1 -1
  50. package/dist/events/index.js +3 -2
  51. package/dist/events/index.js.map +1 -1
  52. package/dist/events/subscribers.d.ts +33 -1
  53. package/dist/events/subscribers.d.ts.map +1 -1
  54. package/dist/events/subscribers.js +124 -1
  55. package/dist/events/subscribers.js.map +1 -1
  56. package/dist/executor.d.ts +14 -3
  57. package/dist/executor.d.ts.map +1 -1
  58. package/dist/executor.js +155 -48
  59. package/dist/executor.js.map +1 -1
  60. package/dist/generated/version.d.ts +1 -1
  61. package/dist/generated/version.js +1 -1
  62. package/dist/index.d.ts +47 -25
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +35 -18
  65. package/dist/index.js.map +1 -1
  66. package/dist/middleware/base.d.ts +25 -3
  67. package/dist/middleware/base.d.ts.map +1 -1
  68. package/dist/middleware/base.js +24 -0
  69. package/dist/middleware/base.js.map +1 -1
  70. package/dist/middleware/circuit-breaker.d.ts +54 -0
  71. package/dist/middleware/circuit-breaker.d.ts.map +1 -0
  72. package/dist/middleware/circuit-breaker.js +168 -0
  73. package/dist/middleware/circuit-breaker.js.map +1 -0
  74. package/dist/middleware/context-namespace.d.ts +30 -0
  75. package/dist/middleware/context-namespace.d.ts.map +1 -0
  76. package/dist/middleware/context-namespace.js +38 -0
  77. package/dist/middleware/context-namespace.js.map +1 -0
  78. package/dist/middleware/index.d.ts +8 -2
  79. package/dist/middleware/index.d.ts.map +1 -1
  80. package/dist/middleware/index.js +5 -2
  81. package/dist/middleware/index.js.map +1 -1
  82. package/dist/middleware/logging.d.ts +6 -0
  83. package/dist/middleware/logging.d.ts.map +1 -1
  84. package/dist/middleware/logging.js +13 -3
  85. package/dist/middleware/logging.js.map +1 -1
  86. package/dist/middleware/manager.d.ts +11 -4
  87. package/dist/middleware/manager.d.ts.map +1 -1
  88. package/dist/middleware/manager.js +26 -9
  89. package/dist/middleware/manager.js.map +1 -1
  90. package/dist/middleware/platform-notify.d.ts +8 -4
  91. package/dist/middleware/platform-notify.d.ts.map +1 -1
  92. package/dist/middleware/platform-notify.js +15 -7
  93. package/dist/middleware/platform-notify.js.map +1 -1
  94. package/dist/middleware/retry.d.ts +16 -7
  95. package/dist/middleware/retry.d.ts.map +1 -1
  96. package/dist/middleware/retry.js +21 -15
  97. package/dist/middleware/retry.js.map +1 -1
  98. package/dist/middleware/tracing.d.ts +50 -0
  99. package/dist/middleware/tracing.d.ts.map +1 -0
  100. package/dist/middleware/tracing.js +89 -0
  101. package/dist/middleware/tracing.js.map +1 -0
  102. package/dist/observability/batch-span-processor.d.ts +48 -0
  103. package/dist/observability/batch-span-processor.d.ts.map +1 -0
  104. package/dist/observability/batch-span-processor.js +89 -0
  105. package/dist/observability/batch-span-processor.js.map +1 -0
  106. package/dist/observability/context-logger.d.ts +54 -1
  107. package/dist/observability/context-logger.d.ts.map +1 -1
  108. package/dist/observability/context-logger.js +287 -10
  109. package/dist/observability/context-logger.js.map +1 -1
  110. package/dist/observability/error-history.d.ts +36 -7
  111. package/dist/observability/error-history.d.ts.map +1 -1
  112. package/dist/observability/error-history.js +169 -50
  113. package/dist/observability/error-history.js.map +1 -1
  114. package/dist/observability/index.d.ts +16 -5
  115. package/dist/observability/index.d.ts.map +1 -1
  116. package/dist/observability/index.js +8 -3
  117. package/dist/observability/index.js.map +1 -1
  118. package/dist/observability/metrics-utils.d.ts.map +1 -1
  119. package/dist/observability/metrics-utils.js +3 -5
  120. package/dist/observability/metrics-utils.js.map +1 -1
  121. package/dist/observability/metrics.d.ts +15 -1
  122. package/dist/observability/metrics.d.ts.map +1 -1
  123. package/dist/observability/metrics.js +37 -3
  124. package/dist/observability/metrics.js.map +1 -1
  125. package/dist/observability/prometheus-exporter.d.ts +37 -0
  126. package/dist/observability/prometheus-exporter.d.ts.map +1 -0
  127. package/dist/observability/prometheus-exporter.js +135 -0
  128. package/dist/observability/prometheus-exporter.js.map +1 -0
  129. package/dist/observability/storage.d.ts +43 -0
  130. package/dist/observability/storage.d.ts.map +1 -0
  131. package/dist/observability/storage.js +58 -0
  132. package/dist/observability/storage.js.map +1 -0
  133. package/dist/observability/store.d.ts +29 -0
  134. package/dist/observability/store.d.ts.map +1 -0
  135. package/dist/observability/store.js +36 -0
  136. package/dist/observability/store.js.map +1 -0
  137. package/dist/observability/tracing.d.ts +2 -0
  138. package/dist/observability/tracing.d.ts.map +1 -1
  139. package/dist/observability/tracing.js +12 -2
  140. package/dist/observability/tracing.js.map +1 -1
  141. package/dist/observability/usage-exporter.d.ts +58 -0
  142. package/dist/observability/usage-exporter.d.ts.map +1 -0
  143. package/dist/observability/usage-exporter.js +86 -0
  144. package/dist/observability/usage-exporter.js.map +1 -0
  145. package/dist/observability/usage.d.ts +18 -1
  146. package/dist/observability/usage.d.ts.map +1 -1
  147. package/dist/observability/usage.js +35 -4
  148. package/dist/observability/usage.js.map +1 -1
  149. package/dist/pipeline-config.d.ts +24 -7
  150. package/dist/pipeline-config.d.ts.map +1 -1
  151. package/dist/pipeline-config.js +113 -19
  152. package/dist/pipeline-config.js.map +1 -1
  153. package/dist/pipeline.d.ts +123 -2
  154. package/dist/pipeline.d.ts.map +1 -1
  155. package/dist/pipeline.js +249 -50
  156. package/dist/pipeline.js.map +1 -1
  157. package/dist/registry/conflicts.d.ts +2 -2
  158. package/dist/registry/conflicts.d.ts.map +1 -1
  159. package/dist/registry/conflicts.js +10 -11
  160. package/dist/registry/conflicts.js.map +1 -1
  161. package/dist/registry/dependencies.d.ts +1 -1
  162. package/dist/registry/dependencies.d.ts.map +1 -1
  163. package/dist/registry/dependencies.js +69 -20
  164. package/dist/registry/dependencies.js.map +1 -1
  165. package/dist/registry/index.d.ts +2 -0
  166. package/dist/registry/index.d.ts.map +1 -1
  167. package/dist/registry/index.js +1 -0
  168. package/dist/registry/index.js.map +1 -1
  169. package/dist/registry/multi-class.d.ts +57 -0
  170. package/dist/registry/multi-class.d.ts.map +1 -0
  171. package/dist/registry/multi-class.js +120 -0
  172. package/dist/registry/multi-class.js.map +1 -0
  173. package/dist/registry/registry.d.ts +99 -4
  174. package/dist/registry/registry.d.ts.map +1 -1
  175. package/dist/registry/registry.js +291 -33
  176. package/dist/registry/registry.js.map +1 -1
  177. package/dist/registry/scanner.d.ts.map +1 -1
  178. package/dist/registry/scanner.js +6 -0
  179. package/dist/registry/scanner.js.map +1 -1
  180. package/dist/registry/version.d.ts +1 -0
  181. package/dist/registry/version.d.ts.map +1 -1
  182. package/dist/registry/version.js +33 -4
  183. package/dist/registry/version.js.map +1 -1
  184. package/dist/schema/constants.d.ts +9 -0
  185. package/dist/schema/constants.d.ts.map +1 -0
  186. package/dist/schema/constants.js +9 -0
  187. package/dist/schema/constants.js.map +1 -0
  188. package/dist/schema/extractor.d.ts +69 -0
  189. package/dist/schema/extractor.d.ts.map +1 -0
  190. package/dist/schema/extractor.js +142 -0
  191. package/dist/schema/extractor.js.map +1 -0
  192. package/dist/schema/index.d.ts +3 -1
  193. package/dist/schema/index.d.ts.map +1 -1
  194. package/dist/schema/index.js +2 -1
  195. package/dist/schema/index.js.map +1 -1
  196. package/dist/schema/loader.d.ts +27 -3
  197. package/dist/schema/loader.d.ts.map +1 -1
  198. package/dist/schema/loader.js +137 -32
  199. package/dist/schema/loader.js.map +1 -1
  200. package/dist/schema/ref-resolver.d.ts.map +1 -1
  201. package/dist/schema/ref-resolver.js +10 -1
  202. package/dist/schema/ref-resolver.js.map +1 -1
  203. package/dist/schema/types.d.ts +4 -0
  204. package/dist/schema/types.d.ts.map +1 -1
  205. package/dist/schema/types.js.map +1 -1
  206. package/dist/schema/validator.d.ts +9 -0
  207. package/dist/schema/validator.d.ts.map +1 -1
  208. package/dist/schema/validator.js +153 -4
  209. package/dist/schema/validator.js.map +1 -1
  210. package/dist/sys-modules/audit.d.ts +50 -0
  211. package/dist/sys-modules/audit.d.ts.map +1 -0
  212. package/dist/sys-modules/audit.js +89 -0
  213. package/dist/sys-modules/audit.js.map +1 -0
  214. package/dist/sys-modules/control.d.ts +32 -4
  215. package/dist/sys-modules/control.d.ts.map +1 -1
  216. package/dist/sys-modules/control.js +197 -23
  217. package/dist/sys-modules/control.js.map +1 -1
  218. package/dist/sys-modules/index.d.ts +7 -2
  219. package/dist/sys-modules/index.d.ts.map +1 -1
  220. package/dist/sys-modules/index.js +3 -1
  221. package/dist/sys-modules/index.js.map +1 -1
  222. package/dist/sys-modules/overrides.d.ts +58 -0
  223. package/dist/sys-modules/overrides.d.ts.map +1 -0
  224. package/dist/sys-modules/overrides.js +106 -0
  225. package/dist/sys-modules/overrides.js.map +1 -0
  226. package/dist/sys-modules/registration.d.ts +18 -1
  227. package/dist/sys-modules/registration.d.ts.map +1 -1
  228. package/dist/sys-modules/registration.js +115 -11
  229. package/dist/sys-modules/registration.js.map +1 -1
  230. package/dist/sys-modules/toggle.d.ts +7 -2
  231. package/dist/sys-modules/toggle.d.ts.map +1 -1
  232. package/dist/sys-modules/toggle.js +61 -5
  233. package/dist/sys-modules/toggle.js.map +1 -1
  234. package/dist/trace-context.d.ts +47 -9
  235. package/dist/trace-context.d.ts.map +1 -1
  236. package/dist/trace-context.js +139 -16
  237. package/dist/trace-context.js.map +1 -1
  238. package/dist/utils/index.d.ts.map +1 -1
  239. package/dist/utils/index.js +2 -1
  240. package/dist/utils/index.js.map +1 -1
  241. package/package.json +1 -1
package/README.md CHANGED
@@ -20,12 +20,15 @@ apcore is an AI-Perceivable module standard that makes every interface naturally
20
20
 
21
21
  - **Schema-driven modules** — Define input/output schemas with TypeBox for runtime validation
22
22
  - **Executor pipeline** — Secured execution lifecycle: context → call chain guard → lookup → ACL → approval gate → middleware before → validation → execute → output validation → middleware after → return
23
- - **Registry system** — File-based module discovery with metadata, dependencies, and topological ordering
23
+ - **Registry system** — File-based module discovery with metadata, dependencies, and topological ordering; multi-class discovery from a single file
24
24
  - **Binding loader** — YAML-based module registration for no-code integration
25
25
  - **Access control (ACL)** — Pattern-based rules with identity types, roles, and call-depth conditions
26
26
  - **Approval system** — Pluggable approval gate in the executor pipeline with sync and async (polling) flows, built-in handlers, and tracing integration
27
- - **Middleware** — Onion-model middleware with before/after/onError hooks and error recovery
28
- - **Observability** — Tracing (spans + exporters), metrics (counters + histograms + Prometheus export), structured logging with redaction
27
+ - **Middleware** — Onion-model middleware with before/after/onError hooks and error recovery; built-in `CircuitBreakerMiddleware` (CLOSED/OPEN/HALF_OPEN) and OTel-compatible `TracingMiddleware`
28
+ - **Observability** — Tracing (spans + `BatchSpanProcessor` + exporters), metrics (counters + histograms + Prometheus export with `/metrics`/`/healthz`/`/readyz`), structured logging with `RedactionConfig`
29
+ - **System modules** — Built-in `system.*` modules for AI bidirectional introspection: health, manifest, usage, and runtime control (`update_config`, `reload_module`, `toggle_feature`). Audit trail via `AuditStore`, config persistence via `overridesPath`, usage metrics in Prometheus, bulk reload via `path_filter` glob
30
+ - **Event system** — `EventEmitter` with subscriber-level `CircuitBreakerWrapper`, built-in `FileSubscriber`, `StdoutSubscriber`, `FilterSubscriber`, and pluggable custom types
31
+ - **Async tasks** — `AsyncTaskManager` with injectable `TaskStore` (bring your own Redis/Postgres backend), `RetryConfig` with exponential backoff, and opt-in background reaper
29
32
  - **Schema export** — JSON/YAML schema export with strict and compact modes
30
33
  - **Caching & pagination annotations** — `cacheable`, `cacheTtl`, `cacheKeyFields` for result caching; `paginated`, `paginationStyle` for paginated modules
31
34
  - **Config Bus** — Namespace-based configuration registry with typed access, env prefix dispatch, hot-reload, and external config mounting (`Config.registerNamespace()`, `config.namespace()`, `config.bind<T>()`, `config.mount()`)
@@ -46,6 +49,8 @@ For full documentation, including Quick Start guides and API reference, visit:
46
49
  npm install apcore-js
47
50
  ```
48
51
 
52
+ > **Note:** The npm package is published as `apcore-js` (the `apcore` name is reserved on npm). Python uses `apcore`, Rust uses the `apcore` crate.
53
+
49
54
  ## Quick Start
50
55
 
51
56
  ### Simplified Client (Recommended)
@@ -75,6 +80,39 @@ const preflight = await client.validate('math.add', { a: 10, b: 5 });
75
80
  // => { valid: true, checks: [...], requiresApproval: false, errors: [] }
76
81
  ```
77
82
 
83
+ ### Enabling sys_modules for control/events APIs
84
+
85
+ `APCore.disable`, `APCore.enable`, `APCore.on`, and `APCore.off` are gated on the
86
+ optional system-modules subsystem. They require a `Config` instance with
87
+ `sys_modules.enabled: true` to be passed into the `APCore` constructor —
88
+ otherwise the calls throw with a clear "sys_modules must be enabled" message.
89
+
90
+ ```typescript
91
+ import { APCore, Config } from 'apcore-js';
92
+
93
+ // Inline config (equivalent to writing the same YAML and calling Config.load):
94
+ const config = new Config({
95
+ sys_modules: {
96
+ enabled: true,
97
+ // Optional sub-systems:
98
+ events: { enabled: true }, // required for on/off (event emitter)
99
+ control: { enabled: true }, // required for disable/enable (toggle)
100
+ },
101
+ });
102
+
103
+ const client = new APCore({ config });
104
+
105
+ // Now safe to call:
106
+ await client.disable('math.add');
107
+ await client.enable('math.add');
108
+ client.on('apcore.module.disabled', (event) => {
109
+ console.log('Disabled:', event.data);
110
+ });
111
+ ```
112
+
113
+ If you do not need these control/events APIs, omit the `Config` entirely (as in
114
+ the basic Quick Start above).
115
+
78
116
  ### Advanced: Manual Registry + Executor
79
117
 
80
118
  ```typescript
@@ -101,14 +139,20 @@ const result = await executor.call('example.greet', { name: 'World' });
101
139
 
102
140
  | Class | Description |
103
141
  |-------|-------------|
104
- | `APCore` | High-level client — register modules, call, stream, validate |
142
+ | `APCore` | High-level client — register modules, call, stream, validate, listModules, describe, on/off, disable/enable. Note: `disable`, `enable`, `on`, and `off` require `sys_modules.enabled: true` in the `Config` passed to `APCore` (see [Quick Start: Enabling sys_modules](#enabling-sys_modules-for-controlevents-apis)). |
105
143
  | `Registry` | Module storage — discover, register, get, list, watch |
106
144
  | `Executor` | Execution engine — call with middleware pipeline, ACL, approval |
107
145
  | `Context` | Request context — trace ID, identity, call chain, cancel token |
108
146
  | `Config` | Configuration — load from YAML, namespace bus, get/set/bind values |
109
147
  | `ACL` | Access control — rule-based caller/target authorization |
110
148
  | `Middleware` | Pipeline hooks — before/after/onError interception |
149
+ | `CircuitBreakerMiddleware` | Per-(module, caller) circuit breaker — CLOSED/OPEN/HALF_OPEN with configurable threshold and cooldown |
150
+ | `TracingMiddleware` | OTel-compatible span tracing — accepts any `OtelTracer`/`OtelSpan` without runtime `@opentelemetry/*` dependency |
111
151
  | `EventEmitter` | Event system — subscribe, emit, flush |
152
+ | `CircuitBreakerWrapper` | Subscriber-level circuit breaker — protects `EventEmitter` subscribers from cascading failures |
153
+ | `AsyncTaskManager` | Background task execution — injectable store, retry with backoff, opt-in reaper |
154
+ | `PrometheusExporter` | HTTP metrics server — `/metrics`, `/healthz`, `/readyz`; optional `usageCollector` for usage gauges |
155
+ | `InMemoryAuditStore` | Control module audit log — records actor, action, before/after change for every control call |
112
156
 
113
157
  ## Configuration
114
158
 
@@ -197,7 +241,7 @@ Configuration files support two modes. **Legacy mode** (no `apcore:` key) is ful
197
241
  ```yaml
198
242
  # Namespace mode
199
243
  apcore:
200
- version: "0.18.0"
244
+ version: "0.20.0"
201
245
 
202
246
  _config:
203
247
  strict: true
@@ -212,6 +256,51 @@ myPlugin:
212
256
  retries: 5
213
257
  ```
214
258
 
259
+ ### System Modules
260
+
261
+ `registerSysModules()` auto-registers the built-in `system.*` modules that let AI agents query, monitor, and control the apcore runtime. Enable them via `sys_modules.enabled: true` in config, and pass the optional hardening options for production use:
262
+
263
+ ```typescript
264
+ import { registerSysModules, InMemoryAuditStore } from 'apcore-js';
265
+
266
+ const auditStore = new InMemoryAuditStore();
267
+
268
+ registerSysModules(registry, executor, config, null, {
269
+ failOnError: true, // throw on any registration failure (default: false)
270
+ overridesPath: '/etc/apcore/overrides.yaml', // persist runtime changes across restarts
271
+ auditStore, // record every control-module action with actor + change
272
+ });
273
+
274
+ // Available system modules:
275
+ // system.health.summary / system.health.module — health status + error rates
276
+ // system.manifest.module / system.manifest.full — module introspection
277
+ // system.usage.summary / system.usage.module — call counts + latency trends
278
+ // system.control.update_config — hot-patch config values
279
+ // system.control.reload_module — hot-reload from disk; supports path_filter glob
280
+ // system.control.toggle_feature — disable/enable modules at runtime
281
+
282
+ // Query the audit log after control calls:
283
+ const entries = auditStore.query({ moduleId: 'system.control.update_config' });
284
+ // entries[0] = { timestamp, action, targetModuleId, actorId, actorType, traceId, change }
285
+ ```
286
+
287
+ **Prometheus usage metrics** — wire `PrometheusExporter` with the `UsageCollector` returned by `registerSysModules`:
288
+
289
+ ```typescript
290
+ import { PrometheusExporter, MetricsCollector } from 'apcore-js';
291
+
292
+ const ctx = registerSysModules(registry, executor, config);
293
+ const exporter = new PrometheusExporter({
294
+ collector: new MetricsCollector(),
295
+ usageCollector: ctx.usageCollector, // adds apcore_usage_* metrics to /metrics
296
+ });
297
+ exporter.start({ port: 9090 });
298
+ // GET /metrics now includes:
299
+ // apcore_usage_calls_total{module_id="math.add",status="success"} 5000
300
+ // apcore_usage_error_rate{module_id="math.add"} 0.0004
301
+ // apcore_usage_p99_latency_ms{module_id="math.add"} 45.0
302
+ ```
303
+
215
304
  ### Error Codes
216
305
 
217
306
  New error codes added in v0.15.0:
@@ -225,6 +314,17 @@ New error codes added in v0.15.0:
225
314
  | `CONFIG_BIND_ERROR` | `config.bind<T>()` or `config.getTyped<T>()` type guard fails |
226
315
  | `ERROR_FORMATTER_DUPLICATE` | `ErrorFormatterRegistry.register()` called for an already-registered surface |
227
316
 
317
+ New error codes added in v0.20.0:
318
+
319
+ | Code | Description |
320
+ |------|-------------|
321
+ | `CIRCUIT_BREAKER_OPEN` | `CircuitBreakerMiddleware` short-circuited a call because the circuit is OPEN |
322
+ | `MODULE_RELOAD_CONFLICT` | Both `module_id` and `path_filter` supplied to `system.control.reload_module` |
323
+ | `SYS_MODULE_REGISTRATION_FAILED` | `registerSysModules()` with `failOnError: true` and a module failed to register |
324
+ | `MODULE_ID_CONFLICT` | Two classes in the same file produce the same module ID segment (`discoverMultiClass`) |
325
+ | `INVALID_SEGMENT` | A derived class segment does not match `^[a-z][a-z0-9_]*$` |
326
+ | `ID_TOO_LONG` | A derived module ID exceeds 192 characters |
327
+
228
328
  ### Event Type Canonical Names
229
329
 
230
330
  apcore 0.15.0 resolved two event-type collisions in favor of dot-namespaced canonical
@@ -443,12 +543,15 @@ npm run build
443
543
 
444
544
  - Core executor pipeline
445
545
  - Schema validation (strict mode, type coercion)
446
- - Middleware chain (ordering, transforms, error recovery)
546
+ - Middleware chain (ordering, transforms, error recovery, circuit breaker)
447
547
  - ACL enforcement (patterns, conditions, identity types)
448
- - Registry system (scanner, metadata, entry points, dependencies)
548
+ - Registry system (scanner, metadata, entry points, dependencies, multi-class discovery)
449
549
  - Binding loader (YAML loading, target resolution, schema modes)
450
- - Observability (tracing, metrics, structured logging)
451
- - Integration tests (end-to-end flows, error propagation, safety checks)
550
+ - Observability (tracing, BatchSpanProcessor, metrics, Prometheus export, structured logging with redaction)
551
+ - Event system (circuit breaker wrapper, subscriber types, filter/file/stdout)
552
+ - System modules (health, manifest, usage, control, audit trail, overrides persistence, Prometheus usage metrics)
553
+ - Async tasks (pluggable store, retry backoff, reaper)
554
+ - Cross-language conformance suite (`tests/conformance.test.ts`) — canonical JSON fixtures from `apcore/conformance/fixtures/` run identically across Python, TypeScript, and Rust SDKs
452
555
 
453
556
  ## Links
454
557
 
@@ -11,6 +11,8 @@ export interface ACLConditionHandler {
11
11
  }
12
12
  /** Type alias for the recursive evaluation function used by compound handlers. */
13
13
  export type EvalFn = (conditions: Record<string, unknown>, context: Context) => boolean;
14
+ /** Async variant of EvalFn for use under asyncCheck(). */
15
+ export type AsyncEvalFn = (conditions: Record<string, unknown>, context: Context) => Promise<boolean>;
14
16
  /** Check context.identity.type is in the allowed list. */
15
17
  export declare class IdentityTypesHandler implements ACLConditionHandler {
16
18
  evaluate(value: unknown, context: Context): boolean;
@@ -35,6 +37,18 @@ export declare class NotHandler implements ACLConditionHandler {
35
37
  constructor(evaluateFn: EvalFn);
36
38
  evaluate(value: unknown, context: Context): boolean;
37
39
  }
40
+ /** $or async: list of condition dicts evaluated via the async evaluator. */
41
+ export declare class OrHandlerAsync implements ACLConditionHandler {
42
+ private readonly _evaluate;
43
+ constructor(evaluateFn: AsyncEvalFn);
44
+ evaluate(value: unknown, context: Context): Promise<boolean>;
45
+ }
46
+ /** $not async: single condition dict evaluated via the async evaluator. */
47
+ export declare class NotHandlerAsync implements ACLConditionHandler {
48
+ private readonly _evaluate;
49
+ constructor(evaluateFn: AsyncEvalFn);
50
+ evaluate(value: unknown, context: Context): Promise<boolean>;
51
+ }
38
52
  /** Compare two arrays for element-wise equality. */
39
53
  export declare function arraysEqual(a: unknown[], b: unknown[]): boolean;
40
54
  /** Deep equality for plain objects (conditions comparison). */
@@ -1 +1 @@
1
- {"version":3,"file":"acl-handlers.d.ts","sourceRoot":"","sources":["../src/acl-handlers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,+DAA+D;AAC/D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACxE;AAED,kFAAkF;AAClF,MAAM,MAAM,MAAM,GAAG,CACnB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,OAAO,EAAE,OAAO,KACb,OAAO,CAAC;AAMb,0DAA0D;AAC1D,qBAAa,oBAAqB,YAAW,mBAAmB;IAC9D,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;CAKpD;AAED,4EAA4E;AAC5E,qBAAa,YAAa,YAAW,mBAAmB;IACtD,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;CAMpD;AAED,yDAAyD;AACzD,qBAAa,mBAAoB,YAAW,mBAAmB;IAC7D,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;CAWpD;AAMD,wEAAwE;AACxE,qBAAa,SAAU,YAAW,mBAAmB;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,UAAU,EAAE,MAAM;IAI9B,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;CAQpD;AAED,sEAAsE;AACtE,qBAAa,UAAW,YAAW,mBAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,UAAU,EAAE,MAAM;IAI9B,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;CAIpD;AAMD,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAM/D;AAED,+DAA+D;AAC/D,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAyBzD"}
1
+ {"version":3,"file":"acl-handlers.d.ts","sourceRoot":"","sources":["../src/acl-handlers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,+DAA+D;AAC/D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACxE;AAED,kFAAkF;AAClF,MAAM,MAAM,MAAM,GAAG,CACnB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,OAAO,EAAE,OAAO,KACb,OAAO,CAAC;AAEb,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,CACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,OAAO,EAAE,OAAO,KACb,OAAO,CAAC,OAAO,CAAC,CAAC;AAMtB,0DAA0D;AAC1D,qBAAa,oBAAqB,YAAW,mBAAmB;IAC9D,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;CAKpD;AAED,4EAA4E;AAC5E,qBAAa,YAAa,YAAW,mBAAmB;IACtD,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;CAMpD;AAED,yDAAyD;AACzD,qBAAa,mBAAoB,YAAW,mBAAmB;IAC7D,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;CAgBpD;AAMD,wEAAwE;AACxE,qBAAa,SAAU,YAAW,mBAAmB;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,UAAU,EAAE,MAAM;IAI9B,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;CAQpD;AAED,sEAAsE;AACtE,qBAAa,UAAW,YAAW,mBAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,UAAU,EAAE,MAAM;IAI9B,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;CAIpD;AAED,4EAA4E;AAC5E,qBAAa,cAAe,YAAW,mBAAmB;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;gBAE5B,UAAU,EAAE,WAAW;IAI7B,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;CAQnE;AAED,2EAA2E;AAC3E,qBAAa,eAAgB,YAAW,mBAAmB;IACzD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;gBAE5B,UAAU,EAAE,WAAW;IAI7B,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;CAInE;AAMD,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAM/D;AAED,+DAA+D;AAC/D,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAyBzD"}
@@ -32,12 +32,15 @@ export class RolesHandler {
32
32
  export class MaxCallDepthHandler {
33
33
  evaluate(value, context) {
34
34
  let threshold;
35
- if (typeof value === 'object' && value !== null && 'lte' in value) {
36
- threshold = value.lte;
37
- }
38
- else if (typeof value === 'number') {
35
+ if (typeof value === 'number') {
39
36
  threshold = value;
40
37
  }
38
+ else if (typeof value === 'object' &&
39
+ value !== null &&
40
+ 'lte' in value &&
41
+ typeof value.lte === 'number') {
42
+ threshold = value.lte;
43
+ }
41
44
  else {
42
45
  return false;
43
46
  }
@@ -77,6 +80,36 @@ export class NotHandler {
77
80
  return !this._evaluate(value, context);
78
81
  }
79
82
  }
83
+ /** $or async: list of condition dicts evaluated via the async evaluator. */
84
+ export class OrHandlerAsync {
85
+ _evaluate;
86
+ constructor(evaluateFn) {
87
+ this._evaluate = evaluateFn;
88
+ }
89
+ async evaluate(value, context) {
90
+ if (!Array.isArray(value))
91
+ return false;
92
+ for (const sub of value) {
93
+ if (typeof sub !== 'object' || sub === null || Array.isArray(sub))
94
+ continue;
95
+ if (await this._evaluate(sub, context))
96
+ return true;
97
+ }
98
+ return false;
99
+ }
100
+ }
101
+ /** $not async: single condition dict evaluated via the async evaluator. */
102
+ export class NotHandlerAsync {
103
+ _evaluate;
104
+ constructor(evaluateFn) {
105
+ this._evaluate = evaluateFn;
106
+ }
107
+ async evaluate(value, context) {
108
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
109
+ return false;
110
+ return !(await this._evaluate(value, context));
111
+ }
112
+ }
80
113
  // ---------------------------------------------------------------------------
81
114
  // Utility functions for element-wise comparison (used by removeRule fix)
82
115
  // ---------------------------------------------------------------------------
@@ -1 +1 @@
1
- {"version":3,"file":"acl-handlers.js","sourceRoot":"","sources":["../src/acl-handlers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"acl-handlers.js","sourceRoot":"","sources":["../src/acl-handlers.ts"],"names":[],"mappings":"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"}
package/dist/acl.d.ts CHANGED
@@ -23,11 +23,32 @@ export interface AuditEntry {
23
23
  readonly roles: readonly string[];
24
24
  readonly callDepth: number | null;
25
25
  readonly traceId: string | null;
26
+ /** Error message from a condition handler that threw during evaluation, if any.
27
+ * Cross-language parity with apcore-python AuditEntry.handler_error (sync A-D-024). */
28
+ readonly handlerError: string | null;
26
29
  }
27
30
  export type AuditLogger = (entry: AuditEntry) => void;
28
31
  export declare class ACL {
29
32
  private static conditionHandlers;
33
+ private static asyncConditionHandlers;
30
34
  static registerCondition(key: string, handler: ACLConditionHandler): void;
35
+ /** Register an async-aware handler for use specifically under asyncCheck(). Falls back to conditionHandlers. */
36
+ static registerAsyncCondition(key: string, handler: ACLConditionHandler): void;
37
+ /**
38
+ * Per-call-stack handler-error message captured by `_evaluateConditions[Async]`.
39
+ * Set inside catch blocks; consumed by `_buildAuditEntry` to populate
40
+ * `AuditEntry.handlerError`. Mirrors apcore-python's `_handler_error_var`
41
+ * contextvar (sync finding A-D-026). JS is single-threaded so a static field
42
+ * is sufficient — the accessor pair `_takeLastHandlerError()` and the
43
+ * implicit reset at each `check()` / `asyncCheck()` call ensures no leakage
44
+ * across evaluations.
45
+ */
46
+ private static _lastHandlerError;
47
+ /**
48
+ * Read-and-clear the most recent handler-error message captured by an
49
+ * `_evaluateConditions[Async]` invocation.
50
+ */
51
+ static _takeLastHandlerError(): string | null;
31
52
  static _evaluateConditions(conditions: Record<string, unknown>, context: Context): boolean;
32
53
  static _evaluateConditionsAsync(conditions: Record<string, unknown>, context: Context): Promise<boolean>;
33
54
  private _rules;
@@ -47,7 +68,7 @@ export declare class ACL {
47
68
  private _matchesRule;
48
69
  private _checkConditions;
49
70
  addRule(rule: ACLRule): void;
50
- removeRule(callers: string[], targets: string[]): boolean;
71
+ removeRule(callers: string[], targets: string[], conditions?: Record<string, unknown> | null): boolean;
51
72
  reload(): void;
52
73
  }
53
74
  //# sourceMappingURL=acl.d.ts.map
package/dist/acl.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"acl.d.ts","sourceRoot":"","sources":["../src/acl.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG5C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAc7D,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC7C;AAED,kDAAkD;AAClD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;AAsCtD,qBAAa,GAAG;IACd,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAA0C;IAE1E,MAAM,CAAC,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI;IAIzE,MAAM,CAAC,mBAAmB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;WAyB7E,wBAAwB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAkB9G,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,YAAY,CAA4B;IAChD,KAAK,EAAE,OAAO,CAAS;gBAEX,KAAK,EAAE,OAAO,EAAE,EAAE,aAAa,GAAE,MAAe,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI;IAS9F,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG;IAqClC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO;IA6B7E,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;YA6BzF,mBAAmB;YAoBnB,iBAAiB;IAY/B,OAAO,CAAC,gBAAgB;IAsCxB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,cAAc;IAiBtB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAI5B,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO;IAWzD,MAAM,IAAI,IAAI;CASf"}
1
+ {"version":3,"file":"acl.d.ts","sourceRoot":"","sources":["../src/acl.ts"],"names":[],"mappings":"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"}
package/dist/acl.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import yaml from 'js-yaml';
5
5
  import { ACLRuleError, ConfigNotFoundError } from './errors.js';
6
6
  import { matchPattern } from './utils/pattern.js';
7
- import { IdentityTypesHandler, RolesHandler, MaxCallDepthHandler, OrHandler, NotHandler, arraysEqual, } from './acl-handlers.js';
7
+ import { IdentityTypesHandler, RolesHandler, MaxCallDepthHandler, OrHandler, NotHandler, OrHandlerAsync, NotHandlerAsync, arraysEqual, deepEqual, } from './acl-handlers.js';
8
8
  // Lazy-load Node.js built-in modules for browser compatibility
9
9
  let _nodeFs = null;
10
10
  try {
@@ -43,28 +43,58 @@ function parseAclRule(rawRule, index) {
43
43
  }
44
44
  export class ACL {
45
45
  static conditionHandlers = new Map();
46
+ static asyncConditionHandlers = new Map();
46
47
  static registerCondition(key, handler) {
47
48
  ACL.conditionHandlers.set(key, handler);
48
49
  }
50
+ /** Register an async-aware handler for use specifically under asyncCheck(). Falls back to conditionHandlers. */
51
+ static registerAsyncCondition(key, handler) {
52
+ ACL.asyncConditionHandlers.set(key, handler);
53
+ }
54
+ /**
55
+ * Per-call-stack handler-error message captured by `_evaluateConditions[Async]`.
56
+ * Set inside catch blocks; consumed by `_buildAuditEntry` to populate
57
+ * `AuditEntry.handlerError`. Mirrors apcore-python's `_handler_error_var`
58
+ * contextvar (sync finding A-D-026). JS is single-threaded so a static field
59
+ * is sufficient — the accessor pair `_takeLastHandlerError()` and the
60
+ * implicit reset at each `check()` / `asyncCheck()` call ensures no leakage
61
+ * across evaluations.
62
+ */
63
+ static _lastHandlerError = null;
64
+ /**
65
+ * Read-and-clear the most recent handler-error message captured by an
66
+ * `_evaluateConditions[Async]` invocation.
67
+ */
68
+ static _takeLastHandlerError() {
69
+ const err = ACL._lastHandlerError;
70
+ ACL._lastHandlerError = null;
71
+ return err;
72
+ }
49
73
  static _evaluateConditions(conditions, context) {
50
74
  for (const [key, value] of Object.entries(conditions)) {
51
75
  const handler = ACL.conditionHandlers.get(key);
52
76
  if (handler === undefined) {
53
- console.warn(`[apcore:acl] Unknown ACL condition '${key}' — treated as unsatisfied`);
77
+ const msg = `Unknown ACL condition '${key}'`;
78
+ ACL._lastHandlerError = msg;
79
+ console.warn(`[apcore:acl] ${msg} — treated as unsatisfied`);
54
80
  return false;
55
81
  }
56
82
  try {
57
83
  const result = handler.evaluate(value, context);
58
84
  if (result instanceof Promise) {
59
85
  // Async handler in sync context — fail-closed
60
- console.warn(`[apcore:acl] Async condition '${key}' in sync context — treated as unsatisfied. Use asyncCheck().`);
86
+ const msg = `Async condition '${key}' in sync context — use asyncCheck()`;
87
+ ACL._lastHandlerError = msg;
88
+ console.warn(`[apcore:acl] ${msg}`);
61
89
  return false;
62
90
  }
63
91
  if (!result)
64
92
  return false;
65
93
  }
66
- catch {
67
- console.warn(`[apcore:acl] Handler for condition '${key}' threw treated as unsatisfied`);
94
+ catch (e) {
95
+ const msg = `Handler for condition '${key}' threw: ${e instanceof Error ? e.message : String(e)}`;
96
+ ACL._lastHandlerError = msg;
97
+ console.warn(`[apcore:acl] ${msg} — treated as unsatisfied`);
68
98
  return false;
69
99
  }
70
100
  }
@@ -72,9 +102,11 @@ export class ACL {
72
102
  }
73
103
  static async _evaluateConditionsAsync(conditions, context) {
74
104
  for (const [key, value] of Object.entries(conditions)) {
75
- const handler = ACL.conditionHandlers.get(key);
105
+ const handler = ACL.asyncConditionHandlers.get(key) ?? ACL.conditionHandlers.get(key);
76
106
  if (handler === undefined) {
77
- console.warn(`[apcore:acl] Unknown ACL condition '${key}' — treated as unsatisfied`);
107
+ const msg = `Unknown ACL condition '${key}'`;
108
+ ACL._lastHandlerError = msg;
109
+ console.warn(`[apcore:acl] ${msg} — treated as unsatisfied`);
78
110
  return false;
79
111
  }
80
112
  try {
@@ -82,8 +114,10 @@ export class ACL {
82
114
  if (!result)
83
115
  return false;
84
116
  }
85
- catch {
86
- console.warn(`[apcore:acl] Handler for condition '${key}' threw treated as unsatisfied`);
117
+ catch (e) {
118
+ const msg = `Handler for condition '${key}' threw: ${e instanceof Error ? e.message : String(e)}`;
119
+ ACL._lastHandlerError = msg;
120
+ console.warn(`[apcore:acl] ${msg} — treated as unsatisfied`);
87
121
  return false;
88
122
  }
89
123
  }
@@ -137,44 +171,59 @@ export class ACL {
137
171
  check(callerId, targetId, context) {
138
172
  const effectiveCaller = callerId === null ? '@external' : callerId;
139
173
  const ctx = context ?? null;
140
- for (let idx = 0; idx < this._rules.length; idx++) {
141
- const rule = this._rules[idx];
174
+ // Snapshot rules + defaultEffect + auditLogger atomically so concurrent
175
+ // addRule/removeRule/setDefaultEffect calls cannot mutate state mid-evaluation.
176
+ // Mirrors asyncCheck snapshot semantics for sync/async parity.
177
+ const rules = this._rules.slice();
178
+ const defaultEffect = this._defaultEffect;
179
+ const auditLogger = this._auditLogger;
180
+ // Clear any leftover handler-error captured by a previous evaluation.
181
+ ACL._takeLastHandlerError();
182
+ for (let idx = 0; idx < rules.length; idx++) {
183
+ const rule = rules[idx];
142
184
  if (this._matchesRule(rule, effectiveCaller, targetId, ctx)) {
143
185
  const decision = rule.effect === 'allow';
144
- if (this._auditLogger) {
145
- this._auditLogger(this._buildAuditEntry(effectiveCaller, targetId, decision ? 'allow' : 'deny', 'rule_match', rule, idx, ctx));
186
+ if (auditLogger) {
187
+ auditLogger(this._buildAuditEntry(effectiveCaller, targetId, decision ? 'allow' : 'deny', 'rule_match', rule, idx, ctx, ACL._takeLastHandlerError()));
146
188
  }
147
189
  return decision;
148
190
  }
149
191
  }
150
- const defaultDecision = this._defaultEffect === 'allow';
151
- if (this._auditLogger) {
152
- const reason = this._rules.length === 0 ? 'no_rules' : 'default_effect';
153
- this._auditLogger(this._buildAuditEntry(effectiveCaller, targetId, defaultDecision ? 'allow' : 'deny', reason, null, null, ctx));
192
+ const defaultDecision = defaultEffect === 'allow';
193
+ if (auditLogger) {
194
+ const reason = rules.length === 0 ? 'no_rules' : 'default_effect';
195
+ auditLogger(this._buildAuditEntry(effectiveCaller, targetId, defaultDecision ? 'allow' : 'deny', reason, null, null, ctx, ACL._takeLastHandlerError()));
154
196
  }
155
197
  return defaultDecision;
156
198
  }
157
199
  async asyncCheck(callerId, targetId, context) {
158
200
  const effectiveCaller = callerId === null ? '@external' : callerId;
159
201
  const ctx = context ?? null;
160
- for (let idx = 0; idx < this._rules.length; idx++) {
161
- const rule = this._rules[idx];
202
+ // Snapshot mutable fields before any await to prevent async-gap races
203
+ // (e.g. a concurrent setDefaultEffect() or addRule() call mid-evaluation).
204
+ const rules = this._rules.slice();
205
+ const defaultEffect = this._defaultEffect;
206
+ const auditLogger = this._auditLogger;
207
+ // Clear any leftover handler-error captured by a previous evaluation.
208
+ ACL._takeLastHandlerError();
209
+ for (let idx = 0; idx < rules.length; idx++) {
210
+ const rule = rules[idx];
162
211
  if (await this._matchesRuleAsync(rule, effectiveCaller, targetId, ctx)) {
163
212
  const decision = rule.effect === 'allow';
164
- if (this._auditLogger) {
165
- this._auditLogger(this._buildAuditEntry(effectiveCaller, targetId, decision ? 'allow' : 'deny', 'rule_match', rule, idx, ctx));
213
+ if (auditLogger) {
214
+ auditLogger(this._buildAuditEntry(effectiveCaller, targetId, decision ? 'allow' : 'deny', 'rule_match', rule, idx, ctx, ACL._takeLastHandlerError()));
166
215
  }
167
216
  return decision;
168
217
  }
169
218
  }
170
- const defaultDecision = this._defaultEffect === 'allow';
171
- if (this._auditLogger) {
172
- const reason = this._rules.length === 0 ? 'no_rules' : 'default_effect';
173
- this._auditLogger(this._buildAuditEntry(effectiveCaller, targetId, defaultDecision ? 'allow' : 'deny', reason, null, null, ctx));
219
+ const defaultDecision = defaultEffect === 'allow';
220
+ if (auditLogger) {
221
+ const reason = rules.length === 0 ? 'no_rules' : 'default_effect';
222
+ auditLogger(this._buildAuditEntry(effectiveCaller, targetId, defaultDecision ? 'allow' : 'deny', reason, null, null, ctx, ACL._takeLastHandlerError()));
174
223
  }
175
224
  return defaultDecision;
176
225
  }
177
- async _matchPatternsAsync(patterns, value, context) {
226
+ _matchPatternsAsync(patterns, value, context) {
178
227
  if (patterns.length === 0)
179
228
  return false;
180
229
  // Check for compound operators
@@ -195,9 +244,9 @@ export class ACL {
195
244
  return patterns.some((p) => this._matchPattern(p, value, context));
196
245
  }
197
246
  async _matchesRuleAsync(rule, caller, target, context) {
198
- if (!await this._matchPatternsAsync(rule.callers, caller, context))
247
+ if (!this._matchPatternsAsync(rule.callers, caller, context))
199
248
  return false;
200
- if (!await this._matchPatternsAsync(rule.targets, target, context))
249
+ if (!this._matchPatternsAsync(rule.targets, target, context))
201
250
  return false;
202
251
  if (rule.conditions != null) {
203
252
  if (context === null)
@@ -207,7 +256,7 @@ export class ACL {
207
256
  }
208
257
  return true;
209
258
  }
210
- _buildAuditEntry(callerId, targetId, decision, reason, matchedRule, matchedRuleIndex, context) {
259
+ _buildAuditEntry(callerId, targetId, decision, reason, matchedRule, matchedRuleIndex, context, handlerError = null) {
211
260
  let identityType = null;
212
261
  let roles = [];
213
262
  let callDepth = null;
@@ -232,6 +281,7 @@ export class ACL {
232
281
  roles,
233
282
  callDepth,
234
283
  traceId,
284
+ handlerError,
235
285
  };
236
286
  }
237
287
  _matchPattern(pattern, value, context) {
@@ -277,13 +327,15 @@ export class ACL {
277
327
  addRule(rule) {
278
328
  this._rules.unshift(rule);
279
329
  }
280
- removeRule(callers, targets) {
330
+ removeRule(callers, targets, conditions) {
281
331
  for (let i = 0; i < this._rules.length; i++) {
282
332
  const rule = this._rules[i];
283
- if (arraysEqual(rule.callers, callers) && arraysEqual(rule.targets, targets)) {
284
- this._rules.splice(i, 1);
285
- return true;
286
- }
333
+ if (!arraysEqual(rule.callers, callers) || !arraysEqual(rule.targets, targets))
334
+ continue;
335
+ if (conditions !== undefined && !deepEqual(rule.conditions ?? null, conditions ?? null))
336
+ continue;
337
+ this._rules.splice(i, 1);
338
+ return true;
287
339
  }
288
340
  return false;
289
341
  }
@@ -309,4 +361,8 @@ ACL.registerCondition('roles', new RolesHandler());
309
361
  ACL.registerCondition('max_call_depth', new MaxCallDepthHandler());
310
362
  ACL.registerCondition('$or', new OrHandler(ACL._evaluateConditions.bind(ACL)));
311
363
  ACL.registerCondition('$not', new NotHandler(ACL._evaluateConditions.bind(ACL)));
364
+ // Async-aware variants used by asyncCheck() so Promise-returning conditions
365
+ // inside $or/$not are awaited rather than dropped via fail-closed.
366
+ ACL.registerAsyncCondition('$or', new OrHandlerAsync(ACL._evaluateConditionsAsync.bind(ACL)));
367
+ ACL.registerAsyncCondition('$not', new NotHandlerAsync(ACL._evaluateConditionsAsync.bind(ACL)));
312
368
  //# sourceMappingURL=acl.js.map