@irogane/kaji 0.2.0-beta.11 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +112 -551
  2. package/dist/index.d.mts +253 -0
  3. package/dist/index.mjs +440 -0
  4. package/package.json +28 -166
  5. package/contracts/README.md +0 -14
  6. package/contracts/beta-core-v1.json +0 -52
  7. package/contracts/cli/init-cases-v1.json +0 -27
  8. package/contracts/errors/error-codes.json +0 -48
  9. package/contracts/errors/integration-recovery-v1.json +0 -127
  10. package/contracts/errors/provider-normalization.json +0 -111
  11. package/contracts/events/conformance-invalid.json +0 -20
  12. package/contracts/events/conformance.json +0 -511
  13. package/contracts/events/new-kaji-event-v1.schema.json +0 -1021
  14. package/contracts/events/stored-kaji-event-v1.schema.json +0 -1025
  15. package/contracts/feature-tiers-v1.json +0 -488
  16. package/contracts/integrations/abi-index-v1.json +0 -8
  17. package/contracts/integrations/conformance-invalid.json +0 -443
  18. package/contracts/integrations/conformance-valid.json +0 -121
  19. package/contracts/integrations/copy-provenance-v1.schema.json +0 -61
  20. package/contracts/integrations/echo-tool-abi-v1.json +0 -37
  21. package/contracts/integrations/github-api-conformance-v1.json +0 -644
  22. package/contracts/integrations/github-tool-abi-typescript-v1.json +0 -369
  23. package/contracts/integrations/github-tool-abi-v1.json +0 -146
  24. package/contracts/integrations/gmail-api-conformance-v1.json +0 -750
  25. package/contracts/integrations/gmail-tool-abi-v1.json +0 -62
  26. package/contracts/integrations/index.schema.json +0 -37
  27. package/contracts/integrations/manifest.schema.json +0 -119
  28. package/contracts/parity/expected-normalized.json +0 -4906
  29. package/contracts/parity/scenarios.json +0 -100
  30. package/contracts/parity/scenarios.schema.json +0 -214
  31. package/contracts/providers/cost-conformance.json +0 -112
  32. package/contracts/release/github-proof-v1.schema.json +0 -138
  33. package/contracts/release/gmail-proof-v1.schema.json +0 -138
  34. package/contracts/release/kaji-ts-consumer-handoff-v1.schema.json +0 -1289
  35. package/contracts/release/publisher-identity-receipt-v1.schema.json +0 -319
  36. package/contracts/release/typescript-onboarding-evidence-v1.schema.json +0 -740
  37. package/contracts/tools/conformance-invalid.json +0 -271
  38. package/contracts/tools/conformance-valid.json +0 -78
  39. package/contracts/tools/tool-schema-v1.schema.json +0 -18
  40. package/dist/anthropic.cjs +0 -1231
  41. package/dist/anthropic.cjs.map +0 -1
  42. package/dist/anthropic.d.cts +0 -26
  43. package/dist/anthropic.d.ts +0 -26
  44. package/dist/anthropic.js +0 -270
  45. package/dist/anthropic.js.map +0 -1
  46. package/dist/auth.cjs +0 -1507
  47. package/dist/auth.cjs.map +0 -1
  48. package/dist/auth.d.cts +0 -129
  49. package/dist/auth.d.ts +0 -129
  50. package/dist/auth.js +0 -1039
  51. package/dist/auth.js.map +0 -1
  52. package/dist/base-B9FRMcP8.d.cts +0 -140
  53. package/dist/base-nHQd1VtS.d.ts +0 -140
  54. package/dist/chunk-AAM33KAO.js +0 -4367
  55. package/dist/chunk-AAM33KAO.js.map +0 -1
  56. package/dist/chunk-KAJ6BM64.js +0 -153
  57. package/dist/chunk-KAJ6BM64.js.map +0 -1
  58. package/dist/chunk-KCAXIOZS.js +0 -308
  59. package/dist/chunk-KCAXIOZS.js.map +0 -1
  60. package/dist/chunk-LSJ4AVO2.js +0 -243
  61. package/dist/chunk-LSJ4AVO2.js.map +0 -1
  62. package/dist/chunk-TM7ZGOJX.js +0 -716
  63. package/dist/chunk-TM7ZGOJX.js.map +0 -1
  64. package/dist/cli/bin.d.ts +0 -2
  65. package/dist/cli/bin.js +0 -13
  66. package/dist/cli/bin.js.map +0 -1
  67. package/dist/cli/chunk-2RCWPRVY.js +0 -6277
  68. package/dist/cli/chunk-2RCWPRVY.js.map +0 -1
  69. package/dist/cli/chunk-SEBX54TR.js +0 -681
  70. package/dist/cli/chunk-SEBX54TR.js.map +0 -1
  71. package/dist/cli/index.d.ts +0 -232
  72. package/dist/cli/index.js +0 -11
  73. package/dist/cli/index.js.map +0 -1
  74. package/dist/cli/init-worker.d.ts +0 -2
  75. package/dist/cli/init-worker.js +0 -18
  76. package/dist/cli/init-worker.js.map +0 -1
  77. package/dist/cli/integration-copy-worker.js +0 -48
  78. package/dist/cli/integration-copy-worker.js.map +0 -1
  79. package/dist/cli/package-entry-cjs.cjs +0 -21
  80. package/dist/cli/package-entry-cjs.cjs.map +0 -1
  81. package/dist/cli/package-entry-cjs.d.cts +0 -2
  82. package/dist/cli/package-entry.d.ts +0 -2
  83. package/dist/cli/package-entry.js +0 -13
  84. package/dist/cli/package-entry.js.map +0 -1
  85. package/dist/context-BaFHrQHv.d.cts +0 -21
  86. package/dist/context-BaFHrQHv.d.ts +0 -21
  87. package/dist/context-C-YPY-GS.d.cts +0 -1538
  88. package/dist/context-C-YPY-GS.d.ts +0 -1538
  89. package/dist/index.cjs +0 -12346
  90. package/dist/index.cjs.map +0 -1
  91. package/dist/index.d.cts +0 -1560
  92. package/dist/index.d.ts +0 -1560
  93. package/dist/index.js +0 -7852
  94. package/dist/index.js.map +0 -1
  95. package/dist/integrations/github.cjs +0 -2092
  96. package/dist/integrations/github.cjs.map +0 -1
  97. package/dist/integrations/github.d.cts +0 -21
  98. package/dist/integrations/github.d.ts +0 -21
  99. package/dist/integrations/github.js +0 -2088
  100. package/dist/integrations/github.js.map +0 -1
  101. package/dist/integrations.cjs +0 -3370
  102. package/dist/integrations.cjs.map +0 -1
  103. package/dist/integrations.d.cts +0 -202
  104. package/dist/integrations.d.ts +0 -202
  105. package/dist/integrations.js +0 -2650
  106. package/dist/integrations.js.map +0 -1
  107. package/dist/observability-Cj--OkME.d.cts +0 -96
  108. package/dist/observability-Cj--OkME.d.ts +0 -96
  109. package/dist/openai.cjs +0 -1235
  110. package/dist/openai.cjs.map +0 -1
  111. package/dist/openai.d.cts +0 -32
  112. package/dist/openai.d.ts +0 -32
  113. package/dist/openai.js +0 -272
  114. package/dist/openai.js.map +0 -1
  115. package/dist/testing.cjs +0 -554
  116. package/dist/testing.cjs.map +0 -1
  117. package/dist/testing.d.cts +0 -43
  118. package/dist/testing.d.ts +0 -43
  119. package/dist/testing.js +0 -118
  120. package/dist/testing.js.map +0 -1
  121. package/registry/echo/index.ts +0 -53
  122. package/registry/echo/manifest.json +0 -53
  123. package/registry/github/LICENSE +0 -105
  124. package/registry/github/client.ts +0 -1727
  125. package/registry/github/index.ts +0 -263
  126. package/registry/github/manifest.json +0 -227
  127. package/registry/github/owner-fixtures.json +0 -10
  128. package/registry/github/tests/github.test.ts +0 -32
  129. package/registry/gmail/LICENSE +0 -105
  130. package/registry/gmail/client.ts +0 -548
  131. package/registry/gmail/index.ts +0 -165
  132. package/registry/gmail/manifest.json +0 -102
  133. package/registry/gmail/owner-fixtures.json +0 -10
  134. package/registry/gmail/tests/gmail.test.ts +0 -32
  135. package/registry/index.json +0 -21
  136. package/registry/index.schema.json +0 -37
  137. package/registry/schema.json +0 -119
@@ -0,0 +1,253 @@
1
+ //#region src/execution-context.d.ts
2
+ /**
3
+ * The information a capability's `execute` function needs to run safely.
4
+ *
5
+ * This stays intentionally small: no store, registry, session, or engine
6
+ * reference. Identity is exactly `principalId`. Cancellation uses the
7
+ * platform's native `AbortSignal` instead of a Kaji-specific token.
8
+ */
9
+ type ExecutionContext = {
10
+ readonly principalId: string;
11
+ readonly idempotencyKey: string;
12
+ readonly signal: AbortSignal;
13
+ };
14
+ //#endregion
15
+ //#region src/schema.d.ts
16
+ /**
17
+ * Kaji stays validator-neutral. `input` needs only a `parse` method that
18
+ * returns validated input or throws, per docs/api.md: "input needs only a
19
+ * parser that either returns validated input or throws." Zod, Valibot (via
20
+ * `.parse`), and other libraries satisfy this shape without an adapter.
21
+ */
22
+ type InputParser<Output> = {
23
+ parse(input: unknown): Output;
24
+ };
25
+ //#endregion
26
+ //#region src/capability.d.ts
27
+ /**
28
+ * A request to authorize or require approval for one capability execution.
29
+ * Kept inline rather than named because it is not part of the store contract
30
+ * and does not need to be constructed by callers.
31
+ */
32
+ type PrincipalRequest<Input> = {
33
+ readonly principalId: string;
34
+ readonly input: Input;
35
+ };
36
+ /**
37
+ * Declares one application action: its validated input contract,
38
+ * authorization/approval rules, and the function Kaji will eventually
39
+ * execute. A capability does not execute itself; `feat/execute` owns that.
40
+ *
41
+ * `Input` is the parser's returned/validated type, matching what
42
+ * `authorize`, `approval`, and `execute` receive.
43
+ */
44
+ type CapabilityDefinition<Input, Result> = {
45
+ readonly name: string;
46
+ readonly input: InputParser<Input>;
47
+ readonly authorize: (request: PrincipalRequest<Input>) => boolean | Promise<boolean>;
48
+ readonly approval?: (request: PrincipalRequest<Input>) => boolean;
49
+ readonly execute: (input: Input, context: ExecutionContext) => Result | Promise<Result>;
50
+ };
51
+ /**
52
+ * The declared capability, as frozen by docs/api.md.
53
+ *
54
+ * Only `name` is public today. `Input` and `Result` are retained as type
55
+ * parameters (unused at this field) because a future `Kaji.execute()` needs
56
+ * `Capability<Input, Result>` to recover both types; removing them now would
57
+ * force `feat/execute` to redesign this type. The unused-type-parameter
58
+ * warning this produces is suppressed in .oxlintrc.json for this file.
59
+ */
60
+ type Capability<Input, Result> = {
61
+ readonly name: string;
62
+ };
63
+ /**
64
+ * Defines one application action that Kaji can later execute safely.
65
+ *
66
+ * `capability()` is the single canonical constructor. It validates its own
67
+ * declaration (name, hooks) at construction time; it never invokes
68
+ * `authorize`, `approval`, or `execute` itself.
69
+ */
70
+ declare function capability<Input, Result>(definition: CapabilityDefinition<Input, Result>): Capability<Input, Result>;
71
+ //#endregion
72
+ //#region src/approval.d.ts
73
+ /**
74
+ * What Kaji tells an application's `approve` handler about one request
75
+ * requiring approval, per docs/api.md's inline `createKaji()` contract.
76
+ * Not exported: docs/api.md keeps this shape inline in `KajiOptions`
77
+ * rather than naming it, so there is no caller need to import it directly.
78
+ */
79
+ type ApprovalRequest = {
80
+ readonly capability: string;
81
+ readonly principalId: string;
82
+ readonly input: unknown;
83
+ readonly idempotencyKey: string;
84
+ };
85
+ /** What an application's `approve` handler returns for one request. */
86
+ type ApprovalDecision = {
87
+ readonly approved: boolean;
88
+ readonly evidence?: unknown;
89
+ };
90
+ type ApproveHandler = (request: ApprovalRequest) => ApprovalDecision | Promise<ApprovalDecision>;
91
+ //#endregion
92
+ //#region src/execution-request.d.ts
93
+ /**
94
+ * One caller's request to run a capability, as frozen by docs/api.md.
95
+ * `principalId` and `idempotencyKey` are supplied by the caller — Kaji
96
+ * never infers either from process state (docs/invariants.md
97
+ * "Principal is explicit").
98
+ */
99
+ type ExecutionRequest = {
100
+ readonly input: unknown;
101
+ readonly principalId: string;
102
+ readonly idempotencyKey: string;
103
+ readonly signal?: AbortSignal;
104
+ };
105
+ //#endregion
106
+ //#region src/execution-result.d.ts
107
+ /**
108
+ * The minimal facts Kaji knows about one governed execution.
109
+ *
110
+ * This is deliberately not a general event or audit record: it carries only
111
+ * what a caller needs to identify the execution and detect conflicting
112
+ * idempotency key reuse. See docs/invariants.md "Evidence is minimal".
113
+ */
114
+ type ExecutionEvidence = {
115
+ readonly executionId: string;
116
+ readonly capability: string;
117
+ readonly principalId: string;
118
+ readonly idempotencyKey: string;
119
+ readonly inputFingerprint: string;
120
+ };
121
+ /**
122
+ * The explicit outcome of one governed execution, as frozen by docs/api.md.
123
+ *
124
+ * `unknown` is distinct from `failed`: it means the capability's side effect
125
+ * may have committed but Kaji cannot prove the final result, so callers
126
+ * must not treat it as an ordinary retryable failure.
127
+ */
128
+ type ExecutionResult<Result> = {
129
+ readonly status: "succeeded";
130
+ readonly result: Result;
131
+ readonly evidence: ExecutionEvidence;
132
+ } | {
133
+ readonly status: "denied" | "rejected" | "failed" | "cancelled";
134
+ readonly error: unknown;
135
+ readonly evidence: ExecutionEvidence;
136
+ } | {
137
+ readonly status: "unknown";
138
+ readonly error: unknown;
139
+ readonly evidence: ExecutionEvidence;
140
+ };
141
+ //#endregion
142
+ //#region src/execution-store.d.ts
143
+ /**
144
+ * The material identity of one intended operation: which capability, which
145
+ * principal, which caller-supplied idempotency key, and a fingerprint of
146
+ * the validated input. The store uses this tuple to detect duplicate
147
+ * claims and conflicting reuse; it never sees the raw input or the
148
+ * capability's callbacks.
149
+ */
150
+ type ExecutionClaim = {
151
+ readonly capability: string;
152
+ readonly principalId: string;
153
+ readonly idempotencyKey: string;
154
+ readonly inputFingerprint: string;
155
+ };
156
+ /** The terminal or in-flight record a store persists for one execution. */
157
+ type StoredExecution = ExecutionResult<unknown>;
158
+ /**
159
+ * The outcome of attempting to claim one execution.
160
+ *
161
+ * `"existing"` covers both an execution still running and one already
162
+ * settled: its `outcome` promise resolves once the claim is recorded,
163
+ * so a caller that must not duplicate a side effect can simply await it
164
+ * instead of the store exposing separate running/completed/unknown states.
165
+ */
166
+ type ClaimResult = {
167
+ readonly status: "claimed";
168
+ readonly executionId: string;
169
+ } | {
170
+ readonly status: "existing";
171
+ readonly outcome: Promise<StoredExecution>;
172
+ } | {
173
+ readonly status: "conflict";
174
+ readonly executionId: string;
175
+ };
176
+ /**
177
+ * Persists execution identity, idempotency claims, and minimal outcome
178
+ * evidence. A store does not execute capabilities, enforce authorization
179
+ * or approval, or decide retries — it only answers whether an execution
180
+ * may begin and records what happened once it is known.
181
+ *
182
+ * `claim()` must be atomic enough that concurrent claims for the same
183
+ * identity produce exactly one `"claimed"` result; see docs/invariants.md
184
+ * "Store semantics are atomic where required".
185
+ */
186
+ type ExecutionStore = {
187
+ claim(claim: ExecutionClaim): Promise<ClaimResult>;
188
+ record(execution: StoredExecution): Promise<void>;
189
+ };
190
+ //#endregion
191
+ //#region src/kaji.d.ts
192
+ /** Stable application configuration for the executor, per docs/api.md. */
193
+ type KajiOptions = {
194
+ readonly store: ExecutionStore;
195
+ readonly approve?: ApproveHandler;
196
+ readonly timeoutMs?: number;
197
+ };
198
+ /** The Kaji executor, as frozen by docs/api.md. */
199
+ type Kaji = {
200
+ execute<Input, Result>(capability: Capability<Input, Result>, request: ExecutionRequest): Promise<ExecutionResult<Result>>;
201
+ };
202
+ /**
203
+ * Creates the Kaji executor: the one canonical entry point for running a
204
+ * capability. `kaji.execute()` validates the request, claims its
205
+ * idempotency key, authorizes and approves it, calls the capability at
206
+ * most once, and records the explicit outcome.
207
+ */
208
+ declare function createKaji(options: KajiOptions): Kaji;
209
+ //#endregion
210
+ //#region src/errors.d.ts
211
+ /**
212
+ * Marks a capability's `execute` failure as definitively known rather than
213
+ * ambiguous: application code is asserting that no side effect committed,
214
+ * so Kaji may settle `failed` instead of its safe `unknown` default. See
215
+ * docs/invariants.md "Ambiguous outcomes are preserved" and "Known failure
216
+ * is explicit".
217
+ *
218
+ * `knownFailure()` is the only way to opt out of `unknown`. Kaji never
219
+ * infers this from an error's class, message, or status code — only
220
+ * application code that actually knows the remote side effect did not
221
+ * commit may make this claim.
222
+ */
223
+ declare class KnownFailure extends Error {
224
+ constructor(cause: unknown);
225
+ }
226
+ /**
227
+ * Wraps `cause` so a capability's `execute` function can throw it to
228
+ * report an ordinary, provably non-ambiguous failure (docs/api.md).
229
+ *
230
+ * Usage:
231
+ * ```ts
232
+ * execute: async (input, context) => {
233
+ * try {
234
+ * return await provider.charge(input);
235
+ * } catch (cause) {
236
+ * if (isDefinitelyDeclined(cause)) throw knownFailure(cause);
237
+ * throw cause; // stays unknown: the side effect may have committed
238
+ * }
239
+ * },
240
+ * ```
241
+ */
242
+ declare function knownFailure(cause: unknown): KnownFailure;
243
+ //#endregion
244
+ //#region src/memory-store.d.ts
245
+ /**
246
+ * A process-local reference `ExecutionStore`. It has no external
247
+ * persistence, no cross-process visibility, and no durability guarantee —
248
+ * it exists for development, tests, and examples, but honors the same
249
+ * claim/record contract a durable store must honor.
250
+ */
251
+ declare function memoryStore(): ExecutionStore;
252
+ //#endregion
253
+ export { type Capability, type ClaimResult, type ExecutionClaim, type ExecutionContext, type ExecutionEvidence, type ExecutionRequest, type ExecutionResult, type ExecutionStore, type StoredExecution, capability, createKaji, knownFailure, memoryStore };
package/dist/index.mjs ADDED
@@ -0,0 +1,440 @@
1
+ //#region src/capability.ts
2
+ /**
3
+ * Defines one application action that Kaji can later execute safely.
4
+ *
5
+ * `capability()` is the single canonical constructor. It validates its own
6
+ * declaration (name, hooks) at construction time; it never invokes
7
+ * `authorize`, `approval`, or `execute` itself.
8
+ */
9
+ function capability(definition) {
10
+ const name = definition.name;
11
+ if (typeof name !== "string" || name.trim().length === 0) throw new Error("capability() requires a non-empty name.");
12
+ if (typeof definition.authorize !== "function") throw new Error("capability() requires an authorize function.");
13
+ return Object.freeze({
14
+ name,
15
+ input: definition.input,
16
+ authorize: definition.authorize,
17
+ approval: definition.approval,
18
+ execute: definition.execute
19
+ });
20
+ }
21
+ /**
22
+ * Recovers the full declaration from a `Capability`. The public
23
+ * `Capability<Input, Result>` type only advertises `name`, but the object
24
+ * `capability()` returns always carries `input`/`authorize`/`approval`/
25
+ * `execute` — the executor is their one legitimate reader.
26
+ */
27
+ function capabilityDefinition(capability$1) {
28
+ return capability$1;
29
+ }
30
+
31
+ //#endregion
32
+ //#region src/approval.ts
33
+ /**
34
+ * Resolves whether a request that requires approval may proceed.
35
+ *
36
+ * Approval is fail-closed (docs/invariants.md "Approval is fail-closed"):
37
+ * a missing handler, an invalid decision shape, a rejected decision, or a
38
+ * thrown/rejected handler all prevent execution. Only an explicit
39
+ * `{ approved: true }` allows the caller to continue.
40
+ */
41
+ async function resolveApproval(approve, request) {
42
+ if (approve === void 0) return {
43
+ approved: false,
44
+ error: /* @__PURE__ */ new Error("Approval is required but no approve handler was configured.")
45
+ };
46
+ let decision;
47
+ try {
48
+ decision = await approve(request);
49
+ } catch (cause) {
50
+ return {
51
+ approved: false,
52
+ error: cause
53
+ };
54
+ }
55
+ if (typeof decision !== "object" || decision === null || typeof decision.approved !== "boolean") return {
56
+ approved: false,
57
+ error: /* @__PURE__ */ new Error("approve() returned an invalid decision; expected { approved: boolean }.")
58
+ };
59
+ if (!decision.approved) return {
60
+ approved: false,
61
+ error: /* @__PURE__ */ new Error("Approval was not granted.")
62
+ };
63
+ return {
64
+ approved: true,
65
+ evidence: decision.evidence
66
+ };
67
+ }
68
+
69
+ //#endregion
70
+ //#region src/effective-signal.ts
71
+ /**
72
+ * Combines the caller's signal (if any) with Kaji's optional `timeoutMs`
73
+ * into one effective `AbortSignal`, using only native `AbortSignal`/
74
+ * `AbortController` primitives (docs/invariants.md "Native primitives
75
+ * first"). `cleanup()` must be called once execution finishes so the
76
+ * timeout timer does not keep the process alive.
77
+ */
78
+ function effectiveSignal(callerSignal, timeoutMs) {
79
+ if (timeoutMs === void 0) return {
80
+ signal: callerSignal ?? new AbortController().signal,
81
+ cleanup: () => {}
82
+ };
83
+ const timeoutController = new AbortController();
84
+ const timer = setTimeout(() => timeoutController.abort(), timeoutMs);
85
+ return {
86
+ signal: callerSignal === void 0 ? timeoutController.signal : AbortSignal.any([callerSignal, timeoutController.signal]),
87
+ cleanup: () => clearTimeout(timer)
88
+ };
89
+ }
90
+
91
+ //#endregion
92
+ //#region src/fingerprint.ts
93
+ /**
94
+ * Thrown when a value falls outside the durable value domain — plain
95
+ * JSON-shaped data — and cannot be fingerprinted deterministically.
96
+ * Capability input is schema-validated application data, so this is the
97
+ * natural boundary: it excludes functions, symbols, and other values a
98
+ * schema parser would not normally produce.
99
+ */
100
+ var UnfingerprintableValueError = class extends Error {
101
+ constructor(reason) {
102
+ super(`Cannot fingerprint input: ${reason}`);
103
+ this.name = "UnfingerprintableValueError";
104
+ }
105
+ };
106
+ /**
107
+ * Produces a deterministic string for one capability's validated input.
108
+ *
109
+ * Object key order must not affect the result — two semantically identical
110
+ * inputs must fingerprint identically regardless of how their keys were
111
+ * constructed — so this canonicalizes objects by sorting keys before
112
+ * serializing, rather than relying on `JSON.stringify`'s insertion order.
113
+ */
114
+ function fingerprintInput(capability$1, input) {
115
+ return `${capability$1}:${canonicalize(input)}`;
116
+ }
117
+ function canonicalize(value) {
118
+ if (value === null) return "null";
119
+ if (typeof value === "string") return JSON.stringify(value);
120
+ if (typeof value === "number") {
121
+ if (!Number.isFinite(value)) throw new UnfingerprintableValueError(`non-finite number ${value}`);
122
+ return JSON.stringify(value);
123
+ }
124
+ if (typeof value === "boolean") return JSON.stringify(value);
125
+ if (Array.isArray(value)) return `[${value.map(canonicalize).join(",")}]`;
126
+ if (typeof value === "object") return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalize(value[key])}`).join(",")}}`;
127
+ throw new UnfingerprintableValueError(`unsupported value of type ${typeof value}`);
128
+ }
129
+
130
+ //#endregion
131
+ //#region src/errors.ts
132
+ /**
133
+ * Thrown only when Kaji cannot establish the execution boundary at all —
134
+ * an invalid executor configuration, or a store failure before any
135
+ * execution record exists. Every other outcome (denied, rejected, failed,
136
+ * cancelled, unknown) is an ordinary `ExecutionResult`, not a thrown error;
137
+ * see docs/api.md "Results and outcomes".
138
+ */
139
+ var KajiConfigurationError = class extends Error {
140
+ constructor(message, options) {
141
+ super(message, options);
142
+ this.name = "KajiConfigurationError";
143
+ }
144
+ };
145
+ /**
146
+ * Marks a capability's `execute` failure as definitively known rather than
147
+ * ambiguous: application code is asserting that no side effect committed,
148
+ * so Kaji may settle `failed` instead of its safe `unknown` default. See
149
+ * docs/invariants.md "Ambiguous outcomes are preserved" and "Known failure
150
+ * is explicit".
151
+ *
152
+ * `knownFailure()` is the only way to opt out of `unknown`. Kaji never
153
+ * infers this from an error's class, message, or status code — only
154
+ * application code that actually knows the remote side effect did not
155
+ * commit may make this claim.
156
+ */
157
+ var KnownFailure = class extends Error {
158
+ constructor(cause) {
159
+ super("Capability execution failed with a definitively known outcome.", { cause });
160
+ this.name = "KnownFailure";
161
+ }
162
+ };
163
+ /**
164
+ * Wraps `cause` so a capability's `execute` function can throw it to
165
+ * report an ordinary, provably non-ambiguous failure (docs/api.md).
166
+ *
167
+ * Usage:
168
+ * ```ts
169
+ * execute: async (input, context) => {
170
+ * try {
171
+ * return await provider.charge(input);
172
+ * } catch (cause) {
173
+ * if (isDefinitelyDeclined(cause)) throw knownFailure(cause);
174
+ * throw cause; // stays unknown: the side effect may have committed
175
+ * }
176
+ * },
177
+ * ```
178
+ */
179
+ function knownFailure(cause) {
180
+ return new KnownFailure(cause);
181
+ }
182
+
183
+ //#endregion
184
+ //#region src/schema.ts
185
+ /**
186
+ * Validates unknown input against a capability's parser.
187
+ *
188
+ * This is the one canonical validation primitive in Kaji. The future
189
+ * executor calls this exact function so there is never a second path that
190
+ * could let invalid input reach application code.
191
+ */
192
+ function validateInput(parser, input) {
193
+ return parser.parse(input);
194
+ }
195
+
196
+ //#endregion
197
+ //#region src/execute.ts
198
+ /**
199
+ * Runs the one canonical Kaji execution path for one request, in the exact
200
+ * order docs/api.md freezes: validate, claim idempotency, authorize,
201
+ * obtain approval when needed, execute, and record the explicit outcome.
202
+ *
203
+ * Claiming before authorization is deliberate: claiming an idempotency key
204
+ * is bookkeeping, not the side effect docs/invariants.md's "Authorization
205
+ * precedes side effects" protects. It lets a denied or rejected request
206
+ * still occupy and settle its idempotency key, so a caller who retries a
207
+ * denied request with the same key observes the same denial instead of a
208
+ * fresh authorization check racing a duplicate claim.
209
+ */
210
+ async function executeCapability(dependencies, capability$1, request) {
211
+ const definition = capabilityDefinition(capability$1);
212
+ const principalId = requireNonEmptyString(request.principalId, "principalId");
213
+ const idempotencyKey = requireNonEmptyString(request.idempotencyKey, "idempotencyKey");
214
+ const { signal, cleanup } = effectiveSignal(request.signal, dependencies.timeoutMs);
215
+ try {
216
+ if (signal.aborted) return cancelled(capability$1.name, principalId, idempotencyKey);
217
+ const input = validateInput(definition.input, request.input);
218
+ const inputFingerprint = fingerprintInput(capability$1.name, input);
219
+ const claimResult = await claim(dependencies.store, {
220
+ capability: capability$1.name,
221
+ principalId,
222
+ idempotencyKey,
223
+ inputFingerprint
224
+ });
225
+ if (claimResult.status === "conflict") return conflict(capability$1.name, principalId, idempotencyKey, claimResult.executionId, inputFingerprint);
226
+ if (claimResult.status === "existing") return await claimResult.outcome;
227
+ const evidence = {
228
+ executionId: claimResult.executionId,
229
+ capability: capability$1.name,
230
+ principalId,
231
+ idempotencyKey,
232
+ inputFingerprint
233
+ };
234
+ if (signal.aborted) return await settle(dependencies.store, cancelled(capability$1.name, principalId, idempotencyKey, evidence));
235
+ const authorization = await guarded(() => definition.authorize({
236
+ principalId,
237
+ input
238
+ }));
239
+ if (!authorization.ok) return await settle(dependencies.store, denied(evidence, authorization.error));
240
+ if (authorization.value === false) return await settle(dependencies.store, denied(evidence, /* @__PURE__ */ new Error("Authorization denied the request.")));
241
+ if (signal.aborted) return await settle(dependencies.store, cancelled(capability$1.name, principalId, idempotencyKey, evidence));
242
+ if (definition.approval?.({
243
+ principalId,
244
+ input
245
+ }) === true) {
246
+ const decision = await resolveApproval(dependencies.approve, {
247
+ capability: capability$1.name,
248
+ principalId,
249
+ input,
250
+ idempotencyKey
251
+ });
252
+ if (!decision.approved) return await settle(dependencies.store, rejected(evidence, decision.error));
253
+ }
254
+ if (signal.aborted) return await settle(dependencies.store, cancelled(capability$1.name, principalId, idempotencyKey, evidence));
255
+ const context = {
256
+ principalId,
257
+ idempotencyKey,
258
+ signal
259
+ };
260
+ const outcome = await runCapability(definition.execute, input, context, evidence);
261
+ return await settle(dependencies.store, outcome);
262
+ } finally {
263
+ cleanup();
264
+ }
265
+ }
266
+ async function runCapability(execute, input, context, evidence) {
267
+ try {
268
+ return {
269
+ status: "succeeded",
270
+ result: await execute(input, context),
271
+ evidence
272
+ };
273
+ } catch (cause) {
274
+ if (cause instanceof KnownFailure) return {
275
+ status: "failed",
276
+ error: cause.cause,
277
+ evidence
278
+ };
279
+ return {
280
+ status: "unknown",
281
+ error: cause,
282
+ evidence
283
+ };
284
+ }
285
+ }
286
+ async function claim(store, executionClaim) {
287
+ try {
288
+ return await store.claim(executionClaim);
289
+ } catch (cause) {
290
+ throw new KajiConfigurationError("Execution store failed to claim the operation.", { cause });
291
+ }
292
+ }
293
+ /**
294
+ * Persists a terminal outcome and returns it. A store failure here means
295
+ * Kaji cannot prove the outcome was durably recorded; docs/invariants.md
296
+ * requires that ambiguity to surface as `unknown` rather than the
297
+ * capability's real outcome, since a caller could otherwise be told an
298
+ * action succeeded that Kaji has no durable record of.
299
+ */
300
+ async function settle(store, outcome) {
301
+ try {
302
+ await store.record(outcome);
303
+ return outcome;
304
+ } catch (cause) {
305
+ return {
306
+ status: "unknown",
307
+ error: cause,
308
+ evidence: outcome.evidence
309
+ };
310
+ }
311
+ }
312
+ async function guarded(fn) {
313
+ try {
314
+ return {
315
+ ok: true,
316
+ value: await fn()
317
+ };
318
+ } catch (error) {
319
+ return {
320
+ ok: false,
321
+ error
322
+ };
323
+ }
324
+ }
325
+ function denied(evidence, error) {
326
+ return {
327
+ status: "denied",
328
+ error,
329
+ evidence
330
+ };
331
+ }
332
+ function rejected(evidence, error) {
333
+ return {
334
+ status: "rejected",
335
+ error,
336
+ evidence
337
+ };
338
+ }
339
+ function conflict(capability$1, principalId, idempotencyKey, executionId, inputFingerprint) {
340
+ return {
341
+ status: "failed",
342
+ error: /* @__PURE__ */ new Error(`Idempotency key "${idempotencyKey}" was already used for a different request.`),
343
+ evidence: {
344
+ executionId,
345
+ capability: capability$1,
346
+ principalId,
347
+ idempotencyKey,
348
+ inputFingerprint
349
+ }
350
+ };
351
+ }
352
+ function cancelled(capability$1, principalId, idempotencyKey, evidence) {
353
+ return {
354
+ status: "cancelled",
355
+ error: /* @__PURE__ */ new Error("Execution was cancelled before it began."),
356
+ evidence: evidence ?? {
357
+ executionId: "",
358
+ capability: capability$1,
359
+ principalId,
360
+ idempotencyKey,
361
+ inputFingerprint: ""
362
+ }
363
+ };
364
+ }
365
+ function requireNonEmptyString(value, field) {
366
+ if (typeof value !== "string" || value.trim().length === 0) throw new KajiConfigurationError(`ExecutionRequest.${field} must be a non-empty string.`);
367
+ return value;
368
+ }
369
+
370
+ //#endregion
371
+ //#region src/kaji.ts
372
+ /**
373
+ * Creates the Kaji executor: the one canonical entry point for running a
374
+ * capability. `kaji.execute()` validates the request, claims its
375
+ * idempotency key, authorizes and approves it, calls the capability at
376
+ * most once, and records the explicit outcome.
377
+ */
378
+ function createKaji(options) {
379
+ return { execute(capability$1, request) {
380
+ return executeCapability(options, capability$1, request);
381
+ } };
382
+ }
383
+
384
+ //#endregion
385
+ //#region src/memory-store.ts
386
+ /**
387
+ * A process-local reference `ExecutionStore`. It has no external
388
+ * persistence, no cross-process visibility, and no durability guarantee —
389
+ * it exists for development, tests, and examples, but honors the same
390
+ * claim/record contract a durable store must honor.
391
+ */
392
+ function memoryStore() {
393
+ const claims = /* @__PURE__ */ new Map();
394
+ return {
395
+ async claim(claim$1) {
396
+ const identity = identityKey(claim$1);
397
+ const existing = claims.get(identity);
398
+ if (existing !== void 0) {
399
+ if (existing.claim.inputFingerprint !== claim$1.inputFingerprint) return {
400
+ status: "conflict",
401
+ executionId: existing.executionId
402
+ };
403
+ return {
404
+ status: "existing",
405
+ outcome: existing.outcome
406
+ };
407
+ }
408
+ let settle$1;
409
+ const outcome = new Promise((resolve) => {
410
+ settle$1 = resolve;
411
+ });
412
+ const executionId = crypto.randomUUID();
413
+ claims.set(identity, {
414
+ claim: claim$1,
415
+ executionId,
416
+ outcome,
417
+ settle: settle$1,
418
+ settled: false
419
+ });
420
+ return {
421
+ status: "claimed",
422
+ executionId
423
+ };
424
+ },
425
+ async record(execution) {
426
+ const identity = identityKey(execution.evidence);
427
+ const entry = claims.get(identity);
428
+ if (entry === void 0 || entry.executionId !== execution.evidence.executionId) throw new Error(`record() called for unclaimed executionId "${execution.evidence.executionId}".`);
429
+ if (entry.settled) throw new Error(`record() called twice for executionId "${entry.executionId}".`);
430
+ entry.settled = true;
431
+ entry.settle(execution);
432
+ }
433
+ };
434
+ }
435
+ function identityKey(claim$1) {
436
+ return `${claim$1.capability}:${claim$1.principalId}:${claim$1.idempotencyKey}`;
437
+ }
438
+
439
+ //#endregion
440
+ export { capability, createKaji, knownFailure, memoryStore };