@vxil/feature-configs 0.8.0 → 0.9.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/dist/index.d.ts CHANGED
@@ -6,9 +6,12 @@ export * from './canonicalJson.js';
6
6
  export * from './publicAssets.js';
7
7
  export declare const RESERVED_CREDIT_TYPES: ReadonlySet<string>;
8
8
  /** A function binding's `retry.maxAttempts` ceiling, and the
9
- * binding kinds that may carry `retry` — the platform-delivered event lanes
10
- * (an http invoke returns its own status; a cron tick's retry would overlap
11
- * the next tick). Read by the schema, the deploy clamp and the CLI. */
9
+ * binding kinds that may carry `retry` — every lane the platform delivers
10
+ * through jobs. Since 2026-10-03 (JT-9) that includes `cron` (a failed tick is
11
+ * re-delivered on the ladder; pair it with `overlap: 'skip'` so a retrying
12
+ * tick never runs beside the next one) and `http` (the ASYNC lane only — a
13
+ * synchronous invoke always hands its caller the function's own status).
14
+ * Read by the schema, the deploy clamp and the CLI. */
12
15
  export declare const FN_RETRY_MAX_ATTEMPTS = 5;
13
16
  export declare const FN_RETRY_BINDING_KINDS: ReadonlySet<string>;
14
17
  /** True when a credit_type is a vxil-COGS reserved type (grant/consume/config
package/dist/index.js CHANGED
@@ -42,11 +42,14 @@ if (!FormatRegistry.Has('email')) {
42
42
  // config-write refusal share ONE list. payments-v1/core.ts re-exports these.
43
43
  export const RESERVED_CREDIT_TYPES = new Set(['fn_cpu_ms']);
44
44
  /** A function binding's `retry.maxAttempts` ceiling, and the
45
- * binding kinds that may carry `retry` — the platform-delivered event lanes
46
- * (an http invoke returns its own status; a cron tick's retry would overlap
47
- * the next tick). Read by the schema, the deploy clamp and the CLI. */
45
+ * binding kinds that may carry `retry` — every lane the platform delivers
46
+ * through jobs. Since 2026-10-03 (JT-9) that includes `cron` (a failed tick is
47
+ * re-delivered on the ladder; pair it with `overlap: 'skip'` so a retrying
48
+ * tick never runs beside the next one) and `http` (the ASYNC lane only — a
49
+ * synchronous invoke always hands its caller the function's own status).
50
+ * Read by the schema, the deploy clamp and the CLI. */
48
51
  export const FN_RETRY_MAX_ATTEMPTS = 5;
49
- export const FN_RETRY_BINDING_KINDS = new Set(['queue', 'webhook', 'cmsHook', 'authHook']);
52
+ export const FN_RETRY_BINDING_KINDS = new Set(['queue', 'webhook', 'cmsHook', 'authHook', 'cron', 'http']);
50
53
  /** True when a credit_type is a vxil-COGS reserved type (grant/consume/config
51
54
  * of which is restricted to internal platform machinery). */
52
55
  export function isReservedCreditType(creditType) {
@@ -341,7 +344,24 @@ export const JobsConfigSchema = Type.Object({
341
344
  * pathological target (e.g. an always-throwing function) can generate. */
342
345
  dlqDailyQuota: Type.Integer({ default: 0, minimum: 0, maximum: 100_000 }),
343
346
  schedules: Type.Object({ maxPerTenant: Type.Integer({ default: 50, minimum: 1, maximum: 1000 }) }, { default: {} }),
344
- concurrency: Type.Object({ maxConcurrent: Type.Integer({ default: 10, minimum: 1, maximum: 100 }) }, { default: {} }),
347
+ concurrency: Type.Object(
348
+ /** Per-tenant cap on PLAIN runs (queue / cron / event deliveries,
349
+ * incl. platform-delivered function triggers) in flight at once; generation
350
+ * runs have their own `generation.maxConcurrent`. EFFECTIVE value =
351
+ * min(this, the tenant's fair share of the shared delivery consumer): the
352
+ * platform runs at most 75 queue-lane deliveries in flight for ALL tenants
353
+ * together, and one tenant may hold at most 25 of them (jobs-v1 core.ts
354
+ * TENANT_FAIR_SHARE). Values 26..100 still validate (existing configs keep
355
+ * working) but buy nothing past the share. Over-cap runs wait (deferred,
356
+ * never rejected). The schema `description` below is the tenant-facing
357
+ * copy of this (planner catalog, generated config schema) — keep both in
358
+ * step. */
359
+ {
360
+ maxConcurrent: Type.Integer({
361
+ default: 10, minimum: 1, maximum: 100,
362
+ description: 'Plain runs (queued jobs, cron fires, event deliveries, platform-delivered function triggers) delivered at once; generation runs have their own generation.maxConcurrent. Effective at most 25 (the per-project share of the shared delivery capacity): 26..100 validate but buy nothing more. Over-cap runs wait, never rejected.',
363
+ }),
364
+ }, { default: {} }),
345
365
  // Generation lifecycle knobs (guide ch. 6, jobs). The defaults and bounds are
346
366
  // declared ONCE, as GENERATION_DEFAULTS / GENERATION_BOUNDS below this schema
347
367
  // (2026-09-25): jobs-v1's resolveGenerationConfig imports them and clamps a
@@ -1413,8 +1433,8 @@ export const FunctionsConfigSchema = Type.Object({
1413
1433
  collection: Type.Optional(Type.String()), // cmsHook: the CMS collection slug
1414
1434
  event: Type.Optional(Type.String()), // cmsHook: beforeCreate|beforeUpdate|beforeWrite · authHook: 'user.created'
1415
1435
  // (2026-09-25) the per-binding opt-in to re-delivery on
1416
- // queue / webhook / cmsHook / authHook (the cross-field rule
1417
- // rejects it on http / cron). Absent = the ACK-200 default. The
1436
+ // queue / webhook / cmsHook / authHook, and since 2026-10-03 on
1437
+ // cron and http (the async lane only). Absent = the ACK-200 default. The
1418
1438
  // receiver answers a failed attempt as an enveloped 503 (ladder)
1419
1439
  // and the last one as a terminal 409 (dead + job.dead_lettered);
1420
1440
  // effective attempts = min(maxAttempts, jobs.retry.defaultMaxAttempts).
@@ -2127,9 +2147,8 @@ export function validateFeatureConfig(feature, raw) {
2127
2147
  if (b.overlap !== undefined && b.kind !== 'cron') {
2128
2148
  errs.push(`/functions/${name}/bindings/${i}: 'overlap' applies to cron bindings only (not '${b.kind}')`);
2129
2149
  }
2130
- // retry is an opt-in for the platform-delivered event lanes only —
2131
- // an http invoke returns its real status to its caller, and a cron
2132
- // tick's retry would overlap the next tick.
2150
+ // retry is an opt-in for the platform-delivered lanes
2151
+ // (FN_RETRY_BINDING_KINDS — every binding kind since JT-9)
2133
2152
  if (b.retry !== undefined && !FN_RETRY_BINDING_KINDS.has(b.kind)) {
2134
2153
  errs.push(`/functions/${name}/bindings/${i}: 'retry' applies to ${[...FN_RETRY_BINDING_KINDS].join(' | ')} bindings only (not '${b.kind}')`);
2135
2154
  }
@@ -2223,15 +2242,15 @@ export const FEATURE_KEYS = [
2223
2242
  ];
2224
2243
  export const FEATURE_HINTS = {
2225
2244
  notifications: {
2226
- whenToUse: 'Sending email (transactional or broadcast) or in-app inbox messages to end-users: welcomes, receipts, reminders, alerts, digests.',
2245
+ whenToUse: 'Sending email (transactional or broadcast) or in-app inbox messages to end-users: welcomes, receipts, reminders, alerts, digests. A send can be scheduled for a known time (send_at), so a one-off reminder needs no job or function.',
2227
2246
  signals: ['email', 'notify', 'alert', 'reminder', 'welcome', 'receipt', 'digest', 'inbox'],
2228
2247
  notFor: 'Live in-page updates (realtime) or activity timelines (activity-feed).',
2229
2248
  dependsOn: [],
2230
2249
  },
2231
2250
  jobs: {
2232
- whenToUse: 'Background work: scheduled/cron tasks, delayed sends, retries, queues, long-running or periodic processing, durable multi-step waits.',
2233
- signals: ['schedule', 'cron', 'daily', 'background', 'queue', 'later', 'recurring', 'batch'],
2234
- notFor: 'Simple request-response logic that completes inline.',
2251
+ whenToUse: 'Background work: scheduled/cron tasks, delayed runs, retries, queues, runs that wait for an event, per-user serialisation (concurrency keys), debounced bursts, start deadlines, fan-in (a batch completion event), per-user schedules in their own time zone. Also the bookkeeping for LONG work that runs on the tenant\'s own runtime (renders, transcodes, model calls): a generation run holds reserved credits, a deadline, a signed completion callback and live progress mirrored onto a cms row.',
2252
+ signals: ['schedule', 'cron', 'daily', 'background', 'queue', 'later', 'recurring', 'batch', 'render', 'transcode', 'progress', 'long-running'],
2253
+ notFor: 'Simple request-response logic that completes inline; it is a queue with controls, not a workflow engine, and it never runs the heavy compute itself.',
2235
2254
  dependsOn: [],
2236
2255
  },
2237
2256
  auth: {
@@ -2247,8 +2266,8 @@ export const FEATURE_HINTS = {
2247
2266
  dependsOn: [],
2248
2267
  },
2249
2268
  files: {
2250
- whenToUse: 'End-users upload or download files/images/documents: avatars, attachments, photos, PDFs, media libraries.',
2251
- signals: ['upload', 'image', 'photo', 'file', 'attachment', 'document', 'avatar', 'media'],
2269
+ whenToUse: 'End-users upload or download files/images/documents: avatars, attachments, photos, PDFs, media libraries. Media shown publicly (galleries, product photos, video, audio, fonts) is published to a public CDN URL, with named image variant presets such as thumbnails (publicAssets).',
2270
+ signals: ['upload', 'image', 'photo', 'file', 'attachment', 'document', 'avatar', 'media', 'gallery', 'thumbnail', 'video', 'CDN'],
2252
2271
  notFor: 'Structured records (cms) or text content authored in-app.',
2253
2272
  dependsOn: [],
2254
2273
  },
@@ -2265,7 +2284,7 @@ export const FEATURE_HINTS = {
2265
2284
  dependsOn: [],
2266
2285
  },
2267
2286
  cms: {
2268
- whenToUse: 'The data spine: typed record collections with fields, relations, owner-scoping, draft/publish, and optional keyless public reads. Most apps are, underneath, cms collections.',
2287
+ whenToUse: 'The data spine: typed record collections with fields, relations, owner-scoping, draft/publish, indexed filters, and optional keyless public reads. Most apps are, underneath, cms collections.',
2269
2288
  signals: ['store', 'list', 'catalog', 'posts', 'products', 'records', 'items', 'content', 'blog', 'tasks', 'listings'],
2270
2289
  notFor: 'End-user identity (auth) or file bytes (files).',
2271
2290
  dependsOn: [],
@@ -2327,7 +2346,7 @@ export const FEATURE_HINTS = {
2327
2346
  functions: {
2328
2347
  whenToUse: 'ONLY for truly-unique server logic no feature or cms rule can express: bespoke sagas, custom integrations over the egress guard, computed endpoints. Prefer features/cms first; functions are the escape hatch.',
2329
2348
  signals: ['custom logic', 'custom backend', 'integration with', 'workflow', 'saga'],
2330
- notFor: 'Anything a shipped feature or a cms lifecycle hook already covers.',
2349
+ notFor: 'Anything a shipped feature or a cms lifecycle hook already covers, and heavy or long compute (renders, ffmpeg, ML) — that runs on the tenant\'s own runtime, tracked by a jobs generation run.',
2331
2350
  dependsOn: [],
2332
2351
  },
2333
2352
  copilot: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vxil/feature-configs",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "The per-feature configuration schemas and validators behind vxil.config.ts (published for @vxil/cli and @vxil/config).",
5
5
  "license": "MIT",
6
6
  "homepage": "https://vxil.com",
package/src/index.ts CHANGED
@@ -46,11 +46,14 @@ if (!FormatRegistry.Has('email')) {
46
46
  export const RESERVED_CREDIT_TYPES: ReadonlySet<string> = new Set<string>(['fn_cpu_ms']);
47
47
 
48
48
  /** A function binding's `retry.maxAttempts` ceiling, and the
49
- * binding kinds that may carry `retry` — the platform-delivered event lanes
50
- * (an http invoke returns its own status; a cron tick's retry would overlap
51
- * the next tick). Read by the schema, the deploy clamp and the CLI. */
49
+ * binding kinds that may carry `retry` — every lane the platform delivers
50
+ * through jobs. Since 2026-10-03 (JT-9) that includes `cron` (a failed tick is
51
+ * re-delivered on the ladder; pair it with `overlap: 'skip'` so a retrying
52
+ * tick never runs beside the next one) and `http` (the ASYNC lane only — a
53
+ * synchronous invoke always hands its caller the function's own status).
54
+ * Read by the schema, the deploy clamp and the CLI. */
52
55
  export const FN_RETRY_MAX_ATTEMPTS = 5;
53
- export const FN_RETRY_BINDING_KINDS: ReadonlySet<string> = new Set(['queue', 'webhook', 'cmsHook', 'authHook']);
56
+ export const FN_RETRY_BINDING_KINDS: ReadonlySet<string> = new Set(['queue', 'webhook', 'cmsHook', 'authHook', 'cron', 'http']);
54
57
 
55
58
  /** True when a credit_type is a vxil-COGS reserved type (grant/consume/config
56
59
  * of which is restricted to internal platform machinery). */
@@ -399,7 +402,23 @@ export const JobsConfigSchema = Type.Object({
399
402
  { default: {} },
400
403
  ),
401
404
  concurrency: Type.Object(
402
- { maxConcurrent: Type.Integer({ default: 10, minimum: 1, maximum: 100 }) },
405
+ /** Per-tenant cap on PLAIN runs (queue / cron / event deliveries,
406
+ * incl. platform-delivered function triggers) in flight at once; generation
407
+ * runs have their own `generation.maxConcurrent`. EFFECTIVE value =
408
+ * min(this, the tenant's fair share of the shared delivery consumer): the
409
+ * platform runs at most 75 queue-lane deliveries in flight for ALL tenants
410
+ * together, and one tenant may hold at most 25 of them (jobs-v1 core.ts
411
+ * TENANT_FAIR_SHARE). Values 26..100 still validate (existing configs keep
412
+ * working) but buy nothing past the share. Over-cap runs wait (deferred,
413
+ * never rejected). The schema `description` below is the tenant-facing
414
+ * copy of this (planner catalog, generated config schema) — keep both in
415
+ * step. */
416
+ {
417
+ maxConcurrent: Type.Integer({
418
+ default: 10, minimum: 1, maximum: 100,
419
+ description: 'Plain runs (queued jobs, cron fires, event deliveries, platform-delivered function triggers) delivered at once; generation runs have their own generation.maxConcurrent. Effective at most 25 (the per-project share of the shared delivery capacity): 26..100 validate but buy nothing more. Over-cap runs wait, never rejected.',
420
+ }),
421
+ },
403
422
  { default: {} },
404
423
  ),
405
424
  // Generation lifecycle knobs (guide ch. 6, jobs). The defaults and bounds are
@@ -1754,8 +1773,8 @@ export const FunctionsConfigSchema = Type.Object({
1754
1773
  collection: Type.Optional(Type.String()), // cmsHook: the CMS collection slug
1755
1774
  event: Type.Optional(Type.String()), // cmsHook: beforeCreate|beforeUpdate|beforeWrite · authHook: 'user.created'
1756
1775
  // (2026-09-25) the per-binding opt-in to re-delivery on
1757
- // queue / webhook / cmsHook / authHook (the cross-field rule
1758
- // rejects it on http / cron). Absent = the ACK-200 default. The
1776
+ // queue / webhook / cmsHook / authHook, and since 2026-10-03 on
1777
+ // cron and http (the async lane only). Absent = the ACK-200 default. The
1759
1778
  // receiver answers a failed attempt as an enveloped 503 (ladder)
1760
1779
  // and the last one as a terminal 409 (dead + job.dead_lettered);
1761
1780
  // effective attempts = min(maxAttempts, jobs.retry.defaultMaxAttempts).
@@ -2567,9 +2586,8 @@ export function validateFeatureConfig(feature: string, raw: unknown): ConfigVali
2567
2586
  if (b.overlap !== undefined && b.kind !== 'cron') {
2568
2587
  errs.push(`/functions/${name}/bindings/${i}: 'overlap' applies to cron bindings only (not '${b.kind}')`);
2569
2588
  }
2570
- // retry is an opt-in for the platform-delivered event lanes only —
2571
- // an http invoke returns its real status to its caller, and a cron
2572
- // tick's retry would overlap the next tick.
2589
+ // retry is an opt-in for the platform-delivered lanes
2590
+ // (FN_RETRY_BINDING_KINDS — every binding kind since JT-9)
2573
2591
  if (b.retry !== undefined && !FN_RETRY_BINDING_KINDS.has(b.kind)) {
2574
2592
  errs.push(`/functions/${name}/bindings/${i}: 'retry' applies to ${[...FN_RETRY_BINDING_KINDS].join(' | ')} bindings only (not '${b.kind}')`);
2575
2593
  }
@@ -2683,15 +2701,15 @@ export interface PlannerHint {
2683
2701
 
2684
2702
  export const FEATURE_HINTS: Record<FeatureKey, PlannerHint> = {
2685
2703
  notifications: {
2686
- whenToUse: 'Sending email (transactional or broadcast) or in-app inbox messages to end-users: welcomes, receipts, reminders, alerts, digests.',
2704
+ whenToUse: 'Sending email (transactional or broadcast) or in-app inbox messages to end-users: welcomes, receipts, reminders, alerts, digests. A send can be scheduled for a known time (send_at), so a one-off reminder needs no job or function.',
2687
2705
  signals: ['email', 'notify', 'alert', 'reminder', 'welcome', 'receipt', 'digest', 'inbox'],
2688
2706
  notFor: 'Live in-page updates (realtime) or activity timelines (activity-feed).',
2689
2707
  dependsOn: [],
2690
2708
  },
2691
2709
  jobs: {
2692
- whenToUse: 'Background work: scheduled/cron tasks, delayed sends, retries, queues, long-running or periodic processing, durable multi-step waits.',
2693
- signals: ['schedule', 'cron', 'daily', 'background', 'queue', 'later', 'recurring', 'batch'],
2694
- notFor: 'Simple request-response logic that completes inline.',
2710
+ whenToUse: 'Background work: scheduled/cron tasks, delayed runs, retries, queues, runs that wait for an event, per-user serialisation (concurrency keys), debounced bursts, start deadlines, fan-in (a batch completion event), per-user schedules in their own time zone. Also the bookkeeping for LONG work that runs on the tenant\'s own runtime (renders, transcodes, model calls): a generation run holds reserved credits, a deadline, a signed completion callback and live progress mirrored onto a cms row.',
2711
+ signals: ['schedule', 'cron', 'daily', 'background', 'queue', 'later', 'recurring', 'batch', 'render', 'transcode', 'progress', 'long-running'],
2712
+ notFor: 'Simple request-response logic that completes inline; it is a queue with controls, not a workflow engine, and it never runs the heavy compute itself.',
2695
2713
  dependsOn: [],
2696
2714
  },
2697
2715
  auth: {
@@ -2707,8 +2725,8 @@ export const FEATURE_HINTS: Record<FeatureKey, PlannerHint> = {
2707
2725
  dependsOn: [],
2708
2726
  },
2709
2727
  files: {
2710
- whenToUse: 'End-users upload or download files/images/documents: avatars, attachments, photos, PDFs, media libraries.',
2711
- signals: ['upload', 'image', 'photo', 'file', 'attachment', 'document', 'avatar', 'media'],
2728
+ whenToUse: 'End-users upload or download files/images/documents: avatars, attachments, photos, PDFs, media libraries. Media shown publicly (galleries, product photos, video, audio, fonts) is published to a public CDN URL, with named image variant presets such as thumbnails (publicAssets).',
2729
+ signals: ['upload', 'image', 'photo', 'file', 'attachment', 'document', 'avatar', 'media', 'gallery', 'thumbnail', 'video', 'CDN'],
2712
2730
  notFor: 'Structured records (cms) or text content authored in-app.',
2713
2731
  dependsOn: [],
2714
2732
  },
@@ -2725,7 +2743,7 @@ export const FEATURE_HINTS: Record<FeatureKey, PlannerHint> = {
2725
2743
  dependsOn: [],
2726
2744
  },
2727
2745
  cms: {
2728
- whenToUse: 'The data spine: typed record collections with fields, relations, owner-scoping, draft/publish, and optional keyless public reads. Most apps are, underneath, cms collections.',
2746
+ whenToUse: 'The data spine: typed record collections with fields, relations, owner-scoping, draft/publish, indexed filters, and optional keyless public reads. Most apps are, underneath, cms collections.',
2729
2747
  signals: ['store', 'list', 'catalog', 'posts', 'products', 'records', 'items', 'content', 'blog', 'tasks', 'listings'],
2730
2748
  notFor: 'End-user identity (auth) or file bytes (files).',
2731
2749
  dependsOn: [],
@@ -2787,7 +2805,7 @@ export const FEATURE_HINTS: Record<FeatureKey, PlannerHint> = {
2787
2805
  functions: {
2788
2806
  whenToUse: 'ONLY for truly-unique server logic no feature or cms rule can express: bespoke sagas, custom integrations over the egress guard, computed endpoints. Prefer features/cms first; functions are the escape hatch.',
2789
2807
  signals: ['custom logic', 'custom backend', 'integration with', 'workflow', 'saga'],
2790
- notFor: 'Anything a shipped feature or a cms lifecycle hook already covers.',
2808
+ notFor: 'Anything a shipped feature or a cms lifecycle hook already covers, and heavy or long compute (renders, ffmpeg, ML) — that runs on the tenant\'s own runtime, tracked by a jobs generation run.',
2791
2809
  dependsOn: [],
2792
2810
  },
2793
2811
  copilot: {