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.
- package/README.md +112 -9
- package/dist/acl-handlers.d.ts +14 -0
- package/dist/acl-handlers.d.ts.map +1 -1
- package/dist/acl-handlers.js +37 -4
- package/dist/acl-handlers.js.map +1 -1
- package/dist/acl.d.ts +22 -1
- package/dist/acl.d.ts.map +1 -1
- package/dist/acl.js +90 -34
- package/dist/acl.js.map +1 -1
- package/dist/async-task.d.ts +70 -16
- package/dist/async-task.d.ts.map +1 -1
- package/dist/async-task.js +212 -72
- package/dist/async-task.js.map +1 -1
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +113 -11
- package/dist/bindings.js.map +1 -1
- package/dist/builtin-steps.d.ts +33 -8
- package/dist/builtin-steps.d.ts.map +1 -1
- package/dist/builtin-steps.js +119 -47
- package/dist/builtin-steps.js.map +1 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +163 -33
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +34 -7
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +108 -40
- package/dist/context.js.map +1 -1
- package/dist/decorator.d.ts +3 -0
- package/dist/decorator.d.ts.map +1 -1
- package/dist/decorator.js +3 -0
- package/dist/decorator.js.map +1 -1
- package/dist/errors.d.ts +88 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +231 -56
- package/dist/errors.js.map +1 -1
- package/dist/events/circuit-breaker.d.ts +45 -0
- package/dist/events/circuit-breaker.d.ts.map +1 -0
- package/dist/events/circuit-breaker.js +115 -0
- package/dist/events/circuit-breaker.js.map +1 -0
- package/dist/events/emitter.d.ts +24 -1
- package/dist/events/emitter.d.ts.map +1 -1
- package/dist/events/emitter.js +86 -12
- package/dist/events/emitter.js.map +1 -1
- package/dist/events/index.d.ts +4 -2
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +3 -2
- package/dist/events/index.js.map +1 -1
- package/dist/events/subscribers.d.ts +33 -1
- package/dist/events/subscribers.d.ts.map +1 -1
- package/dist/events/subscribers.js +124 -1
- package/dist/events/subscribers.js.map +1 -1
- package/dist/executor.d.ts +14 -3
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +155 -48
- package/dist/executor.js.map +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/index.d.ts +47 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -18
- package/dist/index.js.map +1 -1
- package/dist/middleware/base.d.ts +25 -3
- package/dist/middleware/base.d.ts.map +1 -1
- package/dist/middleware/base.js +24 -0
- package/dist/middleware/base.js.map +1 -1
- package/dist/middleware/circuit-breaker.d.ts +54 -0
- package/dist/middleware/circuit-breaker.d.ts.map +1 -0
- package/dist/middleware/circuit-breaker.js +168 -0
- package/dist/middleware/circuit-breaker.js.map +1 -0
- package/dist/middleware/context-namespace.d.ts +30 -0
- package/dist/middleware/context-namespace.d.ts.map +1 -0
- package/dist/middleware/context-namespace.js +38 -0
- package/dist/middleware/context-namespace.js.map +1 -0
- package/dist/middleware/index.d.ts +8 -2
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +5 -2
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/logging.d.ts +6 -0
- package/dist/middleware/logging.d.ts.map +1 -1
- package/dist/middleware/logging.js +13 -3
- package/dist/middleware/logging.js.map +1 -1
- package/dist/middleware/manager.d.ts +11 -4
- package/dist/middleware/manager.d.ts.map +1 -1
- package/dist/middleware/manager.js +26 -9
- package/dist/middleware/manager.js.map +1 -1
- package/dist/middleware/platform-notify.d.ts +8 -4
- package/dist/middleware/platform-notify.d.ts.map +1 -1
- package/dist/middleware/platform-notify.js +15 -7
- package/dist/middleware/platform-notify.js.map +1 -1
- package/dist/middleware/retry.d.ts +16 -7
- package/dist/middleware/retry.d.ts.map +1 -1
- package/dist/middleware/retry.js +21 -15
- package/dist/middleware/retry.js.map +1 -1
- package/dist/middleware/tracing.d.ts +50 -0
- package/dist/middleware/tracing.d.ts.map +1 -0
- package/dist/middleware/tracing.js +89 -0
- package/dist/middleware/tracing.js.map +1 -0
- package/dist/observability/batch-span-processor.d.ts +48 -0
- package/dist/observability/batch-span-processor.d.ts.map +1 -0
- package/dist/observability/batch-span-processor.js +89 -0
- package/dist/observability/batch-span-processor.js.map +1 -0
- package/dist/observability/context-logger.d.ts +54 -1
- package/dist/observability/context-logger.d.ts.map +1 -1
- package/dist/observability/context-logger.js +287 -10
- package/dist/observability/context-logger.js.map +1 -1
- package/dist/observability/error-history.d.ts +36 -7
- package/dist/observability/error-history.d.ts.map +1 -1
- package/dist/observability/error-history.js +169 -50
- package/dist/observability/error-history.js.map +1 -1
- package/dist/observability/index.d.ts +16 -5
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +8 -3
- package/dist/observability/index.js.map +1 -1
- package/dist/observability/metrics-utils.d.ts.map +1 -1
- package/dist/observability/metrics-utils.js +3 -5
- package/dist/observability/metrics-utils.js.map +1 -1
- package/dist/observability/metrics.d.ts +15 -1
- package/dist/observability/metrics.d.ts.map +1 -1
- package/dist/observability/metrics.js +37 -3
- package/dist/observability/metrics.js.map +1 -1
- package/dist/observability/prometheus-exporter.d.ts +37 -0
- package/dist/observability/prometheus-exporter.d.ts.map +1 -0
- package/dist/observability/prometheus-exporter.js +135 -0
- package/dist/observability/prometheus-exporter.js.map +1 -0
- package/dist/observability/storage.d.ts +43 -0
- package/dist/observability/storage.d.ts.map +1 -0
- package/dist/observability/storage.js +58 -0
- package/dist/observability/storage.js.map +1 -0
- package/dist/observability/store.d.ts +29 -0
- package/dist/observability/store.d.ts.map +1 -0
- package/dist/observability/store.js +36 -0
- package/dist/observability/store.js.map +1 -0
- package/dist/observability/tracing.d.ts +2 -0
- package/dist/observability/tracing.d.ts.map +1 -1
- package/dist/observability/tracing.js +12 -2
- package/dist/observability/tracing.js.map +1 -1
- package/dist/observability/usage-exporter.d.ts +58 -0
- package/dist/observability/usage-exporter.d.ts.map +1 -0
- package/dist/observability/usage-exporter.js +86 -0
- package/dist/observability/usage-exporter.js.map +1 -0
- package/dist/observability/usage.d.ts +18 -1
- package/dist/observability/usage.d.ts.map +1 -1
- package/dist/observability/usage.js +35 -4
- package/dist/observability/usage.js.map +1 -1
- package/dist/pipeline-config.d.ts +24 -7
- package/dist/pipeline-config.d.ts.map +1 -1
- package/dist/pipeline-config.js +113 -19
- package/dist/pipeline-config.js.map +1 -1
- package/dist/pipeline.d.ts +123 -2
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +249 -50
- package/dist/pipeline.js.map +1 -1
- package/dist/registry/conflicts.d.ts +2 -2
- package/dist/registry/conflicts.d.ts.map +1 -1
- package/dist/registry/conflicts.js +10 -11
- package/dist/registry/conflicts.js.map +1 -1
- package/dist/registry/dependencies.d.ts +1 -1
- package/dist/registry/dependencies.d.ts.map +1 -1
- package/dist/registry/dependencies.js +69 -20
- package/dist/registry/dependencies.js.map +1 -1
- package/dist/registry/index.d.ts +2 -0
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +1 -0
- package/dist/registry/index.js.map +1 -1
- package/dist/registry/multi-class.d.ts +57 -0
- package/dist/registry/multi-class.d.ts.map +1 -0
- package/dist/registry/multi-class.js +120 -0
- package/dist/registry/multi-class.js.map +1 -0
- package/dist/registry/registry.d.ts +99 -4
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +291 -33
- package/dist/registry/registry.js.map +1 -1
- package/dist/registry/scanner.d.ts.map +1 -1
- package/dist/registry/scanner.js +6 -0
- package/dist/registry/scanner.js.map +1 -1
- package/dist/registry/version.d.ts +1 -0
- package/dist/registry/version.d.ts.map +1 -1
- package/dist/registry/version.js +33 -4
- package/dist/registry/version.js.map +1 -1
- package/dist/schema/constants.d.ts +9 -0
- package/dist/schema/constants.d.ts.map +1 -0
- package/dist/schema/constants.js +9 -0
- package/dist/schema/constants.js.map +1 -0
- package/dist/schema/extractor.d.ts +69 -0
- package/dist/schema/extractor.d.ts.map +1 -0
- package/dist/schema/extractor.js +142 -0
- package/dist/schema/extractor.js.map +1 -0
- package/dist/schema/index.d.ts +3 -1
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +2 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/loader.d.ts +27 -3
- package/dist/schema/loader.d.ts.map +1 -1
- package/dist/schema/loader.js +137 -32
- package/dist/schema/loader.js.map +1 -1
- package/dist/schema/ref-resolver.d.ts.map +1 -1
- package/dist/schema/ref-resolver.js +10 -1
- package/dist/schema/ref-resolver.js.map +1 -1
- package/dist/schema/types.d.ts +4 -0
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js.map +1 -1
- package/dist/schema/validator.d.ts +9 -0
- package/dist/schema/validator.d.ts.map +1 -1
- package/dist/schema/validator.js +153 -4
- package/dist/schema/validator.js.map +1 -1
- package/dist/sys-modules/audit.d.ts +50 -0
- package/dist/sys-modules/audit.d.ts.map +1 -0
- package/dist/sys-modules/audit.js +89 -0
- package/dist/sys-modules/audit.js.map +1 -0
- package/dist/sys-modules/control.d.ts +32 -4
- package/dist/sys-modules/control.d.ts.map +1 -1
- package/dist/sys-modules/control.js +197 -23
- package/dist/sys-modules/control.js.map +1 -1
- package/dist/sys-modules/index.d.ts +7 -2
- package/dist/sys-modules/index.d.ts.map +1 -1
- package/dist/sys-modules/index.js +3 -1
- package/dist/sys-modules/index.js.map +1 -1
- package/dist/sys-modules/overrides.d.ts +58 -0
- package/dist/sys-modules/overrides.d.ts.map +1 -0
- package/dist/sys-modules/overrides.js +106 -0
- package/dist/sys-modules/overrides.js.map +1 -0
- package/dist/sys-modules/registration.d.ts +18 -1
- package/dist/sys-modules/registration.d.ts.map +1 -1
- package/dist/sys-modules/registration.js +115 -11
- package/dist/sys-modules/registration.js.map +1 -1
- package/dist/sys-modules/toggle.d.ts +7 -2
- package/dist/sys-modules/toggle.d.ts.map +1 -1
- package/dist/sys-modules/toggle.js +61 -5
- package/dist/sys-modules/toggle.js.map +1 -1
- package/dist/trace-context.d.ts +47 -9
- package/dist/trace-context.d.ts.map +1 -1
- package/dist/trace-context.js +139 -16
- package/dist/trace-context.js.map +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/index.js.map +1 -1
- 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
|
|
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.
|
|
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
|
-
-
|
|
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
|
|
package/dist/acl-handlers.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/acl-handlers.js
CHANGED
|
@@ -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 === '
|
|
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
|
// ---------------------------------------------------------------------------
|
package/dist/acl-handlers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acl-handlers.js","sourceRoot":"","sources":["../src/acl-handlers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
|
|
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 (
|
|
145
|
-
|
|
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 =
|
|
151
|
-
if (
|
|
152
|
-
const reason =
|
|
153
|
-
|
|
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
|
-
|
|
161
|
-
|
|
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 (
|
|
165
|
-
|
|
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 =
|
|
171
|
-
if (
|
|
172
|
-
const reason =
|
|
173
|
-
|
|
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
|
-
|
|
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 (!
|
|
247
|
+
if (!this._matchPatternsAsync(rule.callers, caller, context))
|
|
199
248
|
return false;
|
|
200
|
-
if (!
|
|
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)
|
|
284
|
-
|
|
285
|
-
|
|
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
|