@wairon/cli 5.1.1-dev.11 → 5.1.1-dev.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -65,7 +65,7 @@ var init_defaults = __esm({
65
65
  copilot: ".github/prompts",
66
66
  codex: ".codex/agents"
67
67
  };
68
- WAIRON_VERSION = "5.1.1-dev.11";
68
+ WAIRON_VERSION = "5.1.1-dev.13";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  ARCHITECT_AGENT_ID = "agent-architect";
71
71
  ARCHITECT_TEMPLATE_ID = "architect";
@@ -3554,17 +3554,43 @@ function isExternalNamespaceRef(ctx, ref) {
3554
3554
  if (sep9 === -1) return false;
3555
3555
  return !ctx.subsystemIds.has(ref.slice(0, sep9));
3556
3556
  }
3557
+ function isProvidedBy(snapshot, provider) {
3558
+ return snapshot.projectName === provider || snapshot.projectName.split("::").pop() === provider;
3559
+ }
3560
+ function sameContract(a, b) {
3561
+ return a.component.split("::").pop() === b.component.split("::").pop() && JSON.stringify(a.methods) === JSON.stringify(b.methods) && JSON.stringify(a.dispatch ?? []) === JSON.stringify(b.dispatch ?? []);
3562
+ }
3557
3563
  function resolveSurfaceRef(ctx, ref, fromSubsystem) {
3558
- const local = ref.split("::").filter((seg) => seg && seg !== "super").pop();
3559
- if (!local) return null;
3564
+ const segments = ref.split("::").filter((seg) => seg && seg !== "super");
3565
+ const local = segments.pop();
3566
+ if (!local) return { kind: "unresolved" };
3567
+ const provider = segments.pop();
3560
3568
  const mountPools = fromSubsystem ? enclosingMounts(ctx, fromSubsystem).reverse().map((ns) => ctx.mountSurfaceSnapshots.find((m) => m.namespace === ns)?.snapshots ?? []) : [];
3561
3569
  for (const pool of [...mountPools, ctx.surfaceSnapshots]) {
3570
+ const candidates = [];
3562
3571
  for (const snapshot of pool) {
3572
+ if (provider !== void 0 && !isProvidedBy(snapshot, provider)) continue;
3563
3573
  const entry = snapshot.interfaces.find((e) => e.component === local || e.id === local);
3564
- if (entry) return { snapshot, entry };
3574
+ if (entry) candidates.push({ snapshot, entry });
3575
+ }
3576
+ if (candidates.length === 0) continue;
3577
+ const [first] = candidates;
3578
+ if (candidates.every((c) => sameContract(c.entry, first.entry))) {
3579
+ return { kind: "resolved", ...first };
3565
3580
  }
3581
+ return { kind: "ambiguous", providers: [...new Set(candidates.map((c) => c.snapshot.projectName))] };
3566
3582
  }
3567
- return null;
3583
+ return { kind: "unresolved" };
3584
+ }
3585
+ function reportAmbiguousSurfaceRef(ctx, subject, ref, providers, specId, isDraftContext) {
3586
+ const local = ref.split("::").filter((seg) => seg && seg !== "super").pop() ?? ref;
3587
+ ctx.addIssue(
3588
+ "error",
3589
+ "SURFACE_REF_AMBIGUOUS",
3590
+ `${subject} cross-tree component "${ref}", which the surface snapshots of ${providers.map((p) => `"${p}"`).join(", ")} expose with different contracts \u2014 the reference matches more than one declared contract, so none of them can judge it. Name the provider it means (super::<provider>::${local}), or remove the snapshot that no longer applies.`,
3591
+ specId,
3592
+ isDraftContext
3593
+ );
3568
3594
  }
3569
3595
  function enclosingMounts(ctx, subsystemId) {
3570
3596
  const mounts = [];
@@ -3655,6 +3681,7 @@ var init_contracts = __esm({
3655
3681
  { code: "INVALID_TARGET_COMPONENT_REFERENCE", defaultSeverity: "error", summary: "Call/register step targets a non-existent component" },
3656
3682
  { code: "CROSS_TREE_REF_UNRESOLVED", defaultSeverity: "warning", summary: "Cross-tree reference (super::/:: form) with no surface snapshot covering it \u2014 only the parent project can verify it" },
3657
3683
  { code: "SURFACE_REF_NOT_EXPOSED", defaultSeverity: "error", summary: "Cross-tree reference resolves to a surface snapshot that does not expose the called method/capability" },
3684
+ { code: "SURFACE_REF_AMBIGUOUS", defaultSeverity: "error", summary: "Cross-tree call/dispatch/register target matched by surface snapshots of several providers with different contracts" },
3658
3685
  { code: "UNDECLARED_DEPENDENCY_CALL", defaultSeverity: "error", summary: "Call step targets a component the caller does not depend on or own" },
3659
3686
  { code: "INVALID_TARGET_METHOD_REFERENCE", defaultSeverity: "error", summary: "Call step targets a method not on any target interface" },
3660
3687
  { code: "NARRATIVE_SEMANTIC_UNBACKED", defaultSeverity: "warning", summary: "Narrative asserts a guarantee the called contract does not declare" }
@@ -3709,7 +3736,18 @@ var init_contracts = __esm({
3709
3736
  if (!dispatchTarget) {
3710
3737
  if (isCrossTreeForm || isCollapsedForm) {
3711
3738
  const resolved = resolveSurfaceRef(ctx, step.targetComponent, fromSubsystem);
3712
- if (resolved) {
3739
+ if (resolved.kind === "ambiguous") {
3740
+ reportAmbiguousSurfaceRef(
3741
+ ctx,
3742
+ `Method "${implMethod.name}" in implementation "${impl.id}" dispatches (step ${step.stepNumber}) through`,
3743
+ step.targetComponent,
3744
+ resolved.providers,
3745
+ impl.id,
3746
+ isDraftCtx
3747
+ );
3748
+ continue;
3749
+ }
3750
+ if (resolved.kind === "resolved") {
3713
3751
  if (step.capability && !(resolved.entry.dispatch ?? []).some((b) => b.capability === step.capability)) {
3714
3752
  ctx.addIssue(
3715
3753
  "error",
@@ -3769,7 +3807,18 @@ var init_contracts = __esm({
3769
3807
  if (!targetComp) {
3770
3808
  if (isCrossTreeForm || isCollapsedForm) {
3771
3809
  const resolved = resolveSurfaceRef(ctx, step.targetComponent, fromSubsystem);
3772
- if (resolved) {
3810
+ if (resolved.kind === "ambiguous") {
3811
+ reportAmbiguousSurfaceRef(
3812
+ ctx,
3813
+ `Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" (step ${step.stepNumber}) on`,
3814
+ step.targetComponent,
3815
+ resolved.providers,
3816
+ impl.id,
3817
+ isDraftCtx
3818
+ );
3819
+ continue;
3820
+ }
3821
+ if (resolved.kind === "resolved") {
3773
3822
  const surfaceMethod = resolved.entry.methods.find((m) => m.name === step.targetMethod);
3774
3823
  if (!surfaceMethod) {
3775
3824
  ctx.addIssue(
@@ -5284,6 +5333,7 @@ var init_stereotype_deps = __esm({
5284
5333
  codes: [
5285
5334
  { code: "INVALID_DEPENDENCY_REFERENCE", defaultSeverity: "error", summary: "dependsOn names a non-existent component" },
5286
5335
  { code: "CROSS_TREE_REF_UNRESOLVED", defaultSeverity: "warning", summary: "Cross-tree dependsOn (super::/:: form) with no surface snapshot covering it" },
5336
+ { code: "SURFACE_REF_AMBIGUOUS", defaultSeverity: "error", summary: "Cross-tree dependsOn matched by surface snapshots of several providers with different contracts" },
5287
5337
  { code: "CROSS_SUBSYSTEM_NON_ADAPTER", defaultSeverity: "error", summary: "Non-Adapter component crossing a subsystem boundary" },
5288
5338
  { code: "CROSS_SUBSYSTEM_PRIVATE_ACCESS", defaultSeverity: "error", summary: "Cross-subsystem dependency on an unpublished component" },
5289
5339
  { code: "CROSS_SUBSYSTEM_TARGET_NON_PORTAL", defaultSeverity: "error", summary: "Cross-subsystem hop entering through a non-Portal" },
@@ -5307,7 +5357,11 @@ var init_stereotype_deps = __esm({
5307
5357
  const external = isExternalNamespaceRef(ctx, depId);
5308
5358
  if (external || isCollapsedCrossTreeRef(ctx, depId, comp.subsystem)) {
5309
5359
  const resolved = resolveSurfaceRef(ctx, depId, comp.subsystem);
5310
- if (resolved) {
5360
+ if (resolved.kind === "ambiguous") {
5361
+ reportAmbiguousSurfaceRef(ctx, `Component "${comp.id}" depends on`, depId, resolved.providers, comp.id, isDraftCtx);
5362
+ continue;
5363
+ }
5364
+ if (resolved.kind === "resolved") {
5311
5365
  if (comp.componentType !== "Adapter") {
5312
5366
  ctx.addIssue(
5313
5367
  "error",
@@ -5399,12 +5453,12 @@ var init_stereotype_deps = __esm({
5399
5453
  }
5400
5454
  }
5401
5455
  if (comp.componentType === "Specialist") {
5402
- const forbiddenTypes = ["Portal", "Observer", "Orchestrator", "Store", "Supervisor"];
5456
+ const forbiddenTypes = ["Portal", "Observer", "Orchestrator", "Store", "Registry", "Supervisor", "Actor"];
5403
5457
  if (forbiddenTypes.includes(depComp.componentType)) {
5404
5458
  ctx.addIssue(
5405
5459
  "error",
5406
5460
  "ARCHITECTURE_VIOLATION_SPECIALIST_DEP",
5407
- `Architectural violation: Specialist component "${comp.id}" cannot depend on "${depComp.componentType}" component "${depComp.id}". Specialists are narrow capabilities \u2014 they may use Repositories, Indexes, and Adapters, but not Orchestrators, Supervisors, Stores, Portals, or Observers.` + (depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : ""),
5461
+ `Architectural violation: Specialist component "${comp.id}" cannot depend on "${depComp.componentType}" component "${depComp.id}". Specialists are pure capabilities \u2014 they may use Repository facades, Indexes, and Adapters, but never workflow/runtime blocks (Orchestrators, Supervisors, Actors) and never persistence directly (Stores, Registries \u2014 all storage, even in-memory, is reached through a Repository facade), nor Portals or Observers.` + (depComp.componentType === "Store" ? storeResolutionHint(comp.componentType, depComp.id) : ""),
5408
5462
  comp.id,
5409
5463
  isDraftCtx || ctx.isComponentDraft(depComp.id)
5410
5464
  );
@@ -8676,7 +8730,7 @@ function buildRuleContext(opts) {
8676
8730
  for (const i of interfaces) collectAllows(i.id, i.lint);
8677
8731
  for (const im of implementations) collectAllows(im.id, im.lint);
8678
8732
  for (const t of types) collectAllows(t.id, t.lint);
8679
- const knownIssueCodes = /* @__PURE__ */ new Set([
8733
+ const knownIssueCodes2 = /* @__PURE__ */ new Set([
8680
8734
  ...[...SDD_RULES, ...extensions.rules].flatMap((r) => r.codes.map((c) => c.code)),
8681
8735
  // Declarative assertions bring their own namespaced codes — lint.allow
8682
8736
  // and severity overrides treat them exactly like builtins.
@@ -8739,7 +8793,7 @@ function buildRuleContext(opts) {
8739
8793
  mountSurfaceSnapshots: opts.mountSurfaceSnapshots ?? [],
8740
8794
  codeModel: opts.codeModel ?? emptyCodeModel(),
8741
8795
  lintAllows,
8742
- knownIssueCodes,
8796
+ knownIssueCodes: knownIssueCodes2,
8743
8797
  addIssue
8744
8798
  };
8745
8799
  }
@@ -9009,8 +9063,8 @@ function doctrineIdentity(doctrine, gate) {
9009
9063
  )
9010
9064
  };
9011
9065
  }
9012
- function hashGateState(doctrine, gate = {}) {
9013
- const payload = { tree: loadTree(), doctrine: doctrineIdentity(doctrine, gate) };
9066
+ function hashGateState(doctrine, inputs, gate = {}) {
9067
+ const payload = { tree: loadTree(), doctrine: doctrineIdentity(doctrine, gate), inputs: [...inputs].sort() };
9014
9068
  const digest2 = crypto2.createHash("sha256").update(canonicalize(payload)).digest("hex");
9015
9069
  return { algorithm: GATE_ALGORITHM, digest: digest2 };
9016
9070
  }
@@ -9041,7 +9095,7 @@ var init_statehash = __esm({
9041
9095
  init_specs2();
9042
9096
  init_rules();
9043
9097
  CONTENT_ALGORITHM = "sha256";
9044
- GATE_ALGORITHM = "sha256+doctrine";
9098
+ GATE_ALGORITHM = "sha256+doctrine+inputs";
9045
9099
  }
9046
9100
  });
9047
9101
 
@@ -14015,6 +14069,9 @@ var MODEL = __MODEL_JSON__;
14015
14069
  function addRule(rule) {
14016
14070
  ruleSet.push(rule);
14017
14071
  }
14072
+ function listRules() {
14073
+ return ruleSet;
14074
+ }
14018
14075
  function registerBuiltinRules() {
14019
14076
  ruleSet = [];
14020
14077
  for (const rule of SDD_RULES) addRule(rule);
@@ -14029,6 +14086,9 @@ function ruleSequence() {
14029
14086
  function specScopedRules() {
14030
14087
  return ruleSequence().filter((r) => r.scope === "spec");
14031
14088
  }
14089
+ function knownIssueCodes() {
14090
+ return listRules().flatMap((r) => r.codes);
14091
+ }
14032
14092
  var ruleSet;
14033
14093
  var init_repository = __esm({
14034
14094
  "src/core/rules/repository.ts"() {
@@ -14680,15 +14740,18 @@ function importSurface(sourcePath, origin) {
14680
14740
  saveSnapshot(snapshot);
14681
14741
  return snapshot;
14682
14742
  }
14683
- function pinFamilySurfaces() {
14684
- const parent = resolveChainingParent();
14685
- if (!parent) return null;
14686
- const childRoot = getProjectRoot();
14687
- const projected = runWithProjectRoot(parent.parentRoot, () => {
14743
+ function projectFamilySurfaces(parent) {
14744
+ return runWithProjectRoot(parent.parentRoot, () => {
14688
14745
  invalidateSpecCache();
14689
14746
  const siblings = loadSubsystemSpecs().filter((s) => !s.id.includes("::") && s.id !== parent.subsystemId);
14690
14747
  return [projectChildSurface(), ...siblings.map((s) => projectSubsystemSurface(s.id))];
14691
14748
  });
14749
+ }
14750
+ function pinFamilySurfaces() {
14751
+ const parent = resolveChainingParent();
14752
+ if (!parent) return null;
14753
+ const childRoot = getProjectRoot();
14754
+ const projected = projectFamilySurfaces(parent);
14692
14755
  const before = new Map(listSnapshots(childRoot).map((s) => [s.projectName, surfaceContentKey(s)]));
14693
14756
  const changed = [];
14694
14757
  for (const snapshot of projected) {
@@ -14699,17 +14762,21 @@ function pinFamilySurfaces() {
14699
14762
  }
14700
14763
  return changed;
14701
14764
  }
14702
- function computeParentStateId(parentRoot) {
14703
- return computeStateIdAt(parentRoot);
14765
+ function projectedFamilyContent(parent) {
14766
+ try {
14767
+ return new Map(projectFamilySurfaces(parent).map((s) => [s.projectName, surfaceContentKey(s)]));
14768
+ } catch {
14769
+ return null;
14770
+ }
14704
14771
  }
14705
14772
  function listExternalInterfaces() {
14706
14773
  const snapshots = listSnapshots();
14707
14774
  const chainingParent = resolveChainingParent();
14708
- const parentStateId = chainingParent ? computeParentStateId(chainingParent.parentRoot) : null;
14775
+ const projected = chainingParent ? projectedFamilyContent(chainingParent) : null;
14709
14776
  return snapshots.map((snapshot) => {
14710
14777
  const generated = snapshot.origin === "generated";
14711
14778
  const sourceKind = !generated ? "foreign" : snapshot.projectName.includes("::") ? "sibling" : "parent";
14712
- const freshness = generated && parentStateId ? snapshot.stateId === parentStateId ? "fresh" : "stale" : "unverifiable";
14779
+ const freshness = generated && projected ? projected.get(snapshot.projectName) === surfaceContentKey(snapshot) ? "fresh" : "stale" : "unverifiable";
14713
14780
  return {
14714
14781
  projectName: snapshot.projectName,
14715
14782
  origin: snapshot.origin,
@@ -14848,7 +14915,7 @@ var init_approval = __esm({
14848
14915
  // src/core/validation.ts
14849
14916
  var validation_exports = {};
14850
14917
  __export(validation_exports, {
14851
- listRules: () => listRules,
14918
+ listRules: () => listRules2,
14852
14919
  validateAsComplete: () => validateAsComplete,
14853
14920
  validateProjectConfig: () => validateProjectConfig,
14854
14921
  validateRegistry: () => validateRegistry,
@@ -14861,6 +14928,32 @@ function projectPackSelections() {
14861
14928
  return [];
14862
14929
  }
14863
14930
  }
14931
+ function issueKey(issue2) {
14932
+ return `${issue2.code}|${issue2.specId ?? ""}`;
14933
+ }
14934
+ function worstByKey(issues) {
14935
+ const worst = /* @__PURE__ */ new Map();
14936
+ for (const issue2 of issues) {
14937
+ const key = issueKey(issue2);
14938
+ const seen = worst.get(key);
14939
+ if (!seen || SEVERITY_RANK[issue2.severity] > SEVERITY_RANK[seen]) worst.set(key, issue2.severity);
14940
+ }
14941
+ return worst;
14942
+ }
14943
+ function stricterSeverities(parent, child) {
14944
+ const fromParent = parent?.sddRuleSeverity ?? {};
14945
+ const fromChild = child?.sddRuleSeverity ?? {};
14946
+ const codes = /* @__PURE__ */ new Set([...Object.keys(fromParent), ...Object.keys(fromChild)]);
14947
+ if (codes.size === 0) return parent ?? child;
14948
+ const defaults = new Map(knownIssueCodes().map((rc) => [rc.code, rc.defaultSeverity]));
14949
+ const merged = {};
14950
+ for (const code of codes) {
14951
+ const p = fromParent[code] ?? defaults.get(code);
14952
+ const c = fromChild[code] ?? defaults.get(code);
14953
+ merged[code] = p === void 0 || c === void 0 ? p ?? c : SEVERITY_RANK[p] >= SEVERITY_RANK[c] ? p : c;
14954
+ }
14955
+ return { ...parent ?? child, sddRuleSeverity: merged };
14956
+ }
14864
14957
  function issue(severity, code, message, agentId, specId) {
14865
14958
  return { severity, code, message, agentId, specId };
14866
14959
  }
@@ -14942,7 +15035,7 @@ function validateProjectConfig(config) {
14942
15035
  issues
14943
15036
  };
14944
15037
  }
14945
- function listRules() {
15038
+ function listRules2() {
14946
15039
  const extensions = loadProjectExtensions();
14947
15040
  registerBuiltinRules();
14948
15041
  registerPackRules(extensions.rules);
@@ -15042,21 +15135,22 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
15042
15135
  for (const rule of ruleSequence()) {
15043
15136
  rule.check(ctx);
15044
15137
  }
15045
- const uncovered = (i) => SUBPROJECT_REFERENCE_CODES.has(i.code) && !i.surfaceResolved;
15046
- const chainingParent = crossTree !== "off" && issues.some(uncovered) ? findChainingParent(getProjectRoot()) : null;
15047
- const resolution = chainingParent ? resolveThroughParent(getProjectRoot(), treatAllAsComplete) : null;
15138
+ const unresolved = (i) => RESOLUTION_FAILURE_CODES.has(i.code) && !i.surfaceResolved;
15139
+ const chainingParent = crossTree !== "off" && issues.some(unresolved) ? resolveChainingParent() : null;
15140
+ const resolution = chainingParent ? resolveThroughParent(getProjectRoot(), treatAllAsComplete, rules) : null;
15048
15141
  if (resolution) {
15049
- const parentSeverity = /* @__PURE__ */ new Map();
15050
- for (const i of resolution.issues) {
15051
- const key = `${i.code}|${i.specId ?? ""}`;
15052
- if (i.severity === "error" || !parentSeverity.has(key)) parentSeverity.set(key, i.severity);
15053
- }
15054
- const rejudged = (i) => {
15055
- const judged = parentSeverity.get(`${i.code}|${i.specId ?? ""}`);
15056
- return judged === "error" || judged === "warning" && i.severity === "warning";
15057
- };
15058
- const kept = issues.filter((i) => !uncovered(i) && !(i.surfaceResolved && rejudged(i)));
15059
- const merged = dedupeIssues([...kept, ...resolution.issues]);
15142
+ const judgedByParent = worstByKey(resolution.issues);
15143
+ const kept = issues.filter((i) => {
15144
+ const judged = judgedByParent.get(issueKey(i));
15145
+ if (judged !== void 0) return SEVERITY_RANK[judged] < SEVERITY_RANK[i.severity];
15146
+ return !unresolved(i);
15147
+ });
15148
+ const keptByChild = worstByKey(kept);
15149
+ const added = resolution.issues.filter((i) => {
15150
+ const own = keptByChild.get(issueKey(i));
15151
+ return own === void 0 || SEVERITY_RANK[own] <= SEVERITY_RANK[i.severity];
15152
+ });
15153
+ const merged = dedupeIssues([...kept, ...added]);
15060
15154
  return {
15061
15155
  valid: merged.every((i) => i.severity !== "error"),
15062
15156
  issues: merged,
@@ -15073,14 +15167,14 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
15073
15167
  });
15074
15168
  }
15075
15169
  }
15076
- function resolveThroughParent(boundRoot, treatAllAsComplete) {
15170
+ function resolveThroughParent(boundRoot, treatAllAsComplete, childRules) {
15077
15171
  const reach = getRequestParentReach();
15078
15172
  if (reach && !reach.parentReach) return null;
15079
15173
  const ceiling = reach?.topRoot ? path18.resolve(reach.topRoot) : void 0;
15080
15174
  const chain = [];
15081
15175
  let top = path18.resolve(boundRoot);
15082
15176
  while (top !== ceiling) {
15083
- const hop = findChainingParent(top);
15177
+ const hop = findChainingParent(top, ceiling);
15084
15178
  if (!hop) break;
15085
15179
  const next = path18.resolve(hop.parentRoot);
15086
15180
  if (ceiling && !isWithinOrEqual(ceiling, next)) break;
@@ -15091,10 +15185,10 @@ function resolveThroughParent(boundRoot, treatAllAsComplete) {
15091
15185
  const scope = chain.join("::");
15092
15186
  const inner = runWithProjectRoot(top, () => {
15093
15187
  invalidateSpecCache();
15094
- let governing = {};
15188
+ let governing = { rules: stricterSeverities(void 0, childRules) };
15095
15189
  try {
15096
15190
  const config = loadProjectConfig();
15097
- governing = { rules: config.rules, projectType: config.projectType };
15191
+ governing = { rules: stricterSeverities(config.rules, childRules), projectType: config.projectType };
15098
15192
  } catch {
15099
15193
  }
15100
15194
  return validateSddTree({
@@ -15168,7 +15262,7 @@ function settledStatusBearing(loaded) {
15168
15262
  function validateAsComplete(options) {
15169
15263
  return validateSddTree({ ...options ?? {}, treatAllAsComplete: true });
15170
15264
  }
15171
- var path18, SUBPROJECT_REFERENCE_CODES;
15265
+ var path18, RESOLUTION_FAILURE_CODES, SEVERITY_RANK;
15172
15266
  var init_validation = __esm({
15173
15267
  "src/core/validation.ts"() {
15174
15268
  "use strict";
@@ -15184,16 +15278,15 @@ var init_validation = __esm({
15184
15278
  init_fs();
15185
15279
  path18 = __toESM(require("path"));
15186
15280
  init_approval();
15187
- SUBPROJECT_REFERENCE_CODES = /* @__PURE__ */ new Set([
15281
+ RESOLUTION_FAILURE_CODES = /* @__PURE__ */ new Set([
15188
15282
  "UNDEFINED_TYPE_REFERENCE",
15189
15283
  "INVALID_DEPENDENCY_REFERENCE",
15190
15284
  "INVALID_TARGET_COMPONENT_REFERENCE",
15191
15285
  "INVALID_SUBSYSTEM_REFERENCE",
15192
- "UNDECLARED_DEPENDENCY_CALL",
15193
15286
  "INVALID_TRUSTED_LINK",
15194
- "CROSS_SUBSYSTEM_NON_ADAPTER",
15195
15287
  "CROSS_TREE_REF_UNRESOLVED"
15196
15288
  ]);
15289
+ SEVERITY_RANK = { off: 0, warning: 1, error: 2 };
15197
15290
  }
15198
15291
  });
15199
15292
 
@@ -15756,6 +15849,7 @@ __export(specs_exports, {
15756
15849
  getSubprojectPrefix: () => getSubprojectPrefix,
15757
15850
  getSubsystemPath: () => getSubsystemPath,
15758
15851
  getTypePath: () => getTypePath,
15852
+ inspectChainedRoots: () => inspectChainedRoots,
15759
15853
  invalidateSpecCache: () => invalidateSpecCache,
15760
15854
  listChainedRoots: () => listChainedRoots,
15761
15855
  loadComponentSpec: () => loadComponentSpec,
@@ -15773,6 +15867,7 @@ __export(specs_exports, {
15773
15867
  loadTypeSpecs: () => loadTypeSpecs,
15774
15868
  normalizeComponentLayout: () => normalizeComponentLayout,
15775
15869
  readLockState: () => readLockState,
15870
+ rebaseReference: () => rebaseReference,
15776
15871
  resolveChainingParent: () => resolveChainingParent,
15777
15872
  resolveSubprojectForNamespace: () => resolveSubprojectForNamespace,
15778
15873
  restoreSpecFiles: () => restoreSpecFiles,
@@ -15828,6 +15923,20 @@ function qualifyId(id, prefix, rootSubsystems) {
15828
15923
  }
15829
15924
  return prefix ? `${prefix}::${id}` : id;
15830
15925
  }
15926
+ function qualifyDeclaredId(id, prefix, mountRealization = false) {
15927
+ if (!id || !prefix) return id;
15928
+ if (id.startsWith("::") || id.startsWith("super::")) {
15929
+ return qualifyId(id, prefix, NO_ROOT_SUBSYSTEMS);
15930
+ }
15931
+ if (mountRealization && id === prefix.split("::").pop()) {
15932
+ return prefix;
15933
+ }
15934
+ return `${prefix}::${id}`;
15935
+ }
15936
+ function qualifySubsystemRef(id, prefix, rootSubsystems) {
15937
+ if (prefix && !id.includes("::") && id === prefix.split("::").pop()) return prefix;
15938
+ return qualifyId(id, prefix, rootSubsystems);
15939
+ }
15831
15940
  function splitNamespace(qualifiedId2) {
15832
15941
  if (!qualifiedId2.includes("::")) {
15833
15942
  return { prefix: "", localId: qualifiedId2 };
@@ -15858,33 +15967,75 @@ function relativizeId(id, prefix) {
15858
15967
  if (common === idParts.length) common--;
15859
15968
  return `${"super::".repeat(prefixParts.length - common)}${idParts.slice(common).join("::")}`;
15860
15969
  }
15970
+ function rebaseReference(ref, mount, direction, isMoved = () => false) {
15971
+ if (!ref || ref.startsWith("::")) return ref;
15972
+ const segments = ref.split("::");
15973
+ let hops = 0;
15974
+ while (segments[hops] === "super") hops++;
15975
+ if (hops === segments.length) return ref;
15976
+ const outer = Array.from({ length: hops + 1 }, (_, i) => `<n${i}>`).join("::");
15977
+ const inner = `${outer}::${mount}`;
15978
+ const [from, to] = direction === "into" ? [outer, inner] : [inner, outer];
15979
+ const target = qualifyId(ref, from, NO_ROOT_SUBSYSTEMS);
15980
+ const left = relativizeId(target, from);
15981
+ const travels = !left.startsWith("super::") && (direction === "outOf" || isMoved(left));
15982
+ return relativizeId(travels ? qualifyId(left, to, NO_ROOT_SUBSYSTEMS) : target, to);
15983
+ }
15861
15984
  function isWithin(dir, file) {
15862
15985
  const d = path19.resolve(dir);
15863
15986
  const f = path19.resolve(file);
15864
15987
  return f === d || f.startsWith(d + path19.sep);
15865
15988
  }
15989
+ function canonicalPath(target) {
15990
+ let existing = path19.resolve(target);
15991
+ const rest = [];
15992
+ for (; ; ) {
15993
+ try {
15994
+ fs13.lstatSync(existing);
15995
+ break;
15996
+ } catch {
15997
+ const up = path19.dirname(existing);
15998
+ if (up === existing) return path19.resolve(target);
15999
+ rest.unshift(path19.basename(existing));
16000
+ existing = up;
16001
+ }
16002
+ }
16003
+ try {
16004
+ return path19.join(fs13.realpathSync.native(existing), ...rest);
16005
+ } catch {
16006
+ return null;
16007
+ }
16008
+ }
16009
+ function chainDirKey(dir) {
16010
+ return canonicalPath(dir) ?? path19.resolve(dir);
16011
+ }
15866
16012
  function projectPathEscapesRoot(projectRoot2, projectPath, resolvedChildDir) {
15867
- return path19.isAbsolute(projectPath) || !isWithin(projectRoot2, resolvedChildDir);
16013
+ if (path19.isAbsolute(projectPath) || !isWithin(projectRoot2, resolvedChildDir)) return true;
16014
+ const root = canonicalPath(projectRoot2);
16015
+ const child = canonicalPath(resolvedChildDir);
16016
+ return root === null || child === null || !isWithin(root, child);
15868
16017
  }
15869
16018
  function assertContainedProjectPath(projectRoot2, projectPath) {
15870
16019
  const root = path19.resolve(projectRoot2);
15871
16020
  const resolved = path19.resolve(root, projectPath);
15872
16021
  if (projectPathEscapesRoot(root, projectPath, resolved)) {
15873
16022
  throw new Error(
15874
- `projectPath "${projectPath}" must resolve within the project root "${root}", but resolves to "${resolved}"; absolute paths and ../-escaping paths are rejected so a chained subproject is always contained by its parent.`
16023
+ `projectPath "${projectPath}" must resolve within the project root "${root}", but resolves to "${resolved}"; absolute, ../-escaping and link-escaping paths are rejected so a chained subproject is always contained by its parent.`
15875
16024
  );
15876
16025
  }
15877
16026
  return resolved;
15878
16027
  }
15879
- function findChainingParent(childRoot) {
16028
+ function findChainingParent(childRoot, ceiling) {
15880
16029
  let childResolved;
15881
16030
  try {
15882
16031
  childResolved = path19.resolve(childRoot);
15883
16032
  } catch {
15884
16033
  return null;
15885
16034
  }
16035
+ const bound = ceiling ? path19.resolve(ceiling) : void 0;
15886
16036
  let dir = path19.dirname(childResolved);
15887
16037
  for (let hops = 0; hops < 32; hops++) {
16038
+ if (bound && !isWithin(bound, dir)) break;
15888
16039
  const specsDir = aiPathsAt(dir).specsDir();
15889
16040
  if (pathExists(specsDir)) {
15890
16041
  for (const file of listFilesRecursive(specsDir, ".yaml")) {
@@ -15898,7 +16049,8 @@ function findChainingParent(childRoot) {
15898
16049
  const projectPath = raw.projectPath;
15899
16050
  if (typeof projectPath === "string" && projectPath.trim() !== "") {
15900
16051
  try {
15901
- if (path19.resolve(dir, projectPath) === childResolved) {
16052
+ const mountDir = path19.resolve(dir, projectPath);
16053
+ if (mountDir === childResolved && !projectPathEscapesRoot(dir, projectPath, mountDir)) {
15902
16054
  const id = raw.id;
15903
16055
  return { parentRoot: dir, subsystemId: typeof id === "string" ? id : "?" };
15904
16056
  }
@@ -15914,12 +16066,11 @@ function findChainingParent(childRoot) {
15914
16066
  }
15915
16067
  return null;
15916
16068
  }
15917
- function listChainedRoots(rootDir = getProjectRoot()) {
16069
+ function inspectChainedRoots(rootDir = getProjectRoot()) {
15918
16070
  const root = path19.resolve(rootDir);
15919
- const found = [];
15920
- const visited = /* @__PURE__ */ new Set([root]);
15921
- const walk = (projectDir, depth) => {
15922
- if (depth > 32) return;
16071
+ const inspection = { roots: [], skipped: [] };
16072
+ const listed = /* @__PURE__ */ new Set();
16073
+ const walk = (projectDir, prefix, ancestors, depth) => {
15923
16074
  const specsDir = aiPathsAt(projectDir).specsDir();
15924
16075
  if (!pathExists(specsDir)) return;
15925
16076
  for (const file of listFilesRecursive(specsDir, ".yaml")) {
@@ -15930,24 +16081,37 @@ function listChainedRoots(rootDir = getProjectRoot()) {
15930
16081
  continue;
15931
16082
  }
15932
16083
  if (!raw || typeof raw !== "object" || !("parentSystem" in raw)) continue;
15933
- const projectPath = raw.projectPath;
16084
+ const { id, projectPath } = raw;
15934
16085
  if (typeof projectPath !== "string" || projectPath.trim() === "") continue;
16086
+ const localId = typeof id === "string" ? id : "?";
15935
16087
  let childDir;
15936
16088
  try {
15937
16089
  childDir = path19.resolve(projectDir, projectPath);
15938
16090
  } catch {
15939
16091
  continue;
15940
16092
  }
15941
- if (projectPathEscapesRoot(root, projectPath, childDir)) continue;
15942
- if (visited.has(childDir)) continue;
15943
- if (!fs13.existsSync(childDir)) continue;
15944
- visited.add(childDir);
15945
- found.push(path19.relative(root, childDir).split(path19.sep).join("/"));
15946
- walk(childDir, depth + 1);
16093
+ let reason;
16094
+ if (projectPathEscapesRoot(projectDir, projectPath, childDir)) reason = "escapes";
16095
+ else if (ancestors.has(chainDirKey(childDir))) reason = "cyclic";
16096
+ else if (!fs13.existsSync(childDir)) reason = "missing";
16097
+ else if (depth >= 32) reason = "too-deep";
16098
+ if (reason) {
16099
+ const mount = prefix ? qualifyDeclaredId(localId, prefix, true) : localId;
16100
+ inspection.skipped.push({ mount, projectPath, reason });
16101
+ continue;
16102
+ }
16103
+ const key = chainDirKey(childDir);
16104
+ if (listed.has(key)) continue;
16105
+ listed.add(key);
16106
+ inspection.roots.push(path19.relative(root, childDir).split(path19.sep).join("/"));
16107
+ walk(childDir, prefix ? `${prefix}::${localId}` : localId, /* @__PURE__ */ new Set([...ancestors, key]), depth + 1);
15947
16108
  }
15948
16109
  };
15949
- walk(root, 0);
15950
- return found;
16110
+ walk(root, "", /* @__PURE__ */ new Set([chainDirKey(root)]), 0);
16111
+ return inspection;
16112
+ }
16113
+ function listChainedRoots(rootDir = getProjectRoot()) {
16114
+ return inspectChainedRoots(rootDir).roots;
15951
16115
  }
15952
16116
  function mergeMountRealizations(subs) {
15953
16117
  const result = [];
@@ -16228,7 +16392,26 @@ function buildProjectGraph(level) {
16228
16392
  return buildGraphModel(level);
16229
16393
  }
16230
16394
  function resolveChainingParent() {
16231
- return findChainingParent(getProjectRoot());
16395
+ const reach = getRequestParentReach();
16396
+ if (reach && !reach.parentReach) return null;
16397
+ return findChainingParent(getProjectRoot(), reach?.topRoot);
16398
+ }
16399
+ function snapshotInputKey(snapshot) {
16400
+ const { stateId, generatedAt, origin, ...content } = snapshot;
16401
+ return canonicalize(content);
16402
+ }
16403
+ function consumedSurfaceInputsAt(rootDir) {
16404
+ const dir = path19.join(rootDir, ".wai", "surfaces");
16405
+ if (!pathExists(dir)) return [];
16406
+ const keys = [];
16407
+ for (const file of fs13.readdirSync(dir)) {
16408
+ if (!file.endsWith(".yaml") && !file.endsWith(".yml")) continue;
16409
+ try {
16410
+ keys.push(snapshotInputKey(SurfaceSnapshotSchema.parse(readYamlFile(path19.join(dir, file)))));
16411
+ } catch {
16412
+ }
16413
+ }
16414
+ return keys;
16232
16415
  }
16233
16416
  function computeGateStateId() {
16234
16417
  let gate = {};
@@ -16237,7 +16420,10 @@ function computeGateStateId() {
16237
16420
  gate = { projectType: config.projectType, rules: config.rules };
16238
16421
  } catch {
16239
16422
  }
16240
- return hashGateState(loadProjectExtensions(), gate);
16423
+ const root = getProjectRoot();
16424
+ const roots = [root, ...listChainedRoots(root).map((rel2) => path19.join(root, rel2))];
16425
+ const inputs = roots.flatMap(consumedSurfaceInputsAt);
16426
+ return hashGateState(loadProjectExtensions(), inputs, gate);
16241
16427
  }
16242
16428
  function readLockState() {
16243
16429
  const record2 = readLockRecord();
@@ -16293,7 +16479,7 @@ function findLegacySpecFiles() {
16293
16479
  function updateSpec(kind, id, delta, hooks) {
16294
16480
  return current().updateSpec(kind, id, delta, hooks);
16295
16481
  }
16296
- var fs13, path19, SIGNATURE_TTL_MS, SpecWorkspace, workspaces;
16482
+ var fs13, path19, NO_ROOT_SUBSYSTEMS, SIGNATURE_TTL_MS, SpecWorkspace, workspaces;
16297
16483
  var init_specs2 = __esm({
16298
16484
  "src/core/specs.ts"() {
16299
16485
  "use strict";
@@ -16308,6 +16494,7 @@ var init_specs2 = __esm({
16308
16494
  init_models();
16309
16495
  init_narrative_labels();
16310
16496
  init_diagram();
16497
+ NO_ROOT_SUBSYSTEMS = /* @__PURE__ */ new Set();
16311
16498
  SIGNATURE_TTL_MS = 2e3;
16312
16499
  SpecWorkspace = class {
16313
16500
  constructor(rootDir) {
@@ -16346,7 +16533,7 @@ var init_specs2 = __esm({
16346
16533
  this.rootSubsystems.clear();
16347
16534
  this.cachedRecursive = recursive;
16348
16535
  this.scanVisitedSpecDirs = [];
16349
- const visited = /* @__PURE__ */ new Set([path19.resolve(this.rootDir)]);
16536
+ const visited = /* @__PURE__ */ new Set([chainDirKey(this.rootDir)]);
16350
16537
  const maxDepth = typeof recursive === "number" ? recursive : recursive ? Infinity : 0;
16351
16538
  this.cachedIndex = this.scanSpecsForProject(this.rootDir, "", visited, maxDepth, 0);
16352
16539
  this.cachedSpecDirs = this.scanVisitedSpecDirs;
@@ -16457,7 +16644,7 @@ var init_specs2 = __esm({
16457
16644
  }
16458
16645
  }
16459
16646
  index.subsystems = index.subsystems.map((sub) => {
16460
- const qualifiedSubId = namespacePrefix ? qualifyId(sub.id, namespacePrefix, this.rootSubsystems) : sub.id;
16647
+ const qualifiedSubId = namespacePrefix ? qualifyDeclaredId(sub.id, namespacePrefix, true) : sub.id;
16461
16648
  const componentPrefix = sub.projectPath ? qualifiedSubId : namespacePrefix;
16462
16649
  return {
16463
16650
  ...sub,
@@ -16476,14 +16663,14 @@ var init_specs2 = __esm({
16476
16663
  const originalSubsystemPaths = index.paths.subsystem;
16477
16664
  index.paths.subsystem = {};
16478
16665
  for (const [k, v] of Object.entries(originalSubsystemPaths)) {
16479
- const qualifiedK = namespacePrefix ? qualifyId(k, namespacePrefix, this.rootSubsystems) : k;
16666
+ const qualifiedK = namespacePrefix ? qualifyDeclaredId(k, namespacePrefix, true) : k;
16480
16667
  index.paths.subsystem[qualifiedK] = v;
16481
16668
  }
16482
16669
  if (namespacePrefix) {
16483
16670
  index.components = index.components.map((comp) => ({
16484
16671
  ...comp,
16485
- id: qualifyId(comp.id, namespacePrefix, this.rootSubsystems),
16486
- subsystem: qualifyId(comp.subsystem, namespacePrefix, this.rootSubsystems),
16672
+ id: qualifyDeclaredId(comp.id, namespacePrefix),
16673
+ subsystem: qualifySubsystemRef(comp.subsystem, namespacePrefix, this.rootSubsystems),
16487
16674
  owns: comp.owns.map((o) => qualifyId(o, namespacePrefix, this.rootSubsystems)),
16488
16675
  dependsOn: comp.dependsOn.map((d) => qualifyId(d, namespacePrefix, this.rootSubsystems)),
16489
16676
  dispatch: comp.dispatch?.map((b) => ({
@@ -16493,12 +16680,12 @@ var init_specs2 = __esm({
16493
16680
  }));
16494
16681
  index.interfaces = index.interfaces.map((intf) => ({
16495
16682
  ...intf,
16496
- id: qualifyId(intf.id, namespacePrefix, this.rootSubsystems),
16683
+ id: qualifyDeclaredId(intf.id, namespacePrefix),
16497
16684
  component: qualifyId(intf.component, namespacePrefix, this.rootSubsystems)
16498
16685
  }));
16499
16686
  index.implementations = index.implementations.map((impl) => ({
16500
16687
  ...impl,
16501
- id: qualifyId(impl.id, namespacePrefix, this.rootSubsystems),
16688
+ id: qualifyDeclaredId(impl.id, namespacePrefix),
16502
16689
  contract: qualifyId(impl.contract, namespacePrefix, this.rootSubsystems),
16503
16690
  methods: impl.methods.map((m) => ({
16504
16691
  ...m,
@@ -16510,13 +16697,13 @@ var init_specs2 = __esm({
16510
16697
  }));
16511
16698
  index.types = index.types.map((t) => ({
16512
16699
  ...t,
16513
- id: qualifyId(t.id, namespacePrefix, this.rootSubsystems),
16514
- subsystem: t.subsystem ? qualifyId(t.subsystem, namespacePrefix, this.rootSubsystems) : void 0,
16700
+ id: qualifyDeclaredId(t.id, namespacePrefix),
16701
+ subsystem: t.subsystem ? qualifySubsystemRef(t.subsystem, namespacePrefix, this.rootSubsystems) : void 0,
16515
16702
  group: t.group ? qualifyId(t.group, namespacePrefix, this.rootSubsystems) : void 0
16516
16703
  }));
16517
16704
  index.groups = index.groups.map((g) => ({
16518
16705
  ...g,
16519
- id: qualifyId(g.id, namespacePrefix, this.rootSubsystems)
16706
+ id: qualifyDeclaredId(g.id, namespacePrefix)
16520
16707
  }));
16521
16708
  const originalPaths = index.paths;
16522
16709
  index.paths = {
@@ -16528,39 +16715,40 @@ var init_specs2 = __esm({
16528
16715
  group: {}
16529
16716
  };
16530
16717
  for (const [k, v] of Object.entries(originalPaths.component)) {
16531
- index.paths.component[qualifyId(k, namespacePrefix, this.rootSubsystems)] = v;
16718
+ index.paths.component[qualifyDeclaredId(k, namespacePrefix)] = v;
16532
16719
  }
16533
16720
  for (const [k, v] of Object.entries(originalPaths.interface)) {
16534
- index.paths.interface[qualifyId(k, namespacePrefix, this.rootSubsystems)] = v;
16721
+ index.paths.interface[qualifyDeclaredId(k, namespacePrefix)] = v;
16535
16722
  }
16536
16723
  for (const [k, v] of Object.entries(originalPaths.implementation)) {
16537
- index.paths.implementation[qualifyId(k, namespacePrefix, this.rootSubsystems)] = v;
16724
+ index.paths.implementation[qualifyDeclaredId(k, namespacePrefix)] = v;
16538
16725
  }
16539
16726
  for (const [k, v] of Object.entries(originalPaths.type)) {
16540
- index.paths.type[qualifyId(k, namespacePrefix, this.rootSubsystems)] = v;
16727
+ index.paths.type[qualifyDeclaredId(k, namespacePrefix)] = v;
16541
16728
  }
16542
16729
  for (const [k, v] of Object.entries(originalPaths.group)) {
16543
- index.paths.group[qualifyId(k, namespacePrefix, this.rootSubsystems)] = v;
16730
+ index.paths.group[qualifyDeclaredId(k, namespacePrefix)] = v;
16544
16731
  }
16545
16732
  }
16546
16733
  if (currentDepth < maxDepth) {
16547
16734
  for (const subproj of localSubprojects) {
16548
16735
  const childDir = path19.resolve(projectDir, subproj.projectPath);
16549
- if (projectPathEscapesRoot(this.rootDir, subproj.projectPath, childDir)) {
16736
+ const mountId = namespacePrefix ? qualifyDeclaredId(subproj.subsystemId, namespacePrefix, true) : subproj.subsystemId;
16737
+ if (projectPathEscapesRoot(projectDir, subproj.projectPath, childDir)) {
16550
16738
  this.loaderIssues.push({
16551
16739
  severity: "error",
16552
16740
  code: "PROJECTPATH_ESCAPE",
16553
- message: `Subproject path "${subproj.projectPath}" declared by subsystem "${subproj.subsystemId}" escapes the project root "${this.rootDir}" (resolves to "${childDir}"); absolute and ../-escaping projectPaths are rejected. Skipping this subproject.`,
16554
- specId: subproj.subsystemId
16741
+ message: `Subproject path "${subproj.projectPath}" declared by subsystem "${mountId}" escapes the root of the project declaring it, "${projectDir}" (resolves to "${childDir}"); absolute, ../-escaping and link-escaping projectPaths are rejected. Skipping this subproject.`,
16742
+ specId: mountId
16555
16743
  });
16556
16744
  continue;
16557
16745
  }
16558
- if (visitedDirs.has(childDir)) {
16746
+ if (visitedDirs.has(chainDirKey(childDir))) {
16559
16747
  this.loaderIssues.push({
16560
16748
  severity: "error",
16561
16749
  code: "CIRCULAR_SUBPROJECT_REFERENCE",
16562
- message: `Circular reference detected: Subsystem "${subproj.subsystemId}" refers to subproject "${childDir}" which is already loaded.`,
16563
- specId: subproj.subsystemId
16750
+ message: `Circular reference detected: Subsystem "${mountId}" refers to subproject "${childDir}" which is already loaded.`,
16751
+ specId: mountId
16564
16752
  });
16565
16753
  continue;
16566
16754
  }
@@ -16568,14 +16756,14 @@ var init_specs2 = __esm({
16568
16756
  this.loaderIssues.push({
16569
16757
  severity: "error",
16570
16758
  code: "SUBPROJECT_NOT_FOUND",
16571
- message: `Subproject directory "${childDir}" declared by subsystem "${subproj.subsystemId}" does not exist.`,
16572
- specId: subproj.subsystemId
16759
+ message: `Subproject directory "${childDir}" declared by subsystem "${mountId}" does not exist.`,
16760
+ specId: mountId
16573
16761
  });
16574
16762
  continue;
16575
16763
  }
16576
16764
  const childNamespace = namespacePrefix ? `${namespacePrefix}::${subproj.subsystemId}` : subproj.subsystemId;
16577
16765
  const newVisited = new Set(visitedDirs);
16578
- newVisited.add(childDir);
16766
+ newVisited.add(chainDirKey(childDir));
16579
16767
  const childIndex = this.scanSpecsForProject(childDir, childNamespace, newVisited, maxDepth, currentDepth + 1);
16580
16768
  index.subsystems.push(...childIndex.subsystems);
16581
16769
  index.components.push(...childIndex.components);
@@ -16608,11 +16796,11 @@ var init_specs2 = __esm({
16608
16796
  const sub = index.subsystems.find((s) => s.id === currentPrefix);
16609
16797
  if (sub && sub.projectPath) {
16610
16798
  const nextDir = path19.resolve(currentDir, sub.projectPath);
16611
- if (projectPathEscapesRoot(this.rootDir, sub.projectPath, nextDir)) {
16799
+ if (projectPathEscapesRoot(currentDir, sub.projectPath, nextDir)) {
16612
16800
  this.loaderIssues.push({
16613
16801
  severity: "error",
16614
16802
  code: "PROJECTPATH_ESCAPE",
16615
- message: `Subproject path "${sub.projectPath}" declared by subsystem "${currentPrefix}" escapes the project root "${this.rootDir}" (resolves to "${nextDir}"); absolute and ../-escaping projectPaths are rejected. Skipping this subproject.`,
16803
+ message: `Subproject path "${sub.projectPath}" declared by subsystem "${currentPrefix}" escapes the root of the project declaring it, "${currentDir}" (resolves to "${nextDir}"); absolute, ../-escaping and link-escaping projectPaths are rejected. Skipping this subproject.`,
16616
16804
  specId: currentPrefix
16617
16805
  });
16618
16806
  continue;
@@ -18256,6 +18444,7 @@ function externalizeSubsystem(subsystemId, projectPath) {
18256
18444
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
18257
18445
  });
18258
18446
  rewriteRefsInDir(parentSpecsDir, renameMap, fooDir);
18447
+ rebaseMovedRefs(childFooDir, subsystemId, "into");
18259
18448
  invalidateSpecCache();
18260
18449
  }
18261
18450
  function internalizeSubsystem(subsystemId) {
@@ -18295,6 +18484,7 @@ function internalizeSubsystem(subsystemId) {
18295
18484
  });
18296
18485
  fs14.rmSync(childWai, { recursive: true, force: true });
18297
18486
  rewriteRefsInDir(parentSpecsDir, renameMap, fooDir);
18487
+ rebaseMovedRefs(fooDir, subsystemId, "outOf");
18298
18488
  invalidateSpecCache();
18299
18489
  }
18300
18490
  function buildRenameMap(subsystemId, externalize) {
@@ -18321,7 +18511,27 @@ function buildRenameMap(subsystemId, externalize) {
18321
18511
  }
18322
18512
  function rewriteRefsInDir(specsDir, renameMap, excludeDir) {
18323
18513
  if (renameMap.size === 0) return;
18324
- const remap = (id) => id !== void 0 && renameMap.has(id) ? renameMap.get(id) : id;
18514
+ rewriteRefFields(specsDir, (ref) => renameMap.get(ref) ?? ref, excludeDir);
18515
+ }
18516
+ function rebaseMovedRefs(movedDir, mount, direction) {
18517
+ const declared = componentIdsUnder(movedDir);
18518
+ rewriteRefFields(movedDir, (ref, position) => position === "component" ? rebaseReference(ref, mount, direction, (id) => declared.has(id)) : ref);
18519
+ }
18520
+ function componentIdsUnder(dir) {
18521
+ const ids = /* @__PURE__ */ new Set();
18522
+ for (const file of listFilesRecursive(dir, ".yaml")) {
18523
+ let raw;
18524
+ try {
18525
+ raw = readYamlFile(file);
18526
+ } catch {
18527
+ continue;
18528
+ }
18529
+ if (raw && typeof raw === "object" && "componentType" in raw && typeof raw.id === "string") ids.add(raw.id);
18530
+ }
18531
+ return ids;
18532
+ }
18533
+ function rewriteRefFields(specsDir, remap, excludeDir) {
18534
+ const at = (ref, position) => typeof ref === "string" ? remap(ref, position) : ref;
18325
18535
  for (const file of listFilesRecursive(specsDir, ".yaml")) {
18326
18536
  if (excludeDir && isWithinDir(excludeDir, file)) continue;
18327
18537
  let raw;
@@ -18333,14 +18543,14 @@ function rewriteRefsInDir(specsDir, renameMap, excludeDir) {
18333
18543
  if (!raw || typeof raw !== "object") continue;
18334
18544
  let changed = false;
18335
18545
  if ("componentType" in raw && Array.isArray(raw.dependsOn)) {
18336
- const next = raw.dependsOn.map((d) => remap(d));
18546
+ const next = raw.dependsOn.map((d) => at(d, "component"));
18337
18547
  if (next.some((v, i) => v !== raw.dependsOn[i])) {
18338
18548
  raw.dependsOn = next;
18339
18549
  changed = true;
18340
18550
  }
18341
18551
  if (Array.isArray(raw.dispatch)) {
18342
18552
  for (const b of raw.dispatch) {
18343
- const nc = remap(b.component);
18553
+ const nc = at(b.component, "component");
18344
18554
  if (nc !== b.component) {
18345
18555
  b.component = nc;
18346
18556
  changed = true;
@@ -18349,7 +18559,7 @@ function rewriteRefsInDir(specsDir, renameMap, excludeDir) {
18349
18559
  }
18350
18560
  } else if ("parentSystem" in raw && Array.isArray(raw.lifecycle)) {
18351
18561
  for (const le of raw.lifecycle) {
18352
- const nc = remap(le.component);
18562
+ const nc = at(le.component, "component");
18353
18563
  if (nc !== le.component) {
18354
18564
  le.component = nc;
18355
18565
  changed = true;
@@ -18359,7 +18569,7 @@ function rewriteRefsInDir(specsDir, renameMap, excludeDir) {
18359
18569
  for (const m of raw.methods) {
18360
18570
  if (!Array.isArray(m.params)) continue;
18361
18571
  for (const p of m.params) {
18362
- const nt = remap(p.type);
18572
+ const nt = at(p.type, "type");
18363
18573
  if (nt !== p.type) {
18364
18574
  p.type = nt;
18365
18575
  changed = true;
@@ -18370,7 +18580,7 @@ function rewriteRefsInDir(specsDir, renameMap, excludeDir) {
18370
18580
  for (const m of raw.methods) {
18371
18581
  if (!Array.isArray(m.narrative)) continue;
18372
18582
  for (const step of m.narrative) {
18373
- const nt = remap(step.targetComponent);
18583
+ const nt = at(step.targetComponent, "component");
18374
18584
  if (nt !== step.targetComponent) {
18375
18585
  step.targetComponent = nt;
18376
18586
  changed = true;
@@ -18379,7 +18589,7 @@ function rewriteRefsInDir(specsDir, renameMap, excludeDir) {
18379
18589
  }
18380
18590
  } else if ("kind" in raw && Array.isArray(raw.fields)) {
18381
18591
  for (const f of raw.fields) {
18382
- const nt = remap(f.type);
18592
+ const nt = at(f.type, "type");
18383
18593
  if (nt !== f.type) {
18384
18594
  f.type = nt;
18385
18595
  changed = true;
@@ -22768,8 +22978,34 @@ __export(server_exports, {
22768
22978
  captureBuildStamp: () => captureBuildStamp,
22769
22979
  createMcpServer: () => createMcpServer,
22770
22980
  isBuildStale: () => isBuildStale,
22771
- startMcpServer: () => startMcpServer
22981
+ startMcpServer: () => startMcpServer,
22982
+ statusFamilyContext: () => statusFamilyContext
22772
22983
  });
22984
+ function statusFamilyContext() {
22985
+ const lines = [];
22986
+ try {
22987
+ const parent = resolveChainingParent();
22988
+ if (parent) {
22989
+ let parentName;
22990
+ try {
22991
+ const system = readYamlFile(aiPathsAt(parent.parentRoot).specsSystem());
22992
+ if (typeof system?.name === "string") parentName = system.name;
22993
+ } catch {
22994
+ }
22995
+ lines.push(
22996
+ `Family: this project is a chained subproject, mounted as subsystem "${parent.subsystemId}" of ${parentName ? `the parent project "${parentName}"` : "its parent project"} \u2014 the surfaces it can consume are listed by sdd_list_external_interfaces.`
22997
+ );
22998
+ }
22999
+ const mounts = loadSubsystemSpecs().filter((s) => s.projectPath && !s.id.includes("::"));
23000
+ if (mounts.length > 0) {
23001
+ lines.push(`Family: chained subprojects mounted here \u2014 ${mounts.map((s) => `${s.id} (${s.projectPath})`).join(", ")}.`);
23002
+ }
23003
+ } catch {
23004
+ }
23005
+ return lines.length > 0 ? `${lines.join("\n")}
23006
+
23007
+ ` : "";
23008
+ }
22773
23009
  function requireLoader() {
22774
23010
  return loader_exports;
22775
23011
  }
@@ -23981,10 +24217,11 @@ NOTICE:
23981
24217
  },
23982
24218
  ({ subsystem, recursive }) => {
23983
24219
  try {
23984
- return text(getStatusReport({
24220
+ const report2 = getStatusReport({
23985
24221
  subsystem,
23986
24222
  recursive: recursive ?? true
23987
- }));
24223
+ });
24224
+ return text(`${statusFamilyContext()}${report2}`);
23988
24225
  } catch (e) {
23989
24226
  return errText(String(e));
23990
24227
  }
@@ -24112,8 +24349,10 @@ NOTICE:
24112
24349
  }
24113
24350
  }, hostedStub);
24114
24351
  reg(server, "sdd_host_export_tree", {
24115
- description: "Hosted spec-tree transfer: pack the BOUND project's WHOLE spec tree \u2014 its own .wai plus every chained subproject \u2014 into a .waitree archive, returned as base64 with its roots, file count and state id. The migration counterpart of an import: use it to take a hosted project local, or to move it to another instance. Requires project:read over the project. Bounded by the data-plane body cap; a very large tree exports through the web download route instead.",
24116
- inputSchema: {}
24352
+ description: "Hosted spec-tree transfer: pack the BOUND project's WHOLE spec tree \u2014 its own .wai plus every chained subproject \u2014 into a .waitree archive, returned as base64 with its roots, file count and state id. The migration counterpart of an import: use it to take a hosted project local, or to move it to another instance. Requires project:read over the project. Bounded by the data-plane body cap; a very large tree exports through the web download route instead. Refuses when a chained mount cannot be packed (escaping, missing, cyclic, too deep, or holding no .wai), naming every one and why, unless allowPartial is set \u2014 then the archive is built anyway and the result lists what was skipped.",
24353
+ inputSchema: {
24354
+ allowPartial: import_zod10.z.boolean().optional().describe("Build the archive even when some chained mounts cannot be packed, listing them as skipped; default false (refuse and name them)")
24355
+ }
24117
24356
  }, hostedStub);
24118
24357
  reg(server, "sdd_host_import_tree", {
24119
24358
  description: "Hosted spec-tree transfer: REPLACE the BOUND project's spec tree from a base64 .waitree archive. Requires project:admin over the project (strictly above project:write \u2014 this replaces the whole design, not one spec). Refuses an occupied destination unless replaceExisting is set, always refuses executable entries (rule/code packs install only through the trusted filesystem), and moves the previous tree aside to a backup whose path is returned.",
@@ -24176,6 +24415,7 @@ var init_server = __esm({
24176
24415
  path30 = __toESM(require("path"));
24177
24416
  import_url = require("url");
24178
24417
  init_fs();
24418
+ init_yaml();
24179
24419
  init_status();
24180
24420
  init_defaults();
24181
24421
  init_narrative_labels();
@@ -25305,18 +25545,29 @@ function discardStagingDir(stagingDir) {
25305
25545
  } catch {
25306
25546
  }
25307
25547
  }
25308
- function exportSpecTree(includeDerived) {
25548
+ function exportSpecTree(includeDerived, allowPartial) {
25309
25549
  const system = loadSystemSpec();
25310
25550
  if (!system) {
25311
25551
  throw new Error("no spec tree to export at this project root");
25312
25552
  }
25313
- const chained = listChainedRoots();
25553
+ const inspection = inspectChainedRoots();
25314
25554
  const root = getProjectRoot();
25315
- const candidates = [
25316
- { relativePath: ".", waiDir: aiPathsAt(root).root() },
25317
- ...chained.map((rel2) => ({ relativePath: rel2, waiDir: aiPathsAt(path28.resolve(root, rel2)).root() }))
25318
- ];
25319
- const roots = candidates.filter((source) => pathExists(source.waiDir));
25555
+ const skipped = [...inspection.skipped];
25556
+ const roots = [{ relativePath: ".", waiDir: aiPathsAt(root).root() }];
25557
+ for (const rel2 of inspection.roots) {
25558
+ const waiDir = aiPathsAt(path28.resolve(root, rel2)).root();
25559
+ if (pathExists(waiDir)) {
25560
+ roots.push({ relativePath: rel2, waiDir });
25561
+ } else {
25562
+ skipped.push({ mount: rel2, projectPath: rel2, reason: "no-spec-tree" });
25563
+ }
25564
+ }
25565
+ if (skipped.length > 0 && !allowPartial) {
25566
+ const detail = skipped.map((s) => `${s.mount} (${s.reason})`).join(", ");
25567
+ throw new Error(
25568
+ `cannot export the whole spec tree \u2014 skipped: ${detail}. Pass allowPartial to export the rest anyway.`
25569
+ );
25570
+ }
25320
25571
  const stateId = computeStateId();
25321
25572
  const built = buildTreeArchive2(roots, system.name, stateId.digest, includeDerived);
25322
25573
  const result = {
@@ -25325,7 +25576,8 @@ function exportSpecTree(includeDerived) {
25325
25576
  projectName: built.manifest.projectName,
25326
25577
  roots: built.manifest.roots,
25327
25578
  fileCount: built.fileCount,
25328
- stateId: stateId.digest
25579
+ stateId: stateId.digest,
25580
+ skipped
25329
25581
  };
25330
25582
  return result;
25331
25583
  }
@@ -26254,6 +26506,7 @@ init_loader();
26254
26506
  init_validation();
26255
26507
  function isCiDraftWaivable(issue2) {
26256
26508
  if (issue2.severity !== "warning") return false;
26509
+ if (issue2.code === "DRAFT_SUBSYSTEM_WARNING") return true;
26257
26510
  if (issue2.code === "DRAFT_COMPONENT_WARNING") return true;
26258
26511
  if (issue2.code === "UNUSED_COMPONENT") return issue2.draftContext === true;
26259
26512
  return false;
@@ -27727,7 +27980,7 @@ init_logger();
27727
27980
  init_validation();
27728
27981
  init_extensions();
27729
27982
  init_loader();
27730
- async function listRules2() {
27983
+ async function listRules3() {
27731
27984
  let overrides = {};
27732
27985
  if (isProjectInitialized()) {
27733
27986
  try {
@@ -27742,7 +27995,7 @@ async function listRules2() {
27742
27995
  };
27743
27996
  const ext = loadProjectExtensions();
27744
27997
  const packRuleNames = new Set(ext.rules.map((r) => r.name));
27745
- const active = listRules();
27998
+ const active = listRules2();
27746
27999
  const builtin = active.filter((r) => !packRuleNames.has(r.name));
27747
28000
  const packRules = active.filter((r) => packRuleNames.has(r.name));
27748
28001
  const groups = [
@@ -30104,7 +30357,19 @@ function excludeLocalFiles() {
30104
30357
  const excludePath = path48.join(getProjectRoot(), ".git", "info", "exclude");
30105
30358
  try {
30106
30359
  fs36.mkdirSync(path48.dirname(excludePath), { recursive: true });
30107
- fs36.appendFileSync(excludePath, "\n.wai/lock.json\n.wai/git.json\n");
30360
+ const existing = fs36.existsSync(excludePath) ? fs36.readFileSync(excludePath, "utf8") : "";
30361
+ const lines = existing.split(/\r?\n/).filter((line2) => line2.length > 0);
30362
+ const hadLegacyLockExclusion = lines.some((line2) => line2.trim() === ".wai/lock.json");
30363
+ const kept = lines.filter((line2) => line2.trim() !== ".wai/lock.json");
30364
+ if (!kept.some((line2) => line2.trim() === ".wai/git.json")) {
30365
+ kept.push(".wai/git.json");
30366
+ }
30367
+ fs36.writeFileSync(excludePath, kept.join("\n") + "\n");
30368
+ if (hadLegacyLockExclusion) {
30369
+ console.warn(
30370
+ "[wairon git] removed a .wai/lock.json exclusion left by an earlier version \u2014 the approval it records is committed with the rest of .wai/ now, so it reaches the remote."
30371
+ );
30372
+ }
30108
30373
  } catch {
30109
30374
  }
30110
30375
  }
@@ -31076,13 +31341,13 @@ function listSecrets(_cfg, credential) {
31076
31341
  requireAdmin(credential);
31077
31342
  return listSecretKeys();
31078
31343
  }
31079
- function exportProjectTree(cfg, credential, project2, subproject, includeDerived) {
31344
+ function exportProjectTree(cfg, credential, project2, subproject, includeDerived, allowPartial) {
31080
31345
  const principal = requirePrincipal(cfg, credential);
31081
31346
  if (authorize(cfg.dataDir, principal, "project:read", "project", project2).value !== "yes") {
31082
31347
  throw new AdminAuthError("Forbidden \u2014 exporting a project's spec tree requires project:read over it");
31083
31348
  }
31084
31349
  const root = boundLifecycleRoot(cfg, project2, subproject);
31085
- return runWithProjectRoot(root, () => hostCore.exportSpecTree(includeDerived));
31350
+ return runWithProjectRoot(root, () => hostCore.exportSpecTree(includeDerived, allowPartial));
31086
31351
  }
31087
31352
  function importProjectTree(cfg, credential, project2, archive, subproject, replaceExisting) {
31088
31353
  const principal = requirePrincipal(cfg, credential);
@@ -35787,8 +36052,8 @@ function runPeriodicBackingSync(cfg) {
35787
36052
  }
35788
36053
 
35789
36054
  // src/server/projectops.ts
35790
- function exportProjectTree2(cfg, credential, project2, subproject, includeDerived) {
35791
- return exportProjectTree(cfg, credential, project2, subproject, includeDerived);
36055
+ function exportProjectTree2(cfg, credential, project2, subproject, includeDerived, allowPartial) {
36056
+ return exportProjectTree(cfg, credential, project2, subproject, includeDerived, allowPartial);
35792
36057
  }
35793
36058
  function importProjectTree2(cfg, credential, project2, archive, subproject, replaceExisting) {
35794
36059
  return importProjectTree(cfg, credential, project2, archive, subproject, replaceExisting);
@@ -39178,7 +39443,8 @@ function opsExportProjectTree(cfg, sessionId, url, res) {
39178
39443
  sessionId,
39179
39444
  q(url, "projectId") ?? "",
39180
39445
  void 0,
39181
- q(url, "includeDerived") === "1"
39446
+ q(url, "includeDerived") === "1",
39447
+ q(url, "allowPartial") === "1"
39182
39448
  );
39183
39449
  res.writeHead(200, {
39184
39450
  "content-type": "application/zip",
@@ -39902,7 +40168,8 @@ var PROJECT_RECORD_TOOLS = /* @__PURE__ */ new Set([
39902
40168
  "sdd_host_initialize_project",
39903
40169
  "sdd_host_get_approval_status",
39904
40170
  "sdd_host_await_approval",
39905
- ...PROJECT_OPS_TOOLS
40171
+ ...PROJECT_OPS_TOOLS,
40172
+ ...LANDSCAPE_DISCOVERY_TOOLS
39906
40173
  ]);
39907
40174
  function jsonRpcRequests(body) {
39908
40175
  const arr = Array.isArray(body) ? body : [body];
@@ -39930,11 +40197,31 @@ var WRITE_TOOL_PREFIXES = [
39930
40197
  "sdd_move_"
39931
40198
  ];
39932
40199
  var READ_TOOL_PREFIXES = ["sdd_get_", "sdd_validate_"];
40200
+ var READ_TOOL_NAMES = /* @__PURE__ */ new Set([
40201
+ "sdd_list_external_interfaces",
40202
+ "listAgents",
40203
+ "getAgent",
40204
+ "listDomains",
40205
+ "validateTopology",
40206
+ "getProjectConfig"
40207
+ ]);
39933
40208
  function requiredDataPlaneCapability(toolName) {
39934
- if (READ_TOOL_PREFIXES.some((p) => toolName.startsWith(p))) return "project:read";
40209
+ if (READ_TOOL_NAMES.has(toolName) || READ_TOOL_PREFIXES.some((p) => toolName.startsWith(p))) return "project:read";
39935
40210
  if (WRITE_TOOL_PREFIXES.some((p) => toolName.startsWith(p))) return "project:write";
39936
40211
  return "project:write";
39937
40212
  }
40213
+ var TREE_SCOPED_HOST_TOOLS = /* @__PURE__ */ new Set([
40214
+ "sdd_host_lock_project",
40215
+ ...TREE_TRANSFER_TOOLS
40216
+ ]);
40217
+ function toolScope(toolName) {
40218
+ if (PROJECT_RECORD_TOOLS.has(toolName)) return "record";
40219
+ if (TREE_SCOPED_HOST_TOOLS.has(toolName)) return "tree";
40220
+ if (READ_TOOL_NAMES.has(toolName) || READ_TOOL_PREFIXES.some((p) => toolName.startsWith(p)) || WRITE_TOOL_PREFIXES.some((p) => toolName.startsWith(p))) {
40221
+ return "tree";
40222
+ }
40223
+ return void 0;
40224
+ }
39938
40225
  var MUTATING_HOST_TOOLS = /* @__PURE__ */ new Set([
39939
40226
  "sdd_host_initialize_project",
39940
40227
  "sdd_host_lock_project",
@@ -39975,7 +40262,11 @@ function subprojectConfinementError(projectId, subproject, body) {
39975
40262
  const msg = jsonRpcRequest(body);
39976
40263
  if (!msg || msg.method !== "tools/call") return void 0;
39977
40264
  const name = msg.params?.name;
39978
- if (typeof name !== "string" || !PROJECT_RECORD_TOOLS.has(name)) return void 0;
40265
+ if (typeof name !== "string") return void 0;
40266
+ const scope = toolScope(name);
40267
+ if (scope === "tree") return void 0;
40268
+ const bound = `${projectId}${SUBPROJECT_SEPARATOR}${subproject}`;
40269
+ const why = scope === "record" ? `${name} acts on the whole project "${projectId}", but this credential is bound to subproject "${bound}". ` : `${name} declares no scope, and this credential is bound to subproject "${bound}", which serves only tools that act on the bound tree. `;
39979
40270
  return {
39980
40271
  jsonrpc: "2.0",
39981
40272
  id: msg.id ?? null,
@@ -39983,7 +40274,7 @@ function subprojectConfinementError(projectId, subproject, body) {
39983
40274
  content: [
39984
40275
  {
39985
40276
  type: "text",
39986
- text: `Refused \u2014 ${name} acts on the whole project "${projectId}", but this credential is bound to subproject "${projectId}${SUBPROJECT_SEPARATOR}${subproject}". An unqualified credential for "${projectId}" is required to call ${name}.`
40277
+ text: `Refused \u2014 ${why}An unqualified credential for "${projectId}" is required to call ${name}.`
39987
40278
  }
39988
40279
  ],
39989
40280
  isError: true
@@ -40068,14 +40359,22 @@ async function dispatchProjectLifecycleTool(cfg, credential, projectId, body, su
40068
40359
  );
40069
40360
  break;
40070
40361
  case "sdd_host_export_tree": {
40071
- const exported = exportProjectTree2(cfg, credential, projectId, subproject);
40362
+ const exported = exportProjectTree2(
40363
+ cfg,
40364
+ credential,
40365
+ projectId,
40366
+ subproject,
40367
+ void 0,
40368
+ args.allowPartial === true
40369
+ );
40072
40370
  value = {
40073
40371
  projectName: exported.projectName,
40074
40372
  roots: exported.roots,
40075
40373
  fileCount: exported.fileCount,
40076
40374
  stateId: exported.stateId,
40077
40375
  suggestedFileName: exported.suggestedFileName,
40078
- archiveBase64: Buffer.from(exported.archive).toString("base64")
40376
+ archiveBase64: Buffer.from(exported.archive).toString("base64"),
40377
+ skipped: exported.skipped
40079
40378
  };
40080
40379
  break;
40081
40380
  }
@@ -40663,7 +40962,8 @@ async function routeAdmin(cfg, req, res) {
40663
40962
  return sendJson(res, 200, { ok: true });
40664
40963
  }
40665
40964
  if (req.method === "GET" && parts.length === 4 && parts[3] === "tree") {
40666
- const exported = exportProjectTree(cfg, cred, parts[2]);
40965
+ const allowPartial = url.searchParams.get("allowPartial") === "1";
40966
+ const exported = exportProjectTree(cfg, cred, parts[2], void 0, void 0, allowPartial);
40667
40967
  res.writeHead(200, {
40668
40968
  "content-type": "application/zip",
40669
40969
  "content-disposition": `attachment; filename="${exported.suggestedFileName}"`
@@ -42115,8 +42415,8 @@ init_provision();
42115
42415
  function composeAgentBrief3(agentId) {
42116
42416
  return composeAgentBrief(agentId);
42117
42417
  }
42118
- function exportSpecTree2(includeDerived) {
42119
- return exportSpecTree(includeDerived);
42418
+ function exportSpecTree2(includeDerived, allowPartial) {
42419
+ return exportSpecTree(includeDerived, allowPartial);
42120
42420
  }
42121
42421
  function importSpecTree2(archive, options) {
42122
42422
  return importSpecTree(archive, options);
@@ -42328,8 +42628,8 @@ async function callTool(target, name, args) {
42328
42628
  return raw;
42329
42629
  }
42330
42630
  }
42331
- async function exportRemoteTree(target) {
42332
- const payload = await callTool(target, "sdd_host_export_tree", {});
42631
+ async function exportRemoteTree(target, allowPartial) {
42632
+ const payload = await callTool(target, "sdd_host_export_tree", { allowPartial: allowPartial === true });
42333
42633
  if (!payload || typeof payload.archiveBase64 !== "string" || !payload.archiveBase64) {
42334
42634
  throw new WaironError(`The hosted export returned no archive for project "${target.projectId}".`);
42335
42635
  }
@@ -42338,7 +42638,8 @@ async function exportRemoteTree(target) {
42338
42638
  suggestedFileName: payload.suggestedFileName ?? `${target.projectId}.waitree`,
42339
42639
  projectName: payload.projectName ?? target.projectId,
42340
42640
  roots: payload.roots ?? ["."],
42341
- fileCount: payload.fileCount ?? 0
42641
+ fileCount: payload.fileCount ?? 0,
42642
+ skipped: payload.skipped ?? []
42342
42643
  };
42343
42644
  if (payload.stateId) result.stateId = payload.stateId;
42344
42645
  return result;
@@ -42398,7 +42699,7 @@ async function initializeRemoteProject(target, ownerUnitId) {
42398
42699
  return record2.summary ?? `created project "${record2.id ?? topProjectId(target.projectId)}"`;
42399
42700
  }
42400
42701
  async function pushTree(target, options = {}) {
42401
- const exported = exportSpecTree2(options.includeDerived);
42702
+ const exported = exportSpecTree2(options.includeDerived, options.allowPartial);
42402
42703
  const archivePath = writeArchiveIfAsked(exported.archive, options.archivePath, exported.suggestedFileName);
42403
42704
  let createdProject = false;
42404
42705
  if (options.createUnitId) {
@@ -42418,10 +42719,11 @@ async function pushTree(target, options = {}) {
42418
42719
  };
42419
42720
  if (imported.backupPath) result.backupPath = imported.backupPath;
42420
42721
  if (archivePath) result.archivePath = archivePath;
42722
+ if (exported.skipped.length > 0) result.skipped = exported.skipped;
42421
42723
  return result;
42422
42724
  }
42423
42725
  async function pullTree(target, options = {}) {
42424
- const exported = await exportRemoteTree(target);
42726
+ const exported = await exportRemoteTree(target, options.allowPartial);
42425
42727
  const archivePath = writeArchiveIfAsked(exported.archive, options.archivePath, exported.suggestedFileName);
42426
42728
  const imported = importSpecTree2(exported.archive, {
42427
42729
  replaceExisting: options.replaceExisting === true,
@@ -42438,6 +42740,7 @@ async function pullTree(target, options = {}) {
42438
42740
  };
42439
42741
  if (imported.backupPath) result.backupPath = imported.backupPath;
42440
42742
  if (archivePath) result.archivePath = archivePath;
42743
+ if (exported.skipped.length > 0) result.skipped = exported.skipped;
42441
42744
  return result;
42442
42745
  }
42443
42746
  function resolveTarget(root, overrides = {}) {
@@ -42537,7 +42840,8 @@ async function runRemote(action, options = {}) {
42537
42840
  assertProjectInitialized();
42538
42841
  const transfer = {
42539
42842
  replaceExisting: options.force === true,
42540
- includeDerived: options.includeDerived === true
42843
+ includeDerived: options.includeDerived === true,
42844
+ allowPartial: options.allowPartial === true
42541
42845
  };
42542
42846
  if (options.unit) transfer.createUnitId = options.unit;
42543
42847
  if (options.archive) transfer.archivePath = options.archive;
@@ -42545,7 +42849,10 @@ async function runRemote(action, options = {}) {
42545
42849
  return;
42546
42850
  }
42547
42851
  case "pull": {
42548
- const transfer = { replaceExisting: options.force === true };
42852
+ const transfer = {
42853
+ replaceExisting: options.force === true,
42854
+ allowPartial: options.allowPartial === true
42855
+ };
42549
42856
  if (options.archive) transfer.archivePath = options.archive;
42550
42857
  if (options.dir) transfer.destDir = path68.resolve(options.dir);
42551
42858
  report(await pullTree(target, transfer));
@@ -42654,6 +42961,11 @@ function report(outcome) {
42654
42961
  if (outcome.backupPath) {
42655
42962
  logger.info(`The replaced tree was backed up to ${import_chalk20.default.cyan(outcome.backupPath)} \u2014 restore it by moving it back.`);
42656
42963
  }
42964
+ if (outcome.skipped && outcome.skipped.length > 0) {
42965
+ logger.warn(
42966
+ `Skipped ${outcome.skipped.length} mount(s) \u2014 allowPartial let this through: ` + outcome.skipped.map((s) => `${s.mount} (${s.reason})`).join(", ")
42967
+ );
42968
+ }
42657
42969
  }
42658
42970
 
42659
42971
  // src/cli/index.ts
@@ -42885,7 +43197,7 @@ program.command("doctor").description("Health check: flags stale generated guide
42885
43197
  await runDoctor({ fix: opts.fix });
42886
43198
  });
42887
43199
  async function runRules() {
42888
- await listRules2();
43200
+ await listRules3();
42889
43201
  }
42890
43202
  async function runPatterns() {
42891
43203
  await listPatterns();
@@ -43108,7 +43420,10 @@ program.command("login <url>").description("store a bearer credential for a host
43108
43420
  program.command("logout [url]").description("forget a stored credential for a hosted instance (local only \u2014 does NOT revoke it), or list what is stored").action(async (url) => {
43109
43421
  await runLogout(url, {});
43110
43422
  });
43111
- program.command("remote <action>").description("hosted instance: push | pull (migrate a spec tree) \xB7 attach | detach | status (bind this checkout)").option("--url <url>", "hosted instance base URL (else WAIRON_REMOTE_URL)").option("--project <id>", "hosted project id, optionally qualified as project::subsystem (else WAIRON_REMOTE_PROJECT)").option("--token <token>", "bearer credential (else WAIRON_REMOTE_TOKEN) \u2014 mint one in the hosted UI under Tokens").option("--unit <id>", "push: create the destination project first, owned by this organization unit").option("--force", "replace an authored spec tree at the destination (it is backed up first)").option("--include-derived", "push: pack regenerable artifacts (diagrams, generated topology) too").option("--archive <path>", "also write the transferred archive to this path (file or directory)").option("--dir <path>", "pull: the local project root to import into (default: this project)").action(async (action, opts) => {
43423
+ program.command("remote <action>").description("hosted instance: push | pull (migrate a spec tree) \xB7 attach | detach | status (bind this checkout)").option("--url <url>", "hosted instance base URL (else WAIRON_REMOTE_URL)").option("--project <id>", "hosted project id, optionally qualified as project::subsystem (else WAIRON_REMOTE_PROJECT)").option("--token <token>", "bearer credential (else WAIRON_REMOTE_TOKEN) \u2014 mint one in the hosted UI under Tokens").option("--unit <id>", "push: create the destination project first, owned by this organization unit").option("--force", "replace an authored spec tree at the destination (it is backed up first)").option("--include-derived", "push: pack regenerable artifacts (diagrams, generated topology) too").option(
43424
+ "--allow-partial",
43425
+ "build the archive even when some chained mounts cannot be packed, listing them as skipped (default: refuse)"
43426
+ ).option("--archive <path>", "also write the transferred archive to this path (file or directory)").option("--dir <path>", "pull: the local project root to import into (default: this project)").action(async (action, opts) => {
43112
43427
  await runRemote(action, {
43113
43428
  url: opts.url,
43114
43429
  project: opts.project,
@@ -43116,6 +43431,7 @@ program.command("remote <action>").description("hosted instance: push | pull (mi
43116
43431
  unit: opts.unit,
43117
43432
  force: opts.force,
43118
43433
  includeDerived: opts.includeDerived,
43434
+ allowPartial: opts.allowPartial,
43119
43435
  archive: opts.archive,
43120
43436
  dir: opts.dir
43121
43437
  });