@nifrajs/core 1.13.0 → 2.1.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 +37 -1
- package/dist/assurance.d.ts +11 -2
- package/dist/assurance.d.ts.map +1 -1
- package/dist/assurance.js +20 -0
- package/dist/assurance.js.map +1 -1
- package/dist/capabilities.d.ts +54 -2
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +240 -5
- package/dist/capabilities.js.map +1 -1
- package/dist/durable-adapters.d.ts +241 -0
- package/dist/durable-adapters.d.ts.map +1 -0
- package/dist/durable-adapters.js +697 -0
- package/dist/durable-adapters.js.map +1 -0
- package/dist/durable-execution.d.ts +349 -0
- package/dist/durable-execution.d.ts.map +1 -0
- package/dist/durable-execution.js +1173 -0
- package/dist/durable-execution.js.map +1 -0
- package/dist/effect-ledger.d.ts +9 -0
- package/dist/effect-ledger.d.ts.map +1 -0
- package/dist/effect-ledger.js +8 -0
- package/dist/effect-ledger.js.map +1 -0
- package/dist/effect-lifecycle.d.ts +42 -0
- package/dist/effect-lifecycle.d.ts.map +1 -0
- package/dist/effect-lifecycle.js +66 -0
- package/dist/effect-lifecycle.js.map +1 -0
- package/dist/effect-scope.d.ts +75 -0
- package/dist/effect-scope.d.ts.map +1 -0
- package/dist/effect-scope.js +177 -0
- package/dist/effect-scope.js.map +1 -0
- package/dist/idempotency-plugin.d.ts +13 -0
- package/dist/idempotency-plugin.d.ts.map +1 -0
- package/dist/idempotency-plugin.js +16 -0
- package/dist/idempotency-plugin.js.map +1 -0
- package/dist/index.d.ts +6 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -31
- package/dist/index.js.map +1 -1
- package/dist/internal/capability-runtime.d.ts +62 -1
- package/dist/internal/capability-runtime.d.ts.map +1 -1
- package/dist/internal/capability-runtime.js +11 -2
- package/dist/internal/capability-runtime.js.map +1 -1
- package/dist/internal/effect-execution.d.ts +16 -0
- package/dist/internal/effect-execution.d.ts.map +1 -0
- package/dist/internal/effect-execution.js +50 -0
- package/dist/internal/effect-execution.js.map +1 -0
- package/dist/internal/safe-equal.d.ts +20 -0
- package/dist/internal/safe-equal.d.ts.map +1 -0
- package/dist/internal/safe-equal.js +31 -0
- package/dist/internal/safe-equal.js.map +1 -0
- package/dist/ledger.d.ts +14 -6
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +31 -5
- package/dist/ledger.js.map +1 -1
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +56 -0
- package/dist/mcp.js.map +1 -0
- package/dist/node-direct.d.ts +5 -0
- package/dist/node-direct.d.ts.map +1 -0
- package/dist/node-direct.js +27 -0
- package/dist/node-direct.js.map +1 -0
- package/dist/reconciliation-worker.d.ts +108 -0
- package/dist/reconciliation-worker.d.ts.map +1 -0
- package/dist/reconciliation-worker.js +239 -0
- package/dist/reconciliation-worker.js.map +1 -0
- package/dist/reflection.d.ts +5 -0
- package/dist/reflection.d.ts.map +1 -1
- package/dist/reflection.js +2 -0
- package/dist/reflection.js.map +1 -1
- package/dist/server/client-ip.d.ts +35 -0
- package/dist/server/client-ip.d.ts.map +1 -0
- package/dist/server/client-ip.js +42 -0
- package/dist/server/client-ip.js.map +1 -0
- package/dist/server/context.d.ts +47 -2
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/contract.d.ts +14 -4
- package/dist/server/contract.d.ts.map +1 -1
- package/dist/server/contract.js +8 -2
- package/dist/server/contract.js.map +1 -1
- package/dist/server/http.d.ts +14 -0
- package/dist/server/http.d.ts.map +1 -0
- package/dist/server/http.js +45 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/idempotency-lane.d.ts +45 -0
- package/dist/server/idempotency-lane.d.ts.map +1 -0
- package/dist/server/idempotency-lane.js +205 -0
- package/dist/server/idempotency-lane.js.map +1 -0
- package/dist/server/install.d.ts +21 -0
- package/dist/server/install.d.ts.map +1 -0
- package/dist/server/install.js +21 -0
- package/dist/server/install.js.map +1 -0
- package/dist/server/ledger-lane.d.ts +53 -0
- package/dist/server/ledger-lane.d.ts.map +1 -0
- package/dist/server/ledger-lane.js +82 -0
- package/dist/server/ledger-lane.js.map +1 -0
- package/dist/server/mcp-hook.d.ts +28 -0
- package/dist/server/mcp-hook.d.ts.map +1 -0
- package/dist/server/mcp-hook.js +2 -0
- package/dist/server/mcp-hook.js.map +1 -0
- package/dist/server/node-outcome-hook.d.ts +9 -0
- package/dist/server/node-outcome-hook.d.ts.map +1 -0
- package/dist/server/node-outcome-hook.js +2 -0
- package/dist/server/node-outcome-hook.js.map +1 -0
- package/dist/server/node-outcome.d.ts +37 -0
- package/dist/server/node-outcome.d.ts.map +1 -0
- package/dist/server/node-outcome.js +75 -0
- package/dist/server/node-outcome.js.map +1 -0
- package/dist/server/plugin.d.ts +78 -0
- package/dist/server/plugin.d.ts.map +1 -0
- package/dist/server/plugin.js +57 -0
- package/dist/server/plugin.js.map +1 -0
- package/dist/server/query.d.ts +18 -0
- package/dist/server/query.d.ts.map +1 -0
- package/dist/server/query.js +102 -0
- package/dist/server/query.js.map +1 -0
- package/dist/server/registry.d.ts +31 -6
- package/dist/server/registry.d.ts.map +1 -1
- package/dist/server/request-context.d.ts +51 -0
- package/dist/server/request-context.d.ts.map +1 -0
- package/dist/server/request-context.js +184 -0
- package/dist/server/request-context.js.map +1 -0
- package/dist/server/respond.d.ts +17 -0
- package/dist/server/respond.d.ts.map +1 -0
- package/dist/server/respond.js +81 -0
- package/dist/server/respond.js.map +1 -0
- package/dist/server/runtime-core.d.ts +36 -0
- package/dist/server/runtime-core.d.ts.map +1 -0
- package/dist/server/runtime-core.js +51 -0
- package/dist/server/runtime-core.js.map +1 -0
- package/dist/server/server-types.d.ts +203 -0
- package/dist/server/server-types.d.ts.map +1 -0
- package/dist/server/server-types.js +2 -0
- package/dist/server/server-types.js.map +1 -0
- package/dist/server/server.d.ts +119 -345
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +247 -833
- package/dist/server/server.js.map +1 -1
- package/dist/server/sse-hook.d.ts +10 -0
- package/dist/server/sse-hook.d.ts.map +1 -0
- package/dist/server/sse-hook.js +2 -0
- package/dist/server/sse-hook.js.map +1 -0
- package/dist/server/sse.d.ts +7 -23
- package/dist/server/sse.d.ts.map +1 -1
- package/dist/server/sse.js +18 -0
- package/dist/server/sse.js.map +1 -1
- package/dist/server/transport-lane.d.ts +28 -0
- package/dist/server/transport-lane.d.ts.map +1 -0
- package/dist/server/transport-lane.js +106 -0
- package/dist/server/transport-lane.js.map +1 -0
- package/dist/server/websocket.d.ts +23 -1
- package/dist/server/websocket.d.ts.map +1 -1
- package/dist/server/websocket.js +10 -1
- package/dist/server/websocket.js.map +1 -1
- package/dist/server/ws-hook.d.ts +8 -11
- package/dist/server/ws-hook.d.ts.map +1 -1
- package/dist/server/ws-hook.js +7 -14
- package/dist/server/ws-hook.js.map +1 -1
- package/dist/server.d.ts +21 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +16 -0
- package/dist/server.js.map +1 -0
- package/dist/transport-codec-rich.d.ts +8 -0
- package/dist/transport-codec-rich.d.ts.map +1 -0
- package/dist/transport-codec-rich.js +14 -0
- package/dist/transport-codec-rich.js.map +1 -0
- package/dist/transport-codec.d.ts +31 -0
- package/dist/transport-codec.d.ts.map +1 -0
- package/dist/transport-codec.js +190 -0
- package/dist/transport-codec.js.map +1 -0
- package/dist/transport-plugin.d.ts +3 -0
- package/dist/transport-plugin.d.ts.map +1 -0
- package/dist/transport-plugin.js +3 -0
- package/dist/transport-plugin.js.map +1 -0
- package/dist/wire.d.ts +58 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +445 -0
- package/dist/wire.js.map +1 -0
- package/dist/ws.d.ts +16 -7
- package/dist/ws.d.ts.map +1 -1
- package/dist/ws.js +25 -10
- package/dist/ws.js.map +1 -1
- package/package.json +122 -6
- package/src/assurance.ts +36 -1
- package/src/capabilities.ts +306 -5
- package/src/durable-adapters.ts +1039 -0
- package/src/durable-execution.ts +1676 -0
- package/src/effect-ledger.ts +9 -0
- package/src/effect-lifecycle.ts +120 -0
- package/src/effect-scope.ts +267 -0
- package/src/idempotency-plugin.ts +17 -0
- package/src/index.ts +6 -133
- package/src/internal/capability-runtime.ts +87 -1
- package/src/internal/effect-execution.ts +70 -0
- package/src/internal/safe-equal.ts +27 -0
- package/src/ledger.ts +45 -11
- package/src/mcp.ts +65 -0
- package/src/node-direct.ts +37 -0
- package/src/reconciliation-worker.ts +372 -0
- package/src/reflection.ts +7 -0
- package/src/server/client-ip.ts +60 -0
- package/src/server/context.ts +50 -2
- package/src/server/contract.ts +26 -7
- package/src/server/http.ts +58 -0
- package/src/server/idempotency-lane.ts +321 -0
- package/src/server/install.ts +28 -0
- package/src/server/ledger-lane.ts +125 -0
- package/src/server/mcp-hook.ts +41 -0
- package/src/server/node-outcome-hook.ts +9 -0
- package/src/server/node-outcome.ts +112 -0
- package/src/server/plugin.ts +92 -0
- package/src/server/query.ts +119 -0
- package/src/server/registry.ts +36 -6
- package/src/server/request-context.ts +271 -0
- package/src/server/respond.ts +94 -0
- package/src/server/runtime-core.ts +74 -0
- package/src/server/server-types.ts +204 -0
- package/src/server/server.ts +448 -1314
- package/src/server/sse-hook.ts +14 -0
- package/src/server/sse.ts +26 -0
- package/src/server/transport-lane.ts +141 -0
- package/src/server/websocket.ts +35 -1
- package/src/server/ws-hook.ts +8 -19
- package/src/server.ts +92 -0
- package/src/transport-codec-rich.ts +20 -0
- package/src/transport-codec.ts +250 -0
- package/src/transport-plugin.ts +2 -0
- package/src/wire.ts +474 -0
- package/src/ws.ts +33 -10
- package/dist/invariants.d.ts +0 -70
- package/dist/invariants.d.ts.map +0 -1
- package/dist/invariants.js +0 -487
- package/dist/invariants.js.map +0 -1
- package/src/invariants.ts +0 -604
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ bun add @nifrajs/core
|
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
|
-
import { server } from "@nifrajs/core"
|
|
12
|
+
import { server } from "@nifrajs/core/server"
|
|
13
13
|
|
|
14
14
|
const app = server()
|
|
15
15
|
.get("/users/:id", (c) => ({ id: c.params.id }))
|
|
@@ -19,6 +19,15 @@ const app = server()
|
|
|
19
19
|
export type App = typeof app // hand this to @nifrajs/client for end-to-end types
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
`@nifrajs/core` and `@nifrajs/core/server` expose the same lean common runtime. Optional systems are
|
|
23
|
+
available only from their explicit subpaths, so an ordinary HTTP server never evaluates them:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { defineContract, implement } from "@nifrajs/core/contract"
|
|
27
|
+
import { startCausality } from "@nifrajs/core/causality"
|
|
28
|
+
import { defineAssurancePolicy } from "@nifrajs/core/assurance"
|
|
29
|
+
```
|
|
30
|
+
|
|
22
31
|
- **Inline or contract-first.** Write routes inline (types inferred from the
|
|
23
32
|
builder), or `defineContract(...)` + `implement(...)` for a decoupled, versionable
|
|
24
33
|
surface — handlers lift over unchanged.
|
|
@@ -39,6 +48,33 @@ export type App = typeof app // hand this to @nifrajs/client for end-to-end type
|
|
|
39
48
|
IP-restriction, and security-header modules publish reflection-safe enforcement evidence.
|
|
40
49
|
An ordered `AssurancePolicy` classifies every route and fails closed on missing or forbidden
|
|
41
50
|
evidence without adding work to the request path.
|
|
51
|
+
- **Owned effect execution.** `executeCapability()` correlates intent and terminal evidence with an
|
|
52
|
+
opaque `effectId`, records outcomes automatically, and forwards request cancellation. Add
|
|
53
|
+
order-scoped `aroundCapability()` policies for async approval/admission; they receive token-only
|
|
54
|
+
metadata, have bounded timeouts, and must call `next()` exactly once before the effect can run.
|
|
55
|
+
- **Durable workflows (opt in).** `@nifrajs/core/durable-execution` provides tenant/principal-bound,
|
|
56
|
+
signed single-use approval resumes; a durable effect journal + reconciliation scanner; and a typed
|
|
57
|
+
saga state machine with reverse compensation, retry/backoff, and ambiguous-crash detection. Production
|
|
58
|
+
constructors reject stores that do not declare `durability: "durable"`. Operational scans use bounded
|
|
59
|
+
cursor pages through `reconcileEffectsPage()` / `reconcileSagasPage()`. Provider-confirmed manual
|
|
60
|
+
review uses effect-ID-bound `resolveAmbiguity()`, followed by `resume()` or `compensate()`.
|
|
61
|
+
- **Production durable adapters.** `@nifrajs/core/durable-adapters` supplies
|
|
62
|
+
`PostgresDurableExecutionAdapter`, `SQLiteDurableExecutionAdapter`, and
|
|
63
|
+
`DurableObjectExecutionAdapter`. Each exposes compatible `effects`, `approvals`, `sagas`, and
|
|
64
|
+
`leases` stores. Run `runDurableExecutionAdapterConformance()` against the deployment backend.
|
|
65
|
+
- **Bounded reconciliation workers.** `@nifrajs/core/reconciliation-worker` runs effect or saga
|
|
66
|
+
scans under an atomic lease with durable cursor checkpoints, a finite page budget, bounded handler
|
|
67
|
+
concurrency, filters, cancellation, and token-only metrics. A worker invocation always terminates.
|
|
68
|
+
- **Rich wire values (opt in).** `@nifrajs/core/wire` round-trips dates, bigints, maps, sets, binary,
|
|
69
|
+
shared references, and cycles through JSON transports. Decoding validates every reachable shape,
|
|
70
|
+
preserves owned `__proto__` keys without prototype mutation, and enforces configurable node, depth,
|
|
71
|
+
collection-entry, and decoded-byte limits.
|
|
72
|
+
- **Versioned transport codecs (opt in).** Add `.use(transportCodecs(registry))` from
|
|
73
|
+
`@nifrajs/core/transport-plugin` and configure the typed
|
|
74
|
+
client's `transport` option with the same registry. `@nifrajs/core/transport-codec` negotiates
|
|
75
|
+
bounded HTTP representations and supplies the same frame/loader adapters for WebSockets and
|
|
76
|
+
deferred data. Import `richWireCodec()` from `@nifrajs/core/transport-codec-rich`; the separate
|
|
77
|
+
subpath keeps rich-wire code out of plain JSON bundles.
|
|
42
78
|
|
|
43
79
|
```ts
|
|
44
80
|
import { defineAssurancePolicy, evaluateRouteAssurance, NIFRA_ASSURANCE } from "@nifrajs/core/assurance"
|
package/dist/assurance.d.ts
CHANGED
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
import { type CapabilityAssuranceReport, type CapabilityPolicy } from "./capabilities.js";
|
|
6
6
|
import type { AssuranceDeclaration, AssuranceEvidence, AssuranceScope } from "./internal/route-assurance.js";
|
|
7
7
|
import { withRouteAssurance } from "./internal/route-assurance.js";
|
|
8
|
-
import type { InvariantExecutor } from "./invariants.js";
|
|
9
8
|
import type { NifraManifestSigner } from "./manifest.js";
|
|
10
9
|
import { type ReflectedRoute } from "./reflection.js";
|
|
11
10
|
import type { Method } from "./router/router.js";
|
|
12
11
|
export type { AssuranceDeclaration, AssuranceEvidence, AssuranceScope };
|
|
13
12
|
export { withRouteAssurance };
|
|
13
|
+
/** Isolated request executor used by adversarial contract verification. */
|
|
14
|
+
export type InvariantExecutor = (request: Request) => Response | Promise<Response>;
|
|
14
15
|
/** Canonical evidence ids emitted by Nifra's official middleware modules. */
|
|
15
16
|
export declare const NIFRA_ASSURANCE: Readonly<{
|
|
16
17
|
readonly AUTHENTICATED: "nifra.authenticated";
|
|
@@ -46,8 +47,16 @@ export interface AssurancePolicy {
|
|
|
46
47
|
readonly unmatched?: "error" | "ignore";
|
|
47
48
|
/** Default false: reject an empty reflected source so a wrong import cannot pass CI silently. */
|
|
48
49
|
readonly allowEmpty?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Default false. When true, a route matched by a **pure-classification** rule (no `require`, no `forbid`)
|
|
52
|
+
* that carries NO enforcement evidence is reported (`classified-no-evidence`). This surfaces the gap the
|
|
53
|
+
* feedback flagged: a classification-only policy silently degrades "proof" to a "label". Opt-in, because
|
|
54
|
+
* a genuinely public route legitimately carries no evidence; enable it once your guards emit evidence
|
|
55
|
+
* (inline `schema.assurance` or a `withRouteAssurance` middleware) to keep classification honest.
|
|
56
|
+
*/
|
|
57
|
+
readonly flagClassifiedWithoutEvidence?: boolean;
|
|
49
58
|
}
|
|
50
|
-
export type AssuranceFindingCode = "no-routes" | "unclassified-route" | "missing-evidence" | "forbidden-evidence";
|
|
59
|
+
export type AssuranceFindingCode = "no-routes" | "unclassified-route" | "missing-evidence" | "forbidden-evidence" | "classified-no-evidence";
|
|
51
60
|
export interface AssuranceFinding {
|
|
52
61
|
readonly code: AssuranceFindingCode;
|
|
53
62
|
readonly method: string;
|
package/dist/assurance.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assurance.d.ts","sourceRoot":"","sources":["../src/assurance.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,EAEtB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,KAAK,EACV,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACf,MAAM,+BAA+B,CAAA;AACtC,OAAO,EAKL,kBAAkB,EACnB,MAAM,+BAA+B,CAAA;AACtC,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"assurance.d.ts","sourceRoot":"","sources":["../src/assurance.ts"],"names":[],"mappings":"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"}
|
package/dist/assurance.js
CHANGED
|
@@ -33,6 +33,10 @@ export function defineAssurancePolicy(policy) {
|
|
|
33
33
|
if (policy.allowEmpty !== undefined && typeof policy.allowEmpty !== "boolean") {
|
|
34
34
|
throw new Error(`route assurance: allowEmpty must be boolean`);
|
|
35
35
|
}
|
|
36
|
+
if (policy.flagClassifiedWithoutEvidence !== undefined &&
|
|
37
|
+
typeof policy.flagClassifiedWithoutEvidence !== "boolean") {
|
|
38
|
+
throw new Error(`route assurance: flagClassifiedWithoutEvidence must be boolean`);
|
|
39
|
+
}
|
|
36
40
|
const names = new Set();
|
|
37
41
|
const rules = policy.rules.map((rule) => {
|
|
38
42
|
if (typeof rule.name !== "string" || !nonEmpty(rule.name)) {
|
|
@@ -76,6 +80,7 @@ export function defineAssurancePolicy(policy) {
|
|
|
76
80
|
rules: Object.freeze(rules),
|
|
77
81
|
unmatched: policy.unmatched ?? "error",
|
|
78
82
|
allowEmpty: policy.allowEmpty ?? false,
|
|
83
|
+
flagClassifiedWithoutEvidence: policy.flagClassifiedWithoutEvidence ?? false,
|
|
79
84
|
});
|
|
80
85
|
}
|
|
81
86
|
/** Identity helper for a `nifra.assurance.ts` default export. */
|
|
@@ -171,6 +176,21 @@ export function evaluateRouteAssurance(source, policyInput) {
|
|
|
171
176
|
message: `${route.method} ${route.path} (${rule.name}) carries forbidden ${id}`,
|
|
172
177
|
});
|
|
173
178
|
}
|
|
179
|
+
// Opt-in visibility: a pure-classification rule (no require + no forbid) matching a route with zero
|
|
180
|
+
// evidence is a "label without proof". Surface it so the gap isn't silently green. A rule that expects
|
|
181
|
+
// or forbids evidence already speaks for itself via the loops above, so it's excluded here.
|
|
182
|
+
if (policy.flagClassifiedWithoutEvidence === true &&
|
|
183
|
+
evidence.length === 0 &&
|
|
184
|
+
(rule.require ?? []).length === 0 &&
|
|
185
|
+
(rule.forbid ?? []).length === 0) {
|
|
186
|
+
findings.push({
|
|
187
|
+
code: "classified-no-evidence",
|
|
188
|
+
method: route.method,
|
|
189
|
+
path: route.path,
|
|
190
|
+
rule: rule.name,
|
|
191
|
+
message: `${route.method} ${route.path} (${rule.name}) is classified but carries no enforcement evidence`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
174
194
|
routes.push({
|
|
175
195
|
method: route.method,
|
|
176
196
|
path: route.path,
|
package/dist/assurance.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assurance.js","sourceRoot":"","sources":["../src/assurance.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAGL,sBAAsB,GACvB,MAAM,mBAAmB,CAAA;AAM1B,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,eAAe,EACf,WAAW,EACX,kBAAkB,GACnB,MAAM,+BAA+B,CAAA;
|
|
1
|
+
{"version":3,"file":"assurance.js","sourceRoot":"","sources":["../src/assurance.ts"],"names":[],"mappings":"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"}
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* are a fail-closed defence at owned effect seams. Static provenance remains the security anchor.
|
|
4
4
|
*/
|
|
5
5
|
import type { DataClassification } from "./classification.js";
|
|
6
|
-
import { type
|
|
6
|
+
import { type EffectLifecycleObserver } from "./effect-lifecycle.js";
|
|
7
|
+
import { type AroundCapabilityOptions, type CapabilityApprovalGate, type CapabilityApprovalInput, type CapabilityExecutionIdentity, type CapabilityExecutionJournal, type CapabilityInterceptor, type CapabilityInterceptorEvent, type CapabilityInterceptorNext, type CapabilityUseEvent, validCapabilityId } from "./internal/capability-runtime.js";
|
|
7
8
|
import { type EffectCost, type EffectPhase } from "./ledger.js";
|
|
8
|
-
export type { CapabilityUseEvent };
|
|
9
|
+
export type { AroundCapabilityOptions, CapabilityApprovalGate, CapabilityApprovalInput, CapabilityExecutionIdentity, CapabilityExecutionJournal, CapabilityInterceptor, CapabilityInterceptorEvent, CapabilityInterceptorNext, CapabilityUseEvent, EffectLifecycleObserver, };
|
|
9
10
|
export type CapabilityZone = "domain" | "operational";
|
|
10
11
|
export type CapabilityAccess = "read" | "write";
|
|
11
12
|
export type CapabilityIdempotency = "none" | "request" | "durable";
|
|
@@ -128,6 +129,51 @@ export interface CapabilityOutcomeOptions extends UseCapabilityOptions {
|
|
|
128
129
|
readonly code: string;
|
|
129
130
|
};
|
|
130
131
|
}
|
|
132
|
+
/** Durable controls consumed by `executeCapability`; none of these fields enter the effect ledger. */
|
|
133
|
+
export interface CapabilityExecutionOptions extends UseCapabilityOptions {
|
|
134
|
+
readonly approval?: CapabilityApprovalInput & {
|
|
135
|
+
readonly gate: CapabilityApprovalGate;
|
|
136
|
+
};
|
|
137
|
+
readonly journal?: CapabilityExecutionJournal;
|
|
138
|
+
}
|
|
139
|
+
/** Context passed to the owned effect callback. Use the signal for cancellation-aware I/O. */
|
|
140
|
+
export interface CapabilityExecutionContext {
|
|
141
|
+
readonly effectId: string;
|
|
142
|
+
readonly signal: AbortSignal;
|
|
143
|
+
}
|
|
144
|
+
export type CapabilityExecutor<T> = (execution: CapabilityExecutionContext) => T | PromiseLike<T>;
|
|
145
|
+
/** A capability admission policy returned without calling `next()`. */
|
|
146
|
+
export declare class CapabilityDeniedError extends Error {
|
|
147
|
+
readonly capability: string;
|
|
148
|
+
readonly effectId: string;
|
|
149
|
+
constructor(capability: string, effectId: string);
|
|
150
|
+
}
|
|
151
|
+
/** A capability admission policy exceeded its configured bound. */
|
|
152
|
+
export declare class CapabilityInterceptorTimeoutError extends Error {
|
|
153
|
+
readonly capability: string;
|
|
154
|
+
readonly effectId: string;
|
|
155
|
+
readonly timeoutMs: number;
|
|
156
|
+
constructor(capability: string, effectId: string, timeoutMs: number);
|
|
157
|
+
}
|
|
158
|
+
/** The request was cancelled while capability admission was pending. */
|
|
159
|
+
export declare class CapabilityAdmissionAbortedError extends Error {
|
|
160
|
+
readonly capability: string;
|
|
161
|
+
readonly effectId: string;
|
|
162
|
+
constructor(capability: string, effectId: string);
|
|
163
|
+
}
|
|
164
|
+
/** An interceptor called its one-shot `next()` continuation more than once. */
|
|
165
|
+
export declare class CapabilityInterceptorProtocolError extends Error {
|
|
166
|
+
readonly capability: string;
|
|
167
|
+
readonly effectId: string;
|
|
168
|
+
constructor(capability: string, effectId: string);
|
|
169
|
+
}
|
|
170
|
+
/** The effect may have committed, but its durable terminal transition could not be recorded. */
|
|
171
|
+
export declare class CapabilityJournalTransitionError extends Error {
|
|
172
|
+
readonly capability: string;
|
|
173
|
+
readonly effectId: string;
|
|
174
|
+
readonly transition: "committed";
|
|
175
|
+
constructor(capability: string, effectId: string, transition: "committed");
|
|
176
|
+
}
|
|
131
177
|
/**
|
|
132
178
|
* Runtime effect beacon for owned adapters. It fails closed when the route omitted the capability or
|
|
133
179
|
* when no route guard is present. Static provenance is still required: code can bypass a beacon.
|
|
@@ -136,6 +182,12 @@ export interface CapabilityOutcomeOptions extends UseCapabilityOptions {
|
|
|
136
182
|
export declare function useCapability(context: object, capability: string, options?: UseCapabilityOptions): void;
|
|
137
183
|
/** Record the terminal outcome of an already-admitted capability without debiting admission twice. */
|
|
138
184
|
export declare function recordCapabilityOutcome(context: object, capability: string, options: CapabilityOutcomeOptions): void;
|
|
185
|
+
/**
|
|
186
|
+
* Execute one owned effect behind a fail-closed capability boundary. The boundary assigns a stable
|
|
187
|
+
* effect id, records intent before execution, and records exactly one terminal outcome automatically.
|
|
188
|
+
* The callback result and errors never enter the token-only ledger.
|
|
189
|
+
*/
|
|
190
|
+
export declare function executeCapability<T>(context: object, capability: string, options: CapabilityExecutionOptions, executor: CapabilityExecutor<T>): Promise<T>;
|
|
139
191
|
/**
|
|
140
192
|
* Read the route's token-only declaration for admission plugins. This intentionally exposes neither
|
|
141
193
|
* the request nor runtime evidence; it is the stable public seam for private entitlement policy.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAC7D,OAAO,
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"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"}
|
package/dist/capabilities.js
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* Route effect/capability assurance. Declarations are reflected at build/CI time; runtime beacons
|
|
3
3
|
* are a fail-closed defence at owned effect seams. Static provenance remains the security anchor.
|
|
4
4
|
*/
|
|
5
|
+
import { effectTraceParentOf } from "./effect-lifecycle.js";
|
|
6
|
+
import { createEffectScope } from "./effect-scope.js";
|
|
5
7
|
import { CAPABILITY_GUARD, validCapabilityId, } from "./internal/capability-runtime.js";
|
|
8
|
+
import { effectScopeForContext } from "./internal/effect-execution.js";
|
|
6
9
|
import { NIFRA_ASSURANCE_IDS } from "./internal/route-assurance.js";
|
|
7
|
-
import { effectLedgerOf } from "./ledger.js";
|
|
10
|
+
import { effectLedgerOf, normalizeEffectMetadata, } from "./ledger.js";
|
|
8
11
|
import { reflectRoutes } from "./reflection.js";
|
|
9
12
|
const SAFE_METHODS = new Set(["GET", "HEAD"]);
|
|
10
13
|
export { validCapabilityId };
|
|
@@ -224,6 +227,132 @@ export function evaluateCapabilityAssurance(source, policyInput, evidenceSet) {
|
|
|
224
227
|
findings: Object.freeze(findings),
|
|
225
228
|
});
|
|
226
229
|
}
|
|
230
|
+
/** A capability admission policy returned without calling `next()`. */
|
|
231
|
+
export class CapabilityDeniedError extends Error {
|
|
232
|
+
capability;
|
|
233
|
+
effectId;
|
|
234
|
+
constructor(capability, effectId) {
|
|
235
|
+
super(`capability assurance: ${capability} was denied by an async interceptor`);
|
|
236
|
+
this.capability = capability;
|
|
237
|
+
this.effectId = effectId;
|
|
238
|
+
this.name = "CapabilityDeniedError";
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/** A capability admission policy exceeded its configured bound. */
|
|
242
|
+
export class CapabilityInterceptorTimeoutError extends Error {
|
|
243
|
+
capability;
|
|
244
|
+
effectId;
|
|
245
|
+
timeoutMs;
|
|
246
|
+
constructor(capability, effectId, timeoutMs) {
|
|
247
|
+
super(`capability assurance: ${capability} admission timed out after ${timeoutMs}ms`);
|
|
248
|
+
this.capability = capability;
|
|
249
|
+
this.effectId = effectId;
|
|
250
|
+
this.timeoutMs = timeoutMs;
|
|
251
|
+
this.name = "CapabilityInterceptorTimeoutError";
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/** The request was cancelled while capability admission was pending. */
|
|
255
|
+
export class CapabilityAdmissionAbortedError extends Error {
|
|
256
|
+
capability;
|
|
257
|
+
effectId;
|
|
258
|
+
constructor(capability, effectId) {
|
|
259
|
+
super(`capability assurance: ${capability} admission was aborted`);
|
|
260
|
+
this.capability = capability;
|
|
261
|
+
this.effectId = effectId;
|
|
262
|
+
this.name = "CapabilityAdmissionAbortedError";
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/** An interceptor called its one-shot `next()` continuation more than once. */
|
|
266
|
+
export class CapabilityInterceptorProtocolError extends Error {
|
|
267
|
+
capability;
|
|
268
|
+
effectId;
|
|
269
|
+
constructor(capability, effectId) {
|
|
270
|
+
super(`capability assurance: ${capability} interceptor called next() more than once`);
|
|
271
|
+
this.capability = capability;
|
|
272
|
+
this.effectId = effectId;
|
|
273
|
+
this.name = "CapabilityInterceptorProtocolError";
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/** The effect may have committed, but its durable terminal transition could not be recorded. */
|
|
277
|
+
export class CapabilityJournalTransitionError extends Error {
|
|
278
|
+
capability;
|
|
279
|
+
effectId;
|
|
280
|
+
transition;
|
|
281
|
+
constructor(capability, effectId, transition) {
|
|
282
|
+
super(`capability assurance: ${capability} durable ${transition} transition failed`);
|
|
283
|
+
this.capability = capability;
|
|
284
|
+
this.effectId = effectId;
|
|
285
|
+
this.transition = transition;
|
|
286
|
+
this.name = "CapabilityJournalTransitionError";
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
let unboundedCapabilitySignal;
|
|
290
|
+
function signalFor(context) {
|
|
291
|
+
const signal = context.signal;
|
|
292
|
+
if (signal !== undefined)
|
|
293
|
+
return signal;
|
|
294
|
+
unboundedCapabilitySignal ??= new AbortController().signal;
|
|
295
|
+
return unboundedCapabilitySignal;
|
|
296
|
+
}
|
|
297
|
+
function admissionErrorCode(error) {
|
|
298
|
+
if (error instanceof CapabilityDeniedError)
|
|
299
|
+
return "admission_denied";
|
|
300
|
+
if (error instanceof CapabilityInterceptorTimeoutError)
|
|
301
|
+
return "admission_timeout";
|
|
302
|
+
if (error instanceof CapabilityAdmissionAbortedError)
|
|
303
|
+
return "admission_aborted";
|
|
304
|
+
if (error instanceof CapabilityInterceptorProtocolError)
|
|
305
|
+
return "admission_protocol";
|
|
306
|
+
return "admission_failed";
|
|
307
|
+
}
|
|
308
|
+
async function withInterceptorBound(registration, parentSignal, capability, effectId, run) {
|
|
309
|
+
if (parentSignal.aborted)
|
|
310
|
+
throw new CapabilityAdmissionAbortedError(capability, effectId);
|
|
311
|
+
const controller = new AbortController();
|
|
312
|
+
let timeout;
|
|
313
|
+
let rejectGate;
|
|
314
|
+
const gate = new Promise((_resolve, reject) => {
|
|
315
|
+
rejectGate = reject;
|
|
316
|
+
});
|
|
317
|
+
const abort = () => {
|
|
318
|
+
const error = new CapabilityAdmissionAbortedError(capability, effectId);
|
|
319
|
+
controller.abort(error);
|
|
320
|
+
rejectGate?.(error);
|
|
321
|
+
};
|
|
322
|
+
parentSignal.addEventListener("abort", abort, { once: true });
|
|
323
|
+
timeout = setTimeout(() => {
|
|
324
|
+
const error = new CapabilityInterceptorTimeoutError(capability, effectId, registration.timeoutMs);
|
|
325
|
+
controller.abort(error);
|
|
326
|
+
rejectGate?.(error);
|
|
327
|
+
}, registration.timeoutMs);
|
|
328
|
+
try {
|
|
329
|
+
await Promise.race([run(controller.signal), gate]);
|
|
330
|
+
}
|
|
331
|
+
finally {
|
|
332
|
+
clearTimeout(timeout);
|
|
333
|
+
parentSignal.removeEventListener("abort", abort);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
async function runCapabilityInterceptors(registrations, baseEvent, parentSignal, index = 0) {
|
|
337
|
+
const registration = registrations[index];
|
|
338
|
+
if (registration === undefined)
|
|
339
|
+
return;
|
|
340
|
+
let nextPromise;
|
|
341
|
+
await withInterceptorBound(registration, parentSignal, baseEvent.capability, baseEvent.effectId, async (signal) => {
|
|
342
|
+
const event = Object.freeze({ ...baseEvent, signal });
|
|
343
|
+
await registration.interceptor(event, () => {
|
|
344
|
+
if (nextPromise !== undefined) {
|
|
345
|
+
throw new CapabilityInterceptorProtocolError(baseEvent.capability, baseEvent.effectId);
|
|
346
|
+
}
|
|
347
|
+
nextPromise = runCapabilityInterceptors(registrations, baseEvent, signal, index + 1);
|
|
348
|
+
return nextPromise;
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
if (nextPromise === undefined) {
|
|
352
|
+
throw new CapabilityDeniedError(baseEvent.capability, baseEvent.effectId);
|
|
353
|
+
}
|
|
354
|
+
await nextPromise;
|
|
355
|
+
}
|
|
227
356
|
function guardFor(context, capability) {
|
|
228
357
|
if (!validCapabilityId(capability)) {
|
|
229
358
|
throw new Error(`capability assurance: invalid runtime capability ${JSON.stringify(capability)}`);
|
|
@@ -248,15 +377,121 @@ export function useCapability(context, capability, options) {
|
|
|
248
377
|
// Validate/append the intent before private admission hooks debit budgets. Overflow or malformed
|
|
249
378
|
// evidence therefore cannot consume quota and then fail the request.
|
|
250
379
|
ledger?.append({ capability, ...options, phase: "intent" });
|
|
380
|
+
guard.trackEffect?.(context, false);
|
|
251
381
|
guard.onUse?.({ capability, method: guard.method, path: guard.path });
|
|
252
382
|
}
|
|
253
383
|
/** Record the terminal outcome of an already-admitted capability without debiting admission twice. */
|
|
254
384
|
export function recordCapabilityOutcome(context, capability, options) {
|
|
255
|
-
guardFor(context, capability);
|
|
385
|
+
const guard = guardFor(context, capability);
|
|
256
386
|
const ledger = effectLedgerOf(context);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
387
|
+
ledger?.append({ capability, ...options });
|
|
388
|
+
if (options.phase === "committed" || options.phase === "compensated") {
|
|
389
|
+
guard.trackEffect?.(context, true);
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
guard.trackEffect?.(context, false);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Execute one owned effect behind a fail-closed capability boundary. The boundary assigns a stable
|
|
397
|
+
* effect id, records intent before execution, and records exactly one terminal outcome automatically.
|
|
398
|
+
* The callback result and errors never enter the token-only ledger.
|
|
399
|
+
*/
|
|
400
|
+
export async function executeCapability(context, capability, options, executor) {
|
|
401
|
+
if (typeof executor !== "function") {
|
|
402
|
+
throw new TypeError("capability assurance: executeCapability executor must be a function");
|
|
403
|
+
}
|
|
404
|
+
const guard = guardFor(context, capability);
|
|
405
|
+
const ledger = effectLedgerOf(context);
|
|
406
|
+
const effectId = crypto.randomUUID();
|
|
407
|
+
const metadata = normalizeEffectMetadata(options);
|
|
408
|
+
const trace = effectTraceParentOf(context);
|
|
409
|
+
const identity = options.approval;
|
|
410
|
+
const requestEvidence = effectScopeForContext(context);
|
|
411
|
+
const scope = createEffectScope({}, requestEvidence);
|
|
412
|
+
const result = await scope.run({
|
|
413
|
+
effectId,
|
|
414
|
+
capability,
|
|
415
|
+
...metadata,
|
|
416
|
+
signal: signalFor(context),
|
|
417
|
+
observers: guard.observers,
|
|
418
|
+
...(trace === undefined ? {} : { trace }),
|
|
419
|
+
errorCode: (error, began) => (began ? "execution_failed" : admissionErrorCode(error)),
|
|
420
|
+
async admit(execution) {
|
|
421
|
+
if (identity !== undefined) {
|
|
422
|
+
await identity.gate.authorize({
|
|
423
|
+
effectId: execution.effectId,
|
|
424
|
+
capability,
|
|
425
|
+
...(metadata.target === undefined ? {} : { target: metadata.target }),
|
|
426
|
+
...(metadata.digest === undefined ? {} : { digest: metadata.digest }),
|
|
427
|
+
identity: { tenantId: identity.tenantId, principalId: identity.principalId },
|
|
428
|
+
...(identity.resumeToken === undefined ? {} : { resumeToken: identity.resumeToken }),
|
|
429
|
+
signal: execution.signal,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
guard.onUse?.({ capability, method: guard.method, path: guard.path });
|
|
433
|
+
if (guard.interceptors.length > 0) {
|
|
434
|
+
await runCapabilityInterceptors(guard.interceptors, Object.freeze({
|
|
435
|
+
capability,
|
|
436
|
+
method: guard.method,
|
|
437
|
+
path: guard.path,
|
|
438
|
+
effectId: execution.effectId,
|
|
439
|
+
...metadata,
|
|
440
|
+
}), execution.signal);
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
transitions: {
|
|
444
|
+
async intent(execution) {
|
|
445
|
+
ledger?.append({ capability, effectId: execution.effectId, ...metadata, phase: "intent" });
|
|
446
|
+
await options.journal?.intent({
|
|
447
|
+
effectId: execution.effectId,
|
|
448
|
+
capability,
|
|
449
|
+
...(metadata.target === undefined ? {} : { target: metadata.target }),
|
|
450
|
+
...(metadata.digest === undefined ? {} : { digest: metadata.digest }),
|
|
451
|
+
...(identity === undefined
|
|
452
|
+
? {}
|
|
453
|
+
: { identity: { tenantId: identity.tenantId, principalId: identity.principalId } }),
|
|
454
|
+
});
|
|
455
|
+
},
|
|
456
|
+
async executing(execution) {
|
|
457
|
+
await options.journal?.executing(execution.effectId);
|
|
458
|
+
},
|
|
459
|
+
async committed(execution) {
|
|
460
|
+
try {
|
|
461
|
+
await options.journal?.committed(execution.effectId);
|
|
462
|
+
}
|
|
463
|
+
catch {
|
|
464
|
+
throw new CapabilityJournalTransitionError(capability, execution.effectId, "committed");
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
async failed(execution, failure) {
|
|
468
|
+
try {
|
|
469
|
+
await options.journal?.failed(execution.effectId, {
|
|
470
|
+
began: failure.began,
|
|
471
|
+
errorCode: failure.errorCode,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
finally {
|
|
475
|
+
try {
|
|
476
|
+
ledger?.append({
|
|
477
|
+
capability,
|
|
478
|
+
effectId: execution.effectId,
|
|
479
|
+
...metadata,
|
|
480
|
+
phase: "failed",
|
|
481
|
+
error: { code: failure.errorCode },
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
// Preserve the original effect failure when terminal evidence overflows.
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
}, (execution) => executor(Object.freeze({ effectId: execution.effectId, signal: execution.signal })));
|
|
491
|
+
// Durable commit and success telemetry are already final. A ledger overflow here must not rewrite
|
|
492
|
+
// the effect to failed/unknown or erase the request scope's proven committed evidence.
|
|
493
|
+
ledger?.append({ capability, effectId, ...metadata, phase: "committed" });
|
|
494
|
+
return result;
|
|
260
495
|
}
|
|
261
496
|
/**
|
|
262
497
|
* Read the route's token-only declaration for admission plugins. This intentionally exposes neither
|