@or3/cloud 0.1.70 → 0.1.74

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 (3) hide show
  1. package/README.md +7 -4
  2. package/dist/cli.mjs +102 -74
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -69,8 +69,8 @@ never a development image.
69
69
 
70
70
  Run `verify` after every update and before declaring a deployment healthy. It
71
71
  checks the managed image digest, deep provider health, Basic Auth sign-in,
72
- session hydration, SQLite sync, a disposable filesystem upload/download/delete
73
- cycle, both SQLite databases, volume ownership, proxy runtime settings, and a
72
+ session hydration, SQLite sync, a disposable filesystem upload/download and
73
+ canonical metadata cleanup cycle, both SQLite databases, volume ownership, proxy runtime settings, and a
74
74
  bounded window of serious container logs. On a public VPS, require the real
75
75
  HTTPS path (with no redirects) using `npx @or3/cloud verify --public`.
76
76
  After changing the owner password in the app, pass the current credential only
@@ -80,8 +80,11 @@ Until the one-time handoff file is deleted, `verify` may read its initial owner
80
80
  credential from that protected file; it is never restored to `.env` or Docker
81
81
  container metadata.
82
82
  Verification rejects cross-origin filesystem grants and unexpected methods or
83
- headers, validates the storage ID before uploading, deletes its probe, and
84
- revokes its temporary session even when a later check fails.
83
+ headers, validates the storage ID before uploading, tombstones its probe before
84
+ requesting physical deletion, and revokes its temporary session even when a later
85
+ check fails. When the provider explicitly requires deletion coordination,
86
+ verification reports physical cleanup as deferred; retained probe bytes remain
87
+ subject to the provider retention policy. Other deletion failures still fail verification.
85
88
 
86
89
  Do not run `docker compose down --volumes` on a normal deployment: it deletes
87
90
  the application data. Use `backup` before an update or any destructive action.
package/dist/cli.mjs CHANGED
@@ -31,7 +31,7 @@ import { fileURLToPath } from "node:url";
31
31
  import { createGunzip } from "node:zlib";
32
32
  var execFile = promisify(execFileCallback);
33
33
  var PACKAGE_ROOT = resolve(fileURLToPath(new URL("../", import.meta.url)));
34
- var PACKAGE_VERSION = "0.1.70";
34
+ var PACKAGE_VERSION = "0.1.74";
35
35
  var IMAGE_REPOSITORY = "ghcr.io/saluana/or3-chat";
36
36
  var ASSET_ROOT = resolve(fileURLToPath(new URL("../assets/", import.meta.url)));
37
37
  var LEGACY_STATE_SCHEMA_VERSION = 1;
@@ -368,8 +368,8 @@ async function run(command, args, cwd, environment) {
368
368
  });
369
369
  return {
370
370
  ok: true,
371
- stdout: result.stdout ?? "",
372
- stderr: result.stderr ?? "",
371
+ stdout: result.stdout,
372
+ stderr: result.stderr,
373
373
  command: printable,
374
374
  exitCode: 0
375
375
  };
@@ -494,7 +494,7 @@ function serializeCredentialValue(value) {
494
494
  return serializeEnvValue(value);
495
495
  }
496
496
  function serializeEnv(values) {
497
- return `${Object.entries(values).filter(([, value]) => value !== undefined).map(([key, value]) => `${key}=${serializeEnvValue(value)}`).join(`
497
+ return `${Object.entries(values).filter((entry) => entry[1] !== undefined).map(([key, value]) => `${key}=${serializeEnvValue(value)}`).join(`
498
498
  `)}
499
499
  `;
500
500
  }
@@ -968,10 +968,12 @@ async function managedVolumeRootOwnership(image, volume) {
968
968
  throw new Error(`Managed volume ${volume} returned an invalid root ownership value.`);
969
969
  return { uid: Number(match[1]), gid: Number(match[2]) };
970
970
  }
971
- async function setManagedVolumeRootOwnership(image, volume, ownership) {
971
+ async function setManagedVolumeRootOwnership(directory, state, env, image, ownership) {
972
972
  if (!Number.isSafeInteger(ownership.uid) || ownership.uid < 0 || !Number.isSafeInteger(ownership.gid) || ownership.gid < 0) {
973
973
  throw new Error("Refusing an invalid managed volume root UID/GID.");
974
974
  }
975
+ await ensureManagedDataVolume(directory, state.mode, state, env);
976
+ const volume = state.volumeName;
975
977
  const result = await run("docker", [
976
978
  "run",
977
979
  "--rm",
@@ -1036,7 +1038,7 @@ async function removeManagedDataVolumeForRecreation(directory, state) {
1036
1038
  } catch {
1037
1039
  throw new Error(`Docker returned unreadable metadata for managed volume ${state.volumeName}.`);
1038
1040
  }
1039
- if (volume.Name !== state.volumeName || volume.Labels?.["com.docker.compose.project"] !== state.composeProject || volume.Labels?.["com.docker.compose.volume"] !== "or3-data") {
1041
+ if (volume.Name !== state.volumeName || volume.Labels?.["com.docker.compose.project"] !== state.composeProject || volume.Labels["com.docker.compose.volume"] !== "or3-data") {
1040
1042
  throw new Error(`Volume ${state.volumeName} is not bound to this managed Compose deployment. Refusing to recreate it.`);
1041
1043
  }
1042
1044
  const removedVolume = await run("docker", ["volume", "rm", state.volumeName], directory);
@@ -1045,35 +1047,42 @@ async function removeManagedDataVolumeForRecreation(directory, state) {
1045
1047
  }
1046
1048
  async function ensureManagedDataVolume(directory, mode, state, env) {
1047
1049
  const existing = await run("docker", ["volume", "inspect", state.volumeName, "--format", "{{.Name}}"], directory);
1048
- if (existing.ok && existing.stdout.trim() === state.volumeName)
1049
- return;
1050
- const created = await run("docker", composeArgs(directory, mode, [
1051
- "run",
1052
- "--rm",
1053
- "-T",
1054
- "--no-deps",
1055
- "--entrypoint",
1056
- "sh",
1057
- "or3",
1058
- "-c",
1059
- "true"
1060
- ]), directory);
1061
- if (!created.ok)
1062
- throw new Error(`Could not create the managed data volume. ${redact(created.stderr, secretValues(env))}`);
1050
+ if (existing.ok && existing.stdout.trim() !== state.volumeName) {
1051
+ throw new Error(`Docker returned an unexpected name for managed volume ${state.volumeName}.`);
1052
+ }
1053
+ if (!existing.ok) {
1054
+ if (existing.exitCode !== 1 || !/no such volume/i.test(`${existing.stdout}
1055
+ ${existing.stderr}`)) {
1056
+ throw new Error(`Could not inspect managed volume ${state.volumeName}. Refusing to assume it is missing. ${redact(existing.stderr, secretValues(env))}`);
1057
+ }
1058
+ const created = await run("docker", composeArgs(directory, mode, [
1059
+ "run",
1060
+ "--rm",
1061
+ "-T",
1062
+ "--no-deps",
1063
+ "--entrypoint",
1064
+ "sh",
1065
+ "or3",
1066
+ "-c",
1067
+ "true"
1068
+ ]), directory);
1069
+ if (!created.ok)
1070
+ throw new Error(`Could not create the managed data volume. ${redact(created.stderr, secretValues(env))}`);
1071
+ }
1063
1072
  const inspected = await run("docker", ["volume", "inspect", state.volumeName, "--format", "{{json .Labels}}"], directory);
1064
1073
  if (!inspected.ok)
1065
- throw new Error(`Could not verify the recreated managed data volume ${state.volumeName}.`);
1074
+ throw new Error(`Could not verify managed data volume ${state.volumeName}.`);
1066
1075
  let labels;
1067
1076
  try {
1068
1077
  labels = JSON.parse(inspected.stdout.trim());
1069
1078
  } catch {
1070
- throw new Error(`Docker returned unreadable labels for recreated managed volume ${state.volumeName}.`);
1079
+ throw new Error(`Docker returned unreadable labels for managed volume ${state.volumeName}.`);
1071
1080
  }
1072
1081
  if (!labels || typeof labels !== "object") {
1073
- throw new Error(`Recreated volume ${state.volumeName} has no managed deployment labels.`);
1082
+ throw new Error(`Volume ${state.volumeName} has no managed deployment labels.`);
1074
1083
  }
1075
1084
  if (labels["com.docker.compose.project"] !== state.composeProject || labels["com.docker.compose.volume"] !== "or3-data" || env.OR3_DEPLOYMENT_ID && labels["io.or3.cloud.deployment-id"] !== env.OR3_DEPLOYMENT_ID) {
1076
- throw new Error(`Recreated volume ${state.volumeName} does not carry the expected managed deployment labels.`);
1085
+ throw new Error(`Volume ${state.volumeName} does not carry the expected managed deployment labels.`);
1077
1086
  }
1078
1087
  }
1079
1088
  async function portAvailable(port) {
@@ -1269,6 +1278,8 @@ async function assertImageReleaseIdentity(image, version) {
1269
1278
  } catch {
1270
1279
  throw new Error(`OR3 image ${image} has no readable release labels.`);
1271
1280
  }
1281
+ if (!labels || typeof labels !== "object")
1282
+ throw new Error(`OR3 image ${image} has no readable release labels.`);
1272
1283
  assertImageReleaseLabels(image, labels, version, packagedSourceRevision(version));
1273
1284
  }
1274
1285
  async function imageDigest(image) {
@@ -1636,6 +1647,7 @@ async function installManagedAssets(directory, assets) {
1636
1647
  const token = randomBytes(4).toString("hex");
1637
1648
  const staged = [];
1638
1649
  let retainRollbackCopies = false;
1650
+ const canCleanRollback = () => !retainRollbackCopies;
1639
1651
  try {
1640
1652
  for (const [name, contents] of assets) {
1641
1653
  const destination = join(directory, name);
@@ -1687,7 +1699,7 @@ async function installManagedAssets(directory, assets) {
1687
1699
  await rm(entry.replacement, { force: true }).catch(() => {
1688
1700
  return;
1689
1701
  });
1690
- if (entry.rollback && !retainRollbackCopies)
1702
+ if (entry.rollback && canCleanRollback())
1691
1703
  await rm(entry.rollback, { force: true }).catch(() => {
1692
1704
  return;
1693
1705
  });
@@ -2330,6 +2342,7 @@ async function streamCommandToFile(command, args, destination, cwd, secrets = []
2330
2342
  });
2331
2343
  let stderr = "";
2332
2344
  let timedOut = false;
2345
+ const hasTimedOut = () => timedOut;
2333
2346
  const timeout = setTimeout(() => {
2334
2347
  timedOut = true;
2335
2348
  terminateChildProcess(child);
@@ -2347,7 +2360,7 @@ async function streamCommandToFile(command, args, destination, cwd, secrets = []
2347
2360
  exit,
2348
2361
  pipeline(child.stdout, createWriteStream(temporary, { flags: "wx", mode: 384 }))
2349
2362
  ]);
2350
- if (timedOut)
2363
+ if (hasTimedOut())
2351
2364
  throw new Error(`${command} exceeded the ${STREAM_COMMAND_TIMEOUT_MS / 1000}-second archive deadline.`);
2352
2365
  if (exitCode !== 0) {
2353
2366
  throw new Error(`${command} ${args.join(" ")} exited with ${exitCode}. ${redact(stderr, secrets)}`.trim());
@@ -2375,6 +2388,7 @@ async function streamFileToCommand(command, args, source, cwd, secrets = []) {
2375
2388
  });
2376
2389
  let stderr = "";
2377
2390
  let timedOut = false;
2391
+ const hasTimedOut = () => timedOut;
2378
2392
  const timeout = setTimeout(() => {
2379
2393
  timedOut = true;
2380
2394
  terminateChildProcess(child);
@@ -2392,7 +2406,7 @@ async function streamFileToCommand(command, args, source, cwd, secrets = []) {
2392
2406
  exit,
2393
2407
  pipeline(createReadStream(source), child.stdin)
2394
2408
  ]);
2395
- if (timedOut)
2409
+ if (hasTimedOut())
2396
2410
  throw new Error(`${command} exceeded the ${STREAM_COMMAND_TIMEOUT_MS / 1000}-second archive deadline.`);
2397
2411
  if (exitCode !== 0) {
2398
2412
  throw new Error(`${command} ${args.join(" ")} exited with ${exitCode}. ${redact(stderr, secrets)}`.trim());
@@ -3785,7 +3799,7 @@ async function assessUpdate(directory, targetVersion, options = {}) {
3785
3799
  }
3786
3800
  try {
3787
3801
  assertKnownStateSchema(state?.schemaVersion);
3788
- checks.push({ code: "state-schema", status: "passed", detail: `Managed state schema ${String(state?.schemaVersion)} is supported.` });
3802
+ checks.push({ code: "state-schema", status: "passed", detail: `Managed state schema ${String(state.schemaVersion)} is supported.` });
3789
3803
  } catch (error) {
3790
3804
  findings.push({ code: "state-schema-unsupported", severity: "blocker", message: error instanceof Error ? error.message : String(error) });
3791
3805
  }
@@ -3954,7 +3968,7 @@ async function runUpdateCommand(directory, flags, asJson) {
3954
3968
  await stopProject(loaded.directory, state.mode);
3955
3969
  }
3956
3970
  if (migrateLegacyVolume && !recreateDataVolume) {
3957
- await setManagedVolumeRootOwnership(targetImage, state.volumeName, {
3971
+ await setManagedVolumeRootOwnership(loaded.directory, state, oldEnv, targetImage, {
3958
3972
  uid: MANAGED_RUNTIME_UID,
3959
3973
  gid: MANAGED_RUNTIME_GID
3960
3974
  });
@@ -3972,7 +3986,7 @@ async function runUpdateCommand(directory, flags, asJson) {
3972
3986
  return;
3973
3987
  });
3974
3988
  if (migrateLegacyVolume && !recreateDataVolume) {
3975
- await setManagedVolumeRootOwnership(targetImage, state.volumeName, previousRootOwnership);
3989
+ await setManagedVolumeRootOwnership(loaded.directory, state, oldEnv, targetImage, previousRootOwnership);
3976
3990
  }
3977
3991
  await restoreBackupData(loaded.directory, state, oldEnv, backup.backupDir, { recreateDataVolume });
3978
3992
  } catch (restoreError) {
@@ -4164,7 +4178,7 @@ async function restorePreMutationSnapshot(directory, state, env) {
4164
4178
  }
4165
4179
  if (pending.previousRootOwnership) {
4166
4180
  await pullAndRequireImage(state.image, state.imageDigest, "Previous deployment");
4167
- await setManagedVolumeRootOwnership(state.image, state.volumeName, pending.previousRootOwnership);
4181
+ await setManagedVolumeRootOwnership(directory, state, env, state.image, pending.previousRootOwnership);
4168
4182
  }
4169
4183
  await restoreBackupData(directory, state, env, previous.path);
4170
4184
  return previous;
@@ -4504,7 +4518,7 @@ async function doctorCommand(directory) {
4504
4518
  }
4505
4519
  function validateVerificationHealth(value) {
4506
4520
  const health = value;
4507
- if (!health || health.status !== "ok" || health.providers?.auth?.provider !== "basic-auth" || health.providers?.sync?.provider !== "sqlite" || health.providers?.storage?.provider !== "fs") {
4521
+ if (!health || health.status !== "ok" || health.providers?.auth?.provider !== "basic-auth" || health.providers.sync?.provider !== "sqlite" || health.providers.storage?.provider !== "fs") {
4508
4522
  throw new Error("Public deep health does not report the managed Basic Auth + SQLite + filesystem profile.");
4509
4523
  }
4510
4524
  return health;
@@ -4524,15 +4538,29 @@ async function verificationJson(baseUrl, path, options = {}) {
4524
4538
  accept: "application/json",
4525
4539
  ...options.body === undefined ? {} : { "content-type": "application/json" },
4526
4540
  ...options.cookie ? { cookie: options.cookie } : {},
4527
- ...method === "GET" ? {} : { origin: baseUrl.origin }
4541
+ ...method === "GET" ? {} : { origin: baseUrl.origin, "x-or3-cloud-intent": "mutation" }
4528
4542
  },
4529
4543
  ...options.body === undefined ? {} : { body: JSON.stringify(options.body) }
4530
4544
  });
4531
4545
  if (response.status !== 200) {
4532
- const detail = (await response.text().catch(() => "")).slice(0, 300);
4546
+ const responseText = await response.text().catch(() => "");
4547
+ const detail = responseText.slice(0, 300);
4548
+ if (options.allowStorageDeletionDeferral && path === "/api/storage/delete" && response.status === 503) {
4549
+ let failure;
4550
+ try {
4551
+ failure = JSON.parse(responseText);
4552
+ } catch {}
4553
+ if (failure && typeof failure === "object" && "statusMessage" in failure && failure.statusMessage === "Provider-owned deletion coordination is required") {
4554
+ return { deletionDeferred: true };
4555
+ }
4556
+ }
4533
4557
  throw new Error(`${response.url} returned HTTP ${response.status}${detail ? `: ${detail}` : ""}`);
4534
4558
  }
4535
- return response.json();
4559
+ const value = await response.json();
4560
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
4561
+ throw new Error(`${response.url} returned an invalid JSON object.`);
4562
+ }
4563
+ return value;
4536
4564
  }
4537
4565
  function sameOriginVerificationUrl(baseUrl, value, label) {
4538
4566
  if (typeof value !== "string")
@@ -4599,7 +4627,7 @@ async function verifyPublicApplication(baseUrl, credentials) {
4599
4627
  throw new Error("Public Basic Auth sign-in did not set a session cookie.");
4600
4628
  try {
4601
4629
  const session = await verificationJson(baseUrl, "/api/auth/session", { cookie });
4602
- if (session.session?.user?.email !== email || !session.session?.workspace?.id) {
4630
+ if (session.session?.user?.email !== email || !session.session.workspace?.id) {
4603
4631
  throw new Error("Public session hydration did not return the verification user and workspace.");
4604
4632
  }
4605
4633
  const workspaceId = String(session.session.workspace.id);
@@ -4617,6 +4645,7 @@ async function verifyPublicApplication(baseUrl, credentials) {
4617
4645
  const hash = `sha256:${createHash("sha256").update(probeBytes).digest("hex")}`;
4618
4646
  let storageId;
4619
4647
  let metadataAttempted = false;
4648
+ let physicalCleanupDeferred = false;
4620
4649
  try {
4621
4650
  const presign = await verificationJson(baseUrl, "/api/storage/presign-upload", {
4622
4651
  cookie,
@@ -4639,6 +4668,7 @@ async function verifyPublicApplication(baseUrl, credentials) {
4639
4668
  });
4640
4669
  if (!upload.ok)
4641
4670
  throw new Error(`Filesystem verification upload returned HTTP ${upload.status}.`);
4671
+ metadataAttempted = true;
4642
4672
  await verificationJson(baseUrl, "/api/storage/commit", {
4643
4673
  cookie,
4644
4674
  body: {
@@ -4654,7 +4684,6 @@ async function verifyPublicApplication(baseUrl, credentials) {
4654
4684
  });
4655
4685
  const metadataCreatedAt = Date.now();
4656
4686
  const metadataOpId = randomUUID();
4657
- metadataAttempted = true;
4658
4687
  const pushed = await verificationJson(baseUrl, "/api/sync/push", {
4659
4688
  cookie,
4660
4689
  body: {
@@ -4702,45 +4731,44 @@ async function verifyPublicApplication(baseUrl, credentials) {
4702
4731
  }
4703
4732
  } finally {
4704
4733
  if (storageId) {
4705
- try {
4706
- await verificationJson(baseUrl, "/api/storage/delete", {
4734
+ if (metadataAttempted) {
4735
+ const metadataDeleteAt = Date.now();
4736
+ const metadataDeleteOpId = randomUUID();
4737
+ const deleted = await verificationJson(baseUrl, "/api/sync/push", {
4707
4738
  cookie,
4708
- body: { workspace_id: workspaceId, hash, storage_id: storageId }
4709
- });
4710
- } finally {
4711
- if (metadataAttempted) {
4712
- const metadataDeleteAt = Date.now();
4713
- const metadataDeleteOpId = randomUUID();
4714
- const deleted = await verificationJson(baseUrl, "/api/sync/push", {
4715
- cookie,
4716
- body: {
4717
- scope: { workspaceId },
4718
- ops: [{
4719
- id: `or3-verification-${metadataDeleteOpId}`,
4720
- tableName: "file_meta",
4721
- operation: "delete",
4722
- pk: hash,
4723
- payload: { hash },
4724
- stamp: {
4725
- deviceId: "or3-cloud-verification",
4726
- opId: metadataDeleteOpId,
4727
- hlc: `${String(metadataDeleteAt).padStart(13, "0")}:0000:or3-cloud-verification`,
4728
- clock: metadataDeleteAt
4729
- },
4730
- createdAt: metadataDeleteAt,
4731
- attempts: 0,
4732
- status: "pending"
4733
- }]
4734
- }
4735
- });
4736
- if (deleted.results?.[0]?.success !== true) {
4737
- throw new Error(`Filesystem verification metadata cleanup failed: ${JSON.stringify(deleted)}`);
4739
+ body: {
4740
+ scope: { workspaceId },
4741
+ ops: [{
4742
+ id: `or3-verification-${metadataDeleteOpId}`,
4743
+ tableName: "file_meta",
4744
+ operation: "delete",
4745
+ pk: hash,
4746
+ payload: { hash },
4747
+ stamp: {
4748
+ deviceId: "or3-cloud-verification",
4749
+ opId: metadataDeleteOpId,
4750
+ hlc: `${String(metadataDeleteAt).padStart(13, "0")}:0000:or3-cloud-verification`,
4751
+ clock: metadataDeleteAt
4752
+ },
4753
+ createdAt: metadataDeleteAt,
4754
+ attempts: 0,
4755
+ status: "pending"
4756
+ }]
4738
4757
  }
4758
+ });
4759
+ if (deleted.results?.[0]?.success !== true) {
4760
+ throw new Error(`Filesystem verification metadata cleanup failed: ${JSON.stringify(deleted)}`);
4739
4761
  }
4740
4762
  }
4763
+ const deletion = await verificationJson(baseUrl, "/api/storage/delete", {
4764
+ cookie,
4765
+ body: { workspace_id: workspaceId, hash, storage_id: storageId },
4766
+ allowStorageDeletionDeferral: true
4767
+ });
4768
+ physicalCleanupDeferred = deletion.deletionDeferred === true;
4741
4769
  }
4742
4770
  }
4743
- return health;
4771
+ return { ...health, physicalCleanupDeferred };
4744
4772
  } finally {
4745
4773
  await verificationJson(baseUrl, "/api/basic-auth/sign-out", {
4746
4774
  cookie,
@@ -4767,7 +4795,7 @@ async function provisionManagedCredentialsOnce(baseUrl, email, password, adminUs
4767
4795
  const appCookie = responseCookie(appSignIn, "Managed owner provisioning");
4768
4796
  try {
4769
4797
  const session = await verificationJson(baseUrl, "/api/auth/session", { cookie: appCookie });
4770
- if (session.session?.user?.email !== email || !session.session?.workspace?.id) {
4798
+ if (session.session?.user?.email !== email || !session.session.workspace?.id) {
4771
4799
  throw new Error("Managed owner provisioning did not create the expected account and workspace.");
4772
4800
  }
4773
4801
  } finally {
@@ -4818,7 +4846,7 @@ async function verifyCommand(directory, flags, positionals) {
4818
4846
  }
4819
4847
  await waitForDeepHealth(loaded.directory, loaded.state.mode, secretValues(loaded.env));
4820
4848
  const baseUrl = new URL(loaded.state.mode === "public" ? `https://${loaded.state.domain}` : `http://127.0.0.1:${loaded.state.port}`);
4821
- await verifyPublicApplication(baseUrl, await verificationCredentials(loaded.directory, flags, loaded.env));
4849
+ const verification = await verifyPublicApplication(baseUrl, await verificationCredentials(loaded.directory, flags, loaded.env));
4822
4850
  const databaseCheck = await run("docker", [
4823
4851
  ...composeArgs(loaded.directory, loaded.state.mode, ["exec", "-T", "or3", ...containerNodeCommand(VERIFY_DATABASES_SCRIPT)])
4824
4852
  ], loaded.directory);
@@ -4849,7 +4877,7 @@ ${redact(serious.slice(-20).join(`
4849
4877
  console.log(`✓ OR3 ${loaded.state.appVersion} image digest matches managed state`);
4850
4878
  console.log(`✓ ${baseUrl.origin} root and deep health return HTTP 200 without redirects`);
4851
4879
  console.log("✓ Basic Auth sign-in, session hydration, and SQLite sync pull passed");
4852
- console.log("✓ Filesystem storage write/read/delete probe passed");
4880
+ console.log(verification.physicalCleanupDeferred ? "✓ Filesystem storage write/read and canonical metadata cleanup passed; physical cleanup deferred by provider retention policy" : "✓ Filesystem storage write/read and canonical/physical cleanup passed");
4853
4881
  console.log("✓ auth.sqlite and sync.sqlite quick_check passed with managed ownership");
4854
4882
  console.log("✓ Recent bounded logs contain no fatal, panic, unhandled, or OOM events");
4855
4883
  console.log("OR3 production verification passed.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@or3/cloud",
3
- "version": "0.1.70",
3
+ "version": "0.1.74",
4
4
  "description": "Install and operate the OR3 Cloud container distribution",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0",
@@ -19,9 +19,9 @@
19
19
  "dashboardUpdateProtocol": 1,
20
20
  "dashboardUpdateMinimumSourceVersion": "0.1.40",
21
21
  "stateSchema": 1,
22
- "imageDigest": "sha256:98bf853b1f7f4e482ce705b9819c2921be648b30e9ad52980e1f12641fbe8064",
23
- "operatorImageDigest": "sha256:bd585210d63c09d98073d19bb4c4f6ba598059fe6c91d6b8db7c8f646cd86161",
24
- "sourceRevision": "bd1f1310af3c1ef9e66de6d438057eed48a9e049"
22
+ "imageDigest": "sha256:d01c7513cb26858b23c279ab0cfe39a28bb73691e69e566be29460ed385d42ca",
23
+ "operatorImageDigest": "sha256:bd7162734324f40daa85861c314771d7437cbde258f0b45ae7d6a878810e5fdf",
24
+ "sourceRevision": "77049d904cf405b3f5a33ef85b622d91c9250008"
25
25
  },
26
26
  "files": [
27
27
  "dist",