@caelo-cms/provisioning 0.10.48 → 0.10.49

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.
@@ -35,9 +35,12 @@ const POLICY: StoredUpdatePolicy = {
35
35
  windowEnd: null,
36
36
  windowTimezone: "UTC",
37
37
  soakHours: 24,
38
+ securitySoakHours: 2,
38
39
  paused: false,
39
40
  pausedReason: null,
40
41
  pausedAt: null,
42
+ pausedUntil: null,
43
+ pausedByOwner: false,
41
44
  updatedAt: "2026-10-13T00:00:00Z",
42
45
  };
43
46
 
@@ -57,6 +60,7 @@ interface Row {
57
60
  readonly manifest?: ReleaseManifestResult;
58
61
  readonly installed?: InstalledState;
59
62
  readonly trigger?: "manual" | "auto";
63
+ readonly autoBasis?: "security" | "patch" | "minor";
60
64
  readonly policy?: StoredUpdatePolicy;
61
65
  readonly target?: string;
62
66
  /** "proceed" or `<status>/<code>`. */
@@ -157,6 +161,39 @@ const ROWS: readonly Row[] = [
157
161
  manifest: verified({ security: true, advisories: ["GHSA-abcd-efgh-ijkm"] }),
158
162
  expect: "proceed",
159
163
  },
164
+ {
165
+ name: "automatic 'security' flag without a published advisory",
166
+ trigger: "auto",
167
+ policy: { ...POLICY, autoMinor: false, autoPatch: false },
168
+ manifest: verified({ security: true, advisories: [] }),
169
+ expect: "refused/policy",
170
+ },
171
+ {
172
+ name: "queued as a security fix, signed manifest says it is not one",
173
+ trigger: "auto",
174
+ autoBasis: "security",
175
+ expect: "refused/policy",
176
+ reasonContains: "names no published security advisory",
177
+ },
178
+ {
179
+ name: "queued as a security fix, signed manifest agrees",
180
+ trigger: "auto",
181
+ autoBasis: "security",
182
+ manifest: verified({ security: true, advisories: ["GHSA-abcd-efgh-ijkm"] }),
183
+ expect: "proceed",
184
+ },
185
+ {
186
+ name: "automatic, Owner pause already over",
187
+ trigger: "auto",
188
+ policy: {
189
+ ...POLICY,
190
+ paused: true,
191
+ pausedByOwner: true,
192
+ pausedAt: "2026-10-12T00:00:00Z",
193
+ pausedUntil: "2026-10-13T00:00:00Z",
194
+ },
195
+ expect: "proceed",
196
+ },
160
197
  {
161
198
  name: "automatic, outside the window",
162
199
  trigger: "auto",
@@ -175,7 +212,11 @@ const ROWS: readonly Row[] = [
175
212
  describe("assessUpgradeRequest — refusal matrix", () => {
176
213
  it.each(ROWS.map((r) => [r.name, r] as const))("%s", (_name, row) => {
177
214
  const d = assessUpgradeRequest({
178
- request: { targetVersion: row.target ?? "0.11.0", trigger: row.trigger ?? "manual" },
215
+ request: {
216
+ targetVersion: row.target ?? "0.11.0",
217
+ trigger: row.trigger ?? "manual",
218
+ autoBasis: row.autoBasis ?? null,
219
+ },
179
220
  manifest: row.manifest ?? verified(),
180
221
  installed: row.installed ?? INSTALLED,
181
222
  policy: row.policy ?? POLICY,
@@ -30,6 +30,7 @@
30
30
  * | contract (destructive) migrations, automatic run | refused |
31
31
  * | contract migrations, Owner's click | proceed + warning |
32
32
  * | automatic run the update policy does not allow | refused |
33
+ * | automatic "security" run, signed manifest disagrees | refused |
33
34
  *
34
35
  * Self-hosted installs (the host agent, #696 Phase 4c) have no separately
35
36
  * converged infrastructure: their Compose file, roles and bootstrap script
@@ -40,6 +41,7 @@
40
41
  */
41
42
 
42
43
  import {
44
+ type AutoUpdateBasis,
43
45
  autoInstallRefusal,
44
46
  type PlatformUpdateTrigger,
45
47
  type StoredUpdatePolicy,
@@ -119,7 +121,12 @@ const stop = (
119
121
 
120
122
  /** Decide what the job does with a request. See the file comment for the matrix. */
121
123
  export function assessUpgradeRequest(input: {
122
- readonly request: { readonly targetVersion: string; readonly trigger: PlatformUpdateTrigger };
124
+ readonly request: {
125
+ readonly targetVersion: string;
126
+ readonly trigger: PlatformUpdateTrigger;
127
+ /** Why the admin's scheduler queued an automatic request (Phase 5). */
128
+ readonly autoBasis?: AutoUpdateBasis | null;
129
+ };
123
130
  readonly manifest: ReleaseManifestResult;
124
131
  readonly installed: InstalledState;
125
132
  readonly policy: StoredUpdatePolicy;
@@ -228,6 +235,16 @@ export function assessUpgradeRequest(input: {
228
235
  if (refusal) {
229
236
  return stop("refused", "policy", `Not installed automatically: ${refusal}.`, manifest);
230
237
  }
238
+ // The scheduler read the manifest unverified; it may only have used the
239
+ // security fix's shorter soak period if the SIGNED manifest backs it.
240
+ if (request.autoBasis === "security" && assessment.kind !== "security") {
241
+ return stop(
242
+ "refused",
243
+ "policy",
244
+ `Not installed automatically: v${target} was queued as a security fix, but its signed release information names no published security advisory. Install it from Settings → Updates.`,
245
+ manifest,
246
+ );
247
+ }
231
248
  }
232
249
  return { kind: "proceed", manifest, warnings };
233
250
  }
@@ -53,9 +53,12 @@ const POLICY: StoredUpdatePolicy = {
53
53
  windowEnd: null,
54
54
  windowTimezone: "UTC",
55
55
  soakHours: 24,
56
+ securitySoakHours: 2,
56
57
  paused: false,
57
58
  pausedReason: null,
58
59
  pausedAt: null,
60
+ pausedUntil: null,
61
+ pausedByOwner: false,
59
62
  updatedAt: "2026-10-13T00:00:00Z",
60
63
  };
61
64
 
@@ -33,6 +33,7 @@
33
33
  */
34
34
 
35
35
  import type {
36
+ AutoUpdateBasis,
36
37
  PlatformUpdateEventLevel,
37
38
  PlatformUpdatePhase,
38
39
  PlatformUpdateTrigger,
@@ -61,6 +62,8 @@ export interface ClaimedUpdate {
61
62
  readonly id: string;
62
63
  readonly targetVersion: string;
63
64
  readonly trigger: PlatformUpdateTrigger;
65
+ /** Why the admin's scheduler queued an automatic request; null for the Owner's. */
66
+ readonly autoBasis?: AutoUpdateBasis | null;
64
67
  readonly policy: StoredUpdatePolicy;
65
68
  }
66
69