@caelo-cms/shared 0.10.46 → 0.10.48

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 (44) hide show
  1. package/dist/ai-tools.d.ts +1 -0
  2. package/dist/ai-tools.d.ts.map +1 -1
  3. package/dist/ai-tools.js +8 -3
  4. package/dist/ai-tools.js.map +1 -1
  5. package/dist/build-page.d.ts +2 -0
  6. package/dist/build-page.d.ts.map +1 -1
  7. package/dist/build-page.js +7 -1
  8. package/dist/build-page.js.map +1 -1
  9. package/dist/cloud-shell-install.d.ts +27 -0
  10. package/dist/cloud-shell-install.d.ts.map +1 -0
  11. package/dist/cloud-shell-install.js +38 -0
  12. package/dist/cloud-shell-install.js.map +1 -0
  13. package/dist/content.d.ts +21 -0
  14. package/dist/content.d.ts.map +1 -1
  15. package/dist/content.js +25 -0
  16. package/dist/content.js.map +1 -1
  17. package/dist/database-url.d.ts +15 -0
  18. package/dist/database-url.d.ts.map +1 -1
  19. package/dist/database-url.js +15 -0
  20. package/dist/database-url.js.map +1 -1
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/platform-updates.d.ts +154 -0
  26. package/dist/platform-updates.d.ts.map +1 -0
  27. package/dist/platform-updates.js +238 -0
  28. package/dist/platform-updates.js.map +1 -0
  29. package/dist/subagents.d.ts +1 -1
  30. package/dist/version.d.ts +2 -2
  31. package/dist/version.js +1 -1
  32. package/package.json +1 -1
  33. package/src/ai-tools.ts +8 -3
  34. package/src/build-page.ts +9 -3
  35. package/src/cloud-shell-install.test.ts +20 -0
  36. package/src/cloud-shell-install.ts +47 -0
  37. package/src/content.ts +27 -0
  38. package/src/database-url.test.ts +16 -1
  39. package/src/database-url.ts +21 -0
  40. package/src/index.ts +2 -0
  41. package/src/page-ref-limits.test.ts +23 -0
  42. package/src/platform-updates.test.ts +155 -0
  43. package/src/platform-updates.ts +313 -0
  44. package/src/version.ts +1 -1
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC;AAExC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAKL,UAAU,EACV,aAAa,EACb,MAAM,GAGP,MAAM,aAAa,CAAC;AACrB,cAAc,YAAY,CAAC;AAC3B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,aAAa,EACb,aAAa,EAEb,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC;AAExC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAKL,UAAU,EACV,aAAa,EACb,MAAM,GAGP,MAAM,aAAa,CAAC;AACrB,cAAc,YAAY,CAAC;AAC3B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,aAAa,EACb,aAAa,EAEb,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAC"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * In-product Caelo updates (epic #696): the vocabulary the admin (Settings →
3
+ * Updates, the `platform_updates.*` ops), the upgrader job and the Phase 5
4
+ * scheduler share. One module so the timeline the page renders, the states
5
+ * the job writes and the database CHECK constraints (migration 0267) agree.
6
+ */
7
+ import { z } from "zod";
8
+ /** Every state of an update request (`platform_update_requests.status`). */
9
+ export declare const PLATFORM_UPDATE_STATUSES: readonly ["pending", "running", "succeeded", "rolled_back", "failed", "refused", "needs_cloud_shell", "cancelled"];
10
+ export type PlatformUpdateStatusValue = (typeof PLATFORM_UPDATE_STATUSES)[number];
11
+ /** States in which a request is still in flight (at most one at a time). */
12
+ export declare const ACTIVE_UPDATE_STATUSES: readonly PlatformUpdateStatusValue[];
13
+ /** Whether `status` is an end state. */
14
+ export declare function isFinishedUpdateStatus(status: PlatformUpdateStatusValue): boolean;
15
+ /**
16
+ * The phases of an upgrader run, in order (`platform_update_events.phase`).
17
+ * `rollback` only appears when a roll had to be undone.
18
+ */
19
+ export declare const PLATFORM_UPDATE_PHASES: readonly ["queued", "start", "verify", "assess", "backup", "health-baseline", "migrations", "roll", "health", "rollback", "record", "self-update", "finish"];
20
+ export type PlatformUpdatePhase = (typeof PLATFORM_UPDATE_PHASES)[number];
21
+ /** Severity of one timeline line. */
22
+ export declare const PLATFORM_UPDATE_EVENT_LEVELS: readonly ["info", "success", "warn", "error"];
23
+ export type PlatformUpdateEventLevel = (typeof PLATFORM_UPDATE_EVENT_LEVELS)[number];
24
+ /** Who started a request: the Owner's click, or the Phase 5 scheduler. */
25
+ export declare const PLATFORM_UPDATE_TRIGGERS: readonly ["manual", "auto"];
26
+ export type PlatformUpdateTrigger = (typeof PLATFORM_UPDATE_TRIGGERS)[number];
27
+ /**
28
+ * A run still `running` this long after it started died (the upgrader job's
29
+ * task timeout is 60 minutes). The next job run, and the next install
30
+ * request, end it as failed — so a crashed run never blocks updates.
31
+ */
32
+ export declare const PLATFORM_UPDATE_STALE_MINUTES = 90;
33
+ /**
34
+ * Why a stale run was ended (shown on its timeline). Provider-neutral: the
35
+ * same text ends a dead Google Cloud job run and a dead self-hosted agent
36
+ * run, and Settings → Updates shows each install its own way to upgrade by
37
+ * hand (Cloud Shell there, the upgrade command on the server here).
38
+ */
39
+ export declare const PLATFORM_UPDATE_STALE_REASON = "The upgrader stopped before it finished (its run timed out or crashed). The previous release may still serve, or the new one may be partly rolled out: open the site, and if it misbehaves run the upgrade by hand once \u2014 Settings \u2192 Updates shows how for this install.";
40
+ /** Longest an event message may be (DB CHECK). */
41
+ export declare const PLATFORM_UPDATE_EVENT_MAX = 4000;
42
+ /** A SemVer release version without a leading `v` (`0.11.0`, `0.11.0-rc.1`). */
43
+ export declare const releaseVersionSchema: z.ZodString;
44
+ /** How big a step from `from` to `to` is, by SemVer position. */
45
+ export type UpdateKind = "major" | "minor" | "patch";
46
+ /**
47
+ * Classify the step from the installed release to a newer one.
48
+ *
49
+ * @returns null when `to` is not newer than `from` or either is not SemVer.
50
+ * @example updateKind("0.10.45", "0.10.46") // → "patch"
51
+ */
52
+ export declare function updateKind(from: string, to: string): UpdateKind | null;
53
+ /** The automatic-update modes. */
54
+ export declare const UPDATE_POLICY_MODES: readonly ["off", "notify", "auto"];
55
+ export type UpdatePolicyMode = (typeof UPDATE_POLICY_MODES)[number];
56
+ /** Release channels a policy may follow (only `stable` installs automatically). */
57
+ export declare const UPDATE_POLICY_CHANNELS: readonly ["stable", "rc", "beta", "alpha"];
58
+ /** Whether `tz` is an IANA time zone this runtime knows. */
59
+ export declare function isKnownTimeZone(tz: string): boolean;
60
+ /**
61
+ * The Owner's automatic-update policy (`platform_update_policy`), as the
62
+ * ops read and write it. Major releases have no toggle: they are never
63
+ * installed automatically before 1.0.
64
+ */
65
+ export declare const updatePolicySchema: z.ZodObject<{
66
+ mode: z.ZodEnum<{
67
+ auto: "auto";
68
+ off: "off";
69
+ notify: "notify";
70
+ }>;
71
+ autoSecurity: z.ZodBoolean;
72
+ autoPatch: z.ZodBoolean;
73
+ autoMinor: z.ZodBoolean;
74
+ channel: z.ZodEnum<{
75
+ stable: "stable";
76
+ rc: "rc";
77
+ beta: "beta";
78
+ alpha: "alpha";
79
+ }>;
80
+ windowStart: z.ZodNullable<z.ZodString>;
81
+ windowEnd: z.ZodNullable<z.ZodString>;
82
+ windowTimezone: z.ZodString;
83
+ soakHours: z.ZodNumber;
84
+ }, z.core.$strict>;
85
+ export type UpdatePolicy = z.infer<typeof updatePolicySchema>;
86
+ /** The policy plus its pause state, as stored. */
87
+ export interface StoredUpdatePolicy extends UpdatePolicy {
88
+ readonly paused: boolean;
89
+ readonly pausedReason: string | null;
90
+ readonly pausedAt: string | null;
91
+ readonly updatedAt: string;
92
+ }
93
+ /**
94
+ * Whether `at` falls inside the policy's maintenance window. A window whose
95
+ * end is before its start spans midnight (`22:00`–`04:00`).
96
+ */
97
+ export declare function inMaintenanceWindow(policy: Pick<UpdatePolicy, "windowStart" | "windowEnd" | "windowTimezone">, at: Date): boolean;
98
+ /** What the policy needs to know about a release to decide on an automatic install. */
99
+ export interface AutoInstallCandidate {
100
+ readonly kind: UpdateKind;
101
+ /** From the signed manifest AND auto-installable (assessAutoInstall's `kind`). */
102
+ readonly security: boolean;
103
+ /** Manifest `migrations.contract` (or a skipped contract release). */
104
+ readonly contractMigrations: boolean;
105
+ /** The release's channel. */
106
+ readonly channel: string;
107
+ }
108
+ /**
109
+ * Whether the policy lets the upgrader install `candidate` unattended at
110
+ * `at` — the job's check for `trigger = 'auto'` requests (the scheduler
111
+ * applies the soak period before it ever queues one).
112
+ *
113
+ * @returns null when allowed, else the reason in plain words
114
+ */
115
+ export declare function autoInstallRefusal(policy: Pick<StoredUpdatePolicy, "mode" | "autoSecurity" | "autoPatch" | "autoMinor" | "paused" | "windowStart" | "windowEnd" | "windowTimezone">, candidate: AutoInstallCandidate, at: Date): string | null;
116
+ /**
117
+ * How long a verified Google sign-in authorizes one guarded action. Long
118
+ * enough to read the confirmation dialog, short enough that an unattended
119
+ * browser is useless soon after.
120
+ */
121
+ export declare const STEP_UP_MAX_AGE_SECONDS = 600;
122
+ /** What a step-up is for (`step_up_challenges.purpose`). */
123
+ export declare const STEP_UP_PURPOSES: readonly ["platform_update", "update_policy", "step_up_client"];
124
+ export type StepUpPurpose = (typeof STEP_UP_PURPOSES)[number];
125
+ /**
126
+ * Self-hosted installs (#696 Phase 4c): the host agent (`caelo-agent`, a
127
+ * systemd timer on the install's host) polls for update requests this
128
+ * often and writes a heartbeat each time it looks.
129
+ */
130
+ export declare const PLATFORM_UPDATE_AGENT_POLL_SECONDS = 60;
131
+ /**
132
+ * The admin offers "Install update" on a self-hosted install only while the
133
+ * agent's last heartbeat is younger than this (five missed polls): a request
134
+ * nobody picks up would just sit there.
135
+ */
136
+ export declare const PLATFORM_UPDATE_AGENT_FRESH_SECONDS = 300;
137
+ /** The host agent's last check-in (`platform_update_agent`), as the admin reads it. */
138
+ export interface PlatformUpdateAgentStatus {
139
+ /** The Caelo release of the agent's own code. */
140
+ readonly agentVersion: string;
141
+ /** The host it runs on (its hostname). */
142
+ readonly host: string;
143
+ readonly lastSeenAt: string;
144
+ /**
145
+ * What would make an install fail on that host (cosign missing, Docker
146
+ * not reachable), in plain words; null when its preflight passed.
147
+ */
148
+ readonly problem: string | null;
149
+ /** Whether {@link lastSeenAt} is within {@link PLATFORM_UPDATE_AGENT_FRESH_SECONDS}. */
150
+ readonly checkedInRecently: boolean;
151
+ }
152
+ /** Whether an agent last seen at `lastSeenAt` counts as running at `now`. */
153
+ export declare function agentCheckedInRecently(lastSeenAt: Date | string, now: Date): boolean;
154
+ //# sourceMappingURL=platform-updates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-updates.d.ts","sourceRoot":"","sources":["../src/platform-updates.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,238 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+ /**
3
+ * In-product Caelo updates (epic #696): the vocabulary the admin (Settings →
4
+ * Updates, the `platform_updates.*` ops), the upgrader job and the Phase 5
5
+ * scheduler share. One module so the timeline the page renders, the states
6
+ * the job writes and the database CHECK constraints (migration 0267) agree.
7
+ */
8
+ import { z } from "zod";
9
+ /** Every state of an update request (`platform_update_requests.status`). */
10
+ export const PLATFORM_UPDATE_STATUSES = [
11
+ "pending",
12
+ "running",
13
+ "succeeded",
14
+ "rolled_back",
15
+ "failed",
16
+ "refused",
17
+ "needs_cloud_shell",
18
+ "cancelled",
19
+ ];
20
+ /** States in which a request is still in flight (at most one at a time). */
21
+ export const ACTIVE_UPDATE_STATUSES = ["pending", "running"];
22
+ /** Whether `status` is an end state. */
23
+ export function isFinishedUpdateStatus(status) {
24
+ return !ACTIVE_UPDATE_STATUSES.includes(status);
25
+ }
26
+ /**
27
+ * The phases of an upgrader run, in order (`platform_update_events.phase`).
28
+ * `rollback` only appears when a roll had to be undone.
29
+ */
30
+ export const PLATFORM_UPDATE_PHASES = [
31
+ "queued",
32
+ "start",
33
+ "verify",
34
+ "assess",
35
+ "backup",
36
+ "health-baseline",
37
+ "migrations",
38
+ "roll",
39
+ "health",
40
+ "rollback",
41
+ "record",
42
+ "self-update",
43
+ "finish",
44
+ ];
45
+ /** Severity of one timeline line. */
46
+ export const PLATFORM_UPDATE_EVENT_LEVELS = ["info", "success", "warn", "error"];
47
+ /** Who started a request: the Owner's click, or the Phase 5 scheduler. */
48
+ export const PLATFORM_UPDATE_TRIGGERS = ["manual", "auto"];
49
+ /**
50
+ * A run still `running` this long after it started died (the upgrader job's
51
+ * task timeout is 60 minutes). The next job run, and the next install
52
+ * request, end it as failed — so a crashed run never blocks updates.
53
+ */
54
+ export const PLATFORM_UPDATE_STALE_MINUTES = 90;
55
+ /**
56
+ * Why a stale run was ended (shown on its timeline). Provider-neutral: the
57
+ * same text ends a dead Google Cloud job run and a dead self-hosted agent
58
+ * run, and Settings → Updates shows each install its own way to upgrade by
59
+ * hand (Cloud Shell there, the upgrade command on the server here).
60
+ */
61
+ export const PLATFORM_UPDATE_STALE_REASON = "The upgrader stopped before it finished (its run timed out or crashed). The previous release may still serve, or the new one may be partly rolled out: open the site, and if it misbehaves run the upgrade by hand once — Settings → Updates shows how for this install.";
62
+ /** Longest an event message may be (DB CHECK). */
63
+ export const PLATFORM_UPDATE_EVENT_MAX = 4000;
64
+ const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/;
65
+ /** A SemVer release version without a leading `v` (`0.11.0`, `0.11.0-rc.1`). */
66
+ export const releaseVersionSchema = z
67
+ .string()
68
+ .regex(SEMVER, "expected a release version like 0.11.0 (no leading v)");
69
+ /**
70
+ * Classify the step from the installed release to a newer one.
71
+ *
72
+ * @returns null when `to` is not newer than `from` or either is not SemVer.
73
+ * @example updateKind("0.10.45", "0.10.46") // → "patch"
74
+ */
75
+ export function updateKind(from, to) {
76
+ const a = SEMVER.exec(from);
77
+ const b = SEMVER.exec(to);
78
+ if (!a || !b)
79
+ return null;
80
+ const [fa, fb, fc] = [Number(a[1]), Number(a[2]), Number(a[3])];
81
+ const [ta, tb, tc] = [Number(b[1]), Number(b[2]), Number(b[3])];
82
+ if (ta !== fa)
83
+ return ta > fa ? "major" : null;
84
+ if (tb !== fb)
85
+ return tb > fb ? "minor" : null;
86
+ if (tc !== fc)
87
+ return tc > fc ? "patch" : null;
88
+ // Same X.Y.Z: a stable release over its own pre-release, or a later
89
+ // pre-release (rc.1 → rc.2), is a step.
90
+ if (a[4] === undefined)
91
+ return null;
92
+ if (b[4] === undefined)
93
+ return "patch";
94
+ return comparePrerelease(b[4], a[4]) > 0 ? "patch" : null;
95
+ }
96
+ /** SemVer §11 precedence of two pre-release identifier lists. */
97
+ function comparePrerelease(x, y) {
98
+ const xs = x.split(".");
99
+ const ys = y.split(".");
100
+ for (let i = 0; i < Math.max(xs.length, ys.length); i++) {
101
+ const p = xs[i];
102
+ const q = ys[i];
103
+ if (p === undefined)
104
+ return -1;
105
+ if (q === undefined)
106
+ return 1;
107
+ const pn = /^\d+$/.test(p);
108
+ const qn = /^\d+$/.test(q);
109
+ if (pn && qn && Number(p) !== Number(q))
110
+ return Number(p) - Number(q);
111
+ if (pn !== qn)
112
+ return pn ? -1 : 1;
113
+ if (p !== q)
114
+ return p < q ? -1 : 1;
115
+ }
116
+ return 0;
117
+ }
118
+ /** The automatic-update modes. */
119
+ export const UPDATE_POLICY_MODES = ["off", "notify", "auto"];
120
+ /** Release channels a policy may follow (only `stable` installs automatically). */
121
+ export const UPDATE_POLICY_CHANNELS = ["stable", "rc", "beta", "alpha"];
122
+ const WALL_CLOCK = /^([01]\d|2[0-3]):[0-5]\d$/;
123
+ /** Whether `tz` is an IANA time zone this runtime knows. */
124
+ export function isKnownTimeZone(tz) {
125
+ try {
126
+ new Intl.DateTimeFormat("en-US", { timeZone: tz });
127
+ return true;
128
+ }
129
+ catch {
130
+ return false;
131
+ }
132
+ }
133
+ /**
134
+ * The Owner's automatic-update policy (`platform_update_policy`), as the
135
+ * ops read and write it. Major releases have no toggle: they are never
136
+ * installed automatically before 1.0.
137
+ */
138
+ export const updatePolicySchema = z
139
+ .object({
140
+ mode: z.enum(UPDATE_POLICY_MODES),
141
+ autoSecurity: z.boolean(),
142
+ autoPatch: z.boolean(),
143
+ autoMinor: z.boolean(),
144
+ channel: z.enum(UPDATE_POLICY_CHANNELS),
145
+ /** `HH:MM` local time in `windowTimezone`, or null with windowEnd null = any time. */
146
+ windowStart: z.string().regex(WALL_CLOCK, "expected HH:MM").nullable(),
147
+ windowEnd: z.string().regex(WALL_CLOCK, "expected HH:MM").nullable(),
148
+ windowTimezone: z.string().min(1).max(64).refine(isKnownTimeZone, "unknown time zone"),
149
+ soakHours: z.number().int().min(0).max(720),
150
+ })
151
+ .strict()
152
+ .refine((p) => (p.windowStart === null) === (p.windowEnd === null), {
153
+ message: "set both ends of the maintenance window, or neither",
154
+ })
155
+ .refine((p) => p.mode !== "auto" || p.channel === "stable", {
156
+ message: "automatic installs follow the stable channel only",
157
+ });
158
+ /** Minutes since local midnight of `at` in `timeZone`. */
159
+ function localMinutes(at, timeZone) {
160
+ const parts = new Intl.DateTimeFormat("en-GB", {
161
+ timeZone,
162
+ hour: "2-digit",
163
+ minute: "2-digit",
164
+ hourCycle: "h23",
165
+ }).formatToParts(at);
166
+ const n = (t) => Number(parts.find((p) => p.type === t)?.value ?? "0");
167
+ return n("hour") * 60 + n("minute");
168
+ }
169
+ const toMinutes = (hhmm) => Number(hhmm.slice(0, 2)) * 60 + Number(hhmm.slice(3, 5));
170
+ /**
171
+ * Whether `at` falls inside the policy's maintenance window. A window whose
172
+ * end is before its start spans midnight (`22:00`–`04:00`).
173
+ */
174
+ export function inMaintenanceWindow(policy, at) {
175
+ if (policy.windowStart === null || policy.windowEnd === null)
176
+ return true;
177
+ const now = localMinutes(at, policy.windowTimezone);
178
+ const start = toMinutes(policy.windowStart);
179
+ const end = toMinutes(policy.windowEnd);
180
+ if (start === end)
181
+ return true;
182
+ return start < end ? now >= start && now < end : now >= start || now < end;
183
+ }
184
+ /**
185
+ * Whether the policy lets the upgrader install `candidate` unattended at
186
+ * `at` — the job's check for `trigger = 'auto'` requests (the scheduler
187
+ * applies the soak period before it ever queues one).
188
+ *
189
+ * @returns null when allowed, else the reason in plain words
190
+ */
191
+ export function autoInstallRefusal(policy, candidate, at) {
192
+ if (policy.mode !== "auto")
193
+ return "automatic installs are switched off in the update policy";
194
+ if (policy.paused)
195
+ return "automatic installs are paused after a failed install";
196
+ if (candidate.channel !== "stable")
197
+ return "only stable releases install automatically";
198
+ if (candidate.contractMigrations) {
199
+ return "the release carries destructive database changes; the Owner installs it by hand";
200
+ }
201
+ if (candidate.kind === "major")
202
+ return "major releases never install automatically";
203
+ const allowed = (candidate.security && policy.autoSecurity) ||
204
+ (candidate.kind === "patch" && policy.autoPatch) ||
205
+ (candidate.kind === "minor" && policy.autoMinor);
206
+ if (!allowed) {
207
+ return `the policy does not install ${candidate.security ? "security fixes or " : ""}${candidate.kind} releases automatically`;
208
+ }
209
+ if (!inMaintenanceWindow(policy, at))
210
+ return "outside the maintenance window";
211
+ return null;
212
+ }
213
+ /**
214
+ * How long a verified Google sign-in authorizes one guarded action. Long
215
+ * enough to read the confirmation dialog, short enough that an unattended
216
+ * browser is useless soon after.
217
+ */
218
+ export const STEP_UP_MAX_AGE_SECONDS = 600;
219
+ /** What a step-up is for (`step_up_challenges.purpose`). */
220
+ export const STEP_UP_PURPOSES = ["platform_update", "update_policy", "step_up_client"];
221
+ /**
222
+ * Self-hosted installs (#696 Phase 4c): the host agent (`caelo-agent`, a
223
+ * systemd timer on the install's host) polls for update requests this
224
+ * often and writes a heartbeat each time it looks.
225
+ */
226
+ export const PLATFORM_UPDATE_AGENT_POLL_SECONDS = 60;
227
+ /**
228
+ * The admin offers "Install update" on a self-hosted install only while the
229
+ * agent's last heartbeat is younger than this (five missed polls): a request
230
+ * nobody picks up would just sit there.
231
+ */
232
+ export const PLATFORM_UPDATE_AGENT_FRESH_SECONDS = 300;
233
+ /** Whether an agent last seen at `lastSeenAt` counts as running at `now`. */
234
+ export function agentCheckedInRecently(lastSeenAt, now) {
235
+ const seen = new Date(lastSeenAt).getTime();
236
+ return (Number.isFinite(seen) && now.getTime() - seen <= PLATFORM_UPDATE_AGENT_FRESH_SECONDS * 1000);
237
+ }
238
+ //# sourceMappingURL=platform-updates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-updates.js","sourceRoot":"","sources":["../src/platform-updates.ts"],"names":[],"mappings":"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"}
@@ -159,8 +159,8 @@ export declare const rebuildReturnShape: z.ZodObject<{
159
159
  slug: z.ZodOptional<z.ZodString>;
160
160
  status: z.ZodEnum<{
161
161
  rebuilt: "rebuilt";
162
- skipped: "skipped";
163
162
  failed: "failed";
163
+ skipped: "skipped";
164
164
  needs_escalation: "needs_escalation";
165
165
  }>;
166
166
  notes: z.ZodOptional<z.ZodString>;
package/dist/version.d.ts CHANGED
@@ -19,7 +19,7 @@
19
19
  * Format: SemVer 2.0.0. Pre-1.0 minor bumps are breaking; post-1.0
20
20
  * follow standard SemVer.
21
21
  */
22
- export declare const CAELO_VERSION = "0.10.46";
22
+ export declare const CAELO_VERSION = "0.10.48";
23
23
  /**
24
24
  * Deprecated alias for back-compat — early P17 work spelled this
25
25
  * `CALEO_VERSION` (typo of "Caelo"). New code should import
@@ -31,7 +31,7 @@ export declare const CAELO_VERSION = "0.10.46";
31
31
  *
32
32
  * @deprecated use CAELO_VERSION
33
33
  */
34
- export declare const CALEO_VERSION = "0.10.46";
34
+ export declare const CALEO_VERSION = "0.10.48";
35
35
  /**
36
36
  * Parsed shape — major/minor/patch + optional pre-release tag.
37
37
  * Stable interface for callers that need to feature-gate (rare —
package/dist/version.js CHANGED
@@ -20,7 +20,7 @@
20
20
  * Format: SemVer 2.0.0. Pre-1.0 minor bumps are breaking; post-1.0
21
21
  * follow standard SemVer.
22
22
  */
23
- export const CAELO_VERSION = "0.10.46";
23
+ export const CAELO_VERSION = "0.10.48";
24
24
  /**
25
25
  * Deprecated alias for back-compat — early P17 work spelled this
26
26
  * `CALEO_VERSION` (typo of "Caelo"). New code should import
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caelo-cms/shared",
3
- "version": "0.10.46",
3
+ "version": "0.10.48",
4
4
  "private": false,
5
5
  "license": "MPL-2.0",
6
6
  "description": "Shared Zod schemas + types + the two-pass HTML composer used by every Caelo CMS workspace package. Stable enough for plugin authors and embedders to depend on directly.",
package/src/ai-tools.ts CHANGED
@@ -174,8 +174,9 @@ export type LoadSkillToolInput = z.infer<typeof loadSkillToolInput>;
174
174
  export const addModuleToolInput = z
175
175
  .object({
176
176
  target: z.enum(["page", "layout", "template"]),
177
- /** Slug or uuid of the page / layout / template. Resolved server-side. */
178
- targetRef: z.string().min(1).max(200),
177
+ /** Page: id or URL path. Layout / template: slug or id. Resolved server-side. */
178
+ // A page path can run to ~390 chars (6 levels x 64-char slugs).
179
+ targetRef: z.string().min(1).max(500),
179
180
  blockName: z.string().min(1).max(80),
180
181
  position: positionInputSchema,
181
182
  /** Reuse an existing module by id (any target). Mutually exclusive with the
@@ -636,7 +637,8 @@ export const createTemplateToolInput = z
636
637
  export const removeModuleFromToolInput = z
637
638
  .object({
638
639
  target: z.enum(["page", "layout"]),
639
- targetRef: z.string().min(1).max(200),
640
+ // A page path can run to ~390 chars (6 levels x 64-char slugs).
641
+ targetRef: z.string().min(1).max(500),
640
642
  moduleId: z.string().uuid(),
641
643
  })
642
644
  .strict();
@@ -713,6 +715,9 @@ export const duplicatePageToolInput = z
713
715
  newName: z.string().min(1).max(256).optional(),
714
716
  newTitle: z.string().min(1).max(256).optional(),
715
717
  targetTemplateId: z.string().uuid().optional(),
718
+ /** Where the copy sits: a parent page id, or null for the top level.
719
+ * Omitted = next to the source (same parent). */
720
+ newParentPageId: z.string().uuid().nullable().optional(),
716
721
  })
717
722
  .strict();
718
723
 
package/src/build-page.ts CHANGED
@@ -195,6 +195,12 @@ export const buildPageTargetSchema = z
195
195
  name: z.string().min(1).max(256).optional(),
196
196
  templateId: z.string().uuid().optional(),
197
197
  status: pageStatusSchema.optional(),
198
+ /**
199
+ * 0263 — create the page UNDER this parent page; its URL becomes
200
+ * "<parent's path>/<slug>". Create mode only (an existing page moves
201
+ * with `pages.move_many`).
202
+ */
203
+ parentPageId: z.string().uuid().optional(),
198
204
  /**
199
205
  * Migration linkage (issue #278 flow): the `import_pages` row this
200
206
  * page rebuilds. The op stamps `import_pages.accepted_page_id` with
@@ -208,9 +214,9 @@ export const buildPageTargetSchema = z
208
214
  })
209
215
  .strict()
210
216
  .superRefine((page, ctx) => {
211
- const createKeys = (["slug", "title", "name", "templateId", "status"] as const).filter(
212
- (k) => page[k] !== undefined,
213
- );
217
+ const createKeys = (
218
+ ["slug", "title", "name", "templateId", "status", "parentPageId"] as const
219
+ ).filter((k) => page[k] !== undefined);
214
220
  if (page.pageId !== undefined) {
215
221
  if (createKeys.length > 0) {
216
222
  ctx.addIssue({
@@ -0,0 +1,20 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ import { describe, expect, it } from "bun:test";
4
+ import { cloudShellInstallUrl } from "./cloud-shell-install.js";
5
+
6
+ describe("cloudShellInstallUrl", () => {
7
+ it("pins the release branch of the install repo and the provider's tutorial", () => {
8
+ expect(cloudShellInstallUrl("0.10.47", "gcp")).toBe(
9
+ "https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/caelo-cms/install&cloudshell_git_branch=release-v0.10.47&cloudshell_tutorial=gcp.md",
10
+ );
11
+ expect(cloudShellInstallUrl("v1.2.3", "gcp-firebase")).toContain(
12
+ "cloudshell_git_branch=release-v1.2.3&cloudshell_tutorial=gcp-firebase.md",
13
+ );
14
+ });
15
+
16
+ it("refuses pre-releases and garbage: they have no pinned branch", () => {
17
+ expect(() => cloudShellInstallUrl("0.11.0-rc.1", "gcp")).toThrow(/only stable releases/);
18
+ expect(() => cloudShellInstallUrl("latest", "gcp")).toThrow();
19
+ });
20
+ });
@@ -0,0 +1,47 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ /**
4
+ * The "Open in Google Cloud Shell" install link (epic #696, Phase 3).
5
+ *
6
+ * The link opens github.com/caelo-cms/install on the `release-v<version>`
7
+ * branch that repository's track-release workflow creates for every stable
8
+ * release (and never moves), with the provider's walkthrough tutorial. Its
9
+ * `run.sh` pins and verifies exactly that release of the provisioner. One
10
+ * builder for every place that shows the link — README, docs, and the
11
+ * admin's "run this release in Cloud Shell" hint — so they can't drift.
12
+ */
13
+
14
+ /** The repository the Cloud Shell button clones. */
15
+ export const CLOUD_SHELL_INSTALL_REPO = "https://github.com/caelo-cms/install";
16
+
17
+ /** The button image Google publishes for "Open in Cloud Shell" links. */
18
+ export const CLOUD_SHELL_BUTTON_IMAGE = "https://gstatic.com/cloudssh/images/open-btn.svg";
19
+
20
+ /** Providers the Cloud Shell install covers (one tutorial each). */
21
+ export type CloudShellProvider = "gcp" | "gcp-firebase";
22
+
23
+ const STABLE = /^v?(\d+\.\d+\.\d+)$/;
24
+
25
+ /**
26
+ * The Cloud Shell install link for a stable release.
27
+ *
28
+ * @throws for a pre-release or non-SemVer version — only stable releases
29
+ * get a `release-v<version>` branch in the install repository
30
+ *
31
+ * @example cloudShellInstallUrl("0.10.47", "gcp")
32
+ * // → "https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/caelo-cms/install&cloudshell_git_branch=release-v0.10.47&cloudshell_tutorial=gcp.md"
33
+ */
34
+ export function cloudShellInstallUrl(version: string, provider: CloudShellProvider): string {
35
+ const m = STABLE.exec(version);
36
+ if (!m) {
37
+ throw new Error(
38
+ `no Cloud Shell install link for "${version}": only stable releases are pinned in ${CLOUD_SHELL_INSTALL_REPO}`,
39
+ );
40
+ }
41
+ return [
42
+ "https://shell.cloud.google.com/cloudshell/editor",
43
+ `?cloudshell_git_repo=${CLOUD_SHELL_INSTALL_REPO}`,
44
+ `&cloudshell_git_branch=release-v${m[1]}`,
45
+ `&cloudshell_tutorial=${provider}.md`,
46
+ ].join("");
47
+ }
package/src/content.ts CHANGED
@@ -563,6 +563,33 @@ export const pageCreateSchema = z
563
563
  * left the first page unpublished.)
564
564
  */
565
565
  status: pageStatusSchema.optional(),
566
+ /**
567
+ * 0263 — nested page URLs: create the page under this parent; its URL
568
+ * becomes "<parent's path>/<slug>". Omit (or null) for a top-level page.
569
+ */
570
+ parentPageId: z.string().uuid().nullable().optional(),
571
+ })
572
+ .strict();
573
+
574
+ /**
575
+ * 0263 — `pages.move_many`: reparent pages (their whole subtrees move
576
+ * with them) and optionally rename them in the same step.
577
+ */
578
+ export const pageMoveSchema = z
579
+ .object({
580
+ pageId: z.string().uuid(),
581
+ /** The new parent page, or null for the top level. */
582
+ parentPageId: z.string().uuid().nullable(),
583
+ /** Rename while moving — required when the target URL is taken. */
584
+ newSlug: slugSchema.optional(),
585
+ })
586
+ .strict();
587
+
588
+ export const pagesMoveManySchema = z
589
+ .object({
590
+ moves: z.array(pageMoveSchema).min(1).max(200),
591
+ /** `'skip'` suppresses the 301s from the old URLs (operator asked for 404s). */
592
+ redirectFromOld: z.enum(["auto", "skip"]).default("auto"),
566
593
  })
567
594
  .strict();
568
595