@intentic/cli 1.325.0 → 1.327.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 (84) hide show
  1. package/README.md +7 -4
  2. package/dist/adopt/adopt.command.d.ts.map +1 -1
  3. package/dist/adopt/adopt.command.js +13 -6
  4. package/dist/adopt/adopt.command.js.map +1 -1
  5. package/dist/adopt/adopt.d.ts +5 -1
  6. package/dist/adopt/adopt.d.ts.map +1 -1
  7. package/dist/adopt/adopt.js +28 -3
  8. package/dist/adopt/adopt.js.map +1 -1
  9. package/dist/apply/apply-lock.d.ts +12 -0
  10. package/dist/apply/apply-lock.d.ts.map +1 -1
  11. package/dist/apply/apply-lock.js +50 -1
  12. package/dist/apply/apply-lock.js.map +1 -1
  13. package/dist/apply/apply.command.d.ts.map +1 -1
  14. package/dist/apply/apply.command.js +126 -48
  15. package/dist/apply/apply.command.js.map +1 -1
  16. package/dist/apply/baseline.d.ts +38 -0
  17. package/dist/apply/baseline.d.ts.map +1 -0
  18. package/dist/apply/baseline.js +108 -0
  19. package/dist/apply/baseline.js.map +1 -0
  20. package/dist/apply/prune-phase.d.ts +21 -0
  21. package/dist/apply/prune-phase.d.ts.map +1 -0
  22. package/dist/apply/prune-phase.js +46 -0
  23. package/dist/apply/prune-phase.js.map +1 -0
  24. package/dist/apply/retired-hosts.d.ts +18 -0
  25. package/dist/apply/retired-hosts.d.ts.map +1 -0
  26. package/dist/apply/retired-hosts.js +54 -0
  27. package/dist/apply/retired-hosts.js.map +1 -0
  28. package/dist/demo.js +2 -1
  29. package/dist/demo.js.map +1 -1
  30. package/dist/destroy/destroy.command.d.ts.map +1 -1
  31. package/dist/destroy/destroy.command.js +18 -6
  32. package/dist/destroy/destroy.command.js.map +1 -1
  33. package/dist/host-ssh-tunnel/host-ssh-tunnel.d.ts.map +1 -1
  34. package/dist/host-ssh-tunnel/host-ssh-tunnel.js +10 -5
  35. package/dist/host-ssh-tunnel/host-ssh-tunnel.js.map +1 -1
  36. package/dist/init/init.d.ts.map +1 -1
  37. package/dist/init/init.js +2 -1
  38. package/dist/init/init.js.map +1 -1
  39. package/dist/init/scaffold-app.js +1 -1
  40. package/dist/lib/cf-tunnel.d.ts +0 -1
  41. package/dist/lib/cf-tunnel.d.ts.map +1 -1
  42. package/dist/lib/cf-tunnel.js +12 -15
  43. package/dist/lib/cf-tunnel.js.map +1 -1
  44. package/dist/lib/flags.d.ts +2 -0
  45. package/dist/lib/flags.d.ts.map +1 -0
  46. package/dist/lib/flags.js +8 -0
  47. package/dist/lib/flags.js.map +1 -0
  48. package/dist/lib/known-hosts.d.ts +2 -1
  49. package/dist/lib/known-hosts.d.ts.map +1 -1
  50. package/dist/lib/known-hosts.js +2 -0
  51. package/dist/lib/known-hosts.js.map +1 -1
  52. package/dist/lib/output.d.ts.map +1 -1
  53. package/dist/lib/output.js +16 -5
  54. package/dist/lib/output.js.map +1 -1
  55. package/dist/lib/tables.d.ts +14 -0
  56. package/dist/lib/tables.d.ts.map +1 -1
  57. package/dist/lib/tables.js +42 -0
  58. package/dist/lib/tables.js.map +1 -1
  59. package/dist/logs/logs.command.d.ts.map +1 -1
  60. package/dist/logs/logs.command.js +6 -5
  61. package/dist/logs/logs.command.js.map +1 -1
  62. package/dist/pipelines/adopt-pipelines.d.ts +1 -0
  63. package/dist/pipelines/adopt-pipelines.d.ts.map +1 -1
  64. package/dist/pipelines/adopt-pipelines.js +6 -1
  65. package/dist/pipelines/adopt-pipelines.js.map +1 -1
  66. package/dist/plan/plan.command.d.ts +13 -0
  67. package/dist/plan/plan.command.d.ts.map +1 -1
  68. package/dist/plan/plan.command.js +94 -12
  69. package/dist/plan/plan.command.js.map +1 -1
  70. package/dist/resolve/resolve.command.d.ts.map +1 -1
  71. package/dist/resolve/resolve.command.js +10 -2
  72. package/dist/resolve/resolve.command.js.map +1 -1
  73. package/dist/resolve/resolve.d.ts +5 -1
  74. package/dist/resolve/resolve.d.ts.map +1 -1
  75. package/dist/resolve/resolve.js +31 -2
  76. package/dist/resolve/resolve.js.map +1 -1
  77. package/dist/restore/restore.command.js +3 -3
  78. package/dist/restore/restore.command.js.map +1 -1
  79. package/dist/secrets/generated-secrets.d.ts +1 -0
  80. package/dist/secrets/generated-secrets.d.ts.map +1 -1
  81. package/dist/secrets/generated-secrets.js +22 -0
  82. package/dist/secrets/generated-secrets.js.map +1 -1
  83. package/package.json +13 -14
  84. package/templates/workflows/apply.yaml.eta +26 -6
package/README.md CHANGED
@@ -12,17 +12,20 @@ flowchart LR
12
12
  engine --> infra["Hosts · Cloudflare<br/>Forgejo · Komodo"]
13
13
  ```
14
14
 
15
- - Four route groups in `src/app.ts`: `deploy` (the tool itself), `sandbox` (prints the canonical `docker run` for a sandbox), `tunnel host` (a host's own Cloudflare SSH tunnel) and `scaffold` (a pnpm + turbo monorepo and its apps).
15
+ - Four route groups in `src/app.ts`: `deploy` (the tool itself), `sandbox` (prints the canonical `docker run` for a sandbox), `tunnel host` (a host's own Cloudflare SSH tunnel, made with the providers' own `findOrCreateTunnel` and `upsertCname` on the zone state-resolver's `selectZone` picks) and `scaffold` (a pnpm + turbo monorepo and its apps).
16
16
  - A workspace has separate git repos: `intent/` holds `deploy.config.ts`, `desired-state/` holds the resolved `desired-state.json`, `.env` and generated secrets. `deploy init` scaffolds them; every other command reads these default paths from cwd.
17
- - `deploy resolve` reaches the network only to discover the Cloudflare zone from the API token. `plan` and `apply` never re-resolve; they work from the baked artifact.
18
- - `deploy apply` takes a lock on every host, generates missing secrets, applies authored renames, runs the reconcile loop, then prunes what the previous artifact declared and this one does not. Deletions wait for `--yes`.
19
- - `deploy adopt` pushes both repos to the provisioned Forgejo and installs pipelines, so later pushes resolve and apply in CI with this CLI's own version.
17
+ - `deploy resolve` reaches the network only to discover the Cloudflare zone from the API token. `plan` and `apply` never re-resolve; they work from the baked artifact. Resolve keeps the artifact's `owner` id, the intent's mark on everything it deploys, and mints one only when neither the artifact nor the baseline has it; the CI pipeline never mints (2026-10-05).
18
+ - `deploy apply` takes a lock on every host, generates missing secrets, applies authored renames, runs the reconcile loop, then prunes what the baseline holds and this artifact does not, this intent's own orphans, and its leftovers on hosts a migration retired. Deletions wait for `--yes`. A heartbeat renews the lock while the run lasts and stops it if the lock is lost (2026-10-05).
19
+ - The prune baseline, `.last-applied.json` beside the artifact (in CI, the `intentic-applied` tag's message), is a record, not a copy (2026-10-05). It advances with every apply, even while deletions wait for `--yes`, and keeps removed nodes in `pendingDeletion` until a delete succeeds, and retired hosts until one scans clean. A baseline that cannot be read stops the run before it changes anything. A missing one is a first apply only when nothing live carries this intent's owner stamp; otherwise it was lost, and `--first-apply` is the person's way through.
20
+ - `deploy plan --check` is the drift check: it changes nothing and fails on anything apply would change or delete, and on unowned resources or a scan that could not look everywhere.
21
+ - `deploy adopt` pushes both repos to the provisioned Forgejo, seeds the `intentic-applied` tag with the local baseline, and installs pipelines, so later pushes resolve and apply in CI with this CLI's own version. The apply pipeline also runs the drift check daily.
20
22
 
21
23
  ## Key files
22
24
 
23
25
  - [src/app.ts](src/app.ts) — the command tree and the one-line error format.
24
26
  - [src/resolve/resolve.ts](src/resolve/resolve.ts) — imports the config in place and discovers the Cloudflare zone.
25
27
  - [src/apply/apply.command.ts](src/apply/apply.command.ts) — lock, secrets, moves, reconcile loop, prune.
28
+ - [src/apply/baseline.ts](src/apply/baseline.ts) — the prune baseline record, and telling a first apply from a lost baseline.
26
29
  - [src/lib/artifact.ts](src/lib/artifact.ts) — default paths, artifact read/write and `.env` loading.
27
30
  - [src/pipelines/adopt-pipelines.ts](src/pipelines/adopt-pipelines.ts) — the CI workflows and repo secrets `adopt` installs.
28
31
 
@@ -1 +1 @@
1
- {"version":3,"file":"adopt.command.d.ts","sourceRoot":"","sources":["../../src/adopt/adopt.command.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAmBlE,eAAO,MAAM,KAAK,iDAiHhB,CAAC"}
1
+ {"version":3,"file":"adopt.command.d.ts","sourceRoot":"","sources":["../../src/adopt/adopt.command.ts"],"names":[],"mappings":"AAKA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAqBlE,eAAO,MAAM,KAAK,iDAwHhB,CAAC"}
@@ -1,14 +1,16 @@
1
- import { dirname, join } from "node:path";
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
2
3
  import { plural } from "@intentic/base/format";
3
- import { FORGEJO_HTTP_PORT, forgejoApi, overSsh, sshExecutor } from "@intentic/providers";
4
+ import { FORGEJO_HTTP_PORT, forgejoApi, overSsh } from "@intentic/providers";
4
5
  import { secretDigest, writeSyncState } from "@intentic/scaffold";
5
6
  import { buildCommand } from "@stricli/core";
6
7
  import { loadConfig } from "../env.config.js";
7
- import { ARTIFACT_FILE, ARTIFACT_PATH, CONFIG_FILE, INTENT_DIR, loadEnvFile, readArtifact, TARGET_DIR } from "../lib/artifact.js";
8
+ import { ARTIFACT_FILE, ARTIFACT_PATH, CONFIG_FILE, INTENT_DIR, LAST_APPLIED_FILE, loadEnvFile, readArtifact, TARGET_DIR } from "../lib/artifact.js";
9
+ import { pinnedSshExecutor } from "../lib/known-hosts.js";
8
10
  import { createOutput, teeOutput } from "../lib/output.js";
9
11
  import { withRunLog } from "../lib/run-log.js";
10
12
  import { version } from "../lib/version.js";
11
- import { collectSecretValues, GIT_TOKEN_SECRET, GIT_USER_SECRET, setRepoSecrets, writeControlPlaneWorkflows, } from "../pipelines/adopt-pipelines.js";
13
+ import { APPLIED_TAG, collectSecretValues, GIT_TOKEN_SECRET, GIT_USER_SECRET, setRepoSecrets, writeControlPlaneWorkflows, } from "../pipelines/adopt-pipelines.js";
12
14
  import { forgejoIdentity } from "../pipelines/control-plane-sync.js";
13
15
  import { createEventsFileSink } from "../lib/events-file.js";
14
16
  import { readGeneratedSecrets } from "../secrets/generated-secrets.js";
@@ -66,6 +68,7 @@ export const adopt = buildCommand({
66
68
  if (desiredStateSecrets["CLOUDFLARE_API_TOKEN"] !== undefined) {
67
69
  intentSecrets["CLOUDFLARE_API_TOKEN"] = desiredStateSecrets["CLOUDFLARE_API_TOKEN"];
68
70
  }
71
+ const baselineFile = resolve(targetDir, LAST_APPLIED_FILE);
69
72
  const run = async (baseUrl) => {
70
73
  const repos = await adoptRepos({
71
74
  baseUrl,
@@ -74,7 +77,11 @@ export const adopt = buildCommand({
74
77
  password,
75
78
  repos: [
76
79
  { dir: intentDir, name: INTENT_DIR },
77
- { dir: targetDir, name: TARGET_DIR },
80
+ {
81
+ dir: targetDir,
82
+ name: TARGET_DIR,
83
+ ...(existsSync(baselineFile) ? { seedTag: { name: APPLIED_TAG, messageFile: baselineFile } } : {}),
84
+ },
78
85
  ],
79
86
  log: out.log,
80
87
  });
@@ -91,7 +98,7 @@ export const adopt = buildCommand({
91
98
  if (sshKey === undefined || sshKey === "") {
92
99
  throw new Error(`host ssh key (${ssh.sshKeyRef.source} secret ${ssh.sshKeyRef.key}) is not available`);
93
100
  }
94
- repos = await overSsh(sshExecutor, { address: ssh.address, user: ssh.user, sshKey, port: ssh.port ?? 22, via: ssh.via ?? "direct" }, FORGEJO_HTTP_PORT, run);
101
+ repos = await overSsh(pinnedSshExecutor(targetDir), { address: ssh.address, user: ssh.user, sshKey, port: ssh.port ?? 22, via: ssh.via ?? "direct" }, FORGEJO_HTTP_PORT, run);
95
102
  }
96
103
  const pushedAt = new Date().toISOString();
97
104
  await writeSyncState(targetDir, Object.fromEntries(Object.entries(desiredStateSecrets).map(([key, value]) => [key, { digest: secretDigest(value), pushedAt }])));
@@ -1 +1 @@
1
- {"version":3,"file":"adopt.command.js","sourceRoot":"","sources":["../../src/adopt/adopt.command.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adopt.command.js","sourceRoot":"","sources":["../../src/adopt/adopt.command.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,12 @@
1
1
  import type { ForgejoApi } from "@intentic/providers";
2
- import { type GitRunner } from "@intentic/scaffold";
2
+ import { type GitRunner } from "@intentic/base/git";
3
3
  export interface AdoptRepo {
4
4
  readonly dir: string;
5
5
  readonly name: string;
6
+ readonly seedTag?: {
7
+ readonly name: string;
8
+ readonly messageFile: string;
9
+ };
6
10
  }
7
11
  export interface AdoptOptions {
8
12
  readonly baseUrl: string;
@@ -1 +1 @@
1
- {"version":3,"file":"adopt.d.ts","sourceRoot":"","sources":["../../src/adopt/adopt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAA4B,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE9E,MAAM,WAAW,SAAS;IAEtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAEzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IACrC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC;CAC5B;AAID,eAAO,MAAM,UAAU,YAAmB,YAAY,KAAG,OAAO,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAAE,CAwBtH,CAAC"}
1
+ {"version":3,"file":"adopt.d.ts","sourceRoot":"","sources":["../../src/adopt/adopt.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAA4B,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE9E,MAAM,WAAW,SAAS;IAEtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAGtB,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9E;AAED,MAAM,WAAW,YAAY;IAEzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IACrC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC;CAC5B;AAID,eAAO,MAAM,UAAU,YAAmB,YAAY,KAAG,OAAO,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAAE,CAiDtH,CAAC"}
@@ -1,12 +1,13 @@
1
+ import { errorMessage } from "@intentic/base/errors";
1
2
  import { forgejoApi } from "@intentic/providers";
2
- import { defaultGit, gitCommitAll } from "@intentic/scaffold";
3
+ import { defaultGit, gitCommitAll } from "@intentic/base/git";
3
4
  export const adoptRepos = async (options) => {
4
5
  const api = options.api ?? forgejoApi;
5
6
  const git = options.git ?? defaultGit;
6
7
  const { baseUrl, originBaseUrl, user, password, repos, log } = options;
7
8
  const email = `${user}@${new URL(originBaseUrl).host}`;
8
9
  const authHeader = `AUTHORIZATION: basic ${Buffer.from(`${user}:${password}`).toString("base64")}`;
9
- return Promise.all(repos.map(async ({ dir, name }) => {
10
+ return Promise.all(repos.map(async ({ dir, name, seedTag }) => {
10
11
  const existing = await api.findRepo({ baseUrl, user, password, owner: user, name });
11
12
  if (existing === undefined) {
12
13
  await api.createRepo({ baseUrl, user, password, owner: user, ownerIsOrg: false, name, private: true, autoInit: false });
@@ -16,7 +17,31 @@ export const adoptRepos = async (options) => {
16
17
  const cloneUrl = `${originBaseUrl}/${user}/${name}.git`;
17
18
  const remotes = (await git(dir, ["remote"])).stdout.split("\n").map((line) => line.trim());
18
19
  await git(dir, remotes.includes("origin") ? ["remote", "set-url", "origin", cloneUrl] : ["remote", "add", "origin", cloneUrl]);
19
- await git(dir, ["-c", `http.extraHeader=${authHeader}`, "push", `${baseUrl}/${user}/${name}.git`, "main"]);
20
+ const pushUrl = `${baseUrl}/${user}/${name}.git`;
21
+ if (seedTag !== undefined) {
22
+ try {
23
+ await git(dir, [
24
+ "-c",
25
+ `user.name=${user}`,
26
+ "-c",
27
+ `user.email=${email}`,
28
+ "tag",
29
+ "-f",
30
+ "-a",
31
+ "--cleanup=verbatim",
32
+ "-F",
33
+ seedTag.messageFile,
34
+ seedTag.name,
35
+ "HEAD",
36
+ ]);
37
+ await git(dir, ["-c", `http.extraHeader=${authHeader}`, "push", pushUrl, `refs/tags/${seedTag.name}`]);
38
+ log(`seeded ${user}/${name}'s ${seedTag.name} tag with the local prune baseline`);
39
+ }
40
+ catch (error) {
41
+ log(`kept ${user}/${name}'s ${seedTag.name} tag as it is (${errorMessage(error)})`);
42
+ }
43
+ }
44
+ await git(dir, ["-c", `http.extraHeader=${authHeader}`, "push", pushUrl, "main"]);
20
45
  log(`pushed ${dir} → ${cloneUrl}`);
21
46
  return { name, cloneUrl };
22
47
  }));
@@ -1 +1 @@
1
- {"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../src/adopt/adopt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAkB,MAAM,oBAAoB,CAAC;AAuB9E,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,OAAqB,EAAmE,EAAE;IACvH,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,UAAU,CAAC;IACtC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,UAAU,CAAC;IACtC,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;IACvE,MAAM,KAAK,GAAG,GAAG,IAAI,IAAI,IAAI,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,EAAE,CAAC;IACvD,MAAM,UAAU,GAAG,wBAAwB,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;IAEnG,OAAO,OAAO,CAAC,GAAG,CACd,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE;QAC9B,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACpF,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,GAAG,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;YACxH,GAAG,CAAC,WAAW,IAAI,IAAI,IAAI,aAAa,CAAC,CAAC;QAC9C,CAAC;QACD,MAAM,YAAY,CAAC,GAAG,EAAE,gBAAgB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,GAAG,CAAC,CAAC;QACtE,MAAM,QAAQ,GAAG,GAAG,aAAa,IAAI,IAAI,IAAI,IAAI,MAAM,CAAC;QACxD,MAAM,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC3F,MAAM,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;QAE/H,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,oBAAoB,UAAU,EAAE,EAAE,MAAM,EAAE,GAAG,OAAO,IAAI,IAAI,IAAI,IAAI,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAC3G,GAAG,CAAC,UAAU,GAAG,MAAM,QAAQ,EAAE,CAAC,CAAC;QACnC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC9B,CAAC,CAAC,CACL,CAAC;AACN,CAAC,CAAC"}
1
+ {"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../src/adopt/adopt.ts"],"names":[],"mappings":"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"}
@@ -3,10 +3,22 @@ export interface ApplyLock {
3
3
  readonly verify: () => Promise<void>;
4
4
  readonly renew: () => Promise<void>;
5
5
  readonly release: () => Promise<void>;
6
+ readonly ttlSeconds: number;
7
+ }
8
+ export declare class LockLostError extends Error {
9
+ constructor(host: string);
6
10
  }
7
11
  export declare const acquireApplyLock: (executor: SshExecutor, targets: readonly SshTarget[], options?: {
8
12
  readonly ttlSeconds?: number;
9
13
  readonly holder?: string;
10
14
  readonly log?: (message: string) => void;
11
15
  }) => Promise<ApplyLock>;
16
+ export interface LockHeartbeat {
17
+ readonly stop: () => void;
18
+ }
19
+ export declare const startLockHeartbeat: (lock: ApplyLock, options: {
20
+ readonly onLost: (error: Error) => void;
21
+ readonly intervalMs?: number;
22
+ readonly log?: (message: string) => void;
23
+ }) => LockHeartbeat;
12
24
  //# sourceMappingURL=apply-lock.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"apply-lock.d.ts","sourceRoot":"","sources":["../../src/apply/apply-lock.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAa,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAU7E,MAAM,WAAW,SAAS;IAEtB,QAAQ,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAErC,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC;AAmED,eAAO,MAAM,gBAAgB,aACf,WAAW,WACZ,SAAS,SAAS,EAAE,YACpB;IAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,KAC9G,OAAO,CAAC,SAAS,CA4DnB,CAAC"}
1
+ {"version":3,"file":"apply-lock.d.ts","sourceRoot":"","sources":["../../src/apply/apply-lock.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAa,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAU7E,MAAM,WAAW,SAAS;IAEtB,QAAQ,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAGrC,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC/B;AAGD,qBAAa,aAAc,SAAQ,KAAK;IACpC,YAAY,IAAI,EAAE,MAAM,EAGvB;CACJ;AAmED,eAAO,MAAM,gBAAgB,aACf,WAAW,WACZ,SAAS,SAAS,EAAE,YACpB;IAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,KAC9G,OAAO,CAAC,SAAS,CAgEnB,CAAC;AAEF,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;CAC7B;AAMD,eAAO,MAAM,kBAAkB,SACrB,SAAS,WACN;IACL,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C,KACF,aAsCF,CAAC"}
@@ -4,6 +4,12 @@ import { HOST_STATE_ROOT } from "@intentic/constants";
4
4
  import { shellQuote } from "@intentic/sandbox-run/quote";
5
5
  const LOCK_DIR = `${HOST_STATE_ROOT}/apply.lock.d`;
6
6
  const DEFAULT_TTL_SECONDS = 30 * 60;
7
+ export class LockLostError extends Error {
8
+ constructor(host) {
9
+ super(`apply lock on ${host} was taken over by another run`);
10
+ this.name = "LockLostError";
11
+ }
12
+ }
7
13
  const lockKey = (target) => `${target.address}:${target.port}`;
8
14
  const orderedHosts = (targets) => {
9
15
  const byKey = new Map();
@@ -95,10 +101,53 @@ export const acquireApplyLock = async (executor, targets, options = {}) => {
95
101
  },
96
102
  renew: async () => {
97
103
  for (const target of held) {
98
- await run(executor, target, renewScript(holder, nonce, ttl));
104
+ const outcome = (await run(executor, target, renewScript(holder, nonce, ttl))).stdout.trim();
105
+ if (!outcome.startsWith("OK")) {
106
+ throw new LockLostError(lockKey(target));
107
+ }
99
108
  }
100
109
  },
101
110
  release: releaseAll,
111
+ ttlSeconds: ttl,
112
+ };
113
+ };
114
+ export const startLockHeartbeat = (lock, options) => {
115
+ const log = options.log ?? (() => { });
116
+ const intervalMs = options.intervalMs ?? (lock.ttlSeconds * 1000) / 3;
117
+ let failures = 0;
118
+ let busy = false;
119
+ let stopped = false;
120
+ const timer = setInterval(() => {
121
+ if (busy || stopped) {
122
+ return;
123
+ }
124
+ busy = true;
125
+ void lock
126
+ .renew()
127
+ .then(() => {
128
+ failures = 0;
129
+ })
130
+ .catch((error) => {
131
+ failures += 1;
132
+ const lost = error instanceof LockLostError;
133
+ if (lost || failures >= 2) {
134
+ stopped = true;
135
+ clearInterval(timer);
136
+ options.onLost(lost ? error : new Error(`could not renew the apply lock twice in a row: ${String(error)}`, { cause: error }));
137
+ return;
138
+ }
139
+ log(`apply-lock: renewal failed, retrying at the next beat: ${String(error)}`);
140
+ })
141
+ .finally(() => {
142
+ busy = false;
143
+ });
144
+ }, intervalMs);
145
+ timer.unref();
146
+ return {
147
+ stop: () => {
148
+ stopped = true;
149
+ clearInterval(timer);
150
+ },
102
151
  };
103
152
  };
104
153
  //# sourceMappingURL=apply-lock.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"apply-lock.js","sourceRoot":"","sources":["../../src/apply/apply-lock.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"apply-lock.js","sourceRoot":"","sources":["../../src/apply/apply-lock.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"apply.command.d.ts","sourceRoot":"","sources":["../../src/apply/apply.command.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAgB,KAAK,cAAc,EAAgB,MAAM,eAAe,CAAC;AA0BhF,eAAO,MAAM,KAAK,iDAuNhB,CAAC"}
1
+ {"version":3,"file":"apply.command.d.ts","sourceRoot":"","sources":["../../src/apply/apply.command.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AA8ClE,eAAO,MAAM,KAAK,iDAoThB,CAAC"}
@@ -1,26 +1,32 @@
1
- import { existsSync } from "node:fs";
2
- import { readFile, writeFile } from "node:fs/promises";
3
1
  import { tmpdir } from "node:os";
4
2
  import { dirname, join } from "node:path";
5
3
  import { plural } from "@intentic/base/format";
6
- import { applyMoves, collectOrphans, createStore, prune, pruneOrphans, ReadinessTimeoutError, reconcile, resolveInputs, rewriteGraphForMoves, } from "@intentic/engine";
7
- import { connectWithRetry, createProviders, createSshExecutor, createSshProbe, hostTarget, readinessDiagnostics } from "@intentic/providers";
8
- import { buildCommand, numberParser } from "@stricli/core";
4
+ import { applyMoves, collectOrphans, createStore, ReadinessTimeoutError, reconcile, resolveInputs, rewriteGraphForMoves, } from "@intentic/engine";
5
+ import { connectWithRetry, createProviders, createSshProbe, hostTarget, readinessDiagnostics, } from "@intentic/providers";
6
+ import { buildCommand } from "@stricli/core";
9
7
  import { collectSecretUsage, subgraph } from "@intentic/graph";
10
8
  import { loadConfig } from "../env.config.js";
11
9
  import { ACCESS_FILE, ARTIFACT_PATH, LAST_APPLIED_FILE, loadEnvFile, readArtifact, STATUS_FILE, writeStatus } from "../lib/artifact.js";
12
- import { createKnownHostsStore } from "../lib/known-hosts.js";
10
+ import { countAtLeast } from "../lib/flags.js";
11
+ import { pinnedSshExecutor } from "../lib/known-hosts.js";
13
12
  import { createOutput, createRedactor, teeOutput } from "../lib/output.js";
14
13
  import { withRunLog } from "../lib/run-log.js";
15
- import { resourceTable } from "../lib/tables.js";
16
- import { ensureGeneratedSecrets } from "../secrets/generated-secrets.js";
14
+ import { pendingBlock, resourceTable, retiredBlock, scanGapBlock, unownedBlock } from "../lib/tables.js";
15
+ import { ensureGeneratedSecrets, loadStoredSecrets } from "../secrets/generated-secrets.js";
17
16
  import { generatedSecretStore } from "../secrets/secret-store.js";
18
17
  import { collectSecrets } from "../secrets/secrets.js";
19
18
  import { collectAccess, formatAccessSummary, writeAccessFile } from "./access.js";
20
19
  import { createEventsFileSink } from "../lib/events-file.js";
21
- import { acquireApplyLock } from "./apply-lock.js";
20
+ import { acquireApplyLock, startLockHeartbeat } from "./apply-lock.js";
21
+ import { checkFirstApply, loadBaseline, mergeRetiredHosts, nextBaseline, pruneBase, removedNodes, writeBaseline, } from "./baseline.js";
22
22
  import { detectHostMoves, migrateHosts } from "./migrate.js";
23
+ import { runPrunePhase } from "./prune-phase.js";
24
+ import { retiredTarget, scanRetiredHosts } from "./retired-hosts.js";
23
25
  const DEFAULT_MAX_ITERATIONS = 5;
26
+ const trackedGraph = (base, retired) => ({
27
+ version: 1,
28
+ resources: { ...base?.resources, ...Object.fromEntries(retired.map((host) => [`retired:${host.id}@${host.address}`, host.node])) },
29
+ });
24
30
  export const apply = buildCommand({
25
31
  docs: { brief: "Execute the desired-state artifact until state reads true" },
26
32
  parameters: {
@@ -28,7 +34,7 @@ export const apply = buildCommand({
28
34
  artifact: { kind: "parsed", parse: String, optional: true, brief: `Path to the artifact (default: ${ARTIFACT_PATH})` },
29
35
  maxIterations: {
30
36
  kind: "parsed",
31
- parse: numberParser,
37
+ parse: countAtLeast(1),
32
38
  optional: true,
33
39
  brief: `Max reconcile iterations (default ${DEFAULT_MAX_ITERATIONS})`,
34
40
  },
@@ -36,11 +42,11 @@ export const apply = buildCommand({
36
42
  kind: "parsed",
37
43
  parse: String,
38
44
  optional: true,
39
- brief: "Path to the last successfully-applied artifact; resources absent from the new one are pruned after convergence",
45
+ brief: `Path to the prune baseline (default: ${LAST_APPLIED_FILE} beside the artifact); resources it holds and the new artifact lacks are pruned after convergence`,
40
46
  },
41
47
  yes: {
42
48
  kind: "boolean",
43
- brief: "Confirm deletions: without it, pending prunes are listed and left in place (converge still runs)",
49
+ brief: "Confirm deletions: without it, pending prunes are listed and kept in the baseline, left in place (converge still runs)",
44
50
  },
45
51
  target: {
46
52
  kind: "parsed",
@@ -48,6 +54,10 @@ export const apply = buildCommand({
48
54
  optional: true,
49
55
  brief: "Comma-separated resource ids: reconcile only these and their dependencies (prune and orphan scan are skipped)",
50
56
  },
57
+ firstApply: {
58
+ kind: "boolean",
59
+ brief: "Start a new prune baseline when none can be read, even though resources of this intent are live (what a lost baseline tracked is then removed by hand)",
60
+ },
51
61
  },
52
62
  },
53
63
  async func(flags) {
@@ -65,10 +75,20 @@ export const apply = buildCommand({
65
75
  .map((id) => id.trim())
66
76
  .filter((id) => id !== "");
67
77
  const graph = targetIds === undefined ? full : subgraph(full, targetIds);
68
- const ssh = createSshExecutor(createKnownHostsStore(dir));
69
- const previousPath = flags.previous ?? join(dir, LAST_APPLIED_FILE);
70
- const previous = existsSync(previousPath) ? await readArtifact(previousPath) : undefined;
71
- const hostMoves = previous !== undefined ? detectHostMoves(previous, graph) : [];
78
+ const ssh = pinnedSshExecutor(dir);
79
+ const baselinePath = flags.previous ?? join(dir, LAST_APPLIED_FILE);
80
+ const lastApplied = join(dir, LAST_APPLIED_FILE);
81
+ const previous = await loadBaseline(baselinePath, { explicit: flags.previous !== undefined, firstApply: flags.firstApply, note: out.text });
82
+ const owner = full.owner ?? previous?.owner;
83
+ if (full.owner !== undefined && previous?.owner !== undefined && full.owner !== previous.owner) {
84
+ throw new Error(`the artifact's owner id (${full.owner}) is not the one this intent was applied with (${previous.owner}): every resource it stamped would read as another intent's. Put "owner": "${previous.owner}" back in ${artifact} (resolve keeps whatever is there).`);
85
+ }
86
+ if (full.owner === undefined) {
87
+ out.text("This artifact carries no owner id (it was resolved before owner stamps): nothing is stamped with an owner and no orphan is pruned. Re-run `intentic deploy resolve` to mint one.");
88
+ }
89
+ const hostMoves = previous !== undefined ? detectHostMoves({ version: 1, resources: previous.resources }, graph) : [];
90
+ const sinceUtcDay = new Date().toISOString().slice(0, 10);
91
+ const retiredHosts = mergeRetiredHosts(previous?.retiredHosts ?? [], hostMoves.map((move) => ({ id: move.id, address: move.oldAddress, node: move.oldNode, since: sinceUtcDay })), full);
72
92
  const targets = Object.values(graph.resources)
73
93
  .filter((node) => node.type === "host")
74
94
  .map((node) => hostTarget(resolveInputs(node.inputs, createStore(), process.env, { lenient: false })));
@@ -88,7 +108,18 @@ export const apply = buildCommand({
88
108
  await session.dispose();
89
109
  }));
90
110
  const oldMoveTargets = hostMoves.map((move) => hostTarget(resolveInputs(move.oldNode.inputs, createStore(), process.env, { lenient: false })));
91
- const lock = await acquireApplyLock(ssh, [...targets, ...oldMoveTargets], { log: out.log });
111
+ const retiredTargets = retiredHosts
112
+ .map((host) => retiredTarget(host, process.env))
113
+ .filter((target) => typeof target !== "string");
114
+ const lock = await acquireApplyLock(ssh, [...targets, ...oldMoveTargets, ...retiredTargets], { log: out.log });
115
+ const stop = new AbortController();
116
+ const heartbeat = startLockHeartbeat(lock, {
117
+ log: out.log,
118
+ onLost: (error) => {
119
+ out.log(`apply-lock: ${error.message}; stopping before the next change`);
120
+ stop.abort(error);
121
+ },
122
+ });
92
123
  const onSignal = () => {
93
124
  void Promise.allSettled([lock.release(), ssh.dispose?.()]).finally(() => process.exit(130));
94
125
  };
@@ -97,20 +128,43 @@ export const apply = buildCommand({
97
128
  process.once("SIGHUP", onSignal);
98
129
  const signals = process;
99
130
  try {
100
- await ensureGeneratedSecrets(generatedSecretStore(graph, dir, ssh, true, out.log), collectSecrets(graph).generated, process.env);
101
- redactor.add(collectSecretUsage(graph).map((usage) => process.env[usage.key]));
102
- if (hostMoves.length > 0) {
103
- await migrateHosts(hostMoves, { next: graph, ssh, env: process.env, tmpDir: tmpdir(), log: out.log });
104
- }
105
- const movedApplied = await applyMoves(graph, {
131
+ const engine = () => ({
106
132
  providers: createProviders({ ssh }),
107
133
  log: out.log,
108
134
  onEvent: out.onEvent,
109
135
  env: process.env,
136
+ signal: stop.signal,
137
+ ...(owner !== undefined ? { owner } : {}),
110
138
  });
139
+ const store = generatedSecretStore(graph, dir, ssh, true, out.log);
140
+ await ensureGeneratedSecrets(store, collectSecrets(graph).generated, process.env);
141
+ const tracked = trackedGraph(previous !== undefined ? pruneBase(previous) : undefined, retiredHosts);
142
+ const current = new Set(collectSecrets(graph).generated);
143
+ await loadStoredSecrets(store, collectSecrets(tracked).generated.filter((key) => !current.has(key)), process.env, out.log);
144
+ redactor.add([...collectSecretUsage(graph), ...collectSecretUsage(tracked)].map((usage) => process.env[usage.key]));
145
+ if (previous === undefined && targetIds === undefined) {
146
+ const line = await checkFirstApply({
147
+ path: baselinePath,
148
+ owner,
149
+ firstApply: flags.firstApply,
150
+ scan: () => collectOrphans(graph, { ...engine(), onEvent: () => { }, log: () => { } }),
151
+ });
152
+ out.text(line);
153
+ }
154
+ if (hostMoves.length > 0) {
155
+ stop.signal.throwIfAborted();
156
+ await migrateHosts(hostMoves, { next: graph, ssh, env: process.env, tmpDir: tmpdir(), log: out.log });
157
+ }
158
+ const movedApplied = await applyMoves(graph, engine());
159
+ const base = previous !== undefined ? rewriteGraphForMoves(pruneBase(previous), movedApplied) : undefined;
160
+ if (targetIds === undefined) {
161
+ await writeBaseline(lastApplied, nextBaseline({ graph, owner, pending: base === undefined ? [] : removedNodes(base, graph), retiredHosts }));
162
+ }
111
163
  let result;
112
164
  try {
113
- result = await reconcile(graph, { providers: createProviders({ ssh }), log: out.log, onEvent: out.onEvent, ...(probe !== undefined ? { probe } : {}) }, { maxIterations: flags.maxIterations ?? DEFAULT_MAX_ITERATIONS });
165
+ result = await reconcile(graph, { ...engine(), ...(probe !== undefined ? { probe } : {}) }, {
166
+ maxIterations: flags.maxIterations ?? DEFAULT_MAX_ITERATIONS,
167
+ });
114
168
  }
115
169
  catch (error) {
116
170
  if (error instanceof ReadinessTimeoutError && targets.length > 0) {
@@ -125,41 +179,63 @@ export const apply = buildCommand({
125
179
  steps: result.outcome.steps,
126
180
  access,
127
181
  });
182
+ let report = {};
128
183
  let pruned = { deleted: [], skipped: [] };
129
184
  if (targetIds !== undefined) {
130
185
  out.text("A targeted apply reconciles only the named resources, so prune and the orphan scan were skipped.");
131
186
  }
132
187
  else {
133
- const pruneConfig = { providers: createProviders({ ssh }), log: out.log, onEvent: out.onEvent, env: process.env };
134
- const baseline = previous !== undefined ? rewriteGraphForMoves(previous, movedApplied) : undefined;
135
- const removed = baseline === undefined ? [] : Object.values(baseline.resources).filter((node) => graph.resources[node.id] === undefined);
136
- const orphans = await collectOrphans(graph, pruneConfig);
137
- const pending = [
138
- ...removed.filter((node) => node.inputs["protect"] !== true),
139
- ...orphans.filter((orphan) => orphan.protected !== true),
140
- ];
141
- if (pending.length > 0 && !flags.yes) {
188
+ const retired = await scanRetiredHosts(retiredHosts, { ssh, env: process.env, owner, providers: createProviders({ ssh }) });
189
+ const phase = await runPrunePhase({
190
+ graph,
191
+ base,
192
+ owner,
193
+ retired,
194
+ yes: flags.yes,
195
+ config: engine(),
196
+ beforeDelete: async () => {
197
+ await lock.renew();
198
+ await lock.verify();
199
+ },
200
+ });
201
+ pruned = phase.pruned;
202
+ const retiredReports = retired.map((scan) => ({
203
+ id: scan.host.id,
204
+ address: scan.host.address,
205
+ leftovers: scan.leftovers.map(({ id, type }) => ({ id, type })),
206
+ unowned: scan.unowned,
207
+ ...(scan.error !== undefined ? { error: scan.error } : {}),
208
+ }));
209
+ for (const line of [...unownedBlock(phase.scan.unowned), ...retiredBlock(retiredReports), ...scanGapBlock(phase.scan.skipped)]) {
210
+ out.text(line);
211
+ }
212
+ if (phase.scan.foreign > 0) {
213
+ out.text(`${plural(phase.scan.foreign, "stamped resource")} on shared hosts or zones belong to other intents; left alone.`);
214
+ }
215
+ if (phase.pending.length > 0) {
142
216
  out.text("");
143
- for (const line of resourceTable("delete", pending)) {
217
+ for (const line of resourceTable("delete", phase.pending)) {
144
218
  out.text(line);
145
219
  }
146
- out.text(`${plural(pending.length, "deletion")} pending, nothing was deleted. Re-run \`intentic deploy apply --yes\` to prune.`);
220
+ out.text(`${plural(phase.pending.length, "deletion")} pending, nothing was deleted; the baseline keeps them. Re-run \`intentic deploy apply --yes\` to prune.`);
147
221
  }
148
- else {
149
- if (pending.length > 0) {
150
- await lock.renew();
151
- await lock.verify();
152
- }
153
- if (baseline !== undefined) {
154
- pruned = await prune(baseline, graph, pruneConfig);
155
- }
156
- const orphaned = await pruneOrphans(orphans, pruneConfig);
157
- pruned = { deleted: [...pruned.deleted, ...orphaned.deleted], skipped: [...pruned.skipped, ...orphaned.skipped] };
158
- if (pruned.deleted.length > 0 || pruned.skipped.length > 0) {
159
- out.text(`pruned ${plural(pruned.deleted.length, "resource")}${pruned.skipped.length > 0 ? `, ${pruned.skipped.length} left in place` : ""}`);
222
+ else if (phase.pruned.deleted.length > 0 || phase.pruned.skipped.length > 0) {
223
+ out.text(`pruned ${plural(phase.pruned.deleted.length, "resource")}${phase.pruned.skipped.length > 0 ? `, ${phase.pruned.skipped.length} left in place` : ""}`);
224
+ }
225
+ const stillPending = Object.values(phase.baseline.pendingDeletion ?? {});
226
+ if (phase.pending.length === 0) {
227
+ for (const line of pendingBlock(stillPending)) {
228
+ out.text(line);
160
229
  }
161
- await writeFile(join(dir, LAST_APPLIED_FILE), await readFile(artifact, "utf8"));
162
230
  }
231
+ stop.signal.throwIfAborted();
232
+ await writeBaseline(lastApplied, phase.baseline);
233
+ report = {
234
+ unowned: phase.scan.unowned,
235
+ pendingDeletion: phase.pending.length > 0 ? phase.pending : stillPending.map(({ id, type }) => ({ id, type })),
236
+ retiredHosts: retiredReports,
237
+ scanSkipped: phase.scan.skipped,
238
+ };
163
239
  }
164
240
  out.text(`${result.converged ? "converged" : "did not converge"} in ${plural(result.iterations, "iteration")}`);
165
241
  if (access.length > 0) {
@@ -173,6 +249,7 @@ export const apply = buildCommand({
173
249
  outputs: result.outcome.outputs,
174
250
  pruned,
175
251
  access,
252
+ ...report,
176
253
  });
177
254
  const reconcileWebhook = result.outcome.outputs["discord"]?.["reconcileWebhook"];
178
255
  if (typeof reconcileWebhook === "string" && reconcileWebhook !== "") {
@@ -198,6 +275,7 @@ export const apply = buildCommand({
198
275
  }
199
276
  }
200
277
  finally {
278
+ heartbeat.stop();
201
279
  signals.removeListener("SIGINT", onSignal);
202
280
  signals.removeListener("SIGTERM", onSignal);
203
281
  signals.removeListener("SIGHUP", onSignal);