@stardeck-customer-apps/compose 0.14.1 → 0.15.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
  }
@@ -2414,16 +2426,16 @@ var moduleManifestSchema = import_zod.z.object({
2414
2426
  }
2415
2427
  }
2416
2428
  const seenWorkflows = /* @__PURE__ */ new Map();
2417
- const noteWorkflow = (name, path5) => {
2429
+ const noteWorkflow = (name, path6) => {
2418
2430
  const existing = seenWorkflows.get(name);
2419
2431
  if (existing != null) {
2420
2432
  ctx.addIssue({
2421
2433
  code: import_zod.z.ZodIssueCode.custom,
2422
2434
  message: `duplicate workflow name "${name}" (also at ${existing})`,
2423
- path: path5
2435
+ path: path6
2424
2436
  });
2425
2437
  } else {
2426
- seenWorkflows.set(name, path5.join("."));
2438
+ seenWorkflows.set(name, path6.join("."));
2427
2439
  }
2428
2440
  };
2429
2441
  for (let i = 0; i < manifest.workflows.length; i += 1) {
@@ -2435,7 +2447,7 @@ var moduleManifestSchema = import_zod.z.object({
2435
2447
  }
2436
2448
  }
2437
2449
  const seenEndpointKeys = /* @__PURE__ */ new Map();
2438
- const noteEndpoint = (claim, path5) => {
2450
+ const noteEndpoint = (claim, path6) => {
2439
2451
  let key;
2440
2452
  try {
2441
2453
  key = normalizeEndpointPathForCollision(claim.path);
@@ -2447,10 +2459,10 @@ var moduleManifestSchema = import_zod.z.object({
2447
2459
  ctx.addIssue({
2448
2460
  code: import_zod.z.ZodIssueCode.custom,
2449
2461
  message: `duplicate normalized endpoint path "${key}" (also at ${existing})`,
2450
- path: path5
2462
+ path: path6
2451
2463
  });
2452
2464
  } else {
2453
- seenEndpointKeys.set(key, path5.join("."));
2465
+ seenEndpointKeys.set(key, path6.join("."));
2454
2466
  }
2455
2467
  };
2456
2468
  for (let i = 0; i < manifest.endpoints.length; i += 1) {
@@ -2551,8 +2563,8 @@ var moduleManifestSchema = import_zod.z.object({
2551
2563
  }).transform(({ version: _legacyVersion, ...manifest }) => manifest);
2552
2564
  function formatZodErrors(error) {
2553
2565
  return error.issues.map((issue) => {
2554
- const path5 = issue.path.length > 0 ? issue.path.join(".") : "root";
2555
- return `${path5}: ${issue.message}`;
2566
+ const path6 = issue.path.length > 0 ? issue.path.join(".") : "root";
2567
+ return `${path6}: ${issue.message}`;
2556
2568
  });
2557
2569
  }
2558
2570
  function parseModuleManifest(raw) {
@@ -2952,16 +2964,16 @@ var collectedE2eFactsSchema = import_zod3.z.object({
2952
2964
  function validateRouteExportEvidenceCongruence(input) {
2953
2965
  const entrySet = new Set(input.routeEntries);
2954
2966
  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`);
2967
+ for (const path6 of exportPaths) {
2968
+ if (!entrySet.has(path6)) {
2969
+ input.onIssue(`routePageExports path "${path6}" is not listed in routeEntries`);
2958
2970
  }
2959
2971
  }
2960
2972
  if (input.routeCollectionErrors.length === 0) {
2961
2973
  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`);
2974
+ for (const path6 of input.routeEntries) {
2975
+ if (!exportSet.has(path6)) {
2976
+ input.onIssue(`routeEntries path "${path6}" is missing from routePageExports`);
2965
2977
  }
2966
2978
  }
2967
2979
  }
@@ -3906,6 +3918,50 @@ function analyzeClientSafeEntryImports(sourceText, fileName) {
3906
3918
  return analyzeClientSafeEntryImportsWithTs(import_typescript3.default, sourceText, fileName);
3907
3919
  }
3908
3920
 
3921
+ // ../../packages/lib/src/shared/ownership-record.ts
3922
+ var import_zod6 = require("zod");
3923
+ var OWNERSHIP_RECORD_PATH = "stardeck.ownership.json";
3924
+ var OWNERSHIP_RECORD_VERSION = 1;
3925
+ var moduleList = import_zod6.z.array(import_zod6.z.string().refine(isValidModuleName, "not a Module name"));
3926
+ var ownershipRecordSchema = import_zod6.z.object({
3927
+ version: import_zod6.z.literal(OWNERSHIP_RECORD_VERSION),
3928
+ /** False once the app stopped all Stardeck updates: then every file is the app's. */
3929
+ stardeckUpdates: import_zod6.z.boolean(),
3930
+ /** projects.ejected_modules: updates stopped for these Modules, so their folders are the app's. */
3931
+ updatesStoppedModules: moduleList,
3932
+ /** projects.ui_ejected_modules: Customize UI, so their screens are the app's. */
3933
+ customizedUiModules: moduleList,
3934
+ /** projects.external_modules: installed from another app, so the app owns the copy. */
3935
+ externalModules: moduleList,
3936
+ /** projects.removed_modules names: uninstalled on purpose. */
3937
+ uninstalledModules: moduleList
3938
+ });
3939
+ function parseOwnershipRecord(content) {
3940
+ let json;
3941
+ try {
3942
+ json = JSON.parse(content);
3943
+ } catch (error) {
3944
+ return { ok: false, error: `${OWNERSHIP_RECORD_PATH} is not valid JSON: ${String(error)}` };
3945
+ }
3946
+ const parsed = ownershipRecordSchema.safeParse(json);
3947
+ if (!parsed.success) {
3948
+ return {
3949
+ ok: false,
3950
+ error: `${OWNERSHIP_RECORD_PATH} is not a record Stardeck wrote: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ")}`
3951
+ };
3952
+ }
3953
+ return { ok: true, record: parsed.data };
3954
+ }
3955
+ var BLUEPRINT_RELEASE_COMMITTER_EMAIL = "ci@stardeck.ai";
3956
+ var BLUEPRINT_RELEASE_SUBJECT_PREFIX = "publish: ";
3957
+ var STARDECK_OWNED_ALTERNATIVES = [
3958
+ "If the Module has a setting for this, change the setting.",
3959
+ "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).",
3960
+ "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.",
3961
+ "If the Module's logic is wrong for every app, report it to Stardeck so every app gets the fix.",
3962
+ "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."
3963
+ ];
3964
+
3909
3965
  // ../../packages/lib/src/shared/platform-owned-paths.ts
3910
3966
  var APP_WEB_PREFIX = "apps/web/";
3911
3967
  var BLUEPRINT_LINT_MARKER = `${APP_WEB_PREFIX}eslint-rules/module-boundaries.mjs`;
@@ -3919,20 +3975,217 @@ var MODULE_INIT_SERVER_GEN_PATH = `${APP_WEB_PREFIX}src/module-init.server.gen.t
3919
3975
  var MODULE_SERVER_GEN_PATH = `${APP_WEB_PREFIX}src/module-server.gen.ts`;
3920
3976
  var MODULES_GEN_PATH = `${APP_WEB_PREFIX}src/modules.gen.ts`;
3921
3977
  var APP_EXTENSIONS_PATH = `${APP_WEB_PREFIX}src/lib/modules/app-extensions.ts`;
3978
+ var APP_OWNED_PATHS = [APP_EXTENSIONS_PATH];
3979
+ function platformOwnedSet(input) {
3980
+ const externalModules = input.externalModules ?? [];
3981
+ const removedModules = input.removedModules ?? [];
3982
+ const moduleNames = [
3983
+ ...input.moduleNames,
3984
+ ...input.ejectedModules,
3985
+ ...input.uiEjectedModules,
3986
+ ...externalModules,
3987
+ ...removedModules
3988
+ ];
3989
+ for (const moduleName of moduleNames) {
3990
+ if (!isValidModuleName(moduleName)) {
3991
+ throw new Error(`Invalid Module name: ${moduleName}`);
3992
+ }
3993
+ }
3994
+ const appOwned = /* @__PURE__ */ new Set([...input.ejectedModules, ...externalModules]);
3995
+ const activeModules = input.moduleNames.filter((name) => !appOwned.has(name));
3996
+ const uiEjectedModules = new Set(input.uiEjectedModules);
3997
+ return {
3998
+ prefixes: [
3999
+ ...activeModules.map((name) => `${APP_WEB_PREFIX}src/modules/${name}/`),
4000
+ `${APP_WEB_PREFIX}src/lib/modules/`
4001
+ ].sort(),
4002
+ // Build outputs, not source: compose regenerates them from Module source on
4003
+ // every dev/build/lint/typecheck/test run and git ignores them, so there is
4004
+ // nothing for the write refusal or the Blueprint updater to protect.
4005
+ files: [],
4006
+ // Detach dominates: a detached Module has no prefix, so it needs no carve-out.
4007
+ carveOuts: activeModules.filter((name) => uiEjectedModules.has(name)).flatMap(
4008
+ (name) => MODULE_UI_SUBPATHS.map((subpath) => `${APP_WEB_PREFIX}src/modules/${name}/${subpath}`)
4009
+ ).sort(),
4010
+ externalModules: [...externalModules].sort(),
4011
+ removedModules: [...removedModules].sort()
4012
+ };
4013
+ }
4014
+ function appOwnedExcludePathspecs(set) {
4015
+ return [
4016
+ ...APP_OWNED_PATHS.map((file) => `:(exclude)${file}`),
4017
+ ...set.carveOuts.map((carveOut) => `:(glob,exclude)${carveOut}**`)
4018
+ ];
4019
+ }
4020
+ function platformOwnedDiffPathspecs(set) {
4021
+ return [...set.prefixes, ...set.files, ...appOwnedExcludePathspecs(set)];
4022
+ }
4023
+
4024
+ // ../../packages/lib/src/server/module-rail/ownership-guard.ts
4025
+ var STRICT_RECORD = {
4026
+ version: 1,
4027
+ stardeckUpdates: true,
4028
+ updatesStoppedModules: [],
4029
+ customizedUiModules: [],
4030
+ externalModules: [],
4031
+ uninstalledModules: []
4032
+ };
4033
+ var MANIFEST_PATH = new RegExp(
4034
+ `^${MODULES_SANDBOX_DIR.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/([^/]+)/${MODULE_MANIFEST_FILENAME.replace(".", "\\.")}$`
4035
+ );
4036
+ function realpath(dir) {
4037
+ try {
4038
+ return import_node_fs.default.realpathSync(dir);
4039
+ } catch {
4040
+ return import_node_path.default.resolve(dir);
4041
+ }
4042
+ }
4043
+ function zList(stdout) {
4044
+ return stdout.split("\0").filter(Boolean);
4045
+ }
4046
+ async function findRelease(git) {
4047
+ const found = await git([
4048
+ "rev-list",
4049
+ "-n1",
4050
+ "--topo-order",
4051
+ `--committer=${BLUEPRINT_RELEASE_COMMITTER_EMAIL.replace(/\./g, "\\.")}`,
4052
+ `--grep=^${BLUEPRINT_RELEASE_SUBJECT_PREFIX}`,
4053
+ "HEAD"
4054
+ ]);
4055
+ const sha = found.status === 0 ? found.stdout.trim() : "";
4056
+ return sha || null;
4057
+ }
4058
+ async function noReleaseReason(git) {
4059
+ const shallow = await git(["rev-parse", "--is-shallow-repository"]);
4060
+ if (shallow.status === 0 && shallow.stdout.trim() === "true") {
4061
+ return {
4062
+ status: "skipped",
4063
+ warning: true,
4064
+ 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."
4065
+ };
4066
+ }
4067
+ return {
4068
+ status: "skipped",
4069
+ warning: false,
4070
+ reason: "no Stardeck release in this repo's history, so it holds no Stardeck-owned files"
4071
+ };
4072
+ }
4073
+ function readRecord(repoRoot) {
4074
+ let content;
4075
+ try {
4076
+ content = import_node_fs.default.readFileSync(import_node_path.default.join(repoRoot, OWNERSHIP_RECORD_PATH), "utf8");
4077
+ } catch (error) {
4078
+ if (error.code === "ENOENT") {
4079
+ return { ok: true, record: STRICT_RECORD, found: false };
4080
+ }
4081
+ throw error;
4082
+ }
4083
+ const parsed = parseOwnershipRecord(content);
4084
+ return parsed.ok ? { ok: true, record: parsed.record, found: true } : parsed;
4085
+ }
4086
+ async function releaseModules(git, release, record) {
4087
+ const listed = await git([
4088
+ "ls-tree",
4089
+ "-r",
4090
+ "-z",
4091
+ "--name-only",
4092
+ release,
4093
+ "--",
4094
+ MODULES_SANDBOX_DIR
4095
+ ]);
4096
+ if (listed.status !== 0) throw new Error(`git ls-tree ${release} failed`);
4097
+ const uninstalled = new Set(record.uninstalledModules);
4098
+ return zList(listed.stdout).map((file) => MANIFEST_PATH.exec(file)?.[1]).filter((name) => !!name && !uninstalled.has(name));
4099
+ }
4100
+ async function checkStardeckOwnedFiles(input) {
4101
+ const { git, repoRoot } = input;
4102
+ const top = await git(["rev-parse", "--show-toplevel"]);
4103
+ if (top.status !== 0) {
4104
+ return { status: "skipped", warning: false, reason: "not a git checkout" };
4105
+ }
4106
+ if (realpath(top.stdout.trim()) !== realpath(repoRoot)) {
4107
+ return {
4108
+ status: "skipped",
4109
+ warning: false,
4110
+ reason: `${repoRoot} is not the root of its git repo, so it is not an app's own repo`
4111
+ };
4112
+ }
4113
+ const release = await findRelease(git);
4114
+ if (!release) return noReleaseReason(git);
4115
+ const read = readRecord(repoRoot);
4116
+ if (!read.ok) return { status: "invalid-record", error: read.error };
4117
+ const { record } = read;
4118
+ if (!record.stardeckUpdates) {
4119
+ return {
4120
+ status: "skipped",
4121
+ warning: false,
4122
+ reason: "this app stopped all Stardeck updates, so every file is the app's"
4123
+ };
4124
+ }
4125
+ const moduleNames = await releaseModules(git, release, record);
4126
+ const set = platformOwnedSet({
4127
+ moduleNames,
4128
+ ejectedModules: record.updatesStoppedModules,
4129
+ uiEjectedModules: record.customizedUiModules,
4130
+ externalModules: record.externalModules,
4131
+ removedModules: record.uninstalledModules
4132
+ });
4133
+ const pathspecs = [
4134
+ ...platformOwnedDiffPathspecs(set),
4135
+ ...record.uninstalledModules.map(
4136
+ (name) => `:(glob,exclude)${MODULES_SANDBOX_DIR}/*/enhancements/${name}/**`
4137
+ )
4138
+ ];
4139
+ const [changed, added] = await Promise.all([
4140
+ git([
4141
+ "diff",
4142
+ "--name-only",
4143
+ "-z",
4144
+ "--no-renames",
4145
+ "--no-ext-diff",
4146
+ release,
4147
+ "--",
4148
+ ...pathspecs
4149
+ ]),
4150
+ git(["ls-files", "-z", "--others", "--exclude-standard", "--", ...pathspecs])
4151
+ ]);
4152
+ if (changed.status !== 0 || added.status !== 0) {
4153
+ throw new Error(`git could not compare the checkout with Stardeck's version ${release}`);
4154
+ }
4155
+ const paths = [.../* @__PURE__ */ new Set([...zList(changed.stdout), ...zList(added.stdout)])].sort();
4156
+ console.log(
4157
+ `[OwnershipGuard] release=${release.slice(0, 12)} record=${read.found ? "found" : "missing"} modules=${moduleNames.length} carveOuts=${set.carveOuts.length} changed=${paths.length}`
4158
+ );
4159
+ return paths.length > 0 ? { status: "changed", release, paths } : { status: "clean", release };
4160
+ }
4161
+ function formatOwnershipGuardFailure(result) {
4162
+ if (result.status === "invalid-record") {
4163
+ return `${result.error}
4164
+ Restore it with \`git checkout origin/HEAD -- ${OWNERSHIP_RECORD_PATH}\`. Stardeck writes this file; ownership changes in the dashboard's Modules tab.`;
4165
+ }
4166
+ const count = `${result.paths.length} Stardeck-owned ${result.paths.length === 1 ? "file" : "files"}`;
4167
+ return [
4168
+ `${count} changed from Stardeck's version (${result.release.slice(0, 12)}). The next Stardeck update replaces them, so these changes would be lost:`,
4169
+ ...result.paths.map((file) => ` - ${file}`),
4170
+ "",
4171
+ `Undo them (\`git checkout ${result.release.slice(0, 12)} -- <path>\`, or delete a new file) and do one of these instead:`,
4172
+ ...STARDECK_OWNED_ALTERNATIVES.map((line) => ` - ${line.replace(/\*\*/g, "")}`)
4173
+ ].join("\n");
4174
+ }
3922
4175
 
3923
4176
  // src/cli.ts
3924
4177
  var import_typescript7 = __toESM(require("typescript"));
3925
- var import_node_path4 = __toESM(require("path"));
4178
+ var import_node_path5 = __toESM(require("path"));
3926
4179
 
3927
4180
  // src/index.ts
3928
4181
  var import_node_child_process2 = require("child_process");
3929
- var import_node_fs3 = __toESM(require("fs"));
3930
- var import_node_path3 = __toESM(require("path"));
4182
+ var import_node_fs4 = __toESM(require("fs"));
4183
+ var import_node_path4 = __toESM(require("path"));
3931
4184
 
3932
4185
  // ../../packages/lib/src/server/module-rail/local-fs-sandbox.ts
3933
4186
  var import_node_child_process = require("child_process");
3934
- var import_node_fs = __toESM(require("fs"));
3935
- var import_node_path = __toESM(require("path"));
4187
+ var import_node_fs2 = __toESM(require("fs"));
4188
+ var import_node_path2 = __toESM(require("path"));
3936
4189
  function localFsSandbox(root) {
3937
4190
  return {
3938
4191
  async exec(command, options) {
@@ -3956,13 +4209,13 @@ ${result.error}`);
3956
4209
  },
3957
4210
  async fileExists(relative) {
3958
4211
  try {
3959
- return import_node_fs.default.statSync(import_node_path.default.join(root, relative)).isFile();
4212
+ return import_node_fs2.default.statSync(import_node_path2.default.join(root, relative)).isFile();
3960
4213
  } catch {
3961
4214
  return false;
3962
4215
  }
3963
4216
  },
3964
4217
  async readFile(relative) {
3965
- return import_node_fs.default.readFileSync(import_node_path.default.join(root, relative), "utf8");
4218
+ return import_node_fs2.default.readFileSync(import_node_path2.default.join(root, relative), "utf8");
3966
4219
  },
3967
4220
  async fetchRemoteRef() {
3968
4221
  throw new Error("[ModuleRail] local filesystem sandbox cannot reach the network");
@@ -3995,28 +4248,28 @@ function shellQuote(value) {
3995
4248
  }
3996
4249
 
3997
4250
  // ../../packages/lib/src/shared/compose-inputs.ts
3998
- var import_zod6 = require("zod");
4251
+ var import_zod7 = require("zod");
3999
4252
  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),
4253
+ var composeInputsSchema = import_zod7.z.strictObject({
4254
+ connectedStores: import_zod7.z.array(
4255
+ import_zod7.z.strictObject({
4256
+ storeId: import_zod7.z.string().min(1),
4257
+ slug: import_zod7.z.string().min(1),
4005
4258
  // A row with an empty binding key must not fail a deploy: the rail
4006
4259
  // 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"]),
4260
+ bindingKey: import_zod7.z.string().nullish().transform((value) => value || null),
4261
+ accessLevel: import_zod7.z.enum(["read", "write", "admin"]),
4009
4262
  // Defaulted so a file written before the platform emitted store types
4010
4263
  // (database rows only) still parses. The platform starts writing
4011
4264
  // storage rows, with `type` set, once forks run a compose that reads it.
4012
- type: import_zod6.z.enum(["database", "storage"]).default("database")
4265
+ type: import_zod7.z.enum(["database", "storage"]).default("database")
4013
4266
  })
4014
4267
  ).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()
4268
+ installs: import_zod7.z.record(
4269
+ import_zod7.z.string(),
4270
+ import_zod7.z.strictObject({
4271
+ installedVersion: import_zod7.z.string().min(1),
4272
+ datastoreBindings: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.string()).nullish()
4020
4273
  })
4021
4274
  ).default({})
4022
4275
  });
@@ -4176,13 +4429,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
4176
4429
  function createMessageTree() {
4177
4430
  return /* @__PURE__ */ Object.create(null);
4178
4431
  }
4179
- function assertMessageTree(value, path5) {
4432
+ function assertMessageTree(value, path6) {
4180
4433
  if (typeof value === "string") return;
4181
4434
  if (value === null || Array.isArray(value) || typeof value !== "object") {
4182
- throw new Error(`Invalid message at ${path5}: expected string or nested object`);
4435
+ throw new Error(`Invalid message at ${path6}: expected string or nested object`);
4183
4436
  }
4184
4437
  for (const [key, child] of Object.entries(value)) {
4185
- assertMessageTree(child, `${path5}.${key}`);
4438
+ assertMessageTree(child, `${path6}.${key}`);
4186
4439
  }
4187
4440
  }
4188
4441
  function cloneTree(value) {
@@ -4192,17 +4445,17 @@ function cloneTree(value) {
4192
4445
  }
4193
4446
  return clone;
4194
4447
  }
4195
- function collectLeafPaths(value, path5, out) {
4448
+ function collectLeafPaths(value, path6, out) {
4196
4449
  if (typeof value === "string") {
4197
- out.push(path5);
4450
+ out.push(path6);
4198
4451
  return;
4199
4452
  }
4200
- assertMessageTree(value, path5);
4201
- for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path5}.${key}`, out);
4453
+ assertMessageTree(value, path6);
4454
+ for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path6}.${key}`, out);
4202
4455
  }
4203
- function overrideInto(target, source, owner, path5, missing) {
4456
+ function overrideInto(target, source, owner, path6, missing) {
4204
4457
  for (const [key, value] of Object.entries(source)) {
4205
- const nextPath = path5 ? `${path5}.${key}` : key;
4458
+ const nextPath = path6 ? `${path6}.${key}` : key;
4206
4459
  if (!Object.prototype.hasOwnProperty.call(target, key)) {
4207
4460
  collectLeafPaths(value, nextPath, missing);
4208
4461
  continue;
@@ -4229,9 +4482,9 @@ function overrideInto(target, source, owner, path5, missing) {
4229
4482
  function composeLocaleMessagesForPlan(layers) {
4230
4483
  const result = createMessageTree();
4231
4484
  const leafOwners = /* @__PURE__ */ new Map();
4232
- function mergeInto(target, source, owner, path5) {
4485
+ function mergeInto(target, source, owner, path6) {
4233
4486
  for (const [key, value] of Object.entries(source)) {
4234
- const nextPath = path5 ? `${path5}.${key}` : key;
4487
+ const nextPath = path6 ? `${path6}.${key}` : key;
4235
4488
  const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
4236
4489
  const existing = hasExisting ? target[key] : void 0;
4237
4490
  if (typeof value === "string") {
@@ -4792,8 +5045,8 @@ var METHOD_ORDER = {
4792
5045
  function compareNames2(a, b) {
4793
5046
  return a < b ? -1 : a > b ? 1 : 0;
4794
5047
  }
4795
- function endpointPairKey(method, path5) {
4796
- return `${method} ${path5}`;
5048
+ function endpointPairKey(method, path6) {
5049
+ return `${method} ${path6}`;
4797
5050
  }
4798
5051
  function apiPathToRouteFile(apiPath) {
4799
5052
  if (!apiPath.startsWith("/api/")) {
@@ -4807,11 +5060,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
4807
5060
  }
4808
5061
  var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
4809
5062
  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);
5063
+ function appRouteFileToNextRelativePath(path6) {
5064
+ const pagePath = appPageFileToNextRelativePath(path6, APP_PACKAGE_DIR);
4812
5065
  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);
5066
+ if (!path6.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
5067
+ const relative = path6.slice(APP_ROUTE_FILE_PREFIX.length);
4815
5068
  if (!relative.startsWith("api/")) return null;
4816
5069
  const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
4817
5070
  if (!suffix) return null;
@@ -4906,9 +5159,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
4906
5159
  const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
4907
5160
  return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
4908
5161
  }
4909
- function parseLegacyApiAdapter(path5, source) {
4910
- const physicalPath = appRouteFileToNextRelativePath(path5);
4911
- if (expectedMarkerKindForPath(path5) !== "endpoint" || physicalPath == null) return null;
5162
+ function parseLegacyApiAdapter(path6, source) {
5163
+ const physicalPath = appRouteFileToNextRelativePath(path6);
5164
+ if (expectedMarkerKindForPath(path6) !== "endpoint" || physicalPath == null) return null;
4912
5165
  const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
4913
5166
  const match = firstLine.match(
4914
5167
  new RegExp(
@@ -4918,7 +5171,7 @@ function parseLegacyApiAdapter(path5, source) {
4918
5171
  if (!match || match[2] !== physicalPath) return null;
4919
5172
  const owner = match[1];
4920
5173
  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);
5174
+ const file = import_typescript4.default.createSourceFile(path6, source, import_typescript4.default.ScriptTarget.Latest, false, import_typescript4.default.ScriptKind.TS);
4922
5175
  if (file.parseDiagnostics.length > 0) {
4923
5176
  return null;
4924
5177
  }
@@ -4960,9 +5213,9 @@ function parseLegacyApiAdapter(path5, source) {
4960
5213
  }
4961
5214
  return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
4962
5215
  }
4963
- function parseLegacyRouteAdapter(path5, source) {
4964
- const physicalPath = appRouteFileToNextRelativePath(path5);
4965
- if (expectedMarkerKindForPath(path5) !== "route" || physicalPath == null) return null;
5216
+ function parseLegacyRouteAdapter(path6, source) {
5217
+ const physicalPath = appRouteFileToNextRelativePath(path6);
5218
+ if (expectedMarkerKindForPath(path6) !== "route" || physicalPath == null) return null;
4966
5219
  const normalized = source.replace(/\r\n/g, "\n");
4967
5220
  const match = normalized.match(
4968
5221
  new RegExp(
@@ -4974,30 +5227,30 @@ function parseLegacyRouteAdapter(path5, source) {
4974
5227
  const nextRelativePath = match[2];
4975
5228
  return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
4976
5229
  }
4977
- function legacyRouteAdapterMatchesStub(path5, source, stub) {
4978
- const legacy = parseLegacyRouteAdapter(path5, source);
5230
+ function legacyRouteAdapterMatchesStub(path6, source, stub) {
5231
+ const legacy = parseLegacyRouteAdapter(path6, source);
4979
5232
  return legacy != null && legacy.owner === stub.owner;
4980
5233
  }
4981
5234
  var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
4982
5235
  var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
4983
5236
  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))) {
5237
+ function expectedMarkerKindForPath(path6) {
5238
+ if (path6 === MODULES_GEN_PATH) return "registry";
5239
+ if (path6 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
5240
+ if (path6 === MODULE_DATASTORES_GEN_PATH) return "datastores";
5241
+ if (path6 === MODULE_I18N_GEN_PATH) return "i18n";
5242
+ if (path6 === MODULE_INIT_SERVER_GEN_PATH) return "init";
5243
+ if (path6 === MODULE_SERVER_GEN_PATH) return "server";
5244
+ if (path6.startsWith(API_ROUTE_DIR_PREFIX) && path6.endsWith("/route.ts")) return "endpoint";
5245
+ if (path6.startsWith(APP_DIR_PREFIX) && !path6.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path6.endsWith(suffix))) {
4993
5246
  return "route";
4994
5247
  }
4995
5248
  return null;
4996
5249
  }
4997
- function markerCongruentWithPath(path5, marker) {
4998
- if (expectedMarkerKindForPath(path5) !== marker.kind) return false;
5250
+ function markerCongruentWithPath(path6, marker) {
5251
+ if (expectedMarkerKindForPath(path6) !== marker.kind) return false;
4999
5252
  if (marker.kind === "route") {
5000
- const derived = appPageFileToNextRelativePath(path5, APP_PACKAGE_DIR);
5253
+ const derived = appPageFileToNextRelativePath(path6, APP_PACKAGE_DIR);
5001
5254
  return derived != null && derived === marker.nextRelativePath;
5002
5255
  }
5003
5256
  return true;
@@ -5273,8 +5526,8 @@ function buildGeneratedModuleRegistry(input) {
5273
5526
  function activeEnhancementPeers(registryModule) {
5274
5527
  return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
5275
5528
  }
5276
- function endpointBucketKey(path5, owner, scope) {
5277
- return `${path5}\0${owner}\0${scopeLabel(scope)}`;
5529
+ function endpointBucketKey(path6, owner, scope) {
5530
+ return `${path6}\0${owner}\0${scopeLabel(scope)}`;
5278
5531
  }
5279
5532
  function planRouteStubs(input) {
5280
5533
  const endpointBuckets = /* @__PURE__ */ new Map();
@@ -5398,7 +5651,7 @@ function planRouteStubs(input) {
5398
5651
  for (const entry of entries) {
5399
5652
  const nextRelativePath = entry.nextRelativePath;
5400
5653
  const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
5401
- const path5 = routeEntryToAppPageFile(nextRelativePath);
5654
+ const path6 = routeEntryToAppPageFile(nextRelativePath);
5402
5655
  const existing = routeDestinationOwners.get(destination);
5403
5656
  if (existing) {
5404
5657
  throw new Error(
@@ -5408,13 +5661,13 @@ function planRouteStubs(input) {
5408
5661
  routeDestinationOwners.set(destination, {
5409
5662
  owner: regMod.name,
5410
5663
  nextRelativePath,
5411
- path: path5
5664
+ path: path6
5412
5665
  });
5413
5666
  const importModule = routeEntryImport(regMod.name, nextRelativePath);
5414
5667
  const analyzed = analyzeRouteEntryPath(nextRelativePath);
5415
5668
  const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
5416
5669
  routeStubs.push({
5417
- path: path5,
5670
+ path: path6,
5418
5671
  owner: regMod.name,
5419
5672
  nextRelativePath,
5420
5673
  importModule,
@@ -5475,16 +5728,16 @@ function planCompositionArtifacts(input) {
5475
5728
  ...stubs.routeStubs.map((stub) => stub.path)
5476
5729
  ]);
5477
5730
  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);
5731
+ const isProvenStaleDeletionCandidate = (path6, content) => {
5732
+ if (path6 === MODULES_GEN_PATH) return false;
5733
+ if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path6)) return false;
5734
+ if (desiredPaths.has(path6)) return false;
5735
+ if (!existingGeneratedPathSet.has(path6)) return false;
5736
+ const legacy = parseLegacyApiAdapter(path6, content);
5484
5737
  if (legacy) return true;
5485
- if (parseLegacyRouteAdapter(path5, content)) return true;
5738
+ if (parseLegacyRouteAdapter(path6, content)) return true;
5486
5739
  const marker = parseCompositionArtifactMarker(content);
5487
- return marker != null && markerCongruentWithPath(path5, marker);
5740
+ return marker != null && markerCongruentWithPath(path6, marker);
5488
5741
  };
5489
5742
  const plannedByDestination = /* @__PURE__ */ new Map();
5490
5743
  for (const stub of stubs.routeStubs) {
@@ -5638,13 +5891,13 @@ function planCompositionArtifacts(input) {
5638
5891
  for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
5639
5892
  const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
5640
5893
  const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
5641
- for (const [path5] of desired) {
5642
- const existing = input.existingFiles[path5];
5894
+ for (const [path6] of desired) {
5895
+ const existing = input.existingFiles[path6];
5643
5896
  if (existing == null) continue;
5644
5897
  let marker = parseCompositionArtifactMarker(existing);
5645
- const plannedEndpoint = endpointStubByPath.get(path5);
5646
- const plannedRoute = routeStubByPath.get(path5);
5647
- const legacy = parseLegacyApiAdapter(path5, existing);
5898
+ const plannedEndpoint = endpointStubByPath.get(path6);
5899
+ const plannedRoute = routeStubByPath.get(path6);
5900
+ const legacy = parseLegacyApiAdapter(path6, existing);
5648
5901
  if (legacy && plannedEndpoint) {
5649
5902
  marker = {
5650
5903
  kind: "endpoint",
@@ -5652,64 +5905,64 @@ function planCompositionArtifacts(input) {
5652
5905
  scope: plannedEndpoint.scope
5653
5906
  };
5654
5907
  }
5655
- if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path5, existing, plannedRoute)) {
5908
+ if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path6, existing, plannedRoute)) {
5656
5909
  marker = {
5657
5910
  kind: "route",
5658
5911
  owner: plannedRoute.owner,
5659
5912
  nextRelativePath: plannedRoute.nextRelativePath
5660
5913
  };
5661
5914
  }
5662
- const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path5);
5915
+ const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path6);
5663
5916
  if (!marker) {
5664
5917
  return {
5665
5918
  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
5919
+ 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}`,
5920
+ path: path6
5668
5921
  };
5669
5922
  }
5670
- const expectedKind = expectedMarkerKindForPath(path5);
5923
+ const expectedKind = expectedMarkerKindForPath(path6);
5671
5924
  if (marker.kind !== expectedKind) {
5672
5925
  return {
5673
5926
  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
5927
+ 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)`,
5928
+ path: path6
5676
5929
  };
5677
5930
  }
5678
5931
  if (marker.kind === "endpoint") {
5679
- const stub = endpointStubByPath.get(path5);
5932
+ const stub = endpointStubByPath.get(path6);
5680
5933
  if (!endpointMarkerOwnsStub(marker, stub)) {
5681
5934
  return {
5682
5935
  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,
5936
+ 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) : "?"})`,
5937
+ path: path6,
5685
5938
  owner: stub?.owner
5686
5939
  };
5687
5940
  }
5688
5941
  } else if (marker.kind === "route") {
5689
- const stub = routeStubByPath.get(path5);
5942
+ const stub = routeStubByPath.get(path6);
5690
5943
  const ownerMatches = stub != null && marker.owner === stub.owner;
5691
5944
  const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
5692
5945
  if (!ownerMatches || !pathMatches) {
5693
5946
  return {
5694
5947
  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,
5948
+ 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 ?? "?"})`,
5949
+ path: path6,
5697
5950
  owner: stub?.owner
5698
5951
  };
5699
5952
  }
5700
5953
  }
5701
5954
  }
5702
5955
  const deletes = [];
5703
- for (const path5 of input.existingGeneratedPaths) {
5704
- const existing = input.existingFiles[path5];
5956
+ for (const path6 of input.existingGeneratedPaths) {
5957
+ const existing = input.existingFiles[path6];
5705
5958
  if (existing == null) continue;
5706
- if (!isProvenStaleDeletionCandidate(path5, existing)) {
5959
+ if (!isProvenStaleDeletionCandidate(path6, existing)) {
5707
5960
  continue;
5708
5961
  }
5709
- deletes.push(path5);
5962
+ deletes.push(path6);
5710
5963
  }
5711
5964
  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));
5965
+ 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
5966
  return {
5714
5967
  ok: true,
5715
5968
  plan: {
@@ -5980,14 +6233,14 @@ function rejectPseudoModuleEntries(entries, parentDir, label) {
5980
6233
  }
5981
6234
 
5982
6235
  // ../../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")
6236
+ var import_zod8 = require("zod");
6237
+ var creationSourceBlankSchema = import_zod8.z.object({
6238
+ type: import_zod8.z.literal("blank")
5986
6239
  });
5987
- var creationSourceBlueprintSchema = import_zod7.z.object({
5988
- type: import_zod7.z.literal("blueprint"),
6240
+ var creationSourceBlueprintSchema = import_zod8.z.object({
6241
+ type: import_zod8.z.literal("blueprint"),
5989
6242
  /** Internal template/fork project id — keep template naming in identifiers. */
5990
- blueprintProjectId: import_zod7.z.string().uuid(),
6243
+ blueprintProjectId: import_zod8.z.string().uuid(),
5991
6244
  /**
5992
6245
  * `null`/absent means every feature Module: the platform stores that as the
5993
6246
  * explicit list of feature Modules in the published Blueprint version the fork
@@ -5995,192 +6248,197 @@ var creationSourceBlueprintSchema = import_zod7.z.object({
5995
6248
  * a custom app: no feature Module, Library Modules only (`resolvePlatformEnv`
5996
6249
  * emits it as a set value naming no Module).
5997
6250
  */
5998
- enabledModules: import_zod7.z.array(moduleNameSchema).max(100).nullable().optional()
6251
+ enabledModules: import_zod8.z.array(moduleNameSchema).max(100).nullable().optional(),
6252
+ /**
6253
+ * The kind of business (a preset id the published version defines). Its Modules are built
6254
+ * and `enabledModules` adds extras on top; for the general kind `enabledModules` is the pick.
6255
+ */
6256
+ businessKind: import_zod8.z.string().regex(/^[a-z][a-z0-9-]{0,39}$/).optional()
5999
6257
  });
6000
- var creationSourceSchema = import_zod7.z.discriminatedUnion("type", [
6258
+ var creationSourceSchema = import_zod8.z.discriminatedUnion("type", [
6001
6259
  creationSourceBlankSchema,
6002
6260
  creationSourceBlueprintSchema
6003
6261
  ]);
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()
6262
+ var moduleInstallHandoffSchema = import_zod8.z.object({
6263
+ moduleName: import_zod8.z.string().min(1),
6264
+ homeProjectId: import_zod8.z.string().uuid(),
6265
+ installedVersion: import_zod8.z.string().min(1),
6266
+ baselineSha: import_zod8.z.string().min(1),
6267
+ mounts: import_zod8.z.array(
6268
+ import_zod8.z.object({
6269
+ group: import_zod8.z.string(),
6270
+ segment: import_zod8.z.string()
6013
6271
  })
6014
6272
  ),
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)
6273
+ integration: import_zod8.z.array(import_zod8.z.string()),
6274
+ workflows: import_zod8.z.array(import_zod8.z.string()),
6275
+ prunedEnhancements: import_zod8.z.array(import_zod8.z.string()),
6276
+ restoredEnhancements: import_zod8.z.array(import_zod8.z.string()).optional(),
6277
+ permissions: import_zod8.z.array(
6278
+ import_zod8.z.object({
6279
+ key: import_zod8.z.string().min(1),
6280
+ description: import_zod8.z.string().min(1)
6023
6281
  })
6024
6282
  ).optional()
6025
6283
  });
6026
6284
 
6027
6285
  // ../../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(),
6286
+ var import_zod9 = require("zod");
6287
+ var columnSpecSchema = import_zod9.z.object({
6288
+ name: import_zod9.z.string().min(1).max(63),
6289
+ pgType: import_zod9.z.string().min(1),
6290
+ nullable: import_zod9.z.boolean(),
6033
6291
  /**
6034
6292
  * A SQL expression used as the column default, as a raw string (e.g. `'t'`,
6035
6293
  * `0`, `now()`, `gen_random_uuid()`). `null` or omitted means no default.
6036
6294
  * NOT a JS literal — must already be a valid SQL expression.
6037
6295
  */
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()
6296
+ default: import_zod9.z.string().nullable().optional(),
6297
+ fieldType: import_zod9.z.string().optional(),
6298
+ fieldConfig: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.unknown()).optional()
6041
6299
  });
6042
- var referentialActionSchema = import_zod8.z.enum([
6300
+ var referentialActionSchema = import_zod9.z.enum([
6043
6301
  "cascade",
6044
6302
  "set null",
6045
6303
  "set default",
6046
6304
  "restrict",
6047
6305
  "no action"
6048
6306
  ]);
6049
- var schemaOpSchema = import_zod8.z.discriminatedUnion("type", [
6307
+ var schemaOpSchema = import_zod9.z.discriminatedUnion("type", [
6050
6308
  // 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()
6309
+ import_zod9.z.object({
6310
+ type: import_zod9.z.literal("createTable"),
6311
+ table: import_zod9.z.string().min(1).max(63),
6312
+ columns: import_zod9.z.array(columnSpecSchema).min(1),
6313
+ primaryKey: import_zod9.z.array(import_zod9.z.string()).optional(),
6314
+ ifNotExists: import_zod9.z.boolean().optional()
6057
6315
  }),
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()
6316
+ import_zod9.z.object({
6317
+ type: import_zod9.z.literal("dropTable"),
6318
+ table: import_zod9.z.string().min(1).max(63),
6319
+ cascade: import_zod9.z.boolean().optional(),
6320
+ ifExists: import_zod9.z.boolean().optional()
6063
6321
  }),
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)
6322
+ import_zod9.z.object({
6323
+ type: import_zod9.z.literal("renameTable"),
6324
+ from: import_zod9.z.string().min(1).max(63),
6325
+ to: import_zod9.z.string().min(1).max(63)
6068
6326
  }),
6069
6327
  // Columns
6070
- import_zod8.z.object({
6071
- type: import_zod8.z.literal("addColumn"),
6072
- table: import_zod8.z.string().min(1).max(63),
6328
+ import_zod9.z.object({
6329
+ type: import_zod9.z.literal("addColumn"),
6330
+ table: import_zod9.z.string().min(1).max(63),
6073
6331
  column: columnSpecSchema,
6074
- ifNotExists: import_zod8.z.boolean().optional()
6332
+ ifNotExists: import_zod9.z.boolean().optional()
6075
6333
  }),
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()
6334
+ import_zod9.z.object({
6335
+ type: import_zod9.z.literal("dropColumn"),
6336
+ table: import_zod9.z.string().min(1).max(63),
6337
+ column: import_zod9.z.string().min(1).max(63),
6338
+ cascade: import_zod9.z.boolean().optional(),
6339
+ ifExists: import_zod9.z.boolean().optional()
6082
6340
  }),
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)
6341
+ import_zod9.z.object({
6342
+ type: import_zod9.z.literal("renameColumn"),
6343
+ table: import_zod9.z.string().min(1).max(63),
6344
+ from: import_zod9.z.string().min(1).max(63),
6345
+ to: import_zod9.z.string().min(1).max(63)
6088
6346
  }),
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),
6347
+ import_zod9.z.object({
6348
+ type: import_zod9.z.literal("alterColumnType"),
6349
+ table: import_zod9.z.string().min(1).max(63),
6350
+ column: import_zod9.z.string().min(1).max(63),
6351
+ newType: import_zod9.z.string().min(1),
6094
6352
  /** Optional USING expression for potentially-lossy casts */
6095
- using: import_zod8.z.string().optional()
6353
+ using: import_zod9.z.string().optional()
6096
6354
  }),
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()
6355
+ import_zod9.z.object({
6356
+ type: import_zod9.z.literal("alterColumnNullable"),
6357
+ table: import_zod9.z.string().min(1).max(63),
6358
+ column: import_zod9.z.string().min(1).max(63),
6359
+ nullable: import_zod9.z.boolean()
6102
6360
  }),
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),
6361
+ import_zod9.z.object({
6362
+ type: import_zod9.z.literal("alterColumnDefault"),
6363
+ table: import_zod9.z.string().min(1).max(63),
6364
+ column: import_zod9.z.string().min(1).max(63),
6107
6365
  /** null means drop the default; otherwise a SQL expression string. */
6108
- default: import_zod8.z.string().nullable()
6366
+ default: import_zod9.z.string().nullable()
6109
6367
  }),
6110
6368
  // 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(),
6369
+ import_zod9.z.object({
6370
+ type: import_zod9.z.literal("addIndex"),
6371
+ table: import_zod9.z.string().min(1).max(63),
6372
+ name: import_zod9.z.string().min(1).max(63),
6373
+ columns: import_zod9.z.array(import_zod9.z.string().min(1)).min(1),
6374
+ unique: import_zod9.z.boolean().optional(),
6117
6375
  /** Partial-index WHERE clause, as a raw SQL expression */
6118
- where: import_zod8.z.string().optional(),
6119
- ifNotExists: import_zod8.z.boolean().optional()
6376
+ where: import_zod9.z.string().optional(),
6377
+ ifNotExists: import_zod9.z.boolean().optional()
6120
6378
  }),
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()
6379
+ import_zod9.z.object({
6380
+ type: import_zod9.z.literal("dropIndex"),
6381
+ name: import_zod9.z.string().min(1).max(63),
6382
+ cascade: import_zod9.z.boolean().optional(),
6383
+ ifExists: import_zod9.z.boolean().optional()
6126
6384
  }),
6127
6385
  // 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),
6386
+ import_zod9.z.object({
6387
+ type: import_zod9.z.literal("addForeignKey"),
6388
+ table: import_zod9.z.string().min(1).max(63),
6389
+ name: import_zod9.z.string().min(1).max(63),
6390
+ columns: import_zod9.z.array(import_zod9.z.string().min(1)).min(1),
6391
+ refTable: import_zod9.z.string().min(1).max(63),
6392
+ refColumns: import_zod9.z.array(import_zod9.z.string().min(1)).min(1),
6135
6393
  onDelete: referentialActionSchema.optional(),
6136
6394
  onUpdate: referentialActionSchema.optional()
6137
6395
  }),
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)
6396
+ import_zod9.z.object({
6397
+ type: import_zod9.z.literal("addUniqueConstraint"),
6398
+ table: import_zod9.z.string().min(1).max(63),
6399
+ name: import_zod9.z.string().min(1).max(63),
6400
+ columns: import_zod9.z.array(import_zod9.z.string().min(1)).min(1)
6143
6401
  }),
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)
6402
+ import_zod9.z.object({
6403
+ type: import_zod9.z.literal("addCheckConstraint"),
6404
+ table: import_zod9.z.string().min(1).max(63),
6405
+ name: import_zod9.z.string().min(1).max(63),
6406
+ expression: import_zod9.z.string().min(1)
6149
6407
  }),
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()
6408
+ import_zod9.z.object({
6409
+ type: import_zod9.z.literal("dropConstraint"),
6410
+ table: import_zod9.z.string().min(1).max(63),
6411
+ name: import_zod9.z.string().min(1).max(63),
6412
+ cascade: import_zod9.z.boolean().optional(),
6413
+ ifExists: import_zod9.z.boolean().optional()
6156
6414
  }),
6157
6415
  // 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)
6416
+ import_zod9.z.object({
6417
+ type: import_zod9.z.literal("createEnum"),
6418
+ name: import_zod9.z.string().min(1).max(63),
6419
+ values: import_zod9.z.array(import_zod9.z.string().min(1)).min(1)
6162
6420
  }),
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),
6421
+ import_zod9.z.object({
6422
+ type: import_zod9.z.literal("alterEnumAddValue"),
6423
+ name: import_zod9.z.string().min(1).max(63),
6424
+ value: import_zod9.z.string().min(1),
6167
6425
  /** Position the new value before an existing one (mutually exclusive with `after`) */
6168
- before: import_zod8.z.string().optional(),
6426
+ before: import_zod9.z.string().optional(),
6169
6427
  /** 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()
6428
+ after: import_zod9.z.string().optional(),
6429
+ ifNotExists: import_zod9.z.boolean().optional()
6172
6430
  }),
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()
6431
+ import_zod9.z.object({
6432
+ type: import_zod9.z.literal("dropEnum"),
6433
+ name: import_zod9.z.string().min(1).max(63),
6434
+ cascade: import_zod9.z.boolean().optional(),
6435
+ ifExists: import_zod9.z.boolean().optional()
6178
6436
  }),
6179
6437
  // 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()
6438
+ import_zod9.z.object({
6439
+ type: import_zod9.z.literal("createExtension"),
6440
+ name: import_zod9.z.string().min(1),
6441
+ ifNotExists: import_zod9.z.boolean().optional()
6184
6442
  }),
6185
6443
  // Data operations
6186
6444
  /**
@@ -6188,26 +6446,26 @@ var schemaOpSchema = import_zod8.z.discriminatedUnion("type", [
6188
6446
  * destructive — requires explicit confirm to replay on prod. The
6189
6447
  * `description` is what the agent/user sees at confirm time.
6190
6448
  */
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)
6449
+ import_zod9.z.object({
6450
+ type: import_zod9.z.literal("backfill"),
6451
+ description: import_zod9.z.string().min(1),
6452
+ sql: import_zod9.z.string().min(1)
6195
6453
  }),
6196
6454
  /**
6197
6455
  * Escape hatch for DDL the structured ops can't express. `destructive`
6198
6456
  * forces classification — use false only for genuinely additive-only SQL
6199
6457
  * (e.g. CREATE EXTENSION variants the structured op doesn't cover).
6200
6458
  */
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()
6459
+ import_zod9.z.object({
6460
+ type: import_zod9.z.literal("rawSql"),
6461
+ description: import_zod9.z.string().min(1),
6462
+ sql: import_zod9.z.string().min(1),
6463
+ destructive: import_zod9.z.boolean()
6206
6464
  })
6207
6465
  ]);
6208
6466
 
6209
6467
  // ../../packages/lib/src/server/module-rail/ops.ts
6210
- var import_zod9 = require("zod");
6468
+ var import_zod10 = require("zod");
6211
6469
 
6212
6470
  // ../../packages/lib/src/server/module-rail/reconcile.ts
6213
6471
  var SANDBOX_BATCH_SIZE = 200;
@@ -6237,11 +6495,11 @@ function countedCompositionContext(context) {
6237
6495
  counts.sandboxExecs += 1;
6238
6496
  return context.sandbox.exec(...args);
6239
6497
  },
6240
- readFile: (path5) => {
6498
+ readFile: (path6) => {
6241
6499
  counts.sandboxReads += 1;
6242
- return context.sandbox.readFile(path5);
6500
+ return context.sandbox.readFile(path6);
6243
6501
  },
6244
- fileExists: (path5) => context.sandbox.fileExists(path5),
6502
+ fileExists: (path6) => context.sandbox.fileExists(path6),
6245
6503
  fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
6246
6504
  addDependencies: (specs) => context.sandbox.addDependencies(specs)
6247
6505
  }
@@ -6438,14 +6696,14 @@ async function discoverRootRouteEntries(context, moduleName) {
6438
6696
  }
6439
6697
  return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
6440
6698
  }
6441
- async function writeSandboxFileAtomic(target, path5, content) {
6442
- const dir = dirnamePosix(path5);
6443
- const tmp = `${path5}.tmp.${target.runId}`;
6699
+ async function writeSandboxFileAtomic(target, path6, content) {
6700
+ const dir = dirnamePosix(path6);
6701
+ const tmp = `${path6}.tmp.${target.runId}`;
6444
6702
  const b64 = Buffer.from(content, "utf8").toString("base64");
6445
6703
  const cmd = [
6446
6704
  `mkdir -p ${shellQuote(dir)}`,
6447
6705
  `printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
6448
- `mv -f ${shellQuote(tmp)} ${shellQuote(path5)}`
6706
+ `mv -f ${shellQuote(tmp)} ${shellQuote(path6)}`
6449
6707
  ].join(" && ");
6450
6708
  const result = await target.sandbox.exec(cmd, { raiseOnError: false });
6451
6709
  if (result.exitCode !== 0) {
@@ -6460,14 +6718,14 @@ async function writeSandboxFileAtomic(target, path5, content) {
6460
6718
  }
6461
6719
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
6462
6720
  throw new Error(
6463
- `[ModuleRail] composition artifacts: failed to write ${redactSecrets(path5)}: ${redactSecrets(detail)}`
6721
+ `[ModuleRail] composition artifacts: failed to write ${redactSecrets(path6)}: ${redactSecrets(detail)}`
6464
6722
  );
6465
6723
  }
6466
6724
  }
6467
6725
  async function collectAbsentWriteParentDirs(context, writePaths) {
6468
6726
  const absent = /* @__PURE__ */ new Set();
6469
- for (const path5 of writePaths) {
6470
- let dir = dirnamePosix(path5);
6727
+ for (const path6 of writePaths) {
6728
+ let dir = dirnamePosix(path6);
6471
6729
  while (dir !== "." && dir !== "") {
6472
6730
  if (absent.has(dir)) {
6473
6731
  dir = dirnamePosix(dir);
@@ -6504,12 +6762,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
6504
6762
  );
6505
6763
  }
6506
6764
  }
6507
- async function deleteSandboxFile(context, path5) {
6508
- const result = await context.sandbox.exec(`rm -f ${shellQuote(path5)}`, { raiseOnError: false });
6765
+ async function deleteSandboxFile(context, path6) {
6766
+ const result = await context.sandbox.exec(`rm -f ${shellQuote(path6)}`, { raiseOnError: false });
6509
6767
  if (result.exitCode !== 0) {
6510
6768
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
6511
6769
  throw new Error(
6512
- `[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path5)}: ${redactSecrets(detail)}`
6770
+ `[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path6)}: ${redactSecrets(detail)}`
6513
6771
  );
6514
6772
  }
6515
6773
  }
@@ -6576,37 +6834,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
6576
6834
  await writeSandboxFileAtomic(context, write.path, write.content);
6577
6835
  appliedWrites.push({ path: write.path, previous });
6578
6836
  }
6579
- for (const path5 of plan.deletes) {
6580
- const previous = existingFiles[path5];
6837
+ for (const path6 of plan.deletes) {
6838
+ const previous = existingFiles[path6];
6581
6839
  if (previous == null) continue;
6582
- await deleteSandboxFile(context, path5);
6583
- appliedDeletes.push({ path: path5, previous });
6840
+ await deleteSandboxFile(context, path6);
6841
+ appliedDeletes.push({ path: path6, previous });
6584
6842
  }
6585
6843
  } catch (error) {
6586
6844
  console.log(
6587
6845
  `[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
6588
6846
  );
6589
- for (const { path: path5, previous } of [...appliedDeletes].reverse()) {
6847
+ for (const { path: path6, previous } of [...appliedDeletes].reverse()) {
6590
6848
  try {
6591
- await writeSandboxFileAtomic(context, path5, previous);
6849
+ await writeSandboxFileAtomic(context, path6, previous);
6592
6850
  } catch (rollbackError) {
6593
6851
  console.log(
6594
- `[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path5)}: ${redactSecrets(
6852
+ `[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path6)}: ${redactSecrets(
6595
6853
  rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
6596
6854
  )}`
6597
6855
  );
6598
6856
  }
6599
6857
  }
6600
- for (const { path: path5, previous } of [...appliedWrites].reverse()) {
6858
+ for (const { path: path6, previous } of [...appliedWrites].reverse()) {
6601
6859
  try {
6602
6860
  if (previous == null) {
6603
- await deleteSandboxFile(context, path5);
6861
+ await deleteSandboxFile(context, path6);
6604
6862
  } else {
6605
- await writeSandboxFileAtomic(context, path5, previous);
6863
+ await writeSandboxFileAtomic(context, path6, previous);
6606
6864
  }
6607
6865
  } catch (rollbackError) {
6608
6866
  console.log(
6609
- `[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path5)}: ${redactSecrets(
6867
+ `[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path6)}: ${redactSecrets(
6610
6868
  rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
6611
6869
  )}`
6612
6870
  );
@@ -6640,39 +6898,39 @@ async function listExistingGeneratedStubPaths(context) {
6640
6898
  COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
6641
6899
  );
6642
6900
  const presentPaths = [];
6643
- for (const path5 of candidates) {
6901
+ for (const path6 of candidates) {
6644
6902
  const presence = assertRealCompositionArtifactFilePresence(
6645
- path5,
6646
- kinds.get(path5) ?? "unreadable"
6903
+ path6,
6904
+ kinds.get(path6) ?? "unreadable"
6647
6905
  );
6648
- if (presence === "file") presentPaths.push(path5);
6906
+ if (presence === "file") presentPaths.push(path6);
6649
6907
  }
6650
6908
  let contents;
6651
6909
  try {
6652
6910
  contents = await readFilesMany(context, presentPaths);
6653
6911
  } catch (error) {
6654
- const path5 = readFailurePath(error);
6912
+ const path6 = readFailurePath(error);
6655
6913
  throw new Error(
6656
- `[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path5)}: ${redactSecrets(
6914
+ `[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path6)}: ${redactSecrets(
6657
6915
  error instanceof Error ? error.message : String(error)
6658
6916
  )}`
6659
6917
  );
6660
6918
  }
6661
6919
  const owned = [];
6662
- for (const path5 of presentPaths) {
6663
- const content = contents.get(path5);
6920
+ for (const path6 of presentPaths) {
6921
+ const content = contents.get(path6);
6664
6922
  const marker = parseCompositionArtifactMarker(content);
6665
- if (parseLegacyApiAdapter(path5, content) || parseLegacyRouteAdapter(path5, content)) {
6666
- owned.push(path5);
6923
+ if (parseLegacyApiAdapter(path6, content) || parseLegacyRouteAdapter(path6, content)) {
6924
+ owned.push(path6);
6667
6925
  continue;
6668
6926
  }
6669
6927
  if (marker == null) continue;
6670
- if (!markerCongruentWithPath(path5, marker)) {
6928
+ if (!markerCongruentWithPath(path6, marker)) {
6671
6929
  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)`
6930
+ `[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
6931
  );
6674
6932
  }
6675
- owned.push(path5);
6933
+ owned.push(path6);
6676
6934
  }
6677
6935
  return owned;
6678
6936
  }
@@ -6806,7 +7064,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
6806
7064
  { raiseOnError: false }
6807
7065
  );
6808
7066
  if (result.exitCode !== 0) {
6809
- for (const path5 of chunk) kinds.set(path5, "unreadable");
7067
+ for (const path6 of chunk) kinds.set(path6, "unreadable");
6810
7068
  continue;
6811
7069
  }
6812
7070
  const lines = result.output.split("\n").filter(Boolean);
@@ -6817,31 +7075,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
6817
7075
  }
6818
7076
  } else {
6819
7077
  for (const line of lines) {
6820
- const [rawKind, path5] = line.split(" ", 2);
7078
+ const [rawKind, path6] = line.split(" ", 2);
6821
7079
  const kind = rawKind?.replace(/^entry-kind:/, "");
6822
- if (path5 && chunk.includes(path5) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
6823
- kinds.set(path5, kind);
7080
+ if (path6 && chunk.includes(path6) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
7081
+ kinds.set(path6, kind);
6824
7082
  }
6825
7083
  }
6826
7084
  }
6827
- for (const path5 of chunk) {
6828
- if (!kinds.has(path5)) kinds.set(path5, "unreadable");
7085
+ for (const path6 of chunk) {
7086
+ if (!kinds.has(path6)) kinds.set(path6, "unreadable");
6829
7087
  }
6830
7088
  }
6831
7089
  return kinds;
6832
7090
  }
6833
- async function classifySandboxPathPresenceKind(context, path5, probeToken) {
6834
- return (await classifySandboxPathPresenceKindMany(context, [path5], probeToken)).get(path5);
7091
+ async function classifySandboxPathPresenceKind(context, path6, probeToken) {
7092
+ return (await classifySandboxPathPresenceKindMany(context, [path6], probeToken)).get(path6);
6835
7093
  }
6836
7094
  var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
6837
- function assertRealLocaleJsonFilePresence(path5, presence) {
7095
+ function assertRealLocaleJsonFilePresence(path6, presence) {
6838
7096
  if (presence === "file") return;
6839
7097
  if (presence === "absent") {
6840
- throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path5}`);
7098
+ throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path6}`);
6841
7099
  }
6842
- const presenceError = formatConventionalEntryPresenceError(path5, presence);
7100
+ const presenceError = formatConventionalEntryPresenceError(path6, presence);
6843
7101
  throw new Error(
6844
- `[ModuleRail] composition artifacts: ${presenceError ?? `${path5} is not a real locale JSON file`}`
7102
+ `[ModuleRail] composition artifacts: ${presenceError ?? `${path6} is not a real locale JSON file`}`
6845
7103
  );
6846
7104
  }
6847
7105
  function splitTaggedLine(line) {
@@ -6916,11 +7174,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
6916
7174
  }
6917
7175
  async function readFilesMany(context, paths) {
6918
7176
  const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
6919
- const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path5) => {
7177
+ const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path6) => {
6920
7178
  try {
6921
- return { path: path5, value: await context.sandbox.readFile(path5) };
7179
+ return { path: path6, value: await context.sandbox.readFile(path6) };
6922
7180
  } catch (error) {
6923
- return { path: path5, error };
7181
+ return { path: path6, error };
6924
7182
  }
6925
7183
  });
6926
7184
  const files = /* @__PURE__ */ new Map();
@@ -6943,61 +7201,61 @@ function readFailurePath(error) {
6943
7201
  var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
6944
7202
  var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
6945
7203
  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);
7204
+ async function classifyCompositionArtifactPathKind(context, path6) {
7205
+ return classifySandboxPathPresenceKind(context, path6, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
6948
7206
  }
6949
- function assertRealCompositionArtifactFilePresence(path5, presence) {
7207
+ function assertRealCompositionArtifactFilePresence(path6, presence) {
6950
7208
  if (presence === "file") return "file";
6951
7209
  if (presence === "absent") return "absent";
6952
- const presenceError = formatConventionalEntryPresenceError(path5, presence);
7210
+ const presenceError = formatConventionalEntryPresenceError(path6, presence);
6953
7211
  throw new Error(
6954
- `[ModuleRail] composition artifacts: ${presenceError ?? `${path5} is not a real composition artifact file`}`
7212
+ `[ModuleRail] composition artifacts: ${presenceError ?? `${path6} is not a real composition artifact file`}`
6955
7213
  );
6956
7214
  }
6957
- async function readConventionalEntryIfRealFile(path5, kind, presences, files) {
6958
- const presence = presences.get(path5) ?? "unreadable";
6959
- const presenceError = formatConventionalEntryPresenceError(path5, presence);
7215
+ async function readConventionalEntryIfRealFile(path6, kind, presences, files) {
7216
+ const presence = presences.get(path6) ?? "unreadable";
7217
+ const presenceError = formatConventionalEntryPresenceError(path6, presence);
6960
7218
  if (presenceError) {
6961
7219
  throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
6962
7220
  }
6963
7221
  if (presence === "absent") {
6964
7222
  return null;
6965
7223
  }
6966
- const source = files.get(path5);
7224
+ const source = files.get(path6);
6967
7225
  switch (kind) {
6968
7226
  case "contributions":
6969
- assertContributionsExport(source, path5);
6970
- assertClientSafeConventionalEntryImports(source, path5, "contributions");
7227
+ assertContributionsExport(source, path6);
7228
+ assertClientSafeConventionalEntryImports(source, path6, "contributions");
6971
7229
  break;
6972
7230
  case "slotCatalogs":
6973
- assertSlotCatalogsExport(source, path5);
6974
- assertClientSafeConventionalEntryImports(source, path5, "slotCatalogs");
7231
+ assertSlotCatalogsExport(source, path6);
7232
+ assertClientSafeConventionalEntryImports(source, path6, "slotCatalogs");
6975
7233
  break;
6976
7234
  case "initializeEnhancement":
6977
- assertInitializeEnhancementExport(source, path5);
7235
+ assertInitializeEnhancementExport(source, path6);
6978
7236
  break;
6979
7237
  case "initializeModule":
6980
- assertInitializeModuleExport(source, path5);
7238
+ assertInitializeModuleExport(source, path6);
6981
7239
  break;
6982
7240
  }
6983
7241
  return source;
6984
7242
  }
6985
7243
  function hasRealServerRegistrations(modulePath, presences) {
6986
- const path5 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
6987
- return assertRealCompositionArtifactFilePresence(path5, presences.get(path5) ?? "unreadable") === "file";
7244
+ const path6 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
7245
+ return assertRealCompositionArtifactFilePresence(path6, presences.get(path6) ?? "unreadable") === "file";
6988
7246
  }
6989
- async function readJsonObjectFile(path5, files) {
6990
- const raw = files.get(path5);
7247
+ async function readJsonObjectFile(path6, files) {
7248
+ const raw = files.get(path6);
6991
7249
  let parsed;
6992
7250
  try {
6993
7251
  parsed = JSON.parse(raw);
6994
7252
  } catch (error) {
6995
7253
  throw new Error(
6996
- `[ModuleRail] composition artifacts: invalid JSON at ${path5}: ${error instanceof Error ? error.message : String(error)}`
7254
+ `[ModuleRail] composition artifacts: invalid JSON at ${path6}: ${error instanceof Error ? error.message : String(error)}`
6997
7255
  );
6998
7256
  }
6999
7257
  if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
7000
- throw new Error(`[ModuleRail] composition artifacts: ${path5} root must be an object`);
7258
+ throw new Error(`[ModuleRail] composition artifacts: ${path6} root must be an object`);
7001
7259
  }
7002
7260
  return parsed;
7003
7261
  }
@@ -7050,11 +7308,11 @@ async function collectCompositionEntryFacts(context, registry) {
7050
7308
  classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
7051
7309
  ]);
7052
7310
  const presentConventionalPaths = [];
7053
- for (const path5 of conventionalPaths) {
7054
- const presence = conventionalPresences.get(path5) ?? "unreadable";
7055
- if (presence === "file") presentConventionalPaths.push(path5);
7311
+ for (const path6 of conventionalPaths) {
7312
+ const presence = conventionalPresences.get(path6) ?? "unreadable";
7313
+ if (presence === "file") presentConventionalPaths.push(path6);
7056
7314
  }
7057
- const presentLocalePaths = localePaths.filter((path5) => localePresences.get(path5) === "file");
7315
+ const presentLocalePaths = localePaths.filter((path6) => localePresences.get(path6) === "file");
7058
7316
  const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
7059
7317
  const substrateLocales = [];
7060
7318
  for (const locale of substrateNames) {
@@ -7164,8 +7422,8 @@ async function collectCompositionEntryFacts(context, registry) {
7164
7422
  });
7165
7423
  if (rootLocales.length > 0) {
7166
7424
  for (const locale of rootLocales) {
7167
- const path5 = `${modulePath}/i18n/${locale}.json`;
7168
- assertRealLocaleJsonFilePresence(path5, localePresences.get(path5) ?? "unreadable");
7425
+ const path6 = `${modulePath}/i18n/${locale}.json`;
7426
+ assertRealLocaleJsonFilePresence(path6, localePresences.get(path6) ?? "unreadable");
7169
7427
  }
7170
7428
  const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
7171
7429
  const localeMessages = { en: enMessages };
@@ -7189,8 +7447,8 @@ async function collectCompositionEntryFacts(context, registry) {
7189
7447
  if (!enh.active || enh.locales.length === 0) continue;
7190
7448
  const peerDir = `${modulePath}/enhancements/${enh.peer}`;
7191
7449
  for (const locale of enh.locales) {
7192
- const path5 = `${peerDir}/i18n/${locale}.json`;
7193
- assertRealLocaleJsonFilePresence(path5, localePresences.get(path5) ?? "unreadable");
7450
+ const path6 = `${peerDir}/i18n/${locale}.json`;
7451
+ assertRealLocaleJsonFilePresence(path6, localePresences.get(path6) ?? "unreadable");
7194
7452
  }
7195
7453
  const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
7196
7454
  const localeMessages = { en: enMessages };
@@ -7398,16 +7656,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7398
7656
  for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
7399
7657
  const existingFiles = {};
7400
7658
  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];
7659
+ for (const path6 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
7660
+ if (Object.prototype.hasOwnProperty.call(existingAppPages, path6)) {
7661
+ existingFiles[path6] = existingAppPages[path6];
7404
7662
  continue;
7405
7663
  }
7406
- if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path5)) {
7407
- existingFiles[path5] = existingAppRoutes[path5];
7664
+ if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path6)) {
7665
+ existingFiles[path6] = existingAppRoutes[path6];
7408
7666
  continue;
7409
7667
  }
7410
- remainingCandidatePaths.push(path5);
7668
+ remainingCandidatePaths.push(path6);
7411
7669
  }
7412
7670
  const candidateKinds = await classifySandboxPathPresenceKindMany(
7413
7671
  context,
@@ -7415,12 +7673,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7415
7673
  COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
7416
7674
  );
7417
7675
  const presentCandidatePaths = [];
7418
- for (const path5 of remainingCandidatePaths) {
7676
+ for (const path6 of remainingCandidatePaths) {
7419
7677
  const presence = assertRealCompositionArtifactFilePresence(
7420
- path5,
7421
- candidateKinds.get(path5) ?? "unreadable"
7678
+ path6,
7679
+ candidateKinds.get(path6) ?? "unreadable"
7422
7680
  );
7423
- if (presence === "file") presentCandidatePaths.push(path5);
7681
+ if (presence === "file") presentCandidatePaths.push(path6);
7424
7682
  }
7425
7683
  try {
7426
7684
  Object.assign(
@@ -7428,9 +7686,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7428
7686
  Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
7429
7687
  );
7430
7688
  } catch (error) {
7431
- const path5 = readFailurePath(error);
7689
+ const path6 = readFailurePath(error);
7432
7690
  throw new Error(
7433
- `[ModuleRail] composition artifacts: cannot read ${redactSecrets(path5)} during preflight: ${redactSecrets(
7691
+ `[ModuleRail] composition artifacts: cannot read ${redactSecrets(path6)} during preflight: ${redactSecrets(
7434
7692
  error instanceof Error ? error.message : String(error)
7435
7693
  )}`
7436
7694
  );
@@ -7503,8 +7761,8 @@ var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
7503
7761
  var import_typescript6 = __toESM(require("typescript"));
7504
7762
 
7505
7763
  // src/module-boundaries.ts
7506
- var import_node_fs2 = __toESM(require("fs"));
7507
- var import_node_path2 = __toESM(require("path"));
7764
+ var import_node_fs3 = __toESM(require("fs"));
7765
+ var import_node_path3 = __toESM(require("path"));
7508
7766
  var import_typescript5 = __toESM(require("typescript"));
7509
7767
  var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
7510
7768
  var RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
@@ -7523,26 +7781,26 @@ function dataPathsIn(fileName, source) {
7523
7781
  }
7524
7782
  function scanModule(modulesDir, module2) {
7525
7783
  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"));
7784
+ const moduleDir = import_node_path3.default.join(modulesDir, module2);
7785
+ if (!import_node_fs3.default.existsSync(moduleDir)) return scanned;
7786
+ const files = import_node_fs3.default.readdirSync(moduleDir, { recursive: true, encoding: "utf8" }).filter((relative) => SOURCE_FILE.test(relative) && !relative.includes("node_modules"));
7529
7787
  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");
7788
+ const absolute = import_node_path3.default.join(moduleDir, relative);
7789
+ if (!import_node_fs3.default.statSync(absolute).isFile()) continue;
7790
+ const source = import_node_fs3.default.readFileSync(absolute, "utf8");
7533
7791
  const specifiers = import_typescript5.default.preProcessFile(source, true, true).importedFiles.map((f) => f.fileName);
7534
7792
  const dataPaths = dataPathsIn(absolute, source);
7535
7793
  const targets = [...specifiers, ...dataPaths].flatMap((spec) => {
7536
7794
  if (spec.startsWith("@/modules/"))
7537
- return [import_node_path2.default.join(modulesDir, spec.slice("@/modules/".length))];
7795
+ return [import_node_path3.default.join(modulesDir, spec.slice("@/modules/".length))];
7538
7796
  if (spec.startsWith("./") || spec.startsWith("../")) {
7539
- return [import_node_path2.default.resolve(import_node_path2.default.dirname(absolute), spec)];
7797
+ return [import_node_path3.default.resolve(import_node_path3.default.dirname(absolute), spec)];
7540
7798
  }
7541
7799
  return [];
7542
7800
  });
7543
7801
  scanned.set(absolute, {
7544
7802
  module: module2,
7545
- relative: relative.split(import_node_path2.default.sep).join("/"),
7803
+ relative: relative.split(import_node_path3.default.sep).join("/"),
7546
7804
  targets,
7547
7805
  dataPaths
7548
7806
  });
@@ -7559,7 +7817,7 @@ function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = fal
7559
7817
  (q) => file.relative.startsWith(`enhancements/${q}/`)
7560
7818
  );
7561
7819
  const goneWith = (target, peer) => {
7562
- const relative = import_node_path2.default.relative(modulesDir, target).split(import_node_path2.default.sep).join("/");
7820
+ const relative = import_node_path3.default.relative(modulesDir, target).split(import_node_path3.default.sep).join("/");
7563
7821
  return relative === peer || relative.startsWith(`${peer}/`) || new RegExp(`^[^/]+/enhancements/${peer}(/|$)`).test(relative);
7564
7822
  };
7565
7823
  const tainted = /* @__PURE__ */ new Map();
@@ -7605,12 +7863,12 @@ function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = fal
7605
7863
  return violations.sort((a, b) => a.file.localeCompare(b.file) || a.peer.localeCompare(b.peer));
7606
7864
  }
7607
7865
  function findOptionalPeerViolations(modulesDir) {
7608
- if (!import_node_fs2.default.existsSync(modulesDir)) return [];
7866
+ if (!import_node_fs3.default.existsSync(modulesDir)) return [];
7609
7867
  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"));
7868
+ for (const name of import_node_fs3.default.readdirSync(modulesDir)) {
7869
+ const manifestPath = import_node_path3.default.join(modulesDir, name, "module.json");
7870
+ if (!import_node_fs3.default.existsSync(manifestPath)) continue;
7871
+ manifests[name] = JSON.parse(import_node_fs3.default.readFileSync(manifestPath, "utf8"));
7614
7872
  }
7615
7873
  const features = Object.keys(manifests).filter((name) => manifests[name].kind === "feature");
7616
7874
  const peersByModule = {};
@@ -7630,11 +7888,11 @@ function formatBoundaryViolations(modulesDirLabel, violations) {
7630
7888
  // src/index.ts
7631
7889
  var MODULES_DIR2 = MODULES_SANDBOX_DIR;
7632
7890
  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: {} };
7891
+ const file = import_node_path4.default.join(cwd, COMPOSE_INPUTS_PATH);
7892
+ if (!import_node_fs4.default.existsSync(file)) return { connectedStores: [], installs: {} };
7635
7893
  let raw;
7636
7894
  try {
7637
- raw = JSON.parse(import_node_fs3.default.readFileSync(file, "utf8"));
7895
+ raw = JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
7638
7896
  } catch (error) {
7639
7897
  throw new Error(
7640
7898
  `[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
@@ -7676,18 +7934,18 @@ function stubGitignoreEntries(artifactPaths) {
7676
7934
  function writeStubGitignore(cwd, artifactPaths) {
7677
7935
  const entries = stubGitignoreEntries(artifactPaths);
7678
7936
  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;
7937
+ const file = import_node_path4.default.join(cwd, STUB_GITIGNORE_PATH);
7938
+ const existing = import_node_fs4.default.existsSync(file) ? import_node_fs4.default.readFileSync(file, "utf8") : null;
7681
7939
  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);
7940
+ import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
7941
+ import_node_fs4.default.writeFileSync(file, content);
7684
7942
  }
7685
7943
  return entries.length;
7686
7944
  }
7687
7945
  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] ?? "";
7946
+ const file = import_node_path4.default.join(cwd, STUB_GITIGNORE_PATH);
7947
+ if (!import_node_fs4.default.existsSync(file)) return;
7948
+ const firstLine = import_node_fs4.default.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
7691
7949
  if (firstLine !== STUB_GITIGNORE_HEADER) {
7692
7950
  throw new Error(
7693
7951
  `[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
@@ -7695,8 +7953,8 @@ function assertStubGitignoreOwned(cwd) {
7695
7953
  }
7696
7954
  }
7697
7955
  function assertAppRoot(cwd) {
7698
- const appPath = import_node_path3.default.join(cwd, APP_PACKAGE_JSON);
7699
- if (!import_node_fs3.default.existsSync(appPath)) {
7956
+ const appPath = import_node_path4.default.join(cwd, APP_PACKAGE_JSON);
7957
+ if (!import_node_fs4.default.existsSync(appPath)) {
7700
7958
  throw new Error(
7701
7959
  `[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
7702
7960
  );
@@ -7742,9 +8000,9 @@ ${porcelain.trim()}`
7742
8000
  }
7743
8001
  var APP_SRC_DIR = "apps/web/src";
7744
8002
  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);
8003
+ if (!import_node_fs4.default.existsSync(dir)) return;
8004
+ for (const entry of import_node_fs4.default.readdirSync(dir, { withFileTypes: true })) {
8005
+ const full = import_node_path4.default.join(dir, entry.name);
7748
8006
  if (full === skip) continue;
7749
8007
  if (entry.isDirectory()) walkSources(full, skip, out);
7750
8008
  else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
@@ -7753,13 +8011,13 @@ function walkSources(dir, skip, out) {
7753
8011
  function assertNoAppLayerImporters(cwd, excluded) {
7754
8012
  if (excluded.length === 0) return;
7755
8013
  const files = [];
7756
- walkSources(import_node_path3.default.join(cwd, APP_SRC_DIR), import_node_path3.default.join(cwd, MODULES_DIR2), files);
8014
+ walkSources(import_node_path4.default.join(cwd, APP_SRC_DIR), import_node_path4.default.join(cwd, MODULES_DIR2), files);
7757
8015
  const offenders = [];
7758
8016
  for (const file of files) {
7759
- const source = import_node_fs3.default.readFileSync(file, "utf8");
8017
+ const source = import_node_fs4.default.readFileSync(file, "utf8");
7760
8018
  for (const name of excluded) {
7761
8019
  if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
7762
- offenders.push(`${import_node_path3.default.relative(cwd, file)}: ${name}`);
8020
+ offenders.push(`${import_node_path4.default.relative(cwd, file)}: ${name}`);
7763
8021
  }
7764
8022
  }
7765
8023
  }
@@ -7793,8 +8051,8 @@ var BUILD_TYPECHECK_SKIP = [
7793
8051
  "playwright.config.ts"
7794
8052
  ];
7795
8053
  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;
8054
+ const baseFile = import_node_path4.default.join(cwd, TSCONFIG_PATH);
8055
+ if (!import_node_fs4.default.existsSync(import_node_path4.default.join(cwd, MODULES_DIR2)) || !import_node_fs4.default.existsSync(baseFile)) return;
7798
8056
  const base = import_typescript6.default.readConfigFile(baseFile, import_typescript6.default.sys.readFile);
7799
8057
  if (base.error) {
7800
8058
  throw new Error(
@@ -7802,7 +8060,7 @@ function writeBuildTsconfig(cwd, excluded) {
7802
8060
  );
7803
8061
  }
7804
8062
  const baseExclude = base.config?.exclude ?? ["node_modules"];
7805
- const modulesDir = import_node_path3.default.posix.relative("apps/web", MODULES_DIR2);
8063
+ const modulesDir = import_node_path4.default.posix.relative("apps/web", MODULES_DIR2);
7806
8064
  const config = {
7807
8065
  extends: "./tsconfig.json",
7808
8066
  exclude: [
@@ -7817,17 +8075,17 @@ function writeBuildTsconfig(cwd, excluded) {
7817
8075
  const content = `${BUILD_TSCONFIG_HEADER}
7818
8076
  ${JSON.stringify(config, null, 2)}
7819
8077
  `;
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);
8078
+ const file = import_node_path4.default.join(cwd, BUILD_TSCONFIG_PATH);
8079
+ if (!import_node_fs4.default.existsSync(file) || import_node_fs4.default.readFileSync(file, "utf8") !== content) {
8080
+ import_node_fs4.default.writeFileSync(file, content);
7823
8081
  }
7824
8082
  console.log(
7825
8083
  `[compose] ${BUILD_TSCONFIG_PATH} excludes ${excluded.length} Module(s): ${excluded.join(",") || "none"}`
7826
8084
  );
7827
8085
  }
7828
8086
  function removeDir(absolute) {
7829
- if (!import_node_fs3.default.existsSync(absolute)) return false;
7830
- import_node_fs3.default.rmSync(absolute, { recursive: true, force: true });
8087
+ if (!import_node_fs4.default.existsSync(absolute)) return false;
8088
+ import_node_fs4.default.rmSync(absolute, { recursive: true, force: true });
7831
8089
  return true;
7832
8090
  }
7833
8091
  async function compose(options) {
@@ -7855,7 +8113,7 @@ async function compose(options) {
7855
8113
  });
7856
8114
  assertNoAppLayerImporters(options.cwd, preview.excludedModules);
7857
8115
  const violations = findModuleBoundaryViolations(
7858
- import_node_path3.default.join(options.cwd, MODULES_DIR2),
8116
+ import_node_path4.default.join(options.cwd, MODULES_DIR2),
7859
8117
  Object.fromEntries(preview.composedModules.map((name) => [name, preview.excludedModules])),
7860
8118
  { pruning: true }
7861
8119
  );
@@ -7876,11 +8134,11 @@ async function compose(options) {
7876
8134
  let pruned = 0;
7877
8135
  if (options.prune) {
7878
8136
  for (const name of result.excludedModules) {
7879
- if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
8137
+ if (removeDir(import_node_path4.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
7880
8138
  }
7881
8139
  for (const owner of result.composedModules) {
7882
8140
  for (const peer of result.excludedModules) {
7883
- if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
8141
+ if (removeDir(import_node_path4.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
7884
8142
  }
7885
8143
  }
7886
8144
  }
@@ -7902,13 +8160,18 @@ async function main() {
7902
8160
  enabled: { type: "string" },
7903
8161
  prune: { type: "boolean", default: false },
7904
8162
  cwd: { type: "string" },
7905
- "check-boundaries": { type: "boolean", default: false }
8163
+ "check-boundaries": { type: "boolean", default: false },
8164
+ "check-ownership": { type: "boolean", default: false }
7906
8165
  },
7907
8166
  strict: true
7908
8167
  });
8168
+ if (values["check-ownership"]) {
8169
+ await checkOwnership(values.cwd ?? process.cwd());
8170
+ return;
8171
+ }
7909
8172
  if (values["check-boundaries"]) {
7910
8173
  const violations = findOptionalPeerViolations(
7911
- import_node_path4.default.join(values.cwd ?? process.cwd(), MODULES_SANDBOX_DIR)
8174
+ import_node_path5.default.join(values.cwd ?? process.cwd(), MODULES_SANDBOX_DIR)
7912
8175
  );
7913
8176
  if (violations.length > 0) {
7914
8177
  throw new Error(
@@ -7933,6 +8196,36 @@ async function main() {
7933
8196
  `[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
8197
  );
7935
8198
  }
8199
+ function gitAt(cwd) {
8200
+ return (args) => new Promise((resolve, reject) => {
8201
+ const child = (0, import_node_child_process3.spawn)("git", ["-c", "core.quotePath=false", ...args], {
8202
+ cwd,
8203
+ stdio: ["ignore", "pipe", "pipe"]
8204
+ });
8205
+ let stdout = "";
8206
+ child.stdout.setEncoding("utf8").on("data", (chunk) => stdout += chunk);
8207
+ child.on("error", reject);
8208
+ child.on("close", (status) => resolve({ status: status ?? 1, stdout }));
8209
+ });
8210
+ }
8211
+ async function checkOwnership(cwd) {
8212
+ const repoRoot = import_node_path5.default.resolve(cwd);
8213
+ const result = await checkStardeckOwnedFiles({ repoRoot, git: gitAt(repoRoot) });
8214
+ switch (result.status) {
8215
+ case "skipped":
8216
+ if (result.warning)
8217
+ console.warn(`[compose] --check-ownership: not checked: ${result.reason}`);
8218
+ else console.log(`[compose] --check-ownership: skipped: ${result.reason}`);
8219
+ return;
8220
+ case "clean":
8221
+ console.log(
8222
+ `[compose] --check-ownership: no Stardeck-owned file changed from Stardeck's version (${result.release.slice(0, 12)})`
8223
+ );
8224
+ return;
8225
+ default:
8226
+ throw new Error(`[compose] --check-ownership: ${formatOwnershipGuardFailure(result)}`);
8227
+ }
8228
+ }
7936
8229
  main().catch((error) => {
7937
8230
  console.error(error instanceof Error ? error.message : String(error));
7938
8231
  process.exit(1);