primitive-admin 1.1.0-alpha.64 → 1.1.0-alpha.65

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 (54) hide show
  1. package/assets/skill/skills/primitive-platform/SKILL.md +68 -6
  2. package/dist/bin/primitive.js +2 -0
  3. package/dist/bin/primitive.js.map +1 -1
  4. package/dist/src/commands/config.js +11 -4
  5. package/dist/src/commands/config.js.map +1 -1
  6. package/dist/src/commands/cron-triggers.js +25 -28
  7. package/dist/src/commands/cron-triggers.js.map +1 -1
  8. package/dist/src/commands/feature-flags.d.ts +14 -0
  9. package/dist/src/commands/feature-flags.js +116 -0
  10. package/dist/src/commands/feature-flags.js.map +1 -0
  11. package/dist/src/commands/integrations.js +55 -2
  12. package/dist/src/commands/integrations.js.map +1 -1
  13. package/dist/src/commands/prompts.js +58 -3
  14. package/dist/src/commands/prompts.js.map +1 -1
  15. package/dist/src/commands/rule-sets.d.ts +1 -1
  16. package/dist/src/commands/rule-sets.js +1 -0
  17. package/dist/src/commands/rule-sets.js.map +1 -1
  18. package/dist/src/commands/sync.d.ts +69 -12
  19. package/dist/src/commands/sync.js +497 -114
  20. package/dist/src/commands/sync.js.map +1 -1
  21. package/dist/src/commands/users.js +92 -1
  22. package/dist/src/commands/users.js.map +1 -1
  23. package/dist/src/commands/webhooks.js +59 -2
  24. package/dist/src/commands/webhooks.js.map +1 -1
  25. package/dist/src/commands/workflows.js +18 -19
  26. package/dist/src/commands/workflows.js.map +1 -1
  27. package/dist/src/lib/api-client.d.ts +51 -4
  28. package/dist/src/lib/api-client.js +71 -7
  29. package/dist/src/lib/api-client.js.map +1 -1
  30. package/dist/src/lib/config-object-descriptor.js +6 -14
  31. package/dist/src/lib/config-object-descriptor.js.map +1 -1
  32. package/dist/src/lib/config-surface.d.ts +2 -1
  33. package/dist/src/lib/config-surface.js +5 -1
  34. package/dist/src/lib/config-surface.js.map +1 -1
  35. package/dist/src/lib/generated-config-surfaces.d.ts +33 -0
  36. package/dist/src/lib/generated-config-surfaces.js +240 -55
  37. package/dist/src/lib/generated-config-surfaces.js.map +1 -1
  38. package/dist/src/lib/log-inspection.d.ts +2 -2
  39. package/dist/src/lib/log-inspection.js +3 -3
  40. package/dist/src/lib/log-inspection.js.map +1 -1
  41. package/dist/src/lib/object-status-filter.d.ts +22 -0
  42. package/dist/src/lib/object-status-filter.js +45 -0
  43. package/dist/src/lib/object-status-filter.js.map +1 -0
  44. package/dist/src/lib/swift-codegen/generator.js +2 -4
  45. package/dist/src/lib/swift-codegen/generator.js.map +1 -1
  46. package/dist/src/lib/sync-resource-types.js +13 -2
  47. package/dist/src/lib/sync-resource-types.js.map +1 -1
  48. package/dist/src/lib/workflow-defaults.d.ts +1 -2
  49. package/dist/src/lib/workflow-defaults.js +3 -3
  50. package/dist/src/lib/workflow-defaults.js.map +1 -1
  51. package/dist/src/lib/workflow-payload.js +0 -6
  52. package/dist/src/lib/workflow-payload.js.map +1 -1
  53. package/dist/src/types/index.d.ts +1 -0
  54. package/package.json +1 -1
@@ -494,6 +494,12 @@ export declare function requestSchemaKeys(table: ConfigTable, mode: "create" | "
494
494
  * is no place to write one.
495
495
  */
496
496
  export declare function configRequestSchema(table: ConfigTable, mode: "create" | "update"): ConfigRequestSchema;
497
+ /**
498
+ * The retired-key guidance for a generated schema, keyed by body key — or
499
+ * `undefined` when the schema's object has none. Consumed by the app API's
500
+ * request validator (`src/app-api/request-validation.ts`).
501
+ */
502
+ export declare function retiredRequestKeys(schema: object | undefined): Readonly<Record<string, string>> | undefined;
497
503
  /**
498
504
  * One schema for an endpoint whose body spans MORE than one table.
499
505
  *
@@ -512,6 +518,33 @@ export declare function mergeRequestSchemas(...schemas: readonly ConfigRequestSc
512
518
  export declare function unknownRequestKeys(body: unknown, table: ConfigTable, mode: "create" | "update"): string[];
513
519
  /** The 400 message naming the unknown key(s), shared by both APIs. */
514
520
  export declare function unknownRequestKeysMessage(keys: readonly string[]): string;
521
+ /**
522
+ * Keys deliberately RETIRED from a configuration object's write surface.
523
+ *
524
+ * A retired key is not an unknown key. The generic hint for an unrecognized
525
+ * TOML key — "check the spelling, or upgrade the CLI" — is exactly backwards
526
+ * for one this CLI removed on purpose, and the generic 400 for an unaccepted
527
+ * request key says only that the key is not allowed, not where the value moved
528
+ * to. Both surfaces need the same sentence, and neither should invent it.
529
+ *
530
+ * So the guidance lives here, next to the definitions, with one entry per
531
+ * retired key: the TOML wording for `config push` (which can tell the author to
532
+ * delete a line) and the request wording for the API handlers (which cannot).
533
+ *
534
+ * Keyed by the table's TOML path prefix (`workflow`, `cronTrigger`, …), because
535
+ * that is the identifier both consumers already have in hand.
536
+ *
537
+ * Pure: no models, no tenant context, no `env` (see `types.ts` §Purity).
538
+ */
539
+ export interface RetiredConfigKey {
540
+ /** What `config push` says about a file that still carries the key. */
541
+ toml: string;
542
+ /** What a create/update handler says about a body that still sends it. */
543
+ request: string;
544
+ }
545
+ export declare const RETIRED_CONFIG_KEYS: Record<string, Record<string, RetiredConfigKey>>;
546
+ /** The retired-key entry for `key` under `prefix`, or null when it is simply unknown. */
547
+ export declare function retiredConfigKey(prefix: string, key: string): RetiredConfigKey | null;
515
548
  export declare const WORKFLOW_SURFACE: ConfigObjectSurface;
516
549
  export declare const PROMPT_SURFACE: ConfigObjectSurface;
517
550
  export declare const INTEGRATION_SURFACE: ConfigObjectSurface;
@@ -508,7 +508,55 @@ export function configRequestSchema(table, mode) {
508
508
  for (const key of requestSchemaKeys(table, mode)) {
509
509
  properties[key] = {};
510
510
  }
511
- return { type: "object", properties, additionalProperties: false };
511
+ const schema = {
512
+ type: "object",
513
+ properties,
514
+ additionalProperties: false,
515
+ };
516
+ rememberRetiredKeys(schema, RETIRED_CONFIG_KEYS[table.tomlPath[0]]);
517
+ return schema;
518
+ }
519
+ /**
520
+ * Retired-key guidance carried BESIDE a generated schema, not inside it
521
+ * (issue #2803, second opinion).
522
+ *
523
+ * A route that opts into runtime validation is validated before its handler
524
+ * runs, so a handler's targeted "`status` is server-owned, use
525
+ * `<noun> enable|disable`" refusal never fired for the app-API webhook and cron
526
+ * routes: `additionalProperties: false` answered first, with the generic
527
+ * "Request body validation failed". The guidance has to reach the validator.
528
+ *
529
+ * It travels in a side table rather than as a schema keyword because the schema
530
+ * object IS the OpenAPI `meta.request` and is held to a fail-closed keyword
531
+ * allowlist (`RUNTIME_SUPPORTED_SCHEMA_KEYWORDS`) — an invented keyword would
532
+ * be published in the spec and would fail that guard. Keying on the schema's
533
+ * identity also means the guidance cannot drift: it is attached by the same
534
+ * call that builds the key set, so a route cannot opt into one without the
535
+ * other.
536
+ */
537
+ const retiredKeyGuidance = new WeakMap();
538
+ function rememberRetiredKeys(schema, retired) {
539
+ if (!retired)
540
+ return;
541
+ const entries = {};
542
+ for (const [key, entry] of Object.entries(retired)) {
543
+ // A key the schema ACCEPTS is not retired on this surface, whatever the
544
+ // TOML surface says about it. The accepted key set wins, so the guidance
545
+ // can never shadow a live field.
546
+ if (key in schema.properties)
547
+ continue;
548
+ entries[key] = entry.request;
549
+ }
550
+ if (Object.keys(entries).length > 0)
551
+ retiredKeyGuidance.set(schema, entries);
552
+ }
553
+ /**
554
+ * The retired-key guidance for a generated schema, keyed by body key — or
555
+ * `undefined` when the schema's object has none. Consumed by the app API's
556
+ * request validator (`src/app-api/request-validation.ts`).
557
+ */
558
+ export function retiredRequestKeys(schema) {
559
+ return schema ? retiredKeyGuidance.get(schema) : undefined;
512
560
  }
513
561
  /**
514
562
  * One schema for an endpoint whose body spans MORE than one table.
@@ -524,7 +572,22 @@ export function mergeRequestSchemas(...schemas) {
524
572
  for (const key of Object.keys(schema.properties))
525
573
  properties[key] = {};
526
574
  }
527
- return { type: "object", properties, additionalProperties: false };
575
+ // The union's guidance is the union of its halves'. A key one table retired
576
+ // and another accepts is admitted by the merged key set, so `rememberRetiredKeys`
577
+ // drops it below.
578
+ const retired = {};
579
+ for (const schema of schemas) {
580
+ for (const [key, request] of Object.entries(retiredRequestKeys(schema) ?? {})) {
581
+ retired[key] = { request };
582
+ }
583
+ }
584
+ const merged = {
585
+ type: "object",
586
+ properties,
587
+ additionalProperties: false,
588
+ };
589
+ rememberRetiredKeys(merged, retired);
590
+ return merged;
528
591
  }
529
592
  /**
530
593
  * The body keys `table` does not accept in `mode` — the 400's subject.
@@ -546,6 +609,90 @@ export function unknownRequestKeysMessage(keys) {
546
609
  `${keys.join(", ")}. This endpoint rejects keys it does not accept rather ` +
547
610
  "than ignoring them.");
548
611
  }
612
+ export const RETIRED_CONFIG_KEYS = {
613
+ // #2645 — pausing moved off `state` onto its own field, so `state` stopped
614
+ // being author-writable rather than becoming a second way to say "paused".
615
+ // Every pre-#2645 pull wrote the line, so existing repos hit this on their
616
+ // first push after upgrading: the failure is deliberate (sponsor decision at
617
+ // the approval gate, 2026-08-15), because the line has to be removed, not
618
+ // silently ignored, or the file keeps claiming a pause state push does not
619
+ // apply.
620
+ cronTrigger: {
621
+ state: {
622
+ toml: "`state` is no longer authored in TOML (#2645), and #2803 replaced it " +
623
+ "with the server-owned `status`. Availability is " +
624
+ "`primitive cron-triggers enable|disable`. Delete the line and push " +
625
+ "again.",
626
+ request: "`state` is server-owned and superseded by `status` (#2645, #2803). " +
627
+ "Use `primitive cron-triggers enable|disable` to change availability.",
628
+ },
629
+ status: {
630
+ toml: "`status` is no longer authored in TOML (#2803). Availability is " +
631
+ "server-owned: use `primitive cron-triggers enable|disable`. " +
632
+ "Delete the line and push again.",
633
+ request: "`status` is server-owned (#2803). Use " +
634
+ "`primitive cron-triggers enable|disable` to change availability.",
635
+ },
636
+ },
637
+ // #2803 — the OBJECT-level availability leaves TOML. The per-version
638
+ // `[[configs]] status` (`active | archived`) is a different question — which
639
+ // named config is retired — and stays authorable.
640
+ prompt: {
641
+ status: {
642
+ toml: "`[prompt].status` is no longer authored in TOML (#2803). " +
643
+ "Availability is server-owned: use `primitive prompts enable|disable`. " +
644
+ "Delete the line and push again — the per-config `[[configs]] status` " +
645
+ "is unaffected.",
646
+ request: "`status` is server-owned (#2803). Use " +
647
+ "`primitive prompts enable|disable` to change availability.",
648
+ },
649
+ },
650
+ integration: {
651
+ status: {
652
+ toml: "`status` is no longer authored in TOML (#2803), and `draft` no longer " +
653
+ "exists. Availability is server-owned: use " +
654
+ "`primitive integrations enable|disable`. Delete the line and push " +
655
+ "again. If this file describes an ARCHIVED integration, delete the " +
656
+ "file instead — pull no longer exports tombstones, and the next " +
657
+ "confirmed `config push --prune` hard-deletes the row.",
658
+ request: "`status` is server-owned (#2803). Use " +
659
+ "`primitive integrations enable|disable` to change availability.",
660
+ },
661
+ },
662
+ // #2803 — availability is one server-owned `status`. As a TOML key it made
663
+ // `config push` a second writer: pushing a file pulled before an operator ran
664
+ // `webhooks disable` put the endpoint back in service as a side effect. The
665
+ // tenant app-API create/update also stored the value UNVALIDATED, so any app
666
+ // admin could write any string into the field the receiver gates on.
667
+ webhook: {
668
+ status: {
669
+ toml: "`status` is no longer authored in TOML (#2803). Availability is " +
670
+ "server-owned: use `primitive webhooks enable|disable`. Delete the " +
671
+ "line and push again. If this file describes an ARCHIVED webhook, " +
672
+ "delete the file instead — pull no longer exports tombstones, and the " +
673
+ "next confirmed `config push --prune` hard-deletes the row, freeing " +
674
+ "its slot and its key.",
675
+ request: "`status` is server-owned (#2803). Use " +
676
+ "`primitive webhooks enable|disable` to change availability.",
677
+ },
678
+ },
679
+ // #2803 — availability is one server-owned `status`. As a TOML key it made
680
+ // `config push` a second writer: pushing a file pulled before an operator ran
681
+ // `workflows disable` put the workflow back in service as a side effect.
682
+ workflow: {
683
+ status: {
684
+ toml: "`status` is no longer authored in TOML (#2803). Availability is " +
685
+ "server-owned: use `primitive workflows enable|disable`. " +
686
+ "Delete the line and push again.",
687
+ request: "`status` is server-owned (#2803). Use " +
688
+ "`primitive workflows enable|disable` to change availability.",
689
+ },
690
+ },
691
+ };
692
+ /** The retired-key entry for `key` under `prefix`, or null when it is simply unknown. */
693
+ export function retiredConfigKey(prefix, key) {
694
+ return RETIRED_CONFIG_KEYS[prefix]?.[key] ?? null;
695
+ }
549
696
  // ── src/config-surface/workflow.ts ───────────────────────────────────────
550
697
  /**
551
698
  * The `workflow` configuration object's definition (issue #2644, phase 1).
@@ -610,14 +757,6 @@ export const WORKFLOW_SURFACE = {
610
757
  writableOn: BOTH,
611
758
  validation: handler("normalizeWorkflowDescription"),
612
759
  },
613
- {
614
- field: "status",
615
- tomlKey: "status",
616
- type: "string",
617
- emit: "always",
618
- writableOn: BOTH,
619
- validation: handler("normalizeWorkflowStatus"),
620
- },
621
760
  {
622
761
  field: "accessRule",
623
762
  tomlKey: "accessRule",
@@ -705,6 +844,20 @@ export const WORKFLOW_SURFACE = {
705
844
  kind: "server-owned",
706
845
  note: "ULID assigned on create; never author-authored.",
707
846
  },
847
+ // #2803 — `status` is the ONE availability control and it is
848
+ // server-owned. It used to be a `[workflow]` key, which made `config
849
+ // push` a second writer: pushing a file pulled before an operator ran
850
+ // `workflows disable` put the workflow back in service as a side
851
+ // effect. `server-owned` keeps it out of both the TOML surface and the
852
+ // create/update request schemas, so the only writers are
853
+ // `workflows enable|disable` and create (which assigns `active`).
854
+ status: {
855
+ kind: "server-owned",
856
+ note: "Availability (#2803). Written only by `workflows enable|disable` " +
857
+ "and by create, which always assigns `active`. Never authored in " +
858
+ "TOML: `config pull` does not emit it and a file that still " +
859
+ "carries the key fails push (RETIRED_TOML_KEYS).",
860
+ },
708
861
  // #2645 criterion 9 — the operational toggle. Configuration is
709
862
  // authored in TOML and applied with `config push`, so anything this
710
863
  // table exposes has the repo as its single writer. Taking a workflow
@@ -909,14 +1062,6 @@ export const PROMPT_SURFACE = {
909
1062
  writableOn: BOTH,
910
1063
  validation: "passthrough",
911
1064
  },
912
- {
913
- field: "status",
914
- tomlKey: "status",
915
- type: "string",
916
- emit: "always",
917
- writableOn: BOTH,
918
- validation: handledBy(PROMPT_HANDLER, "AdminAPI"),
919
- },
920
1065
  // JSON Schema objects. Stored as JSON strings, returned parsed; TOML
921
1066
  // may present them as an inline table or a JSON string.
922
1067
  {
@@ -957,6 +1102,17 @@ export const PROMPT_SURFACE = {
957
1102
  kind: "server-owned",
958
1103
  note: "ULID assigned on create; never author-authored.",
959
1104
  },
1105
+ // #2803 — the OBJECT-level availability, server-owned. Distinct from
1106
+ // the per-version `[[configs]] status` below, which stays a TOML key:
1107
+ // that says which named config is retired, this says whether the
1108
+ // prompt is in service at all.
1109
+ status: {
1110
+ kind: "server-owned",
1111
+ note: "Availability (#2803): active | inactive. Written only by " +
1112
+ "`prompts enable|disable` and by create, which always assigns " +
1113
+ "`active`. Never authored in TOML; the per-version " +
1114
+ "`[[configs]] status` is unaffected.",
1115
+ },
960
1116
  appId: {
961
1117
  kind: "server-owned",
962
1118
  note: "Tenant scope, taken from the route; never on the wire.",
@@ -1260,15 +1416,6 @@ export const INTEGRATION_SURFACE = {
1260
1416
  writableOn: BOTH,
1261
1417
  validation: "passthrough",
1262
1418
  },
1263
- {
1264
- field: "status",
1265
- tomlKey: "status",
1266
- type: "string",
1267
- emit: "always",
1268
- writableOn: BOTH,
1269
- // draft | active | archived, rejected with a 400 naming the set.
1270
- validation: handledBy(INTEGRATION_HANDLER, "AdminAPI"),
1271
- },
1272
1419
  {
1273
1420
  field: "timeoutMs",
1274
1421
  tomlKey: "timeoutMs",
@@ -1304,6 +1451,15 @@ export const INTEGRATION_SURFACE = {
1304
1451
  kind: "server-owned",
1305
1452
  note: "ULID assigned on create; never author-authored.",
1306
1453
  },
1454
+ // #2803 — availability, server-owned. `draft` is gone from the model;
1455
+ // a stored one reads `inactive` through the canonical normalizer.
1456
+ status: {
1457
+ kind: "server-owned",
1458
+ note: "Availability (#2803): active | inactive | archived. Written only " +
1459
+ "by `integrations enable|disable`, by the delete flow's tombstone, " +
1460
+ "and by create, which always assigns `active`. Never authored in " +
1461
+ "TOML: pull does not emit it and pull skips archived rows.",
1462
+ },
1307
1463
  appId: {
1308
1464
  kind: "server-owned",
1309
1465
  note: "Tenant scope, taken from the route; never on the wire.",
@@ -1422,15 +1578,6 @@ export const WEBHOOK_SURFACE = {
1422
1578
  writableOn: BOTH,
1423
1579
  validation: "passthrough",
1424
1580
  },
1425
- {
1426
- field: "status",
1427
- tomlKey: "status",
1428
- type: "string",
1429
- emit: "always",
1430
- writableOn: BOTH,
1431
- // active | paused; `archived` is reserved for soft-delete.
1432
- validation: handledBy(WEBHOOK_HANDLER, "AdminAPI"),
1433
- },
1434
1581
  {
1435
1582
  field: "workflowKey",
1436
1583
  tomlKey: "workflowKey",
@@ -1507,6 +1654,19 @@ export const WEBHOOK_SURFACE = {
1507
1654
  kind: "server-owned",
1508
1655
  note: "ULID assigned on create; never author-authored.",
1509
1656
  },
1657
+ // #2803 — availability is server-owned. As a `[webhook]` key it made
1658
+ // `config push` a second writer: a file pulled before an operator
1659
+ // paused the webhook put the endpoint back in service on the next
1660
+ // push. `server-owned` keeps it out of the TOML surface AND out of the
1661
+ // create/update request schemas, closing the tenant app-API path that
1662
+ // stored `body.status` unvalidated.
1663
+ status: {
1664
+ kind: "server-owned",
1665
+ note: "Availability (#2803): active | inactive | archived. Written only " +
1666
+ "by `webhooks enable|disable` and by the delete flow's tombstone. " +
1667
+ "Never authored in TOML: pull does not emit it, pull skips " +
1668
+ "archived rows entirely, and a file carrying the key fails push.",
1669
+ },
1510
1670
  appId: {
1511
1671
  kind: "server-owned",
1512
1672
  note: "Tenant scope, taken from the route; never on the wire.",
@@ -1712,29 +1872,37 @@ export const CRON_TRIGGER_SURFACE = {
1712
1872
  },
1713
1873
  ],
1714
1874
  notExposed: {
1715
- // #2645 criterion 9 — pause moved off `state` onto the
1716
- // `operationallyDisabled` toggle. `state` keeps the lifecycle values
1717
- // the server owns (active / error_paused / archived) and is no longer
1718
- // part of the TOML surface: pull does not emit it and push does not
1719
- // apply it, so a push can never resume a trigger an operator paused.
1720
- // `structural` with update-only requestModes, matching the handlers:
1721
- // the update handler still reads a supplied `state` (the console
1722
- // remains a config-editing surface by product decision), while create
1723
- // assigns "active" itself and must keep rejecting the key rather than
1724
- // silently dropping it.
1875
+ // #2803 — the ONE availability control, server-owned. Written only by
1876
+ // `cron-triggers enable|disable`, the delete flow (the `archived`
1877
+ // tombstone) and the alarm's error-pause path. `server-owned` keeps it
1878
+ // out of both the TOML surface and the create/update request schemas,
1879
+ // so no configuration write can change availability as a side effect.
1880
+ status: {
1881
+ kind: "server-owned",
1882
+ note: "Availability (#2803): active | inactive | archived. Written only " +
1883
+ "by `cron-triggers enable|disable`, the delete flow and the " +
1884
+ "error-pause path. Never authored in TOML: pull does not emit it " +
1885
+ "and a file that still carries the key fails push.",
1886
+ },
1887
+ // #2645 criterion 9 moved pausing off `state`; #2803 retired the field
1888
+ // entirely. It mixed lifecycle (`archived`, `error_paused`) with
1889
+ // availability (`paused`), which is what made two writers possible in
1890
+ // the first place. `server-owned` now, not `structural` with an
1891
+ // update-only mode: the console's carve-out is gone with it, so the
1892
+ // generic update rejects a supplied `state` exactly as create always
1893
+ // did.
1725
1894
  state: {
1726
- kind: "structural",
1727
- requestModes: ["update"],
1728
- note: "Lifecycle state (active | paused | error_paused | archived). " +
1729
- "Still accepted on update for the console; pausing from the CLI " +
1730
- "is `cron-triggers pause|resume`, which writes " +
1731
- "`operationallyDisabled` instead (#2645). Not authored in TOML.",
1895
+ kind: "server-owned",
1896
+ note: "DEPRECATED (#2803), superseded by `status`. Read only by " +
1897
+ "`canonicalCronStatus()`; normalized back to `active` by " +
1898
+ "`cron-triggers enable`. Never authored in TOML and never accepted " +
1899
+ "on the wire.",
1732
1900
  },
1733
1901
  operationallyDisabled: {
1734
1902
  kind: "server-owned",
1735
- note: "Operational toggle (#2645). Written only by the pause/resume " +
1736
- "endpoints (`cron-triggers pause|resume`); read by the cron tick. " +
1737
- "Never authored in TOML and never accepted on the config " +
1903
+ note: "DEPRECATED (#2803), superseded by `status`. The #2645 operational " +
1904
+ "toggle, cleared by `cron-triggers enable|disable` as rows are " +
1905
+ "touched. Never authored in TOML and never accepted on the config " +
1738
1906
  "create/update wire.",
1739
1907
  },
1740
1908
  triggerId: {
@@ -3031,6 +3199,21 @@ export const APP_SETTINGS_SURFACE = {
3031
3199
  writableOn: UPDATE_ONLY,
3032
3200
  validation: "passthrough",
3033
3201
  },
3202
+ {
3203
+ field: "directLlmEnabled",
3204
+ tomlKey: "directLlmEnabled",
3205
+ type: "boolean",
3206
+ // Always emitted: an app that has not opted the deprecated direct
3207
+ // LLM/Gemini proxy in must see `directLlmEnabled = false` in its
3208
+ // pulled `app.toml`, not a key it has to already know about (#2687).
3209
+ emit: "always",
3210
+ writableOn: UPDATE_ONLY,
3211
+ // STRICT, not `!!`: the value arms a credit-spending surface read
3212
+ // with `=== true`, so `"false"` must be a 400 rather than a stored
3213
+ // `true`. The generated request schema stays keys-only (#1976), so
3214
+ // the check is `parseDirectLlmEnabledWrite` in the handler.
3215
+ validation: handledBy(SETTINGS_HANDLER, "SettingsController"),
3216
+ },
3034
3217
  {
3035
3218
  field: "allowedDomains",
3036
3219
  tomlKey: "allowedDomains",
@@ -3407,6 +3590,7 @@ export const GENERATED_CONFIG_MODEL_FIELDS = {
3407
3590
  "magicLinkEnabled",
3408
3591
  "otpEnabled",
3409
3592
  "baseUrl",
3593
+ "directLlmEnabled",
3410
3594
  "memberInvitationsEnabled",
3411
3595
  "memberInvitationLimit",
3412
3596
  "createdAt",
@@ -3554,6 +3738,7 @@ export const GENERATED_CONFIG_MODEL_FIELDS = {
3554
3738
  "overlapPolicy",
3555
3739
  "rootInput",
3556
3740
  "inputMapping",
3741
+ "status",
3557
3742
  "state",
3558
3743
  "operationallyDisabled",
3559
3744
  "lastError",