@wtfalch/foundry 0.16.16 → 0.17.1

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 (67) hide show
  1. package/dist/apply.d.ts +22 -1
  2. package/dist/bootstrap/bootstrap.d.ts +6 -1
  3. package/dist/bootstrap/bootstrap.js +26 -3
  4. package/dist/bootstrap/declare.d.ts +11 -1
  5. package/dist/bootstrap/declare.js +18 -6
  6. package/dist/index.d.ts +11 -6
  7. package/dist/index.js +8 -1
  8. package/dist/migrations/0038_calls_version_pin.sql +8 -0
  9. package/dist/migrations/0039_destroy_record.sql +7 -0
  10. package/dist/migrations/0040_company_included_uuid.sql +72 -0
  11. package/dist/migrations/0041_company_included_uuid_keys.sql +132 -0
  12. package/dist/migrations/0042_mutation_leases.sql +12 -0
  13. package/dist/migrations/0043_platform_names.sql +25 -0
  14. package/dist/migrations/0044_platform_mail_lettermint_project.sql +11 -0
  15. package/dist/org.d.ts +128 -3
  16. package/dist/org.js +186 -1
  17. package/dist/plan/adopt.d.ts +76 -0
  18. package/dist/plan/adopt.js +184 -0
  19. package/dist/plan/agora.d.ts +21 -0
  20. package/dist/plan/agora.js +37 -0
  21. package/dist/plan/broker-entries.d.ts +9 -1
  22. package/dist/plan/broker-entries.js +13 -5
  23. package/dist/plan/chat-matrix.d.ts +1 -2
  24. package/dist/plan/chat-matrix.js +15 -4
  25. package/dist/plan/core-files.d.ts +25 -0
  26. package/dist/plan/core-files.js +25 -0
  27. package/dist/plan/diff.d.ts +27 -3
  28. package/dist/plan/diff.js +264 -86
  29. package/dist/plan/edge-worker.d.ts +114 -0
  30. package/dist/plan/edge-worker.js +201 -0
  31. package/dist/plan/entry-ai-key.d.ts +3 -3
  32. package/dist/plan/entry-files-key.d.ts +3 -3
  33. package/dist/plan/entry-names-key.d.ts +73 -0
  34. package/dist/plan/entry-names-key.js +236 -0
  35. package/dist/plan/executor.d.ts +292 -24
  36. package/dist/plan/executor.js +745 -77
  37. package/dist/plan/index.d.ts +6 -2
  38. package/dist/plan/index.js +3 -1
  39. package/dist/plan/live.d.ts +9 -2
  40. package/dist/plan/login-host.d.ts +13 -2
  41. package/dist/plan/login-host.js +26 -7
  42. package/dist/plan/plan-apply.d.ts +153 -6
  43. package/dist/plan/plan-apply.js +639 -78
  44. package/dist/plan/platform-diff.d.ts +106 -1
  45. package/dist/plan/platform-diff.js +182 -2
  46. package/dist/plan/platform-org.d.ts +45 -1
  47. package/dist/plan/platform-org.js +34 -3
  48. package/dist/plan/platform-providers.d.ts +45 -1
  49. package/dist/plan/platform-providers.js +585 -16
  50. package/dist/plan/providers-readers.d.ts +2 -0
  51. package/dist/plan/providers-readers.js +4 -0
  52. package/dist/plan/rollout.d.ts +52 -0
  53. package/dist/plan/rollout.js +37 -0
  54. package/dist/plan/step-system.d.ts +22 -0
  55. package/dist/plan/step-system.js +38 -0
  56. package/dist/plan.d.ts +3 -0
  57. package/dist/platform.d.ts +22 -3
  58. package/dist/platform.js +6 -0
  59. package/dist/state.d.ts +13 -0
  60. package/dist/store/audit.js +15 -1
  61. package/dist/store/postgres.d.ts +1 -1
  62. package/dist/store/postgres.js +142 -15
  63. package/dist/store/schema.d.ts +202 -5
  64. package/dist/store/schema.js +18 -1
  65. package/dist/store.d.ts +47 -4
  66. package/dist/store.js +9 -1
  67. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import type { AppEnvSource, HostingEntry, IssuerApp, OwnerIdentity } from '../org.js';
2
2
  import type { Plan } from '../plan.js';
3
- import type { Checkpoint } from '../state.js';
3
+ import type { Checkpoint, DestroyRecord } from '../state.js';
4
4
  /**
5
5
  * Mirrors `migrations/0001_declared_org.sql`, which is the source of truth:
6
6
  * the SQL carries the CHECKs drizzle-kit does not generate here (this
@@ -270,6 +270,40 @@ export declare const declaredOrgs: import("drizzle-orm/pg-core").PgTableWithColu
270
270
  identity: undefined;
271
271
  generated: undefined;
272
272
  }, {}, {}>;
273
+ callsVersionPinSynapse: import("drizzle-orm/pg-core").PgColumn<{
274
+ name: "calls_version_pin_synapse";
275
+ tableName: "foundry_declared_orgs";
276
+ dataType: "string";
277
+ columnType: "PgText";
278
+ data: string;
279
+ driverParam: string;
280
+ notNull: false;
281
+ hasDefault: false;
282
+ isPrimaryKey: false;
283
+ isAutoincrement: false;
284
+ hasRuntimeDefault: false;
285
+ enumValues: [string, ...string[]];
286
+ baseColumn: never;
287
+ identity: undefined;
288
+ generated: undefined;
289
+ }, {}, {}>;
290
+ callsVersionPinMas: import("drizzle-orm/pg-core").PgColumn<{
291
+ name: "calls_version_pin_mas";
292
+ tableName: "foundry_declared_orgs";
293
+ dataType: "string";
294
+ columnType: "PgText";
295
+ data: string;
296
+ driverParam: string;
297
+ notNull: false;
298
+ hasDefault: false;
299
+ isPrimaryKey: false;
300
+ isAutoincrement: false;
301
+ hasRuntimeDefault: false;
302
+ enumValues: [string, ...string[]];
303
+ baseColumn: never;
304
+ identity: undefined;
305
+ generated: undefined;
306
+ }, {}, {}>;
273
307
  issuerUsesDomain: import("drizzle-orm/pg-core").PgColumn<{
274
308
  name: "issuer_uses_domain";
275
309
  tableName: "foundry_declared_orgs";
@@ -945,6 +979,101 @@ export declare const foundryAppliedState: import("drizzle-orm/pg-core").PgTableW
945
979
  identity: undefined;
946
980
  generated: undefined;
947
981
  }, {}, {}>;
982
+ destroy: import("drizzle-orm/pg-core").PgColumn<{
983
+ name: "destroy";
984
+ tableName: "foundry_applied_state";
985
+ dataType: "json";
986
+ columnType: "PgJsonb";
987
+ data: DestroyRecord;
988
+ driverParam: unknown;
989
+ notNull: false;
990
+ hasDefault: false;
991
+ isPrimaryKey: false;
992
+ isAutoincrement: false;
993
+ hasRuntimeDefault: false;
994
+ enumValues: undefined;
995
+ baseColumn: never;
996
+ identity: undefined;
997
+ generated: undefined;
998
+ }, {}, {
999
+ $type: DestroyRecord;
1000
+ }>;
1001
+ };
1002
+ dialect: "pg";
1003
+ }>;
1004
+ /** Mirrors `migrations/0042_mutation_leases.sql`: one row per lock scope; `token` only ever rises (#441). */
1005
+ export declare const foundryMutationLeases: import("drizzle-orm/pg-core").PgTableWithColumns<{
1006
+ name: "foundry_mutation_leases";
1007
+ schema: undefined;
1008
+ columns: {
1009
+ scope: import("drizzle-orm/pg-core").PgColumn<{
1010
+ name: "scope";
1011
+ tableName: "foundry_mutation_leases";
1012
+ dataType: "string";
1013
+ columnType: "PgText";
1014
+ data: string;
1015
+ driverParam: string;
1016
+ notNull: true;
1017
+ hasDefault: false;
1018
+ isPrimaryKey: true;
1019
+ isAutoincrement: false;
1020
+ hasRuntimeDefault: false;
1021
+ enumValues: [string, ...string[]];
1022
+ baseColumn: never;
1023
+ identity: undefined;
1024
+ generated: undefined;
1025
+ }, {}, {}>;
1026
+ token: import("drizzle-orm/pg-core").PgColumn<{
1027
+ name: "token";
1028
+ tableName: "foundry_mutation_leases";
1029
+ dataType: "number";
1030
+ columnType: "PgBigInt53";
1031
+ data: number;
1032
+ driverParam: string | number;
1033
+ notNull: true;
1034
+ hasDefault: false;
1035
+ isPrimaryKey: false;
1036
+ isAutoincrement: false;
1037
+ hasRuntimeDefault: false;
1038
+ enumValues: undefined;
1039
+ baseColumn: never;
1040
+ identity: undefined;
1041
+ generated: undefined;
1042
+ }, {}, {}>;
1043
+ holder: import("drizzle-orm/pg-core").PgColumn<{
1044
+ name: "holder";
1045
+ tableName: "foundry_mutation_leases";
1046
+ dataType: "string";
1047
+ columnType: "PgText";
1048
+ data: string;
1049
+ driverParam: string;
1050
+ notNull: true;
1051
+ hasDefault: false;
1052
+ isPrimaryKey: false;
1053
+ isAutoincrement: false;
1054
+ hasRuntimeDefault: false;
1055
+ enumValues: [string, ...string[]];
1056
+ baseColumn: never;
1057
+ identity: undefined;
1058
+ generated: undefined;
1059
+ }, {}, {}>;
1060
+ expiresAt: import("drizzle-orm/pg-core").PgColumn<{
1061
+ name: "expires_at";
1062
+ tableName: "foundry_mutation_leases";
1063
+ dataType: "date";
1064
+ columnType: "PgTimestamp";
1065
+ data: Date;
1066
+ driverParam: string;
1067
+ notNull: true;
1068
+ hasDefault: false;
1069
+ isPrimaryKey: false;
1070
+ isAutoincrement: false;
1071
+ hasRuntimeDefault: false;
1072
+ enumValues: undefined;
1073
+ baseColumn: never;
1074
+ identity: undefined;
1075
+ generated: undefined;
1076
+ }, {}, {}>;
948
1077
  };
949
1078
  dialect: "pg";
950
1079
  }>;
@@ -1107,7 +1236,7 @@ export declare const foundryAuditEvents: import("drizzle-orm/pg-core").PgTableWi
1107
1236
  tableName: "foundry_audit_events";
1108
1237
  dataType: "string";
1109
1238
  columnType: "PgText";
1110
- data: "plan" | "apply" | "destroy";
1239
+ data: "apply" | "plan" | "destroy";
1111
1240
  driverParam: string;
1112
1241
  notNull: true;
1113
1242
  hasDefault: false;
@@ -1119,7 +1248,7 @@ export declare const foundryAuditEvents: import("drizzle-orm/pg-core").PgTableWi
1119
1248
  identity: undefined;
1120
1249
  generated: undefined;
1121
1250
  }, {}, {
1122
- $type: "plan" | "apply" | "destroy";
1251
+ $type: "apply" | "plan" | "destroy";
1123
1252
  }>;
1124
1253
  actorId: import("drizzle-orm/pg-core").PgColumn<{
1125
1254
  name: "actor_id";
@@ -1177,7 +1306,7 @@ export declare const foundryAuditEvents: import("drizzle-orm/pg-core").PgTableWi
1177
1306
  tableName: "foundry_audit_events";
1178
1307
  dataType: "string";
1179
1308
  columnType: "PgText";
1180
- data: "applied" | "refused" | "failed" | "destroyed";
1309
+ data: "applied" | "failed" | "refused" | "destroyed";
1181
1310
  driverParam: string;
1182
1311
  notNull: false;
1183
1312
  hasDefault: false;
@@ -1189,7 +1318,7 @@ export declare const foundryAuditEvents: import("drizzle-orm/pg-core").PgTableWi
1189
1318
  identity: undefined;
1190
1319
  generated: undefined;
1191
1320
  }, {}, {
1192
- $type: "applied" | "refused" | "failed" | "destroyed";
1321
+ $type: "applied" | "failed" | "refused" | "destroyed";
1193
1322
  }>;
1194
1323
  reason: import("drizzle-orm/pg-core").PgColumn<{
1195
1324
  name: "reason";
@@ -1429,6 +1558,23 @@ export declare const foundryDeclaredPlatform: import("drizzle-orm/pg-core").PgTa
1429
1558
  identity: undefined;
1430
1559
  generated: undefined;
1431
1560
  }, {}, {}>;
1561
+ mailLettermintProjectId: import("drizzle-orm/pg-core").PgColumn<{
1562
+ name: "mail_lettermint_project_id";
1563
+ tableName: "foundry_declared_platform";
1564
+ dataType: "string";
1565
+ columnType: "PgText";
1566
+ data: string;
1567
+ driverParam: string;
1568
+ notNull: false;
1569
+ hasDefault: false;
1570
+ isPrimaryKey: false;
1571
+ isAutoincrement: false;
1572
+ hasRuntimeDefault: false;
1573
+ enumValues: [string, ...string[]];
1574
+ baseColumn: never;
1575
+ identity: undefined;
1576
+ generated: undefined;
1577
+ }, {}, {}>;
1432
1578
  sfuEnabled: import("drizzle-orm/pg-core").PgColumn<{
1433
1579
  name: "sfu_enabled";
1434
1580
  tableName: "foundry_declared_platform";
@@ -1735,6 +1881,57 @@ export declare const foundryDeclaredPlatform: import("drizzle-orm/pg-core").PgTa
1735
1881
  identity: undefined;
1736
1882
  generated: undefined;
1737
1883
  }, {}, {}>;
1884
+ namesEnabled: import("drizzle-orm/pg-core").PgColumn<{
1885
+ name: "names_enabled";
1886
+ tableName: "foundry_declared_platform";
1887
+ dataType: "boolean";
1888
+ columnType: "PgBoolean";
1889
+ data: boolean;
1890
+ driverParam: boolean;
1891
+ notNull: true;
1892
+ hasDefault: true;
1893
+ isPrimaryKey: false;
1894
+ isAutoincrement: false;
1895
+ hasRuntimeDefault: false;
1896
+ enumValues: undefined;
1897
+ baseColumn: never;
1898
+ identity: undefined;
1899
+ generated: undefined;
1900
+ }, {}, {}>;
1901
+ namesHostname: import("drizzle-orm/pg-core").PgColumn<{
1902
+ name: "names_hostname";
1903
+ tableName: "foundry_declared_platform";
1904
+ dataType: "string";
1905
+ columnType: "PgText";
1906
+ data: string;
1907
+ driverParam: string;
1908
+ notNull: true;
1909
+ hasDefault: false;
1910
+ isPrimaryKey: false;
1911
+ isAutoincrement: false;
1912
+ hasRuntimeDefault: false;
1913
+ enumValues: [string, ...string[]];
1914
+ baseColumn: never;
1915
+ identity: undefined;
1916
+ generated: undefined;
1917
+ }, {}, {}>;
1918
+ namesGitRef: import("drizzle-orm/pg-core").PgColumn<{
1919
+ name: "names_git_ref";
1920
+ tableName: "foundry_declared_platform";
1921
+ dataType: "string";
1922
+ columnType: "PgText";
1923
+ data: string;
1924
+ driverParam: string;
1925
+ notNull: true;
1926
+ hasDefault: true;
1927
+ isPrimaryKey: false;
1928
+ isAutoincrement: false;
1929
+ hasRuntimeDefault: false;
1930
+ enumValues: [string, ...string[]];
1931
+ baseColumn: never;
1932
+ identity: undefined;
1933
+ generated: undefined;
1934
+ }, {}, {}>;
1738
1935
  serviceIds: import("drizzle-orm/pg-core").PgColumn<{
1739
1936
  name: "service_ids";
1740
1937
  tableName: "foundry_declared_platform";
@@ -1,4 +1,4 @@
1
- import { bigserial, boolean, integer, jsonb, pgTable, text, timestamp } from 'drizzle-orm/pg-core';
1
+ import { bigint, bigserial, boolean, integer, jsonb, pgTable, text, timestamp, } from 'drizzle-orm/pg-core';
2
2
  /**
3
3
  * Mirrors `migrations/0001_declared_org.sql`, which is the source of truth:
4
4
  * the SQL carries the CHECKs drizzle-kit does not generate here (this
@@ -31,6 +31,9 @@ export const declaredOrgs = pgTable('foundry_declared_orgs', {
31
31
  chatUsesDomain: boolean('chat_uses_domain').notNull().default(false),
32
32
  callsEnabled: boolean('calls_enabled').notNull().default(false),
33
33
  callsUsesDomain: boolean('calls_uses_domain').notNull().default(false),
34
+ // #259: null holds no pin -- the stack follows versions.json, same as today.
35
+ callsVersionPinSynapse: text('calls_version_pin_synapse'),
36
+ callsVersionPinMas: text('calls_version_pin_mas'),
34
37
  issuerUsesDomain: boolean('issuer_uses_domain').notNull().default(false),
35
38
  // SiteConfig (org.ts, T10). A brand new org has no cms site.
36
39
  siteEnabled: boolean('site_enabled').notNull().default(false),
@@ -101,6 +104,14 @@ export const foundryAppliedState = pgTable('foundry_applied_state', {
101
104
  failedFields: jsonb('failed_fields').notNull().default([]).$type(),
102
105
  lastAppliedAt: timestamp('last_applied_at', { withTimezone: true }),
103
106
  lastAppliedDeclaredOrgVersion: text('last_applied_declared_org_version'),
107
+ destroy: jsonb('destroy').$type(),
108
+ });
109
+ /** Mirrors `migrations/0042_mutation_leases.sql`: one row per lock scope; `token` only ever rises (#441). */
110
+ export const foundryMutationLeases = pgTable('foundry_mutation_leases', {
111
+ scope: text('scope').primaryKey(),
112
+ token: bigint('token', { mode: 'number' }).notNull(),
113
+ holder: text('holder').notNull(),
114
+ expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
104
115
  });
105
116
  /**
106
117
  * Mirrors `migrations/0036_applied_versions.sql`, the source of truth: an
@@ -147,6 +158,8 @@ export const foundryDeclaredPlatform = pgTable('foundry_declared_platform', {
147
158
  mailEnabled: boolean('mail_enabled').notNull().default(false),
148
159
  mailHostname: text('mail_hostname').notNull(),
149
160
  mailGitRef: text('mail_git_ref').notNull().default(''),
161
+ /** #491, `migrations/0044_platform_mail_lettermint_project.sql`. Declared, never discovered. */
162
+ mailLettermintProjectId: text('mail_lettermint_project_id'),
150
163
  sfuEnabled: boolean('sfu_enabled').notNull().default(false),
151
164
  sfuHostname: text('sfu_hostname').notNull(),
152
165
  sfuGitRef: text('sfu_git_ref').notNull().default(''),
@@ -165,6 +178,10 @@ export const foundryDeclaredPlatform = pgTable('foundry_declared_platform', {
165
178
  archonEnabled: boolean('archon_enabled').notNull().default(false),
166
179
  archonHostname: text('archon_hostname').notNull(),
167
180
  archonGitRef: text('archon_git_ref').notNull().default(''),
181
+ /** #317 (F7), `migrations/0038_platform_names.sql`. */
182
+ namesEnabled: boolean('names_enabled').notNull().default(false),
183
+ namesHostname: text('names_hostname').notNull(),
184
+ namesGitRef: text('names_git_ref').notNull().default(''),
168
185
  /** `migrations/0018_platform_apply.sql`: the platform's applied state, `system:kind:key` to provider id. */
169
186
  serviceIds: jsonb('service_ids').$type().notNull().default({}),
170
187
  createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
package/dist/store.d.ts CHANGED
@@ -13,6 +13,24 @@ export interface StaleUpdate {
13
13
  /** The org as it stands now. */
14
14
  current: DeclaredOrg;
15
15
  }
16
+ /**
17
+ * A held mutation lease (#441). `token` rises on every acquire of a scope, so
18
+ * a worker whose lease expired and was taken over holds a token the store no
19
+ * longer accepts: its fenced writes are refused.
20
+ */
21
+ export interface MutationLease {
22
+ scope: string;
23
+ token: number;
24
+ }
25
+ /** What `acquireMutationLease` returns while another holder has the scope: when that lease runs out. */
26
+ export interface HeldLease {
27
+ heldUntil: Date;
28
+ }
29
+ /** A fenced write (`putAppliedState`, `putServiceIds`) came from a lease that is no longer the current one (#441). Nothing was written. */
30
+ export declare class StaleLeaseError extends Error {
31
+ readonly scope: string;
32
+ constructor(scope: string);
33
+ }
16
34
  /**
17
35
  * The persistence seam https://github.com/wtfalch/foundry/issues/18
18
36
  * implements: create, read and edit a {@link DeclaredOrg}, and read and
@@ -44,6 +62,12 @@ export interface DeclaredOrgStore {
44
62
  * record instead.
45
63
  */
46
64
  findOrgIdForZone(zoneId: string): Promise<OrgId | null>;
65
+ /**
66
+ * Archon#271: an org other than `exceptOrgId` whose applied state records
67
+ * `id` as a Coolify app, database or project, or `null`. Adopt refuses a
68
+ * resource another org already holds.
69
+ */
70
+ findOtherOrgRecordingCoolifyId(id: string, exceptOrgId: OrgId): Promise<OrgId | null>;
47
71
  /**
48
72
  * Combinations foundry knows are impossible are rejected with a reason
49
73
  * (#18 "Done when"), so a caller must be ready for this to throw rather
@@ -58,7 +82,26 @@ export interface DeclaredOrgStore {
58
82
  */
59
83
  update(id: OrgId, patch: Partial<DeclaredOrg>, opts: UpdateOptions): Promise<DeclaredOrg | StaleUpdate>;
60
84
  getAppliedState(id: OrgId): Promise<AppliedState | null>;
61
- putAppliedState(state: AppliedState): Promise<void>;
85
+ /**
86
+ * With `fence` (#441), compare-and-set: the write happens only while
87
+ * `fence.token` is still the scope's current lease token, otherwise it
88
+ * throws {@link StaleLeaseError} and writes nothing.
89
+ */
90
+ putAppliedState(state: AppliedState, fence?: MutationLease): Promise<void>;
91
+ /**
92
+ * #441: takes the exclusive mutation lease for `scope` (`company:<orgId>`
93
+ * or `platform`), or returns {@link HeldLease} while another holder's lease
94
+ * has not expired. Every successful acquire issues a higher token than the last.
95
+ */
96
+ acquireMutationLease(scope: string, holder: string, ttlMs: number): Promise<MutationLease | HeldLease>;
97
+ /**
98
+ * Extends the lease to `ttlMs` from now, only while `lease.token` is still
99
+ * the scope's current token. Returns false once it is not: the holder lost
100
+ * the scope and must stop before its next provider step.
101
+ */
102
+ renewMutationLease(lease: MutationLease, ttlMs: number): Promise<boolean>;
103
+ /** Frees the lease at once. A no-op if the lease was already taken over. */
104
+ releaseMutationLease(lease: MutationLease): Promise<void>;
62
105
  /**
63
106
  * Removes the declared org and its applied state together. Called only by
64
107
  * `FoundryPlanApply.destroy` (`plan/plan-apply.ts`), once every resource it
@@ -68,7 +111,7 @@ export interface DeclaredOrgStore {
68
111
  * not an error, so a retried destroy that reaches this step twice never
69
112
  * throws.
70
113
  */
71
- delete(id: OrgId): Promise<void>;
114
+ delete(id: OrgId, fence?: MutationLease): Promise<void>;
72
115
  }
73
116
  /**
74
117
  * The persistence seam for #166's {@link DeclaredPlatform}, mirroring
@@ -87,8 +130,8 @@ export interface PlatformStore {
87
130
  * before the first apply. `put` never touches it.
88
131
  */
89
132
  getServiceIds(): Promise<Record<string, string>>;
90
- /** Replaces the recorded ids. A no-op until the platform is declared. */
91
- putServiceIds(serviceIds: Record<string, string>): Promise<void>;
133
+ /** Replaces the recorded ids. A no-op until the platform is declared. With `fence` (#441), compare-and-set as {@link DeclaredOrgStore.putAppliedState}. */
134
+ putServiceIds(serviceIds: Record<string, string>, fence?: MutationLease): Promise<void>;
92
135
  }
93
136
  /**
94
137
  * One recorded apply, appended to the history #253 keeps beside
package/dist/store.js CHANGED
@@ -1 +1,9 @@
1
- export {};
1
+ /** A fenced write (`putAppliedState`, `putServiceIds`) came from a lease that is no longer the current one (#441). Nothing was written. */
2
+ export class StaleLeaseError extends Error {
3
+ scope;
4
+ constructor(scope) {
5
+ super(`mutation lease for ${scope} was lost to a newer holder; nothing written`);
6
+ this.scope = scope;
7
+ this.name = 'StaleLeaseError';
8
+ }
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wtfalch/foundry",
3
- "version": "0.16.16",
3
+ "version": "0.17.1",
4
4
  "description": "Foundry's SDK: the declared org's types, the plan and apply contracts, and the state store's interface.",
5
5
  "repository": {
6
6
  "type": "git",