@kya-os/checkpoint-nextjs 1.2.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +159 -0
  2. package/EDGE_RUNTIME_WASM_SETUP.md +1 -1
  3. package/bin/setup-edge-wasm.js +1 -1
  4. package/dist/api-middleware.d.mts +9 -1
  5. package/dist/api-middleware.d.ts +9 -1
  6. package/dist/api-middleware.js +14 -4
  7. package/dist/api-middleware.mjs +15 -5
  8. package/dist/composed-policy.d.mts +115 -0
  9. package/dist/composed-policy.d.ts +115 -0
  10. package/dist/composed-policy.js +102 -0
  11. package/dist/composed-policy.mjs +96 -0
  12. package/dist/config-DAwIA4DB.d.mts +214 -0
  13. package/dist/config-DyU4l5er.d.ts +214 -0
  14. package/dist/create-middleware.js +0 -2
  15. package/dist/create-middleware.mjs +0 -2
  16. package/dist/edge-runtime-loader.js +3 -1
  17. package/dist/edge-runtime-loader.mjs +3 -1
  18. package/dist/edge-wasm-middleware.d.mts +6 -6
  19. package/dist/edge-wasm-middleware.d.ts +6 -6
  20. package/dist/index.d.mts +6 -14
  21. package/dist/index.d.ts +6 -14
  22. package/dist/index.js +191 -13
  23. package/dist/index.mjs +192 -14
  24. package/dist/middleware-edge.d.mts +7 -3
  25. package/dist/middleware-edge.d.ts +7 -3
  26. package/dist/middleware-edge.js +174 -4
  27. package/dist/middleware-edge.mjs +171 -4
  28. package/dist/middleware-node.d.mts +39 -116
  29. package/dist/middleware-node.d.ts +39 -116
  30. package/dist/middleware-node.js +181 -4
  31. package/dist/middleware-node.mjs +178 -5
  32. package/dist/middleware.d.mts +10 -1
  33. package/dist/middleware.d.ts +10 -1
  34. package/dist/middleware.js +6 -0
  35. package/dist/middleware.mjs +6 -1
  36. package/dist/nodejs-wasm-loader.d.mts +3 -4
  37. package/dist/nodejs-wasm-loader.d.ts +3 -4
  38. package/dist/nodejs-wasm-loader.js +1 -1
  39. package/dist/nodejs-wasm-loader.mjs +1 -1
  40. package/dist/wasm-setup.js +1 -1
  41. package/dist/wasm-setup.mjs +1 -1
  42. package/package.json +4 -9
  43. package/dist/.tsbuildinfo +0 -1
  44. package/dist/signature-verifier.d.mts +0 -33
  45. package/dist/signature-verifier.d.ts +0 -33
  46. package/dist/signature-verifier.js +0 -384
  47. package/dist/signature-verifier.mjs +0 -360
  48. package/dist/wasm-middleware.d.mts +0 -98
  49. package/dist/wasm-middleware.d.ts +0 -98
  50. package/dist/wasm-middleware.js +0 -125
  51. package/dist/wasm-middleware.mjs +0 -121
  52. 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`.
@@ -236,7 +236,7 @@ export function createAgentShieldMiddleware(options: {
236
236
  { pattern: /openai/i, name: 'OpenAI' },
237
237
  { pattern: /gpt-/i, name: 'GPT' },
238
238
  { pattern: /copilot/i, name: 'GitHub Copilot' },
239
- { pattern: /bard/i, name: 'Google Bard' },
239
+ { pattern: /bard/i, name: 'Google Gemini' },
240
240
  { pattern: /gemini/i, name: 'Google Gemini' },
241
241
  ];
242
242
 
@@ -310,7 +310,7 @@ function patternDetection(
310
310
  { pattern: /openai/i, name: 'OpenAI', confidence: 0.90 },
311
311
  { pattern: /gpt-crawler/i, name: 'GPT Crawler', confidence: 0.95 },
312
312
  { pattern: /copilot/i, name: 'GitHub Copilot', confidence: 0.85 },
313
- { pattern: /bard/i, name: 'Google Bard', confidence: 0.85 },
313
+ { pattern: /bard/i, name: 'Google Gemini', confidence: 0.85 }, // legacy Bard UA → Gemini (brand retired 2024)
314
314
  { pattern: /gemini/i, name: 'Google Gemini', confidence: 0.85 },
315
315
  { pattern: /perplexity/i, name: 'Perplexity', confidence: 0.85 },
316
316
  ];
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
+ import { ChallengeEnvelopeMode } from '@kya-os/checkpoint-shared';
2
3
  import { EnforcementDecision } from './api-client.mjs';
3
- import '@kya-os/checkpoint-shared';
4
4
 
5
5
  /**
6
6
  * API-based AgentShield Middleware for Next.js
@@ -76,6 +76,14 @@ interface CheckpointApiMiddlewareConfig {
76
76
  * @default 'instruct'
77
77
  */
78
78
  redirectMode?: 'instruct' | 'http';
79
+ /**
80
+ * Challenge-emission envelope mode (draft-kya-http-02 §8.1 fetcher-200).
81
+ * `negotiated` serves a body-readable HTTP 200 instruct response to
82
+ * cooperative agents (so fetchers that drop 4xx bodies can read it),
83
+ * keeping the spec 401 for everyone else. Defaults to `spec-401`.
84
+ * A cooperative-UX bridge, NOT an access control.
85
+ */
86
+ delegationChallengeMode?: ChallengeEnvelopeMode;
79
87
  /**
80
88
  * Custom blocked response
81
89
  */
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
+ import { ChallengeEnvelopeMode } from '@kya-os/checkpoint-shared';
2
3
  import { EnforcementDecision } from './api-client.js';
3
- import '@kya-os/checkpoint-shared';
4
4
 
5
5
  /**
6
6
  * API-based AgentShield Middleware for Next.js
@@ -76,6 +76,14 @@ interface CheckpointApiMiddlewareConfig {
76
76
  * @default 'instruct'
77
77
  */
78
78
  redirectMode?: 'instruct' | 'http';
79
+ /**
80
+ * Challenge-emission envelope mode (draft-kya-http-02 §8.1 fetcher-200).
81
+ * `negotiated` serves a body-readable HTTP 200 instruct response to
82
+ * cooperative agents (so fetchers that drop 4xx bodies can read it),
83
+ * keeping the spec 401 for everyone else. Defaults to `spec-401`.
84
+ * A cooperative-UX bridge, NOT an access control.
85
+ */
86
+ delegationChallengeMode?: ChallengeEnvelopeMode;
79
87
  /**
80
88
  * Custom blocked response
81
89
  */
@@ -237,7 +237,7 @@ function safeHostname(url) {
237
237
  // src/responses/agent-instruction.ts
238
238
  var MCP_I_DOCS_URL = "https://docs.knowthat.ai/mcp-i/getting-started";
239
239
  var DEFAULT_CONNECT_PATH = "/connect";
240
- function buildAgentInstructionResponse(request, decision, redirectUrl) {
240
+ function buildAgentInstructionResponse(request, decision, redirectUrl, options) {
241
241
  const resolved = resolveUrl(redirectUrl ?? DEFAULT_CONNECT_PATH, request.url);
242
242
  const agentName = decision.agentName || decision.agentType || "unknown";
243
243
  if (!resolved.searchParams.has("agent")) {
@@ -288,8 +288,11 @@ It only takes a moment and you won't need to do it again. Once you're done, ask
288
288
  confidence: decision.confidence
289
289
  }
290
290
  };
291
- const response = server.NextResponse.json(body, { status: 401 });
292
- response.headers.set("WWW-Authenticate", `KYA realm="api", authorization_uri="${authUrl}"`);
291
+ const bodyReadable200 = options?.bodyReadable200 ?? false;
292
+ const response = server.NextResponse.json(body, { status: bodyReadable200 ? 200 : 401 });
293
+ if (!bodyReadable200) {
294
+ response.headers.set("WWW-Authenticate", `KYA realm="api", authorization_uri="${authUrl}"`);
295
+ }
293
296
  response.headers.set(
294
297
  "Link",
295
298
  `<${authUrl}>; rel="kya-authorize", <${MCP_I_DOCS_URL}>; rel="help"`
@@ -482,7 +485,14 @@ function withCheckpointApi(config = {}) {
482
485
  return buildRedirectResponse(request, decision, config);
483
486
  }
484
487
  const targetUrl = config.redirectUrl || decision.redirectUrl;
485
- return buildAgentInstructionResponse(request, decision, targetUrl);
488
+ const bodyReadable200 = checkpointShared.selectBodyReadable200(
489
+ config.delegationChallengeMode ?? "spec-401",
490
+ request.headers,
491
+ result.data.detection?.detectionClass
492
+ );
493
+ return buildAgentInstructionResponse(request, decision, targetUrl, {
494
+ bodyReadable200
495
+ });
486
496
  }
487
497
  case "challenge": {
488
498
  return buildRedirectResponse(request, decision, config);
@@ -1,5 +1,5 @@
1
1
  import { NextResponse } from 'next/server';
2
- import { matchPath } from '@kya-os/checkpoint-shared';
2
+ import { selectBodyReadable200, matchPath } from '@kya-os/checkpoint-shared';
3
3
 
4
4
  // src/api-middleware.ts
5
5
 
@@ -235,7 +235,7 @@ function safeHostname(url) {
235
235
  // src/responses/agent-instruction.ts
236
236
  var MCP_I_DOCS_URL = "https://docs.knowthat.ai/mcp-i/getting-started";
237
237
  var DEFAULT_CONNECT_PATH = "/connect";
238
- function buildAgentInstructionResponse(request, decision, redirectUrl) {
238
+ function buildAgentInstructionResponse(request, decision, redirectUrl, options) {
239
239
  const resolved = resolveUrl(redirectUrl ?? DEFAULT_CONNECT_PATH, request.url);
240
240
  const agentName = decision.agentName || decision.agentType || "unknown";
241
241
  if (!resolved.searchParams.has("agent")) {
@@ -286,8 +286,11 @@ It only takes a moment and you won't need to do it again. Once you're done, ask
286
286
  confidence: decision.confidence
287
287
  }
288
288
  };
289
- const response = NextResponse.json(body, { status: 401 });
290
- response.headers.set("WWW-Authenticate", `KYA realm="api", authorization_uri="${authUrl}"`);
289
+ const bodyReadable200 = options?.bodyReadable200 ?? false;
290
+ const response = NextResponse.json(body, { status: bodyReadable200 ? 200 : 401 });
291
+ if (!bodyReadable200) {
292
+ response.headers.set("WWW-Authenticate", `KYA realm="api", authorization_uri="${authUrl}"`);
293
+ }
291
294
  response.headers.set(
292
295
  "Link",
293
296
  `<${authUrl}>; rel="kya-authorize", <${MCP_I_DOCS_URL}>; rel="help"`
@@ -480,7 +483,14 @@ function withCheckpointApi(config = {}) {
480
483
  return buildRedirectResponse(request, decision, config);
481
484
  }
482
485
  const targetUrl = config.redirectUrl || decision.redirectUrl;
483
- return buildAgentInstructionResponse(request, decision, targetUrl);
486
+ const bodyReadable200 = selectBodyReadable200(
487
+ config.delegationChallengeMode ?? "spec-401",
488
+ request.headers,
489
+ result.data.detection?.detectionClass
490
+ );
491
+ return buildAgentInstructionResponse(request, decision, targetUrl, {
492
+ bodyReadable200
493
+ });
484
494
  }
485
495
  case "challenge": {
486
496
  return buildRedirectResponse(request, decision, config);
@@ -0,0 +1,115 @@
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
+ * SSOT-served revision pin (sha256 of the evaluated bundle, byte-identical
50
+ * to `policy_revisions.composed_blob_hash`) — set ONLY when `acted`, echoed
51
+ * from `PolicyConfig.policySourceHash` (#3246 PR5). Absent on structured
52
+ * decisions and when the dashboard predates the field.
53
+ */
54
+ composedBlobHash?: string;
55
+ }
56
+ interface ComposedPolicyContext {
57
+ /**
58
+ * Evaluate the project's composed policy for one request. Reads
59
+ * `result.decision` as the structured baseline for the divergence comparison;
60
+ * the caller applies `acted ? decision` afterwards. Never throws.
61
+ */
62
+ apply(result: VerifyResult, path: string | undefined): Promise<ComposedPolicyApplyResult>;
63
+ /**
64
+ * Resolve the org's trusted Layer-2 delegation root DIDs from the project's
65
+ * policy payload (P2 / DR-1). The middleware forwards these into the engine's
66
+ * `ContextSpec.trustedDelegationRoots` on delegation requests. Optional so an
67
+ * injected `composedPolicyEnforcer` (advanced/testing) need not implement it —
68
+ * absence ⇒ no roots ⇒ the engine's open default. Never throws (a fetch fault
69
+ * resolves to `[]`).
70
+ */
71
+ trustedDelegationRoots?(): Promise<string[]>;
72
+ }
73
+ interface MakeComposedPolicyContextOptions {
74
+ projectId: string;
75
+ /** Fetches the project's `PolicyConfig` (carries the composed-policy fields). */
76
+ fetcher: PolicyFetcher;
77
+ /** Target compiler — Node `createPolicyEvaluator`-backed or edge `evaluatePolicy`. */
78
+ compile: ComposedPolicyCompile;
79
+ logger?: ComposedPolicyLogger;
80
+ cacheMax?: number;
81
+ }
82
+ declare function makeComposedPolicyContext(opts: MakeComposedPolicyContextOptions): ComposedPolicyContext;
83
+ /**
84
+ * Resolves the org's trusted delegation roots (empty when none). Built per
85
+ * middleware factory from whatever source can supply them — the composed
86
+ * context's accessor, or (when no context exists, e.g. Edge without
87
+ * `cedarWasmModule`) a `projectId`-only policy fetch.
88
+ */
89
+ type TrustedDelegationRootsResolver = () => Promise<string[]>;
90
+ /**
91
+ * Resolve the org trusted-delegation roots to inject into the engine for THIS
92
+ * request (P2 / DR-1). Returns `undefined` — leaving `VerifyRequestOpts`
93
+ * byte-identical to pre-P2 — unless (a) a resolver is configured and (b) the
94
+ * request actually carries a delegation-proof header (so the human /
95
+ * no-delegation hot path never pays the early policy fetch). Shared by the Node
96
+ * and Edge middleware entries.
97
+ */
98
+ declare function resolveTrustedDelegationRootsForRequest(resolver: TrustedDelegationRootsResolver | null, headers: Headers): Promise<string[] | undefined>;
99
+ /**
100
+ * The per-request seam shared by both middleware entries. Evaluates the composed
101
+ * policy and swaps `result.decision` IN PLACE when the engine acts — so the
102
+ * swap happens BEFORE the detection reporter fires and before
103
+ * `renderDecisionAsResponse`, exactly like the Express runtime (the reporter
104
+ * records the enforced decision). Fail-OPEN: any fault keeps the structured
105
+ * decision; a policy fault never breaks the response.
106
+ */
107
+ declare function applyComposedPolicy(context: ComposedPolicyContext | null, result: VerifyResult, path: string | undefined): Promise<void>;
108
+ /**
109
+ * Debug logger mirroring the worker's `console.warn`/`console.error` shadow +
110
+ * fail-open telemetry. Wired by `withCheckpoint` only when `config.debug` is
111
+ * set, so the default SDK posture stays silent.
112
+ */
113
+ declare const consoleComposedPolicyLogger: ComposedPolicyLogger;
114
+
115
+ export { type ComposedPolicyApplyResult, type ComposedPolicyContext, type ComposedPolicyLogger, DEFAULT_DASHBOARD_URL, type MakeComposedPolicyContextOptions, type ShadowDivergenceInfo, type TrustedDelegationRootsResolver, applyComposedPolicy, consoleComposedPolicyLogger, makeComposedPolicyContext, resolveTrustedDelegationRootsForRequest };
@@ -0,0 +1,115 @@
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
+ * SSOT-served revision pin (sha256 of the evaluated bundle, byte-identical
50
+ * to `policy_revisions.composed_blob_hash`) — set ONLY when `acted`, echoed
51
+ * from `PolicyConfig.policySourceHash` (#3246 PR5). Absent on structured
52
+ * decisions and when the dashboard predates the field.
53
+ */
54
+ composedBlobHash?: string;
55
+ }
56
+ interface ComposedPolicyContext {
57
+ /**
58
+ * Evaluate the project's composed policy for one request. Reads
59
+ * `result.decision` as the structured baseline for the divergence comparison;
60
+ * the caller applies `acted ? decision` afterwards. Never throws.
61
+ */
62
+ apply(result: VerifyResult, path: string | undefined): Promise<ComposedPolicyApplyResult>;
63
+ /**
64
+ * Resolve the org's trusted Layer-2 delegation root DIDs from the project's
65
+ * policy payload (P2 / DR-1). The middleware forwards these into the engine's
66
+ * `ContextSpec.trustedDelegationRoots` on delegation requests. Optional so an
67
+ * injected `composedPolicyEnforcer` (advanced/testing) need not implement it —
68
+ * absence ⇒ no roots ⇒ the engine's open default. Never throws (a fetch fault
69
+ * resolves to `[]`).
70
+ */
71
+ trustedDelegationRoots?(): Promise<string[]>;
72
+ }
73
+ interface MakeComposedPolicyContextOptions {
74
+ projectId: string;
75
+ /** Fetches the project's `PolicyConfig` (carries the composed-policy fields). */
76
+ fetcher: PolicyFetcher;
77
+ /** Target compiler — Node `createPolicyEvaluator`-backed or edge `evaluatePolicy`. */
78
+ compile: ComposedPolicyCompile;
79
+ logger?: ComposedPolicyLogger;
80
+ cacheMax?: number;
81
+ }
82
+ declare function makeComposedPolicyContext(opts: MakeComposedPolicyContextOptions): ComposedPolicyContext;
83
+ /**
84
+ * Resolves the org's trusted delegation roots (empty when none). Built per
85
+ * middleware factory from whatever source can supply them — the composed
86
+ * context's accessor, or (when no context exists, e.g. Edge without
87
+ * `cedarWasmModule`) a `projectId`-only policy fetch.
88
+ */
89
+ type TrustedDelegationRootsResolver = () => Promise<string[]>;
90
+ /**
91
+ * Resolve the org trusted-delegation roots to inject into the engine for THIS
92
+ * request (P2 / DR-1). Returns `undefined` — leaving `VerifyRequestOpts`
93
+ * byte-identical to pre-P2 — unless (a) a resolver is configured and (b) the
94
+ * request actually carries a delegation-proof header (so the human /
95
+ * no-delegation hot path never pays the early policy fetch). Shared by the Node
96
+ * and Edge middleware entries.
97
+ */
98
+ declare function resolveTrustedDelegationRootsForRequest(resolver: TrustedDelegationRootsResolver | null, headers: Headers): Promise<string[] | undefined>;
99
+ /**
100
+ * The per-request seam shared by both middleware entries. Evaluates the composed
101
+ * policy and swaps `result.decision` IN PLACE when the engine acts — so the
102
+ * swap happens BEFORE the detection reporter fires and before
103
+ * `renderDecisionAsResponse`, exactly like the Express runtime (the reporter
104
+ * records the enforced decision). Fail-OPEN: any fault keeps the structured
105
+ * decision; a policy fault never breaks the response.
106
+ */
107
+ declare function applyComposedPolicy(context: ComposedPolicyContext | null, result: VerifyResult, path: string | undefined): Promise<void>;
108
+ /**
109
+ * Debug logger mirroring the worker's `console.warn`/`console.error` shadow +
110
+ * fail-open telemetry. Wired by `withCheckpoint` only when `config.debug` is
111
+ * set, so the default SDK posture stays silent.
112
+ */
113
+ declare const consoleComposedPolicyLogger: ComposedPolicyLogger;
114
+
115
+ export { type ComposedPolicyApplyResult, type ComposedPolicyContext, type ComposedPolicyLogger, DEFAULT_DASHBOARD_URL, type MakeComposedPolicyContextOptions, type ShadowDivergenceInfo, type TrustedDelegationRootsResolver, applyComposedPolicy, consoleComposedPolicyLogger, makeComposedPolicyContext, resolveTrustedDelegationRootsForRequest };
@@ -0,0 +1,102 @@
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 {
52
+ decision: outcome.engineDecision,
53
+ acted: true,
54
+ // Echo the SSOT revision pin with the decision it produced (#3246
55
+ // PR5). Conditional spread: absent upstream stays absent here.
56
+ ...policy.policySourceHash ? { composedBlobHash: policy.policySourceHash } : {}
57
+ };
58
+ }
59
+ return structured;
60
+ },
61
+ async trustedDelegationRoots() {
62
+ const policy = await fetcher.getPolicy(projectId);
63
+ return policy.trustedDelegationRoots ?? [];
64
+ }
65
+ };
66
+ }
67
+ async function resolveTrustedDelegationRootsForRequest(resolver, headers) {
68
+ if (!resolver) return void 0;
69
+ if (!checkpointShared.requestCarriesDelegationProof(headers)) return void 0;
70
+ const roots = await resolver();
71
+ return roots.length > 0 ? roots : void 0;
72
+ }
73
+ async function applyComposedPolicy(context, result, path) {
74
+ if (!context) return;
75
+ try {
76
+ const outcome = await context.apply(result, path);
77
+ if (outcome.acted) {
78
+ result.decision = outcome.decision;
79
+ if (outcome.composedBlobHash) {
80
+ result.composedBlobHash = outcome.composedBlobHash;
81
+ }
82
+ }
83
+ } catch {
84
+ }
85
+ }
86
+ var consoleComposedPolicyLogger = {
87
+ shadowDivergence(info) {
88
+ console.warn("[checkpoint/composed-policy] shadow-divergence", info);
89
+ },
90
+ evaluationError(projectId, error) {
91
+ console.error(
92
+ `[checkpoint/composed-policy] evaluation failed for ${projectId}; using structured decision:`,
93
+ error
94
+ );
95
+ }
96
+ };
97
+
98
+ exports.DEFAULT_DASHBOARD_URL = DEFAULT_DASHBOARD_URL;
99
+ exports.applyComposedPolicy = applyComposedPolicy;
100
+ exports.consoleComposedPolicyLogger = consoleComposedPolicyLogger;
101
+ exports.makeComposedPolicyContext = makeComposedPolicyContext;
102
+ exports.resolveTrustedDelegationRootsForRequest = resolveTrustedDelegationRootsForRequest;