@kya-os/checkpoint-nextjs 1.2.0 → 1.7.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/CHANGELOG.md +159 -0
- package/dist/composed-policy.d.mts +108 -0
- package/dist/composed-policy.d.ts +108 -0
- package/dist/composed-policy.js +91 -0
- package/dist/composed-policy.mjs +85 -0
- package/dist/config-_nfPN3E3.d.mts +205 -0
- package/dist/config-kxFihzR_.d.ts +205 -0
- package/dist/create-middleware.js +0 -2
- package/dist/create-middleware.mjs +0 -2
- package/dist/edge-runtime-loader.js +3 -1
- package/dist/edge-runtime-loader.mjs +3 -1
- package/dist/edge-wasm-middleware.d.mts +6 -6
- package/dist/edge-wasm-middleware.d.ts +6 -6
- package/dist/index.d.mts +6 -14
- package/dist/index.d.ts +6 -14
- package/dist/index.js +160 -8
- package/dist/index.mjs +161 -9
- package/dist/middleware-edge.d.mts +7 -3
- package/dist/middleware-edge.d.ts +7 -3
- package/dist/middleware-edge.js +157 -3
- package/dist/middleware-edge.mjs +154 -3
- package/dist/middleware-node.d.mts +39 -116
- package/dist/middleware-node.d.ts +39 -116
- package/dist/middleware-node.js +164 -3
- package/dist/middleware-node.mjs +161 -4
- package/dist/middleware.d.mts +10 -1
- package/dist/middleware.d.ts +10 -1
- package/dist/middleware.js +6 -0
- package/dist/middleware.mjs +6 -1
- package/dist/nodejs-wasm-loader.d.mts +3 -4
- package/dist/nodejs-wasm-loader.d.ts +3 -4
- package/dist/nodejs-wasm-loader.js +1 -1
- package/dist/nodejs-wasm-loader.mjs +1 -1
- package/dist/signature-verifier.js +2 -2
- package/dist/signature-verifier.mjs +2 -2
- package/dist/wasm-setup.js +1 -1
- package/dist/wasm-setup.mjs +1 -1
- package/package.json +4 -9
- package/dist/.tsbuildinfo +0 -1
- package/dist/wasm-middleware.d.mts +0 -98
- package/dist/wasm-middleware.d.ts +0 -98
- package/dist/wasm-middleware.js +0 -125
- package/dist/wasm-middleware.mjs +0 -121
- package/templates/middleware-wasm-100.ts +0 -161
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,164 @@
|
|
|
1
1
|
# @kya-os/checkpoint-nextjs
|
|
2
2
|
|
|
3
|
+
## 1.6.0 — Self-report SDK identity + runtime (version-gating)
|
|
4
|
+
|
|
5
|
+
**Minor release — additive + a fix.** `withCheckpoint`'s detection reporter now
|
|
6
|
+
self-reports `sdk: { name: '@kya-os/checkpoint-nextjs', version, runtime }` —
|
|
7
|
+
with `runtime: 'node' | 'edge'` distinguishing the two entries — so the dashboard
|
|
8
|
+
can version-gate "composed policy enforces here" on the actual installed SDK,
|
|
9
|
+
and correctly show the **edge** runtime as opt-in (composed enforcement there
|
|
10
|
+
needs `cedarWasmModule`, which detections can't observe) rather than falsely
|
|
11
|
+
"Enforcing" (the @Policy version-gating work, #3076).
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **`VERSION` was stale (`0.1.0`)** — long drifted from `package.json`. It is now
|
|
16
|
+
single-sourced (re-exported from `middleware-node`) and pinned to
|
|
17
|
+
`package.json` by a unit test, so it can't drift again. This matters because
|
|
18
|
+
`VERSION` is now the SDK version the reporter self-reports for gating; the old
|
|
19
|
+
`0.1.0` would have failed the gate for every install.
|
|
20
|
+
|
|
21
|
+
## 1.5.0 — Composed-policy (kya-os-engine) enforcement
|
|
22
|
+
|
|
23
|
+
**Minor release — additive.** Makes a customer's `/policy-compose` output enforce
|
|
24
|
+
IN-PROCESS via the shared composed-policy core
|
|
25
|
+
(`@kya-os/checkpoint-wasm-runtime/composed-policy`), byte-for-byte the same as
|
|
26
|
+
the DNS Gateway and `@kya-os/checkpoint-express@1.5.0` (engine = the detection +
|
|
27
|
+
enforcement SSOT). Without the new `projectId` config the middleware behaves
|
|
28
|
+
exactly as 1.4.0 (detection + structured policy only).
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **`projectId`** on `CheckpointConfig` — opt into composed-policy enforcement.
|
|
33
|
+
The project's policy is fetched from
|
|
34
|
+
`<dashboardUrl ?? baseUrl ?? default>/api/internal/policies/${projectId}`; when
|
|
35
|
+
it carries a deployed Cedar bundle with `engineEnforcementEnabled` on, the
|
|
36
|
+
engine decision is enforced in-process.
|
|
37
|
+
- **Shadow-first**: deployed-but-not-enforcing computes + logs divergence
|
|
38
|
+
(gated on `debug`) without acting.
|
|
39
|
+
- **Fail-open**: any compile/evaluation/fetch fault keeps the structured
|
|
40
|
+
decision — a policy fault never 5xxs.
|
|
41
|
+
- **Empty-Cedar guard**: blank source never reaches the evaluator.
|
|
42
|
+
- **`composedPolicyEnforcer`** — advanced/testing seam to inject a pre-built
|
|
43
|
+
composed-policy context.
|
|
44
|
+
- **`cedarWasmModule`** (Edge only) — the statically-imported cedar-web
|
|
45
|
+
`WebAssembly.Module` that enables composed enforcement under the Edge runtime.
|
|
46
|
+
|
|
47
|
+
### Node vs Edge
|
|
48
|
+
|
|
49
|
+
- **Node runtime (`./node`)** — composed enforcement is **default-on** (sync
|
|
50
|
+
`createPolicyEvaluator`; cedar loads lazily, never bundled to edge).
|
|
51
|
+
- **Edge runtime (`./edge`)** — **consumer-opt-in**: the seam stays inert unless
|
|
52
|
+
the host wires `cedarWasmModule`:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import cedarWasmModule from '@kya-os/checkpoint-wasm-runtime/wasm/kya-os-engine-cedar-web/kya_os_engine_bg.wasm?module';
|
|
56
|
+
export default withCheckpoint({ projectId, cedarWasmModule });
|
|
57
|
+
// next.config: experiments.asyncWebAssembly + a .wasm asset rule
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The ~2 MB cedar binary is deliberately NOT forced into every consumer's Edge
|
|
61
|
+
Middleware bundle (Vercel's plan-dependent size cap). Edge composed
|
|
62
|
+
enforcement is unproven on Vercel/webpack edge in-repo; flipping it to
|
|
63
|
+
default-on is gated on a real edge deploy measurement (follow-up). Until then,
|
|
64
|
+
edge without `cedarWasmModule` behaves exactly as 1.4.0.
|
|
65
|
+
|
|
66
|
+
### Cross-runtime parity
|
|
67
|
+
|
|
68
|
+
Built on the shared core, so the `VerifyResult → AuthorizeInput` projection
|
|
69
|
+
(category map, verified-delegation allowlist, the `mcp_i_handshake`
|
|
70
|
+
optimistic-detail override), the empty-Cedar guard, the compile cache, and the
|
|
71
|
+
act-gate are identical across the DNS Gateway, Express, and Next.js. The composed
|
|
72
|
+
seam runs BEFORE the detection reporter so the dashboard records the enforced
|
|
73
|
+
decision (matching Express). Path comes from `req.nextUrl.pathname` (query-free,
|
|
74
|
+
what verification saw); fetch base honors `dashboardUrl ?? baseUrl`.
|
|
75
|
+
|
|
76
|
+
## 1.4.0 — 2026-05-26 — Detection reporter on `withCheckpoint` (closes latent bug)
|
|
77
|
+
|
|
78
|
+
**Minor release.** Closes the same architectural gap as
|
|
79
|
+
`@kya-os/checkpoint-express@1.4.0` — `withCheckpoint` (both Node and
|
|
80
|
+
Edge variants) verified locally via WASM but had no path to ship
|
|
81
|
+
detections back to the dashboard. The setup flow currently generates
|
|
82
|
+
`withAgentShield` snippets (legacy `api-middleware.ts`, which has its
|
|
83
|
+
own reporter), so this gap was latent in nextjs. Closing it now
|
|
84
|
+
prevents the same silent-onboarding failure when customers migrate to
|
|
85
|
+
the documented canonical API.
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
|
|
89
|
+
- **`apiKey`** on `CheckpointConfig` — wires the shared reporter from
|
|
90
|
+
`@kya-os/checkpoint-wasm-runtime/reporter`. Mirrors the express
|
|
91
|
+
config exactly. Resolve from `process.env.CHECKPOINT_API_KEY`.
|
|
92
|
+
- **`baseUrl`** — override the dashboard host. Default
|
|
93
|
+
`https://kya.vouched.id`.
|
|
94
|
+
- **`debug`** — surface reporter failures via `console.warn`. Default
|
|
95
|
+
`false`.
|
|
96
|
+
|
|
97
|
+
Both `middleware-node.ts` and `middleware-edge.ts` wire the reporter
|
|
98
|
+
from the same `_buildReporter` + `_extractReporterContext` seams, so
|
|
99
|
+
Node and Edge runtimes ship byte-equivalent payloads to
|
|
100
|
+
`/api/v1/log-detection`.
|
|
101
|
+
|
|
102
|
+
## 1.3.0 — 2026-05-18
|
|
103
|
+
|
|
104
|
+
**Minor release with BREAKING removals.** Closes the deprecation cycle
|
|
105
|
+
opened in 1.1.2 via PR #2610 / AgentDetector-Deletion-1. Per project
|
|
106
|
+
semver policy this is shipped as a minor bump (not a major) because:
|
|
107
|
+
(a) the deprecation landed yesterday and was effectively one calendar
|
|
108
|
+
day, (b) the package is pre-Adobe-trial with no external pins to
|
|
109
|
+
`^1.x.0`, and (c) the workspace-wide deletion-gate test pins the absence
|
|
110
|
+
structurally so re-introduction is mechanically blocked.
|
|
111
|
+
|
|
112
|
+
### BREAKING — removals
|
|
113
|
+
|
|
114
|
+
- **`useAgentDetection` hook removed** (`src/hooks.ts`). Wrapped the
|
|
115
|
+
legacy `AgentDetector` class that has now been removed from
|
|
116
|
+
`@kya-os/checkpoint@1.1.0`. **Migration path:** use `withCheckpoint`
|
|
117
|
+
from `@kya-os/checkpoint-nextjs` for server-side middleware
|
|
118
|
+
(engine-backed, returns canonical envelopes) or import
|
|
119
|
+
`KNOWN_AGENT_PATTERNS` directly from `@kya-os/checkpoint-shared` for
|
|
120
|
+
rare client-side pattern matching.
|
|
121
|
+
- **`createWasmAgentShieldMiddleware` factory removed** along with the
|
|
122
|
+
whole `src/wasm-middleware.ts` file. It internally constructed a
|
|
123
|
+
legacy `AgentDetector` and never actually used the WASM instance for
|
|
124
|
+
detection (the `wasmInstance` arg only bumped confidence by 15%).
|
|
125
|
+
**Migration path:** `withCheckpoint` from `@kya-os/checkpoint-nextjs`
|
|
126
|
+
— engine-backed, runs the full orchestrator including envelope
|
|
127
|
+
verification.
|
|
128
|
+
- **`./wasm-middleware` subpath export removed** from `package.json`
|
|
129
|
+
`exports`. Imports of
|
|
130
|
+
`@kya-os/checkpoint-nextjs/wasm-middleware` will fail to resolve.
|
|
131
|
+
- **`instantiateWasm` helper removed** (was co-located in
|
|
132
|
+
`wasm-middleware.ts`). It was a one-line `WebAssembly.instantiate`
|
|
133
|
+
wrapper; customers loading WASM manually can call the Web API
|
|
134
|
+
directly. With `withCheckpoint`, manual WASM loading is no longer
|
|
135
|
+
needed at all — `@kya-os/checkpoint-wasm-runtime`'s loaders handle
|
|
136
|
+
resolution under Node and Edge runtimes.
|
|
137
|
+
- **`useDetectionMonitor` continues to be exported** — it accepts any
|
|
138
|
+
`DetectionResult` and never depended on `AgentDetector`. Reusable
|
|
139
|
+
with engine-backed detection results.
|
|
140
|
+
- **`middleware-wasm-100.ts` template removed** — the canonical starter
|
|
141
|
+
template is now `withCheckpoint` (see `README.md` and the templates
|
|
142
|
+
under `templates/` that already cover Node + Edge runtimes).
|
|
143
|
+
|
|
144
|
+
### Removed tests
|
|
145
|
+
|
|
146
|
+
- `src/__tests__/hooks.test.ts` — covered `useAgentDetection`
|
|
147
|
+
including the deprecation-warning regression test added in #2610.
|
|
148
|
+
- `src/__tests__/wasm-middleware.test.ts` — covered
|
|
149
|
+
`createWasmAgentShieldMiddleware` including the deprecation-warning
|
|
150
|
+
regression test added in #2610.
|
|
151
|
+
|
|
152
|
+
### Build config
|
|
153
|
+
|
|
154
|
+
- `tsup.config.ts` entry list no longer references `src/wasm-middleware.ts`.
|
|
155
|
+
|
|
156
|
+
### Coordination
|
|
157
|
+
|
|
158
|
+
Ships alongside `@kya-os/checkpoint@1.1.0` (defining package — removes
|
|
159
|
+
the `AgentDetector` class). No engine-surface change; cross-runtime
|
|
160
|
+
parity gate is unaffected.
|
|
161
|
+
|
|
3
162
|
## 1.2.0 — 2026-05-18
|
|
4
163
|
|
|
5
164
|
**Minor release** — peer to `@kya-os/checkpoint-wasm-runtime@1.4.0`.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ComposedPolicyCompile } from '@kya-os/checkpoint-wasm-runtime/composed-policy';
|
|
2
|
+
import { Decision, VerifyResult } from '@kya-os/checkpoint-wasm-runtime/engine';
|
|
3
|
+
import { PolicyFetcher } from '@kya-os/checkpoint-shared';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Composed-policy (kya-os-engine) wiring for the Next.js middleware.
|
|
7
|
+
*
|
|
8
|
+
* A THIN layer on top of the shared core
|
|
9
|
+
* (`@kya-os/checkpoint-wasm-runtime/composed-policy`): the core owns all the
|
|
10
|
+
* logic (empty-Cedar deny-all guard, compile-once LRU cache, act-gate,
|
|
11
|
+
* divergence, and the `verifyResultToAuthorizeInput` parity projection), so this
|
|
12
|
+
* module only fetches the project's `PolicyConfig`, drives the core once per
|
|
13
|
+
* request, maps the tagged outcome to a decision-swap signal, and logs
|
|
14
|
+
* (debug-gated). Runtime-neutral — the caller injects the compiler:
|
|
15
|
+
* - Node: `(language, source) => createPolicyEvaluator(source)` (`./policy`)
|
|
16
|
+
* - Edge: `(language, source) => evaluatePolicy(language, source)` (`./policy-edge`)
|
|
17
|
+
*
|
|
18
|
+
* This is the SDK sibling of `@kya-os/checkpoint-express`'s composed-policy
|
|
19
|
+
* wiring; both consume the same core so the DNS Gateway, Express, and Next.js
|
|
20
|
+
* produce the same decision for the same composed Cedar bundle.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Dashboard the composed-policy fetch defaults to when no base URL is set. */
|
|
24
|
+
declare const DEFAULT_DASHBOARD_URL = "https://kya.vouched.id";
|
|
25
|
+
/**
|
|
26
|
+
* Telemetry sink. The shadow-divergence signal is the go/no-go instrument for
|
|
27
|
+
* flipping a project to ENFORCE. Defaults to a no-op so the SDK never logs
|
|
28
|
+
* unprompted — `withCheckpoint({ debug: true })` wires the console logger.
|
|
29
|
+
*/
|
|
30
|
+
interface ComposedPolicyLogger {
|
|
31
|
+
shadowDivergence(info: ShadowDivergenceInfo): void;
|
|
32
|
+
evaluationError(projectId: string, error: unknown): void;
|
|
33
|
+
}
|
|
34
|
+
interface ShadowDivergenceInfo {
|
|
35
|
+
projectId: string;
|
|
36
|
+
path?: string;
|
|
37
|
+
engineDecision: Decision['kind'];
|
|
38
|
+
structuredDecision: Decision['kind'];
|
|
39
|
+
detectionClass: string;
|
|
40
|
+
verificationMethod?: string;
|
|
41
|
+
confidence: number;
|
|
42
|
+
agentName?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Result of one composed-policy evaluation: the decision to enforce + whether it replaced the baseline. */
|
|
45
|
+
interface ComposedPolicyApplyResult {
|
|
46
|
+
decision: Decision;
|
|
47
|
+
acted: boolean;
|
|
48
|
+
}
|
|
49
|
+
interface ComposedPolicyContext {
|
|
50
|
+
/**
|
|
51
|
+
* Evaluate the project's composed policy for one request. Reads
|
|
52
|
+
* `result.decision` as the structured baseline for the divergence comparison;
|
|
53
|
+
* the caller applies `acted ? decision` afterwards. Never throws.
|
|
54
|
+
*/
|
|
55
|
+
apply(result: VerifyResult, path: string | undefined): Promise<ComposedPolicyApplyResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the org's trusted Layer-2 delegation root DIDs from the project's
|
|
58
|
+
* policy payload (P2 / DR-1). The middleware forwards these into the engine's
|
|
59
|
+
* `ContextSpec.trustedDelegationRoots` on delegation requests. Optional so an
|
|
60
|
+
* injected `composedPolicyEnforcer` (advanced/testing) need not implement it —
|
|
61
|
+
* absence ⇒ no roots ⇒ the engine's open default. Never throws (a fetch fault
|
|
62
|
+
* resolves to `[]`).
|
|
63
|
+
*/
|
|
64
|
+
trustedDelegationRoots?(): Promise<string[]>;
|
|
65
|
+
}
|
|
66
|
+
interface MakeComposedPolicyContextOptions {
|
|
67
|
+
projectId: string;
|
|
68
|
+
/** Fetches the project's `PolicyConfig` (carries the composed-policy fields). */
|
|
69
|
+
fetcher: PolicyFetcher;
|
|
70
|
+
/** Target compiler — Node `createPolicyEvaluator`-backed or edge `evaluatePolicy`. */
|
|
71
|
+
compile: ComposedPolicyCompile;
|
|
72
|
+
logger?: ComposedPolicyLogger;
|
|
73
|
+
cacheMax?: number;
|
|
74
|
+
}
|
|
75
|
+
declare function makeComposedPolicyContext(opts: MakeComposedPolicyContextOptions): ComposedPolicyContext;
|
|
76
|
+
/**
|
|
77
|
+
* Resolves the org's trusted delegation roots (empty when none). Built per
|
|
78
|
+
* middleware factory from whatever source can supply them — the composed
|
|
79
|
+
* context's accessor, or (when no context exists, e.g. Edge without
|
|
80
|
+
* `cedarWasmModule`) a `projectId`-only policy fetch.
|
|
81
|
+
*/
|
|
82
|
+
type TrustedDelegationRootsResolver = () => Promise<string[]>;
|
|
83
|
+
/**
|
|
84
|
+
* Resolve the org trusted-delegation roots to inject into the engine for THIS
|
|
85
|
+
* request (P2 / DR-1). Returns `undefined` — leaving `VerifyRequestOpts`
|
|
86
|
+
* byte-identical to pre-P2 — unless (a) a resolver is configured and (b) the
|
|
87
|
+
* request actually carries a delegation-proof header (so the human /
|
|
88
|
+
* no-delegation hot path never pays the early policy fetch). Shared by the Node
|
|
89
|
+
* and Edge middleware entries.
|
|
90
|
+
*/
|
|
91
|
+
declare function resolveTrustedDelegationRootsForRequest(resolver: TrustedDelegationRootsResolver | null, headers: Headers): Promise<string[] | undefined>;
|
|
92
|
+
/**
|
|
93
|
+
* The per-request seam shared by both middleware entries. Evaluates the composed
|
|
94
|
+
* policy and swaps `result.decision` IN PLACE when the engine acts — so the
|
|
95
|
+
* swap happens BEFORE the detection reporter fires and before
|
|
96
|
+
* `renderDecisionAsResponse`, exactly like the Express runtime (the reporter
|
|
97
|
+
* records the enforced decision). Fail-OPEN: any fault keeps the structured
|
|
98
|
+
* decision; a policy fault never breaks the response.
|
|
99
|
+
*/
|
|
100
|
+
declare function applyComposedPolicy(context: ComposedPolicyContext | null, result: VerifyResult, path: string | undefined): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Debug logger mirroring the worker's `console.warn`/`console.error` shadow +
|
|
103
|
+
* fail-open telemetry. Wired by `withCheckpoint` only when `config.debug` is
|
|
104
|
+
* set, so the default SDK posture stays silent.
|
|
105
|
+
*/
|
|
106
|
+
declare const consoleComposedPolicyLogger: ComposedPolicyLogger;
|
|
107
|
+
|
|
108
|
+
export { type ComposedPolicyApplyResult, type ComposedPolicyContext, type ComposedPolicyLogger, DEFAULT_DASHBOARD_URL, type MakeComposedPolicyContextOptions, type ShadowDivergenceInfo, type TrustedDelegationRootsResolver, applyComposedPolicy, consoleComposedPolicyLogger, makeComposedPolicyContext, resolveTrustedDelegationRootsForRequest };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ComposedPolicyCompile } from '@kya-os/checkpoint-wasm-runtime/composed-policy';
|
|
2
|
+
import { Decision, VerifyResult } from '@kya-os/checkpoint-wasm-runtime/engine';
|
|
3
|
+
import { PolicyFetcher } from '@kya-os/checkpoint-shared';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Composed-policy (kya-os-engine) wiring for the Next.js middleware.
|
|
7
|
+
*
|
|
8
|
+
* A THIN layer on top of the shared core
|
|
9
|
+
* (`@kya-os/checkpoint-wasm-runtime/composed-policy`): the core owns all the
|
|
10
|
+
* logic (empty-Cedar deny-all guard, compile-once LRU cache, act-gate,
|
|
11
|
+
* divergence, and the `verifyResultToAuthorizeInput` parity projection), so this
|
|
12
|
+
* module only fetches the project's `PolicyConfig`, drives the core once per
|
|
13
|
+
* request, maps the tagged outcome to a decision-swap signal, and logs
|
|
14
|
+
* (debug-gated). Runtime-neutral — the caller injects the compiler:
|
|
15
|
+
* - Node: `(language, source) => createPolicyEvaluator(source)` (`./policy`)
|
|
16
|
+
* - Edge: `(language, source) => evaluatePolicy(language, source)` (`./policy-edge`)
|
|
17
|
+
*
|
|
18
|
+
* This is the SDK sibling of `@kya-os/checkpoint-express`'s composed-policy
|
|
19
|
+
* wiring; both consume the same core so the DNS Gateway, Express, and Next.js
|
|
20
|
+
* produce the same decision for the same composed Cedar bundle.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Dashboard the composed-policy fetch defaults to when no base URL is set. */
|
|
24
|
+
declare const DEFAULT_DASHBOARD_URL = "https://kya.vouched.id";
|
|
25
|
+
/**
|
|
26
|
+
* Telemetry sink. The shadow-divergence signal is the go/no-go instrument for
|
|
27
|
+
* flipping a project to ENFORCE. Defaults to a no-op so the SDK never logs
|
|
28
|
+
* unprompted — `withCheckpoint({ debug: true })` wires the console logger.
|
|
29
|
+
*/
|
|
30
|
+
interface ComposedPolicyLogger {
|
|
31
|
+
shadowDivergence(info: ShadowDivergenceInfo): void;
|
|
32
|
+
evaluationError(projectId: string, error: unknown): void;
|
|
33
|
+
}
|
|
34
|
+
interface ShadowDivergenceInfo {
|
|
35
|
+
projectId: string;
|
|
36
|
+
path?: string;
|
|
37
|
+
engineDecision: Decision['kind'];
|
|
38
|
+
structuredDecision: Decision['kind'];
|
|
39
|
+
detectionClass: string;
|
|
40
|
+
verificationMethod?: string;
|
|
41
|
+
confidence: number;
|
|
42
|
+
agentName?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Result of one composed-policy evaluation: the decision to enforce + whether it replaced the baseline. */
|
|
45
|
+
interface ComposedPolicyApplyResult {
|
|
46
|
+
decision: Decision;
|
|
47
|
+
acted: boolean;
|
|
48
|
+
}
|
|
49
|
+
interface ComposedPolicyContext {
|
|
50
|
+
/**
|
|
51
|
+
* Evaluate the project's composed policy for one request. Reads
|
|
52
|
+
* `result.decision` as the structured baseline for the divergence comparison;
|
|
53
|
+
* the caller applies `acted ? decision` afterwards. Never throws.
|
|
54
|
+
*/
|
|
55
|
+
apply(result: VerifyResult, path: string | undefined): Promise<ComposedPolicyApplyResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the org's trusted Layer-2 delegation root DIDs from the project's
|
|
58
|
+
* policy payload (P2 / DR-1). The middleware forwards these into the engine's
|
|
59
|
+
* `ContextSpec.trustedDelegationRoots` on delegation requests. Optional so an
|
|
60
|
+
* injected `composedPolicyEnforcer` (advanced/testing) need not implement it —
|
|
61
|
+
* absence ⇒ no roots ⇒ the engine's open default. Never throws (a fetch fault
|
|
62
|
+
* resolves to `[]`).
|
|
63
|
+
*/
|
|
64
|
+
trustedDelegationRoots?(): Promise<string[]>;
|
|
65
|
+
}
|
|
66
|
+
interface MakeComposedPolicyContextOptions {
|
|
67
|
+
projectId: string;
|
|
68
|
+
/** Fetches the project's `PolicyConfig` (carries the composed-policy fields). */
|
|
69
|
+
fetcher: PolicyFetcher;
|
|
70
|
+
/** Target compiler — Node `createPolicyEvaluator`-backed or edge `evaluatePolicy`. */
|
|
71
|
+
compile: ComposedPolicyCompile;
|
|
72
|
+
logger?: ComposedPolicyLogger;
|
|
73
|
+
cacheMax?: number;
|
|
74
|
+
}
|
|
75
|
+
declare function makeComposedPolicyContext(opts: MakeComposedPolicyContextOptions): ComposedPolicyContext;
|
|
76
|
+
/**
|
|
77
|
+
* Resolves the org's trusted delegation roots (empty when none). Built per
|
|
78
|
+
* middleware factory from whatever source can supply them — the composed
|
|
79
|
+
* context's accessor, or (when no context exists, e.g. Edge without
|
|
80
|
+
* `cedarWasmModule`) a `projectId`-only policy fetch.
|
|
81
|
+
*/
|
|
82
|
+
type TrustedDelegationRootsResolver = () => Promise<string[]>;
|
|
83
|
+
/**
|
|
84
|
+
* Resolve the org trusted-delegation roots to inject into the engine for THIS
|
|
85
|
+
* request (P2 / DR-1). Returns `undefined` — leaving `VerifyRequestOpts`
|
|
86
|
+
* byte-identical to pre-P2 — unless (a) a resolver is configured and (b) the
|
|
87
|
+
* request actually carries a delegation-proof header (so the human /
|
|
88
|
+
* no-delegation hot path never pays the early policy fetch). Shared by the Node
|
|
89
|
+
* and Edge middleware entries.
|
|
90
|
+
*/
|
|
91
|
+
declare function resolveTrustedDelegationRootsForRequest(resolver: TrustedDelegationRootsResolver | null, headers: Headers): Promise<string[] | undefined>;
|
|
92
|
+
/**
|
|
93
|
+
* The per-request seam shared by both middleware entries. Evaluates the composed
|
|
94
|
+
* policy and swaps `result.decision` IN PLACE when the engine acts — so the
|
|
95
|
+
* swap happens BEFORE the detection reporter fires and before
|
|
96
|
+
* `renderDecisionAsResponse`, exactly like the Express runtime (the reporter
|
|
97
|
+
* records the enforced decision). Fail-OPEN: any fault keeps the structured
|
|
98
|
+
* decision; a policy fault never breaks the response.
|
|
99
|
+
*/
|
|
100
|
+
declare function applyComposedPolicy(context: ComposedPolicyContext | null, result: VerifyResult, path: string | undefined): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Debug logger mirroring the worker's `console.warn`/`console.error` shadow +
|
|
103
|
+
* fail-open telemetry. Wired by `withCheckpoint` only when `config.debug` is
|
|
104
|
+
* set, so the default SDK posture stays silent.
|
|
105
|
+
*/
|
|
106
|
+
declare const consoleComposedPolicyLogger: ComposedPolicyLogger;
|
|
107
|
+
|
|
108
|
+
export { type ComposedPolicyApplyResult, type ComposedPolicyContext, type ComposedPolicyLogger, DEFAULT_DASHBOARD_URL, type MakeComposedPolicyContextOptions, type ShadowDivergenceInfo, type TrustedDelegationRootsResolver, applyComposedPolicy, consoleComposedPolicyLogger, makeComposedPolicyContext, resolveTrustedDelegationRootsForRequest };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var composedPolicy = require('@kya-os/checkpoint-wasm-runtime/composed-policy');
|
|
4
|
+
var checkpointShared = require('@kya-os/checkpoint-shared');
|
|
5
|
+
|
|
6
|
+
// src/composed-policy.ts
|
|
7
|
+
var DEFAULT_DASHBOARD_URL = "https://kya.vouched.id";
|
|
8
|
+
var NOOP_LOGGER = {
|
|
9
|
+
shadowDivergence: () => {
|
|
10
|
+
},
|
|
11
|
+
evaluationError: () => {
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
function makeComposedPolicyContext(opts) {
|
|
15
|
+
const { projectId, fetcher } = opts;
|
|
16
|
+
const cache = composedPolicy.makeComposedPolicyCache({ compile: opts.compile, cacheMax: opts.cacheMax });
|
|
17
|
+
const logger = opts.logger ?? NOOP_LOGGER;
|
|
18
|
+
return {
|
|
19
|
+
async apply(result, path) {
|
|
20
|
+
const structured = { decision: result.decision, acted: false };
|
|
21
|
+
const policy = await fetcher.getPolicy(projectId);
|
|
22
|
+
const outcome = await composedPolicy.evaluateComposedPolicy({
|
|
23
|
+
cache,
|
|
24
|
+
projectId,
|
|
25
|
+
flags: {
|
|
26
|
+
policyLanguage: policy.policyLanguage,
|
|
27
|
+
policySourceText: policy.policySourceText,
|
|
28
|
+
engineEnforcementEnabled: policy.engineEnforcementEnabled,
|
|
29
|
+
enabled: policy.enabled
|
|
30
|
+
},
|
|
31
|
+
authorizeInput: composedPolicy.verifyResultToAuthorizeInput(result, { tenantId: projectId, path }),
|
|
32
|
+
baselineDecisionKind: result.decision.kind
|
|
33
|
+
});
|
|
34
|
+
if ((outcome.status === "acting" || outcome.status === "shadow") && outcome.diverged) {
|
|
35
|
+
logger.shadowDivergence({
|
|
36
|
+
projectId,
|
|
37
|
+
path,
|
|
38
|
+
engineDecision: outcome.engineDecision.kind,
|
|
39
|
+
structuredDecision: result.decision.kind,
|
|
40
|
+
detectionClass: result.detectionDetail.detectionClass.type,
|
|
41
|
+
verificationMethod: result.detectionDetail.verificationMethod,
|
|
42
|
+
confidence: result.detectionDetail.confidence,
|
|
43
|
+
agentName: result.detectionDetail.detectedAgent?.name
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (outcome.status === "error") {
|
|
47
|
+
logger.evaluationError(projectId, outcome.error);
|
|
48
|
+
return structured;
|
|
49
|
+
}
|
|
50
|
+
if (outcome.status === "acting") {
|
|
51
|
+
return { decision: outcome.engineDecision, acted: true };
|
|
52
|
+
}
|
|
53
|
+
return structured;
|
|
54
|
+
},
|
|
55
|
+
async trustedDelegationRoots() {
|
|
56
|
+
const policy = await fetcher.getPolicy(projectId);
|
|
57
|
+
return policy.trustedDelegationRoots ?? [];
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function resolveTrustedDelegationRootsForRequest(resolver, headers) {
|
|
62
|
+
if (!resolver) return void 0;
|
|
63
|
+
if (!checkpointShared.requestCarriesDelegationProof(headers)) return void 0;
|
|
64
|
+
const roots = await resolver();
|
|
65
|
+
return roots.length > 0 ? roots : void 0;
|
|
66
|
+
}
|
|
67
|
+
async function applyComposedPolicy(context, result, path) {
|
|
68
|
+
if (!context) return;
|
|
69
|
+
try {
|
|
70
|
+
const outcome = await context.apply(result, path);
|
|
71
|
+
if (outcome.acted) result.decision = outcome.decision;
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
var consoleComposedPolicyLogger = {
|
|
76
|
+
shadowDivergence(info) {
|
|
77
|
+
console.warn("[checkpoint/composed-policy] shadow-divergence", info);
|
|
78
|
+
},
|
|
79
|
+
evaluationError(projectId, error) {
|
|
80
|
+
console.error(
|
|
81
|
+
`[checkpoint/composed-policy] evaluation failed for ${projectId}; using structured decision:`,
|
|
82
|
+
error
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
exports.DEFAULT_DASHBOARD_URL = DEFAULT_DASHBOARD_URL;
|
|
88
|
+
exports.applyComposedPolicy = applyComposedPolicy;
|
|
89
|
+
exports.consoleComposedPolicyLogger = consoleComposedPolicyLogger;
|
|
90
|
+
exports.makeComposedPolicyContext = makeComposedPolicyContext;
|
|
91
|
+
exports.resolveTrustedDelegationRootsForRequest = resolveTrustedDelegationRootsForRequest;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { makeComposedPolicyCache, evaluateComposedPolicy, verifyResultToAuthorizeInput } from '@kya-os/checkpoint-wasm-runtime/composed-policy';
|
|
2
|
+
import { requestCarriesDelegationProof } from '@kya-os/checkpoint-shared';
|
|
3
|
+
|
|
4
|
+
// src/composed-policy.ts
|
|
5
|
+
var DEFAULT_DASHBOARD_URL = "https://kya.vouched.id";
|
|
6
|
+
var NOOP_LOGGER = {
|
|
7
|
+
shadowDivergence: () => {
|
|
8
|
+
},
|
|
9
|
+
evaluationError: () => {
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
function makeComposedPolicyContext(opts) {
|
|
13
|
+
const { projectId, fetcher } = opts;
|
|
14
|
+
const cache = makeComposedPolicyCache({ compile: opts.compile, cacheMax: opts.cacheMax });
|
|
15
|
+
const logger = opts.logger ?? NOOP_LOGGER;
|
|
16
|
+
return {
|
|
17
|
+
async apply(result, path) {
|
|
18
|
+
const structured = { decision: result.decision, acted: false };
|
|
19
|
+
const policy = await fetcher.getPolicy(projectId);
|
|
20
|
+
const outcome = await evaluateComposedPolicy({
|
|
21
|
+
cache,
|
|
22
|
+
projectId,
|
|
23
|
+
flags: {
|
|
24
|
+
policyLanguage: policy.policyLanguage,
|
|
25
|
+
policySourceText: policy.policySourceText,
|
|
26
|
+
engineEnforcementEnabled: policy.engineEnforcementEnabled,
|
|
27
|
+
enabled: policy.enabled
|
|
28
|
+
},
|
|
29
|
+
authorizeInput: verifyResultToAuthorizeInput(result, { tenantId: projectId, path }),
|
|
30
|
+
baselineDecisionKind: result.decision.kind
|
|
31
|
+
});
|
|
32
|
+
if ((outcome.status === "acting" || outcome.status === "shadow") && outcome.diverged) {
|
|
33
|
+
logger.shadowDivergence({
|
|
34
|
+
projectId,
|
|
35
|
+
path,
|
|
36
|
+
engineDecision: outcome.engineDecision.kind,
|
|
37
|
+
structuredDecision: result.decision.kind,
|
|
38
|
+
detectionClass: result.detectionDetail.detectionClass.type,
|
|
39
|
+
verificationMethod: result.detectionDetail.verificationMethod,
|
|
40
|
+
confidence: result.detectionDetail.confidence,
|
|
41
|
+
agentName: result.detectionDetail.detectedAgent?.name
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (outcome.status === "error") {
|
|
45
|
+
logger.evaluationError(projectId, outcome.error);
|
|
46
|
+
return structured;
|
|
47
|
+
}
|
|
48
|
+
if (outcome.status === "acting") {
|
|
49
|
+
return { decision: outcome.engineDecision, acted: true };
|
|
50
|
+
}
|
|
51
|
+
return structured;
|
|
52
|
+
},
|
|
53
|
+
async trustedDelegationRoots() {
|
|
54
|
+
const policy = await fetcher.getPolicy(projectId);
|
|
55
|
+
return policy.trustedDelegationRoots ?? [];
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function resolveTrustedDelegationRootsForRequest(resolver, headers) {
|
|
60
|
+
if (!resolver) return void 0;
|
|
61
|
+
if (!requestCarriesDelegationProof(headers)) return void 0;
|
|
62
|
+
const roots = await resolver();
|
|
63
|
+
return roots.length > 0 ? roots : void 0;
|
|
64
|
+
}
|
|
65
|
+
async function applyComposedPolicy(context, result, path) {
|
|
66
|
+
if (!context) return;
|
|
67
|
+
try {
|
|
68
|
+
const outcome = await context.apply(result, path);
|
|
69
|
+
if (outcome.acted) result.decision = outcome.decision;
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
var consoleComposedPolicyLogger = {
|
|
74
|
+
shadowDivergence(info) {
|
|
75
|
+
console.warn("[checkpoint/composed-policy] shadow-divergence", info);
|
|
76
|
+
},
|
|
77
|
+
evaluationError(projectId, error) {
|
|
78
|
+
console.error(
|
|
79
|
+
`[checkpoint/composed-policy] evaluation failed for ${projectId}; using structured decision:`,
|
|
80
|
+
error
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export { DEFAULT_DASHBOARD_URL, applyComposedPolicy, consoleComposedPolicyLogger, makeComposedPolicyContext, resolveTrustedDelegationRootsForRequest };
|