@wtfalch/foundry 0.16.16 → 0.17.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/apply.d.ts +22 -1
- package/dist/bootstrap/bootstrap.d.ts +6 -1
- package/dist/bootstrap/bootstrap.js +26 -3
- package/dist/bootstrap/declare.d.ts +11 -1
- package/dist/bootstrap/declare.js +12 -2
- package/dist/index.d.ts +9 -4
- package/dist/index.js +7 -0
- package/dist/migrations/0038_calls_version_pin.sql +8 -0
- package/dist/migrations/0039_destroy_record.sql +7 -0
- package/dist/migrations/0040_company_included_uuid.sql +72 -0
- package/dist/migrations/0041_company_included_uuid_keys.sql +132 -0
- package/dist/migrations/0042_mutation_leases.sql +12 -0
- package/dist/org.d.ts +54 -0
- package/dist/org.js +43 -1
- package/dist/plan/adopt.d.ts +76 -0
- package/dist/plan/adopt.js +184 -0
- package/dist/plan/agora.d.ts +21 -0
- package/dist/plan/agora.js +37 -0
- package/dist/plan/broker-entries.d.ts +9 -1
- package/dist/plan/broker-entries.js +13 -5
- package/dist/plan/chat-matrix.d.ts +1 -1
- package/dist/plan/chat-matrix.js +11 -3
- package/dist/plan/core-files.d.ts +25 -0
- package/dist/plan/core-files.js +25 -0
- package/dist/plan/diff.d.ts +11 -3
- package/dist/plan/diff.js +27 -62
- package/dist/plan/entry-ai-key.d.ts +3 -3
- package/dist/plan/entry-files-key.d.ts +3 -3
- package/dist/plan/executor.d.ts +57 -23
- package/dist/plan/executor.js +134 -50
- package/dist/plan/index.d.ts +4 -0
- package/dist/plan/index.js +2 -0
- package/dist/plan/live.d.ts +5 -0
- package/dist/plan/login-host.d.ts +1 -1
- package/dist/plan/login-host.js +6 -5
- package/dist/plan/plan-apply.d.ts +139 -6
- package/dist/plan/plan-apply.js +546 -76
- package/dist/plan/platform-diff.d.ts +14 -0
- package/dist/plan/platform-diff.js +16 -1
- package/dist/plan/platform-org.d.ts +16 -0
- package/dist/plan/platform-org.js +17 -1
- package/dist/plan/platform-providers.d.ts +1 -1
- package/dist/plan/platform-providers.js +73 -11
- package/dist/plan/providers-readers.d.ts +2 -0
- package/dist/plan/providers-readers.js +4 -0
- package/dist/plan/rollout.d.ts +52 -0
- package/dist/plan/rollout.js +37 -0
- package/dist/plan/step-system.d.ts +22 -0
- package/dist/plan/step-system.js +37 -0
- package/dist/plan.d.ts +3 -0
- package/dist/state.d.ts +13 -0
- package/dist/store/audit.js +15 -1
- package/dist/store/postgres.d.ts +1 -1
- package/dist/store/postgres.js +127 -14
- package/dist/store/schema.d.ts +134 -5
- package/dist/store/schema.js +12 -1
- package/dist/store.d.ts +47 -4
- package/dist/store.js +9 -1
- package/package.json +1 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { PlanApply } from '../apply.js';
|
|
1
|
+
import type { DestroyProgress, PlanApply } from '../apply.js';
|
|
2
2
|
import type { DeclaredOrg, OrgId } from '../org.js';
|
|
3
3
|
import type { Plan, PlanStep } from '../plan.js';
|
|
4
4
|
import type { DeclaredPlatform } from '../platform.js';
|
|
5
|
+
import type { JobRunner } from '../runner/types.js';
|
|
5
6
|
import type { AppliedState } from '../state.js';
|
|
6
|
-
import type
|
|
7
|
+
import { type AppliedVersionsStore, type DeclaredOrgStore, type PlatformStore } from '../store.js';
|
|
8
|
+
import { type AdoptCandidate, type AdoptInput, type AdoptPlan } from './adopt.js';
|
|
7
9
|
import { type AgoraHostPort } from './agora.js';
|
|
8
10
|
import { type StepAction } from './diff.js';
|
|
9
11
|
import { type LiveReaders, type LiveResource, type PlatformLiveReaders } from './live.js';
|
|
@@ -50,6 +52,16 @@ export interface AuditLog {
|
|
|
50
52
|
target?: 'platform';
|
|
51
53
|
at: Date;
|
|
52
54
|
}): Promise<void>;
|
|
55
|
+
/** `FoundryPlanApply.adopt` and `release` (archon#271): no `Plan` is applied, so this is its own entry. `entryKey` names the hosting entry. */
|
|
56
|
+
recordAdoption(entry: {
|
|
57
|
+
actor: AuditActor;
|
|
58
|
+
orgId: OrgId;
|
|
59
|
+
action: 'adopt' | 'release';
|
|
60
|
+
entryKey: string;
|
|
61
|
+
outcome: 'adopted' | 'released' | 'refused';
|
|
62
|
+
reason?: string;
|
|
63
|
+
at: Date;
|
|
64
|
+
}): Promise<void>;
|
|
53
65
|
/** `FoundryPlanApply.destroy`'s own record -- no `Plan` involved, so this is its own entry shape rather than a reuse of `recordApply`'s. */
|
|
54
66
|
recordDestroy(entry: {
|
|
55
67
|
actor: AuditActor;
|
|
@@ -249,6 +261,13 @@ export interface StepDestroyer {
|
|
|
249
261
|
readonly recordedIds: Readonly<Record<string, string>>;
|
|
250
262
|
}): Promise<void>;
|
|
251
263
|
}
|
|
264
|
+
/** #441: another apply, destroy, adopt or release holds the lease for this company or the platform. Nothing ran; try again when it finishes. */
|
|
265
|
+
export declare class MutationInProgressError extends Error {
|
|
266
|
+
readonly scope: string;
|
|
267
|
+
readonly heldUntil: Date;
|
|
268
|
+
/** @param heldUntil when the holder's lease runs out if it is never released. */
|
|
269
|
+
constructor(scope: string, heldUntil: Date);
|
|
270
|
+
}
|
|
252
271
|
export declare class DeclaredOrgNotFoundError extends Error {
|
|
253
272
|
constructor(orgId: OrgId);
|
|
254
273
|
}
|
|
@@ -323,6 +342,27 @@ export interface FoundryPlanApplyOptions {
|
|
|
323
342
|
aiOrganisation?: boolean;
|
|
324
343
|
/** Overridable for tests; defaults to `() => new Date()`. */
|
|
325
344
|
now?: () => Date;
|
|
345
|
+
/** #441: how long a mutation lease lasts before another caller may take it over. Defaults to 30 minutes. */
|
|
346
|
+
leaseTtlMs?: number;
|
|
347
|
+
/**
|
|
348
|
+
* #441 (part b): a durable, DB-row-backed record of `apply`, `destroy`,
|
|
349
|
+
* `adopt` and `release` for one org, held under the same mutation lease
|
|
350
|
+
* above rather than instead of it -- the lease is what actually keeps two
|
|
351
|
+
* holders from running `fn` at once (`withRunnerSerialization`'s own doc
|
|
352
|
+
* comment explains why the runner cannot safely take that job on its
|
|
353
|
+
* own). Optional, and a no-op without it -- every existing caller of this
|
|
354
|
+
* constructor is unaffected, and the lease alone still serializes every
|
|
355
|
+
* mutation the way it already did (#441, part a).
|
|
356
|
+
*
|
|
357
|
+
* What it adds: a row that never expires on a timer the way the lease
|
|
358
|
+
* does, so a crashed holder leaves something a host can see and query
|
|
359
|
+
* (`JobRunner.currentRun`/`getRun`) rather than nothing but a scope that
|
|
360
|
+
* quietly frees itself. Resuming after a crash reuses that row's id
|
|
361
|
+
* rather than starting a fresh one. Pass the same host-supplied
|
|
362
|
+
* `JobRunner` (`createJobRunner`, migrated with `runner/schema.ts`) that a
|
|
363
|
+
* host may also poll directly for progress.
|
|
364
|
+
*/
|
|
365
|
+
runner?: JobRunner;
|
|
326
366
|
/**
|
|
327
367
|
* #253: appends a history row every time `runSteps` records a new
|
|
328
368
|
* version as applied, beside the single-snapshot `store.putAppliedState`
|
|
@@ -379,10 +419,49 @@ export declare class FoundryPlanApply implements PlanApply {
|
|
|
379
419
|
private readonly hostFiles;
|
|
380
420
|
private readonly hostAi;
|
|
381
421
|
private readonly now;
|
|
422
|
+
private readonly leaseTtlMs;
|
|
382
423
|
private readonly appliedVersions;
|
|
383
424
|
private readonly platformDeps;
|
|
425
|
+
private readonly runner;
|
|
384
426
|
constructor(options: FoundryPlanApplyOptions);
|
|
385
427
|
plan(id: OrgId): Promise<Plan>;
|
|
428
|
+
/**
|
|
429
|
+
* Archon#271: what adopting a live app and database into entry
|
|
430
|
+
* `input.entryKey` would do. A read, like {@link plan}: it reads the project
|
|
431
|
+
* through the reader and changes nothing. A mismatch is reported in the
|
|
432
|
+
* result, with no steps. Needs the apply permission, since it reads a
|
|
433
|
+
* project the caller names.
|
|
434
|
+
*/
|
|
435
|
+
planAdopt(id: OrgId, input: AdoptInput): Promise<AdoptPlan>;
|
|
436
|
+
/**
|
|
437
|
+
* Archon#271: the live apps and databases in `projectUuid` this org has not
|
|
438
|
+
* recorded, for Changes' "Live, not theirs". A read, but it needs the apply
|
|
439
|
+
* permission: it reads a project the caller names.
|
|
440
|
+
*/
|
|
441
|
+
adoptCandidates(id: OrgId, entryKey: string, projectUuid?: string): Promise<AdoptCandidate[]>;
|
|
442
|
+
/**
|
|
443
|
+
* Archon#271: records an app and database that already run in Coolify as
|
|
444
|
+
* entry `input.entryKey`'s resources, so the next plan finds them and plans
|
|
445
|
+
* no create. Verifies first and refuses on a mismatch or an entry that is
|
|
446
|
+
* already applied or held by another org. Same permission as {@link apply}.
|
|
447
|
+
* It never calls an executor: nothing in Coolify changes. Nothing redeploys
|
|
448
|
+
* here, but the next normal plan may propose updates to the adopted app
|
|
449
|
+
* that do (see `adopt.ts`); show that plan before applying it. Two
|
|
450
|
+
* concurrent adopts of one resource are not locked against each other.
|
|
451
|
+
*/
|
|
452
|
+
adopt(id: OrgId, input: AdoptInput): Promise<AppliedState>;
|
|
453
|
+
/**
|
|
454
|
+
* Archon#271: the undo of {@link adopt}. Drops the entry's recorded Coolify
|
|
455
|
+
* ids and its `adopted.<entryKey>` snapshot (and `coolify_project` when
|
|
456
|
+
* adopt recorded it and no other Coolify id remains). It leaves the
|
|
457
|
+
* declaration alone: the entry goes back to declared and not applied, so the
|
|
458
|
+
* next plan creates it, exactly as before adopt. The host then adopts it
|
|
459
|
+
* again or removes the entry from the declaration. Deletes and stops nothing
|
|
460
|
+
* in Coolify. Only an adopted entry can be released. Same permission as
|
|
461
|
+
* {@link apply}. Every path writes an audit row.
|
|
462
|
+
*/
|
|
463
|
+
release(id: OrgId, entryKey: string): Promise<AppliedState>;
|
|
464
|
+
private computeAdopt;
|
|
386
465
|
apply(plan: Plan, confirmedOrgName: string): Promise<AppliedState>;
|
|
387
466
|
/**
|
|
388
467
|
* After an org apply: gives the platform files runner a changed share-domain
|
|
@@ -390,9 +469,10 @@ export declare class FoundryPlanApply implements PlanApply {
|
|
|
390
469
|
* declared orgs) without a platform pass. Touches only the share hosts and
|
|
391
470
|
* redeploys the runner; the platform files step itself never runs here.
|
|
392
471
|
* Skips quietly for an actor who may not apply the platform, and when the
|
|
393
|
-
* platform files step was never applied.
|
|
394
|
-
*
|
|
395
|
-
*
|
|
472
|
+
* platform files step was never applied. It holds the platform lease for
|
|
473
|
+
* the refresh and fences its write with it; while another platform change
|
|
474
|
+
* holds the lease it skips (this class has no log), and the next platform
|
|
475
|
+
* apply does the refresh. Only the share-domains key is recorded. A failure is rethrown naming that the org part succeeded.
|
|
396
476
|
*/
|
|
397
477
|
private refreshPlatformFilesShare;
|
|
398
478
|
/**
|
|
@@ -421,7 +501,60 @@ export declare class FoundryPlanApply implements PlanApply {
|
|
|
421
501
|
* declared org in place with whatever remains recorded in its applied
|
|
422
502
|
* state, so a retried `destroy` only tears down what's left.
|
|
423
503
|
*/
|
|
424
|
-
destroy(id: OrgId, confirmedOrgName: string
|
|
504
|
+
destroy(id: OrgId, confirmedOrgName: string, options?: {
|
|
505
|
+
onProgress?: (event: DestroyProgress) => void;
|
|
506
|
+
}): Promise<void>;
|
|
507
|
+
/** The steps `destroy` runs for `id`, in order. Refuses a non-test org like `destroy` does; writes no audit row since it changes nothing. */
|
|
508
|
+
planDestroy(id: OrgId): Promise<{
|
|
509
|
+
steps: string[];
|
|
510
|
+
kept: string[];
|
|
511
|
+
}>;
|
|
512
|
+
/**
|
|
513
|
+
* #441 (part b): records `fn`'s run in the durable runner, when one is
|
|
514
|
+
* configured, and calls it. Must run with the mutation lease already
|
|
515
|
+
* held -- every one of this method's four callers (`apply`, `destroy`,
|
|
516
|
+
* `adopt`, `release`) calls it from inside its own `withMutationLease`
|
|
517
|
+
* callback, never around it. The reason is `JobRunner.runSteps` itself:
|
|
518
|
+
* it checkpoints a run's one step (the literal id `'mutate'`, the same
|
|
519
|
+
* for all four callers so they share one row per org) by a plain read of
|
|
520
|
+
* the row followed by a later blind write, with nothing to stop two
|
|
521
|
+
* concurrent calls for the same `runId` from both reading "not yet
|
|
522
|
+
* checkpointed" and both calling `fn` -- the runner sequences and
|
|
523
|
+
* checkpoints (its own doc comment), it does not itself exclude a second
|
|
524
|
+
* caller the way the lease's atomic acquire does. Holding the lease first
|
|
525
|
+
* is what makes it safe to reuse an existing, not-yet-succeeded run
|
|
526
|
+
* ({@link JobRunner.currentRun}) rather than refuse it outright: by the
|
|
527
|
+
* time this method runs, this caller is already the lease's sole holder,
|
|
528
|
+
* so any row `currentRun` finds can only be a previous, now-abandoned
|
|
529
|
+
* attempt (the lease's own atomic acquire already refused a second live
|
|
530
|
+
* caller before either of them got here) -- reusing it is the resume #9
|
|
531
|
+
* calls "a human decides when to call it again", not a race.
|
|
532
|
+
*
|
|
533
|
+
* Two *first* attempts for the same org can still race the insert itself
|
|
534
|
+
* when neither has an existing row yet -- `JobRunner.enqueueApply`'s own
|
|
535
|
+
* unique constraint decides it at the database level, a real race rather
|
|
536
|
+
* than one decided by this process's await order, and the loser's
|
|
537
|
+
* {@link AlreadyRunningError} is refused and audited through `refuse`,
|
|
538
|
+
* the same as a lost lease. In practice this needs two lease holders at
|
|
539
|
+
* once to reach it, which the lease itself already prevents; it is this
|
|
540
|
+
* method's defence against a bug in the lease's own acquire path, not
|
|
541
|
+
* the primary lock.
|
|
542
|
+
*
|
|
543
|
+
* `fn`'s result is threaded out through a closure, since
|
|
544
|
+
* {@link RunnerStep.run} returns nothing.
|
|
545
|
+
*/
|
|
546
|
+
private withRunnerSerialization;
|
|
547
|
+
/**
|
|
548
|
+
* #441: runs `fn` while holding the one mutation lease for `scope`, so two
|
|
549
|
+
* `FoundryPlanApply` instances on one store never run provider steps for the
|
|
550
|
+
* same company or platform at once. A second caller is refused with
|
|
551
|
+
* {@link MutationInProgressError}; `fn` gets the lease as the fence its
|
|
552
|
+
* store writes carry, so a worker that lost the lease cannot overwrite the
|
|
553
|
+
* newer holder's state. Both refusals are audited through `refuse`.
|
|
554
|
+
*/
|
|
555
|
+
private withMutationLease;
|
|
556
|
+
/** #441: before each provider step, extends the lease; a holder that lost it stops here, before the step runs. */
|
|
557
|
+
private renewLease;
|
|
425
558
|
private refuse;
|
|
426
559
|
private refusePlatform;
|
|
427
560
|
private refuseDestroy;
|