getculpa 1.0.7 → 1.0.10

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/lib/provision.mjs CHANGED
@@ -17,7 +17,8 @@ import { spawnSync as realSpawnSync } from "node:child_process";
17
17
  import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
18
18
  import path from "node:path";
19
19
  import { fileURLToPath } from "node:url";
20
- import { checkDockerEngineReachable, checkDockerPresent, classifyUpgrade, isIncomingComposeOlder } from "./preflight.mjs";
20
+ import { checkDockerEngineReachable, checkDockerPresent, classifyUpgrade, isComposeUpgrade, isIncomingComposeOlder, versionSkew } from "./preflight.mjs";
21
+ import { runningImageVersions } from "./docker.mjs";
21
22
  import { resolveAssetPath } from "./assets.mjs";
22
23
  import { canonicalAppDir } from "./paths.mjs";
23
24
 
@@ -111,9 +112,14 @@ export function stageParityFiles(appDir, env = process.env, platform = process.p
111
112
  copyFileSync(resolveAssetPath(RELAY_FLY_CONFIG), path.join(appDir, RELAY_FLY_CONFIG));
112
113
  }
113
114
 
114
- // The live compose is staged ONLY when absent. An existing live compose
115
- // belongs to launch-time's QW-1 sync (launch-culpa.ps1) — provisioning must
116
- // never re-pin a running install out from under it.
115
+ // The live compose is staged when absent; when PRESENT it is re-pinned here
116
+ // by the same QW-1 one-direction rule launch uses (T-178, ISS-MAC-7). The
117
+ // earlier rule ("provisioning must never re-pin a running install out from
118
+ // under it") answered a fear that a re-pinned file changes a running stack —
119
+ // it does not: nothing moves until the next `compose up`, which both
120
+ // launchers now run behind QW-3/QW-4 even on a running stack (ISS-V103-7).
121
+ // A refused downgrade never reaches here with a newer shipped file (the
122
+ // re-stage was skipped), and isComposeUpgrade refuses an older one anyway.
117
123
  export function stageLiveComposeIfAbsent(appDir) {
118
124
  const live = path.join(appDir, "docker-compose.yml");
119
125
  if (existsSync(live)) return false;
@@ -121,6 +127,29 @@ export function stageLiveComposeIfAbsent(appDir) {
121
127
  return true;
122
128
  }
123
129
 
130
+ export function repinLiveCompose(appDir) {
131
+ const shipped = path.join(appDir, "culpa-compose.yml");
132
+ const live = path.join(appDir, "docker-compose.yml");
133
+ if (!existsSync(shipped) || !existsSync(live)) return false;
134
+ if (!isComposeUpgrade(readFileSync(shipped, "utf8"), readFileSync(live, "utf8"), (m) => console.warn(m))) return false;
135
+ copyFileSync(shipped, live);
136
+ console.log("getculpa: upgrade detected - live compose re-pinned from the shipped culpa-compose.yml (applies at next `getculpa`).");
137
+ return true;
138
+ }
139
+
140
+ // T-178: what the stack is ACTUALLY running, recorded in install-state.json
141
+ // beside the staging facts — imagesStaged says what was pulled, not what runs.
142
+ function probeLiveStack({ appDir, dockerState, dockerSpawnSync }) {
143
+ if (dockerState !== "ready") return { probed: false, running: false, versions: {}, skew: null };
144
+ const versions = runningImageVersions(dockerSpawnSync);
145
+ const read = (name) => {
146
+ const p = path.join(appDir, name);
147
+ return existsSync(p) ? readFileSync(p, "utf8") : "";
148
+ };
149
+ const skew = versionSkew({ shippedText: read("culpa-compose.yml"), liveText: read("docker-compose.yml"), running: versions });
150
+ return { probed: true, running: Object.keys(versions).length > 0, versions, skew };
151
+ }
152
+
124
153
  // T-CF29-8 — the name culpa-collector.ps1:35 reads from the app dir, and the
125
154
  // same name culpa-setup.iss:52 installs there. NOT the Rust target triple
126
155
  // lib/fetch.mjs uses for the release asset (x86_64-pc-windows-msvc): this is
@@ -260,6 +289,7 @@ export async function provision(opts) {
260
289
  stageParityFiles(appDir, env, platform);
261
290
  }
262
291
  stageLiveComposeIfAbsent(appDir);
292
+ if (!refusedDowngrade) repinLiveCompose(appDir);
263
293
 
264
294
  // T-CF29-8: only attempt it when the fetch reported success; the result is
265
295
  // what gets recorded, not the intent.
@@ -282,6 +312,15 @@ export async function provision(opts) {
282
312
  console.warn(`getculpa: ${why} - image staging deferred to first \`getculpa\`.`);
283
313
  }
284
314
 
315
+ // T-178 (ISS-MAC-7): say plainly what is running and what the next launch
316
+ // will do — a reinstall over a running stack used to look finished.
317
+ const liveStack = probeLiveStack({ appDir, dockerState, dockerSpawnSync });
318
+ if (liveStack.running && liveStack.skew !== null) {
319
+ console.warn(
320
+ `getculpa: Culpa is running on ${liveStack.skew.from}; the next \`getculpa\` will pull ${liveStack.skew.to} and migrate (a database backup is taken first).`,
321
+ );
322
+ }
323
+
285
324
  if (platform === "win32") {
286
325
  // CF20-R: the Windows delegate registers the ONE fixed, per-machine HKCU
287
326
  // uninstall key (install-culpa.ps1 / culpa-setup.iss's AppId). A second
@@ -317,6 +356,9 @@ export async function provision(opts) {
317
356
  dockerState,
318
357
  imagesStaged,
319
358
  collectorStaged,
359
+ // T-178: the live docker state at provision time (what RUNS), distinct
360
+ // from imagesStaged (what was pulled)
361
+ liveStack,
320
362
  platform,
321
363
  });
322
364
 
@@ -325,5 +367,5 @@ export async function provision(opts) {
325
367
  // (scripts/install.js) could not see them — which is how a Docker-less
326
368
  // install still printed "Culpa installed successfully." Returning them
327
369
  // changes no behavior here; it just stops postinstall from having to guess.
328
- return { classification, previousVersion, dockerState, imagesStaged, collectorStaged };
370
+ return { classification, previousVersion, dockerState, imagesStaged, collectorStaged, liveStack };
329
371
  }