@wairon/cli 5.1.1-dev.87 → 5.1.1-dev.89
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 +4065 -2510
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +473 -151
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3060,7 +3060,7 @@ var require_dist = __commonJS({
|
|
|
3060
3060
|
function slug(name) {
|
|
3061
3061
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3062
3062
|
}
|
|
3063
|
-
var
|
|
3063
|
+
var fs25 = __toESM2(require("fs"));
|
|
3064
3064
|
var path46 = __toESM2(require("path"));
|
|
3065
3065
|
var import_fflate = require_node();
|
|
3066
3066
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".svn"]);
|
|
@@ -3100,8 +3100,8 @@ var require_dist = __commonJS({
|
|
|
3100
3100
|
function writeTree(destDir, files) {
|
|
3101
3101
|
for (const file of files) {
|
|
3102
3102
|
const absolute = path46.join(destDir, file.path);
|
|
3103
|
-
|
|
3104
|
-
|
|
3103
|
+
fs25.mkdirSync(path46.dirname(absolute), { recursive: true });
|
|
3104
|
+
fs25.writeFileSync(absolute, file.contents);
|
|
3105
3105
|
}
|
|
3106
3106
|
}
|
|
3107
3107
|
function classifyKind(name, symlinks) {
|
|
@@ -3110,14 +3110,14 @@ var require_dist = __commonJS({
|
|
|
3110
3110
|
return "file";
|
|
3111
3111
|
}
|
|
3112
3112
|
function walkPackDir(root, current2, out) {
|
|
3113
|
-
for (const entry of
|
|
3113
|
+
for (const entry of fs25.readdirSync(current2, { withFileTypes: true })) {
|
|
3114
3114
|
if (entry.isDirectory()) {
|
|
3115
3115
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
3116
3116
|
walkPackDir(root, path46.join(current2, entry.name), out);
|
|
3117
3117
|
} else if (entry.isFile()) {
|
|
3118
3118
|
const absolute = path46.join(current2, entry.name);
|
|
3119
3119
|
const relative22 = path46.relative(root, absolute).split(path46.sep).join("/");
|
|
3120
|
-
out.push({ path: relative22, contents:
|
|
3120
|
+
out.push({ path: relative22, contents: fs25.readFileSync(absolute) });
|
|
3121
3121
|
}
|
|
3122
3122
|
}
|
|
3123
3123
|
}
|
|
@@ -3503,6 +3503,7 @@ __export(src_exports, {
|
|
|
3503
3503
|
SystemPublicInterfaceSchema: () => SystemPublicInterfaceSchema,
|
|
3504
3504
|
SystemSpecSchema: () => SystemSpecSchema,
|
|
3505
3505
|
TargetConfigSchema: () => TargetConfigSchema,
|
|
3506
|
+
TechnologySchema: () => TechnologySchema,
|
|
3506
3507
|
TemplateNotFoundError: () => TemplateNotFoundError,
|
|
3507
3508
|
TemplateSchema: () => TemplateSchema,
|
|
3508
3509
|
TopologyConfigSchema: () => TopologyConfigSchema,
|
|
@@ -3530,6 +3531,7 @@ __export(src_exports, {
|
|
|
3530
3531
|
assertProjectInitialized: () => assertProjectInitialized,
|
|
3531
3532
|
backendCommand: () => backendCommand,
|
|
3532
3533
|
backfillChainedSubprojectConfigs: () => backfillChainedSubprojectConfigs,
|
|
3534
|
+
backupBeside: () => backupBeside,
|
|
3533
3535
|
buildCanvasDataModel: () => buildCanvasDataModel,
|
|
3534
3536
|
buildProjectGraph: () => buildProjectGraph,
|
|
3535
3537
|
buildRuleContext: () => buildRuleContext,
|
|
@@ -3604,6 +3606,7 @@ __export(src_exports, {
|
|
|
3604
3606
|
generateDiagramSet: () => generateDiagramSet,
|
|
3605
3607
|
generateSequenceDiagram: () => generateSequenceDiagram,
|
|
3606
3608
|
getExporter: () => getExporter,
|
|
3609
|
+
getHostedLookup: () => getHostedLookup,
|
|
3607
3610
|
getLoaderIssues: () => getLoaderIssues,
|
|
3608
3611
|
getProjectRoot: () => getProjectRoot,
|
|
3609
3612
|
getProjectRootOverride: () => getProjectRootOverride,
|
|
@@ -3628,6 +3631,7 @@ __export(src_exports, {
|
|
|
3628
3631
|
internalizeMember: () => internalizeMember,
|
|
3629
3632
|
isDraftSubsystem: () => isDraftSubsystem,
|
|
3630
3633
|
isLogic: () => isLogic,
|
|
3634
|
+
isOutsideRoot: () => isOutsideRoot,
|
|
3631
3635
|
isOwnComponentEntry: () => isOwnComponentEntry,
|
|
3632
3636
|
isPattern: () => isPattern,
|
|
3633
3637
|
isProjectInitialized: () => isProjectInitialized,
|
|
@@ -3735,6 +3739,7 @@ __export(src_exports, {
|
|
|
3735
3739
|
resolveVariantGuidance: () => resolveVariantGuidance,
|
|
3736
3740
|
retireSpecialists: () => retireSpecialists,
|
|
3737
3741
|
rewriteReferences: () => rewriteReferences2,
|
|
3742
|
+
runWithHostedLookup: () => runWithHostedLookup,
|
|
3738
3743
|
runWithProjectBinding: () => runWithProjectBinding,
|
|
3739
3744
|
runWithProjectRoot: () => runWithProjectRoot,
|
|
3740
3745
|
sameContract: () => sameContract,
|
|
@@ -3756,6 +3761,8 @@ __export(src_exports, {
|
|
|
3756
3761
|
stepFieldsFor: () => stepFieldsFor,
|
|
3757
3762
|
stepGraph: () => stepGraph,
|
|
3758
3763
|
syncContextFiles: () => syncContextFiles,
|
|
3764
|
+
technologyName: () => technologyName,
|
|
3765
|
+
technologyTokens: () => technologyTokens,
|
|
3759
3766
|
toMarkdown: () => toMarkdown,
|
|
3760
3767
|
typeBindingOf: () => typeBindingOf,
|
|
3761
3768
|
typeGenericParameters: () => typeGenericParameters,
|
|
@@ -3812,6 +3819,15 @@ function readFileOrNull(filePath) {
|
|
|
3812
3819
|
function pathExists(targetPath) {
|
|
3813
3820
|
return fs.existsSync(targetPath);
|
|
3814
3821
|
}
|
|
3822
|
+
function isOutsideRoot(root, target) {
|
|
3823
|
+
const rel2 = path.relative(path.resolve(root), path.resolve(target));
|
|
3824
|
+
return rel2 === ".." || rel2.startsWith(`..${path.sep}`) || path.isAbsolute(rel2);
|
|
3825
|
+
}
|
|
3826
|
+
function backupBeside(file, at = /* @__PURE__ */ new Date()) {
|
|
3827
|
+
const backup = `${file}.wairon-backup-${at.toISOString().replace(/[:.]/g, "-")}`;
|
|
3828
|
+
fs.copyFileSync(file, backup);
|
|
3829
|
+
return backup;
|
|
3830
|
+
}
|
|
3815
3831
|
function listFiles(dirPath, ext) {
|
|
3816
3832
|
if (!fs.existsSync(dirPath)) return [];
|
|
3817
3833
|
return fs.readdirSync(dirPath).filter((f) => f.endsWith(ext)).map((f) => path.join(dirPath, f));
|
|
@@ -3844,18 +3860,33 @@ function runWithProjectBinding(dir, reach2, fn) {
|
|
|
3844
3860
|
root: path.resolve(dir),
|
|
3845
3861
|
topRoot: path.resolve(reach2.topRoot),
|
|
3846
3862
|
parentReach: reach2.parentReach,
|
|
3847
|
-
...reach2.narrowed ? { narrowed: true } : {}
|
|
3863
|
+
...reach2.narrowed ? { narrowed: true } : {},
|
|
3864
|
+
...reach2.unwritableRoots ? { unwritableRoots: reach2.unwritableRoots.map((r) => path.resolve(r)) } : {},
|
|
3865
|
+
...reach2.hostedLookup ? { hostedLookup: reach2.hostedLookup } : {}
|
|
3848
3866
|
},
|
|
3849
3867
|
fn
|
|
3850
3868
|
);
|
|
3851
3869
|
}
|
|
3870
|
+
function getHostedLookup() {
|
|
3871
|
+
return requestRootStore.getStore()?.hostedLookup ?? null;
|
|
3872
|
+
}
|
|
3873
|
+
function runWithHostedLookup(lookup2, fn) {
|
|
3874
|
+
const scope = requestRootStore.getStore();
|
|
3875
|
+
if (!scope?.hostedLookup) return fn();
|
|
3876
|
+
return requestRootStore.run({ ...scope, hostedLookup: lookup2 }, fn);
|
|
3877
|
+
}
|
|
3852
3878
|
function getRequestProjectRoot() {
|
|
3853
3879
|
return requestRootStore.getStore()?.root ?? null;
|
|
3854
3880
|
}
|
|
3855
3881
|
function getRequestParentReach() {
|
|
3856
3882
|
const scope = requestRootStore.getStore();
|
|
3857
3883
|
if (!scope || scope.parentReach === void 0) return null;
|
|
3858
|
-
return {
|
|
3884
|
+
return {
|
|
3885
|
+
topRoot: scope.topRoot,
|
|
3886
|
+
parentReach: scope.parentReach,
|
|
3887
|
+
...scope.narrowed ? { narrowed: true } : {},
|
|
3888
|
+
...scope.unwritableRoots ? { unwritableRoots: scope.unwritableRoots } : {}
|
|
3889
|
+
};
|
|
3859
3890
|
}
|
|
3860
3891
|
function setProjectRoot(dir) {
|
|
3861
3892
|
projectRootOverride = dir === null ? null : path.resolve(dir);
|
|
@@ -4571,7 +4602,8 @@ var ProjectProfileSelectionSchema = import_zod3.z.object({
|
|
|
4571
4602
|
selectedAt: import_zod3.z.string()
|
|
4572
4603
|
});
|
|
4573
4604
|
var ExternalSourceSchema = import_zod3.z.object({
|
|
4574
|
-
path: import_zod3.z.string()
|
|
4605
|
+
path: import_zod3.z.string().optional(),
|
|
4606
|
+
hosted: import_zod3.z.string().optional()
|
|
4575
4607
|
});
|
|
4576
4608
|
var ExternalDeclarationSchema = import_zod3.z.object({
|
|
4577
4609
|
/** The producer's project id; defaults to the alias. Plain string: a malformed id is REPORTED, never unreadable. */
|
|
@@ -4942,6 +4974,12 @@ function projectIdentity(config, lockedProjectId) {
|
|
|
4942
4974
|
};
|
|
4943
4975
|
}
|
|
4944
4976
|
var EXTERNAL_ALIAS_RE = /^[a-z0-9_-]+$/;
|
|
4977
|
+
function sourceProblem(source) {
|
|
4978
|
+
if (source === void 0) return void 0;
|
|
4979
|
+
const named2 = (source.path !== void 0 ? 1 : 0) + (source.hosted !== void 0 ? 1 : 0);
|
|
4980
|
+
if (named2 === 1) return void 0;
|
|
4981
|
+
return named2 === 0 ? "its `source` names neither `path` nor `hosted` \u2014 give exactly one" : "its `source` names both `path` and `hosted` \u2014 give exactly one";
|
|
4982
|
+
}
|
|
4945
4983
|
var USE_ENTRY_RE = /^(\*|[a-z0-9_-]+)$/;
|
|
4946
4984
|
function readUse(alias, use) {
|
|
4947
4985
|
const list = Array.isArray(use) ? use.map((u) => String(u)) : [];
|
|
@@ -4956,11 +4994,12 @@ function declaredExternals(config) {
|
|
|
4956
4994
|
return Object.entries(config.externals ?? {}).map(([alias, declaration2]) => {
|
|
4957
4995
|
const project = declaration2?.project ?? alias;
|
|
4958
4996
|
const imports = readUse(alias, declaration2?.use);
|
|
4959
|
-
const problem = !EXTERNAL_ALIAS_RE.test(alias) ? PROJECT_ID_RE.test(alias) ? `the alias "${alias}" is not a reference name ([a-z0-9-_]+) \u2014 a dotted producer id needs an explicit alias, e.g. \`${alias.replace(/\./g, "-")}: { project: ${alias} }\`` : `the alias "${alias}" breaks [a-z0-9-_]+` : config.members?.[alias] !== void 0 ? `the alias "${alias}" is also declared under \`members\` \u2014 one alias names one project` : !PROJECT_ID_RE.test(project) ? `the producer id "${project}" breaks the project-id grammar` : imports.problem;
|
|
4997
|
+
const problem = !EXTERNAL_ALIAS_RE.test(alias) ? PROJECT_ID_RE.test(alias) ? `the alias "${alias}" is not a reference name ([a-z0-9-_]+) \u2014 a dotted producer id needs an explicit alias, e.g. \`${alias.replace(/\./g, "-")}: { project: ${alias} }\`` : `the alias "${alias}" breaks [a-z0-9-_]+` : config.members?.[alias] !== void 0 ? `the alias "${alias}" is also declared under \`members\` \u2014 one alias names one project` : !PROJECT_ID_RE.test(project) ? `the producer id "${project}" breaks the project-id grammar` : sourceProblem(declaration2?.source) ?? imports.problem;
|
|
4960
4998
|
return {
|
|
4961
4999
|
alias,
|
|
4962
5000
|
project,
|
|
4963
5001
|
...declaration2?.source?.path !== void 0 ? { sourcePath: declaration2.source.path } : {},
|
|
5002
|
+
...declaration2?.source?.hosted !== void 0 ? { sourceHosted: declaration2.source.hosted } : {},
|
|
4964
5003
|
...problem ? { problem } : {},
|
|
4965
5004
|
use: imports.use
|
|
4966
5005
|
};
|
|
@@ -7003,6 +7042,19 @@ function parseDeclaredCall(ref) {
|
|
|
7003
7042
|
if (at <= 0 || at === ref.length - 1) return null;
|
|
7004
7043
|
return { compId: ref.slice(0, at), methodName: ref.slice(at + 1) };
|
|
7005
7044
|
}
|
|
7045
|
+
var TechnologySchema = import_zod8.z.union([
|
|
7046
|
+
import_zod8.z.string().min(1),
|
|
7047
|
+
import_zod8.z.object({
|
|
7048
|
+
name: import_zod8.z.string().min(1),
|
|
7049
|
+
matches: import_zod8.z.array(import_zod8.z.string().min(1)).min(1)
|
|
7050
|
+
}).strict()
|
|
7051
|
+
]);
|
|
7052
|
+
function technologyName(tech) {
|
|
7053
|
+
return typeof tech === "string" ? tech : tech.name;
|
|
7054
|
+
}
|
|
7055
|
+
function technologyTokens(tech) {
|
|
7056
|
+
return typeof tech === "string" ? [tech] : [...tech.matches];
|
|
7057
|
+
}
|
|
7006
7058
|
var ImplementationSpecSchema = import_zod8.z.object({
|
|
7007
7059
|
id: SpecIdSchema,
|
|
7008
7060
|
name: import_zod8.z.string(),
|
|
@@ -7027,9 +7079,10 @@ var ImplementationSpecSchema = import_zod8.z.object({
|
|
|
7027
7079
|
* component's ownership tree the technology's home: references anywhere
|
|
7028
7080
|
* outside it are flagged (TECH_LEAKAGE), contract identifiers must stay
|
|
7029
7081
|
* intent-language (VENDOR_NAME_IN_CONTRACT), and only data-layer
|
|
7030
|
-
* stereotypes should bind tech directly (TECH_ON_LOGIC_COMPONENT).
|
|
7082
|
+
* stereotypes should bind tech directly (TECH_ON_LOGIC_COMPONENT). An entry
|
|
7083
|
+
* is a bare name or `{ name, matches }` (see TechnologySchema).
|
|
7031
7084
|
*/
|
|
7032
|
-
technologies: import_zod8.z.array(
|
|
7085
|
+
technologies: import_zod8.z.array(TechnologySchema).optional(),
|
|
7033
7086
|
/**
|
|
7034
7087
|
* The parameter names THIS realization takes BEFORE the ones its contract
|
|
7035
7088
|
* declares — a config object, a data root, the transport handles a portal is
|
|
@@ -7556,10 +7609,10 @@ function stepGraph(method) {
|
|
|
7556
7609
|
const s = byNum.get(n);
|
|
7557
7610
|
if (s.type !== "parallel" || s.endStep === void 0 || !s.branches?.length) continue;
|
|
7558
7611
|
const entries = s.branches.map((b) => b.step).sort((a, b) => a - b);
|
|
7559
|
-
const
|
|
7612
|
+
const join28 = fallNext(s.endStep);
|
|
7560
7613
|
for (let i = 0; i < entries.length; i++) {
|
|
7561
7614
|
const armEnd = i + 1 < entries.length ? prevOf(entries[i + 1]) : s.endStep;
|
|
7562
|
-
if (armEnd !== void 0 && armEnd >= entries[i]) armEndJoin.set(armEnd,
|
|
7615
|
+
if (armEnd !== void 0 && armEnd >= entries[i]) armEndJoin.set(armEnd, join28);
|
|
7563
7616
|
}
|
|
7564
7617
|
}
|
|
7565
7618
|
const successorsOf = (n) => {
|
|
@@ -13418,10 +13471,11 @@ function isWithinDir(dir, target) {
|
|
|
13418
13471
|
const rel2 = path10.relative(path10.resolve(dir), path10.resolve(target));
|
|
13419
13472
|
return rel2 === "" || !rel2.startsWith("..") && !path10.isAbsolute(rel2);
|
|
13420
13473
|
}
|
|
13421
|
-
function producerUnreadable(
|
|
13474
|
+
function producerUnreadable(external) {
|
|
13475
|
+
const directory = external.directory;
|
|
13422
13476
|
if (!directory) return "the producer has no root directory";
|
|
13423
13477
|
const reach2 = getRequestParentReach();
|
|
13424
|
-
if (reach2) {
|
|
13478
|
+
if (reach2 && external.sourceKind !== "hosted") {
|
|
13425
13479
|
const bound2 = getProjectRoot();
|
|
13426
13480
|
const ceiling = reach2.parentReach ? reach2.topRoot ?? bound2 : bound2;
|
|
13427
13481
|
if (!isWithinDir(ceiling, directory)) return OUT_OF_REACH;
|
|
@@ -13450,7 +13504,7 @@ function usedDigests(usage, snapshot) {
|
|
|
13450
13504
|
function pinBinding(binding, lock) {
|
|
13451
13505
|
const { external } = binding;
|
|
13452
13506
|
const unexported = binding.usage?.unexported ?? [];
|
|
13453
|
-
const unreadable = producerUnreadable(external
|
|
13507
|
+
const unreadable = producerUnreadable(external);
|
|
13454
13508
|
let snapshot;
|
|
13455
13509
|
try {
|
|
13456
13510
|
if (unreadable) throw new Error(unreadable);
|
|
@@ -13553,7 +13607,7 @@ function externalStatus(binding, lock) {
|
|
|
13553
13607
|
const entry = lock?.externals[external.alias];
|
|
13554
13608
|
const pinned = entry !== void 0 && externalsRepository.readSnapshot(external.alias) !== null;
|
|
13555
13609
|
const base = { alias: external.alias, project: external.project, sourceKind: external.sourceKind, pinned };
|
|
13556
|
-
const unreadable = external.sourceKind === "unresolved" ? `the external does not resolve: ${external.problem}` : producerUnreadable(external
|
|
13610
|
+
const unreadable = external.sourceKind === "unresolved" ? hostedOnly(external) ? external.problem : `the external does not resolve: ${external.problem}` : producerUnreadable(external);
|
|
13557
13611
|
let live = null;
|
|
13558
13612
|
let reason = unreadable;
|
|
13559
13613
|
if (!reason) {
|
|
@@ -13564,7 +13618,7 @@ function externalStatus(binding, lock) {
|
|
|
13564
13618
|
}
|
|
13565
13619
|
}
|
|
13566
13620
|
if (!live) {
|
|
13567
|
-
const outOfReach = reason === OUT_OF_REACH || !binding.reachable && external.sourceKind !== "family";
|
|
13621
|
+
const outOfReach = reason === OUT_OF_REACH || hostedOutOfReach(external) || !binding.reachable && external.sourceKind !== "family";
|
|
13568
13622
|
return {
|
|
13569
13623
|
...base,
|
|
13570
13624
|
reachable: false,
|
|
@@ -13575,7 +13629,7 @@ function externalStatus(binding, lock) {
|
|
|
13575
13629
|
};
|
|
13576
13630
|
}
|
|
13577
13631
|
const drifted = entry !== void 0 ? contentDigest(live) !== entry.digest : void 0;
|
|
13578
|
-
if (external.sourceKind === "path") {
|
|
13632
|
+
if (external.sourceKind === "path" || external.sourceKind === "hosted") {
|
|
13579
13633
|
return { ...base, reachable: true, stale: false, ...drifted !== void 0 ? { drifted } : {}, uses: [{ state: "unavailable", code: CHECK_UNAVAILABLE, detail: OUTSIDE_FAMILY }], detail: OUTSIDE_FAMILY };
|
|
13580
13634
|
}
|
|
13581
13635
|
const uses = compareUses(entry, binding.usage, live);
|
|
@@ -13587,6 +13641,12 @@ function externalStatus(binding, lock) {
|
|
|
13587
13641
|
uses
|
|
13588
13642
|
};
|
|
13589
13643
|
}
|
|
13644
|
+
function hostedOnly(external) {
|
|
13645
|
+
return external.hosted !== void 0 && external.sourceKind === "unresolved" && getHostedLookup() === null;
|
|
13646
|
+
}
|
|
13647
|
+
function hostedOutOfReach(external) {
|
|
13648
|
+
return external.hosted !== void 0 && external.sourceKind === "unresolved" && getHostedLookup() !== null;
|
|
13649
|
+
}
|
|
13590
13650
|
function getExternalsStatus() {
|
|
13591
13651
|
const bindings = resolveDeclared();
|
|
13592
13652
|
const lock = externalsRepository.readLock();
|
|
@@ -14250,7 +14310,7 @@ function defaultTargetConfig(type) {
|
|
|
14250
14310
|
enabled: true
|
|
14251
14311
|
};
|
|
14252
14312
|
}
|
|
14253
|
-
var WAIRON_VERSION = "5.1.1-dev.
|
|
14313
|
+
var WAIRON_VERSION = "5.1.1-dev.89";
|
|
14254
14314
|
var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
14255
14315
|
var ARCHITECT_AGENT_ID = "agent-architect";
|
|
14256
14316
|
var ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -15047,13 +15107,13 @@ function buildCodeIndex(model) {
|
|
|
15047
15107
|
const to = resolveImport(scope, specifier, paths);
|
|
15048
15108
|
return to ? republishedFrom(to) : NO_ORIGIN;
|
|
15049
15109
|
};
|
|
15050
|
-
const
|
|
15110
|
+
const scopeOf2 = (site, from) => {
|
|
15051
15111
|
const path46 = pathKey(site.from ?? from);
|
|
15052
15112
|
const f = facts.get(path46);
|
|
15053
15113
|
return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path: path46, facts: f } : void 0;
|
|
15054
15114
|
};
|
|
15055
15115
|
const originOf2 = (site, from) => {
|
|
15056
|
-
const resolved =
|
|
15116
|
+
const resolved = scopeOf2(site, from);
|
|
15057
15117
|
if (!resolved) return NO_ORIGIN;
|
|
15058
15118
|
const { path: scope, facts: f } = resolved;
|
|
15059
15119
|
const landing = (specifier) => landingFrom(scope, specifier);
|
|
@@ -15075,7 +15135,7 @@ function buildCodeIndex(model) {
|
|
|
15075
15135
|
const possibleOriginsOf = (site, from) => {
|
|
15076
15136
|
const proven = originOf2(site, from);
|
|
15077
15137
|
if (!site.field && !site.constructed && !site.via) return proven;
|
|
15078
|
-
const resolved =
|
|
15138
|
+
const resolved = scopeOf2(site, from);
|
|
15079
15139
|
if (!resolved) return proven;
|
|
15080
15140
|
const { path: scope, facts: f } = resolved;
|
|
15081
15141
|
const out = new Set(proven);
|
|
@@ -21381,7 +21441,7 @@ var technologyBindingRule = {
|
|
|
21381
21441
|
ctx.addIssue(
|
|
21382
21442
|
"warning",
|
|
21383
21443
|
"TECH_ON_LOGIC_COMPONENT",
|
|
21384
|
-
`Implementation "${impl.id}" binds technology (${impl.technologies.join(", ")}) on component "${comp.id}" (${comp.componentType}). Technology belongs behind a data-layer seam \u2014 extract an Adapter (or Store/Registry/Index) behind an intent interface and let this component depend on that.`,
|
|
21444
|
+
`Implementation "${impl.id}" binds technology (${impl.technologies.map(technologyName).join(", ")}) on component "${comp.id}" (${comp.componentType}). Technology belongs behind a data-layer seam \u2014 extract an Adapter (or Store/Registry/Index) behind an intent interface and let this component depend on that.`,
|
|
21385
21445
|
impl.id,
|
|
21386
21446
|
ctx.isImplementationDraft(impl)
|
|
21387
21447
|
);
|
|
@@ -21406,6 +21466,16 @@ function makeMatcher(tech) {
|
|
|
21406
21466
|
return false;
|
|
21407
21467
|
};
|
|
21408
21468
|
}
|
|
21469
|
+
function newHome(label7) {
|
|
21470
|
+
const home = {
|
|
21471
|
+
label: label7,
|
|
21472
|
+
matchers: [],
|
|
21473
|
+
match: (text3) => home.matchers.some((m) => m(text3)),
|
|
21474
|
+
ownerComponents: /* @__PURE__ */ new Set(),
|
|
21475
|
+
scope: /* @__PURE__ */ new Set()
|
|
21476
|
+
};
|
|
21477
|
+
return home;
|
|
21478
|
+
}
|
|
21409
21479
|
function contractIdentifiers(m) {
|
|
21410
21480
|
const parts = [m.name, m.signature, m.returns];
|
|
21411
21481
|
for (const p of m.params ?? []) parts.push(p.name, p.type);
|
|
@@ -21415,7 +21485,7 @@ function contractIdentifiers(m) {
|
|
|
21415
21485
|
var technologyRule = {
|
|
21416
21486
|
name: "technology-boundaries",
|
|
21417
21487
|
judges: "design",
|
|
21418
|
-
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.",
|
|
21488
|
+
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.",
|
|
21419
21489
|
codes: [
|
|
21420
21490
|
{ code: "TECH_LEAKAGE", defaultSeverity: "warning", summary: "Technology referenced outside its owning boundary" },
|
|
21421
21491
|
{ code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" }
|
|
@@ -21452,10 +21522,12 @@ var technologyRule = {
|
|
|
21452
21522
|
const intf = ctx.interfaceMap.get(impl.contract);
|
|
21453
21523
|
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
21454
21524
|
if (!comp) continue;
|
|
21455
|
-
for (const tech of impl.technologies)
|
|
21525
|
+
for (const tech of impl.technologies) {
|
|
21526
|
+
declarations.push({ comp, name: technologyName(tech), tokens: technologyTokens(tech) });
|
|
21527
|
+
}
|
|
21456
21528
|
}
|
|
21457
21529
|
const scopes = /* @__PURE__ */ new Map();
|
|
21458
|
-
const
|
|
21530
|
+
const scopeOf2 = (comp) => {
|
|
21459
21531
|
const cached = scopes.get(comp.id);
|
|
21460
21532
|
if (cached) return cached;
|
|
21461
21533
|
const compSet = ownsClosure(ownershipRoot(comp.id));
|
|
@@ -21476,13 +21548,14 @@ var technologyRule = {
|
|
|
21476
21548
|
return scope;
|
|
21477
21549
|
};
|
|
21478
21550
|
const homes = /* @__PURE__ */ new Map();
|
|
21479
|
-
for (const { comp,
|
|
21480
|
-
const
|
|
21481
|
-
if (!
|
|
21482
|
-
const key =
|
|
21483
|
-
const home = homes.get(key) ??
|
|
21551
|
+
for (const { comp, name, tokens } of declarations) {
|
|
21552
|
+
const matchers = tokens.map(makeMatcher).filter((m) => m !== null);
|
|
21553
|
+
if (!matchers.length) continue;
|
|
21554
|
+
const key = name.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
|
|
21555
|
+
const home = homes.get(key) ?? newHome(name);
|
|
21556
|
+
home.matchers.push(...matchers);
|
|
21484
21557
|
home.ownerComponents.add(comp.id);
|
|
21485
|
-
for (const id of
|
|
21558
|
+
for (const id of scopeOf2(comp)) home.scope.add(id);
|
|
21486
21559
|
homes.set(key, home);
|
|
21487
21560
|
}
|
|
21488
21561
|
if (homes.size === 0) return;
|
|
@@ -24904,8 +24977,8 @@ function generateSequenceDiagram(componentId, methodName, options) {
|
|
|
24904
24977
|
const steps = [...methodImpl.narrative].sort((a, b) => a.stepNumber - b.stepNumber);
|
|
24905
24978
|
for (const step of steps) {
|
|
24906
24979
|
for (const par of parallelArms) {
|
|
24907
|
-
const
|
|
24908
|
-
if (
|
|
24980
|
+
const sep13 = par.sepByStep.get(step.stepNumber);
|
|
24981
|
+
if (sep13 !== void 0) lines.push(` and ${escapeLabel(sep13)}`);
|
|
24909
24982
|
}
|
|
24910
24983
|
switch (step.type) {
|
|
24911
24984
|
case "local":
|
|
@@ -25167,6 +25240,7 @@ function bindExternals(node, root, nodes) {
|
|
|
25167
25240
|
return declaredExternals(root.config).map((decl) => {
|
|
25168
25241
|
const unresolved = (problem) => ({ alias: decl.alias, project: decl.project, sourceKind: "unresolved", audience: "instance", problem });
|
|
25169
25242
|
if (decl.problem) return unresolved(decl.problem);
|
|
25243
|
+
if (decl.sourceHosted !== void 0) return { alias: decl.alias, project: decl.project, sourceKind: "hosted", hosted: decl.sourceHosted, audience: "instance" };
|
|
25170
25244
|
if (decl.sourcePath !== void 0) {
|
|
25171
25245
|
const directory = path18.resolve(node.directory, decl.sourcePath);
|
|
25172
25246
|
const found = nodes.find((n) => dirKey(n.directory) === dirKey(directory));
|
|
@@ -25954,7 +26028,7 @@ function mapSpecReferences(kind, spec, map) {
|
|
|
25954
26028
|
const s = spec;
|
|
25955
26029
|
return {
|
|
25956
26030
|
...s,
|
|
25957
|
-
publicInterfaces: s.publicInterfaces.map((p) => ({
|
|
26031
|
+
publicInterfaces: (s.publicInterfaces ?? []).map((p) => ({
|
|
25958
26032
|
...p,
|
|
25959
26033
|
...p.component !== void 0 ? { component: map("publicInterfaces", p.component) } : {},
|
|
25960
26034
|
...p.interface !== void 0 ? { interface: map("publicInterfaces", p.interface) } : {},
|
|
@@ -25970,8 +26044,8 @@ function mapSpecReferences(kind, spec, map) {
|
|
|
25970
26044
|
return {
|
|
25971
26045
|
...c,
|
|
25972
26046
|
subsystem: map("subsystem", c.subsystem),
|
|
25973
|
-
owns: c.owns.map((o) => map("owns", o)),
|
|
25974
|
-
dependsOn: c.dependsOn.map((d) => map("dependsOn", d)),
|
|
26047
|
+
owns: (c.owns ?? []).map((o) => map("owns", o)),
|
|
26048
|
+
dependsOn: (c.dependsOn ?? []).map((d) => map("dependsOn", d)),
|
|
25975
26049
|
...c.dispatch ? { dispatch: c.dispatch.map((b) => ({ ...b, component: map("dispatch", b.component) })) } : {},
|
|
25976
26050
|
...c.mounts ? { mounts: c.mounts.map((m) => ({ ...m, portal: map("mounts", m.portal) })) } : {}
|
|
25977
26051
|
};
|
|
@@ -25985,9 +26059,9 @@ function mapSpecReferences(kind, spec, map) {
|
|
|
25985
26059
|
return {
|
|
25986
26060
|
...impl,
|
|
25987
26061
|
contract: map("contract", impl.contract),
|
|
25988
|
-
methods: impl.methods.map((m) => ({
|
|
26062
|
+
methods: (impl.methods ?? []).map((m) => ({
|
|
25989
26063
|
...m,
|
|
25990
|
-
narrative: m.narrative.map((step) => step.targetComponent !== void 0 ? { ...step, targetComponent: map("narrative", step.targetComponent) } : step)
|
|
26064
|
+
narrative: (m.narrative ?? []).map((step) => step.targetComponent !== void 0 ? { ...step, targetComponent: map("narrative", step.targetComponent) } : step)
|
|
25991
26065
|
}))
|
|
25992
26066
|
};
|
|
25993
26067
|
}
|
|
@@ -27835,10 +27909,10 @@ var SpecWorkspace = class {
|
|
|
27835
27909
|
return this.relativizeSpec("interface", spec);
|
|
27836
27910
|
}
|
|
27837
27911
|
prepareImplementationForWrite(spec, carry = true) {
|
|
27838
|
-
const
|
|
27912
|
+
const relative19 = this.relativizeSpec("implementation", spec, carry);
|
|
27839
27913
|
const member = this.getSubprojectPrefix(spec.id);
|
|
27840
27914
|
const memberRoot = member ? this.resolveSubprojectForNamespace(member) : null;
|
|
27841
|
-
return memberRoot ? childRelativeFilePaths(
|
|
27915
|
+
return memberRoot ? childRelativeFilePaths(relative19, this.rootDir, memberRoot) : relative19;
|
|
27842
27916
|
}
|
|
27843
27917
|
prepareTypeForWrite(spec) {
|
|
27844
27918
|
return this.relativizeSpec("type", spec);
|
|
@@ -28677,7 +28751,10 @@ var SpecWorkspace = class {
|
|
|
28677
28751
|
* second delete written as 7 addresses what used to be step 8. That is why
|
|
28678
28752
|
* a delete may restate the step's `label` or `description` as a guard, and
|
|
28679
28753
|
* why labels are the reliable way to name a jump target. Inserts and
|
|
28680
|
-
* deletes relocate every jump field in the same narrative around
|
|
28754
|
+
* deletes relocate every STORED jump field in the same narrative around
|
|
28755
|
+
* them; a jump the delta itself writes is already in the numbering the
|
|
28756
|
+
* whole delta leaves behind, so none of its later steps moves it. A new
|
|
28757
|
+
* implementation method with no narrative is intent-level ([]); a
|
|
28681
28758
|
* step whose `type` changes is REBUILT for its new type, keeping its
|
|
28682
28759
|
* description and label.
|
|
28683
28760
|
* 5. UNSET the fields the delta names, at the level each `unset` sits in.
|
|
@@ -28735,16 +28812,19 @@ var SpecWorkspace = class {
|
|
|
28735
28812
|
}
|
|
28736
28813
|
const JUMP_FIELDS = ["onTrueStep", "onFalseStep", "defaultStep", "endStep", "finallyStep", "toStep"];
|
|
28737
28814
|
const JUMP_LIST_FIELDS = ["cases", "catches", "branches"];
|
|
28815
|
+
const isPinned = (step, field) => step?.[DELTA_JUMP_PINS] instanceof Set && step[DELTA_JUMP_PINS].has(field);
|
|
28816
|
+
const isPinnedEntry = (entry) => entry?.[DELTA_JUMP_PINS] === true;
|
|
28738
28817
|
const relocateJumps = (step, shiftFrom, deltaN, captureInsertTarget = false) => {
|
|
28739
28818
|
const hit = (v, isRegionTail = false) => deltaN > 0 ? captureInsertTarget && !isRegionTail ? v > shiftFrom : v >= shiftFrom : v > shiftFrom;
|
|
28740
28819
|
const out = { ...step };
|
|
28741
28820
|
for (const f of JUMP_FIELDS) {
|
|
28821
|
+
if (isPinned(step, f)) continue;
|
|
28742
28822
|
if (typeof out[f] === "number" && hit(out[f], f === "endStep")) out[f] = out[f] + deltaN;
|
|
28743
28823
|
}
|
|
28744
28824
|
for (const lf of JUMP_LIST_FIELDS) {
|
|
28745
28825
|
if (Array.isArray(out[lf])) {
|
|
28746
28826
|
out[lf] = out[lf].map(
|
|
28747
|
-
(c) => typeof c?.step === "number" && hit(c.step) ? { ...c, step: c.step + deltaN } : c
|
|
28827
|
+
(c) => !isPinnedEntry(c) && typeof c?.step === "number" && hit(c.step) ? { ...c, step: c.step + deltaN } : c
|
|
28748
28828
|
);
|
|
28749
28829
|
}
|
|
28750
28830
|
}
|
|
@@ -28752,14 +28832,39 @@ var SpecWorkspace = class {
|
|
|
28752
28832
|
};
|
|
28753
28833
|
const jumpRefsTo = (step, target) => {
|
|
28754
28834
|
const refs = [];
|
|
28755
|
-
for (const f of JUMP_FIELDS) if (step[f] === target) refs.push(f);
|
|
28835
|
+
for (const f of JUMP_FIELDS) if (step[f] === target && !isPinned(step, f)) refs.push(f);
|
|
28756
28836
|
for (const lf of JUMP_LIST_FIELDS) {
|
|
28757
28837
|
(Array.isArray(step[lf]) ? step[lf] : []).forEach((c, i) => {
|
|
28758
|
-
if (c?.step === target) refs.push(`${lf}[${i}].step`);
|
|
28838
|
+
if (c?.step === target && !isPinnedEntry(c)) refs.push(`${lf}[${i}].step`);
|
|
28759
28839
|
});
|
|
28760
28840
|
}
|
|
28761
28841
|
return refs;
|
|
28762
28842
|
};
|
|
28843
|
+
const pinDeltaJumps = (deltaStep, carried) => {
|
|
28844
|
+
const pins = new Set(carried ?? []);
|
|
28845
|
+
for (const f of JUMP_FIELDS) if (typeof deltaStep[f] === "number") pins.add(f);
|
|
28846
|
+
const out = { ...deltaStep };
|
|
28847
|
+
for (const lf of JUMP_LIST_FIELDS) {
|
|
28848
|
+
if (!Array.isArray(out[lf])) continue;
|
|
28849
|
+
out[lf] = out[lf].map((c) => c && typeof c === "object" && typeof c.step === "number" ? { ...c, [DELTA_JUMP_PINS]: true } : c);
|
|
28850
|
+
}
|
|
28851
|
+
out[DELTA_JUMP_PINS] = pins;
|
|
28852
|
+
return out;
|
|
28853
|
+
};
|
|
28854
|
+
const unpinned = (step) => {
|
|
28855
|
+
const out = { ...step };
|
|
28856
|
+
delete out[DELTA_JUMP_PINS];
|
|
28857
|
+
for (const lf of JUMP_LIST_FIELDS) {
|
|
28858
|
+
if (!Array.isArray(out[lf])) continue;
|
|
28859
|
+
out[lf] = out[lf].map((c) => {
|
|
28860
|
+
if (!c || typeof c !== "object" || !(DELTA_JUMP_PINS in c)) return c;
|
|
28861
|
+
const entry = { ...c };
|
|
28862
|
+
delete entry[DELTA_JUMP_PINS];
|
|
28863
|
+
return entry;
|
|
28864
|
+
});
|
|
28865
|
+
}
|
|
28866
|
+
return out;
|
|
28867
|
+
};
|
|
28763
28868
|
const normalizeIdentity = (k) => String(k ?? "").toLowerCase().replace(/[_\-\s]/g, "");
|
|
28764
28869
|
const unsetFieldsOf = (item) => Array.isArray(item?.unset) ? item.unset.filter((f) => typeof f === "string") : [];
|
|
28765
28870
|
const withUnset = (merged, deltaItem) => {
|
|
@@ -28939,21 +29044,24 @@ var SpecWorkspace = class {
|
|
|
28939
29044
|
capture
|
|
28940
29045
|
));
|
|
28941
29046
|
const { action, remove: remove2, captureJumps, ...cleanStep } = deltaStep;
|
|
28942
|
-
steps.push(withUnset(cleanStep, deltaStep));
|
|
29047
|
+
steps.push(withUnset(pinDeltaJumps(cleanStep), deltaStep));
|
|
28943
29048
|
} else {
|
|
28944
29049
|
const idx = steps.findIndex((s) => s.stepNumber === stepNum);
|
|
28945
29050
|
if (idx !== -1) {
|
|
28946
|
-
const { action, remove: remove2, ...
|
|
29051
|
+
const { action, remove: remove2, ...rawStep } = deltaStep;
|
|
28947
29052
|
const stored = steps[idx];
|
|
29053
|
+
const cleanStep = pinDeltaJumps(rawStep, stored[DELTA_JUMP_PINS]);
|
|
28948
29054
|
const merged = withUnset(retargetByLabel(mergeElement(stored, cleanStep), cleanStep), deltaStep);
|
|
28949
|
-
|
|
29055
|
+
const rebuilt = rebuildOnRetype(stored, rawStep, merged, methodName);
|
|
29056
|
+
rebuilt[DELTA_JUMP_PINS] = cleanStep[DELTA_JUMP_PINS];
|
|
29057
|
+
steps[idx] = rebuilt;
|
|
28950
29058
|
} else {
|
|
28951
29059
|
const { action, remove: remove2, ...cleanStep } = deltaStep;
|
|
28952
|
-
steps.push(withUnset(cleanStep, deltaStep));
|
|
29060
|
+
steps.push(withUnset(pinDeltaJumps(cleanStep), deltaStep));
|
|
28953
29061
|
}
|
|
28954
29062
|
}
|
|
28955
29063
|
}
|
|
28956
|
-
return steps.sort((a, b) => a.stepNumber - b.stepNumber);
|
|
29064
|
+
return steps.map(unpinned).sort((a, b) => a.stepNumber - b.stepNumber);
|
|
28957
29065
|
};
|
|
28958
29066
|
const isPlainObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
28959
29067
|
const mergeExt = (existing, delta2) => {
|
|
@@ -28976,6 +29084,11 @@ var SpecWorkspace = class {
|
|
|
28976
29084
|
} else {
|
|
28977
29085
|
const existingMethod = merged[idx];
|
|
28978
29086
|
let narrative = existingMethod.narrative ? [...existingMethod.narrative] : [];
|
|
29087
|
+
if (deltaMethod.narrative !== void 0 && deltaMethod.narrative !== null && !Array.isArray(deltaMethod.narrative)) {
|
|
29088
|
+
throw new Error(
|
|
29089
|
+
`Refusing to update implementation method "${String(deltaMethod.name)}": its "narrative" must be a list of step deltas, got ${JSON.stringify(deltaMethod.narrative)}.`
|
|
29090
|
+
);
|
|
29091
|
+
}
|
|
28979
29092
|
if (Array.isArray(deltaMethod.narrative)) {
|
|
28980
29093
|
narrative = deltaMethod.narrative.length === 0 ? [] : mergeNarrative(String(deltaMethod.name), narrative, deltaMethod.narrative);
|
|
28981
29094
|
}
|
|
@@ -28988,11 +29101,21 @@ var SpecWorkspace = class {
|
|
|
28988
29101
|
}
|
|
28989
29102
|
} else {
|
|
28990
29103
|
assertUnmatchedIsAnAddition(deltaMethod, deltaMethod.name, merged.map((m) => m.name), "method");
|
|
28991
|
-
merged.push(withUnset(deltaMethod, deltaMethod));
|
|
29104
|
+
merged.push(withUnset(newMethodOf(deltaMethod), deltaMethod));
|
|
28992
29105
|
}
|
|
28993
29106
|
}
|
|
28994
29107
|
return merged;
|
|
28995
29108
|
};
|
|
29109
|
+
const newMethodOf = (deltaMethod) => {
|
|
29110
|
+
if (kind !== "implementation") return deltaMethod;
|
|
29111
|
+
if (deltaMethod.narrative === void 0 || deltaMethod.narrative === null) return { ...deltaMethod, narrative: [] };
|
|
29112
|
+
if (!Array.isArray(deltaMethod.narrative)) {
|
|
29113
|
+
throw new Error(
|
|
29114
|
+
`Refusing to add implementation method "${String(deltaMethod.name)}": its "narrative" must be a list of steps, got ${JSON.stringify(deltaMethod.narrative)}. Leave it out (or pass []) for an intent-level method.`
|
|
29115
|
+
);
|
|
29116
|
+
}
|
|
29117
|
+
return deltaMethod;
|
|
29118
|
+
};
|
|
28996
29119
|
const mergeNamedArray = (existing, delta2) => {
|
|
28997
29120
|
const merged = [...existing];
|
|
28998
29121
|
for (const deltaItem of delta2) {
|
|
@@ -29170,6 +29293,12 @@ var SpecWorkspace = class {
|
|
|
29170
29293
|
const qualifiedDelta = qualifyDeltaRefs(mergeableDelta);
|
|
29171
29294
|
const mergedResult = mergeDelta(result, qualifiedDelta);
|
|
29172
29295
|
for (const field of unsetFields) delete mergedResult[field];
|
|
29296
|
+
const withDefaults = deltaSchema.safeParse(mergedResult);
|
|
29297
|
+
if (withDefaults.success) {
|
|
29298
|
+
for (const [key, value] of Object.entries(withDefaults.data)) {
|
|
29299
|
+
if (!(key in mergedResult)) mergedResult[key] = value;
|
|
29300
|
+
}
|
|
29301
|
+
}
|
|
29173
29302
|
if (kind === "implementation" && Array.isArray(mergedResult.methods)) {
|
|
29174
29303
|
const labelErrors = [];
|
|
29175
29304
|
for (const m of mergedResult.methods) {
|
|
@@ -29690,6 +29819,7 @@ var PROSE_FIELDS = /* @__PURE__ */ new Set([
|
|
|
29690
29819
|
"note",
|
|
29691
29820
|
"caller"
|
|
29692
29821
|
]);
|
|
29822
|
+
var DELTA_JUMP_PINS = /* @__PURE__ */ Symbol("jumps written by this delta");
|
|
29693
29823
|
function cloneSpec(spec) {
|
|
29694
29824
|
return JSON.parse(JSON.stringify(spec));
|
|
29695
29825
|
}
|
|
@@ -30169,6 +30299,17 @@ function unregisterDomain(id) {
|
|
|
30169
30299
|
|
|
30170
30300
|
// src/core/external-producers.ts
|
|
30171
30301
|
var path21 = __toESM(require("path"));
|
|
30302
|
+
function hostedOnlyProducer(id) {
|
|
30303
|
+
return `hosted-only producer \`${id}\`: available only through the hosted server`;
|
|
30304
|
+
}
|
|
30305
|
+
function resolveHosted(external) {
|
|
30306
|
+
if (external.sourceKind !== "hosted" || external.hosted === void 0) return external;
|
|
30307
|
+
const lookup2 = getHostedLookup();
|
|
30308
|
+
const unresolved = (problem) => ({ ...external, sourceKind: "unresolved", problem });
|
|
30309
|
+
if (!lookup2) return unresolved(hostedOnlyProducer(external.hosted));
|
|
30310
|
+
const root = lookup2(external.hosted);
|
|
30311
|
+
return root === null ? unresolved(`the hosted producer \`${external.hosted}\` is unknown or outside this request's reach`) : { ...external, directory: path21.resolve(root) };
|
|
30312
|
+
}
|
|
30172
30313
|
function dirKey2(dir) {
|
|
30173
30314
|
const resolved = path21.resolve(dir);
|
|
30174
30315
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
@@ -30194,11 +30335,14 @@ function resolveDeclared() {
|
|
|
30194
30335
|
const family = graph();
|
|
30195
30336
|
const node = consumerNode(family, bound2);
|
|
30196
30337
|
if (!node) return null;
|
|
30197
|
-
return node.externals.map((
|
|
30198
|
-
external
|
|
30199
|
-
|
|
30200
|
-
|
|
30201
|
-
|
|
30338
|
+
return node.externals.map((declared) => {
|
|
30339
|
+
const external = resolveHosted(declared);
|
|
30340
|
+
return {
|
|
30341
|
+
external,
|
|
30342
|
+
...external.sourceKind === "family" && external.producer !== void 0 ? { usage: exportUsage(node.namespace, external.producer) } : {},
|
|
30343
|
+
reachable: whole
|
|
30344
|
+
};
|
|
30345
|
+
});
|
|
30202
30346
|
});
|
|
30203
30347
|
return answer(top) ?? answer(bound2) ?? [];
|
|
30204
30348
|
}
|
|
@@ -30881,7 +31025,7 @@ function carryDeclarations(scan, result) {
|
|
|
30881
31025
|
const bound2 = projectConfigRepository.load() ?? {};
|
|
30882
31026
|
for (const [alias, decl] of Object.entries(scan.config?.externals ?? {})) {
|
|
30883
31027
|
if (carriedExternal(scan, alias, decl) === "parent") continue;
|
|
30884
|
-
const source = decl.source && !path22.isAbsolute(decl.source.path) ? { path: toPosixPath(path22.relative(root, path22.resolve(scan.memberDir, decl.source.path))) } : decl.source;
|
|
31028
|
+
const source = decl.source?.path !== void 0 && !path22.isAbsolute(decl.source.path) ? { path: toPosixPath(path22.relative(root, path22.resolve(scan.memberDir, decl.source.path))) } : decl.source;
|
|
30885
31029
|
if (bound2.members?.[alias] === void 0) {
|
|
30886
31030
|
projectConfigRepository.declareExternal(alias, {
|
|
30887
31031
|
...decl.project !== void 0 ? { project: decl.project } : {},
|
|
@@ -32224,17 +32368,17 @@ function memberHop(agentId) {
|
|
|
32224
32368
|
const alias = agentId.slice(0, at);
|
|
32225
32369
|
const member = graph().nodes.find((n) => n.parent === "" && n.mountAlias === alias);
|
|
32226
32370
|
if (!member) return null;
|
|
32227
|
-
const
|
|
32228
|
-
return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative:
|
|
32371
|
+
const relative19 = path26.relative(getProjectRoot(), member.directory).split(path26.sep).join("/");
|
|
32372
|
+
return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative: relative19 };
|
|
32229
32373
|
}
|
|
32230
|
-
function throughMount(brief, alias,
|
|
32231
|
-
const under = (p) => path26.posix.join(
|
|
32374
|
+
function throughMount(brief, alias, relative19) {
|
|
32375
|
+
const under = (p) => path26.posix.join(relative19, p);
|
|
32232
32376
|
return {
|
|
32233
32377
|
...brief,
|
|
32234
32378
|
agentId: `${alias}::${brief.agentId}`,
|
|
32235
32379
|
ownedPaths: brief.ownedPaths.map(under),
|
|
32236
32380
|
...brief.readPaths ? { readPaths: brief.readPaths.map(under) } : {},
|
|
32237
|
-
root: brief.root ? under(brief.root) :
|
|
32381
|
+
root: brief.root ? under(brief.root) : relative19
|
|
32238
32382
|
};
|
|
32239
32383
|
}
|
|
32240
32384
|
function loadRegistry() {
|
|
@@ -32513,19 +32657,19 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
|
|
|
32513
32657
|
const backupDir = path31.join(root, BACKUP_DIR, timestampSlug());
|
|
32514
32658
|
let backedUp = false;
|
|
32515
32659
|
for (const rootRel of roots) {
|
|
32516
|
-
const
|
|
32517
|
-
const stagedSpecDir = path31.join(stagingDir,
|
|
32660
|
+
const relative19 = rootRel === "." ? "" : rootRel;
|
|
32661
|
+
const stagedSpecDir = path31.join(stagingDir, relative19, ".wai");
|
|
32518
32662
|
if (!fs17.existsSync(stagedSpecDir)) continue;
|
|
32519
|
-
const liveSpecDir = liveSpecDirFor(path31.join(root,
|
|
32663
|
+
const liveSpecDir = liveSpecDirFor(path31.join(root, relative19));
|
|
32520
32664
|
if (backupExisting && liveSpecDir && fs17.existsSync(liveSpecDir)) {
|
|
32521
|
-
const backupTarget = path31.join(backupDir,
|
|
32665
|
+
const backupTarget = path31.join(backupDir, relative19, path31.basename(liveSpecDir));
|
|
32522
32666
|
fs17.mkdirSync(path31.dirname(backupTarget), { recursive: true });
|
|
32523
32667
|
fs17.renameSync(liveSpecDir, backupTarget);
|
|
32524
32668
|
backedUp = true;
|
|
32525
32669
|
} else if (liveSpecDir && fs17.existsSync(liveSpecDir)) {
|
|
32526
32670
|
fs17.rmSync(liveSpecDir, { recursive: true, force: true });
|
|
32527
32671
|
}
|
|
32528
|
-
const target = path31.join(root,
|
|
32672
|
+
const target = path31.join(root, relative19, ".wai");
|
|
32529
32673
|
fs17.mkdirSync(path31.dirname(target), { recursive: true });
|
|
32530
32674
|
if (fs17.existsSync(target)) fs17.rmSync(target, { recursive: true, force: true });
|
|
32531
32675
|
fs17.renameSync(stagedSpecDir, target);
|
|
@@ -32583,7 +32727,7 @@ function importSpecTree(archive, options) {
|
|
|
32583
32727
|
throw new Error("incompatible .waitree archive (format or minimum wairon version)");
|
|
32584
32728
|
}
|
|
32585
32729
|
const destDir = path31.resolve(options.destDir ?? getProjectRoot());
|
|
32586
|
-
const
|
|
32730
|
+
const occupied2 = hasSpecTree(destDir);
|
|
32587
32731
|
const authored = hasAuthoredSpecs(destDir);
|
|
32588
32732
|
if (authored && options.replaceExisting !== true) {
|
|
32589
32733
|
throw new Error(
|
|
@@ -32598,7 +32742,7 @@ function importSpecTree(archive, options) {
|
|
|
32598
32742
|
discardStagingDir(stagingDir);
|
|
32599
32743
|
throw err;
|
|
32600
32744
|
}
|
|
32601
|
-
const backupPath = commitStagedTree(stagingDir, destDir, extraction.roots,
|
|
32745
|
+
const backupPath = commitStagedTree(stagingDir, destDir, extraction.roots, occupied2);
|
|
32602
32746
|
discardStagingDir(stagingDir);
|
|
32603
32747
|
invalidateSpecCache();
|
|
32604
32748
|
const result = {
|
|
@@ -32606,7 +32750,7 @@ function importSpecTree(archive, options) {
|
|
|
32606
32750
|
projectName: extraction.manifest.projectName,
|
|
32607
32751
|
roots: extraction.roots,
|
|
32608
32752
|
fileCount: extraction.writtenPaths.length,
|
|
32609
|
-
replaced:
|
|
32753
|
+
replaced: occupied2
|
|
32610
32754
|
};
|
|
32611
32755
|
if (backupPath) result.backupPath = backupPath;
|
|
32612
32756
|
if (extraction.manifest.stateId) result.stateId = extraction.manifest.stateId;
|
|
@@ -33108,7 +33252,7 @@ var CONTEXT_PATHS = {
|
|
|
33108
33252
|
waironGuideMd: () => contextDir("wairon-guide.md")
|
|
33109
33253
|
};
|
|
33110
33254
|
function hasContext() {
|
|
33111
|
-
return pathExists(CONTEXT_PATHS.
|
|
33255
|
+
return pathExists(CONTEXT_PATHS.dir());
|
|
33112
33256
|
}
|
|
33113
33257
|
function readProjectContext() {
|
|
33114
33258
|
return readFileOrNull(CONTEXT_PATHS.projectMd());
|
|
@@ -35205,6 +35349,7 @@ function pin(p, log) {
|
|
|
35205
35349
|
}
|
|
35206
35350
|
|
|
35207
35351
|
// src/migrations/membership.ts
|
|
35352
|
+
var fs22 = __toESM(require("fs"));
|
|
35208
35353
|
var path40 = __toESM(require("path"));
|
|
35209
35354
|
|
|
35210
35355
|
// src/migrations/family-files.ts
|
|
@@ -35285,39 +35430,93 @@ function within2(root, candidate) {
|
|
|
35285
35430
|
const rel2 = nodePath.relative(root, candidate);
|
|
35286
35431
|
return rel2 === "" || !rel2.startsWith("..") && !nodePath.isAbsolute(rel2);
|
|
35287
35432
|
}
|
|
35433
|
+
function areaFiles(owner, area) {
|
|
35434
|
+
const out = [];
|
|
35435
|
+
const walk2 = (rel2) => {
|
|
35436
|
+
if (rel2 === `${WAI}/${TRANSACTIONS}`) return;
|
|
35437
|
+
const abs = nodePath.join(owner, ...rel2.split("/"));
|
|
35438
|
+
let stat;
|
|
35439
|
+
try {
|
|
35440
|
+
stat = fs21.lstatSync(abs);
|
|
35441
|
+
} catch {
|
|
35442
|
+
return;
|
|
35443
|
+
}
|
|
35444
|
+
if (stat.isSymbolicLink()) return;
|
|
35445
|
+
if (stat.isFile()) {
|
|
35446
|
+
out.push(rel2);
|
|
35447
|
+
return;
|
|
35448
|
+
}
|
|
35449
|
+
if (!stat.isDirectory()) return;
|
|
35450
|
+
for (const name of fs21.readdirSync(abs)) walk2(`${rel2}/${name}`);
|
|
35451
|
+
};
|
|
35452
|
+
walk2(area.split(nodePath.sep).join("/").replace(/\/+$/, ""));
|
|
35453
|
+
return out.sort();
|
|
35454
|
+
}
|
|
35455
|
+
function everyFile(owner) {
|
|
35456
|
+
const out = [];
|
|
35457
|
+
const walk2 = (dir, rel2) => {
|
|
35458
|
+
let entries;
|
|
35459
|
+
try {
|
|
35460
|
+
entries = fs21.readdirSync(dir, { withFileTypes: true });
|
|
35461
|
+
} catch {
|
|
35462
|
+
return;
|
|
35463
|
+
}
|
|
35464
|
+
for (const entry of entries) {
|
|
35465
|
+
const relPath = rel2 ? `${rel2}/${entry.name}` : entry.name;
|
|
35466
|
+
if (relPath === `${WAI}/${TRANSACTIONS}`) continue;
|
|
35467
|
+
if (entry.isSymbolicLink()) continue;
|
|
35468
|
+
if (entry.isDirectory()) walk2(nodePath.join(dir, entry.name), relPath);
|
|
35469
|
+
else if (entry.isFile()) out.push(relPath);
|
|
35470
|
+
}
|
|
35471
|
+
};
|
|
35472
|
+
walk2(owner, "");
|
|
35473
|
+
return out.sort();
|
|
35474
|
+
}
|
|
35475
|
+
function insideWhole(whole, dir) {
|
|
35476
|
+
return whole.some((w) => within2(w, dir));
|
|
35477
|
+
}
|
|
35478
|
+
function scopedFiles(owner, areas) {
|
|
35479
|
+
return areas ? [...new Set(areas.flatMap((a) => areaFiles(owner, a)))].sort() : waiFiles(owner);
|
|
35480
|
+
}
|
|
35288
35481
|
function mirror(scope, target) {
|
|
35289
35482
|
const digests = /* @__PURE__ */ new Map();
|
|
35290
|
-
|
|
35291
|
-
|
|
35292
|
-
|
|
35293
|
-
|
|
35294
|
-
const at = nodePath.join(target, nodePath.relative(scope.familyRoot, project));
|
|
35295
|
-
for (const rel2 of waiFiles(project)) {
|
|
35296
|
-
const live = nodePath.join(project, ...rel2.split("/"));
|
|
35297
|
-
let bytes;
|
|
35298
|
-
try {
|
|
35299
|
-
bytes = fs21.readFileSync(live);
|
|
35300
|
-
} catch (e) {
|
|
35301
|
-
throw new Error(`could not copy ${live} into the rehearsal: ${e instanceof Error ? e.message : String(e)}`);
|
|
35302
|
-
}
|
|
35303
|
-
const copy = nodePath.join(at, ...rel2.split("/"));
|
|
35304
|
-
fs21.mkdirSync(nodePath.dirname(copy), { recursive: true });
|
|
35305
|
-
fs21.writeFileSync(copy, bytes);
|
|
35306
|
-
digests.set(live, sha2562(bytes));
|
|
35483
|
+
const whole = scope.whole ?? [];
|
|
35484
|
+
for (const owner of [...scope.projects, ...whole]) {
|
|
35485
|
+
if (!within2(scope.familyRoot, owner)) {
|
|
35486
|
+
throw new Error(`${owner} does not lie within the family root ${scope.familyRoot}; a rehearsal copies only the family's own trees`);
|
|
35307
35487
|
}
|
|
35308
35488
|
}
|
|
35489
|
+
for (const project of scope.projects.filter((p) => !insideWhole(whole, p))) {
|
|
35490
|
+
copyFiles(project, scopedFiles(project, scope.areas), nodePath.join(target, nodePath.relative(scope.familyRoot, project)), digests);
|
|
35491
|
+
}
|
|
35492
|
+
for (const owner of whole) {
|
|
35493
|
+
copyFiles(owner, everyFile(owner), nodePath.join(target, nodePath.relative(scope.familyRoot, owner)), digests);
|
|
35494
|
+
}
|
|
35309
35495
|
return digests;
|
|
35310
35496
|
}
|
|
35497
|
+
function copyFiles(owner, rels, at, digests) {
|
|
35498
|
+
fs21.mkdirSync(at, { recursive: true });
|
|
35499
|
+
for (const rel2 of rels) {
|
|
35500
|
+
const live = nodePath.join(owner, ...rel2.split("/"));
|
|
35501
|
+
let bytes;
|
|
35502
|
+
try {
|
|
35503
|
+
bytes = fs21.readFileSync(live);
|
|
35504
|
+
} catch (e) {
|
|
35505
|
+
throw new Error(`could not copy ${live} into the rehearsal: ${e instanceof Error ? e.message : String(e)}`);
|
|
35506
|
+
}
|
|
35507
|
+
const copy = nodePath.join(at, ...rel2.split("/"));
|
|
35508
|
+
fs21.mkdirSync(nodePath.dirname(copy), { recursive: true });
|
|
35509
|
+
fs21.writeFileSync(copy, bytes);
|
|
35510
|
+
digests.set(live, sha2562(bytes));
|
|
35511
|
+
}
|
|
35512
|
+
}
|
|
35311
35513
|
function compare4(rehearsal) {
|
|
35312
35514
|
const changes = [];
|
|
35313
35515
|
const seen = /* @__PURE__ */ new Set();
|
|
35314
|
-
for (const
|
|
35315
|
-
|
|
35316
|
-
|
|
35317
|
-
|
|
35318
|
-
const change = changeOf(rehearsal, live, rel2, sha2562(fs21.readFileSync(nodePath.join(owner, ...rel2.split("/")))));
|
|
35319
|
-
if (change) changes.push(change);
|
|
35320
|
-
}
|
|
35516
|
+
for (const [live, copyRoot, rel2] of comparedFiles(rehearsal)) {
|
|
35517
|
+
seen.add(nodePath.join(live, ...rel2.split("/")));
|
|
35518
|
+
const change = changeOf(rehearsal, live, rel2, sha2562(fs21.readFileSync(nodePath.join(copyRoot, ...rel2.split("/")))));
|
|
35519
|
+
if (change) changes.push(change);
|
|
35321
35520
|
}
|
|
35322
35521
|
for (const [liveFile, base] of rehearsal.baseDigests) {
|
|
35323
35522
|
if (seen.has(liveFile)) continue;
|
|
@@ -35326,6 +35525,16 @@ function compare4(rehearsal) {
|
|
|
35326
35525
|
}
|
|
35327
35526
|
return changes.sort(byOwnerThenPath);
|
|
35328
35527
|
}
|
|
35528
|
+
function comparedFiles(rehearsal) {
|
|
35529
|
+
const whole = rehearsal.whole ?? [];
|
|
35530
|
+
const owners = rehearsal.areas ? [...rehearsal.roots].map(([live, copy]) => [live, copy, scopedFiles(copy, rehearsal.areas)]) : waiOwners(rehearsal.directory).map((copy) => [nodePath.join(rehearsal.familyRoot, nodePath.relative(rehearsal.directory, copy)), copy, waiFiles(copy)]);
|
|
35531
|
+
const imageOf = (live) => nodePath.join(rehearsal.directory, nodePath.relative(rehearsal.familyRoot, live));
|
|
35532
|
+
const all = [
|
|
35533
|
+
...owners.filter(([live]) => !insideWhole(whole, live)),
|
|
35534
|
+
...whole.map((live) => [live, imageOf(live), everyFile(imageOf(live))])
|
|
35535
|
+
];
|
|
35536
|
+
return all.flatMap(([live, copy, rels]) => rels.map((rel2) => [live, copy, rel2]));
|
|
35537
|
+
}
|
|
35329
35538
|
function changeOf(rehearsal, live, rel2, staged) {
|
|
35330
35539
|
const base = rehearsal.baseDigests.get(nodePath.join(live, ...rel2.split("/")));
|
|
35331
35540
|
if (base === staged) return null;
|
|
@@ -35334,9 +35543,23 @@ function changeOf(rehearsal, live, rel2, staged) {
|
|
|
35334
35543
|
var order = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
35335
35544
|
var byOwnerThenPath = (a, b) => order(a.project, b.project) || order(a.path, b.path);
|
|
35336
35545
|
function ownerOf3(rehearsal, liveFile) {
|
|
35337
|
-
const
|
|
35546
|
+
const whole = (rehearsal.whole ?? []).find((w) => within2(w, liveFile));
|
|
35547
|
+
if (whole !== void 0) return whole;
|
|
35548
|
+
const roots = [...rehearsal.roots.keys()].filter((r) => within2(rehearsal.areas ? r : nodePath.join(r, WAI), liveFile));
|
|
35338
35549
|
return roots.sort((a, b) => b.length - a.length)[0] ?? nodePath.dirname(nodePath.dirname(liveFile));
|
|
35339
35550
|
}
|
|
35551
|
+
function move(from, to) {
|
|
35552
|
+
const rels = everyFile(from);
|
|
35553
|
+
const taken = rels.find((rel2) => fs21.existsSync(nodePath.join(to, ...rel2.split("/"))));
|
|
35554
|
+
if (taken !== void 0) throw new Error(`cannot move ${from} to ${to}: ${nodePath.join(to, ...taken.split("/"))} already exists`);
|
|
35555
|
+
for (const rel2 of rels) {
|
|
35556
|
+
const target = nodePath.join(to, ...rel2.split("/"));
|
|
35557
|
+
makeDirs(nodePath.dirname(target));
|
|
35558
|
+
replace(nodePath.join(from, ...rel2.split("/")), target);
|
|
35559
|
+
}
|
|
35560
|
+
for (const rel2 of rels) prune(nodePath.dirname(nodePath.join(from, ...rel2.split("/"))), nodePath.dirname(from));
|
|
35561
|
+
prune(from, nodePath.dirname(from));
|
|
35562
|
+
}
|
|
35340
35563
|
function read(path46) {
|
|
35341
35564
|
try {
|
|
35342
35565
|
return fs21.readFileSync(path46);
|
|
@@ -35478,6 +35701,13 @@ function hasSystemAt(dir) {
|
|
|
35478
35701
|
}
|
|
35479
35702
|
});
|
|
35480
35703
|
}
|
|
35704
|
+
function occupied(dir) {
|
|
35705
|
+
try {
|
|
35706
|
+
return !fs22.statSync(dir).isDirectory() || fs22.readdirSync(dir).length > 0;
|
|
35707
|
+
} catch {
|
|
35708
|
+
return false;
|
|
35709
|
+
}
|
|
35710
|
+
}
|
|
35481
35711
|
function boundNode(family, bound2) {
|
|
35482
35712
|
return familyNode(family, bound2);
|
|
35483
35713
|
}
|
|
@@ -35551,8 +35781,11 @@ function planDetach(family, bound2, request) {
|
|
|
35551
35781
|
const plan5 = emptyPlan(family, request);
|
|
35552
35782
|
const alias = request.alias ?? "";
|
|
35553
35783
|
const done = configAt3(familyNode(family, bound2).directory)?.externals?.[alias];
|
|
35554
|
-
if (done?.source?.path && !family.nodes.some((n) => n.parent === bound2 && n.mountAlias === alias)) return plan5;
|
|
35784
|
+
if ((done?.source?.path || done?.source?.hosted) && !family.nodes.some((n) => n.parent === bound2 && n.mountAlias === alias)) return plan5;
|
|
35555
35785
|
const member = memberUnder(family, bound2, alias, plan5);
|
|
35786
|
+
if (member && request.relocation && occupied(request.relocation.to)) {
|
|
35787
|
+
refuse(plan5, "relocation-target-exists", bound2, `the member "${alias}" cannot move to ${request.relocation.to}: it already exists`);
|
|
35788
|
+
}
|
|
35556
35789
|
if (!member) return plan5;
|
|
35557
35790
|
const table = resolveProjectExports(member.namespace || void 0);
|
|
35558
35791
|
const leaving = (key) => key !== void 0 && withinMember(family, member, key);
|
|
@@ -35605,27 +35838,34 @@ function planWidening(plan5, member, narrow) {
|
|
|
35605
35838
|
}
|
|
35606
35839
|
function detachEdits(plan5, family, bound2, member, alias, declared, consumers, outward) {
|
|
35607
35840
|
const boundDir = familyNode(family, bound2).directory;
|
|
35608
|
-
|
|
35841
|
+
const relocation = plan5.request.relocation;
|
|
35842
|
+
const sourceTo = (from, producer) => relocation ? { hosted: producer.id ?? producer.mountAlias ?? producer.namespace } : { path: posix3(path40.relative(from, producer.directory)) };
|
|
35843
|
+
const where = (source) => source.hosted !== void 0 ? `hosted record ${source.hosted}` : source.path;
|
|
35844
|
+
edit(plan5, bound2, "member", `members: ${alias} removed (${declared.path} ${relocation ? `moves to ${relocation.to}` : "stays as it is"})`, { root: boundDir, call: "removeMember", args: [alias] });
|
|
35845
|
+
const boundSource = relocation ? sourceTo(boundDir, member) : { path: declared.path };
|
|
35609
35846
|
const external = {
|
|
35610
35847
|
...member.id !== void 0 && member.id !== alias ? { project: member.id } : {},
|
|
35611
|
-
source:
|
|
35848
|
+
source: boundSource,
|
|
35612
35849
|
...declared.use && declared.use.length > 0 ? { use: declared.use } : {},
|
|
35613
35850
|
...declared.description !== void 0 ? { description: declared.description } : {}
|
|
35614
35851
|
};
|
|
35615
|
-
edit(plan5, bound2, "external", `externals: ${alias} \u2192 ${member.id ?? alias} at ${
|
|
35852
|
+
edit(plan5, bound2, "external", `externals: ${alias} \u2192 ${member.id ?? alias} at ${where(boundSource)}`, { root: boundDir, call: "declareExternal", args: [alias, external] });
|
|
35616
35853
|
for (const c of consumers) {
|
|
35617
35854
|
const held = configAt3(c.node.directory)?.externals?.[c.alias];
|
|
35618
|
-
const source =
|
|
35619
|
-
edit(plan5, c.node.namespace, "external", `externals: ${c.alias} found at ${source
|
|
35855
|
+
const source = sourceTo(c.node.directory, member);
|
|
35856
|
+
edit(plan5, c.node.namespace, "external", `externals: ${c.alias} found at ${where(source)} (it leaves the family)`, { root: c.node.directory, call: "repointExternal", args: [c.alias, held?.project ?? null, source] });
|
|
35620
35857
|
}
|
|
35621
35858
|
const own = configAt3(member.directory)?.externals ?? {};
|
|
35622
35859
|
for (const e of outward) {
|
|
35623
35860
|
const producer = familyNode(family, e.producer);
|
|
35624
|
-
const source =
|
|
35625
|
-
edit(plan5, member.namespace, "external", `externals: ${e.alias} found at ${source
|
|
35861
|
+
const source = sourceTo(member.directory, producer);
|
|
35862
|
+
edit(plan5, member.namespace, "external", `externals: ${e.alias} found at ${where(source)} (the member leaves the family)`, { root: member.directory, call: "repointExternal", args: [e.alias, own[e.alias]?.project ?? null, source] });
|
|
35863
|
+
}
|
|
35864
|
+
if (relocation) {
|
|
35865
|
+
edit(plan5, member.namespace, "move", `${member.directory} moved to ${relocation.to} (its own isolated root), its own members with it`, { root: member.directory, call: "move", args: [relocation.to] });
|
|
35626
35866
|
}
|
|
35627
35867
|
edit(plan5, bound2, "pin", `pin ${alias} (the detached project's surface, read last)`, { root: boundDir, call: "pinExternals", args: [[alias]] });
|
|
35628
|
-
plan5.notes.push(`Consumers outside the family are not affected: they already name ${member.id ?? alias} by path, or not at all.`);
|
|
35868
|
+
plan5.notes.push(`Consumers outside the family are not affected: they already name ${member.id ?? alias} by ${relocation ? "hosted record id" : "path"}, or not at all.`);
|
|
35629
35869
|
}
|
|
35630
35870
|
function planAdopt(family, bound2, request) {
|
|
35631
35871
|
const plan5 = emptyPlan(family, request);
|
|
@@ -35636,6 +35876,7 @@ function planAdopt(family, bound2, request) {
|
|
|
35636
35876
|
if (!external && boundConfig?.members?.[alias] !== void 0) return plan5;
|
|
35637
35877
|
let dir = null;
|
|
35638
35878
|
if (!external) refuse(plan5, "not-an-external", bound2, `${label4(bound2)} declares no external "${alias}"`);
|
|
35879
|
+
else if (request.relocation) return planRelocatedAdopt(plan5, family, bound2, alias, external, request.relocation);
|
|
35639
35880
|
else {
|
|
35640
35881
|
dir = resolve30(boundDir, external.source?.path ?? "");
|
|
35641
35882
|
if (dir === null) refuse(plan5, "not-contained", bound2, `the external "${alias}" ${external.source?.path ? `is found at "${external.source.path}", which is not strictly within ${label4(bound2)}` : "names no source.path"}`);
|
|
@@ -35644,7 +35885,18 @@ function planAdopt(family, bound2, request) {
|
|
|
35644
35885
|
if (plan5.refusals.length > 0) return plan5;
|
|
35645
35886
|
return adoptEdits(plan5, family, bound2, alias, external, dir);
|
|
35646
35887
|
}
|
|
35647
|
-
function
|
|
35888
|
+
function planRelocatedAdopt(plan5, family, bound2, alias, external, relocation) {
|
|
35889
|
+
const boundDir = familyNode(family, bound2).directory;
|
|
35890
|
+
const target = resolve30(boundDir, plan5.request.path ?? "");
|
|
35891
|
+
if (!external.source?.hosted) refuse(plan5, "not-contained", bound2, `the external "${alias}" names no source.hosted \u2014 a hosted adopt moves a project named by its hosted record id`);
|
|
35892
|
+
else if (target === null || path40.resolve(target) !== path40.resolve(relocation.to)) refuse(plan5, "not-contained", bound2, `the member path "${plan5.request.path ?? ""}" does not resolve strictly within ${label4(bound2)} to ${relocation.to}`);
|
|
35893
|
+
else if (occupied(relocation.to)) refuse(plan5, "relocation-target-exists", bound2, `the external "${alias}" cannot be adopted at ${relocation.to}: it already exists and is not an empty directory`);
|
|
35894
|
+
const from = relocation.from ?? (external.source?.hosted !== void 0 ? getHostedLookup()?.(external.source.hosted) ?? void 0 : void 0);
|
|
35895
|
+
if (plan5.refusals.length === 0 && !from) refuse(plan5, "not-a-project", bound2, `the hosted record "${external.source?.hosted}" is unknown or outside this request's reach \u2014 there is no root to adopt from`);
|
|
35896
|
+
if (plan5.refusals.length > 0) return plan5;
|
|
35897
|
+
return adoptEdits(plan5, family, bound2, alias, external, from, relocation.to);
|
|
35898
|
+
}
|
|
35899
|
+
function adoptEdits(plan5, family, bound2, alias, external, dir, at = dir) {
|
|
35648
35900
|
const boundDir = familyNode(family, bound2).directory;
|
|
35649
35901
|
const adoptee = configAt3(dir);
|
|
35650
35902
|
if (!adoptee && !hasSystemAt(dir)) {
|
|
@@ -35661,22 +35913,28 @@ function adoptEdits(plan5, family, bound2, alias, external, dir) {
|
|
|
35661
35913
|
edit(plan5, bound2, "pin", `pin ${alias} removed (the family supplies it now)`, { root: boundDir, call: "unpin", args: [alias] });
|
|
35662
35914
|
edit(plan5, bound2, "external", `externals: ${alias} removed`, { root: boundDir, call: "removeExternal", args: [alias] });
|
|
35663
35915
|
const declaration2 = {
|
|
35664
|
-
path: posix3(path40.relative(boundDir,
|
|
35916
|
+
path: posix3(path40.relative(boundDir, at)),
|
|
35665
35917
|
...external.description !== void 0 ? { description: external.description } : {},
|
|
35666
35918
|
...external.use && external.use.length > 0 ? { use: external.use } : {}
|
|
35667
35919
|
};
|
|
35668
35920
|
edit(plan5, bound2, "member", `members: ${alias} \u2192 ${declaration2.path}`, { root: boundDir, call: "declareMember", args: [alias, declaration2] });
|
|
35921
|
+
const namesAdoptee = (from, e) => e.source?.path !== void 0 && path40.resolve(from, e.source.path) === path40.resolve(dir) || plan5.request.relocation !== void 0 && id !== void 0 && e.source?.hosted === id;
|
|
35922
|
+
const sourceText = (e) => e.source?.hosted !== void 0 ? `source.hosted ${e.source.hosted}` : `source.path ${e.source?.path}`;
|
|
35669
35923
|
for (const n of family.nodes) {
|
|
35670
35924
|
for (const [a, e] of Object.entries(configAt3(n.directory)?.externals ?? {})) {
|
|
35671
35925
|
if (n.namespace === bound2 && a === alias) continue;
|
|
35672
|
-
if (!
|
|
35673
|
-
edit(plan5, n.namespace, "external", `externals: ${a} found through the family (its
|
|
35674
|
-
plan5.notes.push(`${label4(n.namespace)}'s external "${a}" named the adoptee by path; adopt removes it \u2014 detach-then-adopt is exact only when detach wrote that
|
|
35926
|
+
if (!namesAdoptee(n.directory, e)) continue;
|
|
35927
|
+
edit(plan5, n.namespace, "external", `externals: ${a} found through the family (its ${sourceText(e)} removed)`, { root: n.directory, call: "repointExternal", args: [a, e.project ?? null, null] });
|
|
35928
|
+
plan5.notes.push(`${label4(n.namespace)}'s external "${a}" named the adoptee by ${e.source?.hosted !== void 0 ? "hosted record id" : "path"}; adopt removes it \u2014 detach-then-adopt is exact only when detach wrote that source`);
|
|
35675
35929
|
}
|
|
35676
35930
|
}
|
|
35931
|
+
const namesFamily = (e) => e.source?.path !== void 0 && nodeAt(family, path40.resolve(dir, e.source.path)) !== void 0 || plan5.request.relocation !== void 0 && e.source?.hosted !== void 0 && family.nodes.some((n) => n.id === e.source.hosted);
|
|
35677
35932
|
for (const [a, e] of Object.entries(adoptee?.externals ?? {})) {
|
|
35678
|
-
if (!
|
|
35679
|
-
edit(plan5, key, "external", `externals: ${a} found through the family (its
|
|
35933
|
+
if (!namesFamily(e)) continue;
|
|
35934
|
+
edit(plan5, key, "external", `externals: ${a} found through the family (its ${sourceText(e)} removed)`, { root: dir, call: "repointExternal", args: [a, e.project ?? null, null] });
|
|
35935
|
+
}
|
|
35936
|
+
if (plan5.request.relocation) {
|
|
35937
|
+
edit(plan5, key, "move", `${dir} moved to ${at} (it joins the family's tree), its own members with it`, { root: dir, call: "move", args: [at] });
|
|
35680
35938
|
}
|
|
35681
35939
|
return plan5;
|
|
35682
35940
|
}
|
|
@@ -35688,11 +35946,13 @@ function write3(plan5, rehearsal) {
|
|
|
35688
35946
|
break;
|
|
35689
35947
|
case "detach":
|
|
35690
35948
|
for (const e of planned.filter((x) => x.write.call === "updateSpecGated")) writeOne(rehearsal, e.write);
|
|
35691
|
-
for (const e of planned.filter((x) =>
|
|
35949
|
+
for (const e of planned.filter((x) => !["pinExternals", "updateSpecGated", "move"].includes(x.write.call))) writeOne(rehearsal, e.write);
|
|
35950
|
+
for (const e of planned.filter((x) => x.write.call === "move")) writeOne(rehearsal, e.write);
|
|
35692
35951
|
for (const e of planned.filter((x) => x.write.call === "pinExternals")) writeOne(rehearsal, e.write);
|
|
35693
35952
|
break;
|
|
35694
35953
|
case "adopt":
|
|
35695
|
-
for (const e of planned) writeOne(rehearsal, e.write);
|
|
35954
|
+
for (const e of planned.filter((x) => x.write.call !== "move")) writeOne(rehearsal, e.write);
|
|
35955
|
+
for (const e of planned.filter((x) => x.write.call === "move")) writeOne(rehearsal, e.write);
|
|
35696
35956
|
break;
|
|
35697
35957
|
default:
|
|
35698
35958
|
throw new Error(`membership_migration writes attach, detach and adopt, not "${plan5.request.verb}"`);
|
|
@@ -35700,6 +35960,7 @@ function write3(plan5, rehearsal) {
|
|
|
35700
35960
|
}
|
|
35701
35961
|
function writeOne(rehearsal, w) {
|
|
35702
35962
|
const args = w.args;
|
|
35963
|
+
if (w.call === "move") return move(rehearsalRoot(rehearsal, w.root), rehearsalRoot(rehearsal, String(args[0])));
|
|
35703
35964
|
runWithProjectRoot(rehearsalRoot(rehearsal, w.root), () => {
|
|
35704
35965
|
switch (w.call) {
|
|
35705
35966
|
case "setId":
|
|
@@ -36124,11 +36385,11 @@ function planExternalize(family, bound2, request) {
|
|
|
36124
36385
|
const donePath = typeof done === "string" ? done : done?.path;
|
|
36125
36386
|
if (donePath !== void 0 && request.path !== void 0 && path42.resolve(node.directory, donePath) === path42.resolve(node.directory, request.path)) return plan5;
|
|
36126
36387
|
const dir = resolve30(node.directory, request.path ?? "");
|
|
36127
|
-
const
|
|
36388
|
+
const occupied2 = dir !== null && holdsProject(dir);
|
|
36128
36389
|
const sub = id.includes("::") ? null : loadSpec("subsystem", subKey);
|
|
36129
36390
|
if (!sub || sub.projectPath || (family.owners.get(subKey) ?? "") !== bound2) refuse3(plan5, "externalize-refused", bound2, `${label6(bound2)} has no subsystem "${id}" to externalize (it is missing, or already a member)`);
|
|
36130
36391
|
if (dir === null) refuse3(plan5, "not-contained", bound2, `the path "${request.path ?? ""}" does not resolve strictly within ${label6(bound2)}`);
|
|
36131
|
-
else if (family.nodes.some((n) => path42.resolve(n.directory) === dir) ||
|
|
36392
|
+
else if (family.nodes.some((n) => path42.resolve(n.directory) === dir) || occupied2) {
|
|
36132
36393
|
refuse3(plan5, "not-contained", bound2, `${dir} already holds a project`);
|
|
36133
36394
|
}
|
|
36134
36395
|
const config = configAt5(node.directory);
|
|
@@ -36243,11 +36504,11 @@ function writeOne3(rehearsal, w) {
|
|
|
36243
36504
|
|
|
36244
36505
|
// src/migrations/transaction.ts
|
|
36245
36506
|
var crypto6 = __toESM(require("crypto"));
|
|
36246
|
-
var
|
|
36507
|
+
var fs24 = __toESM(require("fs"));
|
|
36247
36508
|
var path43 = __toESM(require("path"));
|
|
36248
36509
|
|
|
36249
36510
|
// src/migrations/transaction-files.ts
|
|
36250
|
-
var
|
|
36511
|
+
var fs23 = __toESM(require("fs"));
|
|
36251
36512
|
var os6 = __toESM(require("os"));
|
|
36252
36513
|
var nodePath2 = __toESM(require("path"));
|
|
36253
36514
|
var yaml3 = __toESM(require("js-yaml"));
|
|
@@ -36261,7 +36522,7 @@ function pause2(ms) {
|
|
|
36261
36522
|
function removeTree(dir) {
|
|
36262
36523
|
for (let attempt = 1; ; attempt++) {
|
|
36263
36524
|
try {
|
|
36264
|
-
|
|
36525
|
+
fs23.rmSync(dir, { recursive: true, force: true });
|
|
36265
36526
|
return;
|
|
36266
36527
|
} catch (e) {
|
|
36267
36528
|
const code = e.code;
|
|
@@ -36271,20 +36532,20 @@ function removeTree(dir) {
|
|
|
36271
36532
|
}
|
|
36272
36533
|
}
|
|
36273
36534
|
function writeDurably(file, bytes) {
|
|
36274
|
-
|
|
36535
|
+
fs23.mkdirSync(nodePath2.dirname(file), { recursive: true });
|
|
36275
36536
|
const tmp = `${file}.tmp`;
|
|
36276
36537
|
try {
|
|
36277
|
-
const fd =
|
|
36538
|
+
const fd = fs23.openSync(tmp, "w");
|
|
36278
36539
|
try {
|
|
36279
|
-
|
|
36280
|
-
|
|
36540
|
+
fs23.writeSync(fd, typeof bytes === "string" ? Buffer.from(bytes, "utf8") : bytes);
|
|
36541
|
+
fs23.fsyncSync(fd);
|
|
36281
36542
|
} finally {
|
|
36282
|
-
|
|
36543
|
+
fs23.closeSync(fd);
|
|
36283
36544
|
}
|
|
36284
|
-
|
|
36545
|
+
fs23.renameSync(tmp, file);
|
|
36285
36546
|
} catch (e) {
|
|
36286
36547
|
try {
|
|
36287
|
-
|
|
36548
|
+
fs23.unlinkSync(tmp);
|
|
36288
36549
|
} catch {
|
|
36289
36550
|
}
|
|
36290
36551
|
throw e;
|
|
@@ -36294,18 +36555,18 @@ function writeDurably(file, bytes) {
|
|
|
36294
36555
|
function syncDirectory(dir) {
|
|
36295
36556
|
if (process.platform === "win32") return;
|
|
36296
36557
|
try {
|
|
36297
|
-
const fd =
|
|
36558
|
+
const fd = fs23.openSync(dir, "r");
|
|
36298
36559
|
try {
|
|
36299
|
-
|
|
36560
|
+
fs23.fsyncSync(fd);
|
|
36300
36561
|
} finally {
|
|
36301
|
-
|
|
36562
|
+
fs23.closeSync(fd);
|
|
36302
36563
|
}
|
|
36303
36564
|
} catch {
|
|
36304
36565
|
}
|
|
36305
36566
|
}
|
|
36306
36567
|
function openRehearsal(id) {
|
|
36307
36568
|
const dir = rehearsalDir(id);
|
|
36308
|
-
|
|
36569
|
+
fs23.mkdirSync(dir);
|
|
36309
36570
|
return dir;
|
|
36310
36571
|
}
|
|
36311
36572
|
function dropRehearsal(id) {
|
|
@@ -36316,14 +36577,14 @@ function dropRehearsal(id) {
|
|
|
36316
36577
|
}
|
|
36317
36578
|
function saveJournal(journal) {
|
|
36318
36579
|
const ignore = nodePath2.join(transactionsDir(journal.owner), ".gitignore");
|
|
36319
|
-
if (!
|
|
36580
|
+
if (!fs23.existsSync(ignore)) writeDurably(ignore, "*\n");
|
|
36320
36581
|
writeDurably(nodePath2.join(transactionDir(journal.owner, journal.id), "journal.yaml"), yaml3.dump(journal, { noRefs: true, lineWidth: 200 }));
|
|
36321
36582
|
}
|
|
36322
36583
|
function readJournal(owner, id) {
|
|
36323
36584
|
const file = nodePath2.join(transactionDir(owner, id), "journal.yaml");
|
|
36324
36585
|
let text3;
|
|
36325
36586
|
try {
|
|
36326
|
-
text3 =
|
|
36587
|
+
text3 = fs23.readFileSync(file, "utf8");
|
|
36327
36588
|
} catch (e) {
|
|
36328
36589
|
if (e.code === "ENOENT") return null;
|
|
36329
36590
|
throw e;
|
|
@@ -36338,7 +36599,7 @@ function listJournals(owner) {
|
|
|
36338
36599
|
const dir = transactionsDir(owner);
|
|
36339
36600
|
let names;
|
|
36340
36601
|
try {
|
|
36341
|
-
names =
|
|
36602
|
+
names = fs23.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
36342
36603
|
} catch {
|
|
36343
36604
|
return [];
|
|
36344
36605
|
}
|
|
@@ -36360,7 +36621,7 @@ function putFile(owner, id, area, path46, bytes) {
|
|
|
36360
36621
|
}
|
|
36361
36622
|
function readFile(owner, id, area, path46) {
|
|
36362
36623
|
try {
|
|
36363
|
-
return
|
|
36624
|
+
return fs23.readFileSync(locate(owner, id, area, path46));
|
|
36364
36625
|
} catch (e) {
|
|
36365
36626
|
if (e.code === "ENOENT") return null;
|
|
36366
36627
|
throw e;
|
|
@@ -36374,7 +36635,7 @@ function close(owner, id) {
|
|
|
36374
36635
|
const dir = transactionsDir(owner);
|
|
36375
36636
|
let left;
|
|
36376
36637
|
try {
|
|
36377
|
-
left =
|
|
36638
|
+
left = fs23.readdirSync(dir);
|
|
36378
36639
|
} catch {
|
|
36379
36640
|
return;
|
|
36380
36641
|
}
|
|
@@ -36382,7 +36643,11 @@ function close(owner, id) {
|
|
|
36382
36643
|
removeTree(dir);
|
|
36383
36644
|
const wai = nodePath2.dirname(dir);
|
|
36384
36645
|
try {
|
|
36385
|
-
if (
|
|
36646
|
+
if (fs23.readdirSync(wai).length === 0) fs23.rmdirSync(wai);
|
|
36647
|
+
} catch {
|
|
36648
|
+
}
|
|
36649
|
+
try {
|
|
36650
|
+
if (fs23.readdirSync(owner).length === 0) fs23.rmdirSync(owner);
|
|
36386
36651
|
} catch {
|
|
36387
36652
|
}
|
|
36388
36653
|
}
|
|
@@ -36469,15 +36734,16 @@ function rehearse(scope) {
|
|
|
36469
36734
|
const directory = transactionRepository.openRehearsal(id);
|
|
36470
36735
|
const familyRoot = path43.resolve(scope.familyRoot);
|
|
36471
36736
|
const projects = [...new Set(scope.projects.map((p) => path43.resolve(p)))];
|
|
36737
|
+
const whole = scope.whole ? [...new Set(scope.whole.map((p) => path43.resolve(p)))] : void 0;
|
|
36472
36738
|
let baseDigests;
|
|
36473
36739
|
try {
|
|
36474
|
-
baseDigests = mirror({ familyRoot, projects }, directory);
|
|
36740
|
+
baseDigests = mirror({ familyRoot, projects, ...scope.areas ? { areas: scope.areas } : {}, ...whole ? { whole } : {} }, directory);
|
|
36475
36741
|
} catch (e) {
|
|
36476
36742
|
transactionRepository.dropRehearsal(id);
|
|
36477
36743
|
throw e;
|
|
36478
36744
|
}
|
|
36479
36745
|
const roots = new Map(projects.map((p) => [p, path43.join(directory, path43.relative(familyRoot, p))]));
|
|
36480
|
-
return { id, directory, familyRoot, roots, baseDigests };
|
|
36746
|
+
return { id, directory, familyRoot, roots, baseDigests, ...scope.areas ? { areas: [...scope.areas] } : {}, ...whole ? { whole } : {} };
|
|
36481
36747
|
}
|
|
36482
36748
|
function diff(rehearsal) {
|
|
36483
36749
|
return compare4(rehearsal);
|
|
@@ -36564,7 +36830,7 @@ function missingDirs(owner, entries) {
|
|
|
36564
36830
|
for (const entry of entries.filter((e) => e.action === "create")) {
|
|
36565
36831
|
const missing = [];
|
|
36566
36832
|
let at = path43.dirname(liveOf(owner, entry.path));
|
|
36567
|
-
while (!
|
|
36833
|
+
while (!fs24.existsSync(at) && at !== path43.dirname(at)) {
|
|
36568
36834
|
missing.unshift(at);
|
|
36569
36835
|
at = path43.dirname(at);
|
|
36570
36836
|
}
|
|
@@ -36598,7 +36864,7 @@ function stageBytes(rehearsal, journal, entry) {
|
|
|
36598
36864
|
function checkVolume(journal, entry, target) {
|
|
36599
36865
|
const staged = transactionRepository.locate(journal.owner, journal.id, "staged", entry.path);
|
|
36600
36866
|
let nearest = path43.dirname(target);
|
|
36601
|
-
while (!
|
|
36867
|
+
while (!fs24.existsSync(nearest) && nearest !== path43.dirname(nearest)) nearest = path43.dirname(nearest);
|
|
36602
36868
|
if (!sameVolume(staged, nearest)) {
|
|
36603
36869
|
throw new StagingError(`cross-volume: ${target} lies on another volume than its staged copy; a rename across volumes is not atomic`);
|
|
36604
36870
|
}
|
|
@@ -36682,7 +36948,7 @@ function recover(root, fix) {
|
|
|
36682
36948
|
var coordinatorRoot = (journal) => path43.resolve(journal.owner, journal.coordinator);
|
|
36683
36949
|
function journalAt(owner, id) {
|
|
36684
36950
|
try {
|
|
36685
|
-
const journal =
|
|
36951
|
+
const journal = fs24.existsSync(owner) ? transactionRepository.readJournal(owner, id) : null;
|
|
36686
36952
|
return journal ? { ...journal, owner } : null;
|
|
36687
36953
|
} catch {
|
|
36688
36954
|
return null;
|
|
@@ -36714,7 +36980,7 @@ function resolveOne(journal) {
|
|
|
36714
36980
|
for (const owner of [...owners.filter((o) => o !== coordinator.owner), coordinator.owner]) transactionRepository.close(owner, journal.id);
|
|
36715
36981
|
return { ...base, owners, phase, action: "cleaned", detail: "it had committed; its transaction directories were removed" };
|
|
36716
36982
|
}
|
|
36717
|
-
const absent = owners.filter((o) => !
|
|
36983
|
+
const absent = phase === "staging" ? [] : owners.filter((o) => !fs24.existsSync(o));
|
|
36718
36984
|
if (absent.length > 0) {
|
|
36719
36985
|
return { ...base, owners, phase, action: "refused", detail: `owner(s) out of reach: ${absent.join(", ")}` };
|
|
36720
36986
|
}
|
|
@@ -36745,14 +37011,15 @@ function plan3(request) {
|
|
|
36745
37011
|
const { top, whole } = climb3(bound2);
|
|
36746
37012
|
const family = runWithProjectRoot(top, () => graph());
|
|
36747
37013
|
const boundKey = family.nodes.find((n) => path44.resolve(n.directory) === path44.resolve(bound2))?.namespace ?? "";
|
|
36748
|
-
const pending3 = unfinished(top, bound2);
|
|
37014
|
+
const pending3 = unfinished(top, bound2, request.relocation?.coordinator);
|
|
36749
37015
|
if (pending3.length > 0) return refusedForPending(request, top, whole, pending3);
|
|
36750
37016
|
const planned = request.verb === "chaining" ? route(request, family, boundKey) : runWithProjectRoot(top, () => route(request, family, boundKey));
|
|
36751
37017
|
return rehearse2(request.verb === "chaining" ? planned : { ...planned, familyRoot: top, whole });
|
|
36752
37018
|
}
|
|
36753
|
-
function unfinished(top, bound2) {
|
|
37019
|
+
function unfinished(top, bound2, coordinator) {
|
|
36754
37020
|
const found = recover(top, false);
|
|
36755
37021
|
if (path44.resolve(bound2) !== path44.resolve(top)) found.push(...recover(bound2, false));
|
|
37022
|
+
if (coordinator !== void 0) found.push(...recover(coordinator, false));
|
|
36756
37023
|
return found.filter((t, i) => found.findIndex((u) => u.id === t.id) === i);
|
|
36757
37024
|
}
|
|
36758
37025
|
function refusedForPending(request, top, whole, pending3) {
|
|
@@ -36830,17 +37097,62 @@ function rehearse2(plan5) {
|
|
|
36830
37097
|
if (blocked(plan5) || plan5.request.rehearse === false) return plan5;
|
|
36831
37098
|
const family = runWithProjectRoot(plan5.familyRoot, () => graph());
|
|
36832
37099
|
const projects = [...family.nodes.map((n) => n.directory), ...broughtIn(plan5, family), ...pathProducers(plan5, family)];
|
|
36833
|
-
const rehearsal = rehearse(
|
|
37100
|
+
const rehearsal = rehearse(scopeOf(plan5, projects));
|
|
36834
37101
|
try {
|
|
36835
|
-
onCopy(rehearsal, () => writeVerb(plan5, rehearsal));
|
|
37102
|
+
onCopy(rehearsal, () => relocatedLookup(plan5, rehearsal, () => writeVerb(plan5, rehearsal)));
|
|
36836
37103
|
} catch (e) {
|
|
36837
37104
|
discard(rehearsal);
|
|
36838
37105
|
const detail = e instanceof Error ? e.message : String(e);
|
|
36839
37106
|
return { ...plan5, refusals: [...plan5.refusals, { code: `${plan5.request.verb}-refused`, project: "", detail }] };
|
|
36840
37107
|
}
|
|
36841
37108
|
const changes = diff(rehearsal);
|
|
37109
|
+
const outside = unwritableOwners(changes);
|
|
37110
|
+
if (outside.length > 0) {
|
|
37111
|
+
discard(rehearsal);
|
|
37112
|
+
return { ...plan5, refusals: [...plan5.refusals, ...outside.map((o) => ({ code: "family-partial", project: o, detail: `the plan must write ${o}, which this request may not write` }))] };
|
|
37113
|
+
}
|
|
36842
37114
|
return { ...plan5, rehearsal, changes, relock: relockOf(plan5, changes) };
|
|
36843
37115
|
}
|
|
37116
|
+
function movedOf(plan5) {
|
|
37117
|
+
const relocation = plan5.request.relocation;
|
|
37118
|
+
const move2 = plan5.edits.find((e) => e.write?.call === "move");
|
|
37119
|
+
if (!relocation || !move2?.write) return null;
|
|
37120
|
+
return { from: path44.resolve(move2.write.root), to: path44.resolve(relocation.to) };
|
|
37121
|
+
}
|
|
37122
|
+
function scopeOf(plan5, projects) {
|
|
37123
|
+
const relocation = plan5.request.relocation;
|
|
37124
|
+
const moved = movedOf(plan5);
|
|
37125
|
+
if (!relocation || !moved) return { familyRoot: plan5.familyRoot, projects };
|
|
37126
|
+
return {
|
|
37127
|
+
familyRoot: relocation.coordinator,
|
|
37128
|
+
projects: [relocation.coordinator, ...projects],
|
|
37129
|
+
areas: [".wai", ...relocation.areas],
|
|
37130
|
+
whole: [moved.from, moved.to]
|
|
37131
|
+
};
|
|
37132
|
+
}
|
|
37133
|
+
function relocatedLookup(plan5, rehearsal, run3) {
|
|
37134
|
+
const moved = movedOf(plan5);
|
|
37135
|
+
const live = getHostedLookup();
|
|
37136
|
+
if (!moved || !live) return run3();
|
|
37137
|
+
const copied = [...rehearsal.roots.keys(), ...rehearsal.whole ?? []].filter((r) => r !== path44.resolve(rehearsal.familyRoot));
|
|
37138
|
+
const inside = (root, dir) => {
|
|
37139
|
+
const rel2 = path44.relative(root, dir);
|
|
37140
|
+
return rel2 === "" || !rel2.startsWith("..") && !path44.isAbsolute(rel2);
|
|
37141
|
+
};
|
|
37142
|
+
return runWithHostedLookup((id) => {
|
|
37143
|
+
const root = live(id);
|
|
37144
|
+
if (root === null) return null;
|
|
37145
|
+
const at = path44.resolve(root);
|
|
37146
|
+
if (inside(moved.from, at)) return path44.join(rehearsalRoot(rehearsal, moved.to), path44.relative(moved.from, at));
|
|
37147
|
+
return copied.some((r) => inside(r, at)) ? rehearsalRoot(rehearsal, at) : at;
|
|
37148
|
+
}, run3);
|
|
37149
|
+
}
|
|
37150
|
+
function unwritableOwners(changes) {
|
|
37151
|
+
const denied = getRequestParentReach()?.unwritableRoots ?? [];
|
|
37152
|
+
if (denied.length === 0) return [];
|
|
37153
|
+
const owners = [...new Set(changes.map((c) => path44.resolve(c.project)))];
|
|
37154
|
+
return owners.filter((o) => denied.includes(o));
|
|
37155
|
+
}
|
|
36844
37156
|
function pathProducers(plan5, family) {
|
|
36845
37157
|
const inside = (dir) => {
|
|
36846
37158
|
const rel2 = path44.relative(plan5.familyRoot, dir);
|
|
@@ -36883,7 +37195,9 @@ function onCopy(rehearsal, run3) {
|
|
|
36883
37195
|
function relockOf(plan5, changes) {
|
|
36884
37196
|
const ended = new Set(changes.filter((c) => c.action === "delete" && c.path === ".wai/project.yaml").map((c) => path44.resolve(c.project)));
|
|
36885
37197
|
const owners = [...new Set(changes.map((c) => path44.resolve(c.project)))].filter((o) => !ended.has(o));
|
|
36886
|
-
|
|
37198
|
+
const moved = movedOf(plan5);
|
|
37199
|
+
const relocated = moved !== null && approvalRecord(moved.from) !== null ? [moved.to] : [];
|
|
37200
|
+
if (plan5.request.verb !== "rename") return [...owners.filter((owner) => approvalRecord(owner) !== null), ...relocated];
|
|
36887
37201
|
const renamed = plan5.edits.find((e) => e.write?.call === "renameId")?.write?.root;
|
|
36888
37202
|
const first = renamed !== void 0 ? path44.resolve(renamed) : void 0;
|
|
36889
37203
|
return [...owners.filter((o) => o === first), ...owners.filter((o) => o !== first)];
|
|
@@ -38060,9 +38374,10 @@ ${renderChangeReport(report2)}`,
|
|
|
38060
38374
|
server,
|
|
38061
38375
|
"sdd_adopt_member",
|
|
38062
38376
|
{
|
|
38063
|
-
description: "Make the bound project's external found by a path inside it a
|
|
38377
|
+
description: "Make the bound project's external a member \u2014 sdd_detach_member's inverse. Locally the external is found by a path inside the bound project and adopted where it is; on a hosted instance it names a project by source.hosted and `path` says where to adopt it (the hosting server relocates it there). All-or-nothing; dryRun answers the plan and writes nothing. Never locks.",
|
|
38064
38378
|
inputSchema: {
|
|
38065
38379
|
alias: import_zod11.z.string().describe("The external's alias"),
|
|
38380
|
+
path: import_zod11.z.string().optional().describe("Hosted: the member path to adopt a source.hosted external at, relative to the bound project; a source.path external is adopted where it is"),
|
|
38066
38381
|
dryRun: dryRunArg
|
|
38067
38382
|
}
|
|
38068
38383
|
},
|
|
@@ -38408,7 +38723,7 @@ ${renderChangeReport(report2)}`,
|
|
|
38408
38723
|
contract: import_zod11.z.string().describe("The L3 Interface contract ID this implements"),
|
|
38409
38724
|
sourcePath: import_zod11.z.string().optional().describe("Optional: target source code file path relative to project root \u2014 for a chained subproject's implementation (qualified id), relative to that subproject's root; a path given relative to this root that lands inside the subproject is re-expressed for you"),
|
|
38410
38725
|
simPath: import_zod11.z.string().optional().describe("Optional: the committed integration-sim harness file (project-relative; N:1 sharing allowed). The validator proves it exists and its import graph wires the REAL modules (this component + each direct dependency; technology adapters may stay faked) \u2014 running it is CI's job. Declaring the first simPath in a subsystem activates MISSING_INTEGRATION_SIM for its other complete non-leaf implementations"),
|
|
38411
|
-
technologies: import_zod11.z.array(import_zod11.z.string()).optional().describe(`External technologies this implementation binds to (e.g. ["mysql"]) \u2014 declares this component's ownership tree as the technology's home; references outside it are flagged (TECH_LEAKAGE) and contract identifiers must stay intent-language. Only for Adapter/Store/Registry/Index components.`),
|
|
38726
|
+
technologies: import_zod11.z.array(import_zod11.z.union([import_zod11.z.string(), import_zod11.z.object({ name: import_zod11.z.string(), matches: import_zod11.z.array(import_zod11.z.string()).min(1).describe("The tokens the leakage and contract checks match INSTEAD of the name \u2014 for a technology whose name is also an ordinary word of the tree (the yaml package, the YAML format)") }).strict()])).optional().describe(`External technologies this implementation binds to (e.g. ["mysql"], or [{ "name": "yaml", "matches": ["yaml package", "parseDocument"] }] when the bare name would match a common word) \u2014 declares this component's ownership tree as the technology's home; references outside it are flagged (TECH_LEAKAGE) and contract identifiers must stay intent-language. Only for Adapter/Store/Registry/Index components.`),
|
|
38412
38727
|
injectedParams: import_zod11.z.array(import_zod11.z.string()).optional().describe("The parameter names this realization takes BEFORE the ones its contract declares \u2014 a config object, a data root, the transport handles a portal is handed. Supplied by whatever wires the component up, never by the caller the contract describes, which is why they belong here and not to the contract: another realization may hold them as fields instead. Declared rather than guessed, because a leading parameter the contract does not name cannot be told from one it named under a different name (`seed(config)` realized as `bootstrapInstance(cfg)`); a leading parameter this list does not name is reported (UNDECLARED_PARAM). Only a LEADING run is dropped \u2014 one of these names appearing after the contract's own parameters is an argument in the middle of the caller's list, not wiring"),
|
|
38413
38728
|
detail: detailEnum.optional().describe("Spec-level narrative detail default for all methods"),
|
|
38414
38729
|
conformance: conformanceEnum.optional().describe("Spec-level conformance tier default: declared | anchored | off (omitted = stereotype default: Portal \u2192 anchored, else declared)"),
|
|
@@ -38687,7 +39002,7 @@ ${testsBlock}` : ""}`, deletion);
|
|
|
38687
39002
|
inputSchema: {
|
|
38688
39003
|
kind: import_zod11.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The spec kind to update (system = the singleton L0 \u2014 vision, boundaries, globalRequirements, databases, and publicInterfaces: the project export table, each entry a re-export {from, component, interface, typeDef, as, name, type, details, audience: project|department|instance|partner|external} \u2014 from alone re-exports everything a subsystem exports, and a legacy {id, subsystem} reads as {as, from})"),
|
|
38689
39004
|
id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
|
|
38690
|
-
delta: import_zod11.z.record(import_zod11.z.any()).describe(`The partial fields to merge into the spec. ARRAYS UPSERT, they do not replace: an array whose elements carry an identity is merged element-by-element, so a delta naming ONE element leaves the others intact. Identity is "name" or "id" by default, and per field: dispatch by "capability", a listener's mounts by "portal", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", a subsystem's publicInterfaces by component+interface (or type+details for an entry not yet bound to a component, and by from+item+interface+as for a re-export or a type export), invariants and patterns by "id", lint.allow by "code" AND "at" (an allow covers one occurrence, so several may share a code on one spec), an interface method's findings by "code", boundaries by "name", globalRequirements by "description", switch cases by "value", try catches by "error". Identity merging applies at EVERY depth, including an array INSIDE an element (a method's params, a step's catches). Add "action: 'delete'" (or "remove: true") alongside that identity to REMOVE an element \u2014 including a stale lint allow. Arrays of plain STRINGS (owns, dependsOn, guarantees) carry no per-element identity and are replaced wholesale; pass [] to clear any array outright. To REMOVE an optional field entirely, list it in "unset": e.g. {"unset": ["basePath", "variant"]} \u2014 passing null/undefined means "no change" (they are skipped), and writing "" would leave the field present but empty, which is a different and usually wrong spec. Unsetting a required field is refused by schema validation, which names it. For narrative steps, match by "stepNumber" and use "action: 'insert'" (shifts subsequent steps up) or "action: 'delete'" (shifts subsequent steps down and removes it). Step entries apply in ASCENDING stepNumber order, each against the numbering the earlier entries of the SAME delta left behind \u2014 delete step 3 and step 7 becomes step 6 \u2014 so prefer labels, and restate the step's "label" or "description" on a delete to have it checked against the step actually addressed. Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative. A delete is REJECTED when the narrative has no such step, when a jump still targets it (retarget the referrers first), when a restated label/description does not match, or when it is a loop/try/parallel header whose body would be left standing (retype the header first to dissolve the region, then delete it). Changing a step's "type" REBUILDS it for the new type: its description and label are kept and every field the new type cannot carry is dropped (returned as a NOTICE); a delta that retypes AND sets such a field is refused. A step delta is also refused when it carries a marker the merge does not recognise: a non-boolean "remove", an "action" that is neither "insert" nor "delete", a "captureJumps" outside an insert, or no "stepNumber" to address. Inserting AT a jump target relocates those jumps past the inserted step by default (a NOTICE is returned) \u2014 add "captureJumps": true on the inserted step to retarget entry jumps onto it (loop/try endStep region tails always relocate with the body and are never captured). Every jump field has a "*Label" twin (toLabel, onFalseLabel, endLabel, \u2026, and "label" on a cases/catches entry) resolved against step labels AFTER the merge, so a delta may anchor on a label only pre-existing steps carry; a label the delta supplies REPLACES the stored number it twins, while setting the number and its label together in one delta is refused as a contradiction. Reference ids in deltas may use LOCAL names \u2014 they are qualified against the spec's namespace exactly as the loader would. Per-spec lint suppression: set "lint: { allow: [{ code, at, covers, reason }] }" to silence a WARNING or NOTICE code on this spec only (errors always surface; stale allows are flagged). An allow covers EXACTLY the occurrence it names: "at" is the site the finding names (a contract method, an import edge "from -> to", a declared edge "component -> target") and is REQUIRED for a code whose findings report one, while a finding that reports no site is covered only by an allow that names none; "covers" lists the units an aggregating finding reports, and the allow silences it only when every one is listed. This delta is deliberately OPEN below its top level \u2014 the shapes nest further than a schema here should restate \u2014 so a key that is not a field at its depth is not refused, it is NAMED BACK under NO EFFECT in the answer, together with any value the spec already held and any "unset" that removed nothing. Read that list: it is where a nested typo shows up.`),
|
|
39005
|
+
delta: import_zod11.z.record(import_zod11.z.any()).describe(`The partial fields to merge into the spec. ARRAYS UPSERT, they do not replace: an array whose elements carry an identity is merged element-by-element, so a delta naming ONE element leaves the others intact. Identity is "name" or "id" by default, and per field: dispatch by "capability", a listener's mounts by "portal", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", a subsystem's publicInterfaces by component+interface (or type+details for an entry not yet bound to a component, and by from+item+interface+as for a re-export or a type export), invariants and patterns by "id", lint.allow by "code" AND "at" (an allow covers one occurrence, so several may share a code on one spec), an interface method's findings by "code", boundaries by "name", globalRequirements by "description", switch cases by "value", try catches by "error". Identity merging applies at EVERY depth, including an array INSIDE an element (a method's params, a step's catches). Add "action: 'delete'" (or "remove: true") alongside that identity to REMOVE an element \u2014 including a stale lint allow. Arrays of plain STRINGS (owns, dependsOn, guarantees) carry no per-element identity and are replaced wholesale; pass [] to clear any array outright. To REMOVE an optional field entirely, list it in "unset": e.g. {"unset": ["basePath", "variant"]} \u2014 passing null/undefined means "no change" (they are skipped), and writing "" would leave the field present but empty, which is a different and usually wrong spec. Unsetting a required field is refused by schema validation, which names it. For narrative steps, match by "stepNumber" and use "action: 'insert'" (shifts subsequent steps up) or "action: 'delete'" (shifts subsequent steps down and removes it). Step entries apply in ASCENDING stepNumber order, each against the numbering the earlier entries of the SAME delta left behind \u2014 delete step 3 and step 7 becomes step 6 \u2014 so prefer labels, and restate the step's "label" or "description" on a delete to have it checked against the step actually addressed. Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative. Only STORED jumps relocate: a jump the delta itself writes (on an inserted, appended or edited step) names its target in the numbering the WHOLE delta leaves behind, after all of its inserts and deletes, and no later step of the same delta moves it. A method the delta ADDS without a narrative is an intent-level method with none ([]). A delete is REJECTED when the narrative has no such step, when a jump still targets it (retarget the referrers first), when a restated label/description does not match, or when it is a loop/try/parallel header whose body would be left standing (retype the header first to dissolve the region, then delete it). Changing a step's "type" REBUILDS it for the new type: its description and label are kept and every field the new type cannot carry is dropped (returned as a NOTICE); a delta that retypes AND sets such a field is refused. A step delta is also refused when it carries a marker the merge does not recognise: a non-boolean "remove", an "action" that is neither "insert" nor "delete", a "captureJumps" outside an insert, or no "stepNumber" to address. Inserting AT a jump target relocates those jumps past the inserted step by default (a NOTICE is returned) \u2014 add "captureJumps": true on the inserted step to retarget entry jumps onto it (loop/try endStep region tails always relocate with the body and are never captured). Every jump field has a "*Label" twin (toLabel, onFalseLabel, endLabel, \u2026, and "label" on a cases/catches entry) resolved against step labels AFTER the merge, so a delta may anchor on a label only pre-existing steps carry; a label the delta supplies REPLACES the stored number it twins, while setting the number and its label together in one delta is refused as a contradiction. Reference ids in deltas may use LOCAL names \u2014 they are qualified against the spec's namespace exactly as the loader would. Per-spec lint suppression: set "lint: { allow: [{ code, at, covers, reason }] }" to silence a WARNING or NOTICE code on this spec only (errors always surface; stale allows are flagged). An allow covers EXACTLY the occurrence it names: "at" is the site the finding names (a contract method, an import edge "from -> to", a declared edge "component -> target") and is REQUIRED for a code whose findings report one, while a finding that reports no site is covered only by an allow that names none; "covers" lists the units an aggregating finding reports, and the allow silences it only when every one is listed. This delta is deliberately OPEN below its top level \u2014 the shapes nest further than a schema here should restate \u2014 so a key that is not a field at its depth is not refused, it is NAMED BACK under NO EFFECT in the answer, together with any value the spec already held and any "unset" that removed nothing. Read that list: it is where a nested typo shows up.`),
|
|
38691
39006
|
dryRun: import_zod11.z.boolean().optional().describe("Ask what this delta WOULD do instead of doing it. The whole write runs, the candidate gate included, and the answer is the change report it would have produced \u2014 marked DRY RUN, with nothing stamped and not one byte of the stored file moved. Use it before a delta that renumbers a long narrative.")
|
|
38692
39007
|
},
|
|
38693
39008
|
outputSchema: specChangeReportOutput
|
|
@@ -39026,6 +39341,7 @@ function loadProjectConfig3() {
|
|
|
39026
39341
|
SystemPublicInterfaceSchema,
|
|
39027
39342
|
SystemSpecSchema,
|
|
39028
39343
|
TargetConfigSchema,
|
|
39344
|
+
TechnologySchema,
|
|
39029
39345
|
TemplateNotFoundError,
|
|
39030
39346
|
TemplateSchema,
|
|
39031
39347
|
TopologyConfigSchema,
|
|
@@ -39053,6 +39369,7 @@ function loadProjectConfig3() {
|
|
|
39053
39369
|
assertProjectInitialized,
|
|
39054
39370
|
backendCommand,
|
|
39055
39371
|
backfillChainedSubprojectConfigs,
|
|
39372
|
+
backupBeside,
|
|
39056
39373
|
buildCanvasDataModel,
|
|
39057
39374
|
buildProjectGraph,
|
|
39058
39375
|
buildRuleContext,
|
|
@@ -39127,6 +39444,7 @@ function loadProjectConfig3() {
|
|
|
39127
39444
|
generateDiagramSet,
|
|
39128
39445
|
generateSequenceDiagram,
|
|
39129
39446
|
getExporter,
|
|
39447
|
+
getHostedLookup,
|
|
39130
39448
|
getLoaderIssues,
|
|
39131
39449
|
getProjectRoot,
|
|
39132
39450
|
getProjectRootOverride,
|
|
@@ -39151,6 +39469,7 @@ function loadProjectConfig3() {
|
|
|
39151
39469
|
internalizeMember,
|
|
39152
39470
|
isDraftSubsystem,
|
|
39153
39471
|
isLogic,
|
|
39472
|
+
isOutsideRoot,
|
|
39154
39473
|
isOwnComponentEntry,
|
|
39155
39474
|
isPattern,
|
|
39156
39475
|
isProjectInitialized,
|
|
@@ -39258,6 +39577,7 @@ function loadProjectConfig3() {
|
|
|
39258
39577
|
resolveVariantGuidance,
|
|
39259
39578
|
retireSpecialists,
|
|
39260
39579
|
rewriteReferences,
|
|
39580
|
+
runWithHostedLookup,
|
|
39261
39581
|
runWithProjectBinding,
|
|
39262
39582
|
runWithProjectRoot,
|
|
39263
39583
|
sameContract,
|
|
@@ -39279,6 +39599,8 @@ function loadProjectConfig3() {
|
|
|
39279
39599
|
stepFieldsFor,
|
|
39280
39600
|
stepGraph,
|
|
39281
39601
|
syncContextFiles,
|
|
39602
|
+
technologyName,
|
|
39603
|
+
technologyTokens,
|
|
39282
39604
|
toMarkdown,
|
|
39283
39605
|
typeBindingOf,
|
|
39284
39606
|
typeGenericParameters,
|