@wairon/cli 5.1.1-dev.107 → 5.1.1-dev.108

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/index.js CHANGED
@@ -3059,8 +3059,8 @@ var require_dist = __commonJS({
3059
3059
  function slug(name) {
3060
3060
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3061
3061
  }
3062
- var fs29 = __toESM2(require("fs"));
3063
- var path49 = __toESM2(require("path"));
3062
+ var fs31 = __toESM2(require("fs"));
3063
+ var path50 = __toESM2(require("path"));
3064
3064
  var import_fflate = require_node();
3065
3065
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".svn"]);
3066
3066
  function listEntries(archive) {
@@ -3098,9 +3098,9 @@ var require_dist = __commonJS({
3098
3098
  }
3099
3099
  function writeTree(destDir, files) {
3100
3100
  for (const file of files) {
3101
- const absolute = path49.join(destDir, file.path);
3102
- fs29.mkdirSync(path49.dirname(absolute), { recursive: true });
3103
- fs29.writeFileSync(absolute, file.contents);
3101
+ const absolute = path50.join(destDir, file.path);
3102
+ fs31.mkdirSync(path50.dirname(absolute), { recursive: true });
3103
+ fs31.writeFileSync(absolute, file.contents);
3104
3104
  }
3105
3105
  }
3106
3106
  function classifyKind(name, symlinks) {
@@ -3109,14 +3109,14 @@ var require_dist = __commonJS({
3109
3109
  return "file";
3110
3110
  }
3111
3111
  function walkPackDir(root, current2, out) {
3112
- for (const entry of fs29.readdirSync(current2, { withFileTypes: true })) {
3112
+ for (const entry of fs31.readdirSync(current2, { withFileTypes: true })) {
3113
3113
  if (entry.isDirectory()) {
3114
3114
  if (SKIP_DIRS.has(entry.name)) continue;
3115
- walkPackDir(root, path49.join(current2, entry.name), out);
3115
+ walkPackDir(root, path50.join(current2, entry.name), out);
3116
3116
  } else if (entry.isFile()) {
3117
- const absolute = path49.join(current2, entry.name);
3118
- const relative22 = path49.relative(root, absolute).split(path49.sep).join("/");
3119
- out.push({ path: relative22, contents: fs29.readFileSync(absolute) });
3117
+ const absolute = path50.join(current2, entry.name);
3118
+ const relative22 = path50.relative(root, absolute).split(path50.sep).join("/");
3119
+ out.push({ path: relative22, contents: fs31.readFileSync(absolute) });
3120
3120
  }
3121
3121
  }
3122
3122
  }
@@ -3316,8 +3316,8 @@ var require_dist = __commonJS({
3316
3316
  return normalized === "." ? ".wai/" : `${normalized}/.wai/`;
3317
3317
  }
3318
3318
  function normalizeRootPath(relativePath) {
3319
- const posix7 = relativePath.split(path210.sep).join("/").replace(/^\.\//, "").replace(/\/+$/, "");
3320
- return posix7 === "" || posix7 === "." ? "." : posix7;
3319
+ const posix8 = relativePath.split(path210.sep).join("/").replace(/^\.\//, "").replace(/\/+$/, "");
3320
+ return posix8 === "" || posix8 === "." ? "." : posix8;
3321
3321
  }
3322
3322
  function slugify(projectName) {
3323
3323
  const slug2 = projectName.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -3551,6 +3551,7 @@ __export(src_exports, {
3551
3551
  SwitchCaseSchema: () => SwitchCaseSchema,
3552
3552
  SystemPublicInterfaceSchema: () => SystemPublicInterfaceSchema,
3553
3553
  SystemSpecSchema: () => SystemSpecSchema,
3554
+ TECHNOLOGY_PACKAGES: () => TECHNOLOGY_PACKAGES,
3554
3555
  TargetConfigSchema: () => TargetConfigSchema,
3555
3556
  TechnologySchema: () => TechnologySchema,
3556
3557
  TemplateNotFoundError: () => TemplateNotFoundError,
@@ -3581,7 +3582,9 @@ __export(src_exports, {
3581
3582
  approvalKeyIn: () => approvalKeyIn,
3582
3583
  approvalRecord: () => approvalRecord,
3583
3584
  approvalVerdict: () => approvalVerdict,
3585
+ approvedRevision: () => approvedRevision,
3584
3586
  approvedStatuses: () => approvedStatuses,
3587
+ asSchemaReads: () => asSchemaReads,
3585
3588
  assertContainedProjectPath: () => assertContainedProjectPath,
3586
3589
  assertProjectInitialized: () => assertProjectInitialized,
3587
3590
  attachRetiredMounts: () => attachRetiredMounts,
@@ -3829,6 +3832,7 @@ __export(src_exports, {
3829
3832
  reexpress: () => reexpress,
3830
3833
  registerExporter: () => registerExporter,
3831
3834
  registerPackRef: () => registerPackRef,
3835
+ registerProjectServer: () => registerProjectServer,
3832
3836
  reinjectLocalGuides: () => reinjectLocalGuides,
3833
3837
  relationHealth: () => relationHealth,
3834
3838
  removeDomain: () => removeDomain,
@@ -3841,6 +3845,7 @@ __export(src_exports, {
3841
3845
  renameField: () => renameField,
3842
3846
  renameId: () => renameProjectId,
3843
3847
  renameMethod: () => renameMethod,
3848
+ renameParam: () => renameParam,
3844
3849
  renameType: () => renameType,
3845
3850
  renderDiagram: () => renderDiagram2,
3846
3851
  repairForeignStepFields: () => repairForeignStepFields,
@@ -3897,8 +3902,10 @@ __export(src_exports, {
3897
3902
  storedMethodSignature: () => storedMethodSignature,
3898
3903
  storedTypeMethod: () => storedTypeMethod,
3899
3904
  subsystemDesignView: () => subsystemDesignView,
3905
+ surfaceChanges: () => surfaceChanges,
3900
3906
  syncContextFiles: () => syncContextFiles,
3901
3907
  technologyName: () => technologyName,
3908
+ technologyPackages: () => technologyPackages,
3902
3909
  technologyTokens: () => technologyTokens,
3903
3910
  toMarkdown: () => toMarkdown,
3904
3911
  transportExportKind: () => transportExportKind,
@@ -3989,22 +3996,22 @@ function sampleOf(file) {
3989
3996
  if (fd !== void 0) fs.closeSync(fd);
3990
3997
  }
3991
3998
  }
3992
- function attributeMatches(pattern, relative20) {
3999
+ function attributeMatches(pattern, relative22) {
3993
4000
  const glob = pattern.replace(/^\//, "");
3994
- const target = glob.includes("/") ? relative20 : path.posix.basename(relative20);
4001
+ const target = glob.includes("/") ? relative22 : path.posix.basename(relative22);
3995
4002
  const source = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\//g, "\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\u0000/g, "(?:.*/)?");
3996
4003
  return new RegExp(`^${source}$`).test(target);
3997
4004
  }
3998
4005
  function attributeEol(attributesFile, file) {
3999
4006
  const text3 = readFileOrNull(attributesFile);
4000
4007
  if (text3 === null) return null;
4001
- const relative20 = path.relative(path.dirname(attributesFile), file).split(path.sep).join("/");
4008
+ const relative22 = path.relative(path.dirname(attributesFile), file).split(path.sep).join("/");
4002
4009
  let eol = null;
4003
4010
  for (const raw of text3.split(/\r?\n/)) {
4004
4011
  const line = raw.trim();
4005
4012
  if (line === "" || line.startsWith("#")) continue;
4006
4013
  const [pattern, ...attrs] = line.split(/\s+/);
4007
- if (!attributeMatches(pattern, relative20)) continue;
4014
+ if (!attributeMatches(pattern, relative22)) continue;
4008
4015
  for (const attr of attrs) {
4009
4016
  if (attr === "eol=crlf") eol = "\r\n";
4010
4017
  else if (attr === "eol=lf") eol = "\n";
@@ -4285,7 +4292,7 @@ function defaultTargetConfig(type) {
4285
4292
  enabled: true
4286
4293
  };
4287
4294
  }
4288
- var WAIRON_VERSION = "5.1.1-dev.107";
4295
+ var WAIRON_VERSION = "5.1.1-dev.108";
4289
4296
  var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
4290
4297
  var ARCHITECT_AGENT_ID = "agent-architect";
4291
4298
  var ARCHITECT_TEMPLATE_ID = "architect";
@@ -4406,7 +4413,7 @@ function cacheRoot() {
4406
4413
  return root;
4407
4414
  }
4408
4415
  function git(args, cwd) {
4409
- return (0, import_child_process.execFileSync)("git", args, {
4416
+ return (0, import_child_process.execFileSync)("git", ["-c", "core.longpaths=true", ...args], {
4410
4417
  cwd,
4411
4418
  encoding: "utf8",
4412
4419
  stdio: ["ignore", "pipe", "pipe"],
@@ -4504,6 +4511,20 @@ function repositoryRoot(directory) {
4504
4511
  at = up;
4505
4512
  }
4506
4513
  }
4514
+ function lastCommitOf(directory, pathspec) {
4515
+ try {
4516
+ return git(["log", "-1", "--format=%H", "--", pathspec], directory).trim() || null;
4517
+ } catch {
4518
+ return null;
4519
+ }
4520
+ }
4521
+ function commitOf(directory, ref) {
4522
+ try {
4523
+ return git(["rev-parse", "--verify", "--quiet", `${ref}^{commit}`], directory).trim() || null;
4524
+ } catch {
4525
+ return null;
4526
+ }
4527
+ }
4507
4528
 
4508
4529
  // src/utils/yaml.ts
4509
4530
  var yaml2 = __toESM(require("js-yaml"));
@@ -5838,23 +5859,23 @@ function childrenOf(tokens, index) {
5838
5859
  }
5839
5860
  return out;
5840
5861
  }
5841
- function locateScalar(tokens, path49) {
5862
+ function locateScalar(tokens, path50) {
5842
5863
  let region = tokens;
5843
- for (let k = 0; k < path49.length; k += 1) {
5844
- const segment = path49[k];
5845
- if (region.length === 0) return `nothing is nested at ${path49.slice(0, k).join(".")}`;
5864
+ for (let k = 0; k < path50.length; k += 1) {
5865
+ const segment = path50[k];
5866
+ if (region.length === 0) return `nothing is nested at ${path50.slice(0, k).join(".")}`;
5846
5867
  const base = Math.min(...region.map((t) => t.col));
5847
- const last = k === path49.length - 1;
5868
+ const last = k === path50.length - 1;
5848
5869
  let at;
5849
5870
  const items = region.map((t, i) => ({ t, i })).filter(({ t }) => t.kind === "item" && t.col === base);
5850
5871
  if (items.length === 0) {
5851
5872
  at = region.findIndex((t) => t.kind === "key" && t.col === base && t.name === segment);
5852
- if (at < 0) return `no block key "${segment}" at ${path49.slice(0, k).join(".") || "the document root"}`;
5873
+ if (at < 0) return `no block key "${segment}" at ${path50.slice(0, k).join(".") || "the document root"}`;
5853
5874
  if (last) return region[at].valueStart !== void 0 ? region[at] : `"${segment}" holds no inline scalar`;
5854
5875
  } else {
5855
- if (!/^\d+$/.test(segment)) return `"${segment}" is not a sequence index at ${path49.slice(0, k).join(".") || "the document root"}`;
5876
+ if (!/^\d+$/.test(segment)) return `"${segment}" is not a sequence index at ${path50.slice(0, k).join(".") || "the document root"}`;
5856
5877
  const index = Number(segment);
5857
- if (index >= items.length) return `no block sequence item ${segment} at ${path49.slice(0, k).join(".")}`;
5878
+ if (index >= items.length) return `no block sequence item ${segment} at ${path50.slice(0, k).join(".")}`;
5858
5879
  at = items[index].i;
5859
5880
  if (last) {
5860
5881
  const value = region[at + 1];
@@ -5902,10 +5923,10 @@ function writeScalar(value, style) {
5902
5923
  if (style === "plain" && /^[A-Za-z0-9_][A-Za-z0-9_./-]*$/.test(value)) return value;
5903
5924
  return `'${value.replace(/'/g, "''")}'`;
5904
5925
  }
5905
- function setAt(document, path49, value) {
5926
+ function setAt(document, path50, value) {
5906
5927
  let node = document;
5907
- for (const segment of path49.slice(0, -1)) node = node[segment];
5908
- node[path49[path49.length - 1]] = value;
5928
+ for (const segment of path50.slice(0, -1)) node = node[segment];
5929
+ node[path50[path50.length - 1]] = value;
5909
5930
  }
5910
5931
  function rewriteScalarsInText(text3, edits, root) {
5911
5932
  const refuse4 = (why2) => {
@@ -6396,9 +6417,9 @@ function registryOver(store, root) {
6396
6417
  const next = rekeyAnchor(rename, { spec: entry.spec, at, covers });
6397
6418
  const base = ["rules", "conformance", "carried", String(g), "findings", String(f)];
6398
6419
  const named2 = { code: String(entry.code), spec: entry.spec, at: at ?? "" };
6399
- const change = (path49, field, from, to) => {
6420
+ const change = (path50, field, from, to) => {
6400
6421
  if (from === to) return;
6401
- edits.push({ path: [...base, ...path49], from, to });
6422
+ edits.push({ path: [...base, ...path50], from, to });
6402
6423
  rekeys.push({ ...named2, field, from, to });
6403
6424
  };
6404
6425
  change(["spec"], "spec", entry.spec, next.spec);
@@ -7472,7 +7493,12 @@ var MethodParamSchema = import_zod5.z.object({
7472
7493
  */
7473
7494
  type: import_zod5.z.string(),
7474
7495
  description: import_zod5.z.string().optional(),
7475
- optional: import_zod5.z.boolean().optional()
7496
+ optional: import_zod5.z.boolean().optional(),
7497
+ /**
7498
+ * The parameter's rename trace: every name it held before, oldest first —
7499
+ * written by the rename-param tool, shown as `formerly` in the design export.
7500
+ */
7501
+ previousNames: import_zod5.z.array(import_zod5.z.string().min(1)).optional()
7476
7502
  });
7477
7503
  var FindingDeclarationSchema = import_zod5.z.object({
7478
7504
  /** UPPER_SNAKE and unique within the method; a pack's codes carry the pack prefix (<PACK>_<CODE>). */
@@ -8301,6 +8327,32 @@ function componentDesignView(component) {
8301
8327
  function subsystemDesignView(subsystem) {
8302
8328
  return withoutFields(subsystem, outOfApproval("subsystem"));
8303
8329
  }
8330
+ function withoutUnknownKeys(raw, parsed) {
8331
+ if (Array.isArray(raw)) {
8332
+ if (!Array.isArray(parsed) || parsed.length !== raw.length) return raw;
8333
+ return raw.map((item, i) => withoutUnknownKeys(item, parsed[i]));
8334
+ }
8335
+ if (!raw || typeof raw !== "object" || !parsed || typeof parsed !== "object" || Array.isArray(parsed)) return raw;
8336
+ const out = {};
8337
+ for (const [key2, value] of Object.entries(raw)) {
8338
+ if (key2 in parsed) out[key2] = withoutUnknownKeys(value, parsed[key2]);
8339
+ }
8340
+ return out;
8341
+ }
8342
+ var SCHEMA_OF_KIND = {
8343
+ implementation: ImplementationSpecSchema,
8344
+ type: TypeSpecSchema,
8345
+ component: ComponentSpecSchema,
8346
+ subsystem: SubsystemSpecSchema,
8347
+ interface: InterfaceSpecSchema
8348
+ };
8349
+ function asSchemaReads(kind, doc) {
8350
+ const parsed = SCHEMA_OF_KIND[kind].safeParse(doc);
8351
+ if (!parsed.success) return doc;
8352
+ const kept = withoutUnknownKeys(doc, parsed.data);
8353
+ if (kind === "component" && "mounts" in doc) kept.mounts = doc.mounts;
8354
+ return kept;
8355
+ }
8304
8356
  function interfaceDesignView(contract) {
8305
8357
  return withoutFields(contract, outOfApproval("interface"));
8306
8358
  }
@@ -8441,6 +8493,19 @@ var SurfaceTypeDefSchema = import_zod5.z.object({
8441
8493
  values: import_zod5.z.array(EnumValueSchema).optional(),
8442
8494
  /** A named scalar's one primitive, in place of fields; only on a value-object that declares it. */
8443
8495
  holds: import_zod5.z.string().optional(),
8496
+ /**
8497
+ * The type's own methods (its checked constructors, its pure operations),
8498
+ * each with its signature, params and returns, so a binding written against
8499
+ * the pin knows them. Carried, never digested: a pin taken before they were
8500
+ * carried lacks them and reads as stale (re-pin), never as incompatible.
8501
+ */
8502
+ methods: import_zod5.z.array(import_zod5.z.object({
8503
+ name: import_zod5.z.string(),
8504
+ signature: import_zod5.z.string(),
8505
+ params: import_zod5.z.array(import_zod5.z.object({ name: import_zod5.z.string(), type: import_zod5.z.string(), description: import_zod5.z.string().optional(), optional: import_zod5.z.boolean().optional() })).optional(),
8506
+ returns: import_zod5.z.string(),
8507
+ description: import_zod5.z.string().optional()
8508
+ })).optional(),
8444
8509
  /**
8445
8510
  * The type's rename trace as the producer recorded it (previousIds), so a
8446
8511
  * consumer pinned to a former name reads a rename rather than a removal.
@@ -8875,6 +8940,29 @@ var TemplateSchema = import_zod8.z.object({
8875
8940
  version: import_zod8.z.string().default("1.0.0")
8876
8941
  });
8877
8942
 
8943
+ // src/models/technology-packages.ts
8944
+ var TECHNOLOGY_PACKAGES = [
8945
+ { names: ["postgres", "postgresql", "pg"], packages: ["pg", "postgres", "pg-promise", "@neondatabase/serverless", "@vercel/postgres"] },
8946
+ { names: ["mysql", "mariadb"], packages: ["mysql2", "mysql", "mariadb"] },
8947
+ { names: ["redis", "valkey"], packages: ["redis", "ioredis", "@redis/client"] },
8948
+ { names: ["mongodb", "mongo"], packages: ["mongodb", "mongoose"] },
8949
+ { names: ["sqlite", "sqlite3"], packages: ["better-sqlite3", "sqlite3", "sqlite"] },
8950
+ { names: ["kafka"], packages: ["kafkajs", "node-rdkafka"] },
8951
+ { names: ["rabbitmq", "amqp"], packages: ["amqplib", "amqp-connection-manager"] }
8952
+ ];
8953
+ var lookupKey = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "");
8954
+ function technologyPackages(tech, contributed = {}) {
8955
+ const key2 = lookupKey(technologyName(tech));
8956
+ const out = new Set(technologyTokens(tech).map((t) => t.toLowerCase()));
8957
+ for (const entry of TECHNOLOGY_PACKAGES) {
8958
+ if (entry.names.some((n) => lookupKey(n) === key2)) for (const p of entry.packages) out.add(p);
8959
+ }
8960
+ for (const [name, packages] of Object.entries(contributed)) {
8961
+ if (lookupKey(name) === key2) for (const p of packages) out.add(p.toLowerCase());
8962
+ }
8963
+ return [...out];
8964
+ }
8965
+
8878
8966
  // src/models/type-grammar.ts
8879
8967
  var PRIMITIVE_TYPES = ["string", "int", "float", "bool", "bytes", "date", "datetime", "duration", "void", "any"];
8880
8968
  var PRIMITIVES = new Set(PRIMITIVE_TYPES);
@@ -9547,13 +9635,13 @@ var Canonicalizer = class {
9547
9635
  this.problems = [];
9548
9636
  }
9549
9637
  /** One position: its canonical text when it has one, else the text as written, recorded either way. */
9550
- read(text3, position, path49, omittable2 = false) {
9638
+ read(text3, position, path50, omittable2 = false) {
9551
9639
  const parse = parseTypePosition(text3, position, omittable2);
9552
9640
  if (parse.problem) {
9553
- this.problems.push({ ...parse.problem, specId: this.specId, kind: this.kind, path: path49 });
9641
+ this.problems.push({ ...parse.problem, specId: this.specId, kind: this.kind, path: path50 });
9554
9642
  return text3;
9555
9643
  }
9556
- if (parse.canonical !== text3) this.respellings.push({ specId: this.specId, kind: this.kind, path: path49, written: text3, stored: parse.canonical });
9644
+ if (parse.canonical !== text3) this.respellings.push({ specId: this.specId, kind: this.kind, path: path50, written: text3, stored: parse.canonical });
9557
9645
  return parse.canonical;
9558
9646
  }
9559
9647
  params(params, position, prefix) {
@@ -9638,8 +9726,8 @@ function namesAnInteger(name) {
9638
9726
  const words2 = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[\s_\-.]+/).filter(Boolean);
9639
9727
  return words2.some((word) => INTEGER_WORDS.has(word));
9640
9728
  }
9641
- function positionNameOf(path49) {
9642
- const segments = path49.split(".");
9729
+ function positionNameOf(path50) {
9730
+ const segments = path50.split(".");
9643
9731
  const last = segments[segments.length - 1];
9644
9732
  return last === "returns" && segments.length >= 3 ? segments[segments.length - 2] : last;
9645
9733
  }
@@ -10007,10 +10095,10 @@ function stepGraph(method) {
10007
10095
  const s = byNum.get(n);
10008
10096
  if (s.type !== "parallel" || s.endStep === void 0 || !s.branches?.length) continue;
10009
10097
  const entries = s.branches.map((b) => b.step).sort((a, b) => a - b);
10010
- const join29 = fallNext(s.endStep);
10098
+ const join31 = fallNext(s.endStep);
10011
10099
  for (let i = 0; i < entries.length; i++) {
10012
10100
  const armEnd = i + 1 < entries.length ? prevOf(entries[i + 1]) : s.endStep;
10013
- if (armEnd !== void 0 && armEnd >= entries[i]) armEndJoin.set(armEnd, join29);
10101
+ if (armEnd !== void 0 && armEnd >= entries[i]) armEndJoin.set(armEnd, join31);
10014
10102
  }
10015
10103
  }
10016
10104
  const successorsOf = (n) => {
@@ -10488,7 +10576,10 @@ function carriedFactChanges(pinned, live) {
10488
10576
  const liveTypes = new Map(live.types.map((t) => [t.id, t]));
10489
10577
  for (const def of pinned.types) {
10490
10578
  const now = liveTypes.get(def.id);
10491
- if (now) differs(`rename trace of ${def.id}`, def.formerly, now.formerly);
10579
+ if (!now) continue;
10580
+ differs(`rename trace of ${def.id}`, def.formerly, now.formerly);
10581
+ const methods = (d) => (d.methods ?? []).map((m) => ({ name: m.name, params: (m.params ?? []).map((p) => ({ type: p.type, optional: p.optional === true })), returns: m.returns }));
10582
+ differs(`methods of ${def.id}`, def.methods?.length ? methods(def) : void 0, now.methods?.length ? methods(now) : void 0);
10492
10583
  }
10493
10584
  const unresolved = (snapshot) => [...snapshot.unresolvedExports ?? []].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
10494
10585
  differs("the unresolved re-exports", unresolved(pinned), unresolved(live));
@@ -10518,6 +10609,77 @@ function memberDigest(snapshot, publicName, member) {
10518
10609
  if (!method) return null;
10519
10610
  return sha256(canonicalize({ method: methodShape(snapshot, method), closure: closureShapes(snapshot, methodTypeRefs(method)) }));
10520
10611
  }
10612
+ function shownSignature(method) {
10613
+ return method.signature || `${method.name}(${(method.params ?? []).map((p) => `${p.name}${p.optional ? "?" : ""}: ${p.type}`).join(", ")}): ${method.returns}`;
10614
+ }
10615
+ function surfaceChanges(newer, older) {
10616
+ const out = [];
10617
+ const oldEntries = new Map(older.interfaces.map((e) => [e.id, e]));
10618
+ const oldTypes = new Map((older.exportedTypes ?? []).map((t) => [t.id, t]));
10619
+ const consumed = /* @__PURE__ */ new Set();
10620
+ const formerOf = (formerly, has) => (formerly ?? []).find((f) => has(f) && !consumed.has(f));
10621
+ for (const entry of newer.interfaces) {
10622
+ let before = oldEntries.get(entry.id);
10623
+ if (!before) {
10624
+ const from = formerOf(entry.formerly, (n) => oldEntries.has(n) && !newer.interfaces.some((e) => e.id === n));
10625
+ if (from === void 0) {
10626
+ out.push({ kind: "added", name: entry.id, detail: `added: ${entry.type} contract with ${entry.methods.length} method(s), audience ${entry.audience}` });
10627
+ continue;
10628
+ }
10629
+ consumed.add(from);
10630
+ before = oldEntries.get(from);
10631
+ out.push({ kind: "renamed", name: entry.id, from, detail: `renamed from "${from}"` });
10632
+ }
10633
+ if (before.audience !== entry.audience) out.push({ kind: "changed", name: entry.id, detail: `audience ${before.audience} \u2192 ${entry.audience}` });
10634
+ const oldMethods = new Map(before.methods.map((m) => [m.name, m]));
10635
+ const usedOld = /* @__PURE__ */ new Set();
10636
+ for (const method of entry.methods) {
10637
+ let was = oldMethods.get(method.name);
10638
+ if (!was) {
10639
+ const from = (method.formerly ?? []).find((f) => oldMethods.has(f) && !entry.methods.some((m) => m.name === f));
10640
+ if (from === void 0) {
10641
+ out.push({ kind: "added", name: entry.id, member: method.name, detail: `method added: ${shownSignature(method)}` });
10642
+ continue;
10643
+ }
10644
+ was = oldMethods.get(from);
10645
+ out.push({ kind: "renamed", name: entry.id, member: method.name, from, detail: `method renamed from "${from}"` });
10646
+ }
10647
+ usedOld.add(was.name);
10648
+ const a = memberDigest(older, before.id, was.name);
10649
+ const b = memberDigest(newer, entry.id, method.name);
10650
+ const sameShape = a === b || memberDigest({ ...newer, interfaces: newer.interfaces.map((e) => e !== entry ? e : { ...e, id: before.id, methods: e.methods.map((m) => m === method ? { ...m, name: was.name } : m) }) }, before.id, was.name) === a;
10651
+ if (!sameShape) out.push({ kind: "changed", name: entry.id, member: method.name, detail: `signature ${shownSignature(was)} \u2192 ${shownSignature(method)}` });
10652
+ }
10653
+ for (const m of before.methods) {
10654
+ if (!usedOld.has(m.name)) out.push({ kind: "removed", name: entry.id, member: m.name, detail: `method removed: ${shownSignature(m)}` });
10655
+ }
10656
+ }
10657
+ for (const t of newer.exportedTypes ?? []) {
10658
+ let before = oldTypes.get(t.id);
10659
+ if (!before) {
10660
+ const def = newer.types.find((d) => d.id === t.type);
10661
+ const from = formerOf(def?.formerly, (n) => oldTypes.has(n) && !(newer.exportedTypes ?? []).some((x) => x.id === n));
10662
+ if (from === void 0) {
10663
+ out.push({ kind: "added", name: t.id, detail: `type added (${def?.kind ?? "type"}), audience ${t.audience}` });
10664
+ continue;
10665
+ }
10666
+ consumed.add(from);
10667
+ before = oldTypes.get(from);
10668
+ out.push({ kind: "renamed", name: t.id, from, detail: `type renamed from "${from}"` });
10669
+ }
10670
+ if (before.audience !== t.audience) out.push({ kind: "changed", name: t.id, detail: `audience ${before.audience} \u2192 ${t.audience}` });
10671
+ const a = memberDigest(older, before.id, "type");
10672
+ const b = memberDigest(newer, t.id, "type");
10673
+ if (a !== b && before.id === t.id) out.push({ kind: "changed", name: t.id, detail: "type shape changed (fields, values, held primitive or a type it names)" });
10674
+ }
10675
+ for (const e of older.interfaces) {
10676
+ if (!newer.interfaces.some((x) => x.id === e.id) && !consumed.has(e.id)) out.push({ kind: "removed", name: e.id, detail: `removed: ${e.type} contract with ${e.methods.length} method(s)` });
10677
+ }
10678
+ for (const t of older.exportedTypes ?? []) {
10679
+ if (!(newer.exportedTypes ?? []).some((x) => x.id === t.id) && !consumed.has(t.id)) out.push({ kind: "removed", name: t.id, detail: "type removed" });
10680
+ }
10681
+ return out.sort((x, y) => x.name === y.name ? (x.member ?? "") < (y.member ?? "") ? -1 : (x.member ?? "") > (y.member ?? "") ? 1 : 0 : x.name < y.name ? -1 : 1);
10682
+ }
10521
10683
 
10522
10684
  // src/models/exports.ts
10523
10685
  var PUBLIC_NAME_RE = /^[a-z0-9_-]+$/;
@@ -10731,9 +10893,9 @@ function shortestPath(from, to, members, edges, rank) {
10731
10893
  const v = queue.shift();
10732
10894
  for (const w of [...edges.get(v) ?? []].filter((x) => members.has(x)).sort((a, b) => rank(a) - rank(b))) {
10733
10895
  if (w === to) {
10734
- const path49 = [w];
10735
- for (let at = v; at !== from; at = prev.get(at)) path49.unshift(at);
10736
- return path49;
10896
+ const path50 = [w];
10897
+ for (let at = v; at !== from; at = prev.get(at)) path50.unshift(at);
10898
+ return path50;
10737
10899
  }
10738
10900
  if (seen.has(w)) continue;
10739
10901
  seen.add(w);
@@ -10774,7 +10936,9 @@ var DesignParamSchema = import_zod9.z.object({
10774
10936
  name: import_zod9.z.string(),
10775
10937
  type: DesignTypeRefSchema,
10776
10938
  optional: import_zod9.z.boolean(),
10777
- description: import_zod9.z.string().optional()
10939
+ description: import_zod9.z.string().optional(),
10940
+ /** The parameter's rename trace (its previousNames), oldest first; absent when never renamed. */
10941
+ formerly: import_zod9.z.array(import_zod9.z.string()).optional()
10778
10942
  }).passthrough();
10779
10943
  var DesignFieldSchema = import_zod9.z.object({
10780
10944
  name: import_zod9.z.string(),
@@ -16220,7 +16384,12 @@ var externalsFileAdapter = {
16220
16384
  return parsed.data;
16221
16385
  },
16222
16386
  writeLock(lock) {
16223
- writeAtomically(lockPath2(getProjectRoot()), serializeYaml(sortedLock(lock)));
16387
+ const file = lockPath2(getProjectRoot());
16388
+ if (Object.keys(lock.externals).length === 0 && lock.parent === void 0) {
16389
+ if (fs8.existsSync(file)) fs8.unlinkSync(file);
16390
+ return;
16391
+ }
16392
+ writeAtomically(file, serializeYaml(sortedLock(lock)));
16224
16393
  },
16225
16394
  readSnapshot(alias) {
16226
16395
  const file = snapshotPath(getProjectRoot(), alias);
@@ -16944,7 +17113,8 @@ function projectParams(params, position, keys, owner) {
16944
17113
  name: p.name,
16945
17114
  type: typeRef(p.type, position, keys, owner),
16946
17115
  optional: p.optional === true,
16947
- ...p.description !== void 0 ? { description: p.description } : {}
17116
+ ...p.description !== void 0 ? { description: p.description } : {},
17117
+ ...p.previousNames && p.previousNames.length > 0 ? { formerly: [...p.previousNames] } : {}
16948
17118
  }));
16949
17119
  }
16950
17120
  function exportEntry(e, keys) {
@@ -16993,7 +17163,8 @@ function memberDependency(member, lock, keys) {
16993
17163
  projectId: member.projectId,
16994
17164
  role: "member",
16995
17165
  ...digest3 !== void 0 ? { digest: digest3 } : {},
16996
- uses: [...keys.used.get(member.alias) ?? []].sort(compareOrdinal)
17166
+ // Every public name a reference reaches (the graph's), and every one the projection wrote.
17167
+ uses: [.../* @__PURE__ */ new Set([...member.referenced ?? [], ...keys.used.get(member.alias) ?? []])].sort(compareOrdinal)
16997
17168
  };
16998
17169
  }
16999
17170
  function directMembers(family) {
@@ -17014,7 +17185,8 @@ function directMembers(family) {
17014
17185
  for (const key2 of bound2(target)) if (!names.has(key2)) names.set(key2, name);
17015
17186
  }
17016
17187
  }
17017
- return { alias, projectId: n.id ?? alias, names };
17188
+ const referenced = [...new Set(family.references.filter((r) => r.consumer === "" && r.producer === n.namespace && r.publicName !== void 0).map((r) => r.publicName))];
17189
+ return { alias, projectId: n.id ?? alias, names, referenced };
17018
17190
  }).sort((a, b) => compareOrdinal(a.alias, b.alias));
17019
17191
  }
17020
17192
  function subsystemSection(s, table, keys) {
@@ -17324,6 +17496,8 @@ function computeTypeClosure(entries, types, exported = []) {
17324
17496
  ...t.kind === "enum" ? { values: (t.values ?? []).map((v) => ({ name: v.name, ...v.description ? { description: v.description } : {} })) } : {},
17325
17497
  // A named scalar travels with the primitive it holds.
17326
17498
  ...t.holds !== void 0 ? { holds: t.holds } : {},
17499
+ // Its own methods (checked constructors, pure operations) travel with it — carried, never digested.
17500
+ ...t.methods && t.methods.length ? { methods: t.methods.map((m) => ({ name: m.name, signature: m.signature, ...m.params ? { params: m.params.map(describe2) } : {}, returns: m.returns, ...m.description ? { description: m.description } : {} })) } : {},
17327
17501
  // A renamed type travels with its rename trace — provenance, outside every digest.
17328
17502
  ...formerlyOf(t.previousIds, t.id)
17329
17503
  };
@@ -17438,7 +17612,8 @@ function canonicalReferences(snapshot) {
17438
17612
  ...def,
17439
17613
  fields: def.fields.map((f) => ({ ...f, type: canon(f.type) })),
17440
17614
  ...def.params ? { params: def.params.map((p) => ({ ...p, type: canon(p.type) })) } : {},
17441
- ...def.returns !== void 0 ? { returns: canon(def.returns) } : {}
17615
+ ...def.returns !== void 0 ? { returns: canon(def.returns) } : {},
17616
+ ...def.methods ? { methods: def.methods.map((m) => ({ ...m, returns: canon(m.returns), ...m.params ? { params: m.params.map((p) => ({ ...p, type: canon(p.type) })) } : {} })) } : {}
17442
17617
  }))
17443
17618
  };
17444
17619
  }
@@ -17608,9 +17783,17 @@ function exportResult(snapshot, renderedSet, writtenPaths) {
17608
17783
  ...writtenPaths.length ? { writtenPaths } : {}
17609
17784
  };
17610
17785
  }
17786
+ var OpenApiNotApplicableError = class extends Error {
17787
+ constructor(snapshot, maxAudience) {
17788
+ const kinds = [...new Set(snapshot.interfaces.map((e) => e.type))].sort();
17789
+ super(snapshot.interfaces.length ? `OpenAPI does not apply to "${snapshot.projectName}": it exposes no HTTP Portal at audience \u2265 ${maxAudience} \u2014 its ${snapshot.interfaces.length} exported interface(s) are ${kinds.join(", ")}${kinds.includes("InProcess") ? " (an in-process library is called, not requested over HTTP)" : ""}, which no OpenAPI document describes. Nothing was written; export the native snapshot (\`--format native\`) instead.` : `OpenAPI does not apply to "${snapshot.projectName}": it exports no interface at audience \u2265 ${maxAudience}. Nothing was written.`);
17790
+ this.name = "OpenApiNotApplicableError";
17791
+ }
17792
+ };
17611
17793
  function exportSurface(maxAudience, format, outPath, portalId) {
17612
17794
  const snapshot = projectOwnSurface(maxAudience);
17613
17795
  let renderedSet = format === "openapi" ? toOpenApiSet(snapshot) : void 0;
17796
+ if (renderedSet && renderedSet.length === 0 && outPath) throw new OpenApiNotApplicableError(snapshot, maxAudience);
17614
17797
  if (renderedSet && portalId) renderedSet = selectPortalSpec(renderedSet, portalId);
17615
17798
  const writtenPaths = outPath ? writeSurfaceFile(outPath, snapshot, renderedSet) : [];
17616
17799
  return exportResult(snapshot, renderedSet, writtenPaths);
@@ -17697,22 +17880,57 @@ function producerUnreadable(external) {
17697
17880
  return null;
17698
17881
  }
17699
17882
  function projectProducer(external) {
17700
- const snapshot = runWithProjectRoot(external.directory, () => projectOwnSurface(external.audience));
17883
+ const snapshot = runWithProjectRoot(external.directory, () => {
17884
+ const projected = projectOwnSurface(external.audience);
17885
+ const broken = getLoaderIssues().find((i) => i.code === "SCHEMA_VALIDATION_ERROR");
17886
+ if (broken) throw new Error(unreadableReason(broken.message));
17887
+ return projected;
17888
+ });
17701
17889
  return { ...snapshot, audience: external.audience };
17702
17890
  }
17703
- function usedDigests(usage, snapshot) {
17891
+ var PRODUCER_UNREADABLE = "producer unreadable";
17892
+ function unreadableReason(message) {
17893
+ const parsed = /^Failed to parse (?:\w+ )?spec(?: "([^"]+)")?: ([\s\S]*)$/.exec(message);
17894
+ if (!parsed) return `${PRODUCER_UNREADABLE}: ${message}`;
17895
+ return `${PRODUCER_UNREADABLE}: ${parsed[1] ?? "its system spec"}: ${parsed[2]}`;
17896
+ }
17897
+ function readFailure(e) {
17898
+ const detail = e instanceof Error ? e.message : String(e);
17899
+ return detail.startsWith(PRODUCER_UNREADABLE) ? detail : `the producer could not be read: ${detail}`;
17900
+ }
17901
+ function usedDigests(usage, snapshot, alias) {
17704
17902
  const used = {};
17705
17903
  const missing = [];
17904
+ const uncarried = [];
17706
17905
  for (const use of usage?.used ?? []) {
17707
17906
  const members = {};
17708
17907
  for (const member of use.members) {
17709
17908
  const digest3 = memberDigest(snapshot, use.publicName, member);
17710
- if (digest3 === null) missing.push(`${use.publicName}.${member}`);
17711
- else members[member] = digest3;
17909
+ if (digest3 !== null) {
17910
+ members[member] = digest3;
17911
+ continue;
17912
+ }
17913
+ missing.push(`${use.publicName}.${member}`);
17914
+ uncarried.push({
17915
+ specId: use.specs?.[0] ?? "",
17916
+ position: "uncarried",
17917
+ target: `${alias}::${use.publicName}`,
17918
+ ...member !== "type" ? { member } : {},
17919
+ consumer: usage?.consumer ?? "",
17920
+ producer: alias,
17921
+ authored: `${alias}::${use.publicName}`
17922
+ });
17712
17923
  }
17713
17924
  used[use.publicName] = members;
17714
17925
  }
17715
- return { used, missing };
17926
+ return { used, missing, uncarried };
17927
+ }
17928
+ function pinDetail(missing, usedNames, alias) {
17929
+ if (missing.length) return { detail: `used members the snapshot does not carry cannot be pinned: ${missing.join(", ")}` };
17930
+ if (usedNames === 0) {
17931
+ return { detail: `no spec references "${alias}" yet, so this pin records none of its uses \u2014 it fills on the next pin (\`wairon externals pin ${alias}\` / sdd_pin_externals) once a spec writes \`${alias}::<name>\`; until then each new reference reads "used now, but not in the lock"` };
17932
+ }
17933
+ return {};
17716
17934
  }
17717
17935
  function pinBinding(binding, lock) {
17718
17936
  const { external } = binding;
@@ -17723,14 +17941,13 @@ function pinBinding(binding, lock) {
17723
17941
  if (unreadable) throw new Error(unreadable);
17724
17942
  snapshot = projectProducer(external);
17725
17943
  } catch (e) {
17726
- const detail = e instanceof Error ? e.message : String(e);
17727
- return { pin: { alias: external.alias, outcome: "unreachable", project: external.project, usedNames: 0, unexported, detail: `the producer could not be read: ${detail}; its previous pin stays` }, changed: false };
17944
+ return { pin: { alias: external.alias, outcome: "unreachable", project: external.project, usedNames: 0, unexported, detail: `${readFailure(e)}; its previous pin stays` }, changed: false };
17728
17945
  }
17729
17946
  const digest3 = contentDigest(snapshot);
17730
17947
  const pinned = externalsRepository.readSnapshot(external.alias);
17731
17948
  const snapshotChanged = !pinned || contentDigest(pinned) !== digest3 || pinnedContentKey(pinned) !== pinnedContentKey(snapshot);
17732
17949
  if (snapshotChanged) externalsRepository.saveSnapshot(external.alias, snapshot);
17733
- const { used, missing } = usedDigests(binding.usage, snapshot);
17950
+ const { used, missing, uncarried } = usedDigests(binding.usage, snapshot, external.alias);
17734
17951
  const entry = {
17735
17952
  project: external.project,
17736
17953
  snapshot: `.wai/externals/${external.alias}.yaml`,
@@ -17749,8 +17966,8 @@ function pinBinding(binding, lock) {
17749
17966
  snapshot: entry.snapshot,
17750
17967
  digest: digest3,
17751
17968
  usedNames: Object.keys(used).length,
17752
- unexported,
17753
- ...missing.length ? { detail: `used members the snapshot does not carry cannot be pinned: ${missing.join(", ")}` } : {}
17969
+ unexported: [...unexported, ...uncarried],
17970
+ ...pinDetail(missing, Object.keys(used).length, external.alias)
17754
17971
  },
17755
17972
  changed: entryChanged
17756
17973
  };
@@ -17914,6 +18131,13 @@ function compareUses(entry, usage, live) {
17914
18131
  }
17915
18132
  }
17916
18133
  for (const ref of usage?.unexported ?? []) {
18134
+ const spelled = ref.authored.split("::");
18135
+ const name = spelled.length === 2 ? spelled[1] : spelled.length === 1 ? spelled[0] : void 0;
18136
+ if (name !== void 0 && !liveHas(name) && !uses.some((u) => u.publicName === name && (u.member === ref.member || u.member === void 0))) {
18137
+ const gone = goneUse(live, name, ref.member, void 0);
18138
+ uses.push(gone);
18139
+ continue;
18140
+ }
17917
18141
  uses.push({ member: ref.member, state: "unavailable", code: CHECK_UNAVAILABLE, detail: `"${ref.specId}" reaches "${ref.target}", which the producer does not export \u2014 nothing to compare` });
17918
18142
  }
17919
18143
  return uses;
@@ -17946,7 +18170,7 @@ function externalStatus(binding, lock) {
17946
18170
  try {
17947
18171
  live = projectProducer(external);
17948
18172
  } catch (e) {
17949
- reason = `the producer could not be read: ${e instanceof Error ? e.message : String(e)}`;
18173
+ reason = readFailure(e);
17950
18174
  }
17951
18175
  }
17952
18176
  if (!live) {
@@ -18011,6 +18235,23 @@ function listExternals() {
18011
18235
  }));
18012
18236
  return [...declared, ...orphans];
18013
18237
  }
18238
+ function listConsumers2(search) {
18239
+ return listConsumers(search);
18240
+ }
18241
+ function diff(against) {
18242
+ const current2 = projectOwnSurface("project");
18243
+ let baseline;
18244
+ let label7;
18245
+ if (against !== void 0 && fs9.existsSync(against) && fs9.statSync(against).isFile()) {
18246
+ baseline = SurfaceSnapshotSchema.parse(parseYaml(fs9.readFileSync(against, "utf8"), against));
18247
+ label7 = `the saved surface ${path14.resolve(against)}`;
18248
+ } else {
18249
+ const revision = approvedRevision(against);
18250
+ baseline = runWithProjectRoot(revision.directory, () => projectOwnSurface("project"));
18251
+ label7 = revision.label;
18252
+ }
18253
+ return { project: current2.projectId ?? current2.projectName, against: label7, changes: surfaceChanges(current2, baseline) };
18254
+ }
18014
18255
  function pinnedContentKey(snapshot) {
18015
18256
  const { stateId: _s, generatedAt: _g, origin: _o, ...content } = SurfaceSnapshotSchema.parse(JSON.parse(JSON.stringify(snapshot)));
18016
18257
  return canonicalize({ ...content, interfaces: content.interfaces.map((e) => ({ ...e, component: lastSegment2(e.component) })) });
@@ -18188,8 +18429,8 @@ function remove(alias, dryRun) {
18188
18429
  }
18189
18430
  if (dryRun) return { alias, removed: false, unpinned: pinned };
18190
18431
  const removed = declared ? removeExternal(alias) : false;
18191
- const unpinned = unpin(alias);
18192
- return { alias, removed, unpinned };
18432
+ const unpinned2 = unpin(alias);
18433
+ return { alias, removed, unpinned: unpinned2 };
18193
18434
  }
18194
18435
  function currentUse(declaration2) {
18195
18436
  const use = typeof declaration2 === "object" && declaration2 !== null ? declaration2.use : void 0;
@@ -18287,6 +18528,12 @@ function removeExternal2(alias, dryRun) {
18287
18528
  function updateExternalUse(request) {
18288
18529
  return updateUse(request);
18289
18530
  }
18531
+ function listConsumers3(search) {
18532
+ return listConsumers2(search);
18533
+ }
18534
+ function diffSurface(against) {
18535
+ return diff(against);
18536
+ }
18290
18537
 
18291
18538
  // src/core/family-validation.ts
18292
18539
  var fs11 = __toESM(require("fs"));
@@ -18394,12 +18641,12 @@ function findingChanges(before, after) {
18394
18641
  }
18395
18642
 
18396
18643
  // src/core/rules/reach-model-projector.ts
18397
- function underBase(basePath, path49) {
18644
+ function underBase(basePath, path50) {
18398
18645
  const base = (basePath ?? "").trim().replace(/\/+$/, "");
18399
- if (base === "" || base === "/") return path49;
18646
+ if (base === "" || base === "/") return path50;
18400
18647
  const head2 = base.startsWith("/") ? base : `/${base}`;
18401
- if (path49 === head2 || path49.startsWith(`${head2}/`)) return path49;
18402
- return `${head2}${path49.startsWith("/") ? "" : "/"}${path49}`;
18648
+ if (path50 === head2 || path50.startsWith(`${head2}/`)) return path50;
18649
+ return `${head2}${path50.startsWith("/") ? "" : "/"}${path50}`;
18403
18650
  }
18404
18651
  function bindingLine(endpoint, basePath) {
18405
18652
  if (!endpoint) return void 0;
@@ -18521,6 +18768,7 @@ function project(ctx, network) {
18521
18768
  ...c.subsystem !== void 0 ? { subsystem: c.subsystem } : {},
18522
18769
  componentType: c.componentType
18523
18770
  }));
18771
+ const subsystems = ctx.subsystems.map((s) => s.id).filter((id) => !ctx.isInChainedSubproject(id)).sort();
18524
18772
  const topics = [];
18525
18773
  const addEnd = (component, topic, emits) => {
18526
18774
  if (!topics.some((t) => t.component === component && t.topic === topic && t.emits === emits)) topics.push({ component, topic, emits });
@@ -18540,7 +18788,8 @@ function project(ctx, network) {
18540
18788
  verbs: verbs.sort(byVerb),
18541
18789
  calls: calls.sort(byCall),
18542
18790
  placements: placements.sort(byPlacement),
18543
- topics: topics.sort(byTopic)
18791
+ topics: topics.sort(byTopic),
18792
+ subsystems
18544
18793
  };
18545
18794
  }
18546
18795
  function qualify(projectKey, local) {
@@ -18577,7 +18826,9 @@ function compose(models, family, networks) {
18577
18826
  const calls = [];
18578
18827
  const placements = [];
18579
18828
  const topics = [];
18829
+ const subsystems = [];
18580
18830
  for (const [key2, model] of models) {
18831
+ for (const s of model.subsystems ?? []) subsystems.push(qualify(key2, s));
18581
18832
  for (const p of model.placements ?? []) {
18582
18833
  placements.push({
18583
18834
  ...p,
@@ -18639,7 +18890,8 @@ function compose(models, family, networks) {
18639
18890
  verbs: verbs.sort(byVerb),
18640
18891
  calls: calls.sort(byCall),
18641
18892
  placements: placements.sort(byPlacement),
18642
- topics: topics.sort(byTopic)
18893
+ topics: topics.sort(byTopic),
18894
+ subsystems: subsystems.sort()
18643
18895
  };
18644
18896
  }
18645
18897
 
@@ -18668,6 +18920,7 @@ function judge(model) {
18668
18920
  out.push({ severity, code, message, specId: v.portal, at: v.verb });
18669
18921
  };
18670
18922
  const family = model.scope === "family";
18923
+ const bypassed = /* @__PURE__ */ new Map();
18671
18924
  const byPortal = /* @__PURE__ */ new Map();
18672
18925
  for (const v of model.verbs) byPortal.set(v.portal, [...byPortal.get(v.portal) ?? [], v]);
18673
18926
  for (const v of model.verbs) {
@@ -18676,12 +18929,7 @@ function judge(model) {
18676
18929
  if (entry) {
18677
18930
  const scope = entry.scope ?? "outside";
18678
18931
  if (scope === "outside" && v.network !== void 0 && !v.gateway) {
18679
- verbFinding(
18680
- "error",
18681
- "GATEWAY_BYPASSED",
18682
- v,
18683
- `Portal verb "${verbName(v)}" sits inside ${networkName(v.network)} and is entered from outside it, but "${v.portal}" is not a gateway: callers from outside a network reach it only through its gateway. Make the Portal a gateway (variant: gateway), or route the outside callers through the network's gateway and narrow this entry to scope network.`
18684
- );
18932
+ bypassed.set(v.portal, [...bypassed.get(v.portal) ?? [], v]);
18685
18933
  }
18686
18934
  if (scope === "network" && v.network === void 0 && family) {
18687
18935
  verbFinding(
@@ -18735,9 +18983,27 @@ function judge(model) {
18735
18983
  at: call.evidence
18736
18984
  });
18737
18985
  }
18986
+ for (const [portal, verbs] of bypassed) {
18987
+ const network = verbs[0].network;
18988
+ const gatewayless = (model.networks.find((n) => n.id === network)?.gateways.length ?? 0) === 0;
18989
+ const names = verbs.map((v) => `"${v.verb}"`).join(", ");
18990
+ const subject = verbs.length === 1 ? `Portal verb "${verbName(verbs[0])}"` : `Portal "${portal}" (${verbs.length} verbs: ${names})`;
18991
+ const way = gatewayless ? ` ${networkName(network).replace(/^t/, "T")} has no gateway yet: in a project that runs as one process, the Portal its callers enter IS the gateway \u2014 mark "${portal}" variant: gateway. Otherwise route the outside callers through the network's gateway and narrow ${verbs.length === 1 ? "this entry" : "these entries"} to scope network.` : ` Make the Portal a gateway (variant: gateway), or route the outside callers through the network's gateway and narrow ${verbs.length === 1 ? "this entry" : "these entries"} to scope network.`;
18992
+ out.push({
18993
+ severity: "error",
18994
+ code: "GATEWAY_BYPASSED",
18995
+ message: `${subject} sits inside ${networkName(network)} and is entered from outside it, but "${portal}" is not a gateway: callers from outside a network reach it only through its gateway.${way}`,
18996
+ specId: portal,
18997
+ ...verbs.length === 1 ? { at: verbs[0].verb } : { covers: verbs.map((v) => v.verb) }
18998
+ });
18999
+ }
19000
+ const localName3 = (key2) => key2.includes("::") ? key2.slice(key2.lastIndexOf("::") + 2) : key2;
19001
+ const byLocal = (a, b) => localName3(a).localeCompare(localName3(b)) || a.localeCompare(b);
18738
19002
  for (const n of model.networks) {
18739
19003
  if (n.gateways.length < 2) continue;
18740
- const first = [...n.gateways].sort()[0];
19004
+ const ownProject = (g) => model.verbs.some((v) => v.portal === g && v.project === n.id);
19005
+ const own2 = n.gateways.filter(ownProject);
19006
+ const first = [...own2.length > 0 ? own2 : n.gateways].sort(byLocal)[0];
18741
19007
  out.push({
18742
19008
  severity: "notice",
18743
19009
  code: "MULTIPLE_GATEWAYS",
@@ -18753,6 +19019,8 @@ var FAMILY_SEVERITY = {
18753
19019
  EXTERNAL_INCOMPATIBLE: "error",
18754
19020
  EXTERNAL_DRIFTED: "notice",
18755
19021
  EXTERNAL_CHECK_UNAVAILABLE: "warning",
19022
+ // A used external never pinned: nothing records what it is judged against, so the composed gate fails.
19023
+ EXTERNAL_UNPINNED: "error",
18756
19024
  // The advisory live comparison (advise): printed and counted, never part of a failure decision.
18757
19025
  EXTERNAL_LIVE_INCOMPATIBLE: "warning",
18758
19026
  EXTERNAL_LIVE_UNCOMPARED: "notice",
@@ -18763,6 +19031,8 @@ var FAMILY_SEVERITY = {
18763
19031
  MEMBER_DRIFTED: "warning",
18764
19032
  PROJECT_ID_DEFAULTED: "notice",
18765
19033
  PROJECT_ID_AMBIGUOUS: "warning",
19034
+ // A family-only re-export from another project that no project of the family reaches: dead.
19035
+ EXPORT_UNREACHED: "notice",
18766
19036
  // Governance: a required pack not adopted blocks; a member's deviation from
18767
19037
  // an adopted pack's settings is visible and never fails --ci.
18768
19038
  POLICY_NOT_ADOPTED: "error",
@@ -18986,7 +19256,16 @@ function judgeExternal(status, project3, config) {
18986
19256
  project3
18987
19257
  ));
18988
19258
  }
18989
- for (const use of status.uses.filter((u) => u.state === "unavailable" || u.state === "unlocked")) {
19259
+ const neverPinned = status.pinnedDigest === void 0 ? status.uses.filter((u) => u.state === "unlocked") : [];
19260
+ if (neverPinned.length > 0) {
19261
+ out.push(finding(
19262
+ config,
19263
+ "EXTERNAL_UNPINNED",
19264
+ `${named(project3)} uses ${neverPinned.map(useName).join(", ")} of ${who}, which was never pinned: nothing records what its design is judged against, so a producer change that breaks it cannot be told. Pin it (\`wairon externals pin ${status.alias}\`) \u2014 and \`wairon lock\` refuses until it is.`,
19265
+ project3
19266
+ ));
19267
+ }
19268
+ for (const use of status.uses.filter((u) => u.state === "unavailable" || u.state === "unlocked" && !neverPinned.includes(u))) {
18990
19269
  const why2 = use.detail ?? (use.state === "unlocked" ? "used now, but not in the lock" : "nothing to compare");
18991
19270
  out.push(finding(
18992
19271
  config,
@@ -19184,11 +19463,54 @@ function checkMembers(family) {
19184
19463
  ref.parent
19185
19464
  ));
19186
19465
  }
19466
+ out.push(...unreachedReexports(family, config));
19187
19467
  for (const entry of projectApprovals()) {
19188
19468
  if (present.has(entry.key) && entry.as !== "part") out.push(approvalFinding(entry, config));
19189
19469
  }
19190
19470
  return out.filter((f) => f !== null);
19191
19471
  }
19472
+ function aliasTargets(node) {
19473
+ const out = new Map(node.aliases);
19474
+ for (const e of node.externals) if (e.sourceKind === "family" && e.producer !== void 0 && !out.has(e.alias)) out.set(e.alias, e.producer);
19475
+ return out;
19476
+ }
19477
+ function unreachedReexports(family, config) {
19478
+ const out = [];
19479
+ const keys = new Set(family.nodes.map((n) => n.namespace));
19480
+ const reached = /* @__PURE__ */ new Map();
19481
+ for (const ref of family.authoredReferences) {
19482
+ const owner = family.nodes.find((n) => n.namespace === (family.owners.get(ref.specId) ?? ""));
19483
+ if (!owner) continue;
19484
+ const targets = aliasTargets(owner);
19485
+ const via = ref.form === "import" ? (ref.importedVia ?? "").split(", ") : [ref.authored.split("::")[0]];
19486
+ const name = ref.form === "import" ? ref.authored : ref.authored.split("::")[1];
19487
+ for (const alias of via) {
19488
+ const project3 = targets.get(alias);
19489
+ if (project3 === void 0 || project3 === owner.namespace || name === void 0) continue;
19490
+ reached.set(project3, (reached.get(project3) ?? /* @__PURE__ */ new Set()).add(name));
19491
+ }
19492
+ }
19493
+ for (const node of family.nodes) {
19494
+ let entries;
19495
+ try {
19496
+ entries = resolveProjectExports(node.namespace || void 0).entries;
19497
+ } catch {
19498
+ continue;
19499
+ }
19500
+ for (const entry of entries) {
19501
+ const from = entry.via[0];
19502
+ if ((entry.audience ?? "instance") !== "project" || from === void 0 || from === node.namespace || !keys.has(from)) continue;
19503
+ if (reached.get(node.namespace)?.has(entry.publicName)) continue;
19504
+ out.push(finding(
19505
+ config,
19506
+ "EXPORT_UNREACHED",
19507
+ `${named(node.namespace)} re-exports "${entry.publicName}" from ${named(from)} at audience project \u2014 the family only \u2014 but no project of the family reaches it through ${named(node.namespace)}: a dead re-export. Drop the entry from its L0 export table (or widen its audience if a consumer outside the family uses it).`,
19508
+ node.namespace
19509
+ ));
19510
+ }
19511
+ }
19512
+ return out;
19513
+ }
19192
19514
  function subjectOf(record) {
19193
19515
  return `${record.stateId.algorithm}:${record.stateId.digest}`;
19194
19516
  }
@@ -19569,6 +19891,10 @@ function checkReach(family, models) {
19569
19891
  return out;
19570
19892
  }
19571
19893
  function reachModel(options) {
19894
+ if (options.memberDepth !== 0) {
19895
+ const enclosing2 = enclosingFamilyModel(options);
19896
+ if (enclosing2) return enclosing2;
19897
+ }
19572
19898
  const family = graph();
19573
19899
  const root = path15.resolve(getProjectRoot());
19574
19900
  const ceiling = reachCeiling(root, options.family);
@@ -19580,7 +19906,44 @@ function reachModel(options) {
19580
19906
  const scoped = narrowed(family, selected);
19581
19907
  return within2(root, ceiling, () => {
19582
19908
  const model = compose(models, scoped, networksOf(scoped));
19583
- return { ...model, findings: judgedFindings(model, scoped) };
19909
+ const config = configOrNull();
19910
+ const externals = Object.keys(config?.externals ?? {});
19911
+ return {
19912
+ ...model,
19913
+ findings: judgedFindings(model, scoped),
19914
+ ...config?.id !== void 0 ? { rootProject: config.id } : {},
19915
+ ...externals.length > 0 ? { externals } : {},
19916
+ ...formerNamesOf(scoped)
19917
+ };
19918
+ });
19919
+ }
19920
+ function formerNamesOf(family) {
19921
+ const out = {};
19922
+ for (const node of family.nodes) {
19923
+ if (node.namespace === "") continue;
19924
+ const previous = runWithProjectRoot(node.directory, configOrNull)?.previousIds ?? [];
19925
+ for (const id of previous) {
19926
+ const former = keyIn(node.parent ?? "", id);
19927
+ if (former !== node.namespace && !family.nodes.some((n) => n.namespace === former)) out[former] = node.namespace;
19928
+ }
19929
+ }
19930
+ return Object.keys(out).length > 0 ? { formerNames: out } : {};
19931
+ }
19932
+ function enclosingFamilyModel(options) {
19933
+ const bound2 = path15.resolve(getProjectRoot());
19934
+ let top = null;
19935
+ let parent = resolveChainingParent();
19936
+ for (let hops = 0; parent && hops < 32; hops++) {
19937
+ top = path15.resolve(parent.parentRoot);
19938
+ parent = runWithProjectRoot(top, resolveChainingParent);
19939
+ }
19940
+ if (top === null) return null;
19941
+ const topRoot = top;
19942
+ return runWithProjectRoot(topRoot, () => {
19943
+ const member = graph().nodes.find((n) => path15.resolve(n.directory) === bound2);
19944
+ if (!member || member.namespace === "") return null;
19945
+ const model = reachModel({ ...options, memberDepth: void 0 });
19946
+ return { ...model, focus: member.namespace, judgedAt: topRoot };
19584
19947
  });
19585
19948
  }
19586
19949
  function judgedFindings(model, family) {
@@ -19603,7 +19966,8 @@ function judgedFindings(model, family) {
19603
19966
  code: f.code,
19604
19967
  message: f.message,
19605
19968
  ...f.specId !== void 0 ? { specId: f.specId } : {},
19606
- ...f.at !== void 0 ? { at: f.at } : {}
19969
+ ...f.at !== void 0 ? { at: f.at } : {},
19970
+ ...f.covers !== void 0 ? { covers: f.covers } : {}
19607
19971
  });
19608
19972
  }
19609
19973
  return out;
@@ -19929,6 +20293,13 @@ var DeclarativePackSchema = import_zod10.z.object({
19929
20293
  * builtin + declared are flagged UNKNOWN_GUARANTEE by the validator.
19930
20294
  */
19931
20295
  guarantees: import_zod10.z.array(import_zod10.z.string().min(1)).default([]),
20296
+ /**
20297
+ * Package names a technology's code is imported by, keyed by the
20298
+ * technology's name — extending the built-in table the code twin of the
20299
+ * leakage rule (TECH_LEAKAGE_IN_CODE) compares imports with, for a driver or
20300
+ * SDK the table does not know. Data only.
20301
+ */
20302
+ technologyPackages: import_zod10.z.record(import_zod10.z.array(import_zod10.z.string().min(1))).default({}),
19932
20303
  /**
19933
20304
  * Connecting-agent guidance appended to wairon's own MCP `initialize`
19934
20305
  * instructions, attributed to this pack, in pack load order.
@@ -19963,7 +20334,7 @@ function globalPacksEnabled(config) {
19963
20334
  return config.extensions?.useGlobalPacks ?? GLOBAL_PACKS_DEFAULT;
19964
20335
  }
19965
20336
  function emptyExtensions() {
19966
- return { packNames: [], packs: [], rules: [], profiles: {}, languages: {}, skills: [], patterns: [], guarantees: [], assertions: [], instructions: [], errors: [], selectionFailures: [], deprecations: [] };
20337
+ return { packNames: [], packs: [], rules: [], profiles: {}, languages: {}, skills: [], patterns: [], guarantees: [], technologyPackages: {}, assertions: [], instructions: [], errors: [], selectionFailures: [], deprecations: [] };
19967
20338
  }
19968
20339
  var FOREIGN_BUILTINS_RETIRED = "nothing replaces it: contracts are written in the neutral type grammar and a language is chosen at L4, so the foreign-builtin check it fed is retired";
19969
20340
  function globalPacksDir() {
@@ -20035,6 +20406,10 @@ function mergePack(out, pack, ref, scope, packDir) {
20035
20406
  for (const s of pack.skills) out.skills.push({ ...s, pack: pack.name, packVersion: pack.version, sourcePath: path17.resolve(packDir, s.source) });
20036
20407
  for (const p of pack.patterns) out.patterns.push({ ...p, pack: pack.name });
20037
20408
  out.guarantees = [.../* @__PURE__ */ new Set([...out.guarantees, ...pack.guarantees])];
20409
+ for (const [tech, packages] of Object.entries(pack.technologyPackages ?? {})) {
20410
+ const key2 = tech.toLowerCase();
20411
+ out.technologyPackages[key2] = [.../* @__PURE__ */ new Set([...out.technologyPackages[key2] ?? [], ...packages.map((p) => p.toLowerCase())])];
20412
+ }
20038
20413
  for (const a of pack.assertions) out.assertions.push({ ...a, pack: pack.name, fullCode: assertionFullCode(pack.name, a.code) });
20039
20414
  for (const b of pack.instructions) out.instructions.push({ ...b, pack: pack.name });
20040
20415
  }
@@ -20067,6 +20442,7 @@ function readManifest(target, resolveFrom) {
20067
20442
  skills: mod.skills ?? [],
20068
20443
  patterns: mod.patterns ?? [],
20069
20444
  guarantees: mod.guarantees ?? [],
20445
+ technologyPackages: mod.technologyPackages ?? {},
20070
20446
  assertions: mod.assertions ?? [],
20071
20447
  instructions: mod.instructions ?? [],
20072
20448
  applyByDefault: mod.applyByDefault ?? false
@@ -20377,21 +20753,21 @@ function buildCodeIndex(model) {
20377
20753
  const declarations = /* @__PURE__ */ new Map();
20378
20754
  const anchors = /* @__PURE__ */ new Map();
20379
20755
  const findingAnchors = /* @__PURE__ */ new Map();
20380
- const namesAt = (cache, path49, make) => {
20381
- const cached = cache.get(path49);
20756
+ const namesAt = (cache, path50, make) => {
20757
+ const cached = cache.get(path50);
20382
20758
  if (cached) return cached;
20383
- const f = facts.get(path49);
20759
+ const f = facts.get(path50);
20384
20760
  if (!f) return NO_NAMES;
20385
20761
  const built = make(f);
20386
- cache.set(path49, built);
20762
+ cache.set(path50, built);
20387
20763
  return built;
20388
20764
  };
20389
20765
  const republished = /* @__PURE__ */ new Map();
20390
- const republishedFrom = (path49) => {
20391
- const cached = republished.get(path49);
20766
+ const republishedFrom = (path50) => {
20767
+ const cached = republished.get(path50);
20392
20768
  if (cached) return cached;
20393
- const out = /* @__PURE__ */ new Set([path49]);
20394
- const queue = [path49];
20769
+ const out = /* @__PURE__ */ new Set([path50]);
20770
+ const queue = [path50];
20395
20771
  while (queue.length) {
20396
20772
  const from = queue.pop();
20397
20773
  const f = facts.get(from);
@@ -20404,7 +20780,7 @@ function buildCodeIndex(model) {
20404
20780
  }
20405
20781
  }
20406
20782
  }
20407
- republished.set(path49, out);
20783
+ republished.set(path50, out);
20408
20784
  return out;
20409
20785
  };
20410
20786
  const NO_ORIGIN = /* @__PURE__ */ new Set();
@@ -20413,9 +20789,9 @@ function buildCodeIndex(model) {
20413
20789
  return to ? republishedFrom(to) : NO_ORIGIN;
20414
20790
  };
20415
20791
  const scopeOf2 = (site, from) => {
20416
- const path49 = pathKey(site.from ?? from);
20417
- const f = facts.get(path49);
20418
- return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path: path49, facts: f } : void 0;
20792
+ const path50 = pathKey(site.from ?? from);
20793
+ const f = facts.get(path50);
20794
+ return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path: path50, facts: f } : void 0;
20419
20795
  };
20420
20796
  const originOf2 = (site, from) => {
20421
20797
  const resolved = scopeOf2(site, from);
@@ -20457,6 +20833,8 @@ function buildCodeIndex(model) {
20457
20833
  const target = aliasTargetOf(decl.facts, decl.name);
20458
20834
  if (target !== void 0) widenThroughAt(decl.path, target, typeBindingOf(decl.facts, target));
20459
20835
  }
20836
+ const declared = declaringModule(at, atFacts, typeName);
20837
+ if (declared) for (const implementor of implementorsOf(declared.path, declared.name)) out.add(implementor);
20460
20838
  };
20461
20839
  if (site.field) for (const typeName of fieldTypesOf(f, site.field)) widenThroughType(typeName);
20462
20840
  if (site.receiverPath && site.receiverPath.length >= 2) {
@@ -20529,8 +20907,43 @@ function buildCodeIndex(model) {
20529
20907
  }
20530
20908
  return found;
20531
20909
  };
20910
+ const moduleKey = (p) => pathKey(p).replace(/\.(d\.)?(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/, "").replace(/\/index$/, "");
20911
+ const declaringModule = (at, atFacts, typeName) => {
20912
+ const specifier = typeBindingOf(atFacts, typeName);
20913
+ if (specifier === void 0) return { path: at, name: typeName };
20914
+ const name = importBindingOf(atFacts, typeName)?.imported ?? typeName;
20915
+ const resolved = resolveImport(at, specifier, paths, packages);
20916
+ if (resolved) return { path: resolved, name };
20917
+ if (!specifier.startsWith(".")) return void 0;
20918
+ const dir = pathKey(at).split("/").slice(0, -1);
20919
+ for (const part of specifier.split("/")) {
20920
+ if (part === "..") dir.pop();
20921
+ else if (part !== "." && part !== "") dir.push(part);
20922
+ }
20923
+ return { path: dir.join("/"), name };
20924
+ };
20925
+ let implementors;
20926
+ const implementorsOf = (path50, typeName) => {
20927
+ if (!implementors) {
20928
+ implementors = /* @__PURE__ */ new Map();
20929
+ for (const [file, f] of facts) {
20930
+ if (f.status !== "analyzed" || f.analysisGrade !== "exact" || !f.implementsClauses) continue;
20931
+ for (const names of Object.values(f.implementsClauses)) {
20932
+ for (const name of names) {
20933
+ const declared = declaringModule(file, f, name);
20934
+ if (!declared) continue;
20935
+ const key2 = `${moduleKey(declared.path)}#${declared.name}`;
20936
+ const set = implementors.get(key2) ?? /* @__PURE__ */ new Set();
20937
+ set.add(file);
20938
+ implementors.set(key2, set);
20939
+ }
20940
+ }
20941
+ }
20942
+ }
20943
+ return implementors.get(`${moduleKey(path50)}#${typeName}`) ?? NO_NAMES;
20944
+ };
20532
20945
  const holdsMemberBody = (f, name, container) => f.status === "analyzed" && f.analysisGrade === "exact" && !!f.functionBodies && Object.prototype.hasOwnProperty.call(f.functionBodies, name) && f.functionBodies[name].some((b) => b.container === container);
20533
- const declarationsAt = (path49) => namesAt(declarations, pathKey(path49), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
20946
+ const declarationsAt = (path50) => namesAt(declarations, pathKey(path50), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
20534
20947
  const forwardsOf = (start, name, container) => {
20535
20948
  const out = [];
20536
20949
  const listed = /* @__PURE__ */ new Set();
@@ -20574,12 +20987,13 @@ function buildCodeIndex(model) {
20574
20987
  paths,
20575
20988
  exactPaths,
20576
20989
  packages,
20577
- factsAt: (path49) => facts.get(pathKey(path49)),
20990
+ factsAt: (path50) => facts.get(pathKey(path50)),
20578
20991
  declarationsAt,
20579
- anchorsAt: (path49) => namesAt(anchors, pathKey(path49), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
20580
- findingAnchorsAt: (path49) => namesAt(findingAnchors, pathKey(path49), (f) => new Set(f.anchoredNames)),
20992
+ anchorsAt: (path50) => namesAt(anchors, pathKey(path50), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
20993
+ findingAnchorsAt: (path50) => namesAt(findingAnchors, pathKey(path50), (f) => new Set(f.anchoredNames)),
20581
20994
  originOf: originOf2,
20582
20995
  possibleOriginsOf,
20996
+ implementorsOf,
20583
20997
  forwardsOf,
20584
20998
  // Realization has begun once any named file is one the run analyzed or
20585
20999
  // could read: missing and escaped are the only statuses that hold nothing.
@@ -20622,10 +21036,10 @@ function buildRealizationIndex(ctx) {
20622
21036
  return {
20623
21037
  paths,
20624
21038
  implementationsOf: (compId) => implsByComponent.get(compId) ?? NONE_IMPL,
20625
- implementationsAt: (path49) => implsByPath.get(pathKey(path49)) ?? NONE_IMPL,
21039
+ implementationsAt: (path50) => implsByPath.get(pathKey(path50)) ?? NONE_IMPL,
20626
21040
  filesOf: (compId) => filesByComponent.get(compId) ?? NONE_PATH,
20627
21041
  filesIn: (subsystemId) => filesBySubsystem.get(subsystemId) ?? NONE_PATH,
20628
- componentsAt: (path49) => componentsByPath.get(pathKey(path49)) ?? NONE_COMP
21042
+ componentsAt: (path50) => componentsByPath.get(pathKey(path50)) ?? NONE_COMP
20629
21043
  };
20630
21044
  }
20631
21045
  function buildImportGraph(index, universe) {
@@ -20637,22 +21051,22 @@ function buildImportGraph(index, universe) {
20637
21051
  if (to && to !== from) into.add(to);
20638
21052
  }
20639
21053
  };
20640
- const importsOf = (path49) => {
20641
- const cached = imports.get(path49);
21054
+ const importsOf = (path50) => {
21055
+ const cached = imports.get(path50);
20642
21056
  if (cached) return cached;
20643
- const facts = index.factsAt(path49);
21057
+ const facts = index.factsAt(path50);
20644
21058
  const out = /* @__PURE__ */ new Set();
20645
- if (facts?.status === "analyzed") resolveAll(path49, facts.imports, out);
20646
- imports.set(path49, out);
21059
+ if (facts?.status === "analyzed") resolveAll(path50, facts.imports, out);
21060
+ imports.set(path50, out);
20647
21061
  return out;
20648
21062
  };
20649
- const tracesOf = (path49) => {
20650
- const cached = traces.get(path49);
21063
+ const tracesOf = (path50) => {
21064
+ const cached = traces.get(path50);
20651
21065
  if (cached) return cached;
20652
- const facts = index.factsAt(path49);
20653
- const out = new Set(importsOf(path49));
20654
- if (facts?.status === "analyzed") resolveAll(path49, facts.reexports, out);
20655
- traces.set(path49, out);
21066
+ const facts = index.factsAt(path50);
21067
+ const out = new Set(importsOf(path50));
21068
+ if (facts?.status === "analyzed") resolveAll(path50, facts.reexports, out);
21069
+ traces.set(path50, out);
20656
21070
  return out;
20657
21071
  };
20658
21072
  return {
@@ -21139,6 +21553,105 @@ var signatureTypeReferencesRule = {
21139
21553
  }
21140
21554
  };
21141
21555
 
21556
+ // src/core/rules/integrity/implements-contracts.ts
21557
+ var implementsContractsRule = {
21558
+ name: "implements-contracts",
21559
+ judges: "design",
21560
+ description: "An interface that declares `implements: alias::name` realizes another project's extension point, so it must declare every method of that contract with the same params and returns, read from the export the reference resolves to (a contained member's live table, or a declared external's pin). Types are compared semantically, never by spelling: the producer's own bare name and the consumer's `alias::name` (or the bare name its `use` imports) are one type. Whether the reference resolves and names an entry exported with role implement is project-boundaries' finding (EXTERNAL_NOT_EXPORTED); this rule judges the shape once it resolves. Extra methods are the implementer's own and are not reported.",
21561
+ codes: [
21562
+ { code: "IMPLEMENTS_MISMATCH", defaultSeverity: "error", summary: "An interface implementing an extension point lacks one of its methods, or declares one with different params or returns" }
21563
+ ],
21564
+ check(ctx) {
21565
+ for (const intf of ctx.interfaces) {
21566
+ if (intf.implements === void 0 || !ctx.isSpecInScope(intf.id)) continue;
21567
+ const point = extensionPoint(ctx, intf.id, intf.implements);
21568
+ if (!point) continue;
21569
+ const { methods: contract, publicNames } = point;
21570
+ const alias = intf.implements.split("::")[0];
21571
+ const expectedType = (t) => respelled(t, (n) => publicNames.get(lastName(n)) ?? lastName(n));
21572
+ const ownType = (t) => respelled(t, (n) => n.startsWith(`${alias}::`) ? lastName(n.slice(alias.length + 2)) : n);
21573
+ const draft = intf.status === "draft" || intf.status === "design" || ctx.isComponentDraft(intf.component);
21574
+ for (const expected of contract) {
21575
+ const own2 = intf.methods.find((m) => m.name === expected.name);
21576
+ const differs = own2 ? difference(expected, own2, expectedType, ownType) : "is not declared";
21577
+ if (!differs) continue;
21578
+ const former = own2 ? void 0 : (expected.formerly ?? []).find((f) => intf.methods.some((m) => m.name === f));
21579
+ const renamedFrom = former ? ` It was renamed from "${former}" (the producer's rename trace), which this interface still declares \u2014 rename it to "${expected.name}".` : "";
21580
+ ctx.addIssue(
21581
+ "error",
21582
+ "IMPLEMENTS_MISMATCH",
21583
+ `Interface "${intf.id}" implements "${intf.implements}", whose method "${expected.name}" ${differs === "is not declared" ? "it does not declare" : `it declares with ${differs}`}.${renamedFrom} An implementation of an extension point declares every method of it with the same signature: ${expected.name}(${(expected.params ?? []).map((p) => `${p.name}${p.optional ? "?" : ""}: ${p.type}`).join(", ")}): ${expected.returns}.`,
21584
+ intf.id,
21585
+ draft,
21586
+ void 0,
21587
+ { at: expected.name }
21588
+ );
21589
+ }
21590
+ }
21591
+ }
21592
+ };
21593
+ function lastName(name) {
21594
+ return name.split("::").pop().split(".").pop();
21595
+ }
21596
+ function renamed(expr, rename) {
21597
+ const args = expr.args.map((a) => renamed(a, rename));
21598
+ return expr.form === "named" || expr.form === "applied" ? { ...expr, name: rename(expr.name), args } : { ...expr, args };
21599
+ }
21600
+ function respelled(text3, rename) {
21601
+ const keep = (name) => isTypeVocabulary(name) ? name : rename(name);
21602
+ const parsed = parseTypeExpression(text3, "returns");
21603
+ if (parsed.expression) return canonicalTypeText(renamed(parsed.expression, keep));
21604
+ return text3.replace(/[A-Za-z_][A-Za-z0-9_-]*(?:(?:::|\.)[A-Za-z_][A-Za-z0-9_-]*)*/g, keep);
21605
+ }
21606
+ function difference(expected, own2, expectedType, ownType) {
21607
+ const shape = (m, as) => (m.params ?? []).map((p) => `${as(p.type)}${p.optional ? "?" : ""}`).join(", ");
21608
+ const written = (m) => (m.params ?? []).map((p) => `${p.type}${p.optional ? "?" : ""}`).join(", ");
21609
+ if (shape(expected, expectedType) !== shape(own2, ownType)) return `params (${written(own2)}) instead of (${written(expected)})`;
21610
+ if (expectedType(expected.returns) !== ownType(own2.returns)) return `returns ${own2.returns} instead of ${expected.returns}`;
21611
+ return void 0;
21612
+ }
21613
+ function extensionPoint(ctx, specId, ref) {
21614
+ const resolution = ctx.resolveCrossProject(specId, "implements", ref);
21615
+ if (resolution && resolution.outcome !== "resolved") return void 0;
21616
+ const surface = ctx.resolveSurfaceRef(ref);
21617
+ if (surface.kind === "resolved") {
21618
+ if (surface.entry.role !== "implement") return void 0;
21619
+ const publicNames2 = /* @__PURE__ */ new Map();
21620
+ for (const t of surface.snapshot.exportedTypes ?? []) publicNames2.set(lastName(t.type), t.id);
21621
+ return { methods: surface.entry.methods, publicNames: publicNames2 };
21622
+ }
21623
+ const [alias, name] = ref.split("::");
21624
+ if (name === void 0) return void 0;
21625
+ const bound2 = ctx.projectFamily?.nodes.find((n) => n.namespace === "");
21626
+ const key2 = bound2?.aliases.get(alias);
21627
+ if (key2 === void 0) return void 0;
21628
+ const table = (ctx.exportTables ?? []).find((t) => t.level === "project" && t.owner === key2);
21629
+ const entry = table?.entries.find((e) => e.kind === "component" && e.publicName === name);
21630
+ if (!entry || entry.role !== "implement" || !entry.component) return void 0;
21631
+ const methods = entry.interface ? ctx.interfaceMap.get(entry.interface)?.methods : ctx.interfaceMethodsOf(entry.component);
21632
+ if (!methods) return void 0;
21633
+ const traced2 = methods.map((m) => ({ ...m, formerly: (m.previousNames ?? []).map((k) => k.slice(k.lastIndexOf(".") + 1)) }));
21634
+ const publicNames = /* @__PURE__ */ new Map();
21635
+ for (const e of table?.entries ?? []) {
21636
+ if (e.kind === "type" && e.typeDef) publicNames.set(lastName(e.typeDef), e.publicName);
21637
+ }
21638
+ return { methods: traced2, publicNames };
21639
+ }
21640
+ function renamedExportMethod(ctx, source) {
21641
+ const dot = source.lastIndexOf(".");
21642
+ if (dot <= 0 || !source.includes("::")) return void 0;
21643
+ const [head2, method] = [source.slice(0, dot), source.slice(dot + 1)];
21644
+ const surface = ctx.resolveSurfaceRef(head2);
21645
+ if (surface.kind === "resolved") return surface.entry.methods.find((m) => (m.formerly ?? []).includes(method))?.name;
21646
+ const [alias, name] = head2.split("::");
21647
+ const key2 = ctx.projectFamily?.nodes.find((n) => n.namespace === "")?.aliases.get(alias);
21648
+ if (key2 === void 0 || name === void 0) return void 0;
21649
+ const entry = (ctx.exportTables ?? []).find((t) => t.level === "project" && t.owner === key2)?.entries.find((e) => e.kind === "component" && e.publicName === name);
21650
+ if (!entry?.component) return void 0;
21651
+ const methods = entry.interface ? ctx.interfaceMap.get(entry.interface)?.methods : ctx.interfaceMethodsOf(entry.component);
21652
+ return methods?.find((m) => (m.previousNames ?? []).some((k) => k.slice(k.lastIndexOf(".") + 1) === method))?.name;
21653
+ }
21654
+
21142
21655
  // src/core/rules/integrity/signature-sources.ts
21143
21656
  function draftContext(ctx, interfaceId, component) {
21144
21657
  const intf = ctx.interfaceMap.get(interfaceId);
@@ -21165,17 +21678,20 @@ var signatureSourcesRule = {
21165
21678
  const where = `Method "${fact.method}" on interface "${fact.interfaceId}"`;
21166
21679
  const at = { at: fact.method };
21167
21680
  switch (fact.outcome) {
21168
- case "unresolved":
21681
+ case "unresolved": {
21682
+ const renamed2 = renamedExportMethod(ctx, fact.source);
21683
+ const rename = renamed2 ? ` It was renamed to "${renamed2}" (the producer's rename trace) \u2014 follow the rename: take it from "${fact.source.slice(0, fact.source.lastIndexOf("."))}.${renamed2}".` : "";
21169
21684
  ctx.addIssue(
21170
21685
  "error",
21171
21686
  "SIGNATURE_SOURCE_UNRESOLVED",
21172
- `${where} takes its signature from "${fact.source}", which names ${fact.detail ? `no contract method, and ${fact.detail}` : "no contract method and no signature type"}. A source is a \`component.method\` the component reaches, or a type of kind signature.`,
21687
+ `${where} takes its signature from "${fact.source}", which names ${fact.detail ? `no contract method, and ${fact.detail}` : "no contract method and no signature type"}.${rename} A source is a \`component.method\` the component reaches, an exported contract's method (\`alias::name.method\`), or a type of kind signature.`,
21173
21688
  fact.interfaceId,
21174
21689
  isDraft,
21175
21690
  void 0,
21176
21691
  at
21177
21692
  );
21178
21693
  continue;
21694
+ }
21179
21695
  case "ambiguous":
21180
21696
  ctx.addIssue(
21181
21697
  "error",
@@ -21330,12 +21846,12 @@ function draftContext3(ctx, specId, kind) {
21330
21846
  const sub = type?.subsystem ? ctx.subsystems.find((s) => s.id === type.subsystem) : void 0;
21331
21847
  return !!sub && (sub.status === "draft" || sub.status === "design");
21332
21848
  }
21333
- function siteOf(path49) {
21334
- const [head2, name] = path49.split(".");
21335
- return (head2 === "methods" || head2 === "fields") && name ? name : path49;
21849
+ function siteOf(path50) {
21850
+ const [head2, name] = path50.split(".");
21851
+ return (head2 === "methods" || head2 === "fields") && name ? name : path50;
21336
21852
  }
21337
- function located(kind, specId, path49) {
21338
- return `${path49 ? `"${path49}" of ` : ""}${kind ?? "spec"} "${specId}"`;
21853
+ function located(kind, specId, path50) {
21854
+ return `${path50 ? `"${path50}" of ` : ""}${kind ?? "spec"} "${specId}"`;
21339
21855
  }
21340
21856
  function problemMessage(problem, specId) {
21341
21857
  const where = located(problem.kind, specId, problem.path);
@@ -21616,88 +22132,6 @@ var publicSurfaceConsumersRule = {
21616
22132
  }
21617
22133
  };
21618
22134
 
21619
- // src/core/rules/integrity/implements-contracts.ts
21620
- var implementsContractsRule = {
21621
- name: "implements-contracts",
21622
- judges: "design",
21623
- description: "An interface that declares `implements: alias::name` realizes another project's extension point, so it must declare every method of that contract with the same params and returns, read from the export the reference resolves to (a contained member's live table, or a declared external's pin). Types are compared semantically, never by spelling: the producer's own bare name and the consumer's `alias::name` (or the bare name its `use` imports) are one type. Whether the reference resolves and names an entry exported with role implement is project-boundaries' finding (EXTERNAL_NOT_EXPORTED); this rule judges the shape once it resolves. Extra methods are the implementer's own and are not reported.",
21624
- codes: [
21625
- { code: "IMPLEMENTS_MISMATCH", defaultSeverity: "error", summary: "An interface implementing an extension point lacks one of its methods, or declares one with different params or returns" }
21626
- ],
21627
- check(ctx) {
21628
- for (const intf of ctx.interfaces) {
21629
- if (intf.implements === void 0 || !ctx.isSpecInScope(intf.id)) continue;
21630
- const point = extensionPoint(ctx, intf.id, intf.implements);
21631
- if (!point) continue;
21632
- const { methods: contract, publicNames } = point;
21633
- const alias = intf.implements.split("::")[0];
21634
- const expectedType = (t) => respelled(t, (n) => publicNames.get(lastName(n)) ?? lastName(n));
21635
- const ownType = (t) => respelled(t, (n) => n.startsWith(`${alias}::`) ? lastName(n.slice(alias.length + 2)) : n);
21636
- const draft = intf.status === "draft" || intf.status === "design" || ctx.isComponentDraft(intf.component);
21637
- for (const expected of contract) {
21638
- const own2 = intf.methods.find((m) => m.name === expected.name);
21639
- const differs = own2 ? difference(expected, own2, expectedType, ownType) : "is not declared";
21640
- if (!differs) continue;
21641
- ctx.addIssue(
21642
- "error",
21643
- "IMPLEMENTS_MISMATCH",
21644
- `Interface "${intf.id}" implements "${intf.implements}", whose method "${expected.name}" ${differs === "is not declared" ? "it does not declare" : `it declares with ${differs}`}. An implementation of an extension point declares every method of it with the same signature: ${expected.name}(${(expected.params ?? []).map((p) => `${p.name}${p.optional ? "?" : ""}: ${p.type}`).join(", ")}): ${expected.returns}.`,
21645
- intf.id,
21646
- draft,
21647
- void 0,
21648
- { at: expected.name }
21649
- );
21650
- }
21651
- }
21652
- }
21653
- };
21654
- function lastName(name) {
21655
- return name.split("::").pop().split(".").pop();
21656
- }
21657
- function renamed(expr, rename) {
21658
- const args = expr.args.map((a) => renamed(a, rename));
21659
- return expr.form === "named" || expr.form === "applied" ? { ...expr, name: rename(expr.name), args } : { ...expr, args };
21660
- }
21661
- function respelled(text3, rename) {
21662
- const keep = (name) => isTypeVocabulary(name) ? name : rename(name);
21663
- const parsed = parseTypeExpression(text3, "returns");
21664
- if (parsed.expression) return canonicalTypeText(renamed(parsed.expression, keep));
21665
- return text3.replace(/[A-Za-z_][A-Za-z0-9_-]*(?:(?:::|\.)[A-Za-z_][A-Za-z0-9_-]*)*/g, keep);
21666
- }
21667
- function difference(expected, own2, expectedType, ownType) {
21668
- const shape = (m, as) => (m.params ?? []).map((p) => `${as(p.type)}${p.optional ? "?" : ""}`).join(", ");
21669
- const written = (m) => (m.params ?? []).map((p) => `${p.type}${p.optional ? "?" : ""}`).join(", ");
21670
- if (shape(expected, expectedType) !== shape(own2, ownType)) return `params (${written(own2)}) instead of (${written(expected)})`;
21671
- if (expectedType(expected.returns) !== ownType(own2.returns)) return `returns ${own2.returns} instead of ${expected.returns}`;
21672
- return void 0;
21673
- }
21674
- function extensionPoint(ctx, specId, ref) {
21675
- const resolution = ctx.resolveCrossProject(specId, "implements", ref);
21676
- if (resolution && resolution.outcome !== "resolved") return void 0;
21677
- const surface = ctx.resolveSurfaceRef(ref);
21678
- if (surface.kind === "resolved") {
21679
- if (surface.entry.role !== "implement") return void 0;
21680
- const publicNames2 = /* @__PURE__ */ new Map();
21681
- for (const t of surface.snapshot.exportedTypes ?? []) publicNames2.set(lastName(t.type), t.id);
21682
- return { methods: surface.entry.methods, publicNames: publicNames2 };
21683
- }
21684
- const [alias, name] = ref.split("::");
21685
- if (name === void 0) return void 0;
21686
- const bound2 = ctx.projectFamily?.nodes.find((n) => n.namespace === "");
21687
- const key2 = bound2?.aliases.get(alias);
21688
- if (key2 === void 0) return void 0;
21689
- const table = (ctx.exportTables ?? []).find((t) => t.level === "project" && t.owner === key2);
21690
- const entry = table?.entries.find((e) => e.kind === "component" && e.publicName === name);
21691
- if (!entry || entry.role !== "implement" || !entry.component) return void 0;
21692
- const methods = entry.interface ? ctx.interfaceMap.get(entry.interface)?.methods : ctx.interfaceMethodsOf(entry.component);
21693
- if (!methods) return void 0;
21694
- const publicNames = /* @__PURE__ */ new Map();
21695
- for (const e of table?.entries ?? []) {
21696
- if (e.kind === "type" && e.typeDef) publicNames.set(lastName(e.typeDef), e.publicName);
21697
- }
21698
- return { methods, publicNames };
21699
- }
21700
-
21701
22135
  // src/core/rules/integrity/public-surface-bound-contract.ts
21702
22136
  var publicSurfaceBoundContractRule = {
21703
22137
  name: "public-surface-bound-contract",
@@ -23003,11 +23437,11 @@ var unconditionalCallCyclesRule = {
23003
23437
  const memberEdges = keys.flatMap((k) => (adjacency.get(k) ?? []).filter((e) => inScc.has(e.toKey)));
23004
23438
  if (memberEdges.length === 0) continue;
23005
23439
  const anchor = [...memberEdges].sort((a, b) => byCodeUnit(a.fromKey, b.fromKey) || byCodeUnit(a.impl.id, b.impl.id) || a.stepNumber - b.stepNumber)[0];
23006
- const path49 = [...keys].sort(byCodeUnit).join(" \u2192 ");
23440
+ const path50 = [...keys].sort(byCodeUnit).join(" \u2192 ");
23007
23441
  ctx.addIssue(
23008
23442
  "warning",
23009
23443
  "UNCONDITIONAL_CALL_CYCLE",
23010
- `Call cycle with no guard: ${path49} \u2014 every call edge in this cycle is unavoidable on all paths of its narrative (e.g. step ${anchor.stepNumber} of "${anchor.methodName}" in "${anchor.impl.id}" always calls ${anchor.toLabel}). This recurses without a base case, by construction. Guard at least one edge with a branch/return before the call, or lint.allow with the termination argument.`,
23444
+ `Call cycle with no guard: ${path50} \u2014 every call edge in this cycle is unavoidable on all paths of its narrative (e.g. step ${anchor.stepNumber} of "${anchor.methodName}" in "${anchor.impl.id}" always calls ${anchor.toLabel}). This recurses without a base case, by construction. Guard at least one edge with a branch/return before the call, or lint.allow with the termination argument.`,
23011
23445
  anchor.impl.id,
23012
23446
  memberEdges.some((e) => ctx.isImplementationDraft(e.impl))
23013
23447
  );
@@ -23306,7 +23740,8 @@ var portalsRule = {
23306
23740
  "ENDPOINT_TRANSPORT_MISMATCH",
23307
23741
  `Method "${m.name}" on interface "${intf.id}" declares a "${m.endpoint.transport}" endpoint, but its Portal "${comp.id}" has transport "${comp.transport}" (expects transport "${expected}").`,
23308
23742
  intf.id,
23309
- isDraftCtx || isIntfDraft
23743
+ // A contradiction, not unfinished work: never downgraded on a draft.
23744
+ false
23310
23745
  );
23311
23746
  }
23312
23747
  }
@@ -25890,7 +26325,9 @@ var eventTopologyRule = {
25890
26325
  "UNCONSUMED_TOPIC",
25891
26326
  `Component "${e.compId}" emits topic "${e.topic}" (${e.vias.join(", ")}), but nothing in this tree subscribes to it \u2014 the event goes nowhere. Wire a subscriber (subscribesTo, or a MessageBus subscribe endpoint), fix the topic name, or lint.allow with the external consumer named.`,
25892
26327
  e.compId,
25893
- ctx.isComponentDraft(e.compId)
26328
+ ctx.isComponentDraft(e.compId),
26329
+ void 0,
26330
+ { at: e.topic }
25894
26331
  );
25895
26332
  }
25896
26333
  for (const s of subscribers) {
@@ -25900,7 +26337,9 @@ var eventTopologyRule = {
25900
26337
  "UNSOURCED_SUBSCRIPTION",
25901
26338
  `Component "${s.compId}" subscribes to topic "${s.topic}" (${s.vias.join(", ")}), but nothing in this tree emits it \u2014 the handler can never fire from inside this system. Wire the emitter (emits, or a MessageBus publish endpoint), fix the topic name, or lint.allow with the external source named.`,
25902
26339
  s.compId,
25903
- ctx.isComponentDraft(s.compId)
26340
+ ctx.isComponentDraft(s.compId),
26341
+ void 0,
26342
+ { at: s.topic }
25904
26343
  );
25905
26344
  }
25906
26345
  }
@@ -25914,13 +26353,13 @@ function plannedCode(ctx) {
25914
26353
  var sourceFileLinkageRule = {
25915
26354
  name: "source-file-linkage",
25916
26355
  judges: "code",
25917
- description: "Code\u2194spec Level 1: every source file an implementation names \u2014 its own sourcePath and each method's \u2014 is checked against the code on disk. A named file that is not on disk is PLANNED (SOURCE_FILE_PLANNED, a notice \u2014 planned, not written yet), at method level as at implementation level and whether or not the implementation's realization has begun: a design names the file a method will live in before the method is written, and code linkage, outside the approval since format 3, costs nothing to declare. Whether the realization has BEGUN (code_index.holdsAny over the files it names: at least one exists) decides only how an unlinked method is reported: before that, an implementation that names no file is unlinked design (MISSING_SOURCE_PATH, a notice), so a design-only tree passes `validate --ci`; once it has begun, a contract method that names no file while others do is unlinked (METHOD_SOURCE_PATH_MISSING, a warning), and a method its named, existing file does not hold is method-realization's UNREALIZED_METHOD. A team that wants CI to demand code for every designed implementation sets rules.conformance.requireCode, which reports the two notices at error, naming the setting. A file that escapes the root or cannot be analyzed is reported once and blocks only the methods realized in it. A run in which TypeScript/JavaScript files were analyzed below exact grade reports that once (CONFORMANCE_DEGRADED). Implementations under chained subsystems (projectPath) validate standalone in their own project run and are skipped here.",
26356
+ description: "Code\u2194spec Level 1: every source file an implementation names \u2014 its own sourcePath and each method's \u2014 is checked against the code on disk. A named file that is not on disk is PLANNED (SOURCE_FILE_PLANNED, a notice \u2014 planned, not written yet), at method level as at implementation level and whether or not the implementation's realization has begun: a design names the file a method will live in before the method is written, and code linkage, outside the approval since format 3, costs nothing to declare. The planned files of one implementation are one notice naming them all, so a planned design reads as one line per component, never one per file. Whether the realization has BEGUN (code_index.holdsAny over the files it names: at least one exists) decides only how an unlinked method is reported: before that, an implementation that names no file is unlinked design (MISSING_SOURCE_PATH, a notice), so a design-only tree passes `validate --ci`; once it has begun, a contract method that names no file while others do is unlinked (METHOD_SOURCE_PATH_MISSING, a warning), and a method its named, existing file does not hold is method-realization's UNREALIZED_METHOD. A team that wants CI to demand code for every designed implementation sets rules.conformance.requireCode, which reports the two notices at error, naming the setting. A file that escapes the root or cannot be analyzed is reported once and blocks only the methods realized in it. A run in which TypeScript/JavaScript files were analyzed below exact grade reports that once (CONFORMANCE_DEGRADED). Implementations under chained subsystems (projectPath) validate standalone in their own project run and are skipped here.",
25918
26357
  codes: [
25919
26358
  { code: "MISSING_SOURCE_PATH", defaultSeverity: "notice", summary: "An implementation whose realization has not begun names no source file (for itself or for some contract methods) \u2014 designed, not linked to code yet; an error under rules.conformance.requireCode" },
25920
26359
  { code: "SOURCE_PATH_ESCAPES_ROOT", defaultSeverity: "error", summary: "A source file an implementation or one of its methods names is absolute or escapes the project root (containment refusal)" },
25921
26360
  { code: "CONFORMANCE_ANALYSIS_SKIPPED", defaultSeverity: "warning", summary: "A source file an implementation or one of its methods names could not be analyzed (binary/unreadable) \u2014 realization of the methods in it was not checked" },
25922
26361
  { code: "CONFORMANCE_DEGRADED", defaultSeverity: "warning", summary: "TypeScript/JavaScript files were analyzed below exact grade (compiler not resolvable) \u2014 dependency conformance skips them" },
25923
- { code: "SOURCE_FILE_PLANNED", defaultSeverity: "notice", summary: "A source file an implementation or one of its methods names is not on disk \u2014 planned, not written yet, whether or not another file it names exists; method-realization judges the methods in it once it does. An error under rules.conformance.requireCode" },
26362
+ { code: "SOURCE_FILE_PLANNED", defaultSeverity: "notice", summary: "Source files an implementation or its methods name are not on disk \u2014 planned, not written yet, whether or not another file it names exists; one notice per implementation naming every planned file, and method-realization judges the methods in each once it exists. An error under rules.conformance.requireCode" },
25924
26363
  { code: "METHOD_SOURCE_PATH_MISSING", defaultSeverity: "warning", summary: "An implementation whose realization has begun leaves contract methods without a source file \u2014 structural conformance cannot link them to code" }
25925
26364
  ],
25926
26365
  check(ctx) {
@@ -25972,6 +26411,7 @@ var sourceFileLinkageRule = {
25972
26411
  }
25973
26412
  }
25974
26413
  const reported = /* @__PURE__ */ new Set();
26414
+ const plannedFiles = [];
25975
26415
  for (const file of implementationSourceFiles(impl)) {
25976
26416
  const key2 = pathKey(file);
25977
26417
  if (reported.has(key2)) continue;
@@ -25990,13 +26430,7 @@ var sourceFileLinkageRule = {
25990
26430
  draft
25991
26431
  );
25992
26432
  } else if (facts.status === "missing") {
25993
- ctx.addIssue(
25994
- planned.severity,
25995
- "SOURCE_FILE_PLANNED",
25996
- `${owner} sourcePath "${file}" is planned, not written yet \u2014 method-realization judges the methods in it once it exists${planned.note}.`,
25997
- impl.id,
25998
- draft
25999
- );
26433
+ plannedFiles.push(users.length === 0 ? `"${file}"` : `"${file}" (${users.length === 1 ? "method" : "methods"} ${quoteList(users)})`);
26000
26434
  } else {
26001
26435
  ctx.addIssue(
26002
26436
  "warning",
@@ -26007,6 +26441,16 @@ var sourceFileLinkageRule = {
26007
26441
  );
26008
26442
  }
26009
26443
  }
26444
+ if (plannedFiles.length > 0) {
26445
+ const one = plannedFiles.length === 1;
26446
+ ctx.addIssue(
26447
+ planned.severity,
26448
+ "SOURCE_FILE_PLANNED",
26449
+ `Implementation "${impl.id}" plans ${one ? "a source file" : `${plannedFiles.length} source files`} not written yet: ${plannedFiles.join(", ")} \u2014 method-realization judges the methods in ${one ? "it" : "each"} once it exists${planned.note}.`,
26450
+ impl.id,
26451
+ draft
26452
+ );
26453
+ }
26010
26454
  }
26011
26455
  }
26012
26456
  };
@@ -26420,11 +26864,11 @@ var hiddenStateRule = {
26420
26864
  byPath.set(key2, list);
26421
26865
  }
26422
26866
  }
26423
- for (const path49 of code.exactPaths) {
26424
- const facts = code.factsAt(path49);
26867
+ for (const path50 of code.exactPaths) {
26868
+ const facts = code.factsAt(path50);
26425
26869
  const bindings = facts.topLevelMutableBindings ?? [];
26426
26870
  if (bindings.length === 0) continue;
26427
- const mapped = byPath.get(path49) ?? [];
26871
+ const mapped = byPath.get(path50) ?? [];
26428
26872
  if (mapped.length === 0) continue;
26429
26873
  if (!mapped.every((m) => isStatelessLogic(m.component))) continue;
26430
26874
  const anchor = [...mapped].sort((a, b) => a.impl.id.localeCompare(b.impl.id))[0];
@@ -26441,15 +26885,17 @@ var hiddenStateRule = {
26441
26885
  };
26442
26886
 
26443
26887
  // src/core/rules/conformance/dependency-conformance.ts
26888
+ var import_path = require("path");
26444
26889
  var dependencyConformanceRule = {
26445
26890
  name: "dependency-conformance",
26446
26891
  judges: "code",
26447
- description: "Code\u2194spec Level 2: runtime import edges between component-mapped source files (a component maps to every file its implementations and their methods name) must be justified by declared relations \u2014 a direct dependsOn/owns pair, a shared component, membership in a depended-on pattern, or (across subsystems) a declared edge to the target subsystem's published surface (UNDECLARED_DEPENDENCY). A type-only import alone never accuses \u2014 type coupling is allowed \u2014 but a CALL of a modelled contract method through a collaborator whose declared type comes from another component's file is runtime collaboration whatever the import's form, and the same justification is owed (UNDECLARED_DEPENDENCY on that edge). Conversely, a declared dependsOn/owns edge between components realized in different files should be visible as an import between any file of the source and any file of the target (UNREALIZED_DEPENDENCY \u2014 DI indirection can defeat this, hence warning); a type-only import realizes it, since `import type` is what dependency injection writes down. Two twins of design rules are judged on the same code: a Portal file calling a write- or lifecycle-effect contract method of a Repository, Index, Store or Registry is the persistence shortcut the design rule PORTAL_WRITE_SHORTCUT refuses, unless the Portal's own narrative already claims that call (the design rule's finding then) (PORTAL_WRITE_SHORTCUT_IN_CODE); and a file importing a technology's package \u2014 a bare specifier whose package name is one of the technology's declared tokens (its name, or its `matches` when it declares them), compared exactly, never guessed \u2014 while no component it realizes is that technology's home is the leak TECH_LEAKAGE refuses in the design (TECH_LEAKAGE_IN_CODE). Only exact-grade analyzed files participate; chained subprojects validate standalone.",
26892
+ description: "Code\u2194spec Level 2: runtime import edges between component-mapped source files (a component maps to every file its implementations and their methods name) must be justified by declared relations \u2014 a direct dependsOn/owns pair, a shared component, membership in a depended-on pattern, or (across subsystems) a declared edge to the target subsystem's published surface (UNDECLARED_DEPENDENCY). A type-only import alone never accuses \u2014 type coupling is allowed \u2014 but a CALL of a modelled contract method through a collaborator whose declared type comes from another component's file is runtime collaboration whatever the import's form, and the same justification is owed (UNDECLARED_DEPENDENCY on that edge). Conversely, a declared dependsOn/owns edge between components realized in different files should be visible as an import between any file of the source and any file of the target (UNREALIZED_DEPENDENCY \u2014 DI indirection can defeat this, hence warning); a type-only import realizes it, since `import type` is what dependency injection writes down. An Adapter's edge to a Portal reached over an out-of-process transport (HTTP, gRPC, a bus, a CLI\u2026) is the link the design models: the Adapter's transport client realizes it, never an import, so it is never UNREALIZED_DEPENDENCY \u2014 and nothing asks for an import across that boundary, which would couple two deployables' builds. Two twins of design rules are judged on the same code: a Portal file calling a write- or lifecycle-effect contract method of a Repository, Index, Store or Registry is the persistence shortcut the design rule PORTAL_WRITE_SHORTCUT refuses, unless the Portal's own narrative already claims that call (the design rule's finding then) (PORTAL_WRITE_SHORTCUT_IN_CODE). A receiver is followed through a non-null assertion, parentheses, `satisfies`, a cast to a named type and a local alias or destructuring of a field exactly as through the field itself (a cast to any or unknown says nothing, and is not followed), and a call in a Portal's own file whose receiver the analysis cannot follow at all, under the name of a write- or lifecycle-effect method of a data component in the Portal's own subsystem or one it depends into, is said so rather than passed in silence (PORTAL_CALL_UNRESOLVED). And a file importing a technology's package while no component it realizes is that technology's home is the leak TECH_LEAKAGE refuses in the design (TECH_LEAKAGE_IN_CODE): a technology's packages are its declared tokens (its name, or its `matches`), the curated built-in table of the common packages its name is known by (postgres: pg, postgres, @neondatabase/serverless, @vercel/postgres; mysql: mysql2, mysql; redis: redis, ioredis; mongodb: mongodb, mongoose; sqlite: better-sqlite3, sqlite3; kafka: kafkajs; rabbitmq: amqplib \u2014 an HTTP client is never a technology leak), and the packages a loaded pack contributes for it, each compared exactly with an import's package name. Only exact-grade analyzed files participate; chained subprojects validate standalone.",
26448
26893
  codes: [
26449
26894
  { code: "UNDECLARED_DEPENDENCY", defaultSeverity: "warning", summary: "A runtime import, or a call through a type-only-imported collaborator, between component-mapped files has no declared dependsOn/owns (or published-surface) justification" },
26450
26895
  { code: "PORTAL_WRITE_SHORTCUT_IN_CODE", defaultSeverity: "error", summary: "A Portal's code calls a write- or lifecycle-effect method of a data component (Repository, Index, Store, Registry) directly \u2014 the code twin of PORTAL_WRITE_SHORTCUT: mutations route through an Orchestrator" },
26451
26896
  { code: "TECH_LEAKAGE_IN_CODE", defaultSeverity: "warning", summary: "A source file imports a technology's package although no component it realizes binds that technology \u2014 the code twin of TECH_LEAKAGE" },
26452
- { code: "UNREALIZED_DEPENDENCY", defaultSeverity: "warning", summary: "A declared dependsOn/owns edge between components in different files is realized by no import between any of their files" }
26897
+ { code: "UNREALIZED_DEPENDENCY", defaultSeverity: "warning", summary: "A declared dependsOn/owns edge between components in different files is realized by no import between any of their files" },
26898
+ { code: "PORTAL_CALL_UNRESOLVED", defaultSeverity: "notice", summary: "A call in a Portal's own file, under the name of a data component's write- or lifecycle-effect method, goes through a receiver the analysis cannot follow at all \u2014 neither proven a shortcut nor cleared, and said so rather than passed in silence" }
26453
26899
  ],
26454
26900
  check(ctx) {
26455
26901
  const code = ctx.codeIndex();
@@ -26493,7 +26939,7 @@ var dependencyConformanceRule = {
26493
26939
  }
26494
26940
  return false;
26495
26941
  };
26496
- const draftAt = (path49) => realization.implementationsAt(path49).some((impl) => ctx.isImplementationDraft(impl));
26942
+ const draftAt = (path50) => realization.implementationsAt(path50).some((impl) => ctx.isImplementationDraft(impl));
26497
26943
  for (const fromPath of mappedPaths) {
26498
26944
  const fromComponents = realization.componentsAt(fromPath);
26499
26945
  for (const toPath of graph2.importsOf(fromPath)) {
@@ -26515,16 +26961,16 @@ var dependencyConformanceRule = {
26515
26961
  }
26516
26962
  }
26517
26963
  const typeTraces = /* @__PURE__ */ new Map();
26518
- const typeTracesOf = (path49) => {
26519
- let out = typeTraces.get(path49);
26964
+ const typeTracesOf = (path50) => {
26965
+ let out = typeTraces.get(path50);
26520
26966
  if (out) return out;
26521
26967
  out = /* @__PURE__ */ new Set();
26522
- const facts = code.factsAt(path49);
26968
+ const facts = code.factsAt(path50);
26523
26969
  for (const specifier of new Set(Object.values(facts?.typeOnlyBindings ?? {}))) {
26524
- const to = resolveImport(path49, specifier, mappedPaths, code.packages);
26525
- if (to && to !== path49) out.add(to);
26970
+ const to = resolveImport(path50, specifier, mappedPaths, code.packages);
26971
+ if (to && to !== path50) out.add(to);
26526
26972
  }
26527
- typeTraces.set(path49, out);
26973
+ typeTraces.set(path50, out);
26528
26974
  return out;
26529
26975
  };
26530
26976
  const typeConnects = (from, to) => from.some((f) => {
@@ -26539,6 +26985,7 @@ var dependencyConformanceRule = {
26539
26985
  for (const targetId of declaredTargets) {
26540
26986
  const target = ctx.componentMap.get(targetId);
26541
26987
  if (!target) continue;
26988
+ if (component.componentType === "Adapter" && target.componentType === "Portal" && target.transport !== void 0 && target.transport !== "InProcess") continue;
26542
26989
  const crossSubsystem = component.subsystem !== target.subsystem;
26543
26990
  const toFiles = crossSubsystem ? realization.filesIn(target.subsystem).filter((p) => isExact(p) && !fromFiles.includes(p)) : filesOf(targetId);
26544
26991
  if (toFiles.length === 0) continue;
@@ -26594,12 +27041,44 @@ var dependencyConformanceRule = {
26594
27041
  if (!specifier) continue;
26595
27042
  const toPath = resolveImport(scope, specifier, mappedPaths, code.packages);
26596
27043
  if (toPath && toPath !== scope) out.push({ toPath, typeOnly: typeOnly !== void 0 });
27044
+ const declaredAt = resolveImport(scope, specifier, code.paths, code.packages) ?? (specifier.startsWith(".") ? import_path.posix.join(import_path.posix.dirname(scope), specifier) : void 0);
27045
+ const published = importBindingOf(facts, typeName)?.imported ?? typeName;
27046
+ if (declaredAt) {
27047
+ for (const implementor of code.implementorsOf(declaredAt, published)) {
27048
+ if (mappedPaths.has(implementor) && implementor !== scope) out.push({ toPath: implementor, typeOnly: typeOnly !== void 0 });
27049
+ }
27050
+ }
26597
27051
  }
26598
27052
  return out;
26599
27053
  };
27054
+ const unfollowable = (site, file) => {
27055
+ const facts = code.factsAt(pathKey(site.from ?? file));
27056
+ if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") return false;
27057
+ if (site.field) return fieldTypesOf(facts, site.field).length === 0;
27058
+ if (site.via) return !importBindingOf(facts, site.via) && localTypesOf(facts, site.via).length === 0;
27059
+ return !site.constructed && !site.returnedBy && !site.enclosingClass && !site.receiverPath;
27060
+ };
27061
+ const dataWrites = /* @__PURE__ */ new Map();
27062
+ for (const c of ctx.components) {
27063
+ if (!DATA_COMPONENTS2.has(c.componentType)) continue;
27064
+ for (const m of ctx.interfaceMethodsOf(c.id)) {
27065
+ if (m.effect !== "write" && m.effect !== "lifecycle") continue;
27066
+ const names = /* @__PURE__ */ new Set([m.name]);
27067
+ for (const impl of realization.implementationsOf(c.id)) {
27068
+ const bound2 = impl.methods.find((x) => x.name === m.name)?.symbol;
27069
+ if (bound2) names.add(bound2);
27070
+ }
27071
+ for (const name of names) {
27072
+ const list = dataWrites.get(name) ?? [];
27073
+ list.push({ component: c.id, method: m.name });
27074
+ dataWrites.set(name, list);
27075
+ }
27076
+ }
27077
+ }
26600
27078
  const calledAt = (toPath, name) => realization.componentsAt(toPath).map((c) => ({ component: c, method: calledMethodOf(c.id, name) })).filter((c) => c.method !== void 0);
26601
27079
  const reportedEdges = /* @__PURE__ */ new Set();
26602
27080
  const reportedWrites = /* @__PURE__ */ new Set();
27081
+ const reportedUnresolved = /* @__PURE__ */ new Set();
26603
27082
  for (const fromPath of mappedPaths) {
26604
27083
  const facts = code.factsAt(fromPath);
26605
27084
  const fromComponents = realization.componentsAt(fromPath);
@@ -26612,6 +27091,23 @@ var dependencyConformanceRule = {
26612
27091
  for (const site of sites) {
26613
27092
  if (!site.member || pathKey(site.from ?? fromPath) !== fromPath) continue;
26614
27093
  const landings = /* @__PURE__ */ new Set([...typedLandings(site, fromPath).map((l) => l.toPath), ...code.originOf(site, fromPath)]);
27094
+ if (landings.size === 0 && unfollowable(site, fromPath)) {
27095
+ const near = /* @__PURE__ */ new Set([portal.subsystem, ...portal.dependsOn.map((d) => ctx.componentMap.get(d)?.subsystem)]);
27096
+ const candidates = (dataWrites.get(site.name) ?? []).filter((w) => near.has(ctx.componentMap.get(w.component)?.subsystem) && !claims(portal.id, w.component, w.method));
27097
+ const at = `${fromPath} -> ${site.name}`;
27098
+ if (candidates.length > 0 && !reportedUnresolved.has(at)) {
27099
+ reportedUnresolved.add(at);
27100
+ ctx.addIssue(
27101
+ "notice",
27102
+ "PORTAL_CALL_UNRESOLVED",
27103
+ `Portal "${portal.id}": its method "${method.name}" in "${fromPath}" calls \`${site.via ?? (site.field ? `this.${site.field}` : "<receiver>")}.${site.name}(\u2026)\` through a receiver this analysis cannot follow (nothing in the file declares its type), and ${candidates.map((w) => `${w.component}.${w.method}`).join(", ")} \u2014 a data component's write \u2014 carries that name. Neither proven a persistence shortcut nor cleared: give the receiver a declared type (a field or parameter annotation) so the call can be judged.`,
27104
+ impl.id,
27105
+ draftAt(fromPath),
27106
+ void 0,
27107
+ { at }
27108
+ );
27109
+ }
27110
+ }
26615
27111
  for (const toPath of landings) {
26616
27112
  if (toPath === fromPath) continue;
26617
27113
  for (const { component: target, method: called } of calledAt(toPath, site.name)) {
@@ -26672,7 +27168,7 @@ var dependencyConformanceRule = {
26672
27168
  const facade = ownerOf4.get(owner.id);
26673
27169
  if (facade) members.add(facade.id);
26674
27170
  for (const technology of technologies) {
26675
- for (const token of technologyTokens(technology)) {
27171
+ for (const token of technologyPackages(technology, ctx.ext.technologyPackages)) {
26676
27172
  const key2 = token.toLowerCase();
26677
27173
  const entry = homes.get(key2) ?? { label: technologyName(technology), home: /* @__PURE__ */ new Set() };
26678
27174
  for (const id of members) entry.home.add(id);
@@ -26740,7 +27236,7 @@ var portalImportsRule = {
26740
27236
  }
26741
27237
  return files;
26742
27238
  };
26743
- const draftAt = (path49) => realization.implementationsAt(path49).some((impl) => ctx.isImplementationDraft(impl));
27239
+ const draftAt = (path50) => realization.implementationsAt(path50).some((impl) => ctx.isImplementationDraft(impl));
26744
27240
  for (const fromPath of mappedPaths) {
26745
27241
  const fromSubsystems = new Set(realization.componentsAt(fromPath).map((c) => c.subsystem));
26746
27242
  for (const toPath of graph2.importsOf(fromPath)) {
@@ -26983,20 +27479,48 @@ function ownerOf2(type, file) {
26983
27479
  var typeRealizationRule = {
26984
27480
  name: "type-realization",
26985
27481
  judges: "code",
26986
- description: "Code\u2194spec Level 1 for the data model: a type that names a sourcePath is claiming code. Until that claim has BEGUN (code_index.holdsAny over the type's own file and its methods' files) a named file that is not on disk is PLANNED (SOURCE_FILE_PLANNED, a notice; an error under rules.conformance.requireCode). Once it has begun, every file it names must resolve to a real, readable file inside the project root (MISSING_SOURCE_FILE, an error, otherwise), its file must PUBLISH its declaration (an exported name at exact grade, the declaration tier below it, under its `symbol` when the code-level name differs from the type's name \u2014 and a pure re-export barrel publishes nothing of its own, so a claim on one is never realized), and each of its pure methods must appear in its own file (the method's sourcePath, else the type's) at the declaration tier, under the method's `symbol`, else the method's name. A type that names no sourcePath claims nothing and is never reported. Findings carry the analysis grade, a file that escapes the root or could not be analyzed is reported once and blocks only what it would have realized, and types under chained subsystems (projectPath) validate standalone in their own project run.",
27482
+ description: "Code\u2194spec Level 1 for the data model: a type that names a sourcePath is claiming code. Until that claim has BEGUN (code_index.holdsAny over the type's own file and its methods' files) a named file that is not on disk is PLANNED (SOURCE_FILE_PLANNED, a notice; an error under rules.conformance.requireCode). Once it has begun, every file it names must resolve to a real, readable file inside the project root (MISSING_SOURCE_FILE, an error, otherwise), its file must PUBLISH its declaration (an exported name at exact grade, the declaration tier below it, under its `symbol` when the code-level name differs from the type's name \u2014 and a pure re-export barrel publishes nothing of its own, so a claim on one is never realized), and each of its pure methods must appear in its own file (the method's sourcePath, else the type's) at the declaration tier, under the method's `symbol`, else the method's name. A type that names no sourcePath of its own claims no code, so nothing ever compares its shape with the code \u2014 and that is said, never left silent: before the code of the type's subsystem has begun (no file an implementation of that subsystem names exists; for a type no subsystem owns, no file of any implementation) it is unlinked design (MISSING_TYPE_SOURCE_PATH, a notice \u2014 plan the type's sourcePath with the implementations'), and once that code exists it is a type no check reads (a warning). rules.conformance.requireCode reports the notice at error, naming the setting. Findings carry the analysis grade, a file that escapes the root or could not be analyzed is reported once and blocks only what it would have realized, and types under chained subsystems (projectPath) validate standalone in their own project run.",
26987
27483
  codes: [
26988
27484
  { code: "UNREALIZED_TYPE", defaultSeverity: "warning", summary: "A type names a sourcePath but its declaration is nowhere in that file \u2014 the claim points at code that does not hold it" },
26989
27485
  { code: "UNREALIZED_TYPE_METHOD", defaultSeverity: "warning", summary: "A pure method of a claimed type is nowhere in its own source file (the method's sourcePath, else the type's)" },
26990
27486
  { code: "MISSING_SOURCE_FILE", defaultSeverity: "error", summary: "A source file a type or one of its methods names does not resolve to a file on disk although the type's realization has begun (another file it names exists)" },
26991
27487
  { code: "SOURCE_PATH_ESCAPES_ROOT", defaultSeverity: "error", summary: "A source file a type or one of its methods names is absolute or escapes the project root (containment refusal)" },
26992
27488
  { code: "CONFORMANCE_ANALYSIS_SKIPPED", defaultSeverity: "warning", summary: "A source file a type or one of its methods names could not be analyzed (binary/unreadable) \u2014 what it would have realized was not checked" },
26993
- { code: "SOURCE_FILE_PLANNED", defaultSeverity: "notice", summary: "A source file a type names is not on disk and none of its named files is \u2014 planned, not written yet; an error under rules.conformance.requireCode" }
27489
+ { code: "SOURCE_FILE_PLANNED", defaultSeverity: "notice", summary: "A source file a type names is not on disk and none of its named files is \u2014 planned, not written yet; an error under rules.conformance.requireCode" },
27490
+ { code: "MISSING_TYPE_SOURCE_PATH", defaultSeverity: "notice", summary: "A type names no sourcePath of its own, so its shape is never compared with code \u2014 a notice while its subsystem has no code yet (plan the path with the implementations'), a warning once it has; an error under rules.conformance.requireCode" }
26994
27491
  ],
26995
27492
  check(ctx) {
26996
27493
  const code = ctx.codeIndex();
27494
+ const begunIn = /* @__PURE__ */ new Map();
27495
+ const codeBegunFor = (subsystem) => {
27496
+ const key2 = subsystem ?? "";
27497
+ let begun = begunIn.get(key2);
27498
+ if (begun === void 0) {
27499
+ begun = ctx.implementations.some((impl) => {
27500
+ if (subsystem !== void 0) {
27501
+ const owner = ctx.componentMap.get(ctx.interfaceMap.get(impl.contract)?.component ?? "");
27502
+ if (owner?.subsystem !== subsystem) return false;
27503
+ }
27504
+ return code.holdsAny(implementationSourceFiles(impl));
27505
+ });
27506
+ begunIn.set(key2, begun);
27507
+ }
27508
+ return begun;
27509
+ };
26997
27510
  for (const type of ctx.types) {
26998
- if (!type.sourcePath) continue;
26999
27511
  if (type.subsystem && ctx.isInChainedSubproject(type.subsystem)) continue;
27512
+ if (!type.sourcePath) {
27513
+ const begunHere = codeBegunFor(type.subsystem);
27514
+ const planned = plannedCode(ctx);
27515
+ const methods = typeSourceFiles(type).length > 0 ? " (only its methods name files)" : "";
27516
+ ctx.addIssue(
27517
+ begunHere ? "warning" : planned.severity,
27518
+ "MISSING_TYPE_SOURCE_PATH",
27519
+ begunHere ? `Type "${type.id}" names no sourcePath of its own${methods}, and the code of ${type.subsystem ? `subsystem "${type.subsystem}"` : "this project"} exists \u2014 so nothing compares its fields with the code: it is data no check reads. Name the file that declares it (sourcePath, and symbol when the code name differs).` : `Type "${type.id}" names no sourcePath of its own${methods} \u2014 designed, not linked to code yet, so its shape will never be compared with the code. Plan its sourcePath now, with the implementations': code linkage is not part of the approval, so it costs no re-lock${planned.note}.`,
27520
+ type.id
27521
+ );
27522
+ continue;
27523
+ }
27000
27524
  const blocked3 = /* @__PURE__ */ new Set();
27001
27525
  const begun = code.holdsAny(typeSourceFiles(type));
27002
27526
  for (const file of typeSourceFiles(type)) {
@@ -28203,7 +28727,7 @@ function contractIdentifiers(m) {
28203
28727
  var technologyRule = {
28204
28728
  name: "technology-boundaries",
28205
28729
  judges: "design",
28206
- description: "Technology stays behind its owning boundary: an L4 that declares `technologies` (e.g. [mysql]) makes its component's ownership tree the technology's home. References outside that tree are leakage, and L3 contract identifiers must stay intent-language \u2014 the contract is the swap seam, so the vendor name is wrong even on the owning component's own interface. A technology is matched by its name, or \u2014 when it declares `matches` because its name is also an ordinary word of the tree (a package named after the file format it reads) \u2014 by those tokens alone.",
28730
+ description: "Technology stays behind its owning boundary: an L4 that declares `technologies` (e.g. [mysql]) makes its component's ownership tree the technology's home. References outside that tree are leakage, and L3 contract identifiers must stay intent-language \u2014 the contract is the swap seam, so the vendor name is wrong even on the owning component's own interface. A technology is matched by its name, or \u2014 when it declares `matches` because its name is also an ordinary word of the tree (a package named after the file format it reads) \u2014 by those tokens alone. Two declarations of one technology are one home whichever notation each is written in: a component binding it as a bare name and one binding it as `{name, matches}` are both inside it, even when one form's tokens police nothing.",
28207
28731
  codes: [
28208
28732
  { code: "TECH_LEAKAGE", defaultSeverity: "warning", summary: "Technology referenced outside its owning boundary" },
28209
28733
  { code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" }
@@ -28268,7 +28792,6 @@ var technologyRule = {
28268
28792
  const homes = /* @__PURE__ */ new Map();
28269
28793
  for (const { comp, name, tokens } of declarations) {
28270
28794
  const matchers = tokens.map(makeMatcher).filter((m) => m !== null);
28271
- if (!matchers.length) continue;
28272
28795
  const key2 = name.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
28273
28796
  const home = homes.get(key2) ?? newHome(name);
28274
28797
  home.matchers.push(...matchers);
@@ -28276,6 +28799,7 @@ var technologyRule = {
28276
28799
  for (const id of scopeOf2(comp)) home.scope.add(id);
28277
28800
  homes.set(key2, home);
28278
28801
  }
28802
+ for (const [key2, home] of homes) if (home.matchers.length === 0) homes.delete(key2);
28279
28803
  if (homes.size === 0) return;
28280
28804
  const ownersDesc = (h) => [...h.ownerComponents].map((c) => `"${c}"`).join(", ");
28281
28805
  for (const home of homes.values()) {
@@ -28421,10 +28945,25 @@ function getBaseId(id) {
28421
28945
  return parts[parts.length - 1];
28422
28946
  }
28423
28947
  var isAllUppercase = (s) => /^[A-Z0-9_]+$/.test(s);
28948
+ function implementedNames(ctx, ref) {
28949
+ if (ref === void 0) return /* @__PURE__ */ new Set();
28950
+ const surface = ctx.resolveSurfaceRef(ref);
28951
+ if (surface.kind === "resolved") {
28952
+ return surface.entry.role === "implement" ? new Set(surface.entry.methods.map((m) => m.name)) : /* @__PURE__ */ new Set();
28953
+ }
28954
+ const [alias, name] = ref.split("::");
28955
+ if (name === void 0) return /* @__PURE__ */ new Set();
28956
+ const key2 = ctx.projectFamily?.nodes.find((n) => n.namespace === "")?.aliases.get(alias);
28957
+ if (key2 === void 0) return /* @__PURE__ */ new Set();
28958
+ const entry = (ctx.exportTables ?? []).find((t) => t.level === "project" && t.owner === key2)?.entries.find((e) => e.kind === "component" && e.publicName === name);
28959
+ if (!entry || entry.role !== "implement" || !entry.component) return /* @__PURE__ */ new Set();
28960
+ const methods = entry.interface ? ctx.interfaceMap.get(entry.interface)?.methods : ctx.interfaceMethodsOf(entry.component);
28961
+ return new Set((methods ?? []).map((m) => m.name));
28962
+ }
28424
28963
  var namingRule = {
28425
28964
  name: "naming-conventions",
28426
28965
  judges: "design",
28427
- description: "Enforces naming conventions (casing styles or regular expressions) for subsystem, component, interface, type (differentiating entities and value-objects), method, variables/parameters, fields, and constants names/IDs, plus stereotype-specific naming patterns. A method name is judged by naming_rule_config.methodCasingFor the subsystem's effective targetLanguage: the configured methods casing when set, else the language's own convention, so a Rust tree is snake_case without configuring anything, and the authoring and rename tools accept exactly what this rule accepts.",
28966
+ description: "Enforces naming conventions (casing styles or regular expressions) for subsystem, component, interface, type (differentiating entities and value-objects), method, variables/parameters, fields, and constants names/IDs, plus stereotype-specific naming patterns. A method name is judged by naming_rule_config.methodCasingFor the subsystem's effective targetLanguage: the configured methods casing when set, else the language's own convention, so a Rust tree is snake_case without configuring anything, and the authoring and rename tools accept exactly what this rule accepts. A contract that implements another project's extension point (`implements`) takes the names of the methods it implements \u2014 and of their parameters \u2014 from the producer, whose language they are spelled in: those methods, on the contract and on its implementations, are exempt from the casing rule, and the rename tools accept them.",
28428
28967
  codes: [
28429
28968
  { code: "NAMING_CONVENTION_VIOLATION", defaultSeverity: "warning", summary: "Item name or ID does not match the configured casing pattern or regex" },
28430
28969
  { code: "STEREOTYPE_NAMING_VIOLATION", defaultSeverity: "warning", summary: "Component name or ID does not match stereotype suffix/prefix/regex rules" },
@@ -28508,7 +29047,9 @@ var namingRule = {
28508
29047
  checkNamedValue(ctx, intf.name, namingConfig.interfaces, `Interface Name "${intf.name}" does not match naming convention "${namingConfig.interfaces}".`, intf.id, isDraft);
28509
29048
  }
28510
29049
  const methodCasing = methodCasingFor(namingConfig, ctx.targetLanguageFor(comp?.subsystem));
29050
+ const dictated = implementedNames(ctx, intf.implements);
28511
29051
  for (const m of intf.methods) {
29052
+ if (dictated.has(m.name)) continue;
28512
29053
  checkNamedValue(ctx, m.name, methodCasing, `Interface method "${m.name}" on "${intf.id}" does not match naming convention "${methodCasing}"${namingConfig?.methods ? "" : " (its target language's convention)"}.`, intf.id, isDraft);
28513
29054
  if (namingConfig?.variables) {
28514
29055
  for (const param of m.params ?? []) {
@@ -28523,7 +29064,9 @@ var namingRule = {
28523
29064
  const namingConfig = ctx.namingConfigFor(comp?.subsystem);
28524
29065
  const isDraft = ctx.isImplementationDraft(impl);
28525
29066
  const methodCasing = methodCasingFor(namingConfig, ctx.targetLanguageFor(comp?.subsystem));
29067
+ const dictated = implementedNames(ctx, intf?.implements);
28526
29068
  for (const m of impl.methods) {
29069
+ if (dictated.has(m.name)) continue;
28527
29070
  checkNamedValue(ctx, m.name, methodCasing, `Implementation method "${m.name}" on "${impl.id}" does not match naming convention "${methodCasing}".`, impl.id, isDraft);
28528
29071
  }
28529
29072
  }
@@ -29502,7 +30045,8 @@ function walkExact(ts, sourceText, fileName) {
29502
30045
  const fieldTypes = /* @__PURE__ */ new Map();
29503
30046
  const localTypes = /* @__PURE__ */ new Map();
29504
30047
  const memberTypes = /* @__PURE__ */ new Map();
29505
- const aliasTargets = /* @__PURE__ */ new Map();
30048
+ const aliasTargets2 = /* @__PURE__ */ new Map();
30049
+ const implementsClauses = /* @__PURE__ */ new Map();
29506
30050
  const typeShapes = /* @__PURE__ */ new Map();
29507
30051
  const functionParams = /* @__PURE__ */ new Map();
29508
30052
  const functionRoutes = /* @__PURE__ */ new Map();
@@ -29561,7 +30105,13 @@ function walkExact(ts, sourceText, fileName) {
29561
30105
  const typeReferenceName = (type) => {
29562
30106
  let at = type;
29563
30107
  for (let depth = 0; at && depth < 4; depth++) {
29564
- if (!ts.isTypeReferenceNode(at) || !ts.isIdentifier(at.typeName)) return void 0;
30108
+ while (at && ts.isParenthesizedTypeNode(at)) at = at.type;
30109
+ if (at && ts.isUnionTypeNode(at)) {
30110
+ const present = at.types.filter((t) => !(t.kind === ts.SyntaxKind.UndefinedKeyword || t.kind === ts.SyntaxKind.NullKeyword || ts.isLiteralTypeNode(t) && t.literal.kind === ts.SyntaxKind.NullKeyword));
30111
+ if (present.length !== 1) return void 0;
30112
+ at = present[0];
30113
+ }
30114
+ if (!at || !ts.isTypeReferenceNode(at) || !ts.isIdentifier(at.typeName)) return void 0;
29565
30115
  const name = at.typeName.text;
29566
30116
  if (!MEMBER_PRESERVING.has(name) || !at.typeArguments?.length) return name;
29567
30117
  at = at.typeArguments[0];
@@ -29756,6 +30306,29 @@ function walkExact(ts, sourceText, fileName) {
29756
30306
  const addSite = (site) => {
29757
30307
  callees.set(siteKey(site), site);
29758
30308
  };
30309
+ const namedCast = (type) => ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName);
30310
+ const unwrapped = (expression) => {
30311
+ let at = expression;
30312
+ while (ts.isNonNullExpression(at) || ts.isParenthesizedExpression(at) || typeof ts.isSatisfiesExpression === "function" && ts.isSatisfiesExpression(at) || (ts.isAsExpression(at) || ts.isTypeAssertionExpression(at)) && namedCast(at.type)) at = at.expression;
30313
+ return at;
30314
+ };
30315
+ const fieldAliases = /* @__PURE__ */ new Map();
30316
+ const collectAliases = (node) => {
30317
+ if (ts.isVariableDeclaration(node) && node.initializer) {
30318
+ const init = unwrapped(node.initializer);
30319
+ if (ts.isIdentifier(node.name) && ts.isPropertyAccessExpression(init) && unwrapped(init.expression).kind === ts.SyntaxKind.ThisKeyword && ts.isIdentifier(init.name)) {
30320
+ fieldAliases.set(node.name.text, init.name.text);
30321
+ } else if (ts.isObjectBindingPattern(node.name) && init.kind === ts.SyntaxKind.ThisKeyword) {
30322
+ for (const element of node.name.elements) {
30323
+ if (element.dotDotDotToken || !ts.isIdentifier(element.name)) continue;
30324
+ const property = element.propertyName ? propertyNameText(element.propertyName) : element.name.text;
30325
+ if (property) fieldAliases.set(element.name.text, property);
30326
+ }
30327
+ }
30328
+ }
30329
+ ts.forEachChild(node, collectAliases);
30330
+ };
30331
+ if (fn.body) collectAliases(fn.body);
29759
30332
  const count = (node) => {
29760
30333
  if (namedFunctionName(node) !== void 0) return;
29761
30334
  if (ts.isIfStatement(node) || ts.isConditionalExpression(node) || ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node) || ts.isCaseClause(node) || ts.isCatchClause(node)) {
@@ -29769,10 +30342,12 @@ function walkExact(ts, sourceText, fileName) {
29769
30342
  const callee = node.expression;
29770
30343
  if (ts.isIdentifier(callee)) addSite({ name: callee.text, member: false });
29771
30344
  else if (ts.isPropertyAccessExpression(callee)) {
29772
- const receiver = callee.expression;
30345
+ const receiver = unwrapped(callee.expression);
29773
30346
  const name = callee.name.text;
29774
- if (ts.isIdentifier(receiver)) addSite({ name, member: true, via: receiver.text });
29775
- else if (ts.isPropertyAccessExpression(receiver) && receiver.expression.kind === ts.SyntaxKind.ThisKeyword) {
30347
+ if (ts.isIdentifier(receiver) && fieldAliases.has(receiver.text)) {
30348
+ addSite({ name, member: true, field: fieldAliases.get(receiver.text) });
30349
+ } else if (ts.isIdentifier(receiver)) addSite({ name, member: true, via: receiver.text });
30350
+ else if (ts.isPropertyAccessExpression(receiver) && unwrapped(receiver.expression).kind === ts.SyntaxKind.ThisKeyword) {
29776
30351
  addSite({ name, member: true, field: receiver.name.text });
29777
30352
  } else if (ts.isNewExpression(receiver) && ts.isIdentifier(receiver.expression)) {
29778
30353
  addSite({ name, member: true, constructed: receiver.expression.text });
@@ -29786,7 +30361,7 @@ function walkExact(ts, sourceText, fileName) {
29786
30361
  let at = receiver;
29787
30362
  while (ts.isPropertyAccessExpression(at) && !at.questionDotToken && ts.isIdentifier(at.name)) {
29788
30363
  chain.unshift(at.name.text);
29789
- at = at.expression;
30364
+ at = unwrapped(at.expression);
29790
30365
  }
29791
30366
  if (ts.isIdentifier(at)) chain.unshift(at.text);
29792
30367
  else if (at.kind === ts.SyntaxKind.ThisKeyword) chain.unshift("this");
@@ -29944,13 +30519,15 @@ function walkExact(ts, sourceText, fileName) {
29944
30519
  (clause) => clause.token === ts.SyntaxKind.ExtendsKeyword
29945
30520
  );
29946
30521
  recordDeclaredShape(node.name.text, node.members, extendsOther);
30522
+ const implemented = (node.heritageClauses ?? []).filter((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword).flatMap((clause) => clause.types.map((t) => ts.isIdentifier(t.expression) ? t.expression.text : void 0)).filter((name) => name !== void 0);
30523
+ if (implemented.length > 0 && !implementsClauses.has(node.name.text)) implementsClauses.set(node.name.text, implemented);
29947
30524
  } else if (ts.isTypeAliasDeclaration(node)) {
29948
30525
  if (ts.isTypeLiteralNode(node.type)) recordDeclaredShape(node.name.text, node.type.members, false);
29949
30526
  else {
29950
30527
  if (!aliasTypes.has(node.name.text)) aliasTypes.set(node.name.text, node.type.getText(sf));
29951
30528
  const named2 = typeReferenceName(node.type);
29952
- if (named2 !== void 0 && named2 !== node.name.text && !aliasTargets.has(node.name.text)) {
29953
- aliasTargets.set(node.name.text, named2);
30529
+ if (named2 !== void 0 && named2 !== node.name.text && !aliasTargets2.has(node.name.text)) {
30530
+ aliasTargets2.set(node.name.text, named2);
29954
30531
  }
29955
30532
  const constant = inferredSchemaConstant(node.type);
29956
30533
  if (constant) derivedAliases.push({ name: node.name.text, constant });
@@ -30117,7 +30694,7 @@ function walkExact(ts, sourceText, fileName) {
30117
30694
  importBindings.set(name, fact);
30118
30695
  }
30119
30696
  for (const alias of propertyAliases) if (exported.has(alias.container)) exportAliases.push(alias);
30120
- return { declared, anchors, exported, imports, reexports, starExports, namedReexports, exportAliases, returnTypes, complexity, calls, bodies, importBindings, typeOnlyBindings, fieldTypes, localTypes, memberTypes, aliasTargets, typeShapes, functionParams, functionRoutes, asyncFunctions, enumValues, aliasTypes, mutableBindings, reexportOnly };
30697
+ return { declared, anchors, exported, imports, reexports, starExports, namedReexports, exportAliases, returnTypes, complexity, calls, bodies, importBindings, typeOnlyBindings, fieldTypes, localTypes, memberTypes, aliasTargets: aliasTargets2, implementsClauses, typeShapes, functionParams, functionRoutes, asyncFunctions, enumValues, aliasTypes, mutableBindings, reexportOnly };
30121
30698
  }
30122
30699
  var NO_PACKAGES = /* @__PURE__ */ new Map();
30123
30700
  function resolveModule(fromFile, specifier, packages) {
@@ -30491,6 +31068,7 @@ function buildCodeModel(implementations, types, projectRoot, sourceRoots = [], e
30491
31068
  localTypes: Object.fromEntries([...facts.localTypes].map(([k, v]) => [k, [...v]])),
30492
31069
  memberTypes: Object.fromEntries([...facts.memberTypes].map(([k, v]) => [k, [...v]])),
30493
31070
  aliasTargets: Object.fromEntries(facts.aliasTargets),
31071
+ ...facts.implementsClauses.size > 0 ? { implementsClauses: Object.fromEntries(facts.implementsClauses) } : {},
30494
31072
  typeShapes: Object.fromEntries(facts.typeShapes),
30495
31073
  functionParams: Object.fromEntries(facts.functionParams),
30496
31074
  functionRoutes: Object.fromEntries([...facts.functionRoutes].map(([k, v]) => [k, [...v.values()]])),
@@ -30674,6 +31252,8 @@ var DEPTH_GATED_CODES = {
30674
31252
  // L4 expectations: implementations and their code linkage.
30675
31253
  MISSING_IMPLEMENTATION_METHOD: "implementations",
30676
31254
  MISSING_SOURCE_PATH: "implementations",
31255
+ MISSING_TYPE_SOURCE_PATH: "implementations",
31256
+ PORTAL_CALL_UNRESOLVED: "implementations",
30677
31257
  METHOD_SOURCE_PATH_MISSING: "implementations",
30678
31258
  SOURCE_FILE_PLANNED: "implementations",
30679
31259
  PORTAL_AUTH_UNMET: "implementations",
@@ -30715,6 +31295,7 @@ var COMPLETENESS_RULES = /* @__PURE__ */ new Set([
30715
31295
  // Structural conformance: a draft tree is allowed to name code that does
30716
31296
  // not exist yet — the findings gate only once the specs claim completeness.
30717
31297
  "MISSING_SOURCE_PATH",
31298
+ "MISSING_TYPE_SOURCE_PATH",
30718
31299
  "METHOD_SOURCE_PATH_MISSING",
30719
31300
  "MISSING_SOURCE_FILE",
30720
31301
  "SOURCE_FILE_PLANNED",
@@ -31342,7 +31923,7 @@ function buildRuleContext(opts) {
31342
31923
  documentationConfigFor,
31343
31924
  namingConfigFor,
31344
31925
  isBuiltinType,
31345
- ext: { profiles: extensions.profiles, languages: extensions.languages, patterns: extensions.patterns, guarantees: extensions.guarantees, assertions: extensions.assertions, packSelections: opts.packSelections ?? [], selectionFailures: extensions.selectionFailures ?? [], packRequirements: opts.packRequirements ?? [], deprecations: extensions.deprecations ?? [] },
31926
+ ext: { profiles: extensions.profiles, languages: extensions.languages, patterns: extensions.patterns, guarantees: extensions.guarantees, technologyPackages: extensions.technologyPackages ?? {}, assertions: extensions.assertions, packSelections: opts.packSelections ?? [], selectionFailures: extensions.selectionFailures ?? [], packRequirements: opts.packRequirements ?? [], deprecations: extensions.deprecations ?? [] },
31346
31927
  variants: opts.variants ?? [],
31347
31928
  surfaceSnapshots,
31348
31929
  ...opts.projectIdentity ? { projectIdentity: opts.projectIdentity } : {},
@@ -32905,11 +33486,22 @@ function crossReferences(family, specs) {
32905
33486
  for (const ref of family.authoredReferences) {
32906
33487
  if (ref.position === "type") addRaw(ref, "type");
32907
33488
  else if (ref.position === "auth") addRaw(ref, "auth");
32908
- else if (ref.position === "implements") addRaw(ref, "implements");
32909
- else if (ref.position === "calls") addRaw(ref, "calls", calledMethod.get(`${ref.specId}|${ref.authored}`), callerOf2.get(ref.specId));
33489
+ else if (ref.position === "implements") {
33490
+ for (const method of implementedMethods(ref.specId, ref.authored)) addRaw(ref, "implements", method);
33491
+ } else if (ref.position === "calls") addRaw(ref, "calls", calledMethod.get(`${ref.specId}|${ref.authored}`), callerOf2.get(ref.specId));
32910
33492
  }
32911
33493
  return out;
32912
33494
  }
33495
+ function implementedMethods(interfaceId, implemented) {
33496
+ const intf = loadInterfaceSpecs().find((i) => i.id === interfaceId);
33497
+ const out = /* @__PURE__ */ new Set();
33498
+ for (const method of intf?.methods ?? []) {
33499
+ const source = method.signatureFrom;
33500
+ const dot = source?.lastIndexOf(".") ?? -1;
33501
+ out.add(source !== void 0 && dot > 0 && source.slice(0, dot) === implemented ? source.slice(dot + 1) : method.name);
33502
+ }
33503
+ return [...out];
33504
+ }
32913
33505
  function projectFamilyGraph() {
32914
33506
  const roots = listProjectRoots();
32915
33507
  const cached = memo.get(roots);
@@ -32943,9 +33535,12 @@ function projectFamilyGraph() {
32943
33535
  // src/core/exports.ts
32944
33536
  var CONSUMABLE = /* @__PURE__ */ new Set(["Portal", "Observer"]);
32945
33537
  var IMPLEMENTABLE = /* @__PURE__ */ new Set(["Portal", "Adapter"]);
32946
- function exportKindOf(comp, authored) {
33538
+ function exportKindOf(comp, authored, role) {
32947
33539
  if (comp.componentType === "Portal" && comp.transport === "InProcess") return "InProcess";
32948
33540
  if (comp.componentType === "Portal" && comp.transport) return transportExportKind(comp.transport);
33541
+ if (role === "implement" && comp.componentType === "Adapter") {
33542
+ return !comp.transport || comp.transport === "InProcess" ? "InProcess" : transportExportKind(comp.transport);
33543
+ }
32949
33544
  return authored;
32950
33545
  }
32951
33546
  function localName2(id) {
@@ -33117,7 +33712,7 @@ function bindOwn(world, sub, pi, problems) {
33117
33712
  component: comp.id,
33118
33713
  ...narrowed2 ? { interface: narrowed2 } : {},
33119
33714
  componentType: comp.componentType,
33120
- ...exportKindOf(comp, pi.type) ? { type: exportKindOf(comp, pi.type) } : {},
33715
+ ...exportKindOf(comp, pi.type, pi.role) ? { type: exportKindOf(comp, pi.type, pi.role) } : {},
33121
33716
  ...pi.details !== void 0 ? { details: pi.details } : {},
33122
33717
  ...pi.role ? { role: pi.role } : {},
33123
33718
  via: []
@@ -33470,7 +34065,8 @@ function exportUsageOf(consumer, producer) {
33470
34065
  unexported.push(ref);
33471
34066
  continue;
33472
34067
  }
33473
- const slot = used.get(entry.publicName) ?? { kind: entry.kind, members: /* @__PURE__ */ new Set() };
34068
+ const slot = used.get(entry.publicName) ?? { kind: entry.kind, members: /* @__PURE__ */ new Set(), specs: /* @__PURE__ */ new Set() };
34069
+ slot.specs.add(ref.specId);
33474
34070
  if (entry.kind === "type") slot.members.add("type");
33475
34071
  else if (ref.member !== void 0) slot.members.add(ref.member);
33476
34072
  used.set(entry.publicName, slot);
@@ -33478,7 +34074,7 @@ function exportUsageOf(consumer, producer) {
33478
34074
  return {
33479
34075
  consumer,
33480
34076
  producer,
33481
- used: [...used.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([publicName, u]) => ({ publicName, kind: u.kind, members: [...u.members].sort() })),
34077
+ used: [...used.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([publicName, u]) => ({ publicName, kind: u.kind, members: [...u.members].sort(), specs: [...u.specs].sort() })),
33482
34078
  unexported
33483
34079
  };
33484
34080
  }
@@ -33488,6 +34084,7 @@ function importedName(authored, use) {
33488
34084
  }
33489
34085
  function membersAt(ref) {
33490
34086
  if (ref.position === "type") return ["type"];
34087
+ if (ref.position === "implements") return implementedMethods(ref.specId, ref.authored);
33491
34088
  if (ref.position === "narrative" || ref.position === "calls") {
33492
34089
  const impl = loadImplementationSpecs().find((i) => i.id === ref.specId);
33493
34090
  const out = [];
@@ -33520,14 +34117,15 @@ function pinnedUsageOf(consumer, alias) {
33520
34117
  const used = /* @__PURE__ */ new Map();
33521
34118
  for (const ref of through) {
33522
34119
  const publicName = ref.form === "import" ? importedName(ref.authored, use) : ref.authored.slice(alias.length + 2);
33523
- const slot = used.get(publicName) ?? { kind: ref.position === "type" ? "type" : "component", members: /* @__PURE__ */ new Set() };
34120
+ const slot = used.get(publicName) ?? { kind: ref.position === "type" ? "type" : "component", members: /* @__PURE__ */ new Set(), specs: /* @__PURE__ */ new Set() };
34121
+ slot.specs.add(ref.specId);
33524
34122
  for (const member of membersAt(ref)) slot.members.add(member);
33525
34123
  used.set(publicName, slot);
33526
34124
  }
33527
34125
  return {
33528
34126
  consumer,
33529
34127
  producer: alias,
33530
- used: [...used.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([publicName, u]) => ({ publicName, kind: u.kind, members: [...u.members].sort() })),
34128
+ used: [...used.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([publicName, u]) => ({ publicName, kind: u.kind, members: [...u.members].sort(), specs: [...u.specs].sort() })),
33531
34129
  unexported: []
33532
34130
  };
33533
34131
  }
@@ -33697,7 +34295,8 @@ var UNKNOWN_KEY_HINTS = {
33697
34295
  "system.exports": "The L0 export table is `publicInterfaces` (sdd_set_public_interfaces writes it).",
33698
34296
  "system.export": "The L0 export table is `publicInterfaces` (sdd_set_public_interfaces writes it).",
33699
34297
  "subsystem.exports": "A subsystem publishes through `publicInterfaces`.",
33700
- "component.type": "A component's stereotype is `componentType`."
34298
+ "component.type": "A component's stereotype is `componentType`.",
34299
+ "implementation.symbol": "A symbol names the code realizing ONE contract method: set it on that method (`methods[].symbol`)."
33701
34300
  };
33702
34301
  function unknownKeysIn(raw, parsed, at, out) {
33703
34302
  if (Array.isArray(raw)) {
@@ -34488,7 +35087,7 @@ function resolveFamilyTables(raws) {
34488
35087
  const byKey3 = new Map(raws.map((r) => [r.record.namespace, r]));
34489
35088
  const done = /* @__PURE__ */ new Set();
34490
35089
  const resolving = /* @__PURE__ */ new Set();
34491
- const resolve41 = (raw) => {
35090
+ const resolve42 = (raw) => {
34492
35091
  const key2 = raw.record.namespace;
34493
35092
  if (done.has(key2)) return raw.record.exports;
34494
35093
  if (resolving.has(key2)) return null;
@@ -34503,7 +35102,7 @@ function resolveFamilyTables(raws) {
34503
35102
  const producerKey = source !== void 0 ? raw.record.aliases.get(source) : void 0;
34504
35103
  if (producerKey === void 0 || producerTables.has(producerKey)) continue;
34505
35104
  const producer = byKey3.get(producerKey);
34506
- producerTables.set(producerKey, producer ? resolve41(producer) : null);
35105
+ producerTables.set(producerKey, producer ? resolve42(producer) : null);
34507
35106
  audienceOf.set(producerKey, "project");
34508
35107
  }
34509
35108
  raw.record.subsystemExports = subsystemTables;
@@ -34524,7 +35123,7 @@ function resolveFamilyTables(raws) {
34524
35123
  resolving.delete(key2);
34525
35124
  }
34526
35125
  };
34527
- raws.forEach((raw) => resolve41(raw));
35126
+ raws.forEach((raw) => resolve42(raw));
34528
35127
  }
34529
35128
  function bindAuthored(raw, raws, readables, authored) {
34530
35129
  const key2 = raw.record.namespace;
@@ -35232,13 +35831,13 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
35232
35831
  const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
35233
35832
  const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
35234
35833
  const at = (prefix, key2) => prefix ? `${prefix}.${key2}` : key2;
35235
- const say = (path49, why2) => {
35236
- out.push(`${path49} \u2014 ${why2}`);
35834
+ const say = (path50, why2) => {
35835
+ out.push(`${path50} \u2014 ${why2}`);
35237
35836
  };
35238
- const walkValue = (d, m, s, path49, field) => {
35837
+ const walkValue = (d, m, s, path50, field) => {
35239
35838
  if (Array.isArray(d)) {
35240
35839
  if (!Array.isArray(m)) {
35241
- say(path49, "the level does not have this field, so the write dropped it");
35840
+ say(path50, "the level does not have this field, so the write dropped it");
35242
35841
  return;
35243
35842
  }
35244
35843
  if (field === "narrative" && d.some(carriesEditMarker)) return;
@@ -35246,32 +35845,32 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
35246
35845
  if (carriesEditMarker(element)) continue;
35247
35846
  const key2 = reportIdentityOf(field, element);
35248
35847
  if (key2 === null) {
35249
- if (!same(m, d)) say(path49, "the level does not keep this value, so the write dropped it");
35250
- else if (same(s, d)) say(path49, "the stored spec already held this value");
35848
+ if (!same(m, d)) say(path50, "the level does not keep this value, so the write dropped it");
35849
+ else if (same(s, d)) say(path50, "the stored spec already held this value");
35251
35850
  return;
35252
35851
  }
35253
35852
  const mMatch = m.find((i) => reportIdentityOf(field, i) === key2);
35254
35853
  const sMatch = Array.isArray(s) ? s.find((i) => reportIdentityOf(field, i) === key2) : void 0;
35255
35854
  if (mMatch === void 0) {
35256
- say(at(path49, key2), "the write did not store this element");
35855
+ say(at(path50, key2), "the write did not store this element");
35257
35856
  continue;
35258
35857
  }
35259
- walk2(element, mMatch, sMatch, at(path49, key2), field);
35858
+ walk2(element, mMatch, sMatch, at(path50, key2), field);
35260
35859
  }
35261
35860
  return;
35262
35861
  }
35263
35862
  if (isPlain(d)) {
35264
35863
  if (!isPlain(m)) {
35265
- say(path49, "the level does not have this field, so the write dropped it");
35864
+ say(path50, "the level does not have this field, so the write dropped it");
35266
35865
  return;
35267
35866
  }
35268
- walk2(d, m, s, path49, field);
35867
+ walk2(d, m, s, path50, field);
35269
35868
  return;
35270
35869
  }
35271
- if (!same(m, d)) say(path49, "the write did not store it \u2014 the level's schema does not keep this value");
35272
- else if (same(s, d)) say(path49, "the stored spec already held this value");
35870
+ if (!same(m, d)) say(path50, "the write did not store it \u2014 the level's schema does not keep this value");
35871
+ else if (same(s, d)) say(path50, "the stored spec already held this value");
35273
35872
  };
35274
- function walk2(d, m, s, path49, container) {
35873
+ function walk2(d, m, s, path50, container) {
35275
35874
  for (const [key2, value] of Object.entries(d)) {
35276
35875
  if (DELTA_VERBS.has(key2) || JUMP_LABEL_TWINS.has(key2)) continue;
35277
35876
  if (key2 === "label" && LABEL_TWIN_CONTAINERS.has(container)) continue;
@@ -35281,13 +35880,13 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
35281
35880
  if (typeof name !== "string") continue;
35282
35881
  const had = isPlain(s) && s[name] !== void 0;
35283
35882
  const still = isPlain(m) && m[name] !== void 0;
35284
- if (!had) say(at(path49, name), "unset named a field the stored spec did not have");
35285
- else if (still) say(at(path49, name), "unset did not remove it");
35883
+ if (!had) say(at(path50, name), "unset named a field the stored spec did not have");
35884
+ else if (still) say(at(path50, name), "unset did not remove it");
35286
35885
  }
35287
35886
  continue;
35288
35887
  }
35289
35888
  if (value === void 0 || value === null) continue;
35290
- const here = at(path49, key2);
35889
+ const here = at(path50, key2);
35291
35890
  const mValue = isPlain(m) ? m[key2] : void 0;
35292
35891
  const sValue = isPlain(s) ? s[key2] : void 0;
35293
35892
  if (mValue === void 0) {
@@ -36545,12 +37144,12 @@ var SpecWorkspace = class {
36545
37144
  return this.relativizeSpec("interface", stored);
36546
37145
  }
36547
37146
  prepareImplementationForWrite(spec, carry = true) {
36548
- const relative20 = this.relativizeSpec("implementation", spec, carry);
37147
+ const relative22 = this.relativizeSpec("implementation", spec, carry);
36549
37148
  const member = this.getSubprojectPrefix(spec.id);
36550
37149
  const memberRoot = member ? this.resolveSubprojectForNamespace(member) : null;
36551
- if (memberRoot) return childRelativeFilePaths(relative20, this.rootDir, memberRoot);
37150
+ if (memberRoot) return childRelativeFilePaths(relative22, this.rootDir, memberRoot);
36552
37151
  const partDir = this.partHolding(this.getImplementationPath(spec.id, spec.contract))?.part.directory;
36553
- return partDir ? partRelativeFilePaths(relative20, this.rootDir, partDir) : relative20;
37152
+ return partDir ? partRelativeFilePaths(relative22, this.rootDir, partDir) : relative22;
36554
37153
  }
36555
37154
  /**
36556
37155
  * A type written in its stored form — every type position canonical (one
@@ -37582,7 +38181,7 @@ var SpecWorkspace = class {
37582
38181
  out[DELTA_JUMP_PINS] = pins;
37583
38182
  return out;
37584
38183
  };
37585
- const unpinned = (step) => {
38184
+ const unpinned2 = (step) => {
37586
38185
  const out = { ...step };
37587
38186
  delete out[DELTA_JUMP_PINS];
37588
38187
  for (const lf of JUMP_LIST_FIELDS) {
@@ -37792,7 +38391,7 @@ var SpecWorkspace = class {
37792
38391
  }
37793
38392
  }
37794
38393
  }
37795
- return steps.map(unpinned).sort((a, b) => a.stepNumber - b.stepNumber);
38394
+ return steps.map(unpinned2).sort((a, b) => a.stepNumber - b.stepNumber);
37796
38395
  };
37797
38396
  const isPlainObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
37798
38397
  const mergeExt = (existing, delta2) => {
@@ -39111,6 +39710,7 @@ function unregisterDomain(id) {
39111
39710
  }
39112
39711
 
39113
39712
  // src/core/external-producers.ts
39713
+ var fs16 = __toESM(require("fs"));
39114
39714
  var path23 = __toESM(require("path"));
39115
39715
  function hostedOnlyProducer(id) {
39116
39716
  return `hosted-only producer \`${id}\`: available only through the hosted server`;
@@ -39224,9 +39824,15 @@ function resolveCandidate(alias, declaration2) {
39224
39824
  reachable: whole
39225
39825
  };
39226
39826
  }
39227
- function listConsumers() {
39827
+ function listConsumers(search) {
39228
39828
  const bound2 = path23.resolve(getProjectRoot());
39229
39829
  const { top } = climb(bound2);
39830
+ const family = familyConsumers(bound2, top);
39831
+ const seen = new Set(family.map((c) => dirKey(c.directory)));
39832
+ const found = searchedConsumers(bound2, search ?? []).filter((c) => !seen.has(dirKey(c.directory)));
39833
+ return [...family, ...found];
39834
+ }
39835
+ function familyConsumers(bound2, top) {
39230
39836
  return runWithProjectRoot(top, () => {
39231
39837
  const family = graph();
39232
39838
  const producer = consumerNode(family, bound2);
@@ -39245,16 +39851,76 @@ function listConsumers() {
39245
39851
  directory: node.directory,
39246
39852
  alias,
39247
39853
  section: external ? "externals" : "members",
39248
- names: usage.used.map((u) => u.publicName).sort()
39854
+ names: usage.used.map((u) => u.publicName).sort(),
39855
+ ...brokenNames(usage)
39249
39856
  });
39250
39857
  }
39251
39858
  return consumers.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
39252
39859
  });
39253
39860
  }
39861
+ function brokenNames(usage) {
39862
+ const names = [...new Set(usage.unexported.map((r) => r.authored.split("::").pop() ?? r.authored))].sort();
39863
+ return names.length ? { broken: names } : {};
39864
+ }
39865
+ function projectRootsUnder(folder) {
39866
+ const isRoot = (dir) => fs16.existsSync(path23.join(dir, ".wai", "project.yaml"));
39867
+ const at = path23.resolve(folder);
39868
+ if (!fs16.existsSync(at)) return [];
39869
+ if (isRoot(at)) return [at];
39870
+ return fs16.readdirSync(at, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".") && d.name !== "node_modules").map((d) => path23.join(at, d.name)).filter(isRoot);
39871
+ }
39872
+ function searchedConsumers(bound2, search) {
39873
+ if (search.length === 0) return [];
39874
+ if (getHostedLookup() !== null) throw new Error("searching folders for consumers is a local read: a hosted request reads no folder outside its project");
39875
+ const producerId = runWithProjectRoot(bound2, () => {
39876
+ const config = consumerConfig(bound2);
39877
+ return config ? effectiveProjectId(config) : null;
39878
+ });
39879
+ const roots = [...new Set(search.flatMap(projectRootsUnder).map((r) => path23.resolve(r)))].filter((r) => dirKey(r) !== dirKey(bound2));
39880
+ const out = [];
39881
+ for (const root of roots) {
39882
+ const answer = runWithProjectRoot(root, () => {
39883
+ let config;
39884
+ try {
39885
+ config = consumerConfig(root);
39886
+ } catch {
39887
+ return null;
39888
+ }
39889
+ if (!config) return null;
39890
+ const declared = declaredExternals(config).find((e) => !e.problem && (e.sourcePath !== void 0 && dirKey(path23.resolve(root, e.sourcePath)) === dirKey(bound2) || e.sourceGit !== void 0 && producerId !== null && e.project === producerId));
39891
+ if (!declared) return null;
39892
+ const node = consumerNode(graph(), root);
39893
+ const usage = node ? pinnedUsage(node.namespace, declared.alias) : null;
39894
+ return {
39895
+ project: effectiveProjectId(config) ?? path23.basename(root),
39896
+ key: node?.namespace ?? "",
39897
+ directory: root,
39898
+ alias: declared.alias,
39899
+ section: "externals",
39900
+ names: (usage?.used ?? []).map((u) => u.publicName).sort(),
39901
+ found: "search"
39902
+ };
39903
+ });
39904
+ if (answer) out.push(answer);
39905
+ }
39906
+ return out.sort((a, b) => a.directory < b.directory ? -1 : a.directory > b.directory ? 1 : 0);
39907
+ }
39908
+ function approvedRevision(against) {
39909
+ const bound2 = path23.resolve(getProjectRoot());
39910
+ const repo = repositoryRoot(bound2);
39911
+ if (!repo) throw new Error(`${bound2} is in no git work tree, so there is no earlier revision to compare with \u2014 pass a saved surface snapshot (\`--against <file>\`) instead`);
39912
+ const commit2 = against !== void 0 ? commitOf(bound2, against) : lastCommitOf(bound2, path23.join(".wai", "lock.json"));
39913
+ if (!commit2) {
39914
+ throw new Error(against !== void 0 ? `"${against}" names no commit of the repository at ${repo}` : "no approval of this project was ever committed (.wai/lock.json is in no commit) \u2014 lock and commit first, or name a revision (`--against <ref>`)");
39915
+ }
39916
+ const relative22 = path23.relative(repo, bound2);
39917
+ const directory = fetch(repo, commit2, relative22 === "" ? void 0 : relative22);
39918
+ return { directory, commit: commit2, label: against !== void 0 ? `${against} (${commit2.slice(0, 12)})` : `the last approval, committed at ${commit2.slice(0, 12)}` };
39919
+ }
39254
39920
 
39255
39921
  // src/core/part-context.ts
39256
39922
  var crypto6 = __toESM(require("crypto"));
39257
- var fs16 = __toESM(require("fs"));
39923
+ var fs17 = __toESM(require("fs"));
39258
39924
  var path24 = __toESM(require("path"));
39259
39925
  var PartContextUnavailable = class extends WaironError {
39260
39926
  constructor(message) {
@@ -39265,7 +39931,7 @@ var PartContextUnavailable = class extends WaironError {
39265
39931
  function sameDirectory(a, b) {
39266
39932
  const canonical3 = (d) => {
39267
39933
  try {
39268
- return fs16.realpathSync.native(d);
39934
+ return fs17.realpathSync.native(d);
39269
39935
  } catch {
39270
39936
  return path24.resolve(d);
39271
39937
  }
@@ -39393,7 +40059,7 @@ function excerpt() {
39393
40059
  if (!isPart(config) || !partOf) throw new PartContextUnavailable(`${partRoot} is not a part: its configuration declares no partOf`);
39394
40060
  if (partOf.path === void 0) throw new PartContextUnavailable(`the part at ${partRoot} records no path to its parent "${partOf.project}"`);
39395
40061
  const parentRoot = path24.resolve(partRoot, partOf.path);
39396
- if (!fs16.existsSync(parentRoot)) throw new PartContextUnavailable(`the parent "${partOf.project}" is not on disk at ${parentRoot}`);
40062
+ if (!fs17.existsSync(parentRoot)) throw new PartContextUnavailable(`the parent "${partOf.project}" is not on disk at ${parentRoot}`);
39397
40063
  const read2 = runWithProjectRoot(parentRoot, () => readParent(parentRoot, partRoot, partOf.project));
39398
40064
  const { commit: _provenance, ...content } = read2;
39399
40065
  const digest3 = `sha256:${crypto6.createHash("sha256").update(canonicalize(content)).digest("hex")}`;
@@ -39401,7 +40067,7 @@ function excerpt() {
39401
40067
  }
39402
40068
 
39403
40069
  // src/core/provision.ts
39404
- var fs17 = __toESM(require("fs"));
40070
+ var fs18 = __toESM(require("fs"));
39405
40071
  var path25 = __toESM(require("path"));
39406
40072
  function defaultProjectConfig(name, now, id) {
39407
40073
  const declared = id ?? effectiveProjectId({ name });
@@ -39452,7 +40118,7 @@ function provisionProject(name) {
39452
40118
  function ensureProjectInitialized(fallbackName, id, vision) {
39453
40119
  const now = (/* @__PURE__ */ new Date()).toISOString();
39454
40120
  const paths = aiPathsAt(getProjectRoot());
39455
- const hasSystem = fs17.existsSync(paths.specsSystem());
40121
+ const hasSystem = fs18.existsSync(paths.specsSystem());
39456
40122
  let name = fallbackName;
39457
40123
  if (hasSystem) {
39458
40124
  const existing = loadSystemSpec();
@@ -39472,7 +40138,7 @@ function ensureProjectInitialized(fallbackName, id, vision) {
39472
40138
  return { wroteConfig, wroteSystem };
39473
40139
  }
39474
40140
  function isPartDirectory(dir) {
39475
- return fs17.existsSync(dir) && !holdsProjectContent(dir);
40141
+ return fs18.existsSync(dir) && !holdsProjectContent(dir);
39476
40142
  }
39477
40143
  function memberDeclarationsAt2(dir) {
39478
40144
  let config = null;
@@ -39489,7 +40155,7 @@ function memberDeclarationsAt2(dir) {
39489
40155
  out.push({ alias: member.alias, path: member.path, form: "members" });
39490
40156
  }
39491
40157
  const specsDir = aiPathsAt(dir).specsDir();
39492
- if (!fs17.existsSync(specsDir)) return out;
40158
+ if (!fs18.existsSync(specsDir)) return out;
39493
40159
  for (const file of listFilesRecursive(specsDir, ".yaml")) {
39494
40160
  let raw;
39495
40161
  try {
@@ -39540,7 +40206,7 @@ function listDirectChainedSubprojects(projectRoot) {
39540
40206
  return out;
39541
40207
  }
39542
40208
  function childHasSpecsButNoConfig(childDir) {
39543
- return fs17.existsSync(aiPathsAt(childDir).specsDir()) && !projectConfigRepositoryAt(childDir).exists();
40209
+ return fs18.existsSync(aiPathsAt(childDir).specsDir()) && !projectConfigRepositoryAt(childDir).exists();
39544
40210
  }
39545
40211
  function findChainingSubprojectsMissingConfig(projectRoot) {
39546
40212
  const missing = [];
@@ -39612,7 +40278,7 @@ function createMember(alias, source, description, as) {
39612
40278
  }
39613
40279
  function holdsProjectContent(dir) {
39614
40280
  const paths = aiPathsAt(dir);
39615
- if (fs17.existsSync(paths.specsSystem()) || fs17.existsSync(path25.join(paths.root(), "lock.json"))) return true;
40281
+ if (fs18.existsSync(paths.specsSystem()) || fs18.existsSync(path25.join(paths.root(), "lock.json"))) return true;
39616
40282
  try {
39617
40283
  return projectConfigRepositoryAt(dir).load()?.id !== void 0;
39618
40284
  } catch {
@@ -39736,14 +40402,14 @@ function moveMember(alias, newPath) {
39736
40402
  const oldDir = assertContainedProjectPath(root, currentPath);
39737
40403
  const newDir = assertContainedProjectPath(root, nextPath);
39738
40404
  if (oldDir !== newDir) {
39739
- if (!fs17.existsSync(oldDir)) {
40405
+ if (!fs18.existsSync(oldDir)) {
39740
40406
  throw new WaironError(`Member directory not found at its current path: ${oldDir}`);
39741
40407
  }
39742
- if (fs17.existsSync(newDir)) {
40408
+ if (fs18.existsSync(newDir)) {
39743
40409
  throw new WaironError(`Target directory already exists: ${newDir}`);
39744
40410
  }
39745
40411
  ensureDir(path25.dirname(newDir));
39746
- fs17.renameSync(oldDir, newDir);
40412
+ fs18.renameSync(oldDir, newDir);
39747
40413
  }
39748
40414
  projectConfigRepository.setMemberPath(alias, nextPath);
39749
40415
  invalidateSpecCache();
@@ -39756,13 +40422,13 @@ function moveSibling(alias, currentPath, newPath, isPart2) {
39756
40422
  }
39757
40423
  const oldDir = path25.resolve(root, currentPath);
39758
40424
  const newDir = path25.resolve(root, nextPath);
39759
- if (!fs17.existsSync(oldDir)) throw new WaironError(`Member directory not found at its current path: ${oldDir}`);
39760
- if (fs17.existsSync(newDir)) throw new WaironError(`Target directory already exists: ${newDir}`);
40425
+ if (!fs18.existsSync(oldDir)) throw new WaironError(`Member directory not found at its current path: ${oldDir}`);
40426
+ if (fs18.existsSync(newDir)) throw new WaironError(`Target directory already exists: ${newDir}`);
39761
40427
  ensureDir(path25.dirname(newDir));
39762
- if (fs17.statSync(oldDir).dev !== fs17.statSync(path25.dirname(newDir)).dev) {
40428
+ if (fs18.statSync(oldDir).dev !== fs18.statSync(path25.dirname(newDir)).dev) {
39763
40429
  throw new WaironError(`Refusing to move the member "${alias}" to ${newDir}: it lies on another volume, and a rename across volumes is not atomic.`);
39764
40430
  }
39765
- fs17.renameSync(oldDir, newDir);
40431
+ fs18.renameSync(oldDir, newDir);
39766
40432
  if (isPart2) {
39767
40433
  const parentId = effectiveProjectId(projectConfigRepository.load() ?? { name: loadSystemSpec()?.name ?? "" });
39768
40434
  if (parentId !== null) runWithProjectRoot(newDir, () => projectConfigRepository.setPartOf({ project: parentId, path: toPosixPath(path25.relative(newDir, root)) || "." }));
@@ -39792,8 +40458,8 @@ function isWithinDir2(dir, file) {
39792
40458
  }
39793
40459
  var MOVED_REF_POSITIONS = /* @__PURE__ */ new Set(["component", "interface"]);
39794
40460
  var INTO_MEMBER_POSITIONS = /* @__PURE__ */ new Set([...MOVED_REF_POSITIONS, "type"]);
39795
- function externalizeSubsystem(subsystemId, path49, as) {
39796
- return externalizeInto(subsystemId, path49, as);
40461
+ function externalizeSubsystem(subsystemId, path50, as) {
40462
+ return externalizeInto(subsystemId, path50, as);
39797
40463
  }
39798
40464
  function externalizeInto(subsystemId, partPath, as) {
39799
40465
  if (subsystemId.includes("::")) {
@@ -39820,7 +40486,7 @@ function externalizeInto(subsystemId, partPath, as) {
39820
40486
  if (holdsProjectContent(partDir)) throw new WaironError(`cannot externalize "${subsystemId}": ${partDir} already holds a project.`);
39821
40487
  const joined = bound2?.parts.find((p) => p.directory !== void 0 && path25.resolve(p.directory) === path25.resolve(partDir));
39822
40488
  const alias = joined?.alias ?? subsystemId;
39823
- if (!joined && fs17.existsSync(aiPathsAt(partDir).projectConfig())) {
40489
+ if (!joined && fs18.existsSync(aiPathsAt(partDir).projectConfig())) {
39824
40490
  throw new WaironError(`cannot externalize "${subsystemId}": ${partDir} already holds a configuration (.wai/project.yaml) \u2014 a new part's directory holds none; creating one never overwrites it.`);
39825
40491
  }
39826
40492
  if (!joined && (declaredMembers(projectConfigRepository.load() ?? {}).some((m) => m.alias === alias) || projectConfigRepository.load()?.externals?.[alias] !== void 0)) {
@@ -39828,10 +40494,10 @@ function externalizeInto(subsystemId, partPath, as) {
39828
40494
  }
39829
40495
  const partSpecsDir = aiPathsAt(partDir).specsDir();
39830
40496
  const targetDir = path25.join(partSpecsDir, subsystemId);
39831
- if (owned.home !== void 0 && fs17.existsSync(targetDir)) throw new WaironError(`target already contains a "${subsystemId}" subsystem: ${targetDir}`);
40497
+ if (owned.home !== void 0 && fs18.existsSync(targetDir)) throw new WaironError(`target already contains a "${subsystemId}" subsystem: ${targetDir}`);
39832
40498
  const destinationOf = (file) => path25.join(partSpecsDir, path25.relative(parentSpecsDir, file));
39833
40499
  const strays = owned.files.filter((f) => !(owned.home !== void 0 && isWithinDir2(owned.home, f)));
39834
- const taken = strays.filter((f) => fs17.existsSync(destinationOf(f)));
40500
+ const taken = strays.filter((f) => fs18.existsSync(destinationOf(f)));
39835
40501
  if (taken.length > 0) throw new WaironError(`cannot externalize "${subsystemId}": the part already holds ${taken.map((f) => toPosixPath(path25.relative(partDir, destinationOf(f)))).join(", ")}.`);
39836
40502
  if (as === "project") {
39837
40503
  if (joined) throw new WaironError(`cannot externalize "${subsystemId}" as a project into the existing part "${joined.alias}": externalize it into the part, then promote the part (\`wairon member promote ${joined.alias}\`).`);
@@ -39842,13 +40508,13 @@ function externalizeInto(subsystemId, partPath, as) {
39842
40508
  const moved = [];
39843
40509
  if (owned.home !== void 0) {
39844
40510
  ensureDir(path25.dirname(targetDir));
39845
- fs17.renameSync(owned.home, targetDir);
40511
+ fs18.renameSync(owned.home, targetDir);
39846
40512
  moved.push(...specFilesUnder(targetDir));
39847
40513
  }
39848
40514
  for (const file of strays) {
39849
40515
  const to = destinationOf(file);
39850
40516
  ensureDir(path25.dirname(to));
39851
- fs17.renameSync(file, to);
40517
+ fs18.renameSync(file, to);
39852
40518
  moved.push(to);
39853
40519
  removeEmptyDirsUpTo(path25.dirname(file), parentSpecsDir);
39854
40520
  }
@@ -39859,10 +40525,10 @@ function externalizeInto(subsystemId, partPath, as) {
39859
40525
  }
39860
40526
  function subsystemFiles(subsystemId, specsDir) {
39861
40527
  const subPath = getSubsystemPath(subsystemId);
39862
- const nested = subPath.endsWith(".index.yaml") && path25.resolve(path25.dirname(subPath)) === path25.resolve(path25.join(specsDir, subsystemId)) && fs17.existsSync(subPath);
40528
+ const nested = subPath.endsWith(".index.yaml") && path25.resolve(path25.dirname(subPath)) === path25.resolve(path25.join(specsDir, subsystemId)) && fs18.existsSync(subPath);
39863
40529
  const home = nested ? path25.dirname(subPath) : void 0;
39864
40530
  const files = new Set(home !== void 0 ? specFilesUnder(home).map((f) => path25.resolve(f)) : []);
39865
- if (!nested && fs17.existsSync(subPath)) files.add(path25.resolve(subPath));
40531
+ if (!nested && fs18.existsSync(subPath)) files.add(path25.resolve(subPath));
39866
40532
  const l0 = path25.resolve(aiPathsAt(getProjectRoot()).specsSystem());
39867
40533
  const others = () => specFilesUnder(specsDir).filter((f) => path25.resolve(f) !== l0 && !files.has(path25.resolve(f)));
39868
40534
  const add2 = (file) => {
@@ -39890,13 +40556,13 @@ function subsystemFiles(subsystemId, specsDir) {
39890
40556
  function removeEmptyDirsUpTo(dir, stop) {
39891
40557
  let at = path25.resolve(dir);
39892
40558
  const end = path25.resolve(stop);
39893
- while (at !== end && isWithinDir2(end, at) && fs17.existsSync(at) && fs17.readdirSync(at).length === 0) {
39894
- fs17.rmdirSync(at);
40559
+ while (at !== end && isWithinDir2(end, at) && fs18.existsSync(at) && fs18.readdirSync(at).length === 0) {
40560
+ fs18.rmdirSync(at);
39895
40561
  at = path25.dirname(at);
39896
40562
  }
39897
40563
  }
39898
40564
  function specFilesUnder(dir) {
39899
- return fs17.existsSync(dir) ? listFilesRecursive(dir, ".yaml") : [];
40565
+ return fs18.existsSync(dir) ? listFilesRecursive(dir, ".yaml") : [];
39900
40566
  }
39901
40567
  function ownSpecFiles(family, except) {
39902
40568
  const root = getProjectRoot();
@@ -40029,7 +40695,7 @@ function rewriteRefsIn(specs, remap, record) {
40029
40695
  return rewritten;
40030
40696
  }
40031
40697
  function patchKeepingStamp(file, mutate) {
40032
- if (!fs17.existsSync(file)) return;
40698
+ if (!fs18.existsSync(file)) return;
40033
40699
  const raw = readYamlFile(file);
40034
40700
  if (mutate(raw)) writeYamlFile(file, raw);
40035
40701
  }
@@ -40074,7 +40740,7 @@ function promoteMember(alias, id) {
40074
40740
  patchKeepingStamp(s.file, (raw) => raw.parentSystem === projectName ? false : (raw.parentSystem = projectName, true));
40075
40741
  }
40076
40742
  if (declaration2?.as === "part") projectConfigRepository.updateMember(alias, { as: "" });
40077
- const result = { respelled: [], memberRespelled: [], exported: [], imported: [], consumersDropped: [], entriesDeclared: [], topicsCrossing: [] };
40743
+ const result = { respelled: [], memberRespelled: [], exported: [], imported: [], consumersDropped: [], entriesDeclared: [], topicsCrossing: [], pathsRebased: [], signaturesRederived: [] };
40078
40744
  const parentSide = rewriteRefsIn(crossing.in.map((c) => c.spec).filter(unique), (ref, position) => INTO_MEMBER_POSITIONS.has(position) && crossing.insideTargets.has(ref) ? `${alias}::${ref}` : ref, { result, member: false });
40079
40745
  const climbs = climbsOf(family, crossing.insideIds, parentId).map;
40080
40746
  const climbing = inside.filter((s) => family.authoredReferences.some((r) => r.form === "super" && r.specId === s.id));
@@ -40107,8 +40773,71 @@ function promoteMember(alias, id) {
40107
40773
  });
40108
40774
  }
40109
40775
  invalidateSpecCache();
40776
+ rebaseEscapingPaths(dir, getProjectRoot(), result);
40777
+ rederiveSignatures(new Set(result.respelled.map((r) => r.specId)), result.signaturesRederived);
40778
+ runWithProjectRoot(dir, () => {
40779
+ invalidateSpecCache();
40780
+ rederiveSignatures(new Set(result.memberRespelled.map((r) => localIn(r.specId, memberKey ?? alias))), result.signaturesRederived);
40781
+ });
40782
+ invalidateSpecCache();
40110
40783
  return result;
40111
40784
  }
40785
+ function rebaseEscapingPaths(memberDir, familyRoot, result) {
40786
+ const shownMember = toPosixPath(path25.relative(familyRoot, memberDir)) || ".";
40787
+ for (const file of specFilesUnder(aiPathsAt(memberDir).specsDir())) {
40788
+ let raw;
40789
+ try {
40790
+ raw = readYamlFile(file);
40791
+ } catch {
40792
+ continue;
40793
+ }
40794
+ if (!raw || typeof raw !== "object") continue;
40795
+ const isImplementation = "contract" in raw;
40796
+ if (!isImplementation && specKind(raw) !== "type") continue;
40797
+ let changed = false;
40798
+ const rebase = (holder, key2, where) => {
40799
+ const p = holder[key2];
40800
+ if (typeof p !== "string" || p === "" || path25.isAbsolute(p)) return;
40801
+ const absolute = path25.resolve(memberDir, p);
40802
+ if (isWithinDir2(memberDir, absolute)) return;
40803
+ const next = toPosixPath(path25.relative(familyRoot, absolute));
40804
+ if (next.startsWith("..") || path25.isAbsolute(next)) return;
40805
+ holder[key2] = next;
40806
+ changed = true;
40807
+ result.pathsRebased.push(`${raw.id} ${where}: ${p} \u2192 ${next} (the code is not moved: it is planned there until ${next} of the parent moves into ${shownMember})`);
40808
+ };
40809
+ for (const key2 of isImplementation ? ["sourcePath", "simPath"] : ["sourcePath"]) rebase(raw, key2, key2);
40810
+ if (Array.isArray(raw.methods)) {
40811
+ for (const method of raw.methods) {
40812
+ if (method && typeof method === "object") rebase(method, "sourcePath", `${method.name} sourcePath`);
40813
+ }
40814
+ }
40815
+ if (changed) writeYamlFile(file, raw);
40816
+ }
40817
+ invalidateSpecCache();
40818
+ }
40819
+ function rederiveSignatures(touched, lines) {
40820
+ if (touched.size === 0) return;
40821
+ invalidateSpecCache();
40822
+ const stale = signatureFacts().staleTexts.filter((t) => touched.has(t.specId));
40823
+ if (stale.length === 0) return;
40824
+ const saved = /* @__PURE__ */ new Set();
40825
+ for (const t of stale) {
40826
+ const key2 = `${t.kind}:${t.specId}`;
40827
+ if (!saved.has(key2)) {
40828
+ saved.add(key2);
40829
+ if (t.kind === "interface") {
40830
+ const intf = loadInterfaceSpec(t.specId);
40831
+ if (intf) saveInterfaceSpec(intf);
40832
+ } else {
40833
+ const type = loadTypeSpec(t.specId);
40834
+ if (type) saveSpec("type", type);
40835
+ }
40836
+ }
40837
+ lines.push(`${t.specId}.${t.method}: ${t.stored} \u2192 ${t.derived}`);
40838
+ }
40839
+ invalidateSpecCache();
40840
+ }
40112
40841
  function callsInto2(callers, portals) {
40113
40842
  const componentOf = new Map(callers.filter((s) => s.kind === "interface").map((s) => [s.id, String(s.raw.component ?? "")]));
40114
40843
  const out = /* @__PURE__ */ new Map();
@@ -40269,7 +40998,7 @@ function respellQualified(inside, outside, names, result) {
40269
40998
  };
40270
40999
  for (const [specs, side] of [[inside, "inside"], [outside, "outside"]]) {
40271
41000
  for (const spec of specs) {
40272
- if (!fs17.existsSync(spec.file)) continue;
41001
+ if (!fs18.existsSync(spec.file)) continue;
40273
41002
  const raw = readYamlFile(spec.file);
40274
41003
  if (!raw || typeof raw !== "object") continue;
40275
41004
  const edits = [];
@@ -40333,24 +41062,99 @@ function dropCrossingConsumers(inside, outside, result) {
40333
41062
  }
40334
41063
  }
40335
41064
  }
41065
+ function audienceRank4(audience) {
41066
+ const idx = SURFACE_AUDIENCES.indexOf(audience ?? "instance");
41067
+ return idx === -1 ? SURFACE_AUDIENCES.indexOf("instance") : idx;
41068
+ }
40336
41069
  function exportReExported(inside, memberDir, alias, result) {
40337
41070
  invalidateSpecCache();
40338
- const entries = (loadSystemSpec()?.publicInterfaces ?? []).filter((e) => e.from === alias);
40339
- if (entries.length === 0) return;
40340
- const targets = entries.map((e) => e.component ?? e.typeDef).filter((t) => t !== void 0);
40341
- const whole = entries.some((e) => e.component === void 0 && e.typeDef === void 0);
41071
+ const parent = loadSystemSpec();
41072
+ const parentEntries = parent?.publicInterfaces ?? [];
41073
+ const entries = parentEntries.filter((e) => e.from === alias);
41074
+ if (!parent || entries.length === 0) return;
41075
+ const parentId = effectiveProjectId(projectConfigRepository.load() ?? { name: parent.name }) ?? parent.name;
41076
+ const repointed = /* @__PURE__ */ new Map();
40342
41077
  runWithProjectRoot(memberDir, () => {
40343
- for (const name of exportItems(targets)) result.exported.push(`${alias} L0: ${name} (re-exported by the parent)`);
40344
- if (!whole) return;
40345
41078
  invalidateSpecCache();
40346
41079
  const system = loadSystemSpec();
40347
41080
  if (!system) return;
40348
- const own2 = system.publicInterfaces ?? [];
40349
- const added = inside.filter((s) => s.kind === "subsystem" && !own2.some((e) => e.from === s.id && e.component === void 0 && e.typeDef === void 0)).map((s) => ({ from: s.id, audience: "project" }));
40350
- if (added.length === 0) return;
40351
- saveSystemSpec({ ...system, publicInterfaces: [...own2, ...added] });
40352
- for (const e of added) result.exported.push(`${alias} L0: everything ${e.from} exports (re-exported whole by the parent)`);
41081
+ const own2 = [...system.publicInterfaces ?? []];
41082
+ const types = loadTypeSpecs();
41083
+ let changed = false;
41084
+ for (const e of entries) {
41085
+ const audience = e.audience ?? "instance";
41086
+ if (e.component === void 0 && e.typeDef === void 0 && e.interface === void 0) {
41087
+ for (const s of inside.filter((x) => x.kind === "subsystem")) {
41088
+ const whole = own2.find((x) => x.from === s.id && x.component === void 0 && x.typeDef === void 0 && x.interface === void 0);
41089
+ if (whole) {
41090
+ if (audienceRank4(whole.audience) < audienceRank4(audience)) {
41091
+ whole.audience = audience;
41092
+ changed = true;
41093
+ result.exported.push(`${alias} L0: everything ${s.id} exports widened to ${audience} (re-exported whole by the parent)`);
41094
+ }
41095
+ continue;
41096
+ }
41097
+ own2.push({ from: s.id, audience });
41098
+ changed = true;
41099
+ result.exported.push(`${alias} L0: everything ${s.id} exports at ${audience} (re-exported whole by the parent)`);
41100
+ }
41101
+ continue;
41102
+ }
41103
+ const type = e.typeDef !== void 0 ? types.find((t) => t.id === localIn(e.typeDef, alias)) : void 0;
41104
+ const interfaceId = e.interface !== void 0 ? localIn(e.interface, alias) : void 0;
41105
+ const componentId = e.component !== void 0 ? localIn(e.component, alias) : interfaceId !== void 0 ? loadInterfaceSpec(interfaceId)?.component : void 0;
41106
+ const component = type === void 0 && componentId !== void 0 ? loadComponentSpec(componentId) : null;
41107
+ if (type === void 0 && !component) continue;
41108
+ const field = type ? "typeDef" : "component";
41109
+ const item = type ? type.id : component.id;
41110
+ const publicName = e.as ?? e.id ?? (interfaceId ?? item);
41111
+ if (type?.subsystem) exportTypeFromSubsystem(type.subsystem, type.id);
41112
+ const held = own2.find((x) => x[field] === item && (interfaceId === void 0 || x.interface === void 0 || x.interface === interfaceId));
41113
+ if (held) {
41114
+ if (audienceRank4(held.audience ?? "project") < audienceRank4(audience)) {
41115
+ held.audience = audience;
41116
+ changed = true;
41117
+ }
41118
+ const name = held.as ?? held.id ?? (held.interface ?? item);
41119
+ result.exported.push(`${alias} L0: ${item} as ${name} at ${held.audience} (re-exported by the parent)`);
41120
+ repointed.set(e, { name, field, item });
41121
+ continue;
41122
+ }
41123
+ const subsystem = type ? type.subsystem : component.subsystem;
41124
+ own2.push({
41125
+ ...subsystem ? { from: subsystem } : {},
41126
+ [field]: item,
41127
+ ...interfaceId !== void 0 && !type ? { interface: interfaceId } : {},
41128
+ ...publicName !== (interfaceId ?? item) ? { as: publicName } : {},
41129
+ ...e.details !== void 0 ? { details: e.details } : {},
41130
+ audience
41131
+ });
41132
+ changed = true;
41133
+ result.exported.push(`${alias} L0: ${item}${publicName !== item ? ` as ${publicName}` : ""} at ${audience} (re-exported by the parent)`);
41134
+ repointed.set(e, { name: publicName, field, item });
41135
+ }
41136
+ if (changed) saveSystemSpec({ ...system, publicInterfaces: own2 });
41137
+ invalidateSpecCache();
41138
+ const table = resolveProjectExports();
41139
+ for (const [e, to] of repointed) {
41140
+ const interfaceId = e.interface !== void 0 ? localIn(e.interface, alias) : void 0;
41141
+ const bound2 = table.entries.find((x) => to.field === "typeDef" ? x.typeDef === to.item : x.component === to.item && (interfaceId === void 0 || x.interface === void 0 || x.interface === interfaceId));
41142
+ if (bound2) to.name = bound2.publicName;
41143
+ }
41144
+ });
41145
+ invalidateSpecCache();
41146
+ if (repointed.size === 0) return;
41147
+ const next = parentEntries.map((e) => {
41148
+ const to = repointed.get(e);
41149
+ if (!to) return e;
41150
+ const { component: _c, interface: _i, typeDef: _t, ...rest } = e;
41151
+ const before = e.interface ?? e.component ?? e.typeDef;
41152
+ const out = { ...rest, [to.field]: to.name, ...e.as === void 0 && to.name !== before ? { as: before } : {} };
41153
+ const shownAs = out.as ?? to.name;
41154
+ result.exported.push(`${parentId} L0: ${shownAs} re-exported through ${alias} as ${to.name} at ${e.audience ?? "instance"} (was ${before} from subsystem ${alias})`);
41155
+ return out;
40353
41156
  });
41157
+ saveSystemSpec({ ...parent, publicInterfaces: next });
40354
41158
  invalidateSpecCache();
40355
41159
  }
40356
41160
  function promotedVision(id, parentName) {
@@ -40522,7 +41326,7 @@ function internalizeRefusals(scan, destination) {
40522
41326
  const specsDir = aiPathsAt(getProjectRoot()).specsDir();
40523
41327
  const memberSpecs = aiPathsAt(scan.memberDir).specsDir();
40524
41328
  const skip = path25.resolve(aiPathsAt(scan.memberDir).specsSystem());
40525
- const taken = (fs17.existsSync(memberSpecs) ? listFilesRecursive(memberSpecs, "") : []).filter((f) => path25.resolve(f) !== skip && fs17.existsSync(path25.join(specsDir, path25.relative(memberSpecs, f)))).map((f) => toPosixPath(path25.relative(memberSpecs, f)));
41329
+ const taken = (fs18.existsSync(memberSpecs) ? listFilesRecursive(memberSpecs, "") : []).filter((f) => path25.resolve(f) !== skip && fs18.existsSync(path25.join(specsDir, path25.relative(memberSpecs, f)))).map((f) => toPosixPath(path25.relative(memberSpecs, f)));
40526
41330
  if (taken.length > 0) out.push(`its specs would land on this project's ${taken.join(", ")}`);
40527
41331
  out.push(...aliasConflicts(scan, bound2));
40528
41332
  if (destination.packs === void 0) {
@@ -40540,7 +41344,7 @@ function internalizeRefusals(scan, destination) {
40540
41344
  if (held !== void 0 && held !== scan.alias) out.push(`the export "${name}" is a public name this project already exports for ${held}`);
40541
41345
  }
40542
41346
  const wai = path25.join(scan.memberDir, ".wai");
40543
- for (const entry of fs17.existsSync(wai) ? fs17.readdirSync(wai) : []) {
41347
+ for (const entry of fs18.existsSync(wai) ? fs18.readdirSync(wai) : []) {
40544
41348
  if (entry === "specs" || entry === "transactions" || MEMBER_WAI_GROUPS.has(entry)) continue;
40545
41349
  out.push(`its .wai holds "${entry}", which this write does not recognise \u2014 move or delete it by hand`);
40546
41350
  }
@@ -40751,15 +41555,15 @@ function carryPacks(scan, destination, result) {
40751
41555
  }
40752
41556
  function deleteMemberWai(memberDir) {
40753
41557
  const wai = path25.join(memberDir, ".wai");
40754
- if (!fs17.existsSync(wai)) return [];
41558
+ if (!fs18.existsSync(wai)) return [];
40755
41559
  const deleted = listFilesRecursive(wai, "").filter((f) => !path25.relative(wai, f).split(path25.sep).includes("transactions")).map((f) => {
40756
41560
  const rel2 = toPosixPath(path25.relative(wai, f));
40757
41561
  return `${MEMBER_WAI_GROUPS.get(rel2.split("/")[0]) ?? "L0 (its pieces placed)"}: .wai/${rel2}`;
40758
41562
  });
40759
- for (const entry of fs17.readdirSync(wai)) {
40760
- if (entry !== "transactions") fs17.rmSync(path25.join(wai, entry), { recursive: true, force: true });
41563
+ for (const entry of fs18.readdirSync(wai)) {
41564
+ if (entry !== "transactions") fs18.rmSync(path25.join(wai, entry), { recursive: true, force: true });
40761
41565
  }
40762
- if (fs17.readdirSync(wai).length === 0) fs17.rmdirSync(wai);
41566
+ if (fs18.readdirSync(wai).length === 0) fs18.rmdirSync(wai);
40763
41567
  return deleted.sort();
40764
41568
  }
40765
41569
  function memberToDemote(family, alias) {
@@ -40772,7 +41576,7 @@ function memberToDemote(family, alias) {
40772
41576
  throw new WaironError(`demote refused: the member "${alias}" is fetched from git \u2014 its files are the fetch cache's. Demote it from a checkout of its repository.`);
40773
41577
  }
40774
41578
  if (referenced?.sourceKind === "hosted") throw new WaironError(`demote refused: the member "${alias}" is a hosted record of its own \u2014 a part is part of its parent's record, so it must live in this project's tree.`);
40775
- if (referenced?.directory !== void 0 && fs17.existsSync(referenced.directory)) return { dir: referenced.directory, contained: false };
41579
+ if (referenced?.directory !== void 0 && fs18.existsSync(referenced.directory)) return { dir: referenced.directory, contained: false };
40776
41580
  throw new WaironError(`demote refused: this project declares no project member "${alias}".`);
40777
41581
  }
40778
41582
  function localsOf(root) {
@@ -40804,7 +41608,7 @@ function demoteRefusals(scan, destination, memberIds, family) {
40804
41608
  if (held !== void 0 && held !== scan.alias) out.push(`the export "${name}" is a public name this project already exports for ${held}`);
40805
41609
  }
40806
41610
  const wai = path25.join(scan.memberDir, ".wai");
40807
- for (const entry of fs17.existsSync(wai) ? fs17.readdirSync(wai) : []) {
41611
+ for (const entry of fs18.existsSync(wai) ? fs18.readdirSync(wai) : []) {
40808
41612
  if (entry === "specs" || entry === "transactions" || MEMBER_WAI_GROUPS.has(entry)) continue;
40809
41613
  out.push(`its .wai holds "${entry}", which this write does not recognise \u2014 move or delete it by hand`);
40810
41614
  }
@@ -40832,7 +41636,7 @@ function demoteMember(alias, destination) {
40832
41636
  refusals.push(`its metadata needs a home subsystem (--home) \u2014 ${home === "" ? "none was named" : `"${home}" is no subsystem of the member or of this project`}; its subsystems are ${subsystems.join(", ") || "none"}`);
40833
41637
  }
40834
41638
  if (refusals.length > 0) throw new WaironError(`demote refused: "${alias}" cannot become a part \u2014 ${refusals.join("; ")}.`);
40835
- const result = { subsystems, types, placed: [], externals: [], deleted: [], notCarried: [], consumersRestored: [] };
41639
+ const result = { subsystems, types, placed: [], externals: [], deleted: [], notCarried: [], consumersRestored: [], signaturesRederived: [] };
40836
41640
  const intoMember = localsOf(dir);
40837
41641
  const backOut = localsOf(root);
40838
41642
  invalidateSpecCache();
@@ -40875,6 +41679,7 @@ function demoteMember(alias, destination) {
40875
41679
  invalidateSpecCache();
40876
41680
  for (const spec of [...parentSide, ...memberSide]) if (spec.kind !== "system") normalizeReferences(spec.kind, spec.id);
40877
41681
  invalidateSpecCache();
41682
+ rederiveSignatures(new Set([...parentSide, ...memberSide].map((s) => s.id)), result.signaturesRederived ??= []);
40878
41683
  return result;
40879
41684
  }
40880
41685
  function respellQualifiedBack(parentSide, memberSide, aliases, tables, crossed) {
@@ -40899,7 +41704,7 @@ function respellQualifiedBack(parentSide, memberSide, aliases, tables, crossed)
40899
41704
  return to;
40900
41705
  };
40901
41706
  for (const spec of specs) {
40902
- if (!fs17.existsSync(spec.file)) continue;
41707
+ if (!fs18.existsSync(spec.file)) continue;
40903
41708
  const raw = readYamlFile(spec.file);
40904
41709
  if (!raw || typeof raw !== "object") continue;
40905
41710
  let changed = false;
@@ -40954,16 +41759,16 @@ function deleteProjectContent(memberDir) {
40954
41759
  const wai = path25.join(memberDir, ".wai");
40955
41760
  const deleted = [];
40956
41761
  const l0 = aiPathsAt(memberDir).specsSystem();
40957
- if (fs17.existsSync(l0)) {
40958
- fs17.rmSync(l0);
41762
+ if (fs18.existsSync(l0)) {
41763
+ fs18.rmSync(l0);
40959
41764
  deleted.push(`L0 (its pieces placed): .wai/${toPosixPath(path25.relative(wai, l0))}`);
40960
41765
  }
40961
- for (const entry of fs17.existsSync(wai) ? fs17.readdirSync(wai) : []) {
41766
+ for (const entry of fs18.existsSync(wai) ? fs18.readdirSync(wai) : []) {
40962
41767
  if (entry === "specs" || entry === "transactions") continue;
40963
41768
  const at = path25.join(wai, entry);
40964
- const files = fs17.statSync(at).isDirectory() ? listFilesRecursive(at, "") : [at];
41769
+ const files = fs18.statSync(at).isDirectory() ? listFilesRecursive(at, "") : [at];
40965
41770
  for (const f of files) deleted.push(`${MEMBER_WAI_GROUPS.get(entry) ?? "derived outputs"}: .wai/${toPosixPath(path25.relative(wai, f))}`);
40966
- fs17.rmSync(at, { recursive: true, force: true });
41771
+ fs18.rmSync(at, { recursive: true, force: true });
40967
41772
  }
40968
41773
  return deleted.sort();
40969
41774
  }
@@ -40993,7 +41798,7 @@ function specContents(dir) {
40993
41798
  const out = /* @__PURE__ */ new Map();
40994
41799
  if (dir === null) return out;
40995
41800
  const specsDir = aiPathsAt(dir).specsDir();
40996
- for (const f of specFilesUnder(specsDir)) out.set(toPosixPath(path25.relative(dir, f)), fs17.readFileSync(f, "utf8").replace(/\r\n/g, "\n"));
41801
+ for (const f of specFilesUnder(specsDir)) out.set(toPosixPath(path25.relative(dir, f)), fs18.readFileSync(f, "utf8").replace(/\r\n/g, "\n"));
40997
41802
  return out;
40998
41803
  }
40999
41804
  function advanceMember(alias, ref, commit2, dryRun) {
@@ -41032,16 +41837,16 @@ function crossingReferences(family, from, to) {
41032
41837
  return out;
41033
41838
  }
41034
41839
  function moveTreeInto(src, dst, skip) {
41035
- if (!fs17.existsSync(src)) return [];
41840
+ if (!fs18.existsSync(src)) return [];
41036
41841
  const files = listFilesRecursive(src, "").filter((f) => !skip.has(path25.resolve(f)));
41037
41842
  const targets = files.map((f) => path25.join(dst, path25.relative(src, f)));
41038
- const taken = targets.filter((t) => fs17.existsSync(t));
41843
+ const taken = targets.filter((t) => fs18.existsSync(t));
41039
41844
  if (taken.length > 0) {
41040
41845
  throw new WaironError(`cannot internalize: the member's specs would overwrite ${taken.map((t) => path25.relative(dst, t)).join(", ")} in this project.`);
41041
41846
  }
41042
41847
  files.forEach((file, i) => {
41043
41848
  ensureDir(path25.dirname(targets[i]));
41044
- fs17.renameSync(file, targets[i]);
41849
+ fs18.renameSync(file, targets[i]);
41045
41850
  });
41046
41851
  return targets;
41047
41852
  }
@@ -41139,7 +41944,7 @@ function rebaseImplementationPaths(specsDir, fromRoot, toRoot, only) {
41139
41944
  }
41140
41945
  }
41141
41946
  function patchSubsystemIndex(indexPath, mutate) {
41142
- if (!fs17.existsSync(indexPath)) return;
41947
+ if (!fs18.existsSync(indexPath)) return;
41143
41948
  const raw = readYamlFile(indexPath);
41144
41949
  mutate(raw);
41145
41950
  raw.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -41226,7 +42031,7 @@ function moveRenamedSpecs(componentId, newId, component, movingInterface, moving
41226
42031
  if (movedImplementation) {
41227
42032
  const oldFile = getImplementationPath(movedImplementation.id);
41228
42033
  if (path25.resolve(getImplementationPath(implementationId, movedImplementation.contract)) === path25.resolve(oldFile)) {
41229
- fs17.unlinkSync(oldFile);
42034
+ fs18.unlinkSync(oldFile);
41230
42035
  }
41231
42036
  saveImplementationSpec({ ...movedImplementation, id: implementationId, previousIds: traced(movedImplementation.previousIds, movedImplementation.id) });
41232
42037
  }
@@ -41283,17 +42088,17 @@ function keepPublicNames(specsDir, derives, dryRun = false) {
41283
42088
  return { names: [...new Set(names)], specs };
41284
42089
  }
41285
42090
  function removeSpecFile2(file, id) {
41286
- if (!fs17.existsSync(file)) return;
42091
+ if (!fs18.existsSync(file)) return;
41287
42092
  try {
41288
42093
  if (readYamlFile(file)?.id !== id) return;
41289
42094
  } catch {
41290
42095
  return;
41291
42096
  }
41292
- fs17.unlinkSync(file);
42097
+ fs18.unlinkSync(file);
41293
42098
  const specsRoot = path25.resolve(aiPathsAt(getProjectRoot()).specsDir());
41294
42099
  for (let dir = path25.dirname(path25.resolve(file)); dir !== specsRoot && isWithinDir2(specsRoot, dir); dir = path25.dirname(dir)) {
41295
- if (fs17.readdirSync(dir).length > 0) break;
41296
- fs17.rmdirSync(dir);
42100
+ if (fs18.readdirSync(dir).length > 0) break;
42101
+ fs18.rmdirSync(dir);
41297
42102
  }
41298
42103
  }
41299
42104
  var METHOD_CASINGS = {
@@ -41340,8 +42145,9 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
41340
42145
  );
41341
42146
  }
41342
42147
  const casing = methodCasingOf(component);
41343
- if (!methodNameFits(newName, casing)) {
41344
- throw new WaironError(`invalid-name: "${newName}" is not an identifier in this tree's method casing (${casing}).`);
42148
+ const dictated = loadInterfaceSpecs().some((i) => i.component === componentId && i.implements !== void 0);
42149
+ if (dictated ? !IDENTIFIER.test(newName) : !methodNameFits(newName, casing)) {
42150
+ throw new WaironError(dictated ? `invalid-name: "${newName}" is not an identifier (a letter or underscore, then letters, digits or underscores).` : `invalid-name: "${newName}" is not an identifier in this tree's method casing (${casing}).`);
41345
42151
  }
41346
42152
  const contracts = loadInterfaceSpecs().filter((i) => i.component === componentId);
41347
42153
  const moving = contracts.filter((i) => i.methods.some((m) => m.name === methodName));
@@ -41707,6 +42513,64 @@ function renameField(typeId, field, newName) {
41707
42513
  invalidateSpecCache();
41708
42514
  return { type: qualifiedTypeId(type), from: field, to: newName, rewritten };
41709
42515
  }
42516
+ function renameParam(componentId, methodName, param, newName) {
42517
+ const component = loadComponentSpec(componentId);
42518
+ if (!component) throw new WaironError(`component-missing: no component has the id "${componentId}".`);
42519
+ if (componentId.includes("::")) {
42520
+ throw new WaironError(
42521
+ `chained-component: "${componentId}" lives in another project; rename its parameter from that project's own root. ${memberRootWay(componentId, `sdd_rename_param ${componentId.slice(componentId.lastIndexOf("::") + 2)} ${methodName} ${param} <new name>`)}`
42522
+ );
42523
+ }
42524
+ const contracts = loadInterfaceSpecs().filter((i) => i.component === componentId);
42525
+ const moving = contracts.filter((i) => i.methods.some((m) => m.name === methodName));
42526
+ if (moving.length === 0) throw new WaironError(`method-missing: no contract of "${componentId}" declares the method "${methodName}".`);
42527
+ for (const contract of moving) {
42528
+ const method = contract.methods.find((m) => m.name === methodName);
42529
+ const params = method.params ?? [];
42530
+ if (method.signatureFrom !== void 0) {
42531
+ throw new WaironError(`param-missing: "${contract.id}.${methodName}" takes its parameters from ${method.signatureFrom}; rename the parameter there.`);
42532
+ }
42533
+ if (!params.some((p) => p.name === param)) {
42534
+ throw new WaironError(`param-missing: "${contract.id}.${methodName}" declares no parameter "${param}"${params.length ? ` (it declares ${params.map((p) => p.name).join(", ")})` : ""}.`);
42535
+ }
42536
+ if (params.some((p) => p.name === newName && p.name !== param)) {
42537
+ throw new WaironError(`name-taken: "${contract.id}.${methodName}" already has a parameter "${newName}".`);
42538
+ }
42539
+ const retired = params.find((p) => p.name !== param && (p.previousNames ?? []).includes(newName));
42540
+ if (retired) {
42541
+ throw new WaironError(`name-retired: parameter "${retired.name}" of "${contract.id}.${methodName}" lists "${newName}" in its previousNames \u2014 the name is retired on this method. Unsetting that previousNames releases it.`);
42542
+ }
42543
+ }
42544
+ if (!IDENTIFIER.test(newName)) {
42545
+ throw new WaironError(`invalid-name: "${newName}" is not an identifier (a letter or underscore, then letters, digits or underscores).`);
42546
+ }
42547
+ const variables = projectConfigRepository.load()?.rules?.naming?.variables;
42548
+ const dictated = contracts.some((i) => i.implements !== void 0);
42549
+ if (variables && !dictated && !methodNameFits(newName, variables)) {
42550
+ throw new WaironError(`invalid-name: "${newName}" does not match this tree's parameter naming (rules.naming.variables: ${variables}).`);
42551
+ }
42552
+ if (newName === param) return { component: componentId, method: methodName, from: param, to: newName, movedIn: [], rewritten: [] };
42553
+ assertSpecsInReach(moving.map((i) => ({ kind: "interface", id: i.id })), `renaming parameter "${param}" of "${componentId}.${methodName}"`, false);
42554
+ const rewritten = [];
42555
+ const placeholder = `{${param}}`;
42556
+ for (const contract of moving) {
42557
+ saveInterfaceSpec({
42558
+ ...contract,
42559
+ methods: contract.methods.map((m) => {
42560
+ if (m.name !== methodName) return m;
42561
+ const params = (m.params ?? []).map((p) => p.name === param ? { ...p, name: newName, previousNames: [...p.previousNames ?? [], param] } : p);
42562
+ let endpoint = m.endpoint;
42563
+ if (endpoint && endpoint.transport === "HTTP" && endpoint.path.includes(placeholder)) {
42564
+ endpoint = { ...endpoint, path: endpoint.path.split(placeholder).join(`{${newName}}`) };
42565
+ rewritten.push(`${contract.id}.${methodName}: {${param}} -> {${newName}}`);
42566
+ }
42567
+ return { ...m, params, ...endpoint ? { endpoint } : {} };
42568
+ })
42569
+ });
42570
+ }
42571
+ invalidateSpecCache();
42572
+ return { component: componentId, method: methodName, from: param, to: newName, movedIn: moving.map((i) => i.id), rewritten };
42573
+ }
41710
42574
  function typeFileOf(id, type) {
41711
42575
  return getTypePath(id, type.subsystem, type.group);
41712
42576
  }
@@ -41733,7 +42597,7 @@ function externalSourceOf(declaration2) {
41733
42597
  }
41734
42598
 
41735
42599
  // src/core/detection.ts
41736
- var fs18 = __toESM(require("fs"));
42600
+ var fs19 = __toESM(require("fs"));
41737
42601
  var path26 = __toESM(require("path"));
41738
42602
  var PACKAGE_MARKERS = [
41739
42603
  "package.json",
@@ -41785,7 +42649,7 @@ function deduplicateIds(candidates, existingIds = /* @__PURE__ */ new Set()) {
41785
42649
  });
41786
42650
  }
41787
42651
  function parseGitmodules(filePath) {
41788
- const content = fs18.readFileSync(filePath, "utf-8");
42652
+ const content = fs19.readFileSync(filePath, "utf-8");
41789
42653
  const entries = [];
41790
42654
  let current2 = {};
41791
42655
  for (const line of content.split("\n")) {
@@ -41808,7 +42672,7 @@ function parseGitmodules(filePath) {
41808
42672
  }
41809
42673
  function detectGitSubmodules(projectRoot) {
41810
42674
  const gitmodulesPath = path26.join(projectRoot, ".gitmodules");
41811
- if (!fs18.existsSync(gitmodulesPath)) return [];
42675
+ if (!fs19.existsSync(gitmodulesPath)) return [];
41812
42676
  return parseGitmodules(gitmodulesPath).map((entry) => ({
41813
42677
  suggestedId: pathToId(entry.path),
41814
42678
  suggestedName: pathToName(entry.path),
@@ -41826,7 +42690,7 @@ function walkForGit(projectRoot, currentDir, depth, results) {
41826
42690
  if (depth > MAX_SCAN_DEPTH) return;
41827
42691
  let entries;
41828
42692
  try {
41829
- entries = fs18.readdirSync(currentDir, { withFileTypes: true });
42693
+ entries = fs19.readdirSync(currentDir, { withFileTypes: true });
41830
42694
  } catch {
41831
42695
  return;
41832
42696
  }
@@ -41837,7 +42701,7 @@ function walkForGit(projectRoot, currentDir, depth, results) {
41837
42701
  const relPath = normalizePath(path26.relative(projectRoot, fullPath));
41838
42702
  if (relPath === "" || relPath === ".") continue;
41839
42703
  const gitPath = path26.join(fullPath, ".git");
41840
- if (fs18.existsSync(gitPath)) {
42704
+ if (fs19.existsSync(gitPath)) {
41841
42705
  results.push({
41842
42706
  suggestedId: pathToId(relPath),
41843
42707
  suggestedName: pathToName(relPath),
@@ -41859,7 +42723,7 @@ function walkForPackages(projectRoot, currentDir, depth, results) {
41859
42723
  if (depth > MAX_SCAN_DEPTH) return;
41860
42724
  let entries;
41861
42725
  try {
41862
- entries = fs18.readdirSync(currentDir, { withFileTypes: true });
42726
+ entries = fs19.readdirSync(currentDir, { withFileTypes: true });
41863
42727
  } catch {
41864
42728
  return;
41865
42729
  }
@@ -41869,7 +42733,7 @@ function walkForPackages(projectRoot, currentDir, depth, results) {
41869
42733
  const fullPath = path26.join(currentDir, entry.name);
41870
42734
  const relPath = normalizePath(path26.relative(projectRoot, fullPath));
41871
42735
  if (relPath === "" || relPath === ".") continue;
41872
- const hasMarker = PACKAGE_MARKERS.some((m) => fs18.existsSync(path26.join(fullPath, m)));
42736
+ const hasMarker = PACKAGE_MARKERS.some((m) => fs19.existsSync(path26.join(fullPath, m)));
41873
42737
  if (hasMarker) {
41874
42738
  results.push({
41875
42739
  suggestedId: pathToId(relPath),
@@ -41883,8 +42747,8 @@ function walkForPackages(projectRoot, currentDir, depth, results) {
41883
42747
  }
41884
42748
  }
41885
42749
  function pathToId(relPath) {
41886
- const basename13 = path26.basename(relPath);
41887
- return basename13.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
42750
+ const basename14 = path26.basename(relPath);
42751
+ return basename14.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
41888
42752
  }
41889
42753
  function pathToName(relPath) {
41890
42754
  const id = pathToId(relPath);
@@ -41895,7 +42759,7 @@ function normalizePath(p) {
41895
42759
  }
41896
42760
 
41897
42761
  // src/core/variants.ts
41898
- var fs19 = __toESM(require("fs"));
42762
+ var fs20 = __toESM(require("fs"));
41899
42763
  var os4 = __toESM(require("os"));
41900
42764
  var path27 = __toESM(require("path"));
41901
42765
  var import_zod11 = require("zod");
@@ -41921,7 +42785,7 @@ function builtinVariantsDir() {
41921
42785
  path27.resolve(__dirname, "templates", "variants")
41922
42786
  // dist (library entry)
41923
42787
  ];
41924
- return candidates.find((dir) => fs19.existsSync(dir)) ?? candidates[0];
42788
+ return candidates.find((dir) => fs20.existsSync(dir)) ?? candidates[0];
41925
42789
  }
41926
42790
  function projectVariantsDir() {
41927
42791
  return path27.join(getProjectRoot(), ".wai", "variants");
@@ -41929,7 +42793,7 @@ function projectVariantsDir() {
41929
42793
  function variantFiles(dir) {
41930
42794
  let entries;
41931
42795
  try {
41932
- entries = fs19.readdirSync(dir, { withFileTypes: true });
42796
+ entries = fs20.readdirSync(dir, { withFileTypes: true });
41933
42797
  } catch {
41934
42798
  return [];
41935
42799
  }
@@ -42045,7 +42909,7 @@ function rebasedText(content, fromBase, toBase, rebasedContent) {
42045
42909
 
42046
42910
  // src/core/agent_resolver.ts
42047
42911
  var path29 = __toESM(require("path"));
42048
- var fs20 = __toESM(require("fs"));
42912
+ var fs21 = __toESM(require("fs"));
42049
42913
 
42050
42914
  // src/core/templates.ts
42051
42915
  var path28 = __toESM(require("path"));
@@ -42215,7 +43079,7 @@ function resolveBudget(profile, config, agentId) {
42215
43079
  // src/core/agent_resolver.ts
42216
43080
  var projectFilesCache = /* @__PURE__ */ new Map();
42217
43081
  function listFilesRecursiveSafe(dirPath, ext) {
42218
- if (!fs20.existsSync(dirPath)) return [];
43082
+ if (!fs21.existsSync(dirPath)) return [];
42219
43083
  const nameLower = path29.basename(dirPath).toLowerCase();
42220
43084
  const IGNORED_DIRS = /* @__PURE__ */ new Set([
42221
43085
  "node_modules",
@@ -42231,7 +43095,7 @@ function listFilesRecursiveSafe(dirPath, ext) {
42231
43095
  ".vscode"
42232
43096
  ]);
42233
43097
  if (IGNORED_DIRS.has(nameLower)) return [];
42234
- const entries = fs20.readdirSync(dirPath, { withFileTypes: true });
43098
+ const entries = fs21.readdirSync(dirPath, { withFileTypes: true });
42235
43099
  const files = [];
42236
43100
  for (const entry of entries) {
42237
43101
  const fullPath = path29.join(dirPath, entry.name);
@@ -42628,7 +43492,7 @@ function composeAgentBrief(agentId) {
42628
43492
  const shared = ownFence && isSubsystemOwner(record) ? sharedProjectFiles(ownFence) : [];
42629
43493
  const codeFence = ownFence ? [...ownFence, ...shared] : ownFence;
42630
43494
  if (codeFence) {
42631
- const shown2 = (p) => /[*?]/.test(p) || fs20.existsSync(path29.resolve(getProjectRoot(), p)) ? `- \`${p}\`` : `- \`${p}\` (planned \u2014 create it)`;
43495
+ const shown2 = (p) => /[*?]/.test(p) || fs21.existsSync(path29.resolve(getProjectRoot(), p)) ? `- \`${p}\`` : `- \`${p}\` (planned \u2014 create it)`;
42632
43496
  const own2 = ownFence ?? [];
42633
43497
  const sharedSection = shared.length > 0 ? `
42634
43498
  Shared with the other subsystem owners (the project's setup and shared code; coordinate edits):
@@ -42756,18 +43620,18 @@ function memberHop(agentId) {
42756
43620
  const alias = agentId.slice(0, at);
42757
43621
  const member = graph().nodes.find((n) => n.parent === "" && n.mountAlias === alias);
42758
43622
  if (!member) return null;
42759
- const relative20 = path29.relative(getProjectRoot(), member.directory).split(path29.sep).join("/");
42760
- return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative: relative20 };
43623
+ const relative22 = path29.relative(getProjectRoot(), member.directory).split(path29.sep).join("/");
43624
+ return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative: relative22 };
42761
43625
  }
42762
- function throughMount(brief, alias, relative20) {
42763
- const under = (p) => path29.posix.join(relative20, p);
43626
+ function throughMount(brief, alias, relative22) {
43627
+ const under = (p) => path29.posix.join(relative22, p);
42764
43628
  return {
42765
43629
  ...brief,
42766
43630
  agentId: `${alias}::${brief.agentId}`,
42767
43631
  ownedPaths: brief.ownedPaths.map(under),
42768
43632
  ...brief.codeFence ? { codeFence: brief.codeFence.map(under) } : {},
42769
43633
  ...brief.readPaths ? { readPaths: brief.readPaths.map(under) } : {},
42770
- root: brief.root ? under(brief.root) : relative20
43634
+ root: brief.root ? under(brief.root) : relative22
42771
43635
  };
42772
43636
  }
42773
43637
  function loadRegistry() {
@@ -42804,7 +43668,7 @@ function sharedProjectFiles(fence) {
42804
43668
  if (!claimed.has(file) && !out.includes(file)) out.push(file);
42805
43669
  };
42806
43670
  for (const file of [...SHARED_SETUP_FILES, ...SHARED_ROOT_FILES]) {
42807
- if (fs20.existsSync(path29.join(root, file))) add2(file);
43671
+ if (fs21.existsSync(path29.join(root, file))) add2(file);
42808
43672
  }
42809
43673
  for (const type of loadTypeSpecs()) {
42810
43674
  if (type.subsystem) continue;
@@ -43097,7 +43961,7 @@ function resolveTargetConfig(agentTarget, projectConfig) {
43097
43961
  }
43098
43962
 
43099
43963
  // src/core/treetransfer.ts
43100
- var fs21 = __toESM(require("fs"));
43964
+ var fs22 = __toESM(require("fs"));
43101
43965
  var path34 = __toESM(require("path"));
43102
43966
  var sdkPortal = __toESM(require_dist());
43103
43967
  var STAGING_PREFIX = ".wai-staging-";
@@ -43134,8 +43998,8 @@ function hasAuthoredSpecs(root) {
43134
43998
  }
43135
43999
  function createStagingDir(destDir) {
43136
44000
  const root = path34.resolve(destDir);
43137
- fs21.mkdirSync(root, { recursive: true });
43138
- const stagingDir = fs21.mkdtempSync(path34.join(root, STAGING_PREFIX));
44001
+ fs22.mkdirSync(root, { recursive: true });
44002
+ const stagingDir = fs22.mkdtempSync(path34.join(root, STAGING_PREFIX));
43139
44003
  return stagingDir;
43140
44004
  }
43141
44005
  function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
@@ -43143,22 +44007,22 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
43143
44007
  const backupDir = path34.join(root, BACKUP_DIR, timestampSlug());
43144
44008
  let backedUp = false;
43145
44009
  for (const rootRel of roots) {
43146
- const relative20 = rootRel === "." ? "" : rootRel;
43147
- const stagedSpecDir = path34.join(stagingDir, relative20, ".wai");
43148
- if (!fs21.existsSync(stagedSpecDir)) continue;
43149
- const liveSpecDir = liveSpecDirFor(path34.join(root, relative20));
43150
- if (backupExisting && liveSpecDir && fs21.existsSync(liveSpecDir)) {
43151
- const backupTarget = path34.join(backupDir, relative20, path34.basename(liveSpecDir));
43152
- fs21.mkdirSync(path34.dirname(backupTarget), { recursive: true });
43153
- fs21.renameSync(liveSpecDir, backupTarget);
44010
+ const relative22 = rootRel === "." ? "" : rootRel;
44011
+ const stagedSpecDir = path34.join(stagingDir, relative22, ".wai");
44012
+ if (!fs22.existsSync(stagedSpecDir)) continue;
44013
+ const liveSpecDir = liveSpecDirFor(path34.join(root, relative22));
44014
+ if (backupExisting && liveSpecDir && fs22.existsSync(liveSpecDir)) {
44015
+ const backupTarget = path34.join(backupDir, relative22, path34.basename(liveSpecDir));
44016
+ fs22.mkdirSync(path34.dirname(backupTarget), { recursive: true });
44017
+ fs22.renameSync(liveSpecDir, backupTarget);
43154
44018
  backedUp = true;
43155
- } else if (liveSpecDir && fs21.existsSync(liveSpecDir)) {
43156
- fs21.rmSync(liveSpecDir, { recursive: true, force: true });
44019
+ } else if (liveSpecDir && fs22.existsSync(liveSpecDir)) {
44020
+ fs22.rmSync(liveSpecDir, { recursive: true, force: true });
43157
44021
  }
43158
- const target = path34.join(root, relative20, ".wai");
43159
- fs21.mkdirSync(path34.dirname(target), { recursive: true });
43160
- if (fs21.existsSync(target)) fs21.rmSync(target, { recursive: true, force: true });
43161
- fs21.renameSync(stagedSpecDir, target);
44022
+ const target = path34.join(root, relative22, ".wai");
44023
+ fs22.mkdirSync(path34.dirname(target), { recursive: true });
44024
+ if (fs22.existsSync(target)) fs22.rmSync(target, { recursive: true, force: true });
44025
+ fs22.renameSync(stagedSpecDir, target);
43162
44026
  }
43163
44027
  if (backedUp) {
43164
44028
  return backupDir;
@@ -43167,7 +44031,7 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
43167
44031
  }
43168
44032
  function discardStagingDir(stagingDir) {
43169
44033
  try {
43170
- fs21.rmSync(stagingDir, { recursive: true, force: true });
44034
+ fs22.rmSync(stagingDir, { recursive: true, force: true });
43171
44035
  } catch {
43172
44036
  }
43173
44037
  }
@@ -43244,9 +44108,9 @@ function importSpecTree(archive, options) {
43244
44108
  }
43245
44109
  function liveSpecDirFor(rootDir) {
43246
44110
  const modern = path34.join(rootDir, ".wai");
43247
- if (fs21.existsSync(modern)) return modern;
44111
+ if (fs22.existsSync(modern)) return modern;
43248
44112
  const legacy = path34.join(rootDir, ".wairon");
43249
- if (fs21.existsSync(legacy)) return legacy;
44113
+ if (fs22.existsSync(legacy)) return legacy;
43250
44114
  return null;
43251
44115
  }
43252
44116
  function timestampSlug() {
@@ -43484,8 +44348,8 @@ var RETIRED_CODES = /* @__PURE__ */ new Set([
43484
44348
  var RENAMED_CODES = { MISSING_PORTAL_TYPE: "MISSING_PORTAL_TRANSPORT" };
43485
44349
  var WRITE_ORDER = ["component", "interface", "implementation", "subsystem", "type", "system"];
43486
44350
  var own = (id) => !id.includes("::");
43487
- function liesUnder(path49, prefix) {
43488
- return path49 === prefix || path49.startsWith(prefix + "/");
44351
+ function liesUnder(path50, prefix) {
44352
+ return path50 === prefix || path50.startsWith(prefix + "/");
43489
44353
  }
43490
44354
  function prefixPhrase(prefixes) {
43491
44355
  return prefixes.length > 0 ? prefixes.map((p) => `"${p}"`).join(", ") : "no prefix";
@@ -43776,12 +44640,15 @@ function designDigest(kind, content) {
43776
44640
  if (memo2 !== void 0) return memo2;
43777
44641
  const parsed = parsedSpec(content);
43778
44642
  if (!parsed) return digest(content);
43779
- const out = viewDigest(designViewOf(kind, parsed));
44643
+ const out = viewDigest(designViewOf(kind, schemaRead(kind, parsed)));
43780
44644
  if (designDigestMemo.size > 2e4) designDigestMemo.clear();
43781
44645
  designDigestMemo.set(key2, out);
43782
44646
  return out;
43783
44647
  }
43784
44648
  var STATUS_CANDIDATES = [void 0, "draft", "design", "complete"];
44649
+ function schemaRead(kind, parsed) {
44650
+ return kind ? asSchemaReads(kind, parsed) : parsed;
44651
+ }
43785
44652
  function designViewOf(kind, parsed) {
43786
44653
  switch (kind) {
43787
44654
  case "implementation":
@@ -43813,10 +44680,12 @@ function viewDigest(view3) {
43813
44680
  function statusApprovedIn(kind, content, recorded) {
43814
44681
  const parsed = parsedSpec(content);
43815
44682
  if (!parsed) return void 0;
43816
- const view3 = designViewOf(kind, parsed);
43817
- for (const status of STATUS_CANDIDATES) {
43818
- const candidate = status === void 0 ? view3 : { ...view3, status };
43819
- if (viewDigest(candidate) === recorded) return status ?? null;
44683
+ const views = [designViewOf(kind, schemaRead(kind, parsed)), designViewOf(kind, parsed)];
44684
+ for (const view3 of views) {
44685
+ for (const status of STATUS_CANDIDATES) {
44686
+ const candidate = status === void 0 ? view3 : { ...view3, status };
44687
+ if (viewDigest(candidate) === recorded) return status ?? null;
44688
+ }
43820
44689
  }
43821
44690
  return void 0;
43822
44691
  }
@@ -43908,19 +44777,31 @@ function diffAgainstApproval(root = getProjectRoot()) {
43908
44777
  for (const rel2 of Object.keys(approved)) {
43909
44778
  if (current2[rel2] === void 0) removed.push(rel2);
43910
44779
  }
44780
+ const moved = [];
44781
+ const unpaired = [...removed].sort();
44782
+ for (const rel2 of [...added].sort()) {
44783
+ const at = unpaired.findIndex((old2) => approved[old2] === current2[rel2]);
44784
+ if (at < 0) continue;
44785
+ const [old] = unpaired.splice(at, 1);
44786
+ moved.push(`${old} -> ${rel2}`);
44787
+ added.splice(added.indexOf(rel2), 1);
44788
+ removed.splice(removed.indexOf(old), 1);
44789
+ unchangedPaths.push(rel2);
44790
+ }
43911
44791
  return {
43912
44792
  added: added.sort(),
43913
44793
  changed: changed.sort(),
43914
44794
  removed: removed.sort(),
43915
- unchangedPaths: unchangedPaths.sort()
44795
+ unchangedPaths: unchangedPaths.sort(),
44796
+ moved
43916
44797
  };
43917
44798
  }
43918
44799
  function approvalRecord(root = getProjectRoot()) {
43919
44800
  return readLockRecordAt(root);
43920
44801
  }
43921
44802
  function settledSpecPaths(root = getProjectRoot()) {
43922
- const diff2 = diffAgainstApproval(root);
43923
- return diff2 ? new Set(diff2.unchangedPaths) : null;
44803
+ const diff3 = diffAgainstApproval(root);
44804
+ return diff3 ? new Set(diff3.unchangedPaths) : null;
43924
44805
  }
43925
44806
  var REEXPRESSED_BY = "wairon doctor --fix";
43926
44807
  function reexpress(gate) {
@@ -43941,8 +44822,8 @@ function reexpress(gate) {
43941
44822
  };
43942
44823
  }
43943
44824
  if (fromReading !== "content" || !record.specs) return null;
43944
- const diff2 = diffAgainstApproval();
43945
- if (!diff2 || diffSize(diff2) > 0) return null;
44825
+ const diff3 = diffAgainstApproval();
44826
+ if (!diff3 || diffSize(diff3) > 0) return null;
43946
44827
  return { ...record, specs: captureApprovedSpecs(), specsReading: "design", format: 3, reexpressed };
43947
44828
  }
43948
44829
  function approvedStatuses(record, root = getProjectRoot()) {
@@ -43964,11 +44845,11 @@ function approvalVerdict(approvals) {
43964
44845
  const approval = approvalRecord();
43965
44846
  if (!approval) return quiet;
43966
44847
  const by = describeApprover(approval.lockedBy);
43967
- const diff2 = diffAgainstApproval();
43968
- const notes = memberNotes(approvals ?? [], approval, diff2);
44848
+ const diff3 = diffAgainstApproval();
44849
+ const notes = memberNotes(approvals ?? [], approval, diff3);
43969
44850
  const pinDrift = notes.drift;
43970
44851
  const childNote = notes.text;
43971
- if (!diff2) {
44852
+ if (!diff3) {
43972
44853
  return {
43973
44854
  text: `
43974
44855
  Approved: ${approval.lockedAt} by ${by} \u2014 this lock predates per-spec approval, so drift is only visible at whole-tree level. Re-lock to record it.${childNote}
@@ -43979,7 +44860,7 @@ Approved: ${approval.lockedAt} by ${by} \u2014 this lock predates per-spec appro
43979
44860
  const own2 = (approvals ?? []).find((a) => a.key === "");
43980
44861
  const carry = isContentReadingRecord(approval) && own2?.state === "approved" ? `
43981
44862
  ${REEXPRESS_HINT}` : "";
43982
- if (diffSize(diff2) === 0) {
44863
+ if (diffSize(diff3) === 0) {
43983
44864
  return {
43984
44865
  text: `
43985
44866
  Approved: ${approval.lockedAt} by ${by} \u2014 no spec has changed since.${childNote}${carry}
@@ -43988,21 +44869,21 @@ Approved: ${approval.lockedAt} by ${by} \u2014 no spec has changed since.${child
43988
44869
  };
43989
44870
  }
43990
44871
  const parts = [];
43991
- if (diff2.changed.length) parts.push(`${diff2.changed.length} changed`);
43992
- if (diff2.added.length) parts.push(`${diff2.added.length} added`);
43993
- if (diff2.removed.length) parts.push(`${diff2.removed.length} removed`);
43994
- const named2 = [...diff2.changed, ...diff2.added, ...diff2.removed].slice(0, 5);
43995
- const rest = diffSize(diff2) - named2.length;
43996
- const n = diffSize(diff2);
44872
+ if (diff3.changed.length) parts.push(`${diff3.changed.length} changed`);
44873
+ if (diff3.added.length) parts.push(`${diff3.added.length} added`);
44874
+ if (diff3.removed.length) parts.push(`${diff3.removed.length} removed`);
44875
+ const named2 = [...diff3.changed, ...diff3.added, ...diff3.removed].slice(0, 5);
44876
+ const rest = diffSize(diff3) - named2.length;
44877
+ const n = diffSize(diff3);
43997
44878
  const noun = `${n} spec${n === 1 ? "" : "s"}`;
43998
- const headline = parts.length === 1 ? `${noun} ${parts[0].slice(String(diffSize(diff2)).length + 1)} since approval` : `${noun} changed since approval (${parts.join(", ")})`;
44879
+ const headline = parts.length === 1 ? `${noun} ${parts[0].slice(String(diffSize(diff3)).length + 1)} since approval` : `${noun} changed since approval (${parts.join(", ")})`;
43999
44880
  return {
44000
44881
  text: `
44001
44882
  ${headline} \u2014 approved ${approval.lockedAt} by ${by}:
44002
44883
  ` + named2.map((p) => ` ${p}`).join("\n") + (rest > 0 ? `
44003
44884
  \u2026 and ${rest} more` : "") + childNote + carry + "\n",
44004
44885
  drifted: true,
44005
- moved: [...diff2.changed.map((p) => `~ ${p}`), ...diff2.added.map((p) => `+ ${p}`), ...diff2.removed.map((p) => `- ${p}`)]
44886
+ moved: [...diff3.changed.map((p) => `~ ${p}`), ...diff3.added.map((p) => `+ ${p}`), ...diff3.removed.map((p) => `- ${p}`)]
44006
44887
  };
44007
44888
  } catch {
44008
44889
  return quiet;
@@ -44015,7 +44896,7 @@ var REEXPRESS_HINT = "This approval predates lock format 3 and is read in its or
44015
44896
  var FORMAT2_STALE_NOTE = "it was taken before code linkage (sourcePath, symbol, simPath and the like) left the approval, and something it covered moved since \u2014 the design, or only code linkage, which this record can no longer tell apart. One `wairon lock` clears it for good: from then on linkage never drifts the approval";
44016
44897
  var GATE_UPGRADE_NOTE = "the gate identity gained inputs in this release (members' composition subjects, `composition`; code conformance moved beside the claim)";
44017
44898
  var GATE_MOVED_NOTE = "no own spec file changed since it was taken \u2014 not even its code linkage \u2014 so what moved is the gate it was judged under: the design rules this wairon release judges by, or the project's rule tuning, `composition`, network declaration, consumed contracts or a member's approval. One `wairon lock` re-approves the unchanged design under the current gate";
44018
- function memberNotes(approvals, record, diff2) {
44899
+ function memberNotes(approvals, record, diff3) {
44019
44900
  const own2 = approvals.find((a) => a.key === "");
44020
44901
  const direct = approvals.filter((a) => a.parent === "" && a.as !== "part");
44021
44902
  const moved = direct.filter((a) => a.pinned === "moved");
@@ -44023,7 +44904,7 @@ function memberNotes(approvals, record, diff2) {
44023
44904
  const lines = [];
44024
44905
  if (own2?.upgraded) {
44025
44906
  const by = record?.validatorVersion ? ` (taken by wairon ${record.validatorVersion})` : "";
44026
- lines.push(diff2 && diffSize(diff2) === 0 ? `This approval no longer matches${by}: ${GATE_MOVED_NOTE}.` : record && record.format === 2 ? `This approval no longer matches: ${FORMAT2_STALE_NOTE}.` : `This approval was taken under an earlier gate identity \u2014 ${GATE_UPGRADE_NOTE}. Re-lock once.`);
44907
+ lines.push(diff3 && diffSize(diff3) === 0 ? `This approval no longer matches${by}: ${GATE_MOVED_NOTE}.` : record && record.format === 2 ? `This approval no longer matches: ${FORMAT2_STALE_NOTE}.` : `This approval was taken under an earlier gate identity \u2014 ${GATE_UPGRADE_NOTE}. Re-lock once.`);
44027
44908
  }
44028
44909
  if (moved.length) {
44029
44910
  lines.push(`${moved.length} member pin(s) moved since approval: ${moved.map((a) => a.alias ?? a.key).join(", ")} \u2014 re-lock to approve them.`);
@@ -44075,7 +44956,7 @@ function localApprover() {
44075
44956
  }
44076
44957
 
44077
44958
  // src/utils/ai-guide.ts
44078
- var fs22 = __toESM(require("fs"));
44959
+ var fs23 = __toESM(require("fs"));
44079
44960
  var os6 = __toESM(require("os"));
44080
44961
  var path36 = __toESM(require("path"));
44081
44962
 
@@ -44165,10 +45046,10 @@ ${versionStamp()}
44165
45046
  ${body}
44166
45047
  ${GUIDE_MARKER_END}
44167
45048
  `;
44168
- const existing = fs22.existsSync(filePath) ? fs22.readFileSync(filePath, "utf-8") : "";
45049
+ const existing = fs23.existsSync(filePath) ? fs23.readFileSync(filePath, "utf-8") : "";
44169
45050
  const stripped = stripGuideSection(existing);
44170
45051
  const newContent = stripped.trimEnd() + section;
44171
- fs22.mkdirSync(path36.dirname(filePath), { recursive: true });
45052
+ fs23.mkdirSync(path36.dirname(filePath), { recursive: true });
44172
45053
  writeFile(filePath, newContent);
44173
45054
  }
44174
45055
  function stripGuideSection(content) {
@@ -44213,7 +45094,7 @@ Refer to the rules in [.cursor/rules/](.cursor/rules/) for full instructions.
44213
45094
  writeFile(filePath, content);
44214
45095
  } else if (targetType === "copilot") {
44215
45096
  const filePath = path36.join(projectRoot, ".github", "copilot-instructions.md");
44216
- fs22.mkdirSync(path36.dirname(filePath), { recursive: true });
45097
+ fs23.mkdirSync(path36.dirname(filePath), { recursive: true });
44217
45098
  const content = `# Wairon SDD Project
44218
45099
 
44219
45100
  This project uses the Wairon Spec-Driven Development (SDD) framework.
@@ -44244,6 +45125,22 @@ function reinjectLocalGuides(projectRoot, targetTypes) {
44244
45125
  }
44245
45126
  return written;
44246
45127
  }
45128
+ function registerProjectServer(projectRoot) {
45129
+ const file = path36.join(projectRoot, ".mcp.json");
45130
+ let settings = {};
45131
+ if (fs23.existsSync(file)) {
45132
+ try {
45133
+ settings = JSON.parse(fs23.readFileSync(file, "utf8"));
45134
+ } catch {
45135
+ return null;
45136
+ }
45137
+ }
45138
+ const servers = settings.mcpServers ?? {};
45139
+ if (servers.wairon !== void 0) return null;
45140
+ settings.mcpServers = { ...servers, wairon: { command: "wairon", args: ["mcp", "serve"] } };
45141
+ writeFile(file, JSON.stringify(settings, null, 2) + "\n");
45142
+ return file;
45143
+ }
44247
45144
 
44248
45145
  // src/core/context.ts
44249
45146
  function contextDir(...segments) {
@@ -44612,8 +45509,14 @@ function getStatusReport(options = {}, decor) {
44612
45509
  `;
44613
45510
  }
44614
45511
  const own2 = options.approvals?.find((a) => a.key === "");
44615
- if (own2) output += `${mark.layer("system", "Approval:")} this project is ${OWN_APPROVAL_WORDS[own2.state]}${own2.upgraded ? " (locked under an earlier gate identity \u2014 re-lock once)" : ""}
45512
+ const nothingDesigned = own2?.state === "never" && loadSubsystemSpecs().length === 0 && !(options.approvals ?? []).some((a) => a.key !== "" && a.parent === "" && a.as !== "part");
45513
+ if (own2 && nothingDesigned) {
45514
+ output += `${mark.layer("system", "Approval:")} nothing to approve yet \u2014 the tree holds the L0 and nothing below it (add a subsystem, then \`wairon lock\`)
44616
45515
  `;
45516
+ } else if (own2) {
45517
+ output += `${mark.layer("system", "Approval:")} this project is ${OWN_APPROVAL_WORDS[own2.state]}${own2.upgraded ? " (locked under an earlier gate identity \u2014 re-lock once)" : ""}
45518
+ `;
45519
+ }
44617
45520
  return { text: output, failed: false };
44618
45521
  }
44619
45522
 
@@ -44706,7 +45609,7 @@ var logger = {
44706
45609
 
44707
45610
  // src/core/skills.ts
44708
45611
  var path38 = __toESM(require("path"));
44709
- var fs23 = __toESM(require("fs"));
45612
+ var fs24 = __toESM(require("fs"));
44710
45613
 
44711
45614
  // src/core/instructions.ts
44712
45615
  function governingProfile() {
@@ -44846,6 +45749,22 @@ function packSkillId(skill) {
44846
45749
  function packNamespace(pack) {
44847
45750
  return pack.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
44848
45751
  }
45752
+ function extensionsFor(builtin, packSkills) {
45753
+ return packSkills.filter((s) => s.extends === builtin && fs24.existsSync(s.sourcePath));
45754
+ }
45755
+ function composeBuiltinSkill(name, packSkills) {
45756
+ const srcPath = skillTemplatePath(name);
45757
+ const base = fs24.existsSync(srcPath) ? fs24.readFileSync(srcPath, "utf-8") : "";
45758
+ const sections = extensionsFor(name, packSkills);
45759
+ if (sections.length === 0) return base;
45760
+ const parts = [base.trimEnd()];
45761
+ for (const section of sections) {
45762
+ const body = fs24.readFileSync(section.sourcePath, "utf-8").replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
45763
+ parts.push(`## Platform: ${section.pack}`, body.trim());
45764
+ }
45765
+ return `${parts.join("\n\n")}
45766
+ `;
45767
+ }
44849
45768
  function readFrontmatter(raw, fallbackName) {
44850
45769
  const match2 = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
44851
45770
  const block2 = match2 ? match2[1] : "";
@@ -44861,6 +45780,63 @@ function builtinSkillsDir() {
44861
45780
  function skillTemplatePath(name) {
44862
45781
  return path38.join(builtinSkillsDir(), `${name}.md`);
44863
45782
  }
45783
+ function skillDestPath(type, destDir, name) {
45784
+ if (type === "claude" || type === "codex" || type === "gemini" || type === "agy") {
45785
+ return path38.join(destDir, name, "SKILL.md");
45786
+ }
45787
+ return path38.join(destDir, `${name}.md`);
45788
+ }
45789
+ function skillsDirForTarget(type) {
45790
+ switch (type) {
45791
+ case "claude":
45792
+ return fromProjectRoot(".claude", "skills");
45793
+ case "gemini":
45794
+ // Gemini-based Antigravity
45795
+ case "agy":
45796
+ case "codex":
45797
+ return fromProjectRoot(".agents", "skills");
45798
+ case "cursor":
45799
+ return fromProjectRoot(".cursor", "skills");
45800
+ default:
45801
+ return null;
45802
+ }
45803
+ }
45804
+ function exportSddSkills(targetTypes) {
45805
+ const types = targetTypes ?? activeTargetTypes2();
45806
+ const destinations = [];
45807
+ const skipped = [];
45808
+ let fileCount = 0;
45809
+ const packSkills = loadProjectExtensions().skills;
45810
+ for (const type of types) {
45811
+ const destDir = skillsDirForTarget(type);
45812
+ if (!destDir) {
45813
+ skipped.push(type);
45814
+ continue;
45815
+ }
45816
+ ensureDir(destDir);
45817
+ destinations.push(destDir);
45818
+ for (const name of SKILL_NAMES) {
45819
+ if (!fs24.existsSync(skillTemplatePath(name))) continue;
45820
+ const content = composeBuiltinSkill(name, packSkills.filter((s) => s.targets.includes(type)));
45821
+ const destPath = skillDestPath(type, destDir, name);
45822
+ ensureDir(path38.dirname(destPath));
45823
+ writeFileIfChanged(destPath, content);
45824
+ fileCount++;
45825
+ }
45826
+ for (const skill of packSkills) {
45827
+ if (skill.extends !== void 0) continue;
45828
+ if (!skill.targets.includes(type)) continue;
45829
+ if (!fs24.existsSync(skill.sourcePath)) continue;
45830
+ const id = packSkillId(skill);
45831
+ const content = fs24.readFileSync(skill.sourcePath, "utf-8");
45832
+ const destPath = skillDestPath(type, destDir, id);
45833
+ ensureDir(path38.dirname(destPath));
45834
+ writeFileIfChanged(destPath, content);
45835
+ fileCount++;
45836
+ }
45837
+ }
45838
+ return { destinations, fileCount, skipped };
45839
+ }
44864
45840
  function activeTargetTypes2() {
44865
45841
  const config = loadProjectConfig();
44866
45842
  if (!config) return [];
@@ -44869,7 +45845,7 @@ function activeTargetTypes2() {
44869
45845
  var SKILL_RESOURCE_SCHEME = "wairon-skill";
44870
45846
  var RESOURCE_SKILL_IDS = [...SKILL_NAMES].sort();
44871
45847
  function readSkillFrontmatter(name) {
44872
- return readFrontmatter(fs23.readFileSync(skillTemplatePath(name), "utf-8"), name);
45848
+ return readFrontmatter(fs24.readFileSync(skillTemplatePath(name), "utf-8"), name);
44873
45849
  }
44874
45850
  function listSkillResources() {
44875
45851
  const builtin = RESOURCE_SKILL_IDS.map((id) => {
@@ -44883,9 +45859,9 @@ function listSkillResources() {
44883
45859
  defaultForHostedMcp: true
44884
45860
  };
44885
45861
  });
44886
- const pack = loadProjectExtensions().skills.filter((s) => s.extends === void 0 && fs23.existsSync(s.sourcePath)).map((skill) => {
45862
+ const pack = loadProjectExtensions().skills.filter((s) => s.extends === void 0 && fs24.existsSync(s.sourcePath)).map((skill) => {
44887
45863
  const id = packSkillId(skill);
44888
- const fm = readFrontmatter(fs23.readFileSync(skill.sourcePath, "utf-8"), id);
45864
+ const fm = readFrontmatter(fs24.readFileSync(skill.sourcePath, "utf-8"), id);
44889
45865
  return {
44890
45866
  id,
44891
45867
  name: fm.name,
@@ -44906,7 +45882,7 @@ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
44906
45882
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
44907
45883
  var import_zod12 = require("zod");
44908
45884
  var import_types16 = require("@modelcontextprotocol/sdk/types.js");
44909
- var path48 = __toESM(require("path"));
45885
+ var path49 = __toESM(require("path"));
44910
45886
  var crypto10 = __toESM(require("crypto"));
44911
45887
 
44912
45888
  // src/core/adapters/authoring-core.ts
@@ -45169,6 +46145,16 @@ function carryInto(restatement, existing, candidate) {
45169
46145
  method.previousNames = prev.previousNames;
45170
46146
  carried.push(`previousNames (${String(method.name)})`);
45171
46147
  }
46148
+ for (const method of candidate.methods) {
46149
+ const prev = stored.find((p) => p.name === method.name);
46150
+ const prevParams = Array.isArray(prev?.params) ? prev.params : [];
46151
+ for (const param of Array.isArray(method.params) ? method.params : []) {
46152
+ const before = prevParams.find((p) => p.name === param.name);
46153
+ if (before?.previousNames === void 0 || param.previousNames !== void 0) continue;
46154
+ param.previousNames = before.previousNames;
46155
+ carried.push(`previousNames (param ${String(method.name)}.${String(param.name)})`);
46156
+ }
46157
+ }
45172
46158
  }
45173
46159
  if (restatement.kind === "type" && Array.isArray(candidate.fields)) {
45174
46160
  const stored = existing.fields ?? [];
@@ -45382,6 +46368,9 @@ function updateSpecGated(kind, id, delta, dryRun) {
45382
46368
  const stored = loadSpec(kind, id);
45383
46369
  const gate = componentCandidateGate(bound2, stored?.subsystem, stored);
45384
46370
  const report3 = updateSpec(kind, id, delta, gate, dryRun);
46371
+ if (kind === "component" && typeof delta?.transport === "string" && report3.changes.some((c) => c.path === "transport")) {
46372
+ report3.notices.push(...adapterTransportImpact(id, delta.transport));
46373
+ }
45385
46374
  const changed = changedMethods(report3);
45386
46375
  if (changed.length > 0 && testRoots.length > 0) {
45387
46376
  const written = Array.isArray(delta?.methods) ? delta.methods : [];
@@ -45389,6 +46378,19 @@ function updateSpecGated(kind, id, delta, dryRun) {
45389
46378
  }
45390
46379
  return report3;
45391
46380
  }
46381
+ function adapterTransportImpact(portal, transport) {
46382
+ const components = loadComponentSpecs();
46383
+ if (components.find((c) => c.id === portal)?.componentType !== "Portal") return [];
46384
+ const names = (ref) => ref === portal || ref !== void 0 && ref.endsWith(`::${portal}`);
46385
+ const calledByStep = /* @__PURE__ */ new Set();
46386
+ for (const impl of loadImplementationSpecs()) {
46387
+ const owner = loadSpec("interface", impl.contract)?.component;
46388
+ if (owner === void 0) continue;
46389
+ const steps = impl.methods.flatMap((m) => m.narrative ?? []);
46390
+ if (steps.some((s) => (s.type === "call" || s.type === "register" || s.type === "dispatch") && names(s.targetComponent))) calledByStep.add(owner);
46391
+ }
46392
+ return components.filter((c) => c.componentType === "Adapter" && c.transport !== void 0 && c.transport !== transport).filter((c) => (c.dependsOn ?? []).some(names) || calledByStep.has(c.id)).map((c) => `ADAPTER_TRANSPORT_MISMATCH would follow: Adapter "${c.id}" states transport "${c.transport}" and calls "${portal}", whose transport becomes "${transport}" \u2014 state "${transport}" on "${c.id}", or unset its transport so it follows the Portal.`);
46393
+ }
45392
46394
  function changedMethods(report3) {
45393
46395
  const named2 = /* @__PURE__ */ new Set();
45394
46396
  for (const change of report3.changes) {
@@ -45425,12 +46427,12 @@ function moveMethods2(from, to, methods, dryRun) {
45425
46427
  }
45426
46428
 
45427
46429
  // src/mcp/build.ts
45428
- var fs24 = __toESM(require("fs"));
46430
+ var fs25 = __toESM(require("fs"));
45429
46431
  var path39 = __toESM(require("path"));
45430
46432
  var import_child_process3 = require("child_process");
45431
46433
  function captureBuildStamp(entryPath) {
45432
46434
  try {
45433
- const s = fs24.statSync(entryPath);
46435
+ const s = fs25.statSync(entryPath);
45434
46436
  return { path: entryPath, mtimeMs: s.mtimeMs, size: s.size };
45435
46437
  } catch {
45436
46438
  return null;
@@ -45439,7 +46441,7 @@ function captureBuildStamp(entryPath) {
45439
46441
  function isBuildStale(stamp) {
45440
46442
  if (!stamp) return false;
45441
46443
  try {
45442
- const s = fs24.statSync(stamp.path);
46444
+ const s = fs25.statSync(stamp.path);
45443
46445
  return s.mtimeMs !== stamp.mtimeMs || s.size !== stamp.size;
45444
46446
  } catch {
45445
46447
  return false;
@@ -45447,7 +46449,7 @@ function isBuildStale(stamp) {
45447
46449
  }
45448
46450
  var fingerprintCache = /* @__PURE__ */ new Map();
45449
46451
  function readBuildFingerprint(stamp) {
45450
- const current2 = fs24.statSync(stamp.path);
46452
+ const current2 = fs25.statSync(stamp.path);
45451
46453
  const key2 = `${stamp.path}|${current2.mtimeMs}|${current2.size}`;
45452
46454
  const cached = fingerprintCache.get(key2);
45453
46455
  if (cached !== void 0) return cached;
@@ -45473,9 +46475,9 @@ function fingerprintModuleFor(entryPath) {
45473
46475
  let dir = path39.dirname(entryPath);
45474
46476
  for (; ; ) {
45475
46477
  const manifest = path39.join(dir, "package.json");
45476
- if (fs24.existsSync(manifest)) {
46478
+ if (fs25.existsSync(manifest)) {
45477
46479
  try {
45478
- const pkg = JSON.parse(fs24.readFileSync(manifest, "utf8"));
46480
+ const pkg = JSON.parse(fs25.readFileSync(manifest, "utf8"));
45479
46481
  const inDist = path39.relative(path39.join(dir, "dist"), entryPath);
45480
46482
  if (pkg.name === "@wairon/cli" && pkg.main && !inDist.startsWith("..") && !path39.isAbsolute(inDist)) {
45481
46483
  return path39.resolve(dir, pkg.main);
@@ -45491,6 +46493,9 @@ function fingerprintModuleFor(entryPath) {
45491
46493
  }
45492
46494
 
45493
46495
  // src/core/skills-portal.ts
46496
+ function exportSddSkills2(targetTypes) {
46497
+ return exportSddSkills(targetTypes);
46498
+ }
45494
46499
  function buildServerInstructions3() {
45495
46500
  return buildServerInstructions2();
45496
46501
  }
@@ -45582,10 +46587,10 @@ function findingLine(f) {
45582
46587
  }
45583
46588
  function markFlows(flows3, f) {
45584
46589
  if (f.specId === void 0) return;
45585
- const onVerb = (flow) => flow.from.scope !== void 0 && flow.to.component === f.specId && (f.at === void 0 || flow.to.verb === f.at);
46590
+ const onVerb = (flow) => flow.from.scope !== void 0 && flow.to.component === f.specId && (f.at !== void 0 ? flow.to.verb === f.at : f.covers === void 0 || f.covers.includes(flow.to.verb ?? ""));
45586
46591
  const onCall = (flow) => flow.from.scope === void 0 && flow.from.component === f.specId && f.at !== void 0 && flow.evidence.includes(`call ${f.at}`);
45587
46592
  for (const flow of flows3.filter((x) => onVerb(x) || onCall(x))) {
45588
- const field = f.severity === "error" ? "refusedBy" : "flaggedBy";
46593
+ const field = f.severity === "error" ? "refusedBy" : f.severity === "notice" ? "notedBy" : "flaggedBy";
45589
46594
  const list = flow[field] ?? [];
45590
46595
  if (!list.includes(findingLine(f))) list.push(findingLine(f));
45591
46596
  flow[field] = list;
@@ -45608,7 +46613,10 @@ function project2(model) {
45608
46613
  }
45609
46614
  const marked = [...flows3.values()];
45610
46615
  for (const f of model.findings ?? []) markFlows(marked, f);
45611
- return marked.sort(byCalleeThenCaller);
46616
+ const focus = model.focus;
46617
+ const inside = (party) => focus !== void 0 && party.project !== void 0 && (party.project === focus || party.project.startsWith(`${focus}::`));
46618
+ const kept = focus === void 0 ? marked : marked.filter((f) => inside(f.from) || inside(f.to));
46619
+ return kept.sort(byCalleeThenCaller);
45612
46620
  }
45613
46621
  function networkName2(id) {
45614
46622
  return id === "" ? "(root network)" : id;
@@ -45618,7 +46626,7 @@ function chainOf(flow) {
45618
46626
  for (const network of flow.crosses) lines.push(`enters network ${networkName2(network)}`);
45619
46627
  if (flow.via !== void 0) lines.push(`through gateway ${flow.via}`);
45620
46628
  lines.push(`reaches ${key(flow.to)} over ${flow.transport}${flow.binding ? ` (${flow.binding})` : ""}`);
45621
- for (const flag of flow.flaggedBy ?? []) lines.push(`marked by the gate: ${flag}`);
46629
+ for (const flag of [...flow.flaggedBy ?? [], ...flow.notedBy ?? []]) lines.push(`marked by the gate: ${flag}`);
45622
46630
  return lines;
45623
46631
  }
45624
46632
  function verbParty(v) {
@@ -45627,8 +46635,14 @@ function verbParty(v) {
45627
46635
  function reachesOverNetwork(model, to) {
45628
46636
  return model.verbs.some((v) => transportKind(v.transport) === "network" && isNamed(verbParty(v), to));
45629
46637
  }
46638
+ function throughExternal(model, name) {
46639
+ const at = name.indexOf("::");
46640
+ const alias = at > 0 ? name.slice(0, at) : name;
46641
+ return (model.externals ?? []).includes(alias) ? alias : void 0;
46642
+ }
45630
46643
  function isKnown(model, name) {
45631
46644
  if (name === "outside" || name === "network") return true;
46645
+ if ((model.subsystems ?? []).some((s) => s === name || s.startsWith(`${name}::`)) || throughExternal(model, name) !== void 0) return true;
45632
46646
  if (name.startsWith("network:")) return model.networks.some((n) => n.id === name.slice("network:".length));
45633
46647
  const placed = (model.placements ?? []).some((p) => isNamed({ project: p.project, ...p.subsystem !== void 0 ? { subsystem: p.subsystem } : {}, component: p.component }, name));
45634
46648
  return placed || model.verbs.some((v) => isNamed(verbParty(v), name)) || model.calls.some((c) => isNamed(callerOf(c), name));
@@ -45642,6 +46656,18 @@ function notAllowed(model, flows3, refused3, from, to) {
45642
46656
  return { allowed: false, flows: [], chain: chain2, refusedBy };
45643
46657
  }
45644
46658
  if (!reachesOverNetwork(model, to)) {
46659
+ const alias = throughExternal(model, to);
46660
+ if (alias !== void 0) {
46661
+ return {
46662
+ allowed: false,
46663
+ flows: [],
46664
+ inProcess: true,
46665
+ chain: [
46666
+ `${to} is consumed through the external "${alias}": this design's network model holds no verb of it, so it neither allows nor forbids a network flow to it here`,
46667
+ `a library is reached in-process; a network producer's flows are derived and judged in its own project (\`wairon network flows\` there)`
46668
+ ]
46669
+ };
46670
+ }
45645
46671
  return {
45646
46672
  allowed: false,
45647
46673
  flows: [],
@@ -45689,17 +46715,31 @@ function codeOf(line) {
45689
46715
  }
45690
46716
  function gateCell(flow) {
45691
46717
  if (refusedFlow(flow)) return `REFUSED: ${[...new Set(flow.refusedBy.map(codeOf))].join("; ")}`;
45692
- return [...new Set((flow.flaggedBy ?? []).map(codeOf))].join("; ");
46718
+ return [...new Set([...flow.flaggedBy ?? [], ...flow.notedBy ?? []].map(codeOf))].join("; ");
45693
46719
  }
45694
46720
  function gateFindingsOf(flows3) {
45695
46721
  const errors = /* @__PURE__ */ new Set();
45696
46722
  const flags = /* @__PURE__ */ new Set();
46723
+ const notes = /* @__PURE__ */ new Set();
45697
46724
  for (const f of flows3) {
45698
46725
  for (const line of f.refusedBy ?? []) errors.add(line);
45699
46726
  for (const line of f.flaggedBy ?? []) flags.add(line);
46727
+ for (const line of f.notedBy ?? []) notes.add(line);
45700
46728
  }
45701
46729
  const withSeverity = (line, severity) => `${codeOf(line)} (${severity})${line.slice(codeOf(line).length)}`;
45702
- return [...[...errors].map((l) => withSeverity(l, "error")), ...[...flags].map((l) => withSeverity(l, "warning"))];
46730
+ return [
46731
+ ...[...errors].map((l) => withSeverity(l, "error")),
46732
+ ...[...flags].map((l) => withSeverity(l, "warning")),
46733
+ ...[...notes].map((l) => withSeverity(l, "notice"))
46734
+ ];
46735
+ }
46736
+ function namedRoot(flow, rootProject) {
46737
+ const party = (p) => ({
46738
+ ...p,
46739
+ ...p.project === "" ? { project: rootProject } : {},
46740
+ ...p.network === "" ? { network: rootProject } : {}
46741
+ });
46742
+ return { ...flow, from: party(flow.from), to: party(flow.to), crosses: flow.crosses.map((n) => n === "" ? rootProject : n) };
45703
46743
  }
45704
46744
  function networkName3(id) {
45705
46745
  return id === "" ? "(root network)" : id;
@@ -45734,11 +46774,11 @@ function markdownOf(flows3) {
45734
46774
  const warning = refused3.length > 0 ? [`> **The design fails the gate:** ${refused3.length} row(s) marked REFUSED are refused by \`wairon validate\`, not allowed. Fix the design before trusting this matrix.`, ...gateFindingsOf(flows3).map((l) => `> - ${l.replace(/\n/g, " ")}`), ""] : [];
45735
46775
  return [...warning, ...header, ...rows].join("\n") + "\n" + (flows3.length === 0 ? "\n_No network-transport flows: in-process and local verbs never produce one._\n" : "");
45736
46776
  }
45737
- function encodeFlows(flows3, format) {
46777
+ function encodeFlows(flows3, format, rootProject) {
45738
46778
  const gate = { gateFindings: gateFindingsOf(flows3), refused: flows3.some(refusedFlow) };
45739
46779
  switch (format) {
45740
46780
  case "json":
45741
- return { format, content: JSON.stringify(sortedKeys2(flows3), null, 2) + "\n", unbound: [], ...gate };
46781
+ return { format, content: JSON.stringify(sortedKeys2(rootProject ? flows3.map((f) => namedRoot(f, rootProject)) : flows3), null, 2) + "\n", unbound: [], ...gate };
45742
46782
  case "csv":
45743
46783
  return { format, content: csvOf(flows3), unbound: [], ...gate };
45744
46784
  default:
@@ -45750,13 +46790,24 @@ function encodeFlows(flows3, format) {
45750
46790
  function flows(options, format) {
45751
46791
  const model = reachModel2(options);
45752
46792
  const matrix = project2(model);
45753
- return encodeFlows(matrix, format);
46793
+ const doc = encodeFlows(matrix, format, model.rootProject);
46794
+ return withFamilyNote(doc, model);
46795
+ }
46796
+ function withFamilyNote(doc, model) {
46797
+ if (model.focus === void 0 || model.judgedAt === void 0) return doc;
46798
+ const note = `"${model.focus}" is a member: this is its enclosing family's network, declared and proven at the family root ${model.judgedAt}, narrowed to the flows that start or land inside it \u2014 the same rows \`wairon network\` derives there.`;
46799
+ return { ...doc, notes: [note, ...doc.notes ?? []] };
45754
46800
  }
45755
46801
  function why(options, from, to) {
45756
46802
  const model = reachModel2(options);
45757
- const matrix = project2(model);
46803
+ const matrix = project2(unfocused(model));
45758
46804
  return explain(matrix, from, to, model);
45759
46805
  }
46806
+ function unfocused(model) {
46807
+ if (model.focus === void 0) return model;
46808
+ const { focus: _focus, ...whole } = model;
46809
+ return whole;
46810
+ }
45760
46811
 
45761
46812
  // src/mcp/network.ts
45762
46813
  function lands(flow, to) {
@@ -45772,10 +46823,16 @@ function explain2(from, to) {
45772
46823
  return why({}, from, to);
45773
46824
  }
45774
46825
  function declare2(declared, description) {
45775
- const network = declared ? description ? { description } : {} : null;
46826
+ const held = declared ? loadProjectConfig()?.network : void 0;
46827
+ const kept = description ?? (typeof held === "object" && held !== null ? held.description ?? null : null);
46828
+ const network = declared ? kept ? { description: kept } : {} : null;
45776
46829
  return setNetwork(network);
45777
46830
  }
45778
46831
 
46832
+ // src/mcp/migrations.ts
46833
+ var fs30 = __toESM(require("fs"));
46834
+ var path48 = __toESM(require("path"));
46835
+
45779
46836
  // src/migrations/orchestrator.ts
45780
46837
  var path47 = __toESM(require("path"));
45781
46838
 
@@ -46828,12 +47885,12 @@ function pin(p, log) {
46828
47885
  }
46829
47886
 
46830
47887
  // src/migrations/membership.ts
46831
- var fs26 = __toESM(require("fs"));
47888
+ var fs27 = __toESM(require("fs"));
46832
47889
  var path43 = __toESM(require("path"));
46833
47890
 
46834
47891
  // src/migrations/family-files.ts
46835
47892
  var crypto8 = __toESM(require("crypto"));
46836
- var fs25 = __toESM(require("fs"));
47893
+ var fs26 = __toESM(require("fs"));
46837
47894
  var nodePath = __toESM(require("path"));
46838
47895
  var WAI = ".wai";
46839
47896
  var TRANSACTIONS = "transactions";
@@ -46846,7 +47903,7 @@ function pause(ms) {
46846
47903
  }
46847
47904
  function readOnly(target) {
46848
47905
  try {
46849
- return (fs25.statSync(target).mode & 128) === 0;
47906
+ return (fs26.statSync(target).mode & 128) === 0;
46850
47907
  } catch {
46851
47908
  return false;
46852
47909
  }
@@ -46872,7 +47929,7 @@ function waiFiles(owner) {
46872
47929
  const walk2 = (dir, rel2) => {
46873
47930
  let entries;
46874
47931
  try {
46875
- entries = fs25.readdirSync(dir, { withFileTypes: true });
47932
+ entries = fs26.readdirSync(dir, { withFileTypes: true });
46876
47933
  } catch {
46877
47934
  return;
46878
47935
  }
@@ -46892,7 +47949,7 @@ function waiOwners(root) {
46892
47949
  const walk2 = (dir) => {
46893
47950
  let entries;
46894
47951
  try {
46895
- entries = fs25.readdirSync(dir, { withFileTypes: true });
47952
+ entries = fs26.readdirSync(dir, { withFileTypes: true });
46896
47953
  } catch {
46897
47954
  return;
46898
47955
  }
@@ -46916,7 +47973,7 @@ function areaFiles(owner, area) {
46916
47973
  const abs = nodePath.join(owner, ...rel2.split("/"));
46917
47974
  let stat;
46918
47975
  try {
46919
- stat = fs25.lstatSync(abs);
47976
+ stat = fs26.lstatSync(abs);
46920
47977
  } catch {
46921
47978
  return;
46922
47979
  }
@@ -46926,7 +47983,7 @@ function areaFiles(owner, area) {
46926
47983
  return;
46927
47984
  }
46928
47985
  if (!stat.isDirectory()) return;
46929
- for (const name of fs25.readdirSync(abs)) walk2(`${rel2}/${name}`);
47986
+ for (const name of fs26.readdirSync(abs)) walk2(`${rel2}/${name}`);
46930
47987
  };
46931
47988
  walk2(area.split(nodePath.sep).join("/").replace(/\/+$/, ""));
46932
47989
  return out.sort();
@@ -46936,7 +47993,7 @@ function everyFile(owner) {
46936
47993
  const walk2 = (dir, rel2) => {
46937
47994
  let entries;
46938
47995
  try {
46939
- entries = fs25.readdirSync(dir, { withFileTypes: true });
47996
+ entries = fs26.readdirSync(dir, { withFileTypes: true });
46940
47997
  } catch {
46941
47998
  return;
46942
47999
  }
@@ -46980,22 +48037,22 @@ function assertPlaceable(familyRoot, base, owner) {
46980
48037
  }
46981
48038
  function nearestExisting(p) {
46982
48039
  let at = nodePath.resolve(p);
46983
- while (!fs25.existsSync(at) && at !== nodePath.dirname(at)) at = nodePath.dirname(at);
48040
+ while (!fs26.existsSync(at) && at !== nodePath.dirname(at)) at = nodePath.dirname(at);
46984
48041
  return at;
46985
48042
  }
46986
48043
  function copyFiles(owner, rels, at, digests) {
46987
- fs25.mkdirSync(at, { recursive: true });
48044
+ fs26.mkdirSync(at, { recursive: true });
46988
48045
  for (const rel2 of rels) {
46989
48046
  const live = nodePath.join(owner, ...rel2.split("/"));
46990
48047
  let bytes;
46991
48048
  try {
46992
- bytes = fs25.readFileSync(live);
48049
+ bytes = fs26.readFileSync(live);
46993
48050
  } catch (e) {
46994
48051
  throw new Error(`could not copy ${live} into the rehearsal: ${e instanceof Error ? e.message : String(e)}`);
46995
48052
  }
46996
48053
  const copy = nodePath.join(at, ...rel2.split("/"));
46997
- fs25.mkdirSync(nodePath.dirname(copy), { recursive: true });
46998
- fs25.writeFileSync(copy, bytes);
48054
+ fs26.mkdirSync(nodePath.dirname(copy), { recursive: true });
48055
+ fs26.writeFileSync(copy, bytes);
46999
48056
  digests.set(live, sha2562(bytes));
47000
48057
  }
47001
48058
  }
@@ -47004,7 +48061,7 @@ function compare4(rehearsal) {
47004
48061
  const seen = /* @__PURE__ */ new Set();
47005
48062
  for (const [live, copyRoot, rel2] of comparedFiles(rehearsal)) {
47006
48063
  seen.add(nodePath.join(live, ...rel2.split("/")));
47007
- const change = changeOf(rehearsal, live, rel2, sha2562(fs25.readFileSync(nodePath.join(copyRoot, ...rel2.split("/")))));
48064
+ const change = changeOf(rehearsal, live, rel2, sha2562(fs26.readFileSync(nodePath.join(copyRoot, ...rel2.split("/")))));
47008
48065
  if (change) changes.push(change);
47009
48066
  }
47010
48067
  for (const [liveFile, base] of rehearsal.baseDigests) {
@@ -47039,7 +48096,7 @@ function ownerOf3(rehearsal, liveFile) {
47039
48096
  }
47040
48097
  function move(from, to) {
47041
48098
  const rels = everyFile(from);
47042
- const taken = rels.find((rel2) => fs25.existsSync(nodePath.join(to, ...rel2.split("/"))));
48099
+ const taken = rels.find((rel2) => fs26.existsSync(nodePath.join(to, ...rel2.split("/"))));
47043
48100
  if (taken !== void 0) throw new Error(`cannot move ${from} to ${to}: ${nodePath.join(to, ...taken.split("/"))} already exists`);
47044
48101
  for (const rel2 of rels) {
47045
48102
  const target = nodePath.join(to, ...rel2.split("/"));
@@ -47049,45 +48106,45 @@ function move(from, to) {
47049
48106
  for (const rel2 of rels) prune2(nodePath.dirname(nodePath.join(from, ...rel2.split("/"))), nodePath.dirname(from));
47050
48107
  prune2(from, nodePath.dirname(from));
47051
48108
  }
47052
- function read(path49) {
48109
+ function read(path50) {
47053
48110
  try {
47054
- return fs25.readFileSync(path49);
48111
+ return fs26.readFileSync(path50);
47055
48112
  } catch (e) {
47056
48113
  if (e.code === "ENOENT") return null;
47057
- throw new Error(`could not read ${path49}: ${e instanceof Error ? e.message : String(e)}`);
48114
+ throw new Error(`could not read ${path50}: ${e instanceof Error ? e.message : String(e)}`);
47058
48115
  }
47059
48116
  }
47060
- function digest2(path49) {
47061
- const bytes = read(path49);
48117
+ function digest2(path50) {
48118
+ const bytes = read(path50);
47062
48119
  return bytes === null ? null : sha2562(bytes);
47063
48120
  }
47064
48121
  function replace(source, target) {
47065
- retrying("replace", target, () => fs25.renameSync(source, target));
48122
+ retrying("replace", target, () => fs26.renameSync(source, target));
47066
48123
  }
47067
- function place(path49, bytes) {
47068
- fs25.mkdirSync(nodePath.dirname(path49), { recursive: true });
47069
- const tmp = `${path49}.wairon-restore`;
47070
- const fd = fs25.openSync(tmp, "w");
48124
+ function place(path50, bytes) {
48125
+ fs26.mkdirSync(nodePath.dirname(path50), { recursive: true });
48126
+ const tmp = `${path50}.wairon-restore`;
48127
+ const fd = fs26.openSync(tmp, "w");
47071
48128
  try {
47072
- fs25.writeSync(fd, bytes);
47073
- fs25.fsyncSync(fd);
48129
+ fs26.writeSync(fd, bytes);
48130
+ fs26.fsyncSync(fd);
47074
48131
  } finally {
47075
- fs25.closeSync(fd);
48132
+ fs26.closeSync(fd);
47076
48133
  }
47077
48134
  try {
47078
- retrying("place", path49, () => fs25.renameSync(tmp, path49));
48135
+ retrying("place", path50, () => fs26.renameSync(tmp, path50));
47079
48136
  } catch (e) {
47080
48137
  try {
47081
- fs25.unlinkSync(tmp);
48138
+ fs26.unlinkSync(tmp);
47082
48139
  } catch {
47083
48140
  }
47084
48141
  throw e;
47085
48142
  }
47086
48143
  }
47087
- function remove2(path49) {
47088
- retrying("remove", path49, () => {
48144
+ function remove2(path50) {
48145
+ retrying("remove", path50, () => {
47089
48146
  try {
47090
- fs25.unlinkSync(path49);
48147
+ fs26.unlinkSync(path50);
47091
48148
  } catch (e) {
47092
48149
  if (e.code !== "ENOENT") throw e;
47093
48150
  }
@@ -47096,13 +48153,13 @@ function remove2(path49) {
47096
48153
  function makeDirs(dir) {
47097
48154
  const missing = [];
47098
48155
  let at = nodePath.resolve(dir);
47099
- while (!fs25.existsSync(at)) {
48156
+ while (!fs26.existsSync(at)) {
47100
48157
  missing.unshift(at);
47101
48158
  const up = nodePath.dirname(at);
47102
48159
  if (up === at) break;
47103
48160
  at = up;
47104
48161
  }
47105
- for (const d of missing) fs25.mkdirSync(d);
48162
+ for (const d of missing) fs26.mkdirSync(d);
47106
48163
  return missing;
47107
48164
  }
47108
48165
  function prune2(dir, stopAt) {
@@ -47112,47 +48169,47 @@ function prune2(dir, stopAt) {
47112
48169
  while (at !== stop && within3(stop, at)) {
47113
48170
  let entries;
47114
48171
  try {
47115
- entries = fs25.readdirSync(at);
48172
+ entries = fs26.readdirSync(at);
47116
48173
  } catch {
47117
48174
  break;
47118
48175
  }
47119
48176
  if (entries.length > 0) break;
47120
- fs25.rmdirSync(at);
48177
+ fs26.rmdirSync(at);
47121
48178
  removed.push(at);
47122
48179
  at = nodePath.dirname(at);
47123
48180
  }
47124
48181
  return removed;
47125
48182
  }
47126
- function resolve35(root, path49) {
47127
- const dir = lexicalTarget(root, path49);
48183
+ function resolve35(root, path50) {
48184
+ const dir = lexicalTarget(root, path50);
47128
48185
  return dir !== null && landsWithin(root, dir) ? dir : null;
47129
48186
  }
47130
48187
  function strictlyWithin(root, dir) {
47131
48188
  const rel2 = nodePath.relative(root, dir);
47132
48189
  return rel2 !== "" && !rel2.startsWith("..") && !nodePath.isAbsolute(rel2);
47133
48190
  }
47134
- function lexicalTarget(root, path49) {
47135
- if (!path49 || nodePath.isAbsolute(path49) || nodePath.win32.isAbsolute(path49)) return null;
47136
- const dir = nodePath.resolve(root, path49);
48191
+ function lexicalTarget(root, path50) {
48192
+ if (!path50 || nodePath.isAbsolute(path50) || nodePath.win32.isAbsolute(path50)) return null;
48193
+ const dir = nodePath.resolve(root, path50);
47137
48194
  return strictlyWithin(root, dir) ? dir : null;
47138
48195
  }
47139
48196
  function landsWithin(root, dir) {
47140
48197
  let at = dir;
47141
- while (!fs25.existsSync(at) && at !== nodePath.dirname(at)) at = nodePath.dirname(at);
48198
+ while (!fs26.existsSync(at) && at !== nodePath.dirname(at)) at = nodePath.dirname(at);
47142
48199
  try {
47143
- const real = fs25.realpathSync(at);
47144
- const realRoot = fs25.realpathSync(root);
48200
+ const real = fs26.realpathSync(at);
48201
+ const realRoot = fs26.realpathSync(root);
47145
48202
  return real === realRoot || strictlyWithin(realRoot, real);
47146
48203
  } catch {
47147
48204
  return false;
47148
48205
  }
47149
48206
  }
47150
48207
  function holdsProject(dir) {
47151
- return fs25.existsSync(nodePath.join(dir, ".wai", "project.yaml"));
48208
+ return fs26.existsSync(nodePath.join(dir, ".wai", "project.yaml"));
47152
48209
  }
47153
48210
  function sameVolume(a, b) {
47154
48211
  try {
47155
- return fs25.statSync(a).dev === fs25.statSync(b).dev;
48212
+ return fs26.statSync(a).dev === fs26.statSync(b).dev;
47156
48213
  } catch {
47157
48214
  return false;
47158
48215
  }
@@ -47167,7 +48224,7 @@ function emptyPlan(family, request) {
47167
48224
  const whole = !(reach2 !== null && !reach2.parentReach);
47168
48225
  return { request, familyRoot: familyNode(family, "").directory, whole, edits: [], refusals: [], changes: [], relock: [], notes: [] };
47169
48226
  }
47170
- var posix5 = (p) => p.split(path43.sep).join("/");
48227
+ var posix6 = (p) => p.split(path43.sep).join("/");
47171
48228
  var refuse = (plan6, code, project3, detail) => {
47172
48229
  plan6.refusals.push({ code, project: project3, detail });
47173
48230
  };
@@ -47195,7 +48252,7 @@ function hasSystemAt(dir) {
47195
48252
  }
47196
48253
  function occupied(dir) {
47197
48254
  try {
47198
- return !fs26.statSync(dir).isDirectory() || fs26.readdirSync(dir).length > 0;
48255
+ return !fs27.statSync(dir).isDirectory() || fs27.readdirSync(dir).length > 0;
47199
48256
  } catch {
47200
48257
  return false;
47201
48258
  }
@@ -47245,7 +48302,7 @@ function attachEdits(plan6, family, bound2, dir, alias, bindingConfig) {
47245
48302
  edit(plan6, key2, "id", `id: ${id} (declared${approved !== void 0 ? ", the id its lock approved" : ""})`, { root: dir, call: "setId", args: [id] });
47246
48303
  }
47247
48304
  if (!attached) plan6.notes.push(`${dir} has no project.yaml of its own \u2014 \`wairon doctor --fix\` writes one declaring "${alias}" as its id`);
47248
- const relPath = posix5(path43.relative(familyNode(family, bound2).directory, dir));
48305
+ const relPath = posix6(path43.relative(familyNode(family, bound2).directory, dir));
47249
48306
  const declaration2 = { path: relPath, ...plan6.request.description !== void 0 ? { description: plan6.request.description } : {} };
47250
48307
  edit(plan6, bound2, "member", `members: ${alias} \u2192 ${relPath} (an existing project, kept as it is)`, { root: familyNode(family, bound2).directory, call: "declareMember", args: [alias, declaration2] });
47251
48308
  for (const requirement of requiredPolicies(bindingConfig)) {
@@ -47292,8 +48349,8 @@ function planDetach(family, bound2, request) {
47292
48349
  if (plan6.request.widen) planWidening(plan6, member, narrow);
47293
48350
  if (plan6.refusals.length > 0) return plan6;
47294
48351
  const boundDir = familyNode(family, bound2).directory;
47295
- const held = memberDeclarationOf(configAt3(boundDir)?.members?.[alias] ?? posix5(path43.relative(boundDir, member.directory)));
47296
- const declared = { ...held, path: memberLocationOf(held) ?? posix5(path43.relative(boundDir, member.directory)) };
48352
+ const held = memberDeclarationOf(configAt3(boundDir)?.members?.[alias] ?? posix6(path43.relative(boundDir, member.directory)));
48353
+ const declared = { ...held, path: memberLocationOf(held) ?? posix6(path43.relative(boundDir, member.directory)) };
47297
48354
  detachEdits(plan6, family, bound2, member, alias, declared, consumers, outward);
47298
48355
  return plan6;
47299
48356
  }
@@ -47333,7 +48390,7 @@ function planWidening(plan6, member, narrow) {
47333
48390
  function detachEdits(plan6, family, bound2, member, alias, declared, consumers, outward) {
47334
48391
  const boundDir = familyNode(family, bound2).directory;
47335
48392
  const relocation = plan6.request.relocation;
47336
- const sourceTo = (from, producer) => relocation ? { hosted: producer.id ?? producer.mountAlias ?? producer.namespace } : { path: posix5(path43.relative(from, producer.directory)) };
48393
+ const sourceTo = (from, producer) => relocation ? { hosted: producer.id ?? producer.mountAlias ?? producer.namespace } : { path: posix6(path43.relative(from, producer.directory)) };
47337
48394
  const where = (source) => source.hosted !== void 0 ? `hosted record ${source.hosted}` : source.path;
47338
48395
  edit(plan6, bound2, "member", `members: ${alias} removed (${declared.path} ${relocation ? `moves to ${relocation.to}` : "stays as it is"})`, { root: boundDir, call: "removeMember", args: [alias] });
47339
48396
  const boundSource = relocation ? sourceTo(boundDir, member) : { path: declared.path };
@@ -47407,7 +48464,7 @@ function adoptEdits(plan6, family, bound2, alias, external, dir, at = dir) {
47407
48464
  edit(plan6, bound2, "pin", `pin ${alias} removed (the family supplies it now)`, { root: boundDir, call: "unpin", args: [alias] });
47408
48465
  edit(plan6, bound2, "external", `externals: ${alias} removed`, { root: boundDir, call: "removeExternal", args: [alias] });
47409
48466
  const declaration2 = {
47410
- path: posix5(path43.relative(boundDir, at)),
48467
+ path: posix6(path43.relative(boundDir, at)),
47411
48468
  ...external.description !== void 0 ? { description: external.description } : {},
47412
48469
  ...external.use && external.use.length > 0 ? { use: external.use } : {}
47413
48470
  };
@@ -47597,6 +48654,11 @@ function planRename(family, bound2, request) {
47597
48654
  edit2(plan6, target.namespace, "id", `id: ${oldId} \u2192 ${newId} (${oldId} kept in previousIds)`, { root: target.directory, call: "renameId", args: [oldId, newId] });
47598
48655
  for (const h of holders.filter((x) => x.external)) planPin(plan6, h, oldId, newId);
47599
48656
  plan6.notes.push(`Consumers outside the family that name "${oldId}" by path are not found by a rename: each must rewrite its own external (its next gate reports the id change).`);
48657
+ if (target.namespace !== "" && target.mountAlias === oldId) {
48658
+ const oldKey = target.namespace;
48659
+ const newKey = target.parent ? `${target.parent}::${newId}` : newId;
48660
+ plan6.notes.push(`Deployment names: a network bindings file and an observed-flow export name this project's workloads by its key \u2014 rename the bindings keys "${oldKey}" and every "${oldKey}::\u2026" (its subsystems and Portals) to "${newKey}" and "${newKey}::\u2026", and the telemetry names that follow them. Until then \`wairon network policy\` and \`check\` resolve them through previousIds and print a NOTE naming each key.`);
48661
+ }
47600
48662
  if (approved !== void 0 && target.mountAlias === newId) {
47601
48663
  plan6.notes.push(`${label5(target.namespace)} kept "${oldId}" where its alias is "${newId}" because its lock approved "${approved}" (id-locked): the rename resolves it \u2014 PROJECT_ID_RENAMED until the re-lock approves "${newId}".`);
47602
48664
  }
@@ -47746,7 +48808,7 @@ function emptyPlan3(family, request) {
47746
48808
  const whole = !(reach2 !== null && !reach2.parentReach);
47747
48809
  return { request, familyRoot: familyNode(family, "").directory, whole, edits: [], refusals: [], changes: [], relock: [], notes: [] };
47748
48810
  }
47749
- var posix6 = (p) => p.split(path45.sep).join("/");
48811
+ var posix7 = (p) => p.split(path45.sep).join("/");
47750
48812
  var label6 = (key2) => key2 === "" ? "the top project" : `"${key2}"`;
47751
48813
  var refuse3 = (plan6, code, project3, detail) => {
47752
48814
  plan6.refusals.push({ code, project: project3, detail });
@@ -47956,7 +49018,7 @@ function respellConsumer(plan6, family, c, own2) {
47956
49018
  function repointConsumer(plan6, c, boundNode2) {
47957
49019
  const declared = configAt5(c.node.directory)?.externals?.[c.alias];
47958
49020
  const project3 = boundNode2.id !== void 0 && boundNode2.id !== c.alias ? boundNode2.id : null;
47959
- const source = declared?.source ? { path: posix6(path45.relative(c.node.directory, boundNode2.directory)) } : null;
49021
+ const source = declared?.source ? { path: posix7(path45.relative(c.node.directory, boundNode2.directory)) } : null;
47960
49022
  edit3(plan6, c.node.namespace, "external", `externals: ${c.alias} names ${boundNode2.id ?? "the top project"}${source ? ` at ${source.path}` : ""}`, { root: c.node.directory, call: "repointExternal", args: [c.alias, project3, source] });
47961
49023
  plan6.notes.push(`${label6(c.node.namespace)}'s pin of "${c.alias}" is taken again against ${boundNode2.id ?? "the top project"}: the producer changed, so its lock reads drifted until it is re-locked.`);
47962
49024
  return c;
@@ -47992,8 +49054,8 @@ function planExternalize(family, bound2, request) {
47992
49054
  plan6,
47993
49055
  bound2,
47994
49056
  "move",
47995
- `externalize ${id}: its specs into ${joined ? `the part "${joined.alias}"` : `a part at ${posix6(path45.relative(node.directory, dir))}, declared as "${id}"`} \u2014 a storage move; no reference, export or pin changes`,
47996
- { root: node.directory, call: "externalizeSubsystem", args: [id, posix6(path45.relative(node.directory, dir))] }
49057
+ `externalize ${id}: its specs into ${joined ? `the part "${joined.alias}"` : `a part at ${posix7(path45.relative(node.directory, dir))}, declared as "${id}"`} \u2014 a storage move; no reference, export or pin changes`,
49058
+ { root: node.directory, call: "externalizeSubsystem", args: [id, posix7(path45.relative(node.directory, dir))] }
47997
49059
  );
47998
49060
  plan6.notes.push("Source code is not moved: move the subsystem's code yourself if it should live beside its specs.");
47999
49061
  return plan6;
@@ -48002,7 +49064,7 @@ function planExternalize(family, bound2, request) {
48002
49064
  crossingsBack(plan6, family, bound2, subKey);
48003
49065
  checkSiblings(plan6, family, bound2, id, subKey, table);
48004
49066
  if (plan6.refusals.length > 0) return plan6;
48005
- edit3(plan6, bound2, "move", `externalize ${id} as a project: its specs into a member at ${posix6(path45.relative(node.directory, dir))}, declared as "${id}", then promoted`, { root: node.directory, call: "externalizeSubsystem", args: [id, posix6(path45.relative(node.directory, dir)), "project"] });
49067
+ edit3(plan6, bound2, "move", `externalize ${id} as a project: its specs into a member at ${posix7(path45.relative(node.directory, dir))}, declared as "${id}", then promoted`, { root: node.directory, call: "externalizeSubsystem", args: [id, posix7(path45.relative(node.directory, dir)), "project"] });
48006
49068
  edit3(plan6, id, "pin", `pin the new member's externals (its external for ${node.id ?? "this project"}, when its specs reference it)`, { root: dir, call: "pinExternals", args: [[]] });
48007
49069
  repinIntactConsumers2(plan6, family, bound2, [dir]);
48008
49070
  plan6.notes.push("Source code is not moved: move the subsystem's code into the member yourself.");
@@ -48212,6 +49274,7 @@ function write5(plan6, rehearsal) {
48212
49274
  for (const e of planned.filter((x) => x.write.call === "demoteMember")) {
48213
49275
  const demoted = writeOne3(rehearsal, e.write);
48214
49276
  for (const line of demoted?.consumersRestored ?? []) plan6.edits.push({ project: e.project, kind: "reference", detail: `consumers across the old boundary restored: ${line}` });
49277
+ for (const line of demoted?.signaturesRederived ?? []) plan6.edits.push({ project: e.project, kind: "reference", detail: `signature text re-derived \u2014 ${line}` });
48215
49278
  }
48216
49279
  for (const e of planned.filter((x) => x.write.call === "unpin")) writeOne3(rehearsal, e.write);
48217
49280
  }
@@ -48261,6 +49324,8 @@ function recordCrossing(plan6, bound2, member, result) {
48261
49324
  for (const line of crossed.exported) plan6.edits.push({ project: bound2, kind: "export", detail: `export ${line}` });
48262
49325
  for (const line of crossed.imported) plan6.edits.push({ project: bound2, kind: "external", detail: `import: ${line}` });
48263
49326
  for (const line of crossed.consumersDropped) plan6.edits.push({ project: bound2, kind: "reference", detail: `consumers across the boundary to ${member} dropped: ${line}` });
49327
+ for (const line of crossed.pathsRebased ?? []) plan6.edits.push({ project: member, kind: "reference", detail: `path re-expressed under the new root \u2014 ${line}` });
49328
+ for (const line of crossed.signaturesRederived ?? []) plan6.edits.push({ project: bound2, kind: "reference", detail: `signature text re-derived \u2014 ${line}` });
48264
49329
  for (const line of crossed.entriesDeclared ?? []) plan6.edits.push({ project: member, kind: "reference", detail: `entry declared for the callers left across the boundary \u2014 ${line}` });
48265
49330
  const topics = crossed.topicsCrossing ?? [];
48266
49331
  if (topics.length > 0) {
@@ -48298,11 +49363,11 @@ function writeOne3(rehearsal, w) {
48298
49363
 
48299
49364
  // src/migrations/transaction.ts
48300
49365
  var crypto9 = __toESM(require("crypto"));
48301
- var fs28 = __toESM(require("fs"));
49366
+ var fs29 = __toESM(require("fs"));
48302
49367
  var path46 = __toESM(require("path"));
48303
49368
 
48304
49369
  // src/migrations/transaction-files.ts
48305
- var fs27 = __toESM(require("fs"));
49370
+ var fs28 = __toESM(require("fs"));
48306
49371
  var os7 = __toESM(require("os"));
48307
49372
  var nodePath2 = __toESM(require("path"));
48308
49373
  var yaml4 = __toESM(require("js-yaml"));
@@ -48316,7 +49381,7 @@ function pause2(ms) {
48316
49381
  function removeTree(dir) {
48317
49382
  for (let attempt = 1; ; attempt++) {
48318
49383
  try {
48319
- fs27.rmSync(dir, { recursive: true, force: true });
49384
+ fs28.rmSync(dir, { recursive: true, force: true });
48320
49385
  return;
48321
49386
  } catch (e) {
48322
49387
  const code = e.code;
@@ -48326,20 +49391,20 @@ function removeTree(dir) {
48326
49391
  }
48327
49392
  }
48328
49393
  function writeDurably(file, bytes) {
48329
- fs27.mkdirSync(nodePath2.dirname(file), { recursive: true });
49394
+ fs28.mkdirSync(nodePath2.dirname(file), { recursive: true });
48330
49395
  const tmp = `${file}.tmp`;
48331
49396
  try {
48332
- const fd = fs27.openSync(tmp, "w");
49397
+ const fd = fs28.openSync(tmp, "w");
48333
49398
  try {
48334
- fs27.writeSync(fd, typeof bytes === "string" ? Buffer.from(bytes, "utf8") : bytes);
48335
- fs27.fsyncSync(fd);
49399
+ fs28.writeSync(fd, typeof bytes === "string" ? Buffer.from(bytes, "utf8") : bytes);
49400
+ fs28.fsyncSync(fd);
48336
49401
  } finally {
48337
- fs27.closeSync(fd);
49402
+ fs28.closeSync(fd);
48338
49403
  }
48339
- fs27.renameSync(tmp, file);
49404
+ fs28.renameSync(tmp, file);
48340
49405
  } catch (e) {
48341
49406
  try {
48342
- fs27.unlinkSync(tmp);
49407
+ fs28.unlinkSync(tmp);
48343
49408
  } catch {
48344
49409
  }
48345
49410
  throw e;
@@ -48349,18 +49414,18 @@ function writeDurably(file, bytes) {
48349
49414
  function syncDirectory(dir) {
48350
49415
  if (process.platform === "win32") return;
48351
49416
  try {
48352
- const fd = fs27.openSync(dir, "r");
49417
+ const fd = fs28.openSync(dir, "r");
48353
49418
  try {
48354
- fs27.fsyncSync(fd);
49419
+ fs28.fsyncSync(fd);
48355
49420
  } finally {
48356
- fs27.closeSync(fd);
49421
+ fs28.closeSync(fd);
48357
49422
  }
48358
49423
  } catch {
48359
49424
  }
48360
49425
  }
48361
49426
  function openRehearsal(id) {
48362
49427
  const dir = rehearsalDir(id);
48363
- fs27.mkdirSync(dir);
49428
+ fs28.mkdirSync(dir);
48364
49429
  return dir;
48365
49430
  }
48366
49431
  function dropRehearsal(id) {
@@ -48371,14 +49436,14 @@ function dropRehearsal(id) {
48371
49436
  }
48372
49437
  function saveJournal(journal) {
48373
49438
  const ignore = nodePath2.join(transactionsDir(journal.owner), ".gitignore");
48374
- if (!fs27.existsSync(ignore)) writeDurably(ignore, "*\n");
49439
+ if (!fs28.existsSync(ignore)) writeDurably(ignore, "*\n");
48375
49440
  writeDurably(nodePath2.join(transactionDir(journal.owner, journal.id), "journal.yaml"), yaml4.dump(journal, { noRefs: true, lineWidth: 200 }));
48376
49441
  }
48377
49442
  function readJournal(owner, id) {
48378
49443
  const file = nodePath2.join(transactionDir(owner, id), "journal.yaml");
48379
49444
  let text3;
48380
49445
  try {
48381
- text3 = fs27.readFileSync(file, "utf8");
49446
+ text3 = fs28.readFileSync(file, "utf8");
48382
49447
  } catch (e) {
48383
49448
  if (e.code === "ENOENT") return null;
48384
49449
  throw e;
@@ -48393,7 +49458,7 @@ function listJournals(owner) {
48393
49458
  const dir = transactionsDir(owner);
48394
49459
  let names;
48395
49460
  try {
48396
- names = fs27.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
49461
+ names = fs28.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
48397
49462
  } catch {
48398
49463
  return [];
48399
49464
  }
@@ -48410,26 +49475,26 @@ function listJournals(owner) {
48410
49475
  startedAt: ""
48411
49476
  });
48412
49477
  }
48413
- function putFile(owner, id, area, path49, bytes) {
48414
- writeDurably(locate(owner, id, area, path49), bytes);
49478
+ function putFile(owner, id, area, path50, bytes) {
49479
+ writeDurably(locate(owner, id, area, path50), bytes);
48415
49480
  }
48416
- function readFile(owner, id, area, path49) {
49481
+ function readFile(owner, id, area, path50) {
48417
49482
  try {
48418
- return fs27.readFileSync(locate(owner, id, area, path49));
49483
+ return fs28.readFileSync(locate(owner, id, area, path50));
48419
49484
  } catch (e) {
48420
49485
  if (e.code === "ENOENT") return null;
48421
49486
  throw e;
48422
49487
  }
48423
49488
  }
48424
- function locate(owner, id, area, path49) {
48425
- return nodePath2.join(transactionDir(owner, id), area, ...path49.split("/"));
49489
+ function locate(owner, id, area, path50) {
49490
+ return nodePath2.join(transactionDir(owner, id), area, ...path50.split("/"));
48426
49491
  }
48427
49492
  function close(owner, id) {
48428
49493
  removeTree(transactionDir(owner, id));
48429
49494
  const dir = transactionsDir(owner);
48430
49495
  let left;
48431
49496
  try {
48432
- left = fs27.readdirSync(dir);
49497
+ left = fs28.readdirSync(dir);
48433
49498
  } catch {
48434
49499
  return;
48435
49500
  }
@@ -48437,11 +49502,11 @@ function close(owner, id) {
48437
49502
  removeTree(dir);
48438
49503
  const wai = nodePath2.dirname(dir);
48439
49504
  try {
48440
- if (fs27.readdirSync(wai).length === 0) fs27.rmdirSync(wai);
49505
+ if (fs28.readdirSync(wai).length === 0) fs28.rmdirSync(wai);
48441
49506
  } catch {
48442
49507
  }
48443
49508
  try {
48444
- if (fs27.readdirSync(owner).length === 0) fs27.rmdirSync(owner);
49509
+ if (fs28.readdirSync(owner).length === 0) fs28.rmdirSync(owner);
48445
49510
  } catch {
48446
49511
  }
48447
49512
  }
@@ -48463,14 +49528,14 @@ var transactionStore = {
48463
49528
  listJournals(owner) {
48464
49529
  return listJournals(owner);
48465
49530
  },
48466
- putFile(owner, id, area, path49, bytes) {
48467
- putFile(owner, id, area, path49, bytes);
49531
+ putFile(owner, id, area, path50, bytes) {
49532
+ putFile(owner, id, area, path50, bytes);
48468
49533
  },
48469
- readFile(owner, id, area, path49) {
48470
- return readFile(owner, id, area, path49);
49534
+ readFile(owner, id, area, path50) {
49535
+ return readFile(owner, id, area, path50);
48471
49536
  },
48472
- locate(owner, id, area, path49) {
48473
- return locate(owner, id, area, path49);
49537
+ locate(owner, id, area, path50) {
49538
+ return locate(owner, id, area, path50);
48474
49539
  },
48475
49540
  close(owner, id) {
48476
49541
  close(owner, id);
@@ -48492,14 +49557,14 @@ var transactionRepository = {
48492
49557
  listJournals(owner) {
48493
49558
  return transactionStore.listJournals(owner);
48494
49559
  },
48495
- putFile(owner, id, area, path49, bytes) {
48496
- transactionStore.putFile(owner, id, area, path49, bytes);
49560
+ putFile(owner, id, area, path50, bytes) {
49561
+ transactionStore.putFile(owner, id, area, path50, bytes);
48497
49562
  },
48498
- readFile(owner, id, area, path49) {
48499
- return transactionStore.readFile(owner, id, area, path49);
49563
+ readFile(owner, id, area, path50) {
49564
+ return transactionStore.readFile(owner, id, area, path50);
48500
49565
  },
48501
- locate(owner, id, area, path49) {
48502
- return transactionStore.locate(owner, id, area, path49);
49566
+ locate(owner, id, area, path50) {
49567
+ return transactionStore.locate(owner, id, area, path50);
48503
49568
  },
48504
49569
  close(owner, id) {
48505
49570
  transactionStore.close(owner, id);
@@ -48562,7 +49627,7 @@ function commonAncestor(paths) {
48562
49627
  }
48563
49628
  return at;
48564
49629
  }
48565
- function diff(rehearsal) {
49630
+ function diff2(rehearsal) {
48566
49631
  return compare4(rehearsal);
48567
49632
  }
48568
49633
  function discard(rehearsal) {
@@ -48647,7 +49712,7 @@ function missingDirs(owner, entries) {
48647
49712
  for (const entry of entries.filter((e) => e.action === "create")) {
48648
49713
  const missing = [];
48649
49714
  let at = path46.dirname(liveOf(owner, entry.path));
48650
- while (!fs28.existsSync(at) && at !== path46.dirname(at)) {
49715
+ while (!fs29.existsSync(at) && at !== path46.dirname(at)) {
48651
49716
  missing.unshift(at);
48652
49717
  at = path46.dirname(at);
48653
49718
  }
@@ -48681,7 +49746,7 @@ function stageBytes(rehearsal, journal, entry) {
48681
49746
  function checkVolume(journal, entry, target) {
48682
49747
  const staged = transactionRepository.locate(journal.owner, journal.id, "staged", entry.path);
48683
49748
  let nearest = path46.dirname(target);
48684
- while (!fs28.existsSync(nearest) && nearest !== path46.dirname(nearest)) nearest = path46.dirname(nearest);
49749
+ while (!fs29.existsSync(nearest) && nearest !== path46.dirname(nearest)) nearest = path46.dirname(nearest);
48685
49750
  if (!sameVolume(staged, nearest)) {
48686
49751
  throw new StagingError(`cross-volume: ${target} lies on another volume than its staged copy; a rename across volumes is not atomic`);
48687
49752
  }
@@ -48765,7 +49830,7 @@ function recover(root, fix) {
48765
49830
  var coordinatorRoot = (journal) => path46.resolve(journal.owner, journal.coordinator);
48766
49831
  function journalAt(owner, id) {
48767
49832
  try {
48768
- const journal = fs28.existsSync(owner) ? transactionRepository.readJournal(owner, id) : null;
49833
+ const journal = fs29.existsSync(owner) ? transactionRepository.readJournal(owner, id) : null;
48769
49834
  return journal ? { ...journal, owner } : null;
48770
49835
  } catch {
48771
49836
  return null;
@@ -48797,7 +49862,7 @@ function resolveOne(journal) {
48797
49862
  for (const owner of [...owners.filter((o) => o !== coordinator.owner), coordinator.owner]) transactionRepository.close(owner, journal.id);
48798
49863
  return { ...base, owners, phase, action: "cleaned", detail: "it had committed; its transaction directories were removed" };
48799
49864
  }
48800
- const absent = phase === "staging" ? [] : owners.filter((o) => !fs28.existsSync(o));
49865
+ const absent = phase === "staging" ? [] : owners.filter((o) => !fs29.existsSync(o));
48801
49866
  if (absent.length > 0) {
48802
49867
  return { ...base, owners, phase, action: "refused", detail: `owner(s) out of reach: ${absent.join(", ")}` };
48803
49868
  }
@@ -48923,7 +49988,7 @@ function rehearse2(plan6) {
48923
49988
  const detail = e instanceof Error ? e.message : String(e);
48924
49989
  return { ...plan6, refusals: [...plan6.refusals, { code: `${plan6.request.verb}-refused`, project: "", detail }] };
48925
49990
  }
48926
- const changes = diff(rehearsal);
49991
+ const changes = diff2(rehearsal);
48927
49992
  const refused3 = [
48928
49993
  ...unwritableOwners(changes).map((o) => ({ code: "family-partial", project: o, detail: `the plan must write ${o}, which this request may not write` })),
48929
49994
  ...deniedSubsystems(changes)
@@ -48932,7 +49997,7 @@ function rehearse2(plan6) {
48932
49997
  discard(rehearsal);
48933
49998
  return { ...plan6, refusals: [...plan6.refusals, ...refused3] };
48934
49999
  }
48935
- return { ...plan6, rehearsal, changes, relock: relockOf(plan6, changes) };
50000
+ return { ...plan6, rehearsal, changes, relock: relockOf(plan6, changes, rehearsal) };
48936
50001
  }
48937
50002
  function movedOf(plan6) {
48938
50003
  const relocation = plan6.request.relocation;
@@ -49035,12 +50100,25 @@ function onCopy(rehearsal, run3) {
49035
50100
  const top = reach2.topRoot !== void 0 ? rehearsalRoot(rehearsal, reach2.topRoot) : rehearsal.directory;
49036
50101
  return runWithProjectBinding(rehearsalRoot(rehearsal, getProjectRoot()), { topRoot: top, parentReach: reach2.parentReach, ...reach2.narrowed ? { narrowed: true } : {} }, run3);
49037
50102
  }
49038
- function relockOf(plan6, changes) {
50103
+ function relockOf(plan6, changes, rehearsal) {
49039
50104
  const ended = new Set(changes.filter((c) => c.action === "delete" && c.path === ".wai/project.yaml").map((c) => path47.resolve(c.project)));
49040
50105
  const owners = [...new Set(changes.map((c) => path47.resolve(c.project)))].filter((o) => !ended.has(o));
49041
50106
  const moved = movedOf(plan6);
49042
50107
  const relocated = moved !== null && approvalRecord(moved.from) !== null ? [moved.to] : [];
49043
- if (plan6.request.verb !== "rename") return [...owners.filter((owner) => approvalRecord(owner) !== null), ...relocated];
50108
+ if (plan6.request.verb !== "rename") {
50109
+ const locked = owners.filter((owner) => approvalRecord(owner) !== null);
50110
+ const storageMove = plan6.request.verb === "externalize" && plan6.request.as !== "project";
50111
+ const stillApproved = (owner) => {
50112
+ if (!storageMove || !rehearsal) return false;
50113
+ try {
50114
+ const diff3 = diffAgainstApproval(rehearsalRoot(rehearsal, owner));
50115
+ return diff3 !== null && diff3.added.length + diff3.changed.length + diff3.removed.length === 0;
50116
+ } catch {
50117
+ return false;
50118
+ }
50119
+ };
50120
+ return [...locked.filter((owner) => !stillApproved(owner)), ...relocated];
50121
+ }
49044
50122
  const renamed2 = plan6.edits.find((e) => e.write?.call === "renameId")?.write?.root;
49045
50123
  const first = renamed2 !== void 0 ? path47.resolve(renamed2) : void 0;
49046
50124
  return [...owners.filter((o) => o === first), ...owners.filter((o) => o !== first)];
@@ -49086,7 +50164,34 @@ function run2(request, dryRun) {
49086
50164
  discard3(planned);
49087
50165
  return { plan: planned, applied: false, relock: [] };
49088
50166
  }
49089
- return apply2(planned);
50167
+ const report3 = apply2(planned);
50168
+ const made = report3.applied ? madeProjectRoot(report3) : null;
50169
+ if (made !== null) report3.sessionScaffold = scaffoldSession(made);
50170
+ return report3;
50171
+ }
50172
+ function madeProjectRoot(report3) {
50173
+ const { request, changes } = report3.plan;
50174
+ if (!(request.verb === "promote" || request.verb === "externalize" && request.as === "project")) return null;
50175
+ const created = changes.find((c) => c.action === "create" && c.path.replace(/\\/g, "/") === ".wai/specs/.index.yaml");
50176
+ const root = created?.project ?? null;
50177
+ return root !== null && fs30.existsSync(path48.join(root, ".wai", "project.yaml")) ? root : null;
50178
+ }
50179
+ function scaffoldSession(root) {
50180
+ try {
50181
+ return runWithProjectRoot(root, () => {
50182
+ const config = loadProjectConfig();
50183
+ const targets = config ? activeTargetTypes(config) : ["claude"];
50184
+ const written = [...reinjectLocalGuides(root, targets)];
50185
+ written.push(...exportSddSkills2(targets).destinations);
50186
+ if (targets.includes("claude")) {
50187
+ const server = registerProjectServer(root);
50188
+ if (server !== null) written.push(server);
50189
+ }
50190
+ return written.map((f) => path48.relative(root, f).replace(/\\/g, "/") || ".");
50191
+ });
50192
+ } catch (e) {
50193
+ return [`not set up (${e instanceof Error ? e.message : String(e)}): run \`wairon generate\` and \`wairon mcp install --backend claude\` there`];
50194
+ }
49090
50195
  }
49091
50196
  function pending2() {
49092
50197
  const found = recover3(getProjectRoot(), false);
@@ -49113,7 +50218,7 @@ function statusFamilyContext() {
49113
50218
  const members = listDirectChainedSubprojects(root);
49114
50219
  if (members.length > 0) {
49115
50220
  const described = members.map((m) => {
49116
- const rel2 = path48.relative(root, m.dir).split(path48.sep).join("/") || ".";
50221
+ const rel2 = path49.relative(root, m.dir).split(path49.sep).join("/") || ".";
49117
50222
  return `${m.alias} (${rel2}${m.form === "mount" ? ", legacy L1 mount" : ""})`;
49118
50223
  });
49119
50224
  lines.push(`Family: member projects declared here \u2014 ${described.join(", ")}.`);
@@ -49213,7 +50318,8 @@ function migrationView(report3, dryRun) {
49213
50318
  ...madeAt ? {
49214
50319
  newProject: {
49215
50320
  root: madeAt,
49216
- next: `The new project at ${madeAt} has no guide, skills or MCP registration of its own yet. The human runs, in that folder: \`wairon generate\` and \`wairon mcp install --backend claude\` (the CLI's \`wairon member promote\` / \`subsystem externalize --as project\` do this themselves). Its own session then designs it \u2014 its specs, L0 export table and project.yaml are written from its own root.`
50321
+ ...report3.sessionScaffold ? { sessionScaffold: report3.sessionScaffold } : {},
50322
+ next: report3.sessionScaffold && !report3.sessionScaffold.some((f) => f.startsWith("not set up")) ? `The new project at ${madeAt} is set up for its own sessions (its guide, skills and .mcp.json, listed under sessionScaffold), as the CLI's \`wairon member promote\` / \`subsystem externalize --as project\` set it up. A session opened in that folder designs it \u2014 its specs, L0 export table and project.yaml are written from its own root.` : `The new project at ${madeAt} has no guide, skills or MCP registration of its own yet. The human runs, in that folder: \`wairon generate\` and \`wairon mcp install --backend claude\` (the CLI's \`wairon member promote\` / \`subsystem externalize --as project\` do this themselves). Its own session then designs it \u2014 its specs, L0 export table and project.yaml are written from its own root.`
49217
50323
  }
49218
50324
  } : {},
49219
50325
  ...report3.outcome ? { outcome: { committed: report3.outcome.committed, restored: report3.outcome.restored, unrestored: report3.outcome.unrestored, ...report3.outcome.failure ? { failure: report3.outcome.failure } : {} } } : {},
@@ -49269,6 +50375,31 @@ var externalRemovalOutput = {
49269
50375
  refusal: import_zod12.z.string().optional().describe("Why nothing was removed, in one sentence."),
49270
50376
  ...staleServerOutput
49271
50377
  };
50378
+ var externalConsumersOutput = {
50379
+ consumers: import_zod12.z.array(import_zod12.z.object({
50380
+ project: import_zod12.z.string(),
50381
+ key: import_zod12.z.string(),
50382
+ directory: import_zod12.z.string(),
50383
+ alias: import_zod12.z.string(),
50384
+ section: import_zod12.z.enum(["externals", "members"]),
50385
+ names: import_zod12.z.array(import_zod12.z.string()),
50386
+ broken: import_zod12.z.array(import_zod12.z.string()).optional(),
50387
+ found: import_zod12.z.enum(["search"]).optional()
50388
+ })).describe("Each consumer with the alias and section it declares this project under, the public names its specs use, the names it still writes that this project no longer exports (broken), and found: search for one met in a searched folder outside the family."),
50389
+ ...staleServerOutput
50390
+ };
50391
+ var surfaceDiffOutput = {
50392
+ project: import_zod12.z.string(),
50393
+ against: import_zod12.z.string().describe("What the surface was compared with: the last committed approval, a named revision, or a saved snapshot file."),
50394
+ changes: import_zod12.z.array(import_zod12.z.object({
50395
+ kind: import_zod12.z.enum(["added", "removed", "renamed", "changed"]),
50396
+ name: import_zod12.z.string(),
50397
+ member: import_zod12.z.string().optional(),
50398
+ from: import_zod12.z.string().optional(),
50399
+ detail: import_zod12.z.string()
50400
+ })).describe("Every public name or contract method added, removed, renamed (traced) or changed in signature, sorted by name."),
50401
+ ...staleServerOutput
50402
+ };
49272
50403
  var externalUseChangeOutput = {
49273
50404
  alias: import_zod12.z.string(),
49274
50405
  written: import_zod12.z.boolean().describe("Whether .wai/project.yaml now carries the changed `use` (false on a refusal, a dry run and when nothing changed)."),
@@ -49481,6 +50612,22 @@ var specChangeReportOutput = {
49481
50612
  ),
49482
50613
  ...staleServerOutput
49483
50614
  };
50615
+ function statusNetworkLine() {
50616
+ try {
50617
+ const network = loadProjectConfig()?.network;
50618
+ if (network) {
50619
+ const description = typeof network === "object" && network.description ? ` ("${network.description}")` : "";
50620
+ return `Network: declared${description} \u2014 this project and every member below it form one isolated network boundary, and the network rules (GATEWAY_BYPASSED, ENTRY_UNPROVEN, ...) judge the design; sdd_get_network_flows lists who may reach what.
50621
+ `;
50622
+ }
50623
+ if (resolveChainingParent()) {
50624
+ return "Network: none declared here \u2014 a network the enclosing family declares is judged at its root, and sdd_get_network_flows reads it from there.\n";
50625
+ }
50626
+ return "Network: none declared \u2014 Portal entries are not narrowed by an isolated network (sdd_set_network declares one).\n";
50627
+ } catch {
50628
+ return "";
50629
+ }
50630
+ }
49484
50631
  function externalsSection(advised) {
49485
50632
  if (advised.length === 0) return "";
49486
50633
  const lines = advised.map((i) => `- [${i.code}] ${i.message}`);
@@ -49826,6 +50973,7 @@ var SPEC_WRITE_TOOLS = /* @__PURE__ */ new Set([
49826
50973
  "sdd_rename_method",
49827
50974
  "sdd_rename_type",
49828
50975
  "sdd_rename_field",
50976
+ "sdd_rename_param",
49829
50977
  "sdd_add_component",
49830
50978
  "sdd_define_interface",
49831
50979
  "sdd_set_endpoints",
@@ -50336,7 +51484,7 @@ ${renderChangeReport(report3)}`,
50336
51484
  server,
50337
51485
  "sdd_rename_method",
50338
51486
  {
50339
- description: "Rename a contract method and retarget every reference to it in the bound tree. The method moves on every interface of the component that declares it \u2014 its name, and the name inside its signature \u2014 and on the implementations of those contracts, carrying narrative, sourcePath, symbol, detail, intent and findings unchanged; an implementation that declared no symbol is pinned to the old name unless pinSymbol is false, so the function it already binds to keeps binding. Narrative call, register and dispatch steps naming this component and method, dispatch-table bindings and lifecycle entrypoints are retargeted, and so are the findings keyed on it: a lint allow at the method on a spec it moved in or covering `<component>.<method>`, and every debt-register entry (.wai/project.yaml rules.conformance.carried) keyed the same way \u2014 rewritten in place with every comment and all other formatting kept (a register that cannot be rewritten that precisely is refused before the first write). Prose is never rewritten and a gRPC endpoint binding keeps its wire method \u2014 renaming a contract method must not silently rename an RPC; both are reported as mentions. Refuses, writing nothing: a component that does not exist (component-missing), one inside a chained subproject (chained-component \u2014 rename its method from that project's own root), a new name that is not an identifier in the method casing of the tree \u2014 the configured rules.naming.methods, else the convention of its targetLanguage: snake_case for Rust or Python, camelCase for TypeScript (invalid-name), a method the component does not declare (method-missing), and a name a moving contract already declares (name-taken). Returns the specs the method moved in, the specs retargeted (lint allows included), the specs whose prose still names it, the pinned symbol when one was set, and `carried`: each register edit.",
51487
+ description: "Rename a contract method and retarget every reference to it in the bound tree. The method moves on every interface of the component that declares it \u2014 its name, and the name inside its signature \u2014 and on the implementations of those contracts, carrying narrative, sourcePath, symbol, detail, intent and findings unchanged; an implementation that declared no symbol is pinned to the old name unless pinSymbol is false, so the function it already binds to keeps binding. Narrative call, register and dispatch steps naming this component and method, dispatch-table bindings and lifecycle entrypoints are retargeted, and so are the findings keyed on it: a lint allow at the method on a spec it moved in or covering `<component>.<method>`, and every debt-register entry (.wai/project.yaml rules.conformance.carried) keyed the same way \u2014 rewritten in place with every comment and all other formatting kept (a register that cannot be rewritten that precisely is refused before the first write). Prose is never rewritten and a gRPC endpoint binding keeps its wire method \u2014 renaming a contract method must not silently rename an RPC; both are reported as mentions. Refuses, writing nothing: a component that does not exist (component-missing), one inside a chained subproject (chained-component \u2014 rename its method from that project's own root), a new name that is not an identifier in the method casing of the tree \u2014 the configured rules.naming.methods, else the convention of its targetLanguage: snake_case for Rust or Python, camelCase for TypeScript; for a component one of whose contracts implements another project's extension point, any identifier, since the producer names those methods (invalid-name), a method the component does not declare (method-missing), and a name a moving contract already declares (name-taken). Returns the specs the method moved in, the specs retargeted (lint allows included), the specs whose prose still names it, the pinned symbol when one was set, and `carried`: each register edit.",
50340
51488
  inputSchema: {
50341
51489
  id: import_zod12.z.string().describe("The component whose method is renamed (namespaced if needed)"),
50342
51490
  method: import_zod12.z.string().describe("The method name as it stands"),
@@ -50393,6 +51541,26 @@ ${renderChangeReport(report3)}`,
50393
51541
  }
50394
51542
  }
50395
51543
  );
51544
+ reg(
51545
+ server,
51546
+ "sdd_rename_param",
51547
+ {
51548
+ description: "Rename a parameter of a contract method. The parameter moves on every interface of the component that declares the method, keeping its place, type, description and optionality, and its old name joins the parameter's rename trace (previousNames), which the design export shows as `formerly` so a generator or consumer can tell the rename from a signature change; each signature is re-derived from the params, a method whose signatureFrom names this one follows by itself, and an HTTP endpoint path placeholder `{old}` on the method's own binding is respelled (the URL a caller sends is unchanged). Prose is never rewritten. Refuses, writing nothing: a component that does not exist (component-missing), one inside a chained subproject (chained-component), a method no contract of the component declares (method-missing), a parameter the method does not declare or takes from a signatureFrom (param-missing), a new name that is not an identifier, or not in rules.naming.variables when that is set and the component implements no extension point (invalid-name), and a name another parameter of the method holds (name-taken) or retired (name-retired). Returns the component, method, from and to, the interfaces it moved in and the placeholders respelled. The approval reads the contract as changed until the next `wairon lock`, as with any edit.",
51549
+ inputSchema: {
51550
+ id: import_zod12.z.string().describe("The component whose contract method's parameter is renamed (namespaced if needed)"),
51551
+ method: import_zod12.z.string().describe("The contract method"),
51552
+ param: import_zod12.z.string().describe("The parameter's name as it stands"),
51553
+ newName: import_zod12.z.string().describe("Its new name: an identifier")
51554
+ }
51555
+ },
51556
+ ({ id, method, param, newName }) => {
51557
+ try {
51558
+ return json(renameParam(qualifiedComponentId(id), method, param, newName));
51559
+ } catch (e) {
51560
+ return errText(String(e));
51561
+ }
51562
+ }
51563
+ );
50396
51564
  reg(
50397
51565
  server,
50398
51566
  "sdd_move_methods",
@@ -51086,7 +52254,7 @@ ${testsBlock}` : ""}`, deletion);
51086
52254
  memberDepth,
51087
52255
  approvals
51088
52256
  });
51089
- const family = statusFamilyContext();
52257
+ const family = statusFamilyContext() + statusNetworkLine();
51090
52258
  const verdict = approvalVerdict(approvals);
51091
52259
  return text2(`${banner}${family}${report3.text}${verdict.text}${externalsSection(adviseExternals())}`);
51092
52260
  } catch (e) {
@@ -51210,6 +52378,48 @@ ${testsBlock}` : ""}`, deletion);
51210
52378
  }
51211
52379
  }
51212
52380
  );
52381
+ reg(
52382
+ server,
52383
+ "sdd_list_consumers",
52384
+ {
52385
+ description: `Who consumes the bound project \u2014 the producer's answer to "who breaks if I change my surface": every project of the family in reach that declares it (as an external or a member), and, with search, every project root in (or directly under) the named folders that declares it as an external by a path source reaching it or a git source naming its id \u2014 a sibling checkout outside the family. Each with the alias, the public names its specs use, and the names it still writes that this project no longer exports. A read: writes nothing.`,
52386
+ inputSchema: {
52387
+ search: import_zod12.z.array(import_zod12.z.string()).optional().describe("Folders to scan for consumers outside the family (e.g. the folder holding the sibling checkouts), relative to the bound project root or absolute")
52388
+ },
52389
+ outputSchema: externalConsumersOutput
52390
+ },
52391
+ ({ search }) => {
52392
+ try {
52393
+ const root = getProjectRoot();
52394
+ const consumers = listConsumers3(search?.map((d) => path49.resolve(root, d)));
52395
+ const lines = consumers.length ? consumers.map((c) => `${c.project} (${c.section}.${c.alias})${c.found ? ` [${c.directory}]` : ""}: ${c.names.length ? c.names.join(", ") : "declares it, uses no name yet"}${c.broken?.length ? ` \u2014 still writes ${c.broken.join(", ")} (no longer exported)` : ""}`) : [`No project ${search?.length ? "of the family in reach, nor in the searched folders," : "of the family in reach"} consumes this project.${search?.length ? "" : " Pass search with the folder holding sibling checkouts to look outside the family."}`];
52396
+ return structured(lines.join("\n"), { consumers });
52397
+ } catch (e) {
52398
+ return errMessage(e);
52399
+ }
52400
+ }
52401
+ );
52402
+ reg(
52403
+ server,
52404
+ "sdd_surface_diff",
52405
+ {
52406
+ description: "The bound project's public-surface changelog: its export table now against the same table at its last committed approval (the last commit that changed .wai/lock.json) \u2014 or at a named git revision, or in a saved native surface snapshot file \u2014 as names and contract methods added, removed, renamed (from the rename trace) and changed in signature. What a producer writes release notes from and weighs a breaking change by (pair it with sdd_list_consumers). A read: writes nothing.",
52407
+ inputSchema: {
52408
+ against: import_zod12.z.string().optional().describe("A git revision (branch, tag, commit) or a saved native surface snapshot file to compare with; the last committed approval when omitted")
52409
+ },
52410
+ outputSchema: surfaceDiffOutput
52411
+ },
52412
+ ({ against }) => {
52413
+ try {
52414
+ const diff3 = diffSurface(against);
52415
+ const lines = diff3.changes.length ? diff3.changes.map((c) => `${c.kind} ${c.member ? `${c.name}.${c.member}` : c.name} \u2014 ${c.detail}`) : ["no change \u2014 every exported name, method and signature is as it was"];
52416
+ return structured(`Public surface of "${diff3.project}" against ${diff3.against}:
52417
+ ${lines.join("\n")}`, { ...diff3 });
52418
+ } catch (e) {
52419
+ return errMessage(e);
52420
+ }
52421
+ }
52422
+ );
51213
52423
  reg(
51214
52424
  server,
51215
52425
  "sdd_get_network_flows",
@@ -51478,6 +52688,7 @@ function loadProjectConfig3() {
51478
52688
  SwitchCaseSchema,
51479
52689
  SystemPublicInterfaceSchema,
51480
52690
  SystemSpecSchema,
52691
+ TECHNOLOGY_PACKAGES,
51481
52692
  TargetConfigSchema,
51482
52693
  TechnologySchema,
51483
52694
  TemplateNotFoundError,
@@ -51508,7 +52719,9 @@ function loadProjectConfig3() {
51508
52719
  approvalKeyIn,
51509
52720
  approvalRecord,
51510
52721
  approvalVerdict,
52722
+ approvedRevision,
51511
52723
  approvedStatuses,
52724
+ asSchemaReads,
51512
52725
  assertContainedProjectPath,
51513
52726
  assertProjectInitialized,
51514
52727
  attachRetiredMounts,
@@ -51756,6 +52969,7 @@ function loadProjectConfig3() {
51756
52969
  reexpress,
51757
52970
  registerExporter,
51758
52971
  registerPackRef,
52972
+ registerProjectServer,
51759
52973
  reinjectLocalGuides,
51760
52974
  relationHealth,
51761
52975
  removeDomain,
@@ -51768,6 +52982,7 @@ function loadProjectConfig3() {
51768
52982
  renameField,
51769
52983
  renameId,
51770
52984
  renameMethod,
52985
+ renameParam,
51771
52986
  renameType,
51772
52987
  renderDiagram,
51773
52988
  repairForeignStepFields,
@@ -51824,8 +53039,10 @@ function loadProjectConfig3() {
51824
53039
  storedMethodSignature,
51825
53040
  storedTypeMethod,
51826
53041
  subsystemDesignView,
53042
+ surfaceChanges,
51827
53043
  syncContextFiles,
51828
53044
  technologyName,
53045
+ technologyPackages,
51829
53046
  technologyTokens,
51830
53047
  toMarkdown,
51831
53048
  transportExportKind,