@spacefast/common 0.0.21 → 0.0.24

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 (182) hide show
  1. package/dist/agents/connect-targets.d.ts +9 -6
  2. package/dist/agents/connect-targets.js +9 -6
  3. package/dist/config/domains.d.ts +1 -1
  4. package/dist/config/domains.js +1 -0
  5. package/dist/config/index.d.ts +0 -1
  6. package/dist/config/index.js +0 -1
  7. package/dist/contracts/abuse.d.ts +2 -0
  8. package/dist/contracts/abuse.js +12 -0
  9. package/dist/contracts/access.d.ts +121 -9
  10. package/dist/contracts/access.js +73 -9
  11. package/dist/contracts/activity.d.ts +1 -1
  12. package/dist/contracts/activity.js +0 -9
  13. package/dist/contracts/api-keys.d.ts +21 -21
  14. package/dist/contracts/beta.js +1 -1
  15. package/dist/contracts/billing.d.ts +1 -34
  16. package/dist/contracts/billing.js +4 -53
  17. package/dist/contracts/builds.d.ts +8 -8
  18. package/dist/contracts/builds.js +10 -3
  19. package/dist/contracts/cli.d.ts +10 -0
  20. package/dist/contracts/cli.js +10 -0
  21. package/dist/contracts/collab-public.d.ts +63 -0
  22. package/dist/contracts/collab-public.js +46 -0
  23. package/dist/contracts/comments.d.ts +283 -54
  24. package/dist/contracts/comments.js +173 -52
  25. package/dist/contracts/common.d.ts +3 -3
  26. package/dist/contracts/device-auth.d.ts +6 -6
  27. package/dist/contracts/device-auth.js +1 -1
  28. package/dist/contracts/docs.d.ts +5 -5
  29. package/dist/contracts/domains.d.ts +36 -36
  30. package/dist/contracts/enums.d.ts +33 -30
  31. package/dist/contracts/enums.js +52 -38
  32. package/dist/contracts/error-code-meta.d.ts +37 -126
  33. package/dist/contracts/error-code-meta.js +12 -41
  34. package/dist/contracts/error-codes.d.ts +1 -1
  35. package/dist/contracts/error-codes.js +10 -32
  36. package/dist/contracts/execution.d.ts +35 -57
  37. package/dist/contracts/execution.js +41 -40
  38. package/dist/contracts/feature-lifecycle-core.d.ts +0 -6
  39. package/dist/contracts/feature-lifecycle-core.js +2 -31
  40. package/dist/contracts/feature-lifecycle.d.ts +8 -38
  41. package/dist/contracts/feature-lifecycle.js +16 -48
  42. package/dist/contracts/features.d.ts +15 -13
  43. package/dist/contracts/features.js +39 -38
  44. package/dist/contracts/functions.d.ts +97 -70
  45. package/dist/contracts/functions.js +101 -34
  46. package/dist/contracts/git.d.ts +1 -1
  47. package/dist/contracts/grants.d.ts +7 -3
  48. package/dist/contracts/grants.js +18 -9
  49. package/dist/contracts/internal.d.ts +14 -1
  50. package/dist/contracts/internal.js +17 -7
  51. package/dist/contracts/oauth-resources.d.ts +4 -3
  52. package/dist/contracts/oauth-resources.js +12 -3
  53. package/dist/contracts/operations.d.ts +90 -5
  54. package/dist/contracts/operations.js +75 -1
  55. package/dist/contracts/pages.d.ts +6 -1
  56. package/dist/contracts/pages.js +24 -1
  57. package/dist/contracts/plan-policy.d.ts +1 -3
  58. package/dist/contracts/plan-policy.js +1 -2
  59. package/dist/contracts/platform.d.ts +61 -148
  60. package/dist/contracts/platform.js +70 -126
  61. package/dist/contracts/publish-archive.d.ts +498 -0
  62. package/dist/contracts/publish-archive.js +25 -0
  63. package/dist/contracts/resources.d.ts +12 -24
  64. package/dist/contracts/resources.js +32 -12
  65. package/dist/contracts/runtime-api.d.ts +369 -175
  66. package/dist/contracts/runtime-api.js +372 -348
  67. package/dist/contracts/runtime-app.d.ts +127 -120
  68. package/dist/contracts/runtime-app.js +19 -10
  69. package/dist/contracts/runtime-db.d.ts +1 -1
  70. package/dist/contracts/runtime-services.d.ts +219 -0
  71. package/dist/contracts/runtime-services.js +437 -0
  72. package/dist/contracts/runtime-storage.d.ts +45 -32
  73. package/dist/contracts/runtime-storage.js +30 -8
  74. package/dist/contracts/space-config.d.ts +10 -16
  75. package/dist/contracts/space-config.js +14 -10
  76. package/dist/contracts/spaces.d.ts +241 -436
  77. package/dist/contracts/spaces.js +128 -145
  78. package/dist/contracts/superadmin-activity.d.ts +57 -4
  79. package/dist/contracts/superadmin-activity.js +48 -2
  80. package/dist/contracts/superadmin-search.d.ts +126 -0
  81. package/dist/contracts/superadmin-search.js +61 -0
  82. package/dist/contracts/superadmin-spaces.d.ts +358 -89
  83. package/dist/contracts/superadmin-spaces.js +97 -24
  84. package/dist/contracts/superadmin-teams.d.ts +28 -10
  85. package/dist/contracts/superadmin-teams.js +5 -8
  86. package/dist/contracts/superadmin.d.ts +0 -32
  87. package/dist/contracts/superadmin.js +3 -11
  88. package/dist/contracts/tags.d.ts +9 -9
  89. package/dist/contracts/teams.d.ts +44 -2
  90. package/dist/contracts/teams.js +43 -23
  91. package/dist/contracts/transfers.d.ts +0 -49
  92. package/dist/contracts/transfers.js +2 -6
  93. package/dist/contracts/usage.d.ts +61 -0
  94. package/dist/contracts/usage.js +64 -0
  95. package/dist/contracts/variables.d.ts +2 -7
  96. package/dist/contracts/variables.js +6 -5
  97. package/dist/contracts/webhooks.d.ts +2 -2
  98. package/dist/contracts/zero.d.ts +87 -56
  99. package/dist/contracts/zero.js +82 -20
  100. package/dist/dashboard-paths/index.d.ts +1 -1
  101. package/dist/dashboard-paths/index.js +1 -0
  102. package/dist/docs/agent-prose.d.ts +1 -1
  103. package/dist/docs/agent-prose.js +62 -66
  104. package/dist/docs/agent-setup.js +49 -23
  105. package/dist/docs/agent-solutions.d.ts +4 -4
  106. package/dist/docs/agent-solutions.js +8 -8
  107. package/dist/docs/catalog.d.ts +19 -19
  108. package/dist/docs/catalog.js +30 -19
  109. package/dist/docs/error-docs.js +63 -151
  110. package/dist/docs/skills.d.ts +3 -3
  111. package/dist/docs/skills.js +1 -1
  112. package/dist/test-helpers/fetch-stub.d.ts +16 -0
  113. package/dist/test-helpers/fetch-stub.js +24 -0
  114. package/dist/utils/build-settings.js +9 -3
  115. package/dist/utils/canonical-json.d.ts +10 -0
  116. package/dist/utils/canonical-json.js +21 -0
  117. package/dist/utils/cast-transport.d.ts +28 -0
  118. package/dist/utils/cast-transport.js +52 -0
  119. package/dist/utils/claim-token.d.ts +0 -8
  120. package/dist/utils/claim-token.js +3 -15
  121. package/dist/utils/credential-policy.d.ts +1 -1
  122. package/dist/utils/env-file.d.ts +17 -0
  123. package/dist/utils/env-file.js +96 -0
  124. package/dist/utils/error-code.d.ts +2 -0
  125. package/dist/utils/error-code.js +7 -0
  126. package/dist/utils/generate-space-name.d.ts +8 -10
  127. package/dist/utils/generate-space-name.js +206 -21
  128. package/dist/utils/grant-decision.fixtures.json +2938 -0
  129. package/dist/utils/grants.d.ts +2 -0
  130. package/dist/utils/grants.js +25 -2
  131. package/dist/utils/gravatar.d.ts +9 -0
  132. package/dist/utils/gravatar.js +31 -0
  133. package/dist/utils/idempotency.d.ts +6 -0
  134. package/dist/utils/idempotency.js +28 -0
  135. package/dist/utils/local-space-state.d.ts +1 -5
  136. package/dist/utils/local-space-state.js +7 -26
  137. package/dist/utils/page-fonts.d.ts +2 -2
  138. package/dist/utils/page-fonts.js +22 -4
  139. package/dist/utils/pages.d.ts +1 -2
  140. package/dist/utils/pages.js +21 -5
  141. package/dist/utils/publish-form-data.d.ts +12 -0
  142. package/dist/utils/publish-form-data.js +10 -0
  143. package/dist/utils/publish-policy.d.ts +20 -0
  144. package/dist/utils/publish-policy.fixtures.json +397 -0
  145. package/dist/utils/publish-policy.js +36 -19
  146. package/dist/utils/push-new.d.ts +7 -0
  147. package/dist/utils/push-new.js +7 -0
  148. package/dist/utils/query-keys.d.ts +0 -1
  149. package/dist/utils/query-keys.js +0 -1
  150. package/dist/utils/runtime-paths.d.ts +5 -0
  151. package/dist/utils/runtime-paths.js +5 -0
  152. package/dist/utils/runtime-upload-batch.d.ts +0 -5
  153. package/dist/utils/runtime-upload-batch.js +1 -27
  154. package/dist/utils/runtime-upload.d.ts +3 -13
  155. package/dist/utils/runtime-upload.js +4 -14
  156. package/dist/utils/space-config.d.ts +0 -33
  157. package/dist/utils/space-config.js +5 -326
  158. package/dist/utils/storage-policy.d.ts +1 -1
  159. package/dist/utils/storage-policy.js +3 -1
  160. package/dist/utils/structured-error.d.ts +14 -0
  161. package/dist/utils/structured-error.js +22 -0
  162. package/dist/utils/version-path.js +12 -3
  163. package/dist/vocabulary.d.ts +5 -3
  164. package/dist/vocabulary.js +13 -10
  165. package/package.json +10 -5
  166. package/dist/config/postgres-budget.d.ts +0 -129
  167. package/dist/config/postgres-budget.js +0 -140
  168. package/dist/config/postgres-budget.json +0 -51
  169. package/dist/contracts/archives.d.ts +0 -331
  170. package/dist/contracts/archives.js +0 -120
  171. package/dist/contracts/deployments.d.ts +0 -574
  172. package/dist/contracts/deployments.js +0 -97
  173. package/dist/contracts/sites.d.ts +0 -416
  174. package/dist/contracts/sites.js +0 -168
  175. package/dist/contracts/superadmin-queues.d.ts +0 -414
  176. package/dist/contracts/superadmin-queues.js +0 -202
  177. package/dist/contracts/superadmin-runtime.d.ts +0 -27
  178. package/dist/contracts/superadmin-runtime.js +0 -38
  179. package/dist/index.d.ts +0 -1
  180. package/dist/index.js +0 -1
  181. package/dist/utils/api.d.ts +0 -4
  182. package/dist/utils/api.js +0 -13
@@ -44,6 +44,8 @@ export type GrantEvaluation = {
44
44
  matches: GrantMatch[];
45
45
  };
46
46
  export declare function grantResourcePatternMatches(pattern: string, resource: string, resourceKind?: "route" | "artifact"): boolean;
47
+ /** Does a Grant's resource set reach this path? Include, minus exclude. */
48
+ export declare function grantResourceSetMatches(resources: Grant["resources"], resource: string, resourceKind?: "route" | "artifact"): boolean;
47
49
  export type GrantEvaluationExplanation = {
48
50
  grantId: string;
49
51
  failures: GrantCheckFailure[];
@@ -28,7 +28,7 @@ function targetMatches(selector, target) {
28
28
  case "branch":
29
29
  return target.kind === "branch" && target.branch === selector.branch;
30
30
  case "all_versions":
31
- return target.kind === "version";
31
+ return target.kind === "live" || target.kind === "version";
32
32
  }
33
33
  throw new Error("Unsupported Grant target.");
34
34
  }
@@ -70,6 +70,29 @@ export function grantResourcePatternMatches(pattern, resource, resourceKind = "r
70
70
  };
71
71
  return matches(0, 0);
72
72
  }
73
+ /**
74
+ * Exclude patterns read wider than include patterns, and that asymmetry is the
75
+ * rule, not an accident: the route lane collapses a terminal `index.html`
76
+ * (normalizeAccessScopePath) while the artifact lane keeps it, so an exclude
77
+ * naming an index artifact must also close the route that artifact serves.
78
+ * Include never gets the same courtesy — granting the stripped route would hand
79
+ * out access nobody wrote down. The serving engine compiles the identical pair
80
+ * (access-rules.php _stattic_grant_exclude_pattern_variants).
81
+ */
82
+ function grantExcludePatternMatches(pattern, resource, resourceKind) {
83
+ if (grantResourcePatternMatches(pattern, resource, resourceKind))
84
+ return true;
85
+ const canonical = normalizeGrantResourcePattern(pattern);
86
+ if (!canonical.endsWith("/index.html"))
87
+ return false;
88
+ const stripped = canonical.slice(0, -"/index.html".length);
89
+ return grantResourcePatternMatches(stripped === "" ? "/" : stripped, resource, resourceKind);
90
+ }
91
+ /** Does a Grant's resource set reach this path? Include, minus exclude. */
92
+ export function grantResourceSetMatches(resources, resource, resourceKind = "route") {
93
+ return (resources.include.some((pattern) => grantResourcePatternMatches(pattern, resource, resourceKind)) &&
94
+ !resources.exclude.some((pattern) => grantExcludePatternMatches(pattern, resource, resourceKind)));
95
+ }
73
96
  function grantCheckFailures(grant, input, principal, now) {
74
97
  const failures = [];
75
98
  if (grant.source.kind === "config" && !input.spaceClaimed) {
@@ -120,7 +143,7 @@ function grantCheckFailures(grant, input, principal, now) {
120
143
  if (!included) {
121
144
  failures.push("resource_not_included");
122
145
  }
123
- else if (grant.resources.exclude.some((pattern) => grantResourcePatternMatches(pattern, input.resource, input.resourceKind))) {
146
+ else if (grant.resources.exclude.some((pattern) => grantExcludePatternMatches(pattern, input.resource, input.resourceKind ?? "route"))) {
124
147
  failures.push("resource_excluded");
125
148
  }
126
149
  return failures;
@@ -0,0 +1,9 @@
1
+ /** Gravatar's canonical address form: trimmed and lowercased. */
2
+ export declare function normalizeGravatarEmail(email: string | null | undefined): string | null;
3
+ /** The canonical avatar URL for an already-computed SHA-256 hash. */
4
+ export declare function gravatarUrlFromHash(hashHex: string): string;
5
+ /**
6
+ * SHA-256 hex of a normalized email — Gravatar's identifier. Uses WebCrypto so
7
+ * a browser can derive it locally and never send the raw address anywhere.
8
+ */
9
+ export declare function gravatarEmailHash(email: string | null | undefined): Promise<string | null>;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The one Gravatar URL shape Spacefast emits. Every surface — control-plane
3
+ * profiles, the Comments SDK, Slack notifications — derives the same address
4
+ * for the same person, so an avatar is recognizable wherever it appears and
5
+ * Cast can validate a guest avatar against a single pattern.
6
+ *
7
+ * `d=404` is deliberate: a person without a Gravatar gets no image at all, and
8
+ * the consumer renders its own initials/palette fallback instead of a stock
9
+ * silhouette. `s=160` covers the largest avatar we render, retina included.
10
+ */
11
+ const GRAVATAR_AVATAR_QUERY = "d=404&r=g&s=160";
12
+ /** Gravatar's canonical address form: trimmed and lowercased. */
13
+ export function normalizeGravatarEmail(email) {
14
+ const normalized = email?.trim().toLowerCase();
15
+ return normalized ? normalized : null;
16
+ }
17
+ /** The canonical avatar URL for an already-computed SHA-256 hash. */
18
+ export function gravatarUrlFromHash(hashHex) {
19
+ return `https://gravatar.com/avatar/${hashHex}?${GRAVATAR_AVATAR_QUERY}`;
20
+ }
21
+ /**
22
+ * SHA-256 hex of a normalized email — Gravatar's identifier. Uses WebCrypto so
23
+ * a browser can derive it locally and never send the raw address anywhere.
24
+ */
25
+ export async function gravatarEmailHash(email) {
26
+ const normalized = normalizeGravatarEmail(email);
27
+ if (!normalized || !globalThis.crypto?.subtle)
28
+ return null;
29
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(normalized));
30
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
31
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Stable, secret-safe key for one exact logical mutation attempt. Shared by
3
+ * every client surface: the same publish issued through the CLI and through
4
+ * MCP must land on one key or the API stops deduplicating the retry.
5
+ */
6
+ export declare function stableIdempotencyKey(prefix: string, parts: unknown): string;
@@ -0,0 +1,28 @@
1
+ import { createHash } from "node:crypto";
2
+ /**
3
+ * Key-sorted JSON that treats an absent key and an `undefined` value as the
4
+ * same thing. Deliberately *not* `canonicalJson`: a key the CLI omits and one
5
+ * the MCP server sets to `undefined` describe the same mutation, so they have
6
+ * to hash alike. Merging the two spellings would change every minted key.
7
+ */
8
+ function keyJson(value) {
9
+ if (Array.isArray(value)) {
10
+ return `[${value.map(keyJson).join(",")}]`;
11
+ }
12
+ if (value && typeof value === "object") {
13
+ return `{${Object.entries(value)
14
+ .filter(([, entry]) => entry !== undefined)
15
+ .toSorted(([left], [right]) => left.localeCompare(right))
16
+ .map(([key, entry]) => `${JSON.stringify(key)}:${keyJson(entry)}`)
17
+ .join(",")}}`;
18
+ }
19
+ return JSON.stringify(value ?? null);
20
+ }
21
+ /**
22
+ * Stable, secret-safe key for one exact logical mutation attempt. Shared by
23
+ * every client surface: the same publish issued through the CLI and through
24
+ * MCP must land on one key or the API stops deduplicating the retry.
25
+ */
26
+ export function stableIdempotencyKey(prefix, parts) {
27
+ return `${prefix}-${createHash("sha256").update(keyJson(parts)).digest("hex")}`;
28
+ }
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
2
  export declare const LOCAL_SPACE_STATE_DIR_NAME = ".spacefast";
3
- export declare const LEGACY_LOCAL_SPACE_STATE_DIR_NAME = ".stattic";
4
3
  export declare const LOCAL_SPACE_LINK_FILE_NAME = "space.json";
5
4
  export declare const LOCAL_SPACE_STATE_FILE_NAME = "state.json";
6
5
  /**
@@ -127,10 +126,7 @@ export declare function localSpaceStateDir(root: string): string;
127
126
  export declare function localSpaceStatePath(root: string): string;
128
127
  export declare function localSpaceLinkPath(root: string): string;
129
128
  export declare function readLocalSpaceLink(root: string): Promise<LocalSpaceLink | null>;
130
- /**
131
- * Like readLocalSpaceLink, but reports WHICH file matched — callers surface the
132
- * legacy `.stattic/space.json` path to users so migrations stay visible.
133
- */
129
+ /** Like readLocalSpaceLink, but reports the file path alongside the link. */
134
130
  export declare function readLocalSpaceLinkWithPath(root: string): Promise<{
135
131
  link: LocalSpaceLink;
136
132
  path: string;
@@ -5,7 +5,6 @@ import { z } from "zod";
5
5
  import { createOneShotReplayIdentity } from "./one-shot-replay.js";
6
6
  import { SecureLocalFileConflictError, secureReadFile, secureWriteFile, } from "./secure-local-file.js";
7
7
  export const LOCAL_SPACE_STATE_DIR_NAME = ".spacefast";
8
- export const LEGACY_LOCAL_SPACE_STATE_DIR_NAME = ".stattic";
9
8
  export const LOCAL_SPACE_LINK_FILE_NAME = "space.json";
10
9
  export const LOCAL_SPACE_STATE_FILE_NAME = "state.json";
11
10
  /**
@@ -99,9 +98,6 @@ export function localSpaceStatePath(root) {
99
98
  export function localSpaceLinkPath(root) {
100
99
  return path.join(localSpaceStateDir(root), LOCAL_SPACE_LINK_FILE_NAME);
101
100
  }
102
- function legacyLinkPath(root) {
103
- return path.join(root, LEGACY_LOCAL_SPACE_STATE_DIR_NAME, LOCAL_SPACE_LINK_FILE_NAME);
104
- }
105
101
  async function readJsonFileWithRaw(root, relativePath) {
106
102
  const raw = await secureReadFile({ root, relativePath, encoding: "utf8" }).catch((error) => {
107
103
  if (error.code === "ENOENT")
@@ -156,28 +152,17 @@ function linkSpaceId(link) {
156
152
  export async function readLocalSpaceLink(root) {
157
153
  return (await readLocalSpaceLinkWithPath(root))?.link ?? null;
158
154
  }
159
- /**
160
- * Like readLocalSpaceLink, but reports WHICH file matched — callers surface the
161
- * legacy `.stattic/space.json` path to users so migrations stay visible.
162
- */
155
+ /** Like readLocalSpaceLink, but reports the file path alongside the link. */
163
156
  export async function readLocalSpaceLinkWithPath(root) {
164
157
  const primary = parseLink(await readJsonFile(root, path.join(LOCAL_SPACE_STATE_DIR_NAME, LOCAL_SPACE_LINK_FILE_NAME)));
165
- if (primary) {
166
- return { link: primary, path: localSpaceLinkPath(root) };
167
- }
168
- const legacy = parseLink(await readJsonFile(root, path.join(LEGACY_LOCAL_SPACE_STATE_DIR_NAME, LOCAL_SPACE_LINK_FILE_NAME)));
169
- return legacy ? { link: legacy, path: legacyLinkPath(root) } : null;
158
+ return primary ? { link: primary, path: localSpaceLinkPath(root) } : null;
170
159
  }
171
160
  export async function readLocalSpaceStateAt(root) {
172
161
  const statePath = localSpaceStatePath(root);
173
162
  const primaryState = await readStateFile(root, path.join(LOCAL_SPACE_STATE_DIR_NAME, LOCAL_SPACE_STATE_FILE_NAME));
174
- const legacyState = primaryState
175
- ? undefined
176
- : await readStateFile(root, path.join(LEGACY_LOCAL_SPACE_STATE_DIR_NAME, LOCAL_SPACE_STATE_FILE_NAME));
177
- const state = primaryState?.state ?? legacyState?.state ?? null;
163
+ const state = primaryState?.state ?? null;
178
164
  const linkPath = localSpaceLinkPath(root);
179
- const link = parseLink(await readJsonFile(root, path.join(LOCAL_SPACE_STATE_DIR_NAME, LOCAL_SPACE_LINK_FILE_NAME))) ??
180
- parseLink(await readJsonFile(root, path.join(LEGACY_LOCAL_SPACE_STATE_DIR_NAME, LOCAL_SPACE_LINK_FILE_NAME)));
165
+ const link = parseLink(await readJsonFile(root, path.join(LOCAL_SPACE_STATE_DIR_NAME, LOCAL_SPACE_LINK_FILE_NAME)));
181
166
  if (!state && !link)
182
167
  return null;
183
168
  const committedSpaceId = linkSpaceId(link);
@@ -334,11 +319,7 @@ export async function mutateLocalSpaceState(root, mutate, options = {}) {
334
319
  /* eslint-disable no-await-in-loop -- compare-and-swap retries are intentionally serialized against the latest committed state. */
335
320
  for (let attempt = 1; attempt <= LOCAL_STATE_MUTATION_ATTEMPTS; attempt += 1) {
336
321
  try {
337
- const primaryState = await readJsonFileWithRaw(root, stateRelativePath);
338
- const legacyState = primaryState
339
- ? undefined
340
- : await readJsonFileWithRaw(root, path.join(LEGACY_LOCAL_SPACE_STATE_DIR_NAME, LOCAL_SPACE_STATE_FILE_NAME));
341
- const source = primaryState ?? legacyState;
322
+ const source = await readJsonFileWithRaw(root, stateRelativePath);
342
323
  const parsed = source ? localSpaceStateSchema.safeParse(source.value) : null;
343
324
  if (source && !parsed?.success) {
344
325
  throw new Error(`Local Spacefast state at ${localSpaceStatePath(root)} is invalid.`);
@@ -353,14 +334,14 @@ export async function mutateLocalSpaceState(root, mutate, options = {}) {
353
334
  schemaVersion: LOCAL_SPACE_STATE_SCHEMA_VERSION,
354
335
  });
355
336
  const serialized = `${JSON.stringify(payload, null, 2)}\n`;
356
- if (primaryState?.raw === serialized)
337
+ if (source?.raw === serialized)
357
338
  return payload;
358
339
  await writeJsonAtomic({
359
340
  root,
360
341
  relativePath: stateRelativePath,
361
342
  value: payload,
362
343
  mode: 0o600,
363
- expectedContent: primaryState?.raw ?? null,
344
+ expectedContent: source?.raw ?? null,
364
345
  ...(options.beforeCommit ? { beforeCommit: options.beforeCommit } : {}),
365
346
  ...(options.afterBackup ? { afterBackup: options.afterBackup } : {}),
366
347
  });