@vxil/cli 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/_vxil-feature-configs-apiState.d.ts +181 -0
- package/dist/_vxil-feature-configs-canonicalJson.d.ts +7 -0
- package/dist/_vxil-feature-configs-readmodels.d.ts +38 -0
- package/dist/_vxil-feature-configs.d.ts +65 -4
- package/dist/config.d.ts +33 -0
- package/dist/vxil.js +4174 -1488
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -39,7 +39,9 @@ export default defineConfig({
|
|
|
39
39
|
});
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Verbs: `init templates quickstart try login link keys plan push pull diff gen doctor architect versions rollback secrets env seed export import functions listen mcp cms dev payments migrate config users send deliveries api`.
|
|
42
|
+
Verbs: `init templates quickstart try login link keys account logout invites members projects billing plan push pull diff gen doctor architect versions rollback secrets env seed export import functions listen mcp cms files webhooks search dev payments migrate config users send deliveries api`.
|
|
43
|
+
|
|
44
|
+
`vxil quickstart [--features a,b] [--invite <code>] [--env <label>] [--dev [--ttl <h>]] [--no-push]` is the front door; `vxil api --help` prints the operator cheat-sheet — every dashboard action as one `vxil api` line.
|
|
43
45
|
|
|
44
46
|
**Every verb names its target.** `--dev` selects the dev slot, `--target <name>` a named slot bound by `vxil link <slug> --as <name>` (which merges into the existing binding rather than replacing it). The selector is **fail-closed** — an unbound slot exits 1 telling you the fix, and never falls through to another tenant — and every write verb prints a target banner (slug + env, never a key) before it acts.
|
|
45
47
|
|
|
@@ -50,6 +52,10 @@ Verbs: `init templates quickstart try login link keys plan push pull diff gen do
|
|
|
50
52
|
- `vxil link <slug>` — reuses the key already stored for the slug on this machine; `--mint` for a fresh one, `--key` to hand one in.
|
|
51
53
|
- `vxil keys mint --name <name> --scopes <a,b,c> [--env live|test] [--store]` — mint a **narrow** key for a server (scopes validated locally; `admin` refused); the key prints **once** to stdout (`--json` → `{ api_key, key_id, scopes }`) and is never stored unless `--store`. `vxil keys list` shows names, ids, scopes, created and last-used (never secrets); `vxil keys revoke <key_id> [--yes]` asks you to type the id, and refuses the key the CLI itself is using unless `--yes`. Needs a dashboard session (`vxil login`, or it prompts at a terminal; a piped / `--json` run fails with that remedy instead — pass `--email`/`--password` to script it, which never stores the session).
|
|
52
54
|
- `vxil cms drop <collection> [--yes]` — remove one collection definition through the same guarded ack `push --allow-destructive` uses (rows are retained).
|
|
55
|
+
- `vxil members list|invite|add|rm|uninvite` and `vxil invites accept <token|url>` — the bound project's team from the terminal (your `vxil login` session): invite by e-mail with a role, add an existing account (also how you change a role), remove (typed e-mail, refuses the last owner), revoke a pending invitation, accept one.
|
|
56
|
+
- `vxil projects list|create|rm` and `vxil billing status|upgrade --tier <t>` — your projects and the bound project's plan. `projects rm` deletes any project you own after you type its slug; `billing upgrade` prints the hosted checkout URL (never opens a browser) and also downgrades (`--tier free`).
|
|
57
|
+
- `vxil files put <path> --user <id>`, `vxil webhooks sources add|list|rm|test`, `vxil webhooks events replay|runs`, `vxil cms bulk-rm <collection> --filter '<json>' [--dry-run]`, `vxil search sync run|reset <key> [--purge]`, `vxil users import <file>` — the operator actions where the CLI adds something a raw call cannot (bytes from disk, a print-once receiver URL, a dry run before a typed confirm, a chunked bulk). Everything else the dashboard does per feature is one `vxil api` line: `vxil api --help`.
|
|
58
|
+
- `vxil gen --offline --check` ignores the pinned API majors (only the online generator knows them), so an online-generated `vxil.types.ts` passes the offline gate.
|
|
53
59
|
- `vxil api <METHOD> <path> ['<json>' | --data '<json>']` — call any `/v1` route; bare JSON on stdout, banner on stderr.
|
|
54
60
|
- `vxil secrets list --all-projects` — the names-and-metadata matrix across every bound project (values never come back).
|
|
55
61
|
- `vxil migrate payments --from-provider stripe|paddle|paypal|revenuecat (--customers <f.csv> | --all) [--dry-run]` — backfill subscriptions through the server sync leg; resumable.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { DeclaredRlPolicy, DeclaredWebhookSubscription } from './_vxil-feature-configs.js';
|
|
2
|
+
/** The target_url path markers of the platform's function-delivery lanes
|
|
3
|
+
* (control-plane fnCmsHooks.ts FN_CMSHOOK_PATH / FN_AUTHHOOK_PATH /
|
|
4
|
+
* FN_WEBHOOK_TRIGGER_PATH — mirrored here because this package is
|
|
5
|
+
* typebox-only). A subscription on one of them is derived from the functions
|
|
6
|
+
* manifest by the deploy reconciler: never declarable, never "undeclared". */
|
|
7
|
+
export declare const FN_TRIGGER_TARGET_MARKERS: readonly ["/v1/internal/fn/cms-hook/", "/v1/internal/fn/auth-hook/", "/v1/internal/fn/trigger/"];
|
|
8
|
+
export declare function isFnTriggerSubscriptionUrl(targetUrl: string): boolean;
|
|
9
|
+
export type ApiStateDatum = 'policies' | 'subscriptions' | 'templates';
|
|
10
|
+
/** feature → the declared-API-state datum its config block may carry. */
|
|
11
|
+
export declare const API_STATE_DATUMS: ReadonlyArray<{
|
|
12
|
+
feature: string;
|
|
13
|
+
datum: ApiStateDatum;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function apiStateDatumOf(feature: string): ApiStateDatum | null;
|
|
16
|
+
/** The declared list a validated manifest carries for its feature's datum, or
|
|
17
|
+
* null when the feature has no datum / the block is absent. An absent block
|
|
18
|
+
* means "not managed by config" (nothing converges, nothing is reported) —
|
|
19
|
+
* distinct from an EMPTY array, which declares "there should be none". */
|
|
20
|
+
export declare function declaredApiState(feature: string, manifest: Record<string, unknown> | null | undefined): {
|
|
21
|
+
datum: ApiStateDatum;
|
|
22
|
+
declared: unknown[];
|
|
23
|
+
} | null;
|
|
24
|
+
/** ONE row of GET /v1/rate-limits/policies (rate-limits-v1 core.ts Policy). */
|
|
25
|
+
export interface LiveRlPolicy {
|
|
26
|
+
policy_id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
key_template: string;
|
|
29
|
+
limit: number;
|
|
30
|
+
window_seconds: number;
|
|
31
|
+
behavior: string;
|
|
32
|
+
/** absent on a policy stored before the token-bucket pass → 'sliding_window' */
|
|
33
|
+
algorithm?: string;
|
|
34
|
+
}
|
|
35
|
+
/** The PUT /v1/rate-limits/policies/:id body the converge sends (core.ts
|
|
36
|
+
* UpdateBody minus `name`, which is the key). */
|
|
37
|
+
export interface RlPolicyPatch {
|
|
38
|
+
limit?: number;
|
|
39
|
+
window_seconds?: number;
|
|
40
|
+
behavior?: 'block' | 'shape';
|
|
41
|
+
algorithm?: 'sliding_window' | 'token_bucket';
|
|
42
|
+
}
|
|
43
|
+
export declare const RL_DEFAULT_BEHAVIOR: "block";
|
|
44
|
+
export declare const RL_DEFAULT_ALGORITHM: "sliding_window";
|
|
45
|
+
export interface RlPolicyPlan {
|
|
46
|
+
create: DeclaredRlPolicy[];
|
|
47
|
+
update: Array<{
|
|
48
|
+
policy_id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
patch: RlPolicyPatch;
|
|
51
|
+
changed: string[];
|
|
52
|
+
}>;
|
|
53
|
+
/** `key_template` differs — the update route cannot change it, so the only
|
|
54
|
+
* fix is delete+recreate (a NEW policy_id): destructive-shaped. */
|
|
55
|
+
recreate: Array<{
|
|
56
|
+
policy_id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
declared: DeclaredRlPolicy;
|
|
59
|
+
}>;
|
|
60
|
+
unchanged: string[];
|
|
61
|
+
/** live rows the config does not declare (left in place by default) */
|
|
62
|
+
undeclared: Array<{
|
|
63
|
+
policy_id: string;
|
|
64
|
+
name: string;
|
|
65
|
+
}>;
|
|
66
|
+
/** a declared name matched by MORE THAN ONE live row — names are not a
|
|
67
|
+
* unique key on the route, so the converge cannot pick; nothing is touched */
|
|
68
|
+
ambiguous: Array<{
|
|
69
|
+
name: string;
|
|
70
|
+
count: number;
|
|
71
|
+
}>;
|
|
72
|
+
}
|
|
73
|
+
export declare function planRlPolicies(declared: readonly DeclaredRlPolicy[], live: readonly LiveRlPolicy[]): RlPolicyPlan;
|
|
74
|
+
/** ONE row of GET /v1/webhooks/subscriptions (state 'deleted' never listed). */
|
|
75
|
+
export interface LiveWebhookSubscription {
|
|
76
|
+
sub_id: string;
|
|
77
|
+
target_url: string;
|
|
78
|
+
event_prefixes: string[];
|
|
79
|
+
state?: string;
|
|
80
|
+
}
|
|
81
|
+
export interface WebhookSubscriptionPlan {
|
|
82
|
+
create: DeclaredWebhookSubscription[];
|
|
83
|
+
/** the prefix SET differs — `PATCH /v1/webhooks/subscriptions/:subId
|
|
84
|
+
* { event_prefixes }` (2026-09-25) changes it in place: the sub_id and the
|
|
85
|
+
* cursor are kept, so this is an ordinary update, not destructive-shaped
|
|
86
|
+
* (before 2026-09-25 it was delete+recreate under --allow-destructive). */
|
|
87
|
+
update: Array<{
|
|
88
|
+
sub_id: string;
|
|
89
|
+
target_url: string;
|
|
90
|
+
event_prefixes: string[];
|
|
91
|
+
}>;
|
|
92
|
+
unchanged: string[];
|
|
93
|
+
/** live rows the config does not declare, EXCLUDING the function-delivery
|
|
94
|
+
* lanes (those derive from the functions manifest) — left in place by default */
|
|
95
|
+
undeclared: Array<{
|
|
96
|
+
sub_id: string;
|
|
97
|
+
target_url: string;
|
|
98
|
+
}>;
|
|
99
|
+
}
|
|
100
|
+
export declare function planWebhookSubscriptions(declared: readonly DeclaredWebhookSubscription[], live: readonly LiveWebhookSubscription[]): WebhookSubscriptionPlan;
|
|
101
|
+
/** ONE row of GET /v1/ai/templates. `content_sha256` is the canonical hash of
|
|
102
|
+
* the LATEST version's {system,user,schema} (@vxil/runtime
|
|
103
|
+
* aiTemplateContentSha256), stamped by ai-v1 since 2026-09-23; absent on an
|
|
104
|
+
* older server. */
|
|
105
|
+
export interface LiveAiTemplate {
|
|
106
|
+
name: string;
|
|
107
|
+
latest_version: number;
|
|
108
|
+
content_sha256?: string;
|
|
109
|
+
}
|
|
110
|
+
/** A declared template with its content hash pre-computed by the caller (the
|
|
111
|
+
* hash is async WebCrypto; this planner stays pure/sync). */
|
|
112
|
+
export interface HashedDeclaredAiTemplate {
|
|
113
|
+
template: string;
|
|
114
|
+
content_sha256: string;
|
|
115
|
+
}
|
|
116
|
+
export interface AiTemplatePlan {
|
|
117
|
+
create: string[];
|
|
118
|
+
/** content differs → POST a new version (monotonic per name) */
|
|
119
|
+
update: string[];
|
|
120
|
+
unchanged: string[];
|
|
121
|
+
/** stored names the config does not declare — there is NO delete route for
|
|
122
|
+
* templates, so these are only ever reported, never removed */
|
|
123
|
+
undeclared: string[];
|
|
124
|
+
/** live rows with no `content_sha256` (an older server): the content cannot
|
|
125
|
+
* be compared, so each is re-put as `update` and named here */
|
|
126
|
+
unknownContent: string[];
|
|
127
|
+
}
|
|
128
|
+
export declare function planAiTemplates(declared: readonly HashedDeclaredAiTemplate[], live: readonly LiveAiTemplate[]): AiTemplatePlan;
|
|
129
|
+
export interface ApiStateSummary {
|
|
130
|
+
datum: ApiStateDatum;
|
|
131
|
+
created: string[];
|
|
132
|
+
updated: string[];
|
|
133
|
+
/** destructive-shaped changes APPLIED under allow-destructive (delete+recreate) */
|
|
134
|
+
recreated: string[];
|
|
135
|
+
unchanged: string[];
|
|
136
|
+
/** live rows the config does not declare — left in place (or, under
|
|
137
|
+
* allow-destructive, listed in `deleted` instead) */
|
|
138
|
+
undeclared: string[];
|
|
139
|
+
/** undeclared rows DELETED under allow-destructive */
|
|
140
|
+
deleted: string[];
|
|
141
|
+
/** destructive-shaped changes NOT applied (need allow-destructive) */
|
|
142
|
+
pending_destructive: string[];
|
|
143
|
+
/** declared keys the converge could not resolve (duplicate live rows) */
|
|
144
|
+
ambiguous: string[];
|
|
145
|
+
/** rows a route refused (a 4xx) — the converge continues past them */
|
|
146
|
+
failed: Array<{
|
|
147
|
+
key: string;
|
|
148
|
+
error: string;
|
|
149
|
+
}>;
|
|
150
|
+
}
|
|
151
|
+
export declare function emptyApiStateSummary(datum: ApiStateDatum): ApiStateSummary;
|
|
152
|
+
/** The `api_state` object a config-write response carries (`PUT /v1/config/
|
|
153
|
+
* :feature` and its dashboard twin, dry-run included — control-plane
|
|
154
|
+
* handlers/apiState.ts apiStateResponseField) and the shape `vxil push` /
|
|
155
|
+
* `vxil plan` print. `ok:false` + `deferred:true` = not converged for an
|
|
156
|
+
* INDEFINITE reason — the feature worker has not seen the config yet, or a
|
|
157
|
+
* transient failure (a push asks again through POST /v1/apply after its
|
|
158
|
+
* wait); `ok:false` otherwise = a DEFINITE, named refusal (nothing to retry);
|
|
159
|
+
* `note` = converged nothing on purpose (a disabled feature). */
|
|
160
|
+
export interface ApiStateResponseField extends ApiStateSummary {
|
|
161
|
+
ok: boolean;
|
|
162
|
+
dry_run?: boolean;
|
|
163
|
+
deferred?: boolean;
|
|
164
|
+
error?: string;
|
|
165
|
+
note?: string;
|
|
166
|
+
}
|
|
167
|
+
/** The note every executor reports for a DISABLED feature whose block still
|
|
168
|
+
* declares rows: the declaration is kept in the manifest and nothing is
|
|
169
|
+
* converged (a parked feature is not a propagation window — review finding 2). */
|
|
170
|
+
export declare const API_STATE_DISABLED_NOTE = "feature disabled \u2014 declaration kept, not converged";
|
|
171
|
+
/** `GET /v1/rate-limits/policies` is a 100-row HARD cap with no cursor. A list
|
|
172
|
+
* AT the cap is not a complete live set, so planning creates against it would
|
|
173
|
+
* re-POST every declared name the cap hid (review finding 5). The server's
|
|
174
|
+
* converge and the CLI's read-only plan refuse with this message instead. */
|
|
175
|
+
export declare function rlPolicyListCapError(liveCount: number): string | null;
|
|
176
|
+
export declare const RL_POLICY_LIST_CAP = 100;
|
|
177
|
+
/** `+2 created · ~1 updated · =3 unchanged · 1 undeclared (left in place)` —
|
|
178
|
+
* the one-line summary `vxil push` prints and the apply journal notes. */
|
|
179
|
+
export declare function formatApiStateSummary(s: ApiStateSummary): string;
|
|
180
|
+
/** The multi-line detail under the summary: one line per named row. */
|
|
181
|
+
export declare function formatApiStateDetail(s: ApiStateSummary): string[];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** JSON.stringify with object keys sorted at every depth (arrays keep their
|
|
2
|
+
* order; undefined-valued keys are dropped, as JSON.stringify drops them).
|
|
3
|
+
* Two values with equal output are equal in CONTENT. A stored manifest comes
|
|
4
|
+
* back from storage with its object keys re-ordered, so every declared-vs-
|
|
5
|
+
* stored config compare (the server's shallowDiff, the CLI's diffManifest)
|
|
6
|
+
* must go through this, never through plain JSON.stringify. */
|
|
7
|
+
export declare function canonicalJson(v: unknown): string | undefined;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare const READ_MODEL_LIMITS: {
|
|
2
|
+
readonly maxReadModels: 20;
|
|
3
|
+
readonly maxAggregates: 4;
|
|
4
|
+
readonly maxGroupBy: 2;
|
|
5
|
+
readonly maxWindowDays: 366;
|
|
6
|
+
readonly maxSpecBytes: 4096;
|
|
7
|
+
readonly maxCdcRules: 25;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Structural validation of a declared read-model `spec` — shared by the
|
|
11
|
+
* config-write gate here and re-exported for anyone needing the pure check.
|
|
12
|
+
* `kind` selects the §3.1 (aggregate) or §4.1 (rank) grammar.
|
|
13
|
+
*/
|
|
14
|
+
export declare function validateReadModelSpecShape(where: string, kind: 'aggregate' | 'rank', spec: unknown): string[];
|
|
15
|
+
/** Shape of one declared read-model (mirrors the CmsConfigSchema Record). */
|
|
16
|
+
interface ReadModelDefLike {
|
|
17
|
+
collection?: string;
|
|
18
|
+
kind?: string;
|
|
19
|
+
spec?: unknown;
|
|
20
|
+
materialize?: {
|
|
21
|
+
cron?: string;
|
|
22
|
+
to?: string;
|
|
23
|
+
};
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Cross-field rule for CmsConfig.readModels (called from validateFeatureConfig). */
|
|
27
|
+
export declare function validateReadModelsConfig(readModels: Record<string, ReadModelDefLike> | undefined): string[];
|
|
28
|
+
/** Shape of one CDC rule (mirrors the CmsConfigSchema Record). */
|
|
29
|
+
interface CdcRuleLike {
|
|
30
|
+
collection?: string;
|
|
31
|
+
channel?: string;
|
|
32
|
+
events?: unknown;
|
|
33
|
+
payload?: unknown;
|
|
34
|
+
enabled?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** Cross-field rule for CmsConfig.cdc (called from validateFeatureConfig). */
|
|
37
|
+
export declare function validateCdcConfig(cdc: Record<string, CdcRuleLike> | undefined): string[];
|
|
38
|
+
export {};
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { type Static, type TSchema } from '@sinclair/typebox';
|
|
2
2
|
export * from './_vxil-feature-configs-hooks.js';
|
|
3
|
-
export * from './readmodels.js';
|
|
4
|
-
export * from './apiState.js';
|
|
3
|
+
export * from './_vxil-feature-configs-readmodels.js';
|
|
4
|
+
export * from './_vxil-feature-configs-apiState.js';
|
|
5
|
+
export * from './_vxil-feature-configs-canonicalJson.js';
|
|
5
6
|
export declare const RESERVED_CREDIT_TYPES: ReadonlySet<string>;
|
|
7
|
+
/** F33 (2026-09-25): a function binding's `retry.maxAttempts` ceiling, and the
|
|
8
|
+
* binding kinds that may carry `retry` — the platform-delivered event lanes
|
|
9
|
+
* (an http invoke returns its own status; a cron tick's retry would overlap
|
|
10
|
+
* the next tick). Read by the schema, the deploy clamp and the CLI. */
|
|
11
|
+
export declare const FN_RETRY_MAX_ATTEMPTS = 5;
|
|
12
|
+
export declare const FN_RETRY_BINDING_KINDS: ReadonlySet<string>;
|
|
6
13
|
/** True when a credit_type is a vxil-COGS reserved type (grant/consume/config
|
|
7
14
|
* of which is restricted to internal platform machinery). */
|
|
8
15
|
export declare function isReservedCreditType(creditType: string): boolean;
|
|
@@ -38,6 +45,10 @@ export declare function validateNotificationOverrides(templates: {
|
|
|
38
45
|
text?: string;
|
|
39
46
|
}>>;
|
|
40
47
|
} | undefined): string[];
|
|
48
|
+
/** `notifications.ses.snsTopicArn` grammar: a plain SNS topic ARN (the
|
|
49
|
+
* `aws-cn` / `aws-us-gov` partitions included). Mirrored by SNS_TOPIC_ARN_RE
|
|
50
|
+
* in the notifications worker's snsIntake.ts. */
|
|
51
|
+
export declare const SNS_TOPIC_ARN_PATTERN = "^arn:aws(-[a-z]+)?:sns:[a-z0-9-]+:\\d{12}:[A-Za-z0-9_-]{1,256}$";
|
|
41
52
|
/** AWS region grammar for `notifications.ses.region` (`us-east-1`,
|
|
42
53
|
* `eu-central-1`, `ap-southeast-2`, `us-gov-west-1`, …): two-letter partition,
|
|
43
54
|
* one or more lowercase words, a single digit. Pinned as a pattern rather than
|
|
@@ -56,6 +67,7 @@ export declare const NotificationsConfigSchema: import("@sinclair/typebox").TObj
|
|
|
56
67
|
region: import("@sinclair/typebox").TString;
|
|
57
68
|
accessKeyIdRef: import("@sinclair/typebox").TString;
|
|
58
69
|
secretAccessKeyRef: import("@sinclair/typebox").TString;
|
|
70
|
+
snsTopicArn: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
59
71
|
}>>;
|
|
60
72
|
defaultLocale: import("@sinclair/typebox").TString;
|
|
61
73
|
retry: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
@@ -104,6 +116,49 @@ export declare const NotificationsConfigSchema: import("@sinclair/typebox").TObj
|
|
|
104
116
|
export type NotificationsConfig = Static<typeof NotificationsConfigSchema>;
|
|
105
117
|
/** The §11b.5 broadcast bag as persisted (present ⇒ leaf defaults applied). */
|
|
106
118
|
export type BroadcastConfig = NonNullable<NotificationsConfig['broadcast']>;
|
|
119
|
+
/** Generation-lifecycle config defaults (jobs.md §11 / §5 ≤15-flag budget). */
|
|
120
|
+
export declare const GENERATION_DEFAULTS: {
|
|
121
|
+
/** per-tenant in-flight generation cap (separate budget from queue jobs) */
|
|
122
|
+
readonly maxConcurrent: 20;
|
|
123
|
+
/** default expiry/timeout when the descriptor omits one — 5 min */
|
|
124
|
+
readonly defaultTimeoutMs: number;
|
|
125
|
+
/** hard ceiling a tenant-supplied timeout is clamped to — 1 h */
|
|
126
|
+
readonly maxTimeoutMs: number;
|
|
127
|
+
/** poll-mode: how many poll cycles before giving up (→ terminal-fail) */
|
|
128
|
+
readonly pollMaxAttempts: 60;
|
|
129
|
+
/** MANDATORY per-hold cap on a `reserve_credits.amount` (clamped, never rejected) */
|
|
130
|
+
readonly maxReserveCredits: 1000;
|
|
131
|
+
/** MANDATORY per-tenant ceiling on the sum of un-settled provisional holds */
|
|
132
|
+
readonly maxOutstandingReserveCredits: 100000;
|
|
133
|
+
};
|
|
134
|
+
/** The inclusive [min, max] each `generation` leaf is clamped to — by the
|
|
135
|
+
* schema at config-write time and by jobs-v1 at read time. */
|
|
136
|
+
export declare const GENERATION_BOUNDS: {
|
|
137
|
+
readonly maxConcurrent: {
|
|
138
|
+
readonly min: 1;
|
|
139
|
+
readonly max: 200;
|
|
140
|
+
};
|
|
141
|
+
readonly defaultTimeoutMs: {
|
|
142
|
+
readonly min: 1000;
|
|
143
|
+
readonly max: 3600000;
|
|
144
|
+
};
|
|
145
|
+
readonly maxTimeoutMs: {
|
|
146
|
+
readonly min: 1000;
|
|
147
|
+
readonly max: 3600000;
|
|
148
|
+
};
|
|
149
|
+
readonly pollMaxAttempts: {
|
|
150
|
+
readonly min: 1;
|
|
151
|
+
readonly max: 1000;
|
|
152
|
+
};
|
|
153
|
+
readonly maxReserveCredits: {
|
|
154
|
+
readonly min: 1;
|
|
155
|
+
readonly max: 1000000;
|
|
156
|
+
};
|
|
157
|
+
readonly maxOutstandingReserveCredits: {
|
|
158
|
+
readonly min: 1;
|
|
159
|
+
readonly max: 100000000;
|
|
160
|
+
};
|
|
161
|
+
};
|
|
107
162
|
export declare const JobsConfigSchema: import("@sinclair/typebox").TObject<{
|
|
108
163
|
enabled: import("@sinclair/typebox").TBoolean;
|
|
109
164
|
retry: import("@sinclair/typebox").TObject<{
|
|
@@ -614,11 +669,14 @@ export declare const PaymentsConfigSchema: import("@sinclair/typebox").TObject<{
|
|
|
614
669
|
trialDays: import("@sinclair/typebox").TInteger;
|
|
615
670
|
}>;
|
|
616
671
|
ledger: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
617
|
-
productMap: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
|
|
672
|
+
productMap: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
|
|
618
673
|
creditType: import("@sinclair/typebox").TString;
|
|
619
674
|
amount: import("@sinclair/typebox").TInteger;
|
|
620
675
|
period: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"once">, import("@sinclair/typebox").TLiteral<"monthly">, import("@sinclair/typebox").TLiteral<"annual">]>;
|
|
621
|
-
}
|
|
676
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
677
|
+
tier: import("@sinclair/typebox").TString;
|
|
678
|
+
durationDays: import("@sinclair/typebox").TInteger;
|
|
679
|
+
}>]>>;
|
|
622
680
|
tierMap: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
|
|
623
681
|
entitlements: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>;
|
|
624
682
|
quotas: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TInteger>;
|
|
@@ -657,6 +715,9 @@ export declare const FunctionsConfigSchema: import("@sinclair/typebox").TObject<
|
|
|
657
715
|
source: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
658
716
|
collection: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
659
717
|
event: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
718
|
+
retry: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
719
|
+
maxAttempts: import("@sinclair/typebox").TInteger;
|
|
720
|
+
}>>;
|
|
660
721
|
}>>>;
|
|
661
722
|
scriptRef: import("@sinclair/typebox").TString;
|
|
662
723
|
scopes: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
package/dist/config.d.ts
CHANGED
|
@@ -106,6 +106,26 @@ export interface CollectionDef {
|
|
|
106
106
|
* session.revoked — auth.session.revoked { user_id, session_id, reason }
|
|
107
107
|
* signin.failure — auth.signin.failure { email_hash | user_id, reason } */
|
|
108
108
|
export type AuthHookEvent = 'user.created' | 'session.created' | 'session.revoked' | 'signin.failure';
|
|
109
|
+
/** The opt-in re-delivery of a platform-delivered trigger (F33, 2026-09-25).
|
|
110
|
+
* DEFAULT OFF: the platform acknowledges every delivery with a 200 whatever
|
|
111
|
+
* the handler returned — an application error is observed (`functions.run.failed`),
|
|
112
|
+
* never re-delivered. With `retry` set on a `queue` / `webhook` / `cmsHook` /
|
|
113
|
+
* `authHook` trigger, a non-2xx answer of the handler is handed back to the
|
|
114
|
+
* jobs retry ladder instead (60 s, 120 s, … backoff) and the last failed
|
|
115
|
+
* attempt dead-letters the run (`job.dead_lettered`, replayable from the
|
|
116
|
+
* Jobs page). Each attempt is a billed invocation and carries the SAME
|
|
117
|
+
* `idempotency_key` — dedupe on it. Effective attempts =
|
|
118
|
+
* min(maxAttempts, the project's `jobs.retry.defaultMaxAttempts`, default 5).
|
|
119
|
+
* A handler that partially writes and then fails re-fires its downstream
|
|
120
|
+
* events on EVERY attempt, so the chain is a tree (up to maxAttempts children
|
|
121
|
+
* per hop): the causal-depth guard bounds its length (32 hops), not its size —
|
|
122
|
+
* the breaker, the loop guard, the dead-letter quota and the daily share do.
|
|
123
|
+
* Write only once you will return 2xx, or dedupe on idempotency_key. Not accepted on `http` (it returns its
|
|
124
|
+
* real status to its caller) or `cron` (a retry would overlap the next tick). */
|
|
125
|
+
export interface FunctionTriggerRetry {
|
|
126
|
+
/** 1–5 attempts in all (1 = a failed attempt dead-letters at once, no re-delivery) */
|
|
127
|
+
maxAttempts: number;
|
|
128
|
+
}
|
|
109
129
|
/** A function trigger (the §7.3 crossing). cmsHook fires on a CMS write;
|
|
110
130
|
* authHook fires on ONE auth lifecycle event (default `user.created` — the
|
|
111
131
|
* post-signup hook; at-least-once, ~1min fanout latency). */
|
|
@@ -118,6 +138,7 @@ export type FunctionTrigger = {
|
|
|
118
138
|
} | {
|
|
119
139
|
kind: 'queue';
|
|
120
140
|
source: string;
|
|
141
|
+
retry?: FunctionTriggerRetry;
|
|
121
142
|
}
|
|
122
143
|
/** `webhook`: the function is woken by a webhook_subscription fan-out whose
|
|
123
144
|
* target_url is `<edge>/v1/internal/fn/trigger/<name>?tenant=<id>`. That
|
|
@@ -132,6 +153,7 @@ export type FunctionTrigger = {
|
|
|
132
153
|
| {
|
|
133
154
|
kind: 'webhook';
|
|
134
155
|
source: string;
|
|
156
|
+
retry?: FunctionTriggerRetry;
|
|
135
157
|
}
|
|
136
158
|
/** `cmsHook`: fires after a CMS write in `collection`. The platform filters
|
|
137
159
|
* deliveries on the binding (2026-09-19): `beforeCreate` → created only,
|
|
@@ -142,15 +164,26 @@ export type FunctionTrigger = {
|
|
|
142
164
|
kind: 'cmsHook';
|
|
143
165
|
collection: string;
|
|
144
166
|
event: 'beforeCreate' | 'beforeUpdate' | 'beforeWrite';
|
|
167
|
+
retry?: FunctionTriggerRetry;
|
|
145
168
|
} | {
|
|
146
169
|
kind: 'authHook';
|
|
147
170
|
event?: AuthHookEvent;
|
|
171
|
+
retry?: FunctionTriggerRetry;
|
|
148
172
|
};
|
|
149
173
|
/** A deployed tenant function — source in functions/, deployed on `vxil push`. */
|
|
150
174
|
export interface FunctionDef {
|
|
151
175
|
/** path to the ES-module entrypoint, e.g. ./functions/<name>.ts */
|
|
152
176
|
entry: string;
|
|
153
177
|
trigger?: FunctionTrigger;
|
|
178
|
+
/** Further bindings beside `trigger` (2026-09-25): a function is code + a SET
|
|
179
|
+
* of trigger bindings, so one bundle can be reached by several front doors —
|
|
180
|
+
* a webhook AND a cron, an http door AND a cmsHook. `trigger` and `triggers`
|
|
181
|
+
* may both be present; the deploy carries them in that order as
|
|
182
|
+
* `bindings[]`, at most 8 in all (the CLI refuses a 9th before the wire).
|
|
183
|
+
* Every binding lands the same isolate; only the envelope's `trigger` /
|
|
184
|
+
* `payload` differ. Each platform-delivered binding is billed per
|
|
185
|
+
* invocation and bounded by the loop guard and the breaker. */
|
|
186
|
+
triggers?: FunctionTrigger[];
|
|
154
187
|
/** clamped by DENY_FUNCTION_SCOPES at deploy (admin, wildcard, features:write,
|
|
155
188
|
* functions:write, secrets:write rejected; payments:write / notifications:send /
|
|
156
189
|
* users:* allowed as owner-granted business scopes). */
|