@amboras-dev/capability-kit 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,313 @@
1
+ import { z } from "zod/v4";
2
+ //#region src/canonical-json.d.ts
3
+ /** Sorted keys, no whitespace. Throws on BigInt and circular values; `undefined` at the top serializes as `null`. */
4
+ export declare function canonicalJson(v: unknown): string;
5
+ //#endregion
6
+ //#region src/ids.d.ts
7
+ export declare const AREAS: readonly ["promotions", "catalog", "inventory", "setup", "content", "orders", "payments", "domains", "publish"];
8
+ type Area = (typeof AREAS)[number];
9
+ /** `<area>.<verb>` or `<area>.<entity>.<verb>`: lowercase segments of `a-z` and `_`. */
10
+ type CapabilityId = `${Area}.${string}`;
11
+ type Role = 'owner' | 'manager' | 'staff';
12
+ /** Higher rank includes every lower one: an owner can do what a manager can. */
13
+ export declare const ROLE_RANK: Readonly<Record<Role, number>>;
14
+ /** True for a well-formed id in a known area. */
15
+ export declare function isCapabilityId(value: unknown): value is CapabilityId;
16
+ /** The area an id belongs to (`promotions.create` → `promotions`). */
17
+ export declare function areaOf(id: CapabilityId): Area;
18
+ //#endregion
19
+ //#region src/context.d.ts
20
+ type Actor = {
21
+ kind: 'member';
22
+ profileId: string;
23
+ role: Role | null;
24
+ } | {
25
+ kind: 'internal';
26
+ service: string;
27
+ };
28
+ /** A label for logs. Never trusted for a policy decision. */
29
+ type Surface = 'ai' | 'admin' | 'api';
30
+ interface CapabilityCallContext {
31
+ tenantId: string;
32
+ actor: Actor;
33
+ surface: Surface;
34
+ runId?: string;
35
+ }
36
+ //#endregion
37
+ //#region src/json-schema.d.ts
38
+ type JsonSchema = {
39
+ [key: string]: unknown;
40
+ };
41
+ /**
42
+ * A copy of `schema` without the hidden fields (`agent.hideFields`), also
43
+ * dropped from `required`. Adapters build the model-facing input with this.
44
+ */
45
+ export declare function omitFields(schema: JsonSchema, fields: readonly string[]): JsonSchema;
46
+ //#endregion
47
+ //#region src/policy-types.d.ts
48
+ interface CapabilityPolicy {
49
+ minRole: Role;
50
+ planFeature?: string;
51
+ effect: 'read' | 'write' | 'destructive';
52
+ /** True = safe to retry without a key (reads, set-to-value). */
53
+ idempotent: boolean;
54
+ /** Grants or moves value: fail closed everywhere. */
55
+ money?: boolean;
56
+ confirm: 'never' | 'always' | {
57
+ when: {
58
+ recordsAtLeast: number;
59
+ };
60
+ };
61
+ rateLimit?: {
62
+ perMinute: number;
63
+ };
64
+ }
65
+ //#endregion
66
+ //#region src/define.d.ts
67
+ interface CapabilityAgentSpec {
68
+ /** What it does and when to use it, written for the model. */
69
+ description: string;
70
+ /** Only what the schema shape cannot carry. */
71
+ rules?: string[];
72
+ examples?: Array<{
73
+ input: unknown;
74
+ note: string;
75
+ }>;
76
+ /** Dotted input paths the model never sees; each must exist and be optional. */
77
+ hideFields?: string[];
78
+ exposure: 'task-tool' | 'long-tail' | 'hidden';
79
+ /** Phase 1: keeps the legacy tool name (e.g. 'create_promotion') so prompts, evals and TOOL_CAPABILITIES keys stay stable. */
80
+ toolName?: string;
81
+ }
82
+ interface CapabilityDefinition<I = unknown, O = unknown, Ctx = unknown> {
83
+ id: CapabilityId;
84
+ /** Integer ≥ 1; bump on a breaking input or output change. */
85
+ version: number;
86
+ owner: 'medusa' | 'orchestrator';
87
+ http: {
88
+ method: 'GET' | 'POST' | 'DELETE';
89
+ path: string;
90
+ runsOn: 'custom' | 'core';
91
+ };
92
+ input: z.ZodType<I>;
93
+ output: z.ZodType<O>;
94
+ run?(ctx: Ctx, input: I): Promise<O>;
95
+ policy: CapabilityPolicy;
96
+ agent: CapabilityAgentSpec;
97
+ }
98
+ /**
99
+ * Checks a definition and returns a frozen copy. Throws CapabilityDefinitionError
100
+ * on: a bad id or unknown area; a version that is not a positive integer; an
101
+ * input that is not an object, accepts undeclared keys or any value, or names a
102
+ * store / tenant / profile / environment id at any depth; runsOn 'custom'
103
+ * without run; hideFields naming a missing (or required) field; a schema JSON
104
+ * Schema cannot express; a money() field without its unit sentence or with a
105
+ * currencyField that is not a string field at the input root; an invalid
106
+ * minRole, confirm threshold, rate limit or toolName.
107
+ */
108
+ export declare function defineCapability<I, O, Ctx = unknown>(def: CapabilityDefinition<I, O, Ctx>): Readonly<CapabilityDefinition<I, O, Ctx>>;
109
+ //#endregion
110
+ //#region src/errors.d.ts
111
+ type CapabilityErrorCode = 'invalid_input' | 'forbidden' | 'not_found' | 'conflict' | 'confirmation_required' | 'rate_limited' | 'idempotency_conflict' | 'unavailable' | 'internal';
112
+ interface CapabilityErrorBody {
113
+ code: CapabilityErrorCode;
114
+ message: string;
115
+ /** Dotted input path the error is about (`discount.amount_off`). */
116
+ field?: string;
117
+ /** Stable id of the business rule that refused (`idempotency_key_required`). */
118
+ rule?: string;
119
+ retryable: boolean;
120
+ issues?: Array<{
121
+ field: string;
122
+ message: string;
123
+ }>;
124
+ /** Small structured facts the caller can act on (duplicate_code → { existing_id, existing_status }). Never secrets or PII. */
125
+ details?: Record<string, string | number | boolean | null>;
126
+ }
127
+ export declare function httpStatusFor(code: CapabilityErrorCode): number;
128
+ type CapabilityErrorInit = Omit<CapabilityErrorBody, 'retryable'> & {
129
+ retryable?: boolean;
130
+ };
131
+ export declare class CapabilityError extends Error {
132
+ readonly body: CapabilityErrorBody;
133
+ readonly status: number;
134
+ /** `retryable` defaults to true for `rate_limited` and `unavailable`, else false. */
135
+ constructor(init: CapabilityErrorInit);
136
+ }
137
+ /** A definition that breaks a kit rule. Thrown at boot, never per request. */
138
+ export declare class CapabilityDefinitionError extends Error {
139
+ constructor(message: string);
140
+ }
141
+ /** A manifest that fails its shape or hash check. */
142
+ export declare class CapabilityManifestError extends Error {
143
+ constructor(message: string);
144
+ }
145
+ interface FromZodErrorOptions {
146
+ /**
147
+ * Fields that no longer exist, keyed by the old name, valued with what to use
148
+ * instead. An unrecognized key listed here (strict objects only) answers
149
+ * `"value" was removed: use percent_off or amount_off (major units).` with
150
+ * rule `field_renamed`, instead of zod's generic message.
151
+ */
152
+ renamed?: Readonly<Record<string, string>>;
153
+ }
154
+ /**
155
+ * `invalid_input`: `field` is the dotted path of the first issue, `issues` lists
156
+ * all of them. Renamed-field issues (`options.renamed`) come first and make the headline. A custom issue's `params.rule` becomes `body.rule`; see
157
+ * `options.renamed` for steering removed field names.
158
+ */
159
+ export declare function fromZodError(err: z.ZodError, options?: FromZodErrorOptions): CapabilityError;
160
+ //#endregion
161
+ //#region src/idempotency.d.ts
162
+ /**
163
+ * 8–100 of `A-Z a-z 0-9 . _ : -`. The underscore is there because the
164
+ * orchestrator's key is `cap.{runId}.{toolCallId}` and model tool call ids
165
+ * contain one (`toolu_…`, `call_…`).
166
+ */
167
+ export declare const IDEMPOTENCY_KEY_PATTERN: RegExp;
168
+ /** A non-read capability that is not naturally idempotent needs an `Idempotency-Key`. */
169
+ export declare function requiresIdempotencyKey(policy: Pick<CapabilityPolicy, 'effect' | 'idempotent'>): boolean;
170
+ /** sha256 hex of canonicalJson({ id, input }): same key + different fingerprint = a different request. */
171
+ export declare function requestFingerprint(id: CapabilityId, input: unknown): string;
172
+ //#endregion
173
+ //#region src/manifest.d.ts
174
+ interface ManifestEntry {
175
+ id: CapabilityId;
176
+ version: number;
177
+ owner: 'medusa' | 'orchestrator';
178
+ http: CapabilityDefinition['http'];
179
+ input: JsonSchema;
180
+ output: JsonSchema;
181
+ policy: CapabilityPolicy;
182
+ agent: CapabilityAgentSpec;
183
+ }
184
+ interface CapabilityManifest {
185
+ schemaVersion: 1;
186
+ source: 'medusa' | 'orchestrator';
187
+ hash: string;
188
+ capabilities: ManifestEntry[];
189
+ }
190
+ /** Any definition, whatever its input, output and context types. */
191
+ type AnyCapabilityDefinition = Readonly<CapabilityDefinition<unknown, unknown, never>>;
192
+ /**
193
+ * Deterministic: entries sorted by id, schemas converted with
194
+ * `{ target: 'draft-2020-12', io }`, no timestamps. Every definition is checked
195
+ * again (the defineCapability rules). Throws on a duplicate id.
196
+ *
197
+ * The JSON Schema text depends on the zod version that emits it (zod 3.25 writes
198
+ * a discriminated union as anyOf, 4.3 as oneOf), so the hash is only comparable
199
+ * between emits on the SAME zod version. Consumers verify a received manifest
200
+ * with parseManifest; they never re-emit it to compare.
201
+ */
202
+ export declare function emitManifest(defs: readonly AnyCapabilityDefinition[], source: 'medusa' | 'orchestrator'): CapabilityManifest;
203
+ /**
204
+ * Validates the shape, re-runs the definition rules on every entry (tenant ids,
205
+ * open inputs, money sentence …) and recomputes the hash from the JSON as
206
+ * received, never from schemas. Throws CapabilityManifestError on any failure.
207
+ * The hash proves the file is intact, not who wrote it: trust comes from where
208
+ * the file is read (the committed copy), not from the hash.
209
+ */
210
+ export declare function parseManifest(json: unknown): CapabilityManifest;
211
+ //#endregion
212
+ //#region src/money.d.ts
213
+ /** The one unit sentence every money field carries, for the model and for people. */
214
+ export declare const MONEY_UNIT_SENTENCE = "Amount in normal currency units as the merchant says it (99 means 99.00; 1.5 means 1.50). Never cents or minor units.";
215
+ /** JSON Schema keyword marking a money field. */
216
+ export declare const MONEY_META_KEY = "x-amboras-money";
217
+ /**
218
+ * A bad amount or currency code. It is an `invalid_input` CapabilityError, so an
219
+ * adapter that maps CapabilityError answers 400 with the rule (and the field,
220
+ * when the caller passed one) instead of a 500.
221
+ */
222
+ export declare class MoneyError extends CapabilityError {
223
+ constructor(message: string, rule: 'money_precision' | 'money_invalid' | 'currency_invalid', field?: string);
224
+ }
225
+ /**
226
+ * Where the amount's currency comes from. `currencyField` names a field resolved
227
+ * from the INPUT ROOT, at any depth of the money field (`discount.amount_off`
228
+ * with `{ currencyField: 'currency_code' }` reads the top-level `currency_code`).
229
+ */
230
+ type MoneyCurrency = {
231
+ currencyField: string;
232
+ } | {
233
+ currency: 'store';
234
+ };
235
+ type MoneyOptions = MoneyCurrency & {
236
+ /** What the amount is; prepended to the unit sentence. */
237
+ describe?: string;
238
+ /** Strict lower bound (`0` = must be more than zero). Default: amounts are ≥ 0. */
239
+ exclusiveMinimum?: number;
240
+ /** Inclusive upper bound. */
241
+ max?: number;
242
+ };
243
+ /**
244
+ * A major-unit amount: finite, ≥ 0 (or > `exclusiveMinimum`), at most `max`,
245
+ * described with MONEY_UNIT_SENTENCE and marked `x-amboras-money` in JSON Schema.
246
+ *
247
+ * Chained checks (`.positive()`, `.max()`, `.int()`) and `.optional()`,
248
+ * `.nullable()`, `.nullish()` are safe: the kit's JSON Schema keeps the sentence
249
+ * and the marker. `.describe()` replaces the sentence, which `defineCapability`
250
+ * rejects: pass `describe` here instead.
251
+ */
252
+ export declare function money(opts: MoneyOptions): z.ZodNumber;
253
+ /** ISO 4217 minor-unit digits: 0 for JPY/KRW/VND…, 3 for KWD/BHD/OMR/TND/JOD/IQD/LYD, else 2 (also for unknown codes). */
254
+ export declare function fractionDigits(currency: string): 0 | 2 | 3;
255
+ /**
256
+ * Amboras storage rule: ×1000 for the seven three-decimal codes, ×100 for every
257
+ * other code INCLUDING zero-decimal ones (¥5 is stored as 500). By design; see
258
+ * Medusa `divisorFor`.
259
+ */
260
+ export declare function storageScale(currency: string): 100 | 1000;
261
+ /**
262
+ * Major → stored minor integer. Throws MoneyError (`invalid_input`) when the
263
+ * amount has more fraction digits than the currency allows (19.999 USD, 5.5 JPY),
264
+ * is not finite, or does not fit a safe integer. Pass `field` to name the input
265
+ * field in the error.
266
+ *
267
+ * An amount is accepted when its decimal text has at most the allowed digits
268
+ * (exact at any magnitude), or when it is within float noise of such an amount
269
+ * (0.1 + 0.2 → 0.30). Noise is bounded absolutely, so a real extra digit is never
270
+ * rounded away, however large the amount.
271
+ */
272
+ export declare function toStorageAmount(major: number, currency: string, field?: string): number;
273
+ /** Stored minor amount → major units. */
274
+ export declare function fromStorageAmount(minor: number, currency: string): number;
275
+ interface MoneyView {
276
+ amount: number;
277
+ currency: string;
278
+ display: string;
279
+ }
280
+ /** Output schema for a MoneyView (what `moneyView` returns), for capability outputs. */
281
+ export declare const moneyViewSchema: z.ZodType<MoneyView>;
282
+ /**
283
+ * A stored amount as the merchant reads it. Fraction digits are pinned (not left
284
+ * to the runtime's ICU data): ISO digits, widened only to show a stored value
285
+ * that is not a whole minor unit (¥5.5 stays visible).
286
+ */
287
+ export declare function moneyView(minor: number, currency: string): MoneyView;
288
+ //#endregion
289
+ //#region src/policy.d.ts
290
+ /** Phase 1 adapters pass observe/observe. */
291
+ interface PolicyEnforcement {
292
+ role: 'enforce' | 'observe';
293
+ plan: 'enforce' | 'observe';
294
+ }
295
+ /** `observed` holds the rule ids an 'observe' check would have denied, for logging. */
296
+ type PolicyDecision = {
297
+ allow: true;
298
+ observed: string[];
299
+ } | {
300
+ allow: false;
301
+ error: CapabilityError;
302
+ };
303
+ export declare function evaluatePolicy(policy: CapabilityPolicy, ctx: CapabilityCallContext, facts: {
304
+ planFeatures?: string[];
305
+ }, enforcement: PolicyEnforcement): PolicyDecision;
306
+ /**
307
+ * True when the call needs an explicit confirmation before it runs. `records` is
308
+ * how many records it touches. A threshold policy with no count, or a count that
309
+ * is not a non-negative integer, requires confirmation (the adapter could not count).
310
+ */
311
+ export declare function confirmationRequired(policy: Pick<CapabilityPolicy, 'confirm'>, records?: number): boolean;
312
+ //#endregion
313
+ export type { Actor, AnyCapabilityDefinition, Area, CapabilityAgentSpec, CapabilityCallContext, CapabilityDefinition, CapabilityErrorBody, CapabilityErrorCode, CapabilityErrorInit, CapabilityId, CapabilityManifest, CapabilityPolicy, FromZodErrorOptions, JsonSchema, ManifestEntry, MoneyCurrency, MoneyOptions, MoneyView, PolicyDecision, PolicyEnforcement, Role, Surface };
@@ -0,0 +1,313 @@
1
+ import { z } from "zod/v4";
2
+ //#region src/canonical-json.d.ts
3
+ /** Sorted keys, no whitespace. Throws on BigInt and circular values; `undefined` at the top serializes as `null`. */
4
+ export declare function canonicalJson(v: unknown): string;
5
+ //#endregion
6
+ //#region src/ids.d.ts
7
+ export declare const AREAS: readonly ["promotions", "catalog", "inventory", "setup", "content", "orders", "payments", "domains", "publish"];
8
+ type Area = (typeof AREAS)[number];
9
+ /** `<area>.<verb>` or `<area>.<entity>.<verb>`: lowercase segments of `a-z` and `_`. */
10
+ type CapabilityId = `${Area}.${string}`;
11
+ type Role = 'owner' | 'manager' | 'staff';
12
+ /** Higher rank includes every lower one: an owner can do what a manager can. */
13
+ export declare const ROLE_RANK: Readonly<Record<Role, number>>;
14
+ /** True for a well-formed id in a known area. */
15
+ export declare function isCapabilityId(value: unknown): value is CapabilityId;
16
+ /** The area an id belongs to (`promotions.create` → `promotions`). */
17
+ export declare function areaOf(id: CapabilityId): Area;
18
+ //#endregion
19
+ //#region src/context.d.ts
20
+ type Actor = {
21
+ kind: 'member';
22
+ profileId: string;
23
+ role: Role | null;
24
+ } | {
25
+ kind: 'internal';
26
+ service: string;
27
+ };
28
+ /** A label for logs. Never trusted for a policy decision. */
29
+ type Surface = 'ai' | 'admin' | 'api';
30
+ interface CapabilityCallContext {
31
+ tenantId: string;
32
+ actor: Actor;
33
+ surface: Surface;
34
+ runId?: string;
35
+ }
36
+ //#endregion
37
+ //#region src/json-schema.d.ts
38
+ type JsonSchema = {
39
+ [key: string]: unknown;
40
+ };
41
+ /**
42
+ * A copy of `schema` without the hidden fields (`agent.hideFields`), also
43
+ * dropped from `required`. Adapters build the model-facing input with this.
44
+ */
45
+ export declare function omitFields(schema: JsonSchema, fields: readonly string[]): JsonSchema;
46
+ //#endregion
47
+ //#region src/policy-types.d.ts
48
+ interface CapabilityPolicy {
49
+ minRole: Role;
50
+ planFeature?: string;
51
+ effect: 'read' | 'write' | 'destructive';
52
+ /** True = safe to retry without a key (reads, set-to-value). */
53
+ idempotent: boolean;
54
+ /** Grants or moves value: fail closed everywhere. */
55
+ money?: boolean;
56
+ confirm: 'never' | 'always' | {
57
+ when: {
58
+ recordsAtLeast: number;
59
+ };
60
+ };
61
+ rateLimit?: {
62
+ perMinute: number;
63
+ };
64
+ }
65
+ //#endregion
66
+ //#region src/define.d.ts
67
+ interface CapabilityAgentSpec {
68
+ /** What it does and when to use it, written for the model. */
69
+ description: string;
70
+ /** Only what the schema shape cannot carry. */
71
+ rules?: string[];
72
+ examples?: Array<{
73
+ input: unknown;
74
+ note: string;
75
+ }>;
76
+ /** Dotted input paths the model never sees; each must exist and be optional. */
77
+ hideFields?: string[];
78
+ exposure: 'task-tool' | 'long-tail' | 'hidden';
79
+ /** Phase 1: keeps the legacy tool name (e.g. 'create_promotion') so prompts, evals and TOOL_CAPABILITIES keys stay stable. */
80
+ toolName?: string;
81
+ }
82
+ interface CapabilityDefinition<I = unknown, O = unknown, Ctx = unknown> {
83
+ id: CapabilityId;
84
+ /** Integer ≥ 1; bump on a breaking input or output change. */
85
+ version: number;
86
+ owner: 'medusa' | 'orchestrator';
87
+ http: {
88
+ method: 'GET' | 'POST' | 'DELETE';
89
+ path: string;
90
+ runsOn: 'custom' | 'core';
91
+ };
92
+ input: z.ZodType<I>;
93
+ output: z.ZodType<O>;
94
+ run?(ctx: Ctx, input: I): Promise<O>;
95
+ policy: CapabilityPolicy;
96
+ agent: CapabilityAgentSpec;
97
+ }
98
+ /**
99
+ * Checks a definition and returns a frozen copy. Throws CapabilityDefinitionError
100
+ * on: a bad id or unknown area; a version that is not a positive integer; an
101
+ * input that is not an object, accepts undeclared keys or any value, or names a
102
+ * store / tenant / profile / environment id at any depth; runsOn 'custom'
103
+ * without run; hideFields naming a missing (or required) field; a schema JSON
104
+ * Schema cannot express; a money() field without its unit sentence or with a
105
+ * currencyField that is not a string field at the input root; an invalid
106
+ * minRole, confirm threshold, rate limit or toolName.
107
+ */
108
+ export declare function defineCapability<I, O, Ctx = unknown>(def: CapabilityDefinition<I, O, Ctx>): Readonly<CapabilityDefinition<I, O, Ctx>>;
109
+ //#endregion
110
+ //#region src/errors.d.ts
111
+ type CapabilityErrorCode = 'invalid_input' | 'forbidden' | 'not_found' | 'conflict' | 'confirmation_required' | 'rate_limited' | 'idempotency_conflict' | 'unavailable' | 'internal';
112
+ interface CapabilityErrorBody {
113
+ code: CapabilityErrorCode;
114
+ message: string;
115
+ /** Dotted input path the error is about (`discount.amount_off`). */
116
+ field?: string;
117
+ /** Stable id of the business rule that refused (`idempotency_key_required`). */
118
+ rule?: string;
119
+ retryable: boolean;
120
+ issues?: Array<{
121
+ field: string;
122
+ message: string;
123
+ }>;
124
+ /** Small structured facts the caller can act on (duplicate_code → { existing_id, existing_status }). Never secrets or PII. */
125
+ details?: Record<string, string | number | boolean | null>;
126
+ }
127
+ export declare function httpStatusFor(code: CapabilityErrorCode): number;
128
+ type CapabilityErrorInit = Omit<CapabilityErrorBody, 'retryable'> & {
129
+ retryable?: boolean;
130
+ };
131
+ export declare class CapabilityError extends Error {
132
+ readonly body: CapabilityErrorBody;
133
+ readonly status: number;
134
+ /** `retryable` defaults to true for `rate_limited` and `unavailable`, else false. */
135
+ constructor(init: CapabilityErrorInit);
136
+ }
137
+ /** A definition that breaks a kit rule. Thrown at boot, never per request. */
138
+ export declare class CapabilityDefinitionError extends Error {
139
+ constructor(message: string);
140
+ }
141
+ /** A manifest that fails its shape or hash check. */
142
+ export declare class CapabilityManifestError extends Error {
143
+ constructor(message: string);
144
+ }
145
+ interface FromZodErrorOptions {
146
+ /**
147
+ * Fields that no longer exist, keyed by the old name, valued with what to use
148
+ * instead. An unrecognized key listed here (strict objects only) answers
149
+ * `"value" was removed: use percent_off or amount_off (major units).` with
150
+ * rule `field_renamed`, instead of zod's generic message.
151
+ */
152
+ renamed?: Readonly<Record<string, string>>;
153
+ }
154
+ /**
155
+ * `invalid_input`: `field` is the dotted path of the first issue, `issues` lists
156
+ * all of them. Renamed-field issues (`options.renamed`) come first and make the headline. A custom issue's `params.rule` becomes `body.rule`; see
157
+ * `options.renamed` for steering removed field names.
158
+ */
159
+ export declare function fromZodError(err: z.ZodError, options?: FromZodErrorOptions): CapabilityError;
160
+ //#endregion
161
+ //#region src/idempotency.d.ts
162
+ /**
163
+ * 8–100 of `A-Z a-z 0-9 . _ : -`. The underscore is there because the
164
+ * orchestrator's key is `cap.{runId}.{toolCallId}` and model tool call ids
165
+ * contain one (`toolu_…`, `call_…`).
166
+ */
167
+ export declare const IDEMPOTENCY_KEY_PATTERN: RegExp;
168
+ /** A non-read capability that is not naturally idempotent needs an `Idempotency-Key`. */
169
+ export declare function requiresIdempotencyKey(policy: Pick<CapabilityPolicy, 'effect' | 'idempotent'>): boolean;
170
+ /** sha256 hex of canonicalJson({ id, input }): same key + different fingerprint = a different request. */
171
+ export declare function requestFingerprint(id: CapabilityId, input: unknown): string;
172
+ //#endregion
173
+ //#region src/manifest.d.ts
174
+ interface ManifestEntry {
175
+ id: CapabilityId;
176
+ version: number;
177
+ owner: 'medusa' | 'orchestrator';
178
+ http: CapabilityDefinition['http'];
179
+ input: JsonSchema;
180
+ output: JsonSchema;
181
+ policy: CapabilityPolicy;
182
+ agent: CapabilityAgentSpec;
183
+ }
184
+ interface CapabilityManifest {
185
+ schemaVersion: 1;
186
+ source: 'medusa' | 'orchestrator';
187
+ hash: string;
188
+ capabilities: ManifestEntry[];
189
+ }
190
+ /** Any definition, whatever its input, output and context types. */
191
+ type AnyCapabilityDefinition = Readonly<CapabilityDefinition<unknown, unknown, never>>;
192
+ /**
193
+ * Deterministic: entries sorted by id, schemas converted with
194
+ * `{ target: 'draft-2020-12', io }`, no timestamps. Every definition is checked
195
+ * again (the defineCapability rules). Throws on a duplicate id.
196
+ *
197
+ * The JSON Schema text depends on the zod version that emits it (zod 3.25 writes
198
+ * a discriminated union as anyOf, 4.3 as oneOf), so the hash is only comparable
199
+ * between emits on the SAME zod version. Consumers verify a received manifest
200
+ * with parseManifest; they never re-emit it to compare.
201
+ */
202
+ export declare function emitManifest(defs: readonly AnyCapabilityDefinition[], source: 'medusa' | 'orchestrator'): CapabilityManifest;
203
+ /**
204
+ * Validates the shape, re-runs the definition rules on every entry (tenant ids,
205
+ * open inputs, money sentence …) and recomputes the hash from the JSON as
206
+ * received, never from schemas. Throws CapabilityManifestError on any failure.
207
+ * The hash proves the file is intact, not who wrote it: trust comes from where
208
+ * the file is read (the committed copy), not from the hash.
209
+ */
210
+ export declare function parseManifest(json: unknown): CapabilityManifest;
211
+ //#endregion
212
+ //#region src/money.d.ts
213
+ /** The one unit sentence every money field carries, for the model and for people. */
214
+ export declare const MONEY_UNIT_SENTENCE = "Amount in normal currency units as the merchant says it (99 means 99.00; 1.5 means 1.50). Never cents or minor units.";
215
+ /** JSON Schema keyword marking a money field. */
216
+ export declare const MONEY_META_KEY = "x-amboras-money";
217
+ /**
218
+ * A bad amount or currency code. It is an `invalid_input` CapabilityError, so an
219
+ * adapter that maps CapabilityError answers 400 with the rule (and the field,
220
+ * when the caller passed one) instead of a 500.
221
+ */
222
+ export declare class MoneyError extends CapabilityError {
223
+ constructor(message: string, rule: 'money_precision' | 'money_invalid' | 'currency_invalid', field?: string);
224
+ }
225
+ /**
226
+ * Where the amount's currency comes from. `currencyField` names a field resolved
227
+ * from the INPUT ROOT, at any depth of the money field (`discount.amount_off`
228
+ * with `{ currencyField: 'currency_code' }` reads the top-level `currency_code`).
229
+ */
230
+ type MoneyCurrency = {
231
+ currencyField: string;
232
+ } | {
233
+ currency: 'store';
234
+ };
235
+ type MoneyOptions = MoneyCurrency & {
236
+ /** What the amount is; prepended to the unit sentence. */
237
+ describe?: string;
238
+ /** Strict lower bound (`0` = must be more than zero). Default: amounts are ≥ 0. */
239
+ exclusiveMinimum?: number;
240
+ /** Inclusive upper bound. */
241
+ max?: number;
242
+ };
243
+ /**
244
+ * A major-unit amount: finite, ≥ 0 (or > `exclusiveMinimum`), at most `max`,
245
+ * described with MONEY_UNIT_SENTENCE and marked `x-amboras-money` in JSON Schema.
246
+ *
247
+ * Chained checks (`.positive()`, `.max()`, `.int()`) and `.optional()`,
248
+ * `.nullable()`, `.nullish()` are safe: the kit's JSON Schema keeps the sentence
249
+ * and the marker. `.describe()` replaces the sentence, which `defineCapability`
250
+ * rejects: pass `describe` here instead.
251
+ */
252
+ export declare function money(opts: MoneyOptions): z.ZodNumber;
253
+ /** ISO 4217 minor-unit digits: 0 for JPY/KRW/VND…, 3 for KWD/BHD/OMR/TND/JOD/IQD/LYD, else 2 (also for unknown codes). */
254
+ export declare function fractionDigits(currency: string): 0 | 2 | 3;
255
+ /**
256
+ * Amboras storage rule: ×1000 for the seven three-decimal codes, ×100 for every
257
+ * other code INCLUDING zero-decimal ones (¥5 is stored as 500). By design; see
258
+ * Medusa `divisorFor`.
259
+ */
260
+ export declare function storageScale(currency: string): 100 | 1000;
261
+ /**
262
+ * Major → stored minor integer. Throws MoneyError (`invalid_input`) when the
263
+ * amount has more fraction digits than the currency allows (19.999 USD, 5.5 JPY),
264
+ * is not finite, or does not fit a safe integer. Pass `field` to name the input
265
+ * field in the error.
266
+ *
267
+ * An amount is accepted when its decimal text has at most the allowed digits
268
+ * (exact at any magnitude), or when it is within float noise of such an amount
269
+ * (0.1 + 0.2 → 0.30). Noise is bounded absolutely, so a real extra digit is never
270
+ * rounded away, however large the amount.
271
+ */
272
+ export declare function toStorageAmount(major: number, currency: string, field?: string): number;
273
+ /** Stored minor amount → major units. */
274
+ export declare function fromStorageAmount(minor: number, currency: string): number;
275
+ interface MoneyView {
276
+ amount: number;
277
+ currency: string;
278
+ display: string;
279
+ }
280
+ /** Output schema for a MoneyView (what `moneyView` returns), for capability outputs. */
281
+ export declare const moneyViewSchema: z.ZodType<MoneyView>;
282
+ /**
283
+ * A stored amount as the merchant reads it. Fraction digits are pinned (not left
284
+ * to the runtime's ICU data): ISO digits, widened only to show a stored value
285
+ * that is not a whole minor unit (¥5.5 stays visible).
286
+ */
287
+ export declare function moneyView(minor: number, currency: string): MoneyView;
288
+ //#endregion
289
+ //#region src/policy.d.ts
290
+ /** Phase 1 adapters pass observe/observe. */
291
+ interface PolicyEnforcement {
292
+ role: 'enforce' | 'observe';
293
+ plan: 'enforce' | 'observe';
294
+ }
295
+ /** `observed` holds the rule ids an 'observe' check would have denied, for logging. */
296
+ type PolicyDecision = {
297
+ allow: true;
298
+ observed: string[];
299
+ } | {
300
+ allow: false;
301
+ error: CapabilityError;
302
+ };
303
+ export declare function evaluatePolicy(policy: CapabilityPolicy, ctx: CapabilityCallContext, facts: {
304
+ planFeatures?: string[];
305
+ }, enforcement: PolicyEnforcement): PolicyDecision;
306
+ /**
307
+ * True when the call needs an explicit confirmation before it runs. `records` is
308
+ * how many records it touches. A threshold policy with no count, or a count that
309
+ * is not a non-negative integer, requires confirmation (the adapter could not count).
310
+ */
311
+ export declare function confirmationRequired(policy: Pick<CapabilityPolicy, 'confirm'>, records?: number): boolean;
312
+ //#endregion
313
+ export type { Actor, AnyCapabilityDefinition, Area, CapabilityAgentSpec, CapabilityCallContext, CapabilityDefinition, CapabilityErrorBody, CapabilityErrorCode, CapabilityErrorInit, CapabilityId, CapabilityManifest, CapabilityPolicy, FromZodErrorOptions, JsonSchema, ManifestEntry, MoneyCurrency, MoneyOptions, MoneyView, PolicyDecision, PolicyEnforcement, Role, Surface };