@stardeck-customer-apps/compose 0.14.1 → 0.16.0

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/dist/cli.js CHANGED
@@ -1145,16 +1145,16 @@ var require_range = __commonJS({
1145
1145
  };
1146
1146
  var replaceTilde = (comp, options) => {
1147
1147
  const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE];
1148
- const z10 = options.includePrerelease ? "-0" : "";
1148
+ const z11 = options.includePrerelease ? "-0" : "";
1149
1149
  return comp.replace(r, (_, M, m, p, pr) => {
1150
1150
  debug("tilde", comp, _, M, m, p, pr);
1151
1151
  let ret;
1152
1152
  if (isX(M)) {
1153
1153
  ret = "";
1154
1154
  } else if (isX(m)) {
1155
- ret = `>=${M}.0.0${z10} <${+M + 1}.0.0-0`;
1155
+ ret = `>=${M}.0.0${z11} <${+M + 1}.0.0-0`;
1156
1156
  } else if (isX(p)) {
1157
- ret = `>=${M}.${m}.0${z10} <${M}.${+m + 1}.0-0`;
1157
+ ret = `>=${M}.${m}.0${z11} <${M}.${+m + 1}.0-0`;
1158
1158
  } else if (pr) {
1159
1159
  debug("replaceTilde pr", pr);
1160
1160
  ret = `>=${M}.${m}.${p}-${pr} <${M}.${+m + 1}.0-0`;
@@ -1171,19 +1171,19 @@ var require_range = __commonJS({
1171
1171
  var replaceCaret = (comp, options) => {
1172
1172
  debug("caret", comp, options);
1173
1173
  const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET];
1174
- const z10 = options.includePrerelease ? "-0" : "";
1174
+ const z11 = options.includePrerelease ? "-0" : "";
1175
1175
  return comp.replace(r, (_, M, m, p, pr) => {
1176
1176
  debug("caret", comp, _, M, m, p, pr);
1177
1177
  let ret;
1178
1178
  if (isX(M)) {
1179
1179
  ret = "";
1180
1180
  } else if (isX(m)) {
1181
- ret = `>=${M}.0.0${z10} <${+M + 1}.0.0-0`;
1181
+ ret = `>=${M}.0.0${z11} <${+M + 1}.0.0-0`;
1182
1182
  } else if (isX(p)) {
1183
1183
  if (M === "0") {
1184
- ret = `>=${M}.${m}.0${z10} <${M}.${+m + 1}.0-0`;
1184
+ ret = `>=${M}.${m}.0${z11} <${M}.${+m + 1}.0-0`;
1185
1185
  } else {
1186
- ret = `>=${M}.${m}.0${z10} <${+M + 1}.0.0-0`;
1186
+ ret = `>=${M}.${m}.0${z11} <${+M + 1}.0.0-0`;
1187
1187
  }
1188
1188
  } else if (pr) {
1189
1189
  debug("replaceCaret pr", pr);
@@ -2021,8 +2021,13 @@ var require_semver2 = __commonJS({
2021
2021
  });
2022
2022
 
2023
2023
  // src/cli.ts
2024
+ var import_node_child_process3 = require("child_process");
2024
2025
  var import_node_util = require("util");
2025
2026
 
2027
+ // ../../packages/lib/src/server/module-rail/ownership-guard.ts
2028
+ var import_node_fs = __toESM(require("fs"));
2029
+ var import_node_path = __toESM(require("path"));
2030
+
2026
2031
  // ../../packages/module-contract/src/module-manifest.ts
2027
2032
  var import_zod = require("zod");
2028
2033
 
@@ -2053,6 +2058,13 @@ function validateSlug(slug) {
2053
2058
  var MODULES_DIR = "src/modules";
2054
2059
  var APP_PACKAGE_DIR = "apps/web";
2055
2060
  var MODULES_SANDBOX_DIR = `${APP_PACKAGE_DIR}/${MODULES_DIR}`;
2061
+ var MODULE_MANIFEST_FILENAME = "module.json";
2062
+ var MODULE_UI_SUBPATHS = [
2063
+ "components/",
2064
+ "routes/",
2065
+ "enhancements/*/components/",
2066
+ "enhancements/*/routes/"
2067
+ ];
2056
2068
  var KEBAB_SLUG_REGEX = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
2057
2069
  var SNAKE_TABLE_REGEX = /^[a-z][a-z0-9_]*$/;
2058
2070
  var ROUTE_GROUP_REGEX = /^\([a-z0-9-]+\)$/;
@@ -2066,23 +2078,23 @@ function isValidModuleName(value) {
2066
2078
  var projectSlugSchema = import_zod.z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
2067
2079
  var moduleKindSchema = import_zod.z.enum(["feature", "library", "contract"]);
2068
2080
  var endpointMethodSchema = import_zod.z.enum(ENDPOINT_METHODS);
2069
- function endpointPathValidationError(path5) {
2070
- if (!path5.startsWith("/api/")) {
2081
+ function endpointPathValidationError(path6) {
2082
+ if (!path6.startsWith("/api/")) {
2071
2083
  return 'must begin with "/api/"';
2072
2084
  }
2073
- if (path5.endsWith("/")) {
2085
+ if (path6.endsWith("/")) {
2074
2086
  return "must not end with a trailing slash";
2075
2087
  }
2076
- if (path5.includes("?") || path5.includes("#")) {
2088
+ if (path6.includes("?") || path6.includes("#")) {
2077
2089
  return "must not include query or hash";
2078
2090
  }
2079
- if (path5.includes("\\")) {
2091
+ if (path6.includes("\\")) {
2080
2092
  return "must not include backslashes";
2081
2093
  }
2082
- if (path5.includes("%")) {
2094
+ if (path6.includes("%")) {
2083
2095
  return "must not include percent-encoded segments";
2084
2096
  }
2085
- const parts = path5.split("/");
2097
+ const parts = path6.split("/");
2086
2098
  if (parts.length < 3 || parts[0] !== "" || parts[1] !== "api") {
2087
2099
  return 'must begin with "/api/"';
2088
2100
  }
@@ -2132,12 +2144,12 @@ function endpointPathValidationError(path5) {
2132
2144
  }
2133
2145
  return null;
2134
2146
  }
2135
- function normalizeEndpointPathForCollision(path5) {
2136
- const error = endpointPathValidationError(path5);
2147
+ function normalizeEndpointPathForCollision(path6) {
2148
+ const error = endpointPathValidationError(path6);
2137
2149
  if (error != null) {
2138
2150
  throw new Error(`invalid endpoint path: ${error}`);
2139
2151
  }
2140
- return path5.split("/").map((segment, index) => {
2152
+ return path6.split("/").map((segment, index) => {
2141
2153
  if (index < 2) {
2142
2154
  return segment;
2143
2155
  }
@@ -2150,8 +2162,8 @@ function normalizeEndpointPathForCollision(path5) {
2150
2162
  return segment;
2151
2163
  }).join("/");
2152
2164
  }
2153
- var endpointPathSchema = import_zod.z.string().superRefine((path5, ctx) => {
2154
- const error = endpointPathValidationError(path5);
2165
+ var endpointPathSchema = import_zod.z.string().superRefine((path6, ctx) => {
2166
+ const error = endpointPathValidationError(path6);
2155
2167
  if (error != null) {
2156
2168
  ctx.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: error });
2157
2169
  }
@@ -2266,7 +2278,18 @@ var modulePermissionClaimSchema = import_zod.z.object({
2266
2278
  * External Viewer never holds one. An organization that wants different access duplicates the
2267
2279
  * standard role and edits the copy.
2268
2280
  */
2269
- roles: import_zod.z.array(import_zod.z.enum(MODULE_PERMISSION_DEFAULT_ROLES)).optional()
2281
+ roles: import_zod.z.array(import_zod.z.enum(MODULE_PERMISSION_DEFAULT_ROLES)).optional(),
2282
+ /**
2283
+ * Keys this one takes over (a retired or renamed key). On the update that stops declaring
2284
+ * them, every custom role still holding one is granted this key before the old one goes,
2285
+ * so nobody loses access in the change. Standard roles follow `roles` as always.
2286
+ */
2287
+ replaces: import_zod.z.array(
2288
+ import_zod.z.string().regex(
2289
+ /^[a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9-]*$/,
2290
+ "must be a lowercase resource:action key"
2291
+ )
2292
+ ).max(20).optional()
2270
2293
  }).strict();
2271
2294
  var seedBundleSchema = import_zod.z.object({
2272
2295
  table: tableNameSchema,
@@ -2414,16 +2437,16 @@ var moduleManifestSchema = import_zod.z.object({
2414
2437
  }
2415
2438
  }
2416
2439
  const seenWorkflows = /* @__PURE__ */ new Map();
2417
- const noteWorkflow = (name, path5) => {
2440
+ const noteWorkflow = (name, path6) => {
2418
2441
  const existing = seenWorkflows.get(name);
2419
2442
  if (existing != null) {
2420
2443
  ctx.addIssue({
2421
2444
  code: import_zod.z.ZodIssueCode.custom,
2422
2445
  message: `duplicate workflow name "${name}" (also at ${existing})`,
2423
- path: path5
2446
+ path: path6
2424
2447
  });
2425
2448
  } else {
2426
- seenWorkflows.set(name, path5.join("."));
2449
+ seenWorkflows.set(name, path6.join("."));
2427
2450
  }
2428
2451
  };
2429
2452
  for (let i = 0; i < manifest.workflows.length; i += 1) {
@@ -2435,7 +2458,7 @@ var moduleManifestSchema = import_zod.z.object({
2435
2458
  }
2436
2459
  }
2437
2460
  const seenEndpointKeys = /* @__PURE__ */ new Map();
2438
- const noteEndpoint = (claim, path5) => {
2461
+ const noteEndpoint = (claim, path6) => {
2439
2462
  let key;
2440
2463
  try {
2441
2464
  key = normalizeEndpointPathForCollision(claim.path);
@@ -2447,10 +2470,10 @@ var moduleManifestSchema = import_zod.z.object({
2447
2470
  ctx.addIssue({
2448
2471
  code: import_zod.z.ZodIssueCode.custom,
2449
2472
  message: `duplicate normalized endpoint path "${key}" (also at ${existing})`,
2450
- path: path5
2473
+ path: path6
2451
2474
  });
2452
2475
  } else {
2453
- seenEndpointKeys.set(key, path5.join("."));
2476
+ seenEndpointKeys.set(key, path6.join("."));
2454
2477
  }
2455
2478
  };
2456
2479
  for (let i = 0; i < manifest.endpoints.length; i += 1) {
@@ -2551,8 +2574,8 @@ var moduleManifestSchema = import_zod.z.object({
2551
2574
  }).transform(({ version: _legacyVersion, ...manifest }) => manifest);
2552
2575
  function formatZodErrors(error) {
2553
2576
  return error.issues.map((issue) => {
2554
- const path5 = issue.path.length > 0 ? issue.path.join(".") : "root";
2555
- return `${path5}: ${issue.message}`;
2577
+ const path6 = issue.path.length > 0 ? issue.path.join(".") : "root";
2578
+ return `${path6}: ${issue.message}`;
2556
2579
  });
2557
2580
  }
2558
2581
  function parseModuleManifest(raw) {
@@ -2952,16 +2975,16 @@ var collectedE2eFactsSchema = import_zod3.z.object({
2952
2975
  function validateRouteExportEvidenceCongruence(input) {
2953
2976
  const entrySet = new Set(input.routeEntries);
2954
2977
  const exportPaths = input.routePageExports.map((e) => e.nextRelativePath);
2955
- for (const path5 of exportPaths) {
2956
- if (!entrySet.has(path5)) {
2957
- input.onIssue(`routePageExports path "${path5}" is not listed in routeEntries`);
2978
+ for (const path6 of exportPaths) {
2979
+ if (!entrySet.has(path6)) {
2980
+ input.onIssue(`routePageExports path "${path6}" is not listed in routeEntries`);
2958
2981
  }
2959
2982
  }
2960
2983
  if (input.routeCollectionErrors.length === 0) {
2961
2984
  const exportSet = new Set(exportPaths);
2962
- for (const path5 of input.routeEntries) {
2963
- if (!exportSet.has(path5)) {
2964
- input.onIssue(`routeEntries path "${path5}" is missing from routePageExports`);
2985
+ for (const path6 of input.routeEntries) {
2986
+ if (!exportSet.has(path6)) {
2987
+ input.onIssue(`routeEntries path "${path6}" is missing from routePageExports`);
2965
2988
  }
2966
2989
  }
2967
2990
  }
@@ -3906,6 +3929,50 @@ function analyzeClientSafeEntryImports(sourceText, fileName) {
3906
3929
  return analyzeClientSafeEntryImportsWithTs(import_typescript3.default, sourceText, fileName);
3907
3930
  }
3908
3931
 
3932
+ // ../../packages/lib/src/shared/ownership-record.ts
3933
+ var import_zod6 = require("zod");
3934
+ var OWNERSHIP_RECORD_PATH = "stardeck.ownership.json";
3935
+ var OWNERSHIP_RECORD_VERSION = 1;
3936
+ var moduleList = import_zod6.z.array(import_zod6.z.string().refine(isValidModuleName, "not a Module name"));
3937
+ var ownershipRecordSchema = import_zod6.z.object({
3938
+ version: import_zod6.z.literal(OWNERSHIP_RECORD_VERSION),
3939
+ /** False once the app stopped all Stardeck updates: then every file is the app's. */
3940
+ stardeckUpdates: import_zod6.z.boolean(),
3941
+ /** projects.ejected_modules: updates stopped for these Modules, so their folders are the app's. */
3942
+ updatesStoppedModules: moduleList,
3943
+ /** projects.ui_ejected_modules: Customize UI, so their screens are the app's. */
3944
+ customizedUiModules: moduleList,
3945
+ /** projects.external_modules: installed from another app, so the app owns the copy. */
3946
+ externalModules: moduleList,
3947
+ /** projects.removed_modules names: uninstalled on purpose. */
3948
+ uninstalledModules: moduleList
3949
+ });
3950
+ function parseOwnershipRecord(content) {
3951
+ let json;
3952
+ try {
3953
+ json = JSON.parse(content);
3954
+ } catch (error) {
3955
+ return { ok: false, error: `${OWNERSHIP_RECORD_PATH} is not valid JSON: ${String(error)}` };
3956
+ }
3957
+ const parsed = ownershipRecordSchema.safeParse(json);
3958
+ if (!parsed.success) {
3959
+ return {
3960
+ ok: false,
3961
+ error: `${OWNERSHIP_RECORD_PATH} is not a record Stardeck wrote: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ")}`
3962
+ };
3963
+ }
3964
+ return { ok: true, record: parsed.data };
3965
+ }
3966
+ var BLUEPRINT_RELEASE_COMMITTER_EMAIL = "ci@stardeck.ai";
3967
+ var BLUEPRINT_RELEASE_SUBJECT_PREFIX = "publish: ";
3968
+ var STARDECK_OWNED_ALTERNATIVES = [
3969
+ "If the Module has a setting for this, change the setting.",
3970
+ "If a slot contribution can do it, write it in app code outside the Module folder (`apps/web/src/lib/modules/app-extensions.ts` registers it).",
3971
+ "To rework a Module's screens, the app owner can choose **Customize UI** in the dashboard's Modules tab (run **Sync** there first if it is refused). The app then owns that Module's `components/` and `routes/`, and its logic keeps updating.",
3972
+ "If the Module's logic is wrong for every app, report it to Stardeck so every app gets the fix.",
3973
+ "If the app cannot wait, the app owner can stop Stardeck updates for this Module, or for the whole app, in the Modules tab. Both are permanent."
3974
+ ];
3975
+
3909
3976
  // ../../packages/lib/src/shared/platform-owned-paths.ts
3910
3977
  var APP_WEB_PREFIX = "apps/web/";
3911
3978
  var BLUEPRINT_LINT_MARKER = `${APP_WEB_PREFIX}eslint-rules/module-boundaries.mjs`;
@@ -3919,20 +3986,217 @@ var MODULE_INIT_SERVER_GEN_PATH = `${APP_WEB_PREFIX}src/module-init.server.gen.t
3919
3986
  var MODULE_SERVER_GEN_PATH = `${APP_WEB_PREFIX}src/module-server.gen.ts`;
3920
3987
  var MODULES_GEN_PATH = `${APP_WEB_PREFIX}src/modules.gen.ts`;
3921
3988
  var APP_EXTENSIONS_PATH = `${APP_WEB_PREFIX}src/lib/modules/app-extensions.ts`;
3989
+ var APP_OWNED_PATHS = [APP_EXTENSIONS_PATH];
3990
+ function platformOwnedSet(input) {
3991
+ const externalModules = input.externalModules ?? [];
3992
+ const removedModules = input.removedModules ?? [];
3993
+ const moduleNames = [
3994
+ ...input.moduleNames,
3995
+ ...input.ejectedModules,
3996
+ ...input.uiEjectedModules,
3997
+ ...externalModules,
3998
+ ...removedModules
3999
+ ];
4000
+ for (const moduleName of moduleNames) {
4001
+ if (!isValidModuleName(moduleName)) {
4002
+ throw new Error(`Invalid Module name: ${moduleName}`);
4003
+ }
4004
+ }
4005
+ const appOwned = /* @__PURE__ */ new Set([...input.ejectedModules, ...externalModules]);
4006
+ const activeModules = input.moduleNames.filter((name) => !appOwned.has(name));
4007
+ const uiEjectedModules = new Set(input.uiEjectedModules);
4008
+ return {
4009
+ prefixes: [
4010
+ ...activeModules.map((name) => `${APP_WEB_PREFIX}src/modules/${name}/`),
4011
+ `${APP_WEB_PREFIX}src/lib/modules/`
4012
+ ].sort(),
4013
+ // Build outputs, not source: compose regenerates them from Module source on
4014
+ // every dev/build/lint/typecheck/test run and git ignores them, so there is
4015
+ // nothing for the write refusal or the Blueprint updater to protect.
4016
+ files: [],
4017
+ // Detach dominates: a detached Module has no prefix, so it needs no carve-out.
4018
+ carveOuts: activeModules.filter((name) => uiEjectedModules.has(name)).flatMap(
4019
+ (name) => MODULE_UI_SUBPATHS.map((subpath) => `${APP_WEB_PREFIX}src/modules/${name}/${subpath}`)
4020
+ ).sort(),
4021
+ externalModules: [...externalModules].sort(),
4022
+ removedModules: [...removedModules].sort()
4023
+ };
4024
+ }
4025
+ function appOwnedExcludePathspecs(set) {
4026
+ return [
4027
+ ...APP_OWNED_PATHS.map((file) => `:(exclude)${file}`),
4028
+ ...set.carveOuts.map((carveOut) => `:(glob,exclude)${carveOut}**`)
4029
+ ];
4030
+ }
4031
+ function platformOwnedDiffPathspecs(set) {
4032
+ return [...set.prefixes, ...set.files, ...appOwnedExcludePathspecs(set)];
4033
+ }
4034
+
4035
+ // ../../packages/lib/src/server/module-rail/ownership-guard.ts
4036
+ var STRICT_RECORD = {
4037
+ version: 1,
4038
+ stardeckUpdates: true,
4039
+ updatesStoppedModules: [],
4040
+ customizedUiModules: [],
4041
+ externalModules: [],
4042
+ uninstalledModules: []
4043
+ };
4044
+ var MANIFEST_PATH = new RegExp(
4045
+ `^${MODULES_SANDBOX_DIR.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/([^/]+)/${MODULE_MANIFEST_FILENAME.replace(".", "\\.")}$`
4046
+ );
4047
+ function realpath(dir) {
4048
+ try {
4049
+ return import_node_fs.default.realpathSync(dir);
4050
+ } catch {
4051
+ return import_node_path.default.resolve(dir);
4052
+ }
4053
+ }
4054
+ function zList(stdout) {
4055
+ return stdout.split("\0").filter(Boolean);
4056
+ }
4057
+ async function findRelease(git) {
4058
+ const found = await git([
4059
+ "rev-list",
4060
+ "-n1",
4061
+ "--topo-order",
4062
+ `--committer=${BLUEPRINT_RELEASE_COMMITTER_EMAIL.replace(/\./g, "\\.")}`,
4063
+ `--grep=^${BLUEPRINT_RELEASE_SUBJECT_PREFIX}`,
4064
+ "HEAD"
4065
+ ]);
4066
+ const sha = found.status === 0 ? found.stdout.trim() : "";
4067
+ return sha || null;
4068
+ }
4069
+ async function noReleaseReason(git) {
4070
+ const shallow = await git(["rev-parse", "--is-shallow-repository"]);
4071
+ if (shallow.status === 0 && shallow.stdout.trim() === "true") {
4072
+ return {
4073
+ status: "skipped",
4074
+ warning: true,
4075
+ reason: "this is a shallow clone, so Stardeck's version of the Module files is not in its history. Run `git fetch --unshallow` and lint again to check that no Stardeck-owned file changed."
4076
+ };
4077
+ }
4078
+ return {
4079
+ status: "skipped",
4080
+ warning: false,
4081
+ reason: "no Stardeck release in this repo's history, so it holds no Stardeck-owned files"
4082
+ };
4083
+ }
4084
+ function readRecord(repoRoot) {
4085
+ let content;
4086
+ try {
4087
+ content = import_node_fs.default.readFileSync(import_node_path.default.join(repoRoot, OWNERSHIP_RECORD_PATH), "utf8");
4088
+ } catch (error) {
4089
+ if (error.code === "ENOENT") {
4090
+ return { ok: true, record: STRICT_RECORD, found: false };
4091
+ }
4092
+ throw error;
4093
+ }
4094
+ const parsed = parseOwnershipRecord(content);
4095
+ return parsed.ok ? { ok: true, record: parsed.record, found: true } : parsed;
4096
+ }
4097
+ async function releaseModules(git, release, record) {
4098
+ const listed = await git([
4099
+ "ls-tree",
4100
+ "-r",
4101
+ "-z",
4102
+ "--name-only",
4103
+ release,
4104
+ "--",
4105
+ MODULES_SANDBOX_DIR
4106
+ ]);
4107
+ if (listed.status !== 0) throw new Error(`git ls-tree ${release} failed`);
4108
+ const uninstalled = new Set(record.uninstalledModules);
4109
+ return zList(listed.stdout).map((file) => MANIFEST_PATH.exec(file)?.[1]).filter((name) => !!name && !uninstalled.has(name));
4110
+ }
4111
+ async function checkStardeckOwnedFiles(input) {
4112
+ const { git, repoRoot } = input;
4113
+ const top = await git(["rev-parse", "--show-toplevel"]);
4114
+ if (top.status !== 0) {
4115
+ return { status: "skipped", warning: false, reason: "not a git checkout" };
4116
+ }
4117
+ if (realpath(top.stdout.trim()) !== realpath(repoRoot)) {
4118
+ return {
4119
+ status: "skipped",
4120
+ warning: false,
4121
+ reason: `${repoRoot} is not the root of its git repo, so it is not an app's own repo`
4122
+ };
4123
+ }
4124
+ const release = await findRelease(git);
4125
+ if (!release) return noReleaseReason(git);
4126
+ const read = readRecord(repoRoot);
4127
+ if (!read.ok) return { status: "invalid-record", error: read.error };
4128
+ const { record } = read;
4129
+ if (!record.stardeckUpdates) {
4130
+ return {
4131
+ status: "skipped",
4132
+ warning: false,
4133
+ reason: "this app stopped all Stardeck updates, so every file is the app's"
4134
+ };
4135
+ }
4136
+ const moduleNames = await releaseModules(git, release, record);
4137
+ const set = platformOwnedSet({
4138
+ moduleNames,
4139
+ ejectedModules: record.updatesStoppedModules,
4140
+ uiEjectedModules: record.customizedUiModules,
4141
+ externalModules: record.externalModules,
4142
+ removedModules: record.uninstalledModules
4143
+ });
4144
+ const pathspecs = [
4145
+ ...platformOwnedDiffPathspecs(set),
4146
+ ...record.uninstalledModules.map(
4147
+ (name) => `:(glob,exclude)${MODULES_SANDBOX_DIR}/*/enhancements/${name}/**`
4148
+ )
4149
+ ];
4150
+ const [changed, added] = await Promise.all([
4151
+ git([
4152
+ "diff",
4153
+ "--name-only",
4154
+ "-z",
4155
+ "--no-renames",
4156
+ "--no-ext-diff",
4157
+ release,
4158
+ "--",
4159
+ ...pathspecs
4160
+ ]),
4161
+ git(["ls-files", "-z", "--others", "--exclude-standard", "--", ...pathspecs])
4162
+ ]);
4163
+ if (changed.status !== 0 || added.status !== 0) {
4164
+ throw new Error(`git could not compare the checkout with Stardeck's version ${release}`);
4165
+ }
4166
+ const paths = [.../* @__PURE__ */ new Set([...zList(changed.stdout), ...zList(added.stdout)])].sort();
4167
+ console.log(
4168
+ `[OwnershipGuard] release=${release.slice(0, 12)} record=${read.found ? "found" : "missing"} modules=${moduleNames.length} carveOuts=${set.carveOuts.length} changed=${paths.length}`
4169
+ );
4170
+ return paths.length > 0 ? { status: "changed", release, paths } : { status: "clean", release };
4171
+ }
4172
+ function formatOwnershipGuardFailure(result) {
4173
+ if (result.status === "invalid-record") {
4174
+ return `${result.error}
4175
+ Restore it with \`git checkout origin/HEAD -- ${OWNERSHIP_RECORD_PATH}\`. Stardeck writes this file; ownership changes in the dashboard's Modules tab.`;
4176
+ }
4177
+ const count = `${result.paths.length} Stardeck-owned ${result.paths.length === 1 ? "file" : "files"}`;
4178
+ return [
4179
+ `${count} changed from Stardeck's version (${result.release.slice(0, 12)}). The next Stardeck update replaces them, so these changes would be lost:`,
4180
+ ...result.paths.map((file) => ` - ${file}`),
4181
+ "",
4182
+ `Undo them (\`git checkout ${result.release.slice(0, 12)} -- <path>\`, or delete a new file) and do one of these instead:`,
4183
+ ...STARDECK_OWNED_ALTERNATIVES.map((line) => ` - ${line.replace(/\*\*/g, "")}`)
4184
+ ].join("\n");
4185
+ }
3922
4186
 
3923
4187
  // src/cli.ts
3924
4188
  var import_typescript7 = __toESM(require("typescript"));
3925
- var import_node_path4 = __toESM(require("path"));
4189
+ var import_node_path5 = __toESM(require("path"));
3926
4190
 
3927
4191
  // src/index.ts
3928
4192
  var import_node_child_process2 = require("child_process");
3929
- var import_node_fs3 = __toESM(require("fs"));
3930
- var import_node_path3 = __toESM(require("path"));
4193
+ var import_node_fs4 = __toESM(require("fs"));
4194
+ var import_node_path4 = __toESM(require("path"));
3931
4195
 
3932
4196
  // ../../packages/lib/src/server/module-rail/local-fs-sandbox.ts
3933
4197
  var import_node_child_process = require("child_process");
3934
- var import_node_fs = __toESM(require("fs"));
3935
- var import_node_path = __toESM(require("path"));
4198
+ var import_node_fs2 = __toESM(require("fs"));
4199
+ var import_node_path2 = __toESM(require("path"));
3936
4200
  function localFsSandbox(root) {
3937
4201
  return {
3938
4202
  async exec(command, options) {
@@ -3956,13 +4220,13 @@ ${result.error}`);
3956
4220
  },
3957
4221
  async fileExists(relative) {
3958
4222
  try {
3959
- return import_node_fs.default.statSync(import_node_path.default.join(root, relative)).isFile();
4223
+ return import_node_fs2.default.statSync(import_node_path2.default.join(root, relative)).isFile();
3960
4224
  } catch {
3961
4225
  return false;
3962
4226
  }
3963
4227
  },
3964
4228
  async readFile(relative) {
3965
- return import_node_fs.default.readFileSync(import_node_path.default.join(root, relative), "utf8");
4229
+ return import_node_fs2.default.readFileSync(import_node_path2.default.join(root, relative), "utf8");
3966
4230
  },
3967
4231
  async fetchRemoteRef() {
3968
4232
  throw new Error("[ModuleRail] local filesystem sandbox cannot reach the network");
@@ -3995,28 +4259,28 @@ function shellQuote(value) {
3995
4259
  }
3996
4260
 
3997
4261
  // ../../packages/lib/src/shared/compose-inputs.ts
3998
- var import_zod6 = require("zod");
4262
+ var import_zod7 = require("zod");
3999
4263
  var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
4000
- var composeInputsSchema = import_zod6.z.strictObject({
4001
- connectedStores: import_zod6.z.array(
4002
- import_zod6.z.strictObject({
4003
- storeId: import_zod6.z.string().min(1),
4004
- slug: import_zod6.z.string().min(1),
4264
+ var composeInputsSchema = import_zod7.z.strictObject({
4265
+ connectedStores: import_zod7.z.array(
4266
+ import_zod7.z.strictObject({
4267
+ storeId: import_zod7.z.string().min(1),
4268
+ slug: import_zod7.z.string().min(1),
4005
4269
  // A row with an empty binding key must not fail a deploy: the rail
4006
4270
  // treats falsy as "no binding key", so normalise rather than reject.
4007
- bindingKey: import_zod6.z.string().nullish().transform((value) => value || null),
4008
- accessLevel: import_zod6.z.enum(["read", "write", "admin"]),
4271
+ bindingKey: import_zod7.z.string().nullish().transform((value) => value || null),
4272
+ accessLevel: import_zod7.z.enum(["read", "write", "admin"]),
4009
4273
  // Defaulted so a file written before the platform emitted store types
4010
4274
  // (database rows only) still parses. The platform starts writing
4011
4275
  // storage rows, with `type` set, once forks run a compose that reads it.
4012
- type: import_zod6.z.enum(["database", "storage"]).default("database")
4276
+ type: import_zod7.z.enum(["database", "storage"]).default("database")
4013
4277
  })
4014
4278
  ).default([]),
4015
- installs: import_zod6.z.record(
4016
- import_zod6.z.string(),
4017
- import_zod6.z.strictObject({
4018
- installedVersion: import_zod6.z.string().min(1),
4019
- datastoreBindings: import_zod6.z.record(import_zod6.z.string(), import_zod6.z.string()).nullish()
4279
+ installs: import_zod7.z.record(
4280
+ import_zod7.z.string(),
4281
+ import_zod7.z.strictObject({
4282
+ installedVersion: import_zod7.z.string().min(1),
4283
+ datastoreBindings: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.string()).nullish()
4020
4284
  })
4021
4285
  ).default({})
4022
4286
  });
@@ -4176,13 +4440,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
4176
4440
  function createMessageTree() {
4177
4441
  return /* @__PURE__ */ Object.create(null);
4178
4442
  }
4179
- function assertMessageTree(value, path5) {
4443
+ function assertMessageTree(value, path6) {
4180
4444
  if (typeof value === "string") return;
4181
4445
  if (value === null || Array.isArray(value) || typeof value !== "object") {
4182
- throw new Error(`Invalid message at ${path5}: expected string or nested object`);
4446
+ throw new Error(`Invalid message at ${path6}: expected string or nested object`);
4183
4447
  }
4184
4448
  for (const [key, child] of Object.entries(value)) {
4185
- assertMessageTree(child, `${path5}.${key}`);
4449
+ assertMessageTree(child, `${path6}.${key}`);
4186
4450
  }
4187
4451
  }
4188
4452
  function cloneTree(value) {
@@ -4192,17 +4456,17 @@ function cloneTree(value) {
4192
4456
  }
4193
4457
  return clone;
4194
4458
  }
4195
- function collectLeafPaths(value, path5, out) {
4459
+ function collectLeafPaths(value, path6, out) {
4196
4460
  if (typeof value === "string") {
4197
- out.push(path5);
4461
+ out.push(path6);
4198
4462
  return;
4199
4463
  }
4200
- assertMessageTree(value, path5);
4201
- for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path5}.${key}`, out);
4464
+ assertMessageTree(value, path6);
4465
+ for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path6}.${key}`, out);
4202
4466
  }
4203
- function overrideInto(target, source, owner, path5, missing) {
4467
+ function overrideInto(target, source, owner, path6, missing) {
4204
4468
  for (const [key, value] of Object.entries(source)) {
4205
- const nextPath = path5 ? `${path5}.${key}` : key;
4469
+ const nextPath = path6 ? `${path6}.${key}` : key;
4206
4470
  if (!Object.prototype.hasOwnProperty.call(target, key)) {
4207
4471
  collectLeafPaths(value, nextPath, missing);
4208
4472
  continue;
@@ -4229,9 +4493,9 @@ function overrideInto(target, source, owner, path5, missing) {
4229
4493
  function composeLocaleMessagesForPlan(layers) {
4230
4494
  const result = createMessageTree();
4231
4495
  const leafOwners = /* @__PURE__ */ new Map();
4232
- function mergeInto(target, source, owner, path5) {
4496
+ function mergeInto(target, source, owner, path6) {
4233
4497
  for (const [key, value] of Object.entries(source)) {
4234
- const nextPath = path5 ? `${path5}.${key}` : key;
4498
+ const nextPath = path6 ? `${path6}.${key}` : key;
4235
4499
  const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
4236
4500
  const existing = hasExisting ? target[key] : void 0;
4237
4501
  if (typeof value === "string") {
@@ -4792,8 +5056,8 @@ var METHOD_ORDER = {
4792
5056
  function compareNames2(a, b) {
4793
5057
  return a < b ? -1 : a > b ? 1 : 0;
4794
5058
  }
4795
- function endpointPairKey(method, path5) {
4796
- return `${method} ${path5}`;
5059
+ function endpointPairKey(method, path6) {
5060
+ return `${method} ${path6}`;
4797
5061
  }
4798
5062
  function apiPathToRouteFile(apiPath) {
4799
5063
  if (!apiPath.startsWith("/api/")) {
@@ -4807,11 +5071,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
4807
5071
  }
4808
5072
  var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
4809
5073
  var API_ROUTE_FILE_SUFFIXES = ["/route.ts", "/route.tsx", "/route.jsx", "/route.js"];
4810
- function appRouteFileToNextRelativePath(path5) {
4811
- const pagePath = appPageFileToNextRelativePath(path5, APP_PACKAGE_DIR);
5074
+ function appRouteFileToNextRelativePath(path6) {
5075
+ const pagePath = appPageFileToNextRelativePath(path6, APP_PACKAGE_DIR);
4812
5076
  if (pagePath != null) return pagePath;
4813
- if (!path5.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
4814
- const relative = path5.slice(APP_ROUTE_FILE_PREFIX.length);
5077
+ if (!path6.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
5078
+ const relative = path6.slice(APP_ROUTE_FILE_PREFIX.length);
4815
5079
  if (!relative.startsWith("api/")) return null;
4816
5080
  const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
4817
5081
  if (!suffix) return null;
@@ -4906,9 +5170,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
4906
5170
  const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
4907
5171
  return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
4908
5172
  }
4909
- function parseLegacyApiAdapter(path5, source) {
4910
- const physicalPath = appRouteFileToNextRelativePath(path5);
4911
- if (expectedMarkerKindForPath(path5) !== "endpoint" || physicalPath == null) return null;
5173
+ function parseLegacyApiAdapter(path6, source) {
5174
+ const physicalPath = appRouteFileToNextRelativePath(path6);
5175
+ if (expectedMarkerKindForPath(path6) !== "endpoint" || physicalPath == null) return null;
4912
5176
  const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
4913
5177
  const match = firstLine.match(
4914
5178
  new RegExp(
@@ -4918,7 +5182,7 @@ function parseLegacyApiAdapter(path5, source) {
4918
5182
  if (!match || match[2] !== physicalPath) return null;
4919
5183
  const owner = match[1];
4920
5184
  if (!isValidModuleName(owner)) return null;
4921
- const file = import_typescript4.default.createSourceFile(path5, source, import_typescript4.default.ScriptTarget.Latest, false, import_typescript4.default.ScriptKind.TS);
5185
+ const file = import_typescript4.default.createSourceFile(path6, source, import_typescript4.default.ScriptTarget.Latest, false, import_typescript4.default.ScriptKind.TS);
4922
5186
  if (file.parseDiagnostics.length > 0) {
4923
5187
  return null;
4924
5188
  }
@@ -4960,9 +5224,9 @@ function parseLegacyApiAdapter(path5, source) {
4960
5224
  }
4961
5225
  return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
4962
5226
  }
4963
- function parseLegacyRouteAdapter(path5, source) {
4964
- const physicalPath = appRouteFileToNextRelativePath(path5);
4965
- if (expectedMarkerKindForPath(path5) !== "route" || physicalPath == null) return null;
5227
+ function parseLegacyRouteAdapter(path6, source) {
5228
+ const physicalPath = appRouteFileToNextRelativePath(path6);
5229
+ if (expectedMarkerKindForPath(path6) !== "route" || physicalPath == null) return null;
4966
5230
  const normalized = source.replace(/\r\n/g, "\n");
4967
5231
  const match = normalized.match(
4968
5232
  new RegExp(
@@ -4974,30 +5238,30 @@ function parseLegacyRouteAdapter(path5, source) {
4974
5238
  const nextRelativePath = match[2];
4975
5239
  return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
4976
5240
  }
4977
- function legacyRouteAdapterMatchesStub(path5, source, stub) {
4978
- const legacy = parseLegacyRouteAdapter(path5, source);
5241
+ function legacyRouteAdapterMatchesStub(path6, source, stub) {
5242
+ const legacy = parseLegacyRouteAdapter(path6, source);
4979
5243
  return legacy != null && legacy.owner === stub.owner;
4980
5244
  }
4981
5245
  var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
4982
5246
  var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
4983
5247
  var PAGE_FILE_SUFFIXES = ["/page.tsx", "/page.ts", "/page.jsx", "/page.js"];
4984
- function expectedMarkerKindForPath(path5) {
4985
- if (path5 === MODULES_GEN_PATH) return "registry";
4986
- if (path5 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
4987
- if (path5 === MODULE_DATASTORES_GEN_PATH) return "datastores";
4988
- if (path5 === MODULE_I18N_GEN_PATH) return "i18n";
4989
- if (path5 === MODULE_INIT_SERVER_GEN_PATH) return "init";
4990
- if (path5 === MODULE_SERVER_GEN_PATH) return "server";
4991
- if (path5.startsWith(API_ROUTE_DIR_PREFIX) && path5.endsWith("/route.ts")) return "endpoint";
4992
- if (path5.startsWith(APP_DIR_PREFIX) && !path5.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path5.endsWith(suffix))) {
5248
+ function expectedMarkerKindForPath(path6) {
5249
+ if (path6 === MODULES_GEN_PATH) return "registry";
5250
+ if (path6 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
5251
+ if (path6 === MODULE_DATASTORES_GEN_PATH) return "datastores";
5252
+ if (path6 === MODULE_I18N_GEN_PATH) return "i18n";
5253
+ if (path6 === MODULE_INIT_SERVER_GEN_PATH) return "init";
5254
+ if (path6 === MODULE_SERVER_GEN_PATH) return "server";
5255
+ if (path6.startsWith(API_ROUTE_DIR_PREFIX) && path6.endsWith("/route.ts")) return "endpoint";
5256
+ if (path6.startsWith(APP_DIR_PREFIX) && !path6.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path6.endsWith(suffix))) {
4993
5257
  return "route";
4994
5258
  }
4995
5259
  return null;
4996
5260
  }
4997
- function markerCongruentWithPath(path5, marker) {
4998
- if (expectedMarkerKindForPath(path5) !== marker.kind) return false;
5261
+ function markerCongruentWithPath(path6, marker) {
5262
+ if (expectedMarkerKindForPath(path6) !== marker.kind) return false;
4999
5263
  if (marker.kind === "route") {
5000
- const derived = appPageFileToNextRelativePath(path5, APP_PACKAGE_DIR);
5264
+ const derived = appPageFileToNextRelativePath(path6, APP_PACKAGE_DIR);
5001
5265
  return derived != null && derived === marker.nextRelativePath;
5002
5266
  }
5003
5267
  return true;
@@ -5273,8 +5537,8 @@ function buildGeneratedModuleRegistry(input) {
5273
5537
  function activeEnhancementPeers(registryModule) {
5274
5538
  return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
5275
5539
  }
5276
- function endpointBucketKey(path5, owner, scope) {
5277
- return `${path5}\0${owner}\0${scopeLabel(scope)}`;
5540
+ function endpointBucketKey(path6, owner, scope) {
5541
+ return `${path6}\0${owner}\0${scopeLabel(scope)}`;
5278
5542
  }
5279
5543
  function planRouteStubs(input) {
5280
5544
  const endpointBuckets = /* @__PURE__ */ new Map();
@@ -5398,7 +5662,7 @@ function planRouteStubs(input) {
5398
5662
  for (const entry of entries) {
5399
5663
  const nextRelativePath = entry.nextRelativePath;
5400
5664
  const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
5401
- const path5 = routeEntryToAppPageFile(nextRelativePath);
5665
+ const path6 = routeEntryToAppPageFile(nextRelativePath);
5402
5666
  const existing = routeDestinationOwners.get(destination);
5403
5667
  if (existing) {
5404
5668
  throw new Error(
@@ -5408,13 +5672,13 @@ function planRouteStubs(input) {
5408
5672
  routeDestinationOwners.set(destination, {
5409
5673
  owner: regMod.name,
5410
5674
  nextRelativePath,
5411
- path: path5
5675
+ path: path6
5412
5676
  });
5413
5677
  const importModule = routeEntryImport(regMod.name, nextRelativePath);
5414
5678
  const analyzed = analyzeRouteEntryPath(nextRelativePath);
5415
5679
  const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
5416
5680
  routeStubs.push({
5417
- path: path5,
5681
+ path: path6,
5418
5682
  owner: regMod.name,
5419
5683
  nextRelativePath,
5420
5684
  importModule,
@@ -5475,16 +5739,16 @@ function planCompositionArtifacts(input) {
5475
5739
  ...stubs.routeStubs.map((stub) => stub.path)
5476
5740
  ]);
5477
5741
  const existingGeneratedPathSet = new Set(input.existingGeneratedPaths);
5478
- const isProvenStaleDeletionCandidate = (path5, content) => {
5479
- if (path5 === MODULES_GEN_PATH) return false;
5480
- if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path5)) return false;
5481
- if (desiredPaths.has(path5)) return false;
5482
- if (!existingGeneratedPathSet.has(path5)) return false;
5483
- const legacy = parseLegacyApiAdapter(path5, content);
5742
+ const isProvenStaleDeletionCandidate = (path6, content) => {
5743
+ if (path6 === MODULES_GEN_PATH) return false;
5744
+ if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path6)) return false;
5745
+ if (desiredPaths.has(path6)) return false;
5746
+ if (!existingGeneratedPathSet.has(path6)) return false;
5747
+ const legacy = parseLegacyApiAdapter(path6, content);
5484
5748
  if (legacy) return true;
5485
- if (parseLegacyRouteAdapter(path5, content)) return true;
5749
+ if (parseLegacyRouteAdapter(path6, content)) return true;
5486
5750
  const marker = parseCompositionArtifactMarker(content);
5487
- return marker != null && markerCongruentWithPath(path5, marker);
5751
+ return marker != null && markerCongruentWithPath(path6, marker);
5488
5752
  };
5489
5753
  const plannedByDestination = /* @__PURE__ */ new Map();
5490
5754
  for (const stub of stubs.routeStubs) {
@@ -5638,13 +5902,13 @@ function planCompositionArtifacts(input) {
5638
5902
  for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
5639
5903
  const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
5640
5904
  const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
5641
- for (const [path5] of desired) {
5642
- const existing = input.existingFiles[path5];
5905
+ for (const [path6] of desired) {
5906
+ const existing = input.existingFiles[path6];
5643
5907
  if (existing == null) continue;
5644
5908
  let marker = parseCompositionArtifactMarker(existing);
5645
- const plannedEndpoint = endpointStubByPath.get(path5);
5646
- const plannedRoute = routeStubByPath.get(path5);
5647
- const legacy = parseLegacyApiAdapter(path5, existing);
5909
+ const plannedEndpoint = endpointStubByPath.get(path6);
5910
+ const plannedRoute = routeStubByPath.get(path6);
5911
+ const legacy = parseLegacyApiAdapter(path6, existing);
5648
5912
  if (legacy && plannedEndpoint) {
5649
5913
  marker = {
5650
5914
  kind: "endpoint",
@@ -5652,64 +5916,64 @@ function planCompositionArtifacts(input) {
5652
5916
  scope: plannedEndpoint.scope
5653
5917
  };
5654
5918
  }
5655
- if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path5, existing, plannedRoute)) {
5919
+ if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path6, existing, plannedRoute)) {
5656
5920
  marker = {
5657
5921
  kind: "route",
5658
5922
  owner: plannedRoute.owner,
5659
5923
  nextRelativePath: plannedRoute.nextRelativePath
5660
5924
  };
5661
5925
  }
5662
- const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path5);
5926
+ const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path6);
5663
5927
  if (!marker) {
5664
5928
  return {
5665
5929
  ok: false,
5666
- error: path5 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path5}` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path5}` : `[ModuleRail] composition artifacts: refusing to overwrite unmanaged route at ${path5}`,
5667
- path: path5
5930
+ error: path6 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path6}` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path6}` : `[ModuleRail] composition artifacts: refusing to overwrite unmanaged route at ${path6}`,
5931
+ path: path6
5668
5932
  };
5669
5933
  }
5670
- const expectedKind = expectedMarkerKindForPath(path5);
5934
+ const expectedKind = expectedMarkerKindForPath(path6);
5671
5935
  if (marker.kind !== expectedKind) {
5672
5936
  return {
5673
5937
  ok: false,
5674
- error: path5 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path5} (incongruent marker ownership: found ${marker.kind} marker)` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path5} (incongruent marker ownership: found ${marker.kind} marker)` : `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path5} (expected ${expectedKind ?? "no"} marker, found ${marker.kind} marker)`,
5675
- path: path5
5938
+ error: path6 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path6} (incongruent marker ownership: found ${marker.kind} marker)` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path6} (incongruent marker ownership: found ${marker.kind} marker)` : `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path6} (expected ${expectedKind ?? "no"} marker, found ${marker.kind} marker)`,
5939
+ path: path6
5676
5940
  };
5677
5941
  }
5678
5942
  if (marker.kind === "endpoint") {
5679
- const stub = endpointStubByPath.get(path5);
5943
+ const stub = endpointStubByPath.get(path6);
5680
5944
  if (!endpointMarkerOwnsStub(marker, stub)) {
5681
5945
  return {
5682
5946
  ok: false,
5683
- error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path5} (existing owner=${marker.owner} scope=${scopeLabel(marker.scope)} vs planned owner=${stub?.owner ?? "?"} scope=${stub ? scopeLabel(stub.scope) : "?"})`,
5684
- path: path5,
5947
+ error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path6} (existing owner=${marker.owner} scope=${scopeLabel(marker.scope)} vs planned owner=${stub?.owner ?? "?"} scope=${stub ? scopeLabel(stub.scope) : "?"})`,
5948
+ path: path6,
5685
5949
  owner: stub?.owner
5686
5950
  };
5687
5951
  }
5688
5952
  } else if (marker.kind === "route") {
5689
- const stub = routeStubByPath.get(path5);
5953
+ const stub = routeStubByPath.get(path6);
5690
5954
  const ownerMatches = stub != null && marker.owner === stub.owner;
5691
5955
  const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
5692
5956
  if (!ownerMatches || !pathMatches) {
5693
5957
  return {
5694
5958
  ok: false,
5695
- error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path5} (existing owner=${marker.owner} path=${marker.nextRelativePath} vs planned owner=${stub?.owner ?? "?"} path=${stub?.nextRelativePath ?? "?"})`,
5696
- path: path5,
5959
+ error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path6} (existing owner=${marker.owner} path=${marker.nextRelativePath} vs planned owner=${stub?.owner ?? "?"} path=${stub?.nextRelativePath ?? "?"})`,
5960
+ path: path6,
5697
5961
  owner: stub?.owner
5698
5962
  };
5699
5963
  }
5700
5964
  }
5701
5965
  }
5702
5966
  const deletes = [];
5703
- for (const path5 of input.existingGeneratedPaths) {
5704
- const existing = input.existingFiles[path5];
5967
+ for (const path6 of input.existingGeneratedPaths) {
5968
+ const existing = input.existingFiles[path6];
5705
5969
  if (existing == null) continue;
5706
- if (!isProvenStaleDeletionCandidate(path5, existing)) {
5970
+ if (!isProvenStaleDeletionCandidate(path6, existing)) {
5707
5971
  continue;
5708
5972
  }
5709
- deletes.push(path5);
5973
+ deletes.push(path6);
5710
5974
  }
5711
5975
  deletes.sort(compareNames2);
5712
- const writes = [...desired.entries()].filter(([path5, content]) => input.existingFiles[path5] !== content).map(([path5, content]) => ({ path: path5, content })).sort((a, b) => compareNames2(a.path, b.path));
5976
+ const writes = [...desired.entries()].filter(([path6, content]) => input.existingFiles[path6] !== content).map(([path6, content]) => ({ path: path6, content })).sort((a, b) => compareNames2(a.path, b.path));
5713
5977
  return {
5714
5978
  ok: true,
5715
5979
  plan: {
@@ -5980,14 +6244,14 @@ function rejectPseudoModuleEntries(entries, parentDir, label) {
5980
6244
  }
5981
6245
 
5982
6246
  // ../../packages/lib/src/shared/module-composition.ts
5983
- var import_zod7 = require("zod");
5984
- var creationSourceBlankSchema = import_zod7.z.object({
5985
- type: import_zod7.z.literal("blank")
6247
+ var import_zod8 = require("zod");
6248
+ var creationSourceBlankSchema = import_zod8.z.object({
6249
+ type: import_zod8.z.literal("blank")
5986
6250
  });
5987
- var creationSourceBlueprintSchema = import_zod7.z.object({
5988
- type: import_zod7.z.literal("blueprint"),
6251
+ var creationSourceBlueprintSchema = import_zod8.z.object({
6252
+ type: import_zod8.z.literal("blueprint"),
5989
6253
  /** Internal template/fork project id — keep template naming in identifiers. */
5990
- blueprintProjectId: import_zod7.z.string().uuid(),
6254
+ blueprintProjectId: import_zod8.z.string().uuid(),
5991
6255
  /**
5992
6256
  * `null`/absent means every feature Module: the platform stores that as the
5993
6257
  * explicit list of feature Modules in the published Blueprint version the fork
@@ -5995,192 +6259,197 @@ var creationSourceBlueprintSchema = import_zod7.z.object({
5995
6259
  * a custom app: no feature Module, Library Modules only (`resolvePlatformEnv`
5996
6260
  * emits it as a set value naming no Module).
5997
6261
  */
5998
- enabledModules: import_zod7.z.array(moduleNameSchema).max(100).nullable().optional()
6262
+ enabledModules: import_zod8.z.array(moduleNameSchema).max(100).nullable().optional(),
6263
+ /**
6264
+ * The kind of business (a preset id the published version defines). Its Modules are built
6265
+ * and `enabledModules` adds extras on top; for the general kind `enabledModules` is the pick.
6266
+ */
6267
+ businessKind: import_zod8.z.string().regex(/^[a-z][a-z0-9-]{0,39}$/).optional()
5999
6268
  });
6000
- var creationSourceSchema = import_zod7.z.discriminatedUnion("type", [
6269
+ var creationSourceSchema = import_zod8.z.discriminatedUnion("type", [
6001
6270
  creationSourceBlankSchema,
6002
6271
  creationSourceBlueprintSchema
6003
6272
  ]);
6004
- var moduleInstallHandoffSchema = import_zod7.z.object({
6005
- moduleName: import_zod7.z.string().min(1),
6006
- homeProjectId: import_zod7.z.string().uuid(),
6007
- installedVersion: import_zod7.z.string().min(1),
6008
- baselineSha: import_zod7.z.string().min(1),
6009
- mounts: import_zod7.z.array(
6010
- import_zod7.z.object({
6011
- group: import_zod7.z.string(),
6012
- segment: import_zod7.z.string()
6273
+ var moduleInstallHandoffSchema = import_zod8.z.object({
6274
+ moduleName: import_zod8.z.string().min(1),
6275
+ homeProjectId: import_zod8.z.string().uuid(),
6276
+ installedVersion: import_zod8.z.string().min(1),
6277
+ baselineSha: import_zod8.z.string().min(1),
6278
+ mounts: import_zod8.z.array(
6279
+ import_zod8.z.object({
6280
+ group: import_zod8.z.string(),
6281
+ segment: import_zod8.z.string()
6013
6282
  })
6014
6283
  ),
6015
- integration: import_zod7.z.array(import_zod7.z.string()),
6016
- workflows: import_zod7.z.array(import_zod7.z.string()),
6017
- prunedEnhancements: import_zod7.z.array(import_zod7.z.string()),
6018
- restoredEnhancements: import_zod7.z.array(import_zod7.z.string()).optional(),
6019
- permissions: import_zod7.z.array(
6020
- import_zod7.z.object({
6021
- key: import_zod7.z.string().min(1),
6022
- description: import_zod7.z.string().min(1)
6284
+ integration: import_zod8.z.array(import_zod8.z.string()),
6285
+ workflows: import_zod8.z.array(import_zod8.z.string()),
6286
+ prunedEnhancements: import_zod8.z.array(import_zod8.z.string()),
6287
+ restoredEnhancements: import_zod8.z.array(import_zod8.z.string()).optional(),
6288
+ permissions: import_zod8.z.array(
6289
+ import_zod8.z.object({
6290
+ key: import_zod8.z.string().min(1),
6291
+ description: import_zod8.z.string().min(1)
6023
6292
  })
6024
6293
  ).optional()
6025
6294
  });
6026
6295
 
6027
6296
  // ../../packages/lib/src/shared/schema-ops.ts
6028
- var import_zod8 = require("zod");
6029
- var columnSpecSchema = import_zod8.z.object({
6030
- name: import_zod8.z.string().min(1).max(63),
6031
- pgType: import_zod8.z.string().min(1),
6032
- nullable: import_zod8.z.boolean(),
6297
+ var import_zod9 = require("zod");
6298
+ var columnSpecSchema = import_zod9.z.object({
6299
+ name: import_zod9.z.string().min(1).max(63),
6300
+ pgType: import_zod9.z.string().min(1),
6301
+ nullable: import_zod9.z.boolean(),
6033
6302
  /**
6034
6303
  * A SQL expression used as the column default, as a raw string (e.g. `'t'`,
6035
6304
  * `0`, `now()`, `gen_random_uuid()`). `null` or omitted means no default.
6036
6305
  * NOT a JS literal — must already be a valid SQL expression.
6037
6306
  */
6038
- default: import_zod8.z.string().nullable().optional(),
6039
- fieldType: import_zod8.z.string().optional(),
6040
- fieldConfig: import_zod8.z.record(import_zod8.z.string(), import_zod8.z.unknown()).optional()
6307
+ default: import_zod9.z.string().nullable().optional(),
6308
+ fieldType: import_zod9.z.string().optional(),
6309
+ fieldConfig: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.unknown()).optional()
6041
6310
  });
6042
- var referentialActionSchema = import_zod8.z.enum([
6311
+ var referentialActionSchema = import_zod9.z.enum([
6043
6312
  "cascade",
6044
6313
  "set null",
6045
6314
  "set default",
6046
6315
  "restrict",
6047
6316
  "no action"
6048
6317
  ]);
6049
- var schemaOpSchema = import_zod8.z.discriminatedUnion("type", [
6318
+ var schemaOpSchema = import_zod9.z.discriminatedUnion("type", [
6050
6319
  // Tables
6051
- import_zod8.z.object({
6052
- type: import_zod8.z.literal("createTable"),
6053
- table: import_zod8.z.string().min(1).max(63),
6054
- columns: import_zod8.z.array(columnSpecSchema).min(1),
6055
- primaryKey: import_zod8.z.array(import_zod8.z.string()).optional(),
6056
- ifNotExists: import_zod8.z.boolean().optional()
6320
+ import_zod9.z.object({
6321
+ type: import_zod9.z.literal("createTable"),
6322
+ table: import_zod9.z.string().min(1).max(63),
6323
+ columns: import_zod9.z.array(columnSpecSchema).min(1),
6324
+ primaryKey: import_zod9.z.array(import_zod9.z.string()).optional(),
6325
+ ifNotExists: import_zod9.z.boolean().optional()
6057
6326
  }),
6058
- import_zod8.z.object({
6059
- type: import_zod8.z.literal("dropTable"),
6060
- table: import_zod8.z.string().min(1).max(63),
6061
- cascade: import_zod8.z.boolean().optional(),
6062
- ifExists: import_zod8.z.boolean().optional()
6327
+ import_zod9.z.object({
6328
+ type: import_zod9.z.literal("dropTable"),
6329
+ table: import_zod9.z.string().min(1).max(63),
6330
+ cascade: import_zod9.z.boolean().optional(),
6331
+ ifExists: import_zod9.z.boolean().optional()
6063
6332
  }),
6064
- import_zod8.z.object({
6065
- type: import_zod8.z.literal("renameTable"),
6066
- from: import_zod8.z.string().min(1).max(63),
6067
- to: import_zod8.z.string().min(1).max(63)
6333
+ import_zod9.z.object({
6334
+ type: import_zod9.z.literal("renameTable"),
6335
+ from: import_zod9.z.string().min(1).max(63),
6336
+ to: import_zod9.z.string().min(1).max(63)
6068
6337
  }),
6069
6338
  // Columns
6070
- import_zod8.z.object({
6071
- type: import_zod8.z.literal("addColumn"),
6072
- table: import_zod8.z.string().min(1).max(63),
6339
+ import_zod9.z.object({
6340
+ type: import_zod9.z.literal("addColumn"),
6341
+ table: import_zod9.z.string().min(1).max(63),
6073
6342
  column: columnSpecSchema,
6074
- ifNotExists: import_zod8.z.boolean().optional()
6343
+ ifNotExists: import_zod9.z.boolean().optional()
6075
6344
  }),
6076
- import_zod8.z.object({
6077
- type: import_zod8.z.literal("dropColumn"),
6078
- table: import_zod8.z.string().min(1).max(63),
6079
- column: import_zod8.z.string().min(1).max(63),
6080
- cascade: import_zod8.z.boolean().optional(),
6081
- ifExists: import_zod8.z.boolean().optional()
6345
+ import_zod9.z.object({
6346
+ type: import_zod9.z.literal("dropColumn"),
6347
+ table: import_zod9.z.string().min(1).max(63),
6348
+ column: import_zod9.z.string().min(1).max(63),
6349
+ cascade: import_zod9.z.boolean().optional(),
6350
+ ifExists: import_zod9.z.boolean().optional()
6082
6351
  }),
6083
- import_zod8.z.object({
6084
- type: import_zod8.z.literal("renameColumn"),
6085
- table: import_zod8.z.string().min(1).max(63),
6086
- from: import_zod8.z.string().min(1).max(63),
6087
- to: import_zod8.z.string().min(1).max(63)
6352
+ import_zod9.z.object({
6353
+ type: import_zod9.z.literal("renameColumn"),
6354
+ table: import_zod9.z.string().min(1).max(63),
6355
+ from: import_zod9.z.string().min(1).max(63),
6356
+ to: import_zod9.z.string().min(1).max(63)
6088
6357
  }),
6089
- import_zod8.z.object({
6090
- type: import_zod8.z.literal("alterColumnType"),
6091
- table: import_zod8.z.string().min(1).max(63),
6092
- column: import_zod8.z.string().min(1).max(63),
6093
- newType: import_zod8.z.string().min(1),
6358
+ import_zod9.z.object({
6359
+ type: import_zod9.z.literal("alterColumnType"),
6360
+ table: import_zod9.z.string().min(1).max(63),
6361
+ column: import_zod9.z.string().min(1).max(63),
6362
+ newType: import_zod9.z.string().min(1),
6094
6363
  /** Optional USING expression for potentially-lossy casts */
6095
- using: import_zod8.z.string().optional()
6364
+ using: import_zod9.z.string().optional()
6096
6365
  }),
6097
- import_zod8.z.object({
6098
- type: import_zod8.z.literal("alterColumnNullable"),
6099
- table: import_zod8.z.string().min(1).max(63),
6100
- column: import_zod8.z.string().min(1).max(63),
6101
- nullable: import_zod8.z.boolean()
6366
+ import_zod9.z.object({
6367
+ type: import_zod9.z.literal("alterColumnNullable"),
6368
+ table: import_zod9.z.string().min(1).max(63),
6369
+ column: import_zod9.z.string().min(1).max(63),
6370
+ nullable: import_zod9.z.boolean()
6102
6371
  }),
6103
- import_zod8.z.object({
6104
- type: import_zod8.z.literal("alterColumnDefault"),
6105
- table: import_zod8.z.string().min(1).max(63),
6106
- column: import_zod8.z.string().min(1).max(63),
6372
+ import_zod9.z.object({
6373
+ type: import_zod9.z.literal("alterColumnDefault"),
6374
+ table: import_zod9.z.string().min(1).max(63),
6375
+ column: import_zod9.z.string().min(1).max(63),
6107
6376
  /** null means drop the default; otherwise a SQL expression string. */
6108
- default: import_zod8.z.string().nullable()
6377
+ default: import_zod9.z.string().nullable()
6109
6378
  }),
6110
6379
  // Indexes
6111
- import_zod8.z.object({
6112
- type: import_zod8.z.literal("addIndex"),
6113
- table: import_zod8.z.string().min(1).max(63),
6114
- name: import_zod8.z.string().min(1).max(63),
6115
- columns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1),
6116
- unique: import_zod8.z.boolean().optional(),
6380
+ import_zod9.z.object({
6381
+ type: import_zod9.z.literal("addIndex"),
6382
+ table: import_zod9.z.string().min(1).max(63),
6383
+ name: import_zod9.z.string().min(1).max(63),
6384
+ columns: import_zod9.z.array(import_zod9.z.string().min(1)).min(1),
6385
+ unique: import_zod9.z.boolean().optional(),
6117
6386
  /** Partial-index WHERE clause, as a raw SQL expression */
6118
- where: import_zod8.z.string().optional(),
6119
- ifNotExists: import_zod8.z.boolean().optional()
6387
+ where: import_zod9.z.string().optional(),
6388
+ ifNotExists: import_zod9.z.boolean().optional()
6120
6389
  }),
6121
- import_zod8.z.object({
6122
- type: import_zod8.z.literal("dropIndex"),
6123
- name: import_zod8.z.string().min(1).max(63),
6124
- cascade: import_zod8.z.boolean().optional(),
6125
- ifExists: import_zod8.z.boolean().optional()
6390
+ import_zod9.z.object({
6391
+ type: import_zod9.z.literal("dropIndex"),
6392
+ name: import_zod9.z.string().min(1).max(63),
6393
+ cascade: import_zod9.z.boolean().optional(),
6394
+ ifExists: import_zod9.z.boolean().optional()
6126
6395
  }),
6127
6396
  // Constraints
6128
- import_zod8.z.object({
6129
- type: import_zod8.z.literal("addForeignKey"),
6130
- table: import_zod8.z.string().min(1).max(63),
6131
- name: import_zod8.z.string().min(1).max(63),
6132
- columns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1),
6133
- refTable: import_zod8.z.string().min(1).max(63),
6134
- refColumns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1),
6397
+ import_zod9.z.object({
6398
+ type: import_zod9.z.literal("addForeignKey"),
6399
+ table: import_zod9.z.string().min(1).max(63),
6400
+ name: import_zod9.z.string().min(1).max(63),
6401
+ columns: import_zod9.z.array(import_zod9.z.string().min(1)).min(1),
6402
+ refTable: import_zod9.z.string().min(1).max(63),
6403
+ refColumns: import_zod9.z.array(import_zod9.z.string().min(1)).min(1),
6135
6404
  onDelete: referentialActionSchema.optional(),
6136
6405
  onUpdate: referentialActionSchema.optional()
6137
6406
  }),
6138
- import_zod8.z.object({
6139
- type: import_zod8.z.literal("addUniqueConstraint"),
6140
- table: import_zod8.z.string().min(1).max(63),
6141
- name: import_zod8.z.string().min(1).max(63),
6142
- columns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1)
6407
+ import_zod9.z.object({
6408
+ type: import_zod9.z.literal("addUniqueConstraint"),
6409
+ table: import_zod9.z.string().min(1).max(63),
6410
+ name: import_zod9.z.string().min(1).max(63),
6411
+ columns: import_zod9.z.array(import_zod9.z.string().min(1)).min(1)
6143
6412
  }),
6144
- import_zod8.z.object({
6145
- type: import_zod8.z.literal("addCheckConstraint"),
6146
- table: import_zod8.z.string().min(1).max(63),
6147
- name: import_zod8.z.string().min(1).max(63),
6148
- expression: import_zod8.z.string().min(1)
6413
+ import_zod9.z.object({
6414
+ type: import_zod9.z.literal("addCheckConstraint"),
6415
+ table: import_zod9.z.string().min(1).max(63),
6416
+ name: import_zod9.z.string().min(1).max(63),
6417
+ expression: import_zod9.z.string().min(1)
6149
6418
  }),
6150
- import_zod8.z.object({
6151
- type: import_zod8.z.literal("dropConstraint"),
6152
- table: import_zod8.z.string().min(1).max(63),
6153
- name: import_zod8.z.string().min(1).max(63),
6154
- cascade: import_zod8.z.boolean().optional(),
6155
- ifExists: import_zod8.z.boolean().optional()
6419
+ import_zod9.z.object({
6420
+ type: import_zod9.z.literal("dropConstraint"),
6421
+ table: import_zod9.z.string().min(1).max(63),
6422
+ name: import_zod9.z.string().min(1).max(63),
6423
+ cascade: import_zod9.z.boolean().optional(),
6424
+ ifExists: import_zod9.z.boolean().optional()
6156
6425
  }),
6157
6426
  // Enum types
6158
- import_zod8.z.object({
6159
- type: import_zod8.z.literal("createEnum"),
6160
- name: import_zod8.z.string().min(1).max(63),
6161
- values: import_zod8.z.array(import_zod8.z.string().min(1)).min(1)
6427
+ import_zod9.z.object({
6428
+ type: import_zod9.z.literal("createEnum"),
6429
+ name: import_zod9.z.string().min(1).max(63),
6430
+ values: import_zod9.z.array(import_zod9.z.string().min(1)).min(1)
6162
6431
  }),
6163
- import_zod8.z.object({
6164
- type: import_zod8.z.literal("alterEnumAddValue"),
6165
- name: import_zod8.z.string().min(1).max(63),
6166
- value: import_zod8.z.string().min(1),
6432
+ import_zod9.z.object({
6433
+ type: import_zod9.z.literal("alterEnumAddValue"),
6434
+ name: import_zod9.z.string().min(1).max(63),
6435
+ value: import_zod9.z.string().min(1),
6167
6436
  /** Position the new value before an existing one (mutually exclusive with `after`) */
6168
- before: import_zod8.z.string().optional(),
6437
+ before: import_zod9.z.string().optional(),
6169
6438
  /** Position the new value after an existing one (mutually exclusive with `before`) */
6170
- after: import_zod8.z.string().optional(),
6171
- ifNotExists: import_zod8.z.boolean().optional()
6439
+ after: import_zod9.z.string().optional(),
6440
+ ifNotExists: import_zod9.z.boolean().optional()
6172
6441
  }),
6173
- import_zod8.z.object({
6174
- type: import_zod8.z.literal("dropEnum"),
6175
- name: import_zod8.z.string().min(1).max(63),
6176
- cascade: import_zod8.z.boolean().optional(),
6177
- ifExists: import_zod8.z.boolean().optional()
6442
+ import_zod9.z.object({
6443
+ type: import_zod9.z.literal("dropEnum"),
6444
+ name: import_zod9.z.string().min(1).max(63),
6445
+ cascade: import_zod9.z.boolean().optional(),
6446
+ ifExists: import_zod9.z.boolean().optional()
6178
6447
  }),
6179
6448
  // Extensions
6180
- import_zod8.z.object({
6181
- type: import_zod8.z.literal("createExtension"),
6182
- name: import_zod8.z.string().min(1),
6183
- ifNotExists: import_zod8.z.boolean().optional()
6449
+ import_zod9.z.object({
6450
+ type: import_zod9.z.literal("createExtension"),
6451
+ name: import_zod9.z.string().min(1),
6452
+ ifNotExists: import_zod9.z.boolean().optional()
6184
6453
  }),
6185
6454
  // Data operations
6186
6455
  /**
@@ -6188,26 +6457,26 @@ var schemaOpSchema = import_zod8.z.discriminatedUnion("type", [
6188
6457
  * destructive — requires explicit confirm to replay on prod. The
6189
6458
  * `description` is what the agent/user sees at confirm time.
6190
6459
  */
6191
- import_zod8.z.object({
6192
- type: import_zod8.z.literal("backfill"),
6193
- description: import_zod8.z.string().min(1),
6194
- sql: import_zod8.z.string().min(1)
6460
+ import_zod9.z.object({
6461
+ type: import_zod9.z.literal("backfill"),
6462
+ description: import_zod9.z.string().min(1),
6463
+ sql: import_zod9.z.string().min(1)
6195
6464
  }),
6196
6465
  /**
6197
6466
  * Escape hatch for DDL the structured ops can't express. `destructive`
6198
6467
  * forces classification — use false only for genuinely additive-only SQL
6199
6468
  * (e.g. CREATE EXTENSION variants the structured op doesn't cover).
6200
6469
  */
6201
- import_zod8.z.object({
6202
- type: import_zod8.z.literal("rawSql"),
6203
- description: import_zod8.z.string().min(1),
6204
- sql: import_zod8.z.string().min(1),
6205
- destructive: import_zod8.z.boolean()
6470
+ import_zod9.z.object({
6471
+ type: import_zod9.z.literal("rawSql"),
6472
+ description: import_zod9.z.string().min(1),
6473
+ sql: import_zod9.z.string().min(1),
6474
+ destructive: import_zod9.z.boolean()
6206
6475
  })
6207
6476
  ]);
6208
6477
 
6209
6478
  // ../../packages/lib/src/server/module-rail/ops.ts
6210
- var import_zod9 = require("zod");
6479
+ var import_zod10 = require("zod");
6211
6480
 
6212
6481
  // ../../packages/lib/src/server/module-rail/reconcile.ts
6213
6482
  var SANDBOX_BATCH_SIZE = 200;
@@ -6237,11 +6506,11 @@ function countedCompositionContext(context) {
6237
6506
  counts.sandboxExecs += 1;
6238
6507
  return context.sandbox.exec(...args);
6239
6508
  },
6240
- readFile: (path5) => {
6509
+ readFile: (path6) => {
6241
6510
  counts.sandboxReads += 1;
6242
- return context.sandbox.readFile(path5);
6511
+ return context.sandbox.readFile(path6);
6243
6512
  },
6244
- fileExists: (path5) => context.sandbox.fileExists(path5),
6513
+ fileExists: (path6) => context.sandbox.fileExists(path6),
6245
6514
  fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
6246
6515
  addDependencies: (specs) => context.sandbox.addDependencies(specs)
6247
6516
  }
@@ -6438,14 +6707,14 @@ async function discoverRootRouteEntries(context, moduleName) {
6438
6707
  }
6439
6708
  return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
6440
6709
  }
6441
- async function writeSandboxFileAtomic(target, path5, content) {
6442
- const dir = dirnamePosix(path5);
6443
- const tmp = `${path5}.tmp.${target.runId}`;
6710
+ async function writeSandboxFileAtomic(target, path6, content) {
6711
+ const dir = dirnamePosix(path6);
6712
+ const tmp = `${path6}.tmp.${target.runId}`;
6444
6713
  const b64 = Buffer.from(content, "utf8").toString("base64");
6445
6714
  const cmd = [
6446
6715
  `mkdir -p ${shellQuote(dir)}`,
6447
6716
  `printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
6448
- `mv -f ${shellQuote(tmp)} ${shellQuote(path5)}`
6717
+ `mv -f ${shellQuote(tmp)} ${shellQuote(path6)}`
6449
6718
  ].join(" && ");
6450
6719
  const result = await target.sandbox.exec(cmd, { raiseOnError: false });
6451
6720
  if (result.exitCode !== 0) {
@@ -6460,14 +6729,14 @@ async function writeSandboxFileAtomic(target, path5, content) {
6460
6729
  }
6461
6730
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
6462
6731
  throw new Error(
6463
- `[ModuleRail] composition artifacts: failed to write ${redactSecrets(path5)}: ${redactSecrets(detail)}`
6732
+ `[ModuleRail] composition artifacts: failed to write ${redactSecrets(path6)}: ${redactSecrets(detail)}`
6464
6733
  );
6465
6734
  }
6466
6735
  }
6467
6736
  async function collectAbsentWriteParentDirs(context, writePaths) {
6468
6737
  const absent = /* @__PURE__ */ new Set();
6469
- for (const path5 of writePaths) {
6470
- let dir = dirnamePosix(path5);
6738
+ for (const path6 of writePaths) {
6739
+ let dir = dirnamePosix(path6);
6471
6740
  while (dir !== "." && dir !== "") {
6472
6741
  if (absent.has(dir)) {
6473
6742
  dir = dirnamePosix(dir);
@@ -6504,12 +6773,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
6504
6773
  );
6505
6774
  }
6506
6775
  }
6507
- async function deleteSandboxFile(context, path5) {
6508
- const result = await context.sandbox.exec(`rm -f ${shellQuote(path5)}`, { raiseOnError: false });
6776
+ async function deleteSandboxFile(context, path6) {
6777
+ const result = await context.sandbox.exec(`rm -f ${shellQuote(path6)}`, { raiseOnError: false });
6509
6778
  if (result.exitCode !== 0) {
6510
6779
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
6511
6780
  throw new Error(
6512
- `[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path5)}: ${redactSecrets(detail)}`
6781
+ `[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path6)}: ${redactSecrets(detail)}`
6513
6782
  );
6514
6783
  }
6515
6784
  }
@@ -6576,37 +6845,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
6576
6845
  await writeSandboxFileAtomic(context, write.path, write.content);
6577
6846
  appliedWrites.push({ path: write.path, previous });
6578
6847
  }
6579
- for (const path5 of plan.deletes) {
6580
- const previous = existingFiles[path5];
6848
+ for (const path6 of plan.deletes) {
6849
+ const previous = existingFiles[path6];
6581
6850
  if (previous == null) continue;
6582
- await deleteSandboxFile(context, path5);
6583
- appliedDeletes.push({ path: path5, previous });
6851
+ await deleteSandboxFile(context, path6);
6852
+ appliedDeletes.push({ path: path6, previous });
6584
6853
  }
6585
6854
  } catch (error) {
6586
6855
  console.log(
6587
6856
  `[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
6588
6857
  );
6589
- for (const { path: path5, previous } of [...appliedDeletes].reverse()) {
6858
+ for (const { path: path6, previous } of [...appliedDeletes].reverse()) {
6590
6859
  try {
6591
- await writeSandboxFileAtomic(context, path5, previous);
6860
+ await writeSandboxFileAtomic(context, path6, previous);
6592
6861
  } catch (rollbackError) {
6593
6862
  console.log(
6594
- `[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path5)}: ${redactSecrets(
6863
+ `[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path6)}: ${redactSecrets(
6595
6864
  rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
6596
6865
  )}`
6597
6866
  );
6598
6867
  }
6599
6868
  }
6600
- for (const { path: path5, previous } of [...appliedWrites].reverse()) {
6869
+ for (const { path: path6, previous } of [...appliedWrites].reverse()) {
6601
6870
  try {
6602
6871
  if (previous == null) {
6603
- await deleteSandboxFile(context, path5);
6872
+ await deleteSandboxFile(context, path6);
6604
6873
  } else {
6605
- await writeSandboxFileAtomic(context, path5, previous);
6874
+ await writeSandboxFileAtomic(context, path6, previous);
6606
6875
  }
6607
6876
  } catch (rollbackError) {
6608
6877
  console.log(
6609
- `[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path5)}: ${redactSecrets(
6878
+ `[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path6)}: ${redactSecrets(
6610
6879
  rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
6611
6880
  )}`
6612
6881
  );
@@ -6640,39 +6909,39 @@ async function listExistingGeneratedStubPaths(context) {
6640
6909
  COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
6641
6910
  );
6642
6911
  const presentPaths = [];
6643
- for (const path5 of candidates) {
6912
+ for (const path6 of candidates) {
6644
6913
  const presence = assertRealCompositionArtifactFilePresence(
6645
- path5,
6646
- kinds.get(path5) ?? "unreadable"
6914
+ path6,
6915
+ kinds.get(path6) ?? "unreadable"
6647
6916
  );
6648
- if (presence === "file") presentPaths.push(path5);
6917
+ if (presence === "file") presentPaths.push(path6);
6649
6918
  }
6650
6919
  let contents;
6651
6920
  try {
6652
6921
  contents = await readFilesMany(context, presentPaths);
6653
6922
  } catch (error) {
6654
- const path5 = readFailurePath(error);
6923
+ const path6 = readFailurePath(error);
6655
6924
  throw new Error(
6656
- `[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path5)}: ${redactSecrets(
6925
+ `[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path6)}: ${redactSecrets(
6657
6926
  error instanceof Error ? error.message : String(error)
6658
6927
  )}`
6659
6928
  );
6660
6929
  }
6661
6930
  const owned = [];
6662
- for (const path5 of presentPaths) {
6663
- const content = contents.get(path5);
6931
+ for (const path6 of presentPaths) {
6932
+ const content = contents.get(path6);
6664
6933
  const marker = parseCompositionArtifactMarker(content);
6665
- if (parseLegacyApiAdapter(path5, content) || parseLegacyRouteAdapter(path5, content)) {
6666
- owned.push(path5);
6934
+ if (parseLegacyApiAdapter(path6, content) || parseLegacyRouteAdapter(path6, content)) {
6935
+ owned.push(path6);
6667
6936
  continue;
6668
6937
  }
6669
6938
  if (marker == null) continue;
6670
- if (!markerCongruentWithPath(path5, marker)) {
6939
+ if (!markerCongruentWithPath(path6, marker)) {
6671
6940
  throw new Error(
6672
- `[ModuleRail] composition artifacts: incongruent marker at ${redactSecrets(path5)} (found ${marker.kind} marker on a path shaped for ${expectedMarkerKindForPath(path5) ?? "no"} artifacts; refusing to reconcile a swapped marker)`
6941
+ `[ModuleRail] composition artifacts: incongruent marker at ${redactSecrets(path6)} (found ${marker.kind} marker on a path shaped for ${expectedMarkerKindForPath(path6) ?? "no"} artifacts; refusing to reconcile a swapped marker)`
6673
6942
  );
6674
6943
  }
6675
- owned.push(path5);
6944
+ owned.push(path6);
6676
6945
  }
6677
6946
  return owned;
6678
6947
  }
@@ -6806,7 +7075,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
6806
7075
  { raiseOnError: false }
6807
7076
  );
6808
7077
  if (result.exitCode !== 0) {
6809
- for (const path5 of chunk) kinds.set(path5, "unreadable");
7078
+ for (const path6 of chunk) kinds.set(path6, "unreadable");
6810
7079
  continue;
6811
7080
  }
6812
7081
  const lines = result.output.split("\n").filter(Boolean);
@@ -6817,31 +7086,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
6817
7086
  }
6818
7087
  } else {
6819
7088
  for (const line of lines) {
6820
- const [rawKind, path5] = line.split(" ", 2);
7089
+ const [rawKind, path6] = line.split(" ", 2);
6821
7090
  const kind = rawKind?.replace(/^entry-kind:/, "");
6822
- if (path5 && chunk.includes(path5) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
6823
- kinds.set(path5, kind);
7091
+ if (path6 && chunk.includes(path6) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
7092
+ kinds.set(path6, kind);
6824
7093
  }
6825
7094
  }
6826
7095
  }
6827
- for (const path5 of chunk) {
6828
- if (!kinds.has(path5)) kinds.set(path5, "unreadable");
7096
+ for (const path6 of chunk) {
7097
+ if (!kinds.has(path6)) kinds.set(path6, "unreadable");
6829
7098
  }
6830
7099
  }
6831
7100
  return kinds;
6832
7101
  }
6833
- async function classifySandboxPathPresenceKind(context, path5, probeToken) {
6834
- return (await classifySandboxPathPresenceKindMany(context, [path5], probeToken)).get(path5);
7102
+ async function classifySandboxPathPresenceKind(context, path6, probeToken) {
7103
+ return (await classifySandboxPathPresenceKindMany(context, [path6], probeToken)).get(path6);
6835
7104
  }
6836
7105
  var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
6837
- function assertRealLocaleJsonFilePresence(path5, presence) {
7106
+ function assertRealLocaleJsonFilePresence(path6, presence) {
6838
7107
  if (presence === "file") return;
6839
7108
  if (presence === "absent") {
6840
- throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path5}`);
7109
+ throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path6}`);
6841
7110
  }
6842
- const presenceError = formatConventionalEntryPresenceError(path5, presence);
7111
+ const presenceError = formatConventionalEntryPresenceError(path6, presence);
6843
7112
  throw new Error(
6844
- `[ModuleRail] composition artifacts: ${presenceError ?? `${path5} is not a real locale JSON file`}`
7113
+ `[ModuleRail] composition artifacts: ${presenceError ?? `${path6} is not a real locale JSON file`}`
6845
7114
  );
6846
7115
  }
6847
7116
  function splitTaggedLine(line) {
@@ -6916,11 +7185,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
6916
7185
  }
6917
7186
  async function readFilesMany(context, paths) {
6918
7187
  const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
6919
- const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path5) => {
7188
+ const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path6) => {
6920
7189
  try {
6921
- return { path: path5, value: await context.sandbox.readFile(path5) };
7190
+ return { path: path6, value: await context.sandbox.readFile(path6) };
6922
7191
  } catch (error) {
6923
- return { path: path5, error };
7192
+ return { path: path6, error };
6924
7193
  }
6925
7194
  });
6926
7195
  const files = /* @__PURE__ */ new Map();
@@ -6943,61 +7212,61 @@ function readFailurePath(error) {
6943
7212
  var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
6944
7213
  var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
6945
7214
  var COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN = "composition-artifact-kind";
6946
- async function classifyCompositionArtifactPathKind(context, path5) {
6947
- return classifySandboxPathPresenceKind(context, path5, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
7215
+ async function classifyCompositionArtifactPathKind(context, path6) {
7216
+ return classifySandboxPathPresenceKind(context, path6, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
6948
7217
  }
6949
- function assertRealCompositionArtifactFilePresence(path5, presence) {
7218
+ function assertRealCompositionArtifactFilePresence(path6, presence) {
6950
7219
  if (presence === "file") return "file";
6951
7220
  if (presence === "absent") return "absent";
6952
- const presenceError = formatConventionalEntryPresenceError(path5, presence);
7221
+ const presenceError = formatConventionalEntryPresenceError(path6, presence);
6953
7222
  throw new Error(
6954
- `[ModuleRail] composition artifacts: ${presenceError ?? `${path5} is not a real composition artifact file`}`
7223
+ `[ModuleRail] composition artifacts: ${presenceError ?? `${path6} is not a real composition artifact file`}`
6955
7224
  );
6956
7225
  }
6957
- async function readConventionalEntryIfRealFile(path5, kind, presences, files) {
6958
- const presence = presences.get(path5) ?? "unreadable";
6959
- const presenceError = formatConventionalEntryPresenceError(path5, presence);
7226
+ async function readConventionalEntryIfRealFile(path6, kind, presences, files) {
7227
+ const presence = presences.get(path6) ?? "unreadable";
7228
+ const presenceError = formatConventionalEntryPresenceError(path6, presence);
6960
7229
  if (presenceError) {
6961
7230
  throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
6962
7231
  }
6963
7232
  if (presence === "absent") {
6964
7233
  return null;
6965
7234
  }
6966
- const source = files.get(path5);
7235
+ const source = files.get(path6);
6967
7236
  switch (kind) {
6968
7237
  case "contributions":
6969
- assertContributionsExport(source, path5);
6970
- assertClientSafeConventionalEntryImports(source, path5, "contributions");
7238
+ assertContributionsExport(source, path6);
7239
+ assertClientSafeConventionalEntryImports(source, path6, "contributions");
6971
7240
  break;
6972
7241
  case "slotCatalogs":
6973
- assertSlotCatalogsExport(source, path5);
6974
- assertClientSafeConventionalEntryImports(source, path5, "slotCatalogs");
7242
+ assertSlotCatalogsExport(source, path6);
7243
+ assertClientSafeConventionalEntryImports(source, path6, "slotCatalogs");
6975
7244
  break;
6976
7245
  case "initializeEnhancement":
6977
- assertInitializeEnhancementExport(source, path5);
7246
+ assertInitializeEnhancementExport(source, path6);
6978
7247
  break;
6979
7248
  case "initializeModule":
6980
- assertInitializeModuleExport(source, path5);
7249
+ assertInitializeModuleExport(source, path6);
6981
7250
  break;
6982
7251
  }
6983
7252
  return source;
6984
7253
  }
6985
7254
  function hasRealServerRegistrations(modulePath, presences) {
6986
- const path5 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
6987
- return assertRealCompositionArtifactFilePresence(path5, presences.get(path5) ?? "unreadable") === "file";
7255
+ const path6 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
7256
+ return assertRealCompositionArtifactFilePresence(path6, presences.get(path6) ?? "unreadable") === "file";
6988
7257
  }
6989
- async function readJsonObjectFile(path5, files) {
6990
- const raw = files.get(path5);
7258
+ async function readJsonObjectFile(path6, files) {
7259
+ const raw = files.get(path6);
6991
7260
  let parsed;
6992
7261
  try {
6993
7262
  parsed = JSON.parse(raw);
6994
7263
  } catch (error) {
6995
7264
  throw new Error(
6996
- `[ModuleRail] composition artifacts: invalid JSON at ${path5}: ${error instanceof Error ? error.message : String(error)}`
7265
+ `[ModuleRail] composition artifacts: invalid JSON at ${path6}: ${error instanceof Error ? error.message : String(error)}`
6997
7266
  );
6998
7267
  }
6999
7268
  if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
7000
- throw new Error(`[ModuleRail] composition artifacts: ${path5} root must be an object`);
7269
+ throw new Error(`[ModuleRail] composition artifacts: ${path6} root must be an object`);
7001
7270
  }
7002
7271
  return parsed;
7003
7272
  }
@@ -7050,11 +7319,11 @@ async function collectCompositionEntryFacts(context, registry) {
7050
7319
  classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
7051
7320
  ]);
7052
7321
  const presentConventionalPaths = [];
7053
- for (const path5 of conventionalPaths) {
7054
- const presence = conventionalPresences.get(path5) ?? "unreadable";
7055
- if (presence === "file") presentConventionalPaths.push(path5);
7322
+ for (const path6 of conventionalPaths) {
7323
+ const presence = conventionalPresences.get(path6) ?? "unreadable";
7324
+ if (presence === "file") presentConventionalPaths.push(path6);
7056
7325
  }
7057
- const presentLocalePaths = localePaths.filter((path5) => localePresences.get(path5) === "file");
7326
+ const presentLocalePaths = localePaths.filter((path6) => localePresences.get(path6) === "file");
7058
7327
  const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
7059
7328
  const substrateLocales = [];
7060
7329
  for (const locale of substrateNames) {
@@ -7164,8 +7433,8 @@ async function collectCompositionEntryFacts(context, registry) {
7164
7433
  });
7165
7434
  if (rootLocales.length > 0) {
7166
7435
  for (const locale of rootLocales) {
7167
- const path5 = `${modulePath}/i18n/${locale}.json`;
7168
- assertRealLocaleJsonFilePresence(path5, localePresences.get(path5) ?? "unreadable");
7436
+ const path6 = `${modulePath}/i18n/${locale}.json`;
7437
+ assertRealLocaleJsonFilePresence(path6, localePresences.get(path6) ?? "unreadable");
7169
7438
  }
7170
7439
  const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
7171
7440
  const localeMessages = { en: enMessages };
@@ -7189,8 +7458,8 @@ async function collectCompositionEntryFacts(context, registry) {
7189
7458
  if (!enh.active || enh.locales.length === 0) continue;
7190
7459
  const peerDir = `${modulePath}/enhancements/${enh.peer}`;
7191
7460
  for (const locale of enh.locales) {
7192
- const path5 = `${peerDir}/i18n/${locale}.json`;
7193
- assertRealLocaleJsonFilePresence(path5, localePresences.get(path5) ?? "unreadable");
7461
+ const path6 = `${peerDir}/i18n/${locale}.json`;
7462
+ assertRealLocaleJsonFilePresence(path6, localePresences.get(path6) ?? "unreadable");
7194
7463
  }
7195
7464
  const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
7196
7465
  const localeMessages = { en: enMessages };
@@ -7398,16 +7667,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7398
7667
  for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
7399
7668
  const existingFiles = {};
7400
7669
  const remainingCandidatePaths = [];
7401
- for (const path5 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
7402
- if (Object.prototype.hasOwnProperty.call(existingAppPages, path5)) {
7403
- existingFiles[path5] = existingAppPages[path5];
7670
+ for (const path6 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
7671
+ if (Object.prototype.hasOwnProperty.call(existingAppPages, path6)) {
7672
+ existingFiles[path6] = existingAppPages[path6];
7404
7673
  continue;
7405
7674
  }
7406
- if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path5)) {
7407
- existingFiles[path5] = existingAppRoutes[path5];
7675
+ if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path6)) {
7676
+ existingFiles[path6] = existingAppRoutes[path6];
7408
7677
  continue;
7409
7678
  }
7410
- remainingCandidatePaths.push(path5);
7679
+ remainingCandidatePaths.push(path6);
7411
7680
  }
7412
7681
  const candidateKinds = await classifySandboxPathPresenceKindMany(
7413
7682
  context,
@@ -7415,12 +7684,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7415
7684
  COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
7416
7685
  );
7417
7686
  const presentCandidatePaths = [];
7418
- for (const path5 of remainingCandidatePaths) {
7687
+ for (const path6 of remainingCandidatePaths) {
7419
7688
  const presence = assertRealCompositionArtifactFilePresence(
7420
- path5,
7421
- candidateKinds.get(path5) ?? "unreadable"
7689
+ path6,
7690
+ candidateKinds.get(path6) ?? "unreadable"
7422
7691
  );
7423
- if (presence === "file") presentCandidatePaths.push(path5);
7692
+ if (presence === "file") presentCandidatePaths.push(path6);
7424
7693
  }
7425
7694
  try {
7426
7695
  Object.assign(
@@ -7428,9 +7697,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7428
7697
  Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
7429
7698
  );
7430
7699
  } catch (error) {
7431
- const path5 = readFailurePath(error);
7700
+ const path6 = readFailurePath(error);
7432
7701
  throw new Error(
7433
- `[ModuleRail] composition artifacts: cannot read ${redactSecrets(path5)} during preflight: ${redactSecrets(
7702
+ `[ModuleRail] composition artifacts: cannot read ${redactSecrets(path6)} during preflight: ${redactSecrets(
7434
7703
  error instanceof Error ? error.message : String(error)
7435
7704
  )}`
7436
7705
  );
@@ -7503,8 +7772,8 @@ var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
7503
7772
  var import_typescript6 = __toESM(require("typescript"));
7504
7773
 
7505
7774
  // src/module-boundaries.ts
7506
- var import_node_fs2 = __toESM(require("fs"));
7507
- var import_node_path2 = __toESM(require("path"));
7775
+ var import_node_fs3 = __toESM(require("fs"));
7776
+ var import_node_path3 = __toESM(require("path"));
7508
7777
  var import_typescript5 = __toESM(require("typescript"));
7509
7778
  var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
7510
7779
  var RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
@@ -7523,26 +7792,26 @@ function dataPathsIn(fileName, source) {
7523
7792
  }
7524
7793
  function scanModule(modulesDir, module2) {
7525
7794
  const scanned = /* @__PURE__ */ new Map();
7526
- const moduleDir = import_node_path2.default.join(modulesDir, module2);
7527
- if (!import_node_fs2.default.existsSync(moduleDir)) return scanned;
7528
- const files = import_node_fs2.default.readdirSync(moduleDir, { recursive: true, encoding: "utf8" }).filter((relative) => SOURCE_FILE.test(relative) && !relative.includes("node_modules"));
7795
+ const moduleDir = import_node_path3.default.join(modulesDir, module2);
7796
+ if (!import_node_fs3.default.existsSync(moduleDir)) return scanned;
7797
+ const files = import_node_fs3.default.readdirSync(moduleDir, { recursive: true, encoding: "utf8" }).filter((relative) => SOURCE_FILE.test(relative) && !relative.includes("node_modules"));
7529
7798
  for (const relative of files) {
7530
- const absolute = import_node_path2.default.join(moduleDir, relative);
7531
- if (!import_node_fs2.default.statSync(absolute).isFile()) continue;
7532
- const source = import_node_fs2.default.readFileSync(absolute, "utf8");
7799
+ const absolute = import_node_path3.default.join(moduleDir, relative);
7800
+ if (!import_node_fs3.default.statSync(absolute).isFile()) continue;
7801
+ const source = import_node_fs3.default.readFileSync(absolute, "utf8");
7533
7802
  const specifiers = import_typescript5.default.preProcessFile(source, true, true).importedFiles.map((f) => f.fileName);
7534
7803
  const dataPaths = dataPathsIn(absolute, source);
7535
7804
  const targets = [...specifiers, ...dataPaths].flatMap((spec) => {
7536
7805
  if (spec.startsWith("@/modules/"))
7537
- return [import_node_path2.default.join(modulesDir, spec.slice("@/modules/".length))];
7806
+ return [import_node_path3.default.join(modulesDir, spec.slice("@/modules/".length))];
7538
7807
  if (spec.startsWith("./") || spec.startsWith("../")) {
7539
- return [import_node_path2.default.resolve(import_node_path2.default.dirname(absolute), spec)];
7808
+ return [import_node_path3.default.resolve(import_node_path3.default.dirname(absolute), spec)];
7540
7809
  }
7541
7810
  return [];
7542
7811
  });
7543
7812
  scanned.set(absolute, {
7544
7813
  module: module2,
7545
- relative: relative.split(import_node_path2.default.sep).join("/"),
7814
+ relative: relative.split(import_node_path3.default.sep).join("/"),
7546
7815
  targets,
7547
7816
  dataPaths
7548
7817
  });
@@ -7559,7 +7828,7 @@ function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = fal
7559
7828
  (q) => file.relative.startsWith(`enhancements/${q}/`)
7560
7829
  );
7561
7830
  const goneWith = (target, peer) => {
7562
- const relative = import_node_path2.default.relative(modulesDir, target).split(import_node_path2.default.sep).join("/");
7831
+ const relative = import_node_path3.default.relative(modulesDir, target).split(import_node_path3.default.sep).join("/");
7563
7832
  return relative === peer || relative.startsWith(`${peer}/`) || new RegExp(`^[^/]+/enhancements/${peer}(/|$)`).test(relative);
7564
7833
  };
7565
7834
  const tainted = /* @__PURE__ */ new Map();
@@ -7605,12 +7874,12 @@ function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = fal
7605
7874
  return violations.sort((a, b) => a.file.localeCompare(b.file) || a.peer.localeCompare(b.peer));
7606
7875
  }
7607
7876
  function findOptionalPeerViolations(modulesDir) {
7608
- if (!import_node_fs2.default.existsSync(modulesDir)) return [];
7877
+ if (!import_node_fs3.default.existsSync(modulesDir)) return [];
7609
7878
  const manifests = {};
7610
- for (const name of import_node_fs2.default.readdirSync(modulesDir)) {
7611
- const manifestPath = import_node_path2.default.join(modulesDir, name, "module.json");
7612
- if (!import_node_fs2.default.existsSync(manifestPath)) continue;
7613
- manifests[name] = JSON.parse(import_node_fs2.default.readFileSync(manifestPath, "utf8"));
7879
+ for (const name of import_node_fs3.default.readdirSync(modulesDir)) {
7880
+ const manifestPath = import_node_path3.default.join(modulesDir, name, "module.json");
7881
+ if (!import_node_fs3.default.existsSync(manifestPath)) continue;
7882
+ manifests[name] = JSON.parse(import_node_fs3.default.readFileSync(manifestPath, "utf8"));
7614
7883
  }
7615
7884
  const features = Object.keys(manifests).filter((name) => manifests[name].kind === "feature");
7616
7885
  const peersByModule = {};
@@ -7630,11 +7899,11 @@ function formatBoundaryViolations(modulesDirLabel, violations) {
7630
7899
  // src/index.ts
7631
7900
  var MODULES_DIR2 = MODULES_SANDBOX_DIR;
7632
7901
  function readComposeInputs(cwd) {
7633
- const file = import_node_path3.default.join(cwd, COMPOSE_INPUTS_PATH);
7634
- if (!import_node_fs3.default.existsSync(file)) return { connectedStores: [], installs: {} };
7902
+ const file = import_node_path4.default.join(cwd, COMPOSE_INPUTS_PATH);
7903
+ if (!import_node_fs4.default.existsSync(file)) return { connectedStores: [], installs: {} };
7635
7904
  let raw;
7636
7905
  try {
7637
- raw = JSON.parse(import_node_fs3.default.readFileSync(file, "utf8"));
7906
+ raw = JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
7638
7907
  } catch (error) {
7639
7908
  throw new Error(
7640
7909
  `[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
@@ -7676,18 +7945,18 @@ function stubGitignoreEntries(artifactPaths) {
7676
7945
  function writeStubGitignore(cwd, artifactPaths) {
7677
7946
  const entries = stubGitignoreEntries(artifactPaths);
7678
7947
  const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
7679
- const file = import_node_path3.default.join(cwd, STUB_GITIGNORE_PATH);
7680
- const existing = import_node_fs3.default.existsSync(file) ? import_node_fs3.default.readFileSync(file, "utf8") : null;
7948
+ const file = import_node_path4.default.join(cwd, STUB_GITIGNORE_PATH);
7949
+ const existing = import_node_fs4.default.existsSync(file) ? import_node_fs4.default.readFileSync(file, "utf8") : null;
7681
7950
  if (existing !== content) {
7682
- import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true });
7683
- import_node_fs3.default.writeFileSync(file, content);
7951
+ import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
7952
+ import_node_fs4.default.writeFileSync(file, content);
7684
7953
  }
7685
7954
  return entries.length;
7686
7955
  }
7687
7956
  function assertStubGitignoreOwned(cwd) {
7688
- const file = import_node_path3.default.join(cwd, STUB_GITIGNORE_PATH);
7689
- if (!import_node_fs3.default.existsSync(file)) return;
7690
- const firstLine = import_node_fs3.default.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
7957
+ const file = import_node_path4.default.join(cwd, STUB_GITIGNORE_PATH);
7958
+ if (!import_node_fs4.default.existsSync(file)) return;
7959
+ const firstLine = import_node_fs4.default.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
7691
7960
  if (firstLine !== STUB_GITIGNORE_HEADER) {
7692
7961
  throw new Error(
7693
7962
  `[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
@@ -7695,8 +7964,8 @@ function assertStubGitignoreOwned(cwd) {
7695
7964
  }
7696
7965
  }
7697
7966
  function assertAppRoot(cwd) {
7698
- const appPath = import_node_path3.default.join(cwd, APP_PACKAGE_JSON);
7699
- if (!import_node_fs3.default.existsSync(appPath)) {
7967
+ const appPath = import_node_path4.default.join(cwd, APP_PACKAGE_JSON);
7968
+ if (!import_node_fs4.default.existsSync(appPath)) {
7700
7969
  throw new Error(
7701
7970
  `[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
7702
7971
  );
@@ -7742,9 +8011,9 @@ ${porcelain.trim()}`
7742
8011
  }
7743
8012
  var APP_SRC_DIR = "apps/web/src";
7744
8013
  function walkSources(dir, skip, out) {
7745
- if (!import_node_fs3.default.existsSync(dir)) return;
7746
- for (const entry of import_node_fs3.default.readdirSync(dir, { withFileTypes: true })) {
7747
- const full = import_node_path3.default.join(dir, entry.name);
8014
+ if (!import_node_fs4.default.existsSync(dir)) return;
8015
+ for (const entry of import_node_fs4.default.readdirSync(dir, { withFileTypes: true })) {
8016
+ const full = import_node_path4.default.join(dir, entry.name);
7748
8017
  if (full === skip) continue;
7749
8018
  if (entry.isDirectory()) walkSources(full, skip, out);
7750
8019
  else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
@@ -7753,13 +8022,13 @@ function walkSources(dir, skip, out) {
7753
8022
  function assertNoAppLayerImporters(cwd, excluded) {
7754
8023
  if (excluded.length === 0) return;
7755
8024
  const files = [];
7756
- walkSources(import_node_path3.default.join(cwd, APP_SRC_DIR), import_node_path3.default.join(cwd, MODULES_DIR2), files);
8025
+ walkSources(import_node_path4.default.join(cwd, APP_SRC_DIR), import_node_path4.default.join(cwd, MODULES_DIR2), files);
7757
8026
  const offenders = [];
7758
8027
  for (const file of files) {
7759
- const source = import_node_fs3.default.readFileSync(file, "utf8");
8028
+ const source = import_node_fs4.default.readFileSync(file, "utf8");
7760
8029
  for (const name of excluded) {
7761
8030
  if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
7762
- offenders.push(`${import_node_path3.default.relative(cwd, file)}: ${name}`);
8031
+ offenders.push(`${import_node_path4.default.relative(cwd, file)}: ${name}`);
7763
8032
  }
7764
8033
  }
7765
8034
  }
@@ -7793,8 +8062,8 @@ var BUILD_TYPECHECK_SKIP = [
7793
8062
  "playwright.config.ts"
7794
8063
  ];
7795
8064
  function writeBuildTsconfig(cwd, excluded) {
7796
- const baseFile = import_node_path3.default.join(cwd, TSCONFIG_PATH);
7797
- if (!import_node_fs3.default.existsSync(import_node_path3.default.join(cwd, MODULES_DIR2)) || !import_node_fs3.default.existsSync(baseFile)) return;
8065
+ const baseFile = import_node_path4.default.join(cwd, TSCONFIG_PATH);
8066
+ if (!import_node_fs4.default.existsSync(import_node_path4.default.join(cwd, MODULES_DIR2)) || !import_node_fs4.default.existsSync(baseFile)) return;
7798
8067
  const base = import_typescript6.default.readConfigFile(baseFile, import_typescript6.default.sys.readFile);
7799
8068
  if (base.error) {
7800
8069
  throw new Error(
@@ -7802,7 +8071,7 @@ function writeBuildTsconfig(cwd, excluded) {
7802
8071
  );
7803
8072
  }
7804
8073
  const baseExclude = base.config?.exclude ?? ["node_modules"];
7805
- const modulesDir = import_node_path3.default.posix.relative("apps/web", MODULES_DIR2);
8074
+ const modulesDir = import_node_path4.default.posix.relative("apps/web", MODULES_DIR2);
7806
8075
  const config = {
7807
8076
  extends: "./tsconfig.json",
7808
8077
  exclude: [
@@ -7817,17 +8086,17 @@ function writeBuildTsconfig(cwd, excluded) {
7817
8086
  const content = `${BUILD_TSCONFIG_HEADER}
7818
8087
  ${JSON.stringify(config, null, 2)}
7819
8088
  `;
7820
- const file = import_node_path3.default.join(cwd, BUILD_TSCONFIG_PATH);
7821
- if (!import_node_fs3.default.existsSync(file) || import_node_fs3.default.readFileSync(file, "utf8") !== content) {
7822
- import_node_fs3.default.writeFileSync(file, content);
8089
+ const file = import_node_path4.default.join(cwd, BUILD_TSCONFIG_PATH);
8090
+ if (!import_node_fs4.default.existsSync(file) || import_node_fs4.default.readFileSync(file, "utf8") !== content) {
8091
+ import_node_fs4.default.writeFileSync(file, content);
7823
8092
  }
7824
8093
  console.log(
7825
8094
  `[compose] ${BUILD_TSCONFIG_PATH} excludes ${excluded.length} Module(s): ${excluded.join(",") || "none"}`
7826
8095
  );
7827
8096
  }
7828
8097
  function removeDir(absolute) {
7829
- if (!import_node_fs3.default.existsSync(absolute)) return false;
7830
- import_node_fs3.default.rmSync(absolute, { recursive: true, force: true });
8098
+ if (!import_node_fs4.default.existsSync(absolute)) return false;
8099
+ import_node_fs4.default.rmSync(absolute, { recursive: true, force: true });
7831
8100
  return true;
7832
8101
  }
7833
8102
  async function compose(options) {
@@ -7855,7 +8124,7 @@ async function compose(options) {
7855
8124
  });
7856
8125
  assertNoAppLayerImporters(options.cwd, preview.excludedModules);
7857
8126
  const violations = findModuleBoundaryViolations(
7858
- import_node_path3.default.join(options.cwd, MODULES_DIR2),
8127
+ import_node_path4.default.join(options.cwd, MODULES_DIR2),
7859
8128
  Object.fromEntries(preview.composedModules.map((name) => [name, preview.excludedModules])),
7860
8129
  { pruning: true }
7861
8130
  );
@@ -7876,11 +8145,11 @@ async function compose(options) {
7876
8145
  let pruned = 0;
7877
8146
  if (options.prune) {
7878
8147
  for (const name of result.excludedModules) {
7879
- if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
8148
+ if (removeDir(import_node_path4.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
7880
8149
  }
7881
8150
  for (const owner of result.composedModules) {
7882
8151
  for (const peer of result.excludedModules) {
7883
- if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
8152
+ if (removeDir(import_node_path4.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
7884
8153
  }
7885
8154
  }
7886
8155
  }
@@ -7902,13 +8171,18 @@ async function main() {
7902
8171
  enabled: { type: "string" },
7903
8172
  prune: { type: "boolean", default: false },
7904
8173
  cwd: { type: "string" },
7905
- "check-boundaries": { type: "boolean", default: false }
8174
+ "check-boundaries": { type: "boolean", default: false },
8175
+ "check-ownership": { type: "boolean", default: false }
7906
8176
  },
7907
8177
  strict: true
7908
8178
  });
8179
+ if (values["check-ownership"]) {
8180
+ await checkOwnership(values.cwd ?? process.cwd());
8181
+ return;
8182
+ }
7909
8183
  if (values["check-boundaries"]) {
7910
8184
  const violations = findOptionalPeerViolations(
7911
- import_node_path4.default.join(values.cwd ?? process.cwd(), MODULES_SANDBOX_DIR)
8185
+ import_node_path5.default.join(values.cwd ?? process.cwd(), MODULES_SANDBOX_DIR)
7912
8186
  );
7913
8187
  if (violations.length > 0) {
7914
8188
  throw new Error(
@@ -7933,6 +8207,36 @@ async function main() {
7933
8207
  `[compose] modules=${result.composedModules.join(",") || "none"} excluded=${result.excludedModules.join(",") || "none"} writes=${result.writes} deletes=${result.deletes} pruned=${result.pruned} gitignore=${result.gitignoreEntries ?? "kept"} stub paths`
7934
8208
  );
7935
8209
  }
8210
+ function gitAt(cwd) {
8211
+ return (args) => new Promise((resolve, reject) => {
8212
+ const child = (0, import_node_child_process3.spawn)("git", ["-c", "core.quotePath=false", ...args], {
8213
+ cwd,
8214
+ stdio: ["ignore", "pipe", "pipe"]
8215
+ });
8216
+ let stdout = "";
8217
+ child.stdout.setEncoding("utf8").on("data", (chunk) => stdout += chunk);
8218
+ child.on("error", reject);
8219
+ child.on("close", (status) => resolve({ status: status ?? 1, stdout }));
8220
+ });
8221
+ }
8222
+ async function checkOwnership(cwd) {
8223
+ const repoRoot = import_node_path5.default.resolve(cwd);
8224
+ const result = await checkStardeckOwnedFiles({ repoRoot, git: gitAt(repoRoot) });
8225
+ switch (result.status) {
8226
+ case "skipped":
8227
+ if (result.warning)
8228
+ console.warn(`[compose] --check-ownership: not checked: ${result.reason}`);
8229
+ else console.log(`[compose] --check-ownership: skipped: ${result.reason}`);
8230
+ return;
8231
+ case "clean":
8232
+ console.log(
8233
+ `[compose] --check-ownership: no Stardeck-owned file changed from Stardeck's version (${result.release.slice(0, 12)})`
8234
+ );
8235
+ return;
8236
+ default:
8237
+ throw new Error(`[compose] --check-ownership: ${formatOwnershipGuardFailure(result)}`);
8238
+ }
8239
+ }
7936
8240
  main().catch((error) => {
7937
8241
  console.error(error instanceof Error ? error.message : String(error));
7938
8242
  process.exit(1);