@openpalm/lib 0.11.2-rc.4 → 0.11.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpalm/lib",
3
- "version": "0.11.2-rc.4",
3
+ "version": "0.11.2",
4
4
  "license": "MPL-2.0",
5
5
  "type": "module",
6
6
  "description": "Shared control-plane library for OpenPalm — lifecycle, staging, secrets, channels, connections, scheduler",
@@ -25,7 +25,7 @@ import {
25
25
  export { sha256, randomHex } from "./crypto.js";
26
26
  import { sha256, randomHex } from "./crypto.js";
27
27
 
28
- const DEFAULT_IMAGE_TAG = "latest";
28
+ export const DEFAULT_IMAGE_TAG = "latest";
29
29
 
30
30
  const logger = createLogger("config-persistence");
31
31
 
@@ -387,6 +387,41 @@ describe("performSetup", () => {
387
387
  expect(result.error).toBeDefined();
388
388
  });
389
389
 
390
+ // ── OP_IMAGE_TAG reconcile (A1: stop preserving a stale pinned tag) ──────
391
+ const stackEnvPath = () => join(homeDir, "knowledge", "env", "stack.env");
392
+
393
+ it("blank imageTag RESETS a stale pinned OP_IMAGE_TAG to the platform default (latest)", async () => {
394
+ // Simulate an old OP_HOME whose stack.env pinned a now-stale version tag.
395
+ writeFileSync(
396
+ stackEnvPath(),
397
+ ["OP_SETUP_COMPLETE=false", "OP_IMAGE_TAG=v0.11.1", ""].join("\n"),
398
+ );
399
+ const result = await performSetup(makeValidSpec()); // no imageTag => blank
400
+ expect(result.ok).toBe(true);
401
+ const env = readFileSync(stackEnvPath(), "utf-8");
402
+ expect(env).toMatch(/^OP_IMAGE_TAG=latest$/m);
403
+ expect(env).not.toMatch(/OP_IMAGE_TAG=v0\.11\.1/);
404
+ });
405
+
406
+ it("a non-empty imageTag pins deliberately (kept verbatim)", async () => {
407
+ const result = await performSetup(makeValidSpec({ imageTag: "v0.11.1" }));
408
+ expect(result.ok).toBe(true);
409
+ expect(readFileSync(stackEnvPath(), "utf-8")).toMatch(/^OP_IMAGE_TAG=v0\.11\.1$/m);
410
+ });
411
+
412
+ it("imageTag is trimmed before writing", async () => {
413
+ const result = await performSetup(makeValidSpec({ imageTag: " dev " }));
414
+ expect(result.ok).toBe(true);
415
+ expect(readFileSync(stackEnvPath(), "utf-8")).toMatch(/^OP_IMAGE_TAG=dev$/m);
416
+ });
417
+
418
+ it("fresh install with blank imageTag writes OP_IMAGE_TAG=latest", async () => {
419
+ // beforeEach's stub stack.env has no OP_IMAGE_TAG.
420
+ const result = await performSetup(makeValidSpec());
421
+ expect(result.ok).toBe(true);
422
+ expect(readFileSync(stackEnvPath(), "utf-8")).toMatch(/^OP_IMAGE_TAG=latest$/m);
423
+ });
424
+
390
425
  it("writes the UI login password to knowledge/secrets", async () => {
391
426
  const result = await performSetup(makeValidSpec());
392
427
  expect(result.ok).toBe(true);
@@ -14,6 +14,7 @@ import {
14
14
  PROVIDER_KEY_MAP,
15
15
  } from "../provider-constants.js";
16
16
  import { mergeEnvContent } from "./env.js";
17
+ import { DEFAULT_IMAGE_TAG } from "./config-persistence.js";
17
18
  import { ensureHomeDirs } from "./home.js";
18
19
  import { acquireInstallLock, releaseInstallLock, type InstallLockHandle } from "./install-lock.js";
19
20
  import {
@@ -226,7 +227,14 @@ export async function performSetup(
226
227
  ? readFileSync(`${state.stashDir}/env/stack.env`, "utf-8")
227
228
  : "";
228
229
  const akmUpdates: Record<string, string> = {};
229
- if (imageTag) akmUpdates.OP_IMAGE_TAG = imageTag;
230
+ // Reconcile OP_IMAGE_TAG on EVERY setup run. A non-empty wizard value pins
231
+ // a tag deliberately; a BLANK field means "track the platform default", so
232
+ // write `latest` rather than silently preserving a stale pin left in an
233
+ // existing stack.env. Without this, re-running setup over an OP_HOME whose
234
+ // OP_IMAGE_TAG was pinned to an old version (e.g. v0.11.1) kept deploying a
235
+ // months-old image — the akm-0.3.1 surprise. The Advanced image-tag field
236
+ // still lets a power user pin deliberately by entering a value.
237
+ akmUpdates.OP_IMAGE_TAG = imageTag && imageTag.trim() ? imageTag.trim() : DEFAULT_IMAGE_TAG;
230
238
  // NOTE: host-akm sharing no longer repoints the container's primary stash
231
239
  // (the old OP_AKM_STASH/OP_AKM_CONFIG split-brain). The personal ~/akm is
232
240
  // wired as a read-write SECONDARY source — see configureHostAkmSharing()