@noodleseed/one 0.153.0 → 0.155.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/commands/app-purge-reconciliation-args.d.ts +28 -0
  2. package/dist/commands/app-purge-reconciliation-args.d.ts.map +1 -0
  3. package/dist/commands/app-purge-reconciliation-args.js +124 -0
  4. package/dist/commands/app-purge-reconciliation-args.js.map +1 -0
  5. package/dist/commands/app-purge-reconciliation-files.d.ts +8 -0
  6. package/dist/commands/app-purge-reconciliation-files.d.ts.map +1 -0
  7. package/dist/commands/app-purge-reconciliation-files.js +109 -0
  8. package/dist/commands/app-purge-reconciliation-files.js.map +1 -0
  9. package/dist/commands/app-purge-reconciliation-ops.d.ts +3 -0
  10. package/dist/commands/app-purge-reconciliation-ops.d.ts.map +1 -0
  11. package/dist/commands/app-purge-reconciliation-ops.js +190 -0
  12. package/dist/commands/app-purge-reconciliation-ops.js.map +1 -0
  13. package/dist/commands/billing-ops.d.ts.map +1 -1
  14. package/dist/commands/billing-ops.js +7 -0
  15. package/dist/commands/billing-ops.js.map +1 -1
  16. package/dist/commands/billing-org-transfer-files.d.ts +12 -0
  17. package/dist/commands/billing-org-transfer-files.d.ts.map +1 -0
  18. package/dist/commands/billing-org-transfer-files.js +112 -0
  19. package/dist/commands/billing-org-transfer-files.js.map +1 -0
  20. package/dist/commands/billing-org-transfer-ops.d.ts +4 -0
  21. package/dist/commands/billing-org-transfer-ops.d.ts.map +1 -0
  22. package/dist/commands/billing-org-transfer-ops.js +534 -0
  23. package/dist/commands/billing-org-transfer-ops.js.map +1 -0
  24. package/dist/commands/service-ops.d.ts.map +1 -1
  25. package/dist/commands/service-ops.js +4 -1
  26. package/dist/commands/service-ops.js.map +1 -1
  27. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +1 -1
  28. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +2 -0
  29. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  30. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-suggestions.js +5 -3
  31. package/node_modules/@noodle-borg/assistant-gateway/dist/continuity-bounds.d.ts +36 -0
  32. package/node_modules/@noodle-borg/assistant-gateway/dist/continuity-bounds.js +52 -0
  33. package/node_modules/@noodle-borg/assistant-gateway/dist/continuity-store.d.ts +115 -0
  34. package/node_modules/@noodle-borg/assistant-gateway/dist/continuity-store.js +51 -0
  35. package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-continuity-store.d.ts +26 -0
  36. package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-continuity-store.js +80 -0
  37. package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.d.ts +1 -0
  38. package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.js +2 -0
  39. package/node_modules/@noodle-borg/assistant-gateway/dist/model-response-values.js +11 -0
  40. package/node_modules/@noodle-borg/assistant-gateway/dist/model-responses.js +1 -10
  41. package/node_modules/@noodle-borg/assistant-gateway/dist/model-stream.js +1 -10
  42. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +3 -0
  43. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +3 -0
  44. package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
  45. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +35 -0
  46. package/node_modules/@noodle-borg/authoring/dist/assistant.js +4 -0
  47. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-account.js +96 -0
  48. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-billing.js +116 -1
  49. package/node_modules/@noodle-borg/compiler/dist/manifest/branding-schema.js +86 -0
  50. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +44 -25
  51. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +24 -76
  52. package/node_modules/@noodle-borg/control-plane/dist/app-purge-reconciliation-port.d.ts +25 -0
  53. package/node_modules/@noodle-borg/control-plane/dist/app-purge-reconciliation-port.js +10 -0
  54. package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +1 -0
  55. package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
  56. package/node_modules/@noodle-borg/control-plane/package.json +3 -1
  57. package/node_modules/@noodle-borg/service/dist/routes/app-purge-reconciliation.js +72 -0
  58. package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +15 -2
  59. package/node_modules/@noodle-borg/service/dist/service.js +27 -1
  60. package/node_modules/@noodle-borg/service/dist/store/in-memory.js +4 -8
  61. package/node_modules/@noodle-borg/service/dist/store/json-file.js +6 -3
  62. package/node_modules/@noodle-borg/service/dist/store/records.js +20 -0
  63. package/node_modules/@noodle-borg/service/dist/store.d.ts +3 -2
  64. package/node_modules/@noodle-borg/service/package.json +1 -1
  65. package/node_modules/@noodle-borg/wire-contracts/dist/app-purge-reconciliation.d.ts +177 -0
  66. package/node_modules/@noodle-borg/wire-contracts/dist/app-purge-reconciliation.js +189 -0
  67. package/node_modules/@noodle-borg/wire-contracts/dist/assistant.d.ts +1 -1
  68. package/node_modules/@noodle-borg/wire-contracts/dist/billing-org-transfer.d.ts +629 -0
  69. package/node_modules/@noodle-borg/wire-contracts/dist/billing-org-transfer.js +259 -0
  70. package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.d.ts +21 -0
  71. package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.js +23 -0
  72. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +2 -0
  73. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +2 -0
  74. package/node_modules/@noodle-borg/wire-contracts/dist/platform-account-reset.d.ts +4 -4
  75. package/node_modules/@noodleseed/assistant/package.json +1 -1
  76. package/package.json +2 -2
@@ -1,5 +1,4 @@
1
1
  import { z } from 'zod';
2
- import type { PackagedAssetReference } from '../assets.js';
3
2
  type ToolAuthorizationManifest = {
4
3
  readonly requiredScopes?: readonly string[] | undefined;
5
4
  readonly allowedRoles?: readonly string[] | undefined;
@@ -381,6 +380,11 @@ export declare const manifestV1Schema: z.ZodObject<{
381
380
  webmcp: z.ZodOptional<z.ZodObject<{
382
381
  enabled: z.ZodOptional<z.ZodBoolean>;
383
382
  }, z.core.$strict>>;
383
+ continuity: z.ZodOptional<z.ZodObject<{
384
+ enabled: z.ZodOptional<z.ZodBoolean>;
385
+ windowSeconds: z.ZodOptional<z.ZodNumber>;
386
+ maxRestores: z.ZodOptional<z.ZodNumber>;
387
+ }, z.core.$strict>>;
384
388
  }, z.core.$strict>>>;
385
389
  sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
386
390
  exposeToModel: z.ZodOptional<z.ZodBoolean>;
@@ -395,18 +399,18 @@ export declare const manifestV1Schema: z.ZodObject<{
395
399
  surface: z.ZodOptional<z.ZodString>;
396
400
  surfaceDark: z.ZodOptional<z.ZodString>;
397
401
  logo: z.ZodOptional<z.ZodObject<{
398
- uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>;
399
- darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>>;
402
+ uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>;
403
+ darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>>;
400
404
  alt: z.ZodString;
401
405
  }, z.core.$strict>>;
402
406
  mark: z.ZodOptional<z.ZodObject<{
403
- uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>;
404
- darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>>;
407
+ uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>;
408
+ darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>>;
405
409
  alt: z.ZodString;
406
410
  }, z.core.$strict>>;
407
411
  avatar: z.ZodOptional<z.ZodObject<{
408
- uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>;
409
- darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>>;
412
+ uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>;
413
+ darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>>;
410
414
  alt: z.ZodString;
411
415
  }, z.core.$strict>>;
412
416
  theme: z.ZodOptional<z.ZodObject<{
@@ -864,6 +868,11 @@ export declare const manifestV2Schema: z.ZodObject<{
864
868
  webmcp: z.ZodOptional<z.ZodObject<{
865
869
  enabled: z.ZodOptional<z.ZodBoolean>;
866
870
  }, z.core.$strict>>;
871
+ continuity: z.ZodOptional<z.ZodObject<{
872
+ enabled: z.ZodOptional<z.ZodBoolean>;
873
+ windowSeconds: z.ZodOptional<z.ZodNumber>;
874
+ maxRestores: z.ZodOptional<z.ZodNumber>;
875
+ }, z.core.$strict>>;
867
876
  }, z.core.$strict>>>;
868
877
  sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
869
878
  exposeToModel: z.ZodOptional<z.ZodBoolean>;
@@ -878,18 +887,18 @@ export declare const manifestV2Schema: z.ZodObject<{
878
887
  surface: z.ZodOptional<z.ZodString>;
879
888
  surfaceDark: z.ZodOptional<z.ZodString>;
880
889
  logo: z.ZodOptional<z.ZodObject<{
881
- uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>;
882
- darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>>;
890
+ uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>;
891
+ darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>>;
883
892
  alt: z.ZodString;
884
893
  }, z.core.$strict>>;
885
894
  mark: z.ZodOptional<z.ZodObject<{
886
- uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>;
887
- darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>>;
895
+ uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>;
896
+ darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>>;
888
897
  alt: z.ZodString;
889
898
  }, z.core.$strict>>;
890
899
  avatar: z.ZodOptional<z.ZodObject<{
891
- uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>;
892
- darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>>;
900
+ uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>;
901
+ darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>>;
893
902
  alt: z.ZodString;
894
903
  }, z.core.$strict>>;
895
904
  theme: z.ZodOptional<z.ZodObject<{
@@ -1557,6 +1566,11 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1557
1566
  webmcp: z.ZodOptional<z.ZodObject<{
1558
1567
  enabled: z.ZodOptional<z.ZodBoolean>;
1559
1568
  }, z.core.$strict>>;
1569
+ continuity: z.ZodOptional<z.ZodObject<{
1570
+ enabled: z.ZodOptional<z.ZodBoolean>;
1571
+ windowSeconds: z.ZodOptional<z.ZodNumber>;
1572
+ maxRestores: z.ZodOptional<z.ZodNumber>;
1573
+ }, z.core.$strict>>;
1560
1574
  }, z.core.$strict>>>;
1561
1575
  sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
1562
1576
  exposeToModel: z.ZodOptional<z.ZodBoolean>;
@@ -1571,18 +1585,18 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1571
1585
  surface: z.ZodOptional<z.ZodString>;
1572
1586
  surfaceDark: z.ZodOptional<z.ZodString>;
1573
1587
  logo: z.ZodOptional<z.ZodObject<{
1574
- uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>;
1575
- darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>>;
1588
+ uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>;
1589
+ darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>>;
1576
1590
  alt: z.ZodString;
1577
1591
  }, z.core.$strict>>;
1578
1592
  mark: z.ZodOptional<z.ZodObject<{
1579
- uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>;
1580
- darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>>;
1593
+ uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>;
1594
+ darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>>;
1581
1595
  alt: z.ZodString;
1582
1596
  }, z.core.$strict>>;
1583
1597
  avatar: z.ZodOptional<z.ZodObject<{
1584
- uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>;
1585
- darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>>;
1598
+ uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>;
1599
+ darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>>;
1586
1600
  alt: z.ZodString;
1587
1601
  }, z.core.$strict>>;
1588
1602
  theme: z.ZodOptional<z.ZodObject<{
@@ -2039,6 +2053,11 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
2039
2053
  webmcp: z.ZodOptional<z.ZodObject<{
2040
2054
  enabled: z.ZodOptional<z.ZodBoolean>;
2041
2055
  }, z.core.$strict>>;
2056
+ continuity: z.ZodOptional<z.ZodObject<{
2057
+ enabled: z.ZodOptional<z.ZodBoolean>;
2058
+ windowSeconds: z.ZodOptional<z.ZodNumber>;
2059
+ maxRestores: z.ZodOptional<z.ZodNumber>;
2060
+ }, z.core.$strict>>;
2042
2061
  }, z.core.$strict>>>;
2043
2062
  sessionClaims: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
2044
2063
  exposeToModel: z.ZodOptional<z.ZodBoolean>;
@@ -2053,18 +2072,18 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
2053
2072
  surface: z.ZodOptional<z.ZodString>;
2054
2073
  surfaceDark: z.ZodOptional<z.ZodString>;
2055
2074
  logo: z.ZodOptional<z.ZodObject<{
2056
- uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>;
2057
- darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>>;
2075
+ uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>;
2076
+ darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>>;
2058
2077
  alt: z.ZodString;
2059
2078
  }, z.core.$strict>>;
2060
2079
  mark: z.ZodOptional<z.ZodObject<{
2061
- uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>;
2062
- darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>>;
2080
+ uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>;
2081
+ darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>>;
2063
2082
  alt: z.ZodString;
2064
2083
  }, z.core.$strict>>;
2065
2084
  avatar: z.ZodOptional<z.ZodObject<{
2066
- uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>;
2067
- darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<PackagedAssetReference, unknown, z.core.$ZodTypeInternals<PackagedAssetReference, unknown>>]>>;
2085
+ uri: z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>;
2086
+ darkUri: z.ZodOptional<z.ZodUnion<readonly [z.ZodURL, z.ZodType<import("../assets.js").PackagedAssetReference, unknown, z.core.$ZodTypeInternals<import("../assets.js").PackagedAssetReference, unknown>>]>>;
2068
2087
  alt: z.ZodString;
2069
2088
  }, z.core.$strict>>;
2070
2089
  theme: z.ZodOptional<z.ZodObject<{
@@ -3,6 +3,7 @@ import { z } from 'zod';
3
3
  import { MAX_COMPILED_WIDGET_HTML_BYTES, MAX_RAW_WIDGET_HTML_BYTES } from '../widget-limits.js';
4
4
  import { agentGuideSchema } from './agent-guide-schema.js';
5
5
  import { serverAuthSchema, serverAuthV2Schema } from './auth-schema.js';
6
+ import { assistantOriginOrVariableSchema, httpsUrlSchema, managedVariableExpressionSchema, serverBrandingSchema, } from './branding-schema.js';
6
7
  import { NAME_PATTERN } from './naming.js';
7
8
  /**
8
9
  * Zod schema for Core v1 and v2 manifests (ADRs 0150 and 0157). V1 remains accepted unchanged;
@@ -133,82 +134,6 @@ const promptSchema = z.object({
133
134
  arguments: z.array(promptArgumentSchema).optional(),
134
135
  fulfilment: fulfilmentSchema,
135
136
  });
136
- const hexColorSchema = z.string().regex(/^#[0-9A-Fa-f]{6}$/, 'must be a 6-digit hex color');
137
- const httpsUrlSchema = z.url().regex(/^https:\/\//, 'must use https');
138
- const managedVariableExpressionPattern = /^\$\{env\.[A-Za-z0-9_]+\}$/;
139
- const managedVariableExpressionSchema = z
140
- .string()
141
- .regex(managedVariableExpressionPattern, 'must be an exact managed variable expression');
142
- // HTTP is allowed only for loopback development origins (mirrors the host-pattern loopback
143
- // exception used for connector/CSP origins); production embedding origins stay https-only.
144
- const assistantOriginOrVariablePattern = /^(?:\$\{env\.[A-Za-z0-9_]+\}|https:\/\/[^/?#@\\]+|http:\/\/(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?)$/;
145
- function isCanonicalAssistantOrigin(value) {
146
- try {
147
- return new URL(value).origin === value;
148
- }
149
- catch {
150
- return false;
151
- }
152
- }
153
- const assistantOriginOrVariableSchema = z
154
- .string()
155
- .regex(assistantOriginOrVariablePattern, 'must be a canonical bare origin using https (http is allowed only for localhost/127.0.0.1/[::1] loopback development origins)')
156
- .refine((value) => managedVariableExpressionPattern.test(value) || isCanonicalAssistantOrigin(value), 'must be a canonical bare origin');
157
- const packagedAssetSchema = z
158
- .object({
159
- kind: z.literal('asset'),
160
- sourcePath: z.string().min(1),
161
- logicalId: z.string().min(1),
162
- })
163
- .strict();
164
- const brandAssetSchema = z
165
- .object({
166
- uri: z.union([httpsUrlSchema, packagedAssetSchema]),
167
- darkUri: z.union([httpsUrlSchema, packagedAssetSchema]).optional(),
168
- alt: z.string().trim().min(1),
169
- })
170
- .strict();
171
- const brandThemeTokensSchema = z
172
- .object({
173
- surface: hexColorSchema.optional(),
174
- surfaceRaised: hexColorSchema.optional(),
175
- surfaceMuted: hexColorSchema.optional(),
176
- text: hexColorSchema.optional(),
177
- textMuted: hexColorSchema.optional(),
178
- accent: hexColorSchema.optional(),
179
- accentText: hexColorSchema.optional(),
180
- link: hexColorSchema.optional(),
181
- border: hexColorSchema.optional(),
182
- borderStrong: hexColorSchema.optional(),
183
- focus: hexColorSchema.optional(),
184
- success: hexColorSchema.optional(),
185
- warning: hexColorSchema.optional(),
186
- danger: hexColorSchema.optional(),
187
- code: hexColorSchema.optional(),
188
- })
189
- .strict();
190
- const serverBrandingSchema = z
191
- .object({
192
- name: z.string().trim().min(1).optional(),
193
- accent: hexColorSchema.optional(),
194
- surface: hexColorSchema.optional(),
195
- surfaceDark: hexColorSchema.optional(),
196
- logo: brandAssetSchema.optional(),
197
- mark: brandAssetSchema.optional(),
198
- avatar: brandAssetSchema.optional(),
199
- theme: z
200
- .object({
201
- light: brandThemeTokensSchema.optional(),
202
- dark: brandThemeTokensSchema.optional(),
203
- })
204
- .strict()
205
- .optional(),
206
- radius: z.enum(['none', 'sm', 'md', 'lg']).optional(),
207
- density: z.enum(['compact', 'comfortable']).optional(),
208
- typography: z.enum(['system', 'serif', 'mono']).optional(),
209
- colorScheme: z.enum(['auto', 'light', 'dark']).optional(),
210
- })
211
- .strict();
212
137
  const assistantPresentationToneSchema = z.enum(['neutral', 'success', 'warning', 'danger']);
213
138
  const optionalStrictObject = (shape) => z.object(shape).strict().optional();
214
139
  const assistantPresentationSchema = z
@@ -357,6 +282,18 @@ const embeddedAssistantSchema = z
357
282
  // `assistantUiSchema.shape` — this is not renderer presentation, and a surface that
358
283
  // acquired it by a spread would be an accident rather than a decision.
359
284
  webmcp: optionalStrictObject({ enabled: z.boolean().optional() }),
285
+ // Anonymous cross-page display continuity (ADR 0223, clauses 11-16), declared per public
286
+ // or mixed surface. These bounds mirror the structural ceilings the gateway clamps to at
287
+ // runtime; the ADR is the single source, and this package cannot import the gateway
288
+ // because the dependency runs the other way. Refusing rather than clamping is deliberate:
289
+ // the gateway narrows an operator's value silently by design, but a developer who asks
290
+ // for more than the ceiling has made a mistake, and silence would hide it until someone
291
+ // measured the live behaviour.
292
+ continuity: optionalStrictObject({
293
+ enabled: z.boolean().optional(),
294
+ windowSeconds: z.number().int().min(0).max(600).optional(),
295
+ maxRestores: z.number().int().min(0).max(10).optional(),
296
+ }),
360
297
  })
361
298
  .strict())
362
299
  .min(1)
@@ -374,6 +311,17 @@ const embeddedAssistantSchema = z
374
311
  .strict()
375
312
  .superRefine((assistant, ctx) => {
376
313
  assistant.surfaces?.forEach((surface, index) => {
314
+ // The authenticated direction already reattaches through ADR 0223 clause 7, authorized by a
315
+ // backend-verified subject rather than by possession of a handle. A `continuity` block here
316
+ // would be a developer believing they configured something nothing reads, so refuse it rather
317
+ // than drop it silently.
318
+ if (surface.mode === 'authenticated' && surface.continuity !== undefined) {
319
+ ctx.addIssue({
320
+ code: 'custom',
321
+ path: ['surfaces', index, 'continuity'],
322
+ message: 'continuity belongs on a public or mixed website surface',
323
+ });
324
+ }
377
325
  if (surface.mode === 'authenticated')
378
326
  return;
379
327
  // A public surface fails closed: an omitted allowlist would otherwise read as "expose
@@ -0,0 +1,25 @@
1
+ import type { AppPurgeReconciliationApplyRequestV1, AppPurgeReconciliationApplyResponseV1, AppPurgeReconciliationErrorCodeV1, AppPurgeReconciliationPreviewArtifactV1 } from '@noodle-borg/wire-contracts';
2
+ export interface AppPurgeReconciliationActor {
3
+ readonly subject: string;
4
+ readonly email?: string;
5
+ }
6
+ export interface AppPurgeReconciliationOperator {
7
+ preview(input: {
8
+ readonly releaseSha: string;
9
+ readonly limit: number;
10
+ readonly now: Date;
11
+ }): Promise<AppPurgeReconciliationPreviewArtifactV1>;
12
+ apply(input: {
13
+ readonly request: AppPurgeReconciliationApplyRequestV1;
14
+ readonly actor: AppPurgeReconciliationActor;
15
+ readonly currentReleaseSha: string;
16
+ readonly now: Date;
17
+ }): Promise<AppPurgeReconciliationApplyResponseV1>;
18
+ }
19
+ export type AppPurgeReconciliationConflictCode = Exclude<AppPurgeReconciliationErrorCodeV1, 'app_purge_reconciliation_unavailable'>;
20
+ /** A checked operator conflict; query and transaction failures intentionally remain ordinary errors. */
21
+ export declare class AppPurgeReconciliationError extends Error {
22
+ readonly code: AppPurgeReconciliationConflictCode;
23
+ constructor(code: AppPurgeReconciliationConflictCode, message: string);
24
+ }
25
+ //# sourceMappingURL=app-purge-reconciliation-port.d.ts.map
@@ -0,0 +1,10 @@
1
+ /** A checked operator conflict; query and transaction failures intentionally remain ordinary errors. */
2
+ export class AppPurgeReconciliationError extends Error {
3
+ code;
4
+ constructor(code, message) {
5
+ super(message);
6
+ this.name = 'AppPurgeReconciliationError';
7
+ this.code = code;
8
+ }
9
+ }
10
+ //# sourceMappingURL=app-purge-reconciliation-port.js.map
@@ -1,3 +1,4 @@
1
+ export { type AppPurgeReconciliationActor, type AppPurgeReconciliationConflictCode, AppPurgeReconciliationError, type AppPurgeReconciliationOperator, } from './app-purge-reconciliation-port.js';
1
2
  export type { ActiveMcpSubdomainClaim, ChangeMcpSubdomainInput, ControlPlaneIdentity, CreateOrgWithOwnerInput, McpSubdomainMutationResult, McpSubdomainSetting, OrganizationStore, OrgDomainRecord, OrgInvitationRecord, OrgMemberRecord, OrgOpenAIAppsChallengeRecord, OrgRecord, OrgRole, PersonalWorkspaceProvisionInput, PersonalWorkspaceProvisionResult, SignupAllowlistKind, SignupAllowlistRecord, WelcomeEmailRecord, } from './contracts.js';
2
3
  export { allowAllGate, bearerToken, type ControlPlaneAuthResult, type DeployAuthGate, } from './deploy-auth.js';
3
4
  export { CompositeControlPlaneGate, type ControlPlaneSignupMode, GoogleControlPlaneGate, type GoogleControlPlaneGateOptions, type GoogleIdTokenVerifier, GoogleWorkloadControlPlaneGate, type GoogleWorkloadControlPlaneGateOptions, NoodleOAuthControlPlaneGate, type NoodleOAuthControlPlaneGateOptions, type SignupAuthorizer, } from './deploy-gates.js';
@@ -1,3 +1,4 @@
1
+ export { AppPurgeReconciliationError, } from './app-purge-reconciliation-port.js';
1
2
  export { allowAllGate, bearerToken, } from './deploy-auth.js';
2
3
  export { CompositeControlPlaneGate, GoogleControlPlaneGate, GoogleWorkloadControlPlaneGate, NoodleOAuthControlPlaneGate, } from './deploy-gates.js';
3
4
  export { InMemoryControlPlaneStore } from './in-memory-control-plane-store.js';
@@ -26,7 +26,9 @@
26
26
  "typecheck": "tsc -p tsconfig.json --noEmit"
27
27
  },
28
28
  "dependencies": {
29
- "@noodle-borg/module": "0.0.0"
29
+ "@noodle-borg/module": "0.0.0",
30
+ "@noodle-borg/module-audit": "0.0.0",
31
+ "@noodle-borg/wire-contracts": "0.0.0"
30
32
  },
31
33
  "devDependencies": {
32
34
  "@types/node": "^24.13.1",
@@ -0,0 +1,72 @@
1
+ import { AppPurgeReconciliationError, } from '@noodle-borg/control-plane/portable';
2
+ import { readJsonBody, sendJson } from '@noodle-borg/transport-http';
3
+ import { APP_PURGE_RECONCILIATION_MAX_CANDIDATES, AppPurgeReconciliationApplyRequestSchema, AppPurgeReconciliationApplyResponseSchema, AppPurgeReconciliationPreviewRequestSchema, AppPurgeReconciliationPreviewResponseSchema, } from '@noodle-borg/wire-contracts';
4
+ import { sendForbidden } from '../http-util.js';
5
+ import { authorizeControlPlane } from './control-plane.js';
6
+ const UNAVAILABLE = {
7
+ ok: false,
8
+ code: 'app_purge_reconciliation_unavailable',
9
+ error: 'app purge reconciliation is unavailable',
10
+ };
11
+ /** Super-admin-only, exact-set historical repair boundary from ADR 0225. */
12
+ export async function handleAppPurgeReconciliationRoute(req, res, deps, action) {
13
+ const identity = await authorizeControlPlane(req, res, deps.gate, { requireIdentity: true });
14
+ if (identity === false)
15
+ return;
16
+ if (!identity.superAdmin)
17
+ return sendForbidden(res, 'super-admin required');
18
+ const url = new URL(req.url ?? '/', 'http://localhost');
19
+ if (url.search !== '') {
20
+ return sendJson(res, 400, { ok: false, error: 'query parameters are not supported' });
21
+ }
22
+ const contentType = req.headers['content-type']?.split(';', 1)[0]?.trim().toLowerCase();
23
+ if (contentType !== 'application/json') {
24
+ return sendJson(res, 415, { ok: false, error: 'content type must be application/json' });
25
+ }
26
+ const body = await readJsonBody(req, deps.maxBody);
27
+ if (!body.ok)
28
+ return sendJson(res, body.status, { ok: false, error: body.error });
29
+ const parsed = action === 'preview'
30
+ ? AppPurgeReconciliationPreviewRequestSchema.safeParse(body.value)
31
+ : AppPurgeReconciliationApplyRequestSchema.safeParse(body.value);
32
+ if (!parsed.success) {
33
+ return sendJson(res, 400, { ok: false, error: 'invalid app purge reconciliation request' });
34
+ }
35
+ if (deps.operator === undefined || !/^[0-9a-f]{40}$/.test(deps.releaseSha)) {
36
+ return sendJson(res, 503, UNAVAILABLE);
37
+ }
38
+ try {
39
+ const now = deps.clock?.() ?? new Date();
40
+ if (action === 'preview') {
41
+ const request = AppPurgeReconciliationPreviewRequestSchema.parse(parsed.data);
42
+ const artifact = await deps.operator.preview({
43
+ releaseSha: deps.releaseSha,
44
+ limit: request.limit ?? APP_PURGE_RECONCILIATION_MAX_CANDIDATES,
45
+ now,
46
+ });
47
+ return sendJson(res, 200, AppPurgeReconciliationPreviewResponseSchema.parse({ ok: true, artifact }));
48
+ }
49
+ const request = AppPurgeReconciliationApplyRequestSchema.parse(parsed.data);
50
+ const response = await deps.operator.apply({
51
+ request,
52
+ actor: {
53
+ subject: identity.subject,
54
+ ...(identity.email === undefined ? {} : { email: identity.email }),
55
+ },
56
+ currentReleaseSha: deps.releaseSha,
57
+ now,
58
+ });
59
+ return sendJson(res, 200, AppPurgeReconciliationApplyResponseSchema.parse(response));
60
+ }
61
+ catch (error) {
62
+ if (error instanceof AppPurgeReconciliationError) {
63
+ return sendJson(res, 409, {
64
+ ok: false,
65
+ code: error.code,
66
+ error: 'app purge reconciliation request conflicts with current state',
67
+ });
68
+ }
69
+ return sendJson(res, 503, UNAVAILABLE);
70
+ }
71
+ }
72
+ //# sourceMappingURL=app-purge-reconciliation.js.map
@@ -3,7 +3,7 @@ import { ensurePersonalWorkspace } from '@noodle-borg/control-plane/portable';
3
3
  import { DEVELOPER_ASSISTANT_PATH, DEVELOPER_CLI_PATH, } from '@noodle-borg/developer-mcp';
4
4
  import { normalizeServerVersion } from '@noodle-borg/module';
5
5
  import { noopLogger, readDeployBody, sendJson } from '@noodle-borg/transport-http';
6
- import { deployRequestSchema, formatWireError, } from '@noodle-borg/wire-contracts';
6
+ import { deployRequestSchema, formatWireError, WhoamiIdentityResponseSchema, } from '@noodle-borg/wire-contracts';
7
7
  import { authorizeDeveloperGrant, } from '../auth/developer-grant-guard.js';
8
8
  import { baseFromRequest, sendForbidden, sendUnauthorized } from '../http-util.js';
9
9
  import { endpointUrlOptionsForOrg } from '../mcp-public-routing.js';
@@ -443,7 +443,7 @@ export async function authorizeDeploymentOwnerSelection(res, controlPlane, org,
443
443
  }
444
444
  return { ok: true, ownerSubject: requestedOwnerSubject };
445
445
  }
446
- export async function handleWhoami(req, res, gate, controlPlane, options = {}) {
446
+ export async function handleWhoami(req, res, gate, controlPlane, options = {}, scope) {
447
447
  // Identity introspection names no org or environment, so no grant context can describe it.
448
448
  const identity = await authorizeControlPlane(req, res, gate, {
449
449
  requireIdentity: true,
@@ -451,6 +451,19 @@ export async function handleWhoami(req, res, gate, controlPlane, options = {}) {
451
451
  });
452
452
  if (identity === false)
453
453
  return;
454
+ if (scope === 'identity') {
455
+ const response = WhoamiIdentityResponseSchema.parse({
456
+ ok: true,
457
+ data: {
458
+ identity: {
459
+ subject: identity.subject,
460
+ email: identity.email,
461
+ superAdmin: identity.superAdmin,
462
+ },
463
+ },
464
+ });
465
+ return sendJson(res, 200, response);
466
+ }
454
467
  if (!identity.superAdmin && options.controlPlaneSignupMode === 'public') {
455
468
  await ensurePersonalWorkspace(controlPlane, identity);
456
469
  }
@@ -23,6 +23,7 @@ import { isAuthServerPath } from './oauth/paths.js';
23
23
  import { createIntentTargetResolver, createObservabilityDispatcher, createObservabilityStores, } from './observability-runtime.js';
24
24
  import { handleAccessUpdate } from './routes/access.js';
25
25
  import { dispatchAlertRoutes } from './routes/alerts-dispatch.js';
26
+ import { handleAppPurgeReconciliationRoute, } from './routes/app-purge-reconciliation.js';
26
27
  import { handleAppArchive, handleAppRestore } from './routes/archive.js';
27
28
  import { handleAssetPreflight } from './routes/asset-control-plane.js';
28
29
  import { dispatchAssistantRoutes } from './routes/assistant-dispatch.js';
@@ -233,6 +234,26 @@ export function createServiceHandler(registry, options = {}) {
233
234
  ...(advanced ? { modules: capabilityReport.modules } : {}),
234
235
  });
235
236
  }
237
+ const appPurgeReconciliationAction = url.pathname === '/v1/service/app-purge-reconciliation/preview'
238
+ ? 'preview'
239
+ : url.pathname === '/v1/service/app-purge-reconciliation/apply'
240
+ ? 'apply'
241
+ : undefined;
242
+ if (req.method === 'POST' && appPurgeReconciliationAction !== undefined) {
243
+ applySecurityHeaders(res, tls);
244
+ if (enforceHttps(req, res, tls))
245
+ return;
246
+ void handleAppPurgeReconciliationRoute(req, res, {
247
+ gate,
248
+ ...(options.appPurgeReconciliationOperator === undefined
249
+ ? {}
250
+ : { operator: options.appPurgeReconciliationOperator }),
251
+ releaseSha: buildInfo.gitSha,
252
+ maxBody,
253
+ ...(options.clock === undefined ? {} : { clock: options.clock }),
254
+ }, appPurgeReconciliationAction);
255
+ return;
256
+ }
236
257
  if (servicePrincipalDispatcher?.(req, res, url))
237
258
  return;
238
259
  if (dispatchAssistantRoutes(req, res, url, {
@@ -316,7 +337,12 @@ export function createServiceHandler(registry, options = {}) {
316
337
  applySecurityHeaders(res, tls);
317
338
  if (enforceHttps(req, res, tls))
318
339
  return;
319
- handleWhoami(req, res, gate, controlPlane, options).catch(() => {
340
+ const scopes = url.searchParams.getAll('scope');
341
+ if (scopes.length > 1 || (scopes.length === 1 && scopes[0] !== 'identity')) {
342
+ sendJson(res, 400, { error: 'invalid whoami scope' });
343
+ return;
344
+ }
345
+ handleWhoami(req, res, gate, controlPlane, options, scopes[0] === 'identity' ? 'identity' : undefined).catch(() => {
320
346
  if (!res.headersSent)
321
347
  sendJson(res, 500, { error: 'internal error' });
322
348
  });
@@ -3,7 +3,7 @@ import { assertSameAppPackageSnapshot, sanitizeDeployRecordAppPackageSnapshot, }
3
3
  import { assertCustomerAuthRestorePrecondition, assertUniqueActiveCustomerAuthAudienceBindings, findActiveCustomerAuthAudienceConflict, } from '../customer-auth-audience-binding.js';
4
4
  import { assertDeploymentActivationUnlocked, assertDeploymentAppendUnlocked, } from '../deployment-lock.js';
5
5
  import { defaultActiveRecord, sameDeploymentScope, sameTenantRecord, } from '../deployment-versioning.js';
6
- import { appArchivedAt, deploymentSummary, matchesDeploymentFilter, paginateAppSummaries, planAppArchive, planAppRestore, preserveDeploymentOwnerState, resolveActiveAccessUpdate, resolveProductionEnvironment, summarizeApps, summarizeEnvs, validateDeploymentListFilter, withoutArchiveStamp, } from './records.js';
6
+ import { appArchivedAt, deploymentSummary, matchesDeploymentFilter, paginateAppSummaries, planAppArchive, planAppRestore, planArchivedAppSweep, preserveDeploymentOwnerState, resolveActiveAccessUpdate, resolveProductionEnvironment, summarizeApps, summarizeEnvs, validateDeploymentListFilter, withoutArchiveStamp, } from './records.js';
7
7
  import { validateSlug, validateTenantRef } from './validate.js';
8
8
  /** In-memory store: keeps records in a Map. Used by tests and as an explicit, non-persistent option. */
9
9
  export class InMemoryArtifactStore {
@@ -165,13 +165,9 @@ export class InMemoryArtifactStore {
165
165
  return Promise.resolve(plan.result);
166
166
  }
167
167
  sweepArchived(before) {
168
- const deleted = [];
169
- for (const [id, record] of this.#records) {
170
- if (record.archivedAt !== undefined && record.archivedAt < before) {
171
- deleted.push(record);
172
- this.#records.delete(id);
173
- }
174
- }
168
+ const deleted = planArchivedAppSweep([...this.#records.values()], before);
169
+ for (const record of deleted)
170
+ this.#records.delete(record.deploymentId);
175
171
  return Promise.resolve(deleted);
176
172
  }
177
173
  async listApps(org, opts = {}) {
@@ -6,7 +6,7 @@ import { assertSameAppPackageSnapshot, sanitizeDeployRecordAppPackageSnapshot, }
6
6
  import { assertCustomerAuthRestorePrecondition, assertUniqueActiveCustomerAuthAudienceBindings, findActiveCustomerAuthAudienceConflict, } from '../customer-auth-audience-binding.js';
7
7
  import { assertDeploymentActivationUnlocked, assertDeploymentAppendUnlocked, } from '../deployment-lock.js';
8
8
  import { defaultActiveRecord, sameDeploymentScope, sameTenantRecord, } from '../deployment-versioning.js';
9
- import { appArchivedAt, deploymentSummary, matchesDeploymentFilter, paginateAppSummaries, planAppArchive, planAppRestore, preserveDeploymentOwnerState, resolveActiveAccessUpdate, resolveProductionEnvironment, summarizeApps, summarizeEnvs, validateDeploymentListFilter, withoutArchiveStamp, } from './records.js';
9
+ import { appArchivedAt, deploymentSummary, matchesDeploymentFilter, paginateAppSummaries, planAppArchive, planAppRestore, planArchivedAppSweep, preserveDeploymentOwnerState, resolveActiveAccessUpdate, resolveProductionEnvironment, summarizeApps, summarizeEnvs, validateDeploymentListFilter, withoutArchiveStamp, } from './records.js';
10
10
  import { DEPLOYMENT_ID_PATTERN, validateSlug, validateTenantRef } from './validate.js';
11
11
  /**
12
12
  * JSON-file store: one file per deployment at `<dataDir>/deployments/<deploymentId>.json`, written atomically
@@ -228,8 +228,11 @@ export class JsonFileArtifactStore {
228
228
  await Promise.all(plan.clear.map((record) => this.#writeRecord(withoutArchiveStamp(record))));
229
229
  return plan.result;
230
230
  }
231
- async sweepArchived(before) {
232
- const deleted = (await this.loadAll()).filter((record) => record.archivedAt !== undefined && record.archivedAt < before);
231
+ sweepArchived(before) {
232
+ return this.#serializeLifecycle(() => this.#sweepArchivedUnlocked(before));
233
+ }
234
+ async #sweepArchivedUnlocked(before) {
235
+ const deleted = planArchivedAppSweep(await this.loadAll(), before);
233
236
  await Promise.all(deleted.map((record) => rm(join(this.#dir, `${record.deploymentId}.json`), { force: true })));
234
237
  return deleted;
235
238
  }
@@ -101,6 +101,26 @@ export function planAppRestore(records) {
101
101
  const clear = records.filter((record) => record.archivedAt !== undefined);
102
102
  return { result: { restoredDeployments: clear.length }, clear };
103
103
  }
104
+ /** Plan a retention sweep: only delete an app when every deployment is archived before the cutoff. */
105
+ export function planArchivedAppSweep(records, before) {
106
+ const apps = new Map();
107
+ for (const record of records) {
108
+ let byApp = apps.get(record.orgSlug);
109
+ if (byApp === undefined) {
110
+ byApp = new Map();
111
+ apps.set(record.orgSlug, byApp);
112
+ }
113
+ const bucket = byApp.get(record.appSlug);
114
+ if (bucket === undefined)
115
+ byApp.set(record.appSlug, [record]);
116
+ else
117
+ bucket.push(record);
118
+ }
119
+ return [...apps.values()].flatMap((byApp) => [...byApp.values()].flatMap((appRecords) => appRecords.length > 0 &&
120
+ appRecords.every((record) => record.archivedAt !== undefined && record.archivedAt < before)
121
+ ? appRecords
122
+ : []));
123
+ }
104
124
  /** Strip the archive stamp from a record (exact-optional safe). */
105
125
  export function withoutArchiveStamp(record) {
106
126
  const { archivedAt: _cleared, ...rest } = record;
@@ -318,8 +318,9 @@ export interface ArtifactStore {
318
318
  * App soft delete (ADR 0117). `getAppArchivedAt` reports the app's archived stamp (defined iff the
319
319
  * app has records and every record is stamped). `archiveApp` stamps every record for the app (all
320
320
  * environments/versions); `restoreApp` clears the stamps; both resolve `undefined` for an app with
321
- * no records. `sweepArchived` hard-deletes records archived strictly before `before` and returns
322
- * them so the caller can reclaim associated config and emit audit events.
321
+ * no records. `sweepArchived` hard-deletes a complete app only when every one of its records is archived
322
+ * strictly before `before`, returning every record from each deleted app so the caller can reclaim
323
+ * associated config and emit audit events.
323
324
  */
324
325
  getAppArchivedAt(org: string, app: string): Promise<string | undefined>;
325
326
  archiveApp(org: string, app: string, at: string): Promise<AppArchiveResult | undefined>;
@@ -39,7 +39,7 @@
39
39
  "dependencies": {
40
40
  "@modelcontextprotocol/sdk": "^1.29.0",
41
41
  "@noodle-borg/admission-limits": "0.0.0",
42
- "@noodle-borg/agent-kit": "0.92.0",
42
+ "@noodle-borg/agent-kit": "0.93.0",
43
43
  "@noodle-borg/app-package": "0.0.0",
44
44
  "@noodle-borg/assistant-gateway": "0.0.0",
45
45
  "@noodle-borg/auth": "0.0.0",