@wairon/cli 5.1.1-dev.85 → 5.1.1-dev.86
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/README.md +1 -1
- package/dist/cli/index.js +956 -418
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +428 -134
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
68
|
+
WAIRON_VERSION = "5.1.1-dev.86";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -569,6 +569,13 @@ var init_agent = __esm({
|
|
|
569
569
|
dependencies: import_zod2.z.array(import_zod2.z.string()).default([]),
|
|
570
570
|
/** Rendered implementation guidance for this agent's variant-tagged components (deep variant integration); empty when none. */
|
|
571
571
|
variantGuidance: import_zod2.z.string().optional(),
|
|
572
|
+
/**
|
|
573
|
+
* A member project's delegating owner only: the ids of the member's own
|
|
574
|
+
* agents, each qualified `<alias>::<agentId>`, resolved from the member's
|
|
575
|
+
* topology at the member's own root. REFERENCES, never copies: nothing of the
|
|
576
|
+
* member's agents is rendered into this layer (one level deep, ids only).
|
|
577
|
+
*/
|
|
578
|
+
delegatesTo: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
572
579
|
/** Why this agent was created — the architectural reason for its existence */
|
|
573
580
|
creationReason: import_zod2.z.string(),
|
|
574
581
|
status: AgentStatusSchema.default("active"),
|
|
@@ -600,6 +607,13 @@ var init_agent = __esm({
|
|
|
600
607
|
instructions: import_zod2.z.string(),
|
|
601
608
|
/** Rendered variant guidance, also folded into instructions */
|
|
602
609
|
variantGuidance: import_zod2.z.string().optional(),
|
|
610
|
+
/**
|
|
611
|
+
* Set only on a brief for a member's agent (an id qualified `<alias>::`): the
|
|
612
|
+
* member's directory relative to the root the brief was asked from. The
|
|
613
|
+
* brief was composed at that root, and its ownedPaths and readPaths are
|
|
614
|
+
* re-expressed under this prefix so the fence is correct from the asking root.
|
|
615
|
+
*/
|
|
616
|
+
root: import_zod2.z.string().optional(),
|
|
603
617
|
/**
|
|
604
618
|
* The resource shape of this agent's work, and the allowance it earns.
|
|
605
619
|
*
|
|
@@ -1210,7 +1224,13 @@ var init_project = __esm({
|
|
|
1210
1224
|
profile: import_zod3.z.string().optional()
|
|
1211
1225
|
});
|
|
1212
1226
|
CompositionConfigSchema = import_zod3.z.object({
|
|
1213
|
-
requirePolicies: import_zod3.z.array(PackRequirementSchema).optional()
|
|
1227
|
+
requirePolicies: import_zod3.z.array(PackRequirementSchema).optional(),
|
|
1228
|
+
/**
|
|
1229
|
+
* Opt-in, off by default (stage 5). When true, this project's lock refuses
|
|
1230
|
+
* while any DIRECT member is drifted or never approved, naming each, and
|
|
1231
|
+
* writes nothing. Off: the lock proceeds and records each member's state.
|
|
1232
|
+
*/
|
|
1233
|
+
requireApprovedMembers: import_zod3.z.boolean().optional()
|
|
1214
1234
|
});
|
|
1215
1235
|
ProjectConfigSchema = import_zod3.z.object({
|
|
1216
1236
|
/**
|
|
@@ -1473,21 +1493,21 @@ function childrenOf(tokens, index) {
|
|
|
1473
1493
|
}
|
|
1474
1494
|
return out;
|
|
1475
1495
|
}
|
|
1476
|
-
function locateScalar(tokens,
|
|
1496
|
+
function locateScalar(tokens, path82) {
|
|
1477
1497
|
let region = tokens;
|
|
1478
|
-
for (let k = 0; k <
|
|
1479
|
-
const segment =
|
|
1480
|
-
if (region.length === 0) return `nothing is nested at ${
|
|
1498
|
+
for (let k = 0; k < path82.length; k += 1) {
|
|
1499
|
+
const segment = path82[k];
|
|
1500
|
+
if (region.length === 0) return `nothing is nested at ${path82.slice(0, k).join(".")}`;
|
|
1481
1501
|
const base = Math.min(...region.map((t) => t.col));
|
|
1482
|
-
const last = k ===
|
|
1502
|
+
const last = k === path82.length - 1;
|
|
1483
1503
|
let at;
|
|
1484
1504
|
if (typeof segment === "string") {
|
|
1485
1505
|
at = region.findIndex((t) => t.kind === "key" && t.col === base && t.name === segment);
|
|
1486
|
-
if (at < 0) return `no block key "${segment}" at ${
|
|
1506
|
+
if (at < 0) return `no block key "${segment}" at ${path82.slice(0, k).join(".") || "the document root"}`;
|
|
1487
1507
|
if (last) return region[at].valueStart !== void 0 ? region[at] : `"${segment}" holds no inline scalar`;
|
|
1488
1508
|
} else {
|
|
1489
1509
|
const items = region.map((t, i) => ({ t, i })).filter(({ t }) => t.kind === "item" && t.col === base);
|
|
1490
|
-
if (segment >= items.length) return `no block sequence item ${segment} at ${
|
|
1510
|
+
if (segment >= items.length) return `no block sequence item ${segment} at ${path82.slice(0, k).join(".")}`;
|
|
1491
1511
|
at = items[segment].i;
|
|
1492
1512
|
if (last) {
|
|
1493
1513
|
const value = region[at + 1];
|
|
@@ -1535,10 +1555,10 @@ function writeScalar(value, style) {
|
|
|
1535
1555
|
if (style === "plain" && /^[A-Za-z0-9_][A-Za-z0-9_./-]*$/.test(value)) return value;
|
|
1536
1556
|
return `'${value.replace(/'/g, "''")}'`;
|
|
1537
1557
|
}
|
|
1538
|
-
function setAt(document,
|
|
1558
|
+
function setAt(document, path82, value) {
|
|
1539
1559
|
let node = document;
|
|
1540
|
-
for (const segment of
|
|
1541
|
-
node[
|
|
1560
|
+
for (const segment of path82.slice(0, -1)) node = node[segment];
|
|
1561
|
+
node[path82[path82.length - 1]] = value;
|
|
1542
1562
|
}
|
|
1543
1563
|
function rewriteScalarsInText(text3, edits, root) {
|
|
1544
1564
|
const refuse = (why) => {
|
|
@@ -1812,9 +1832,9 @@ function registryOver(store, root) {
|
|
|
1812
1832
|
const next = rekeyAnchor(rename, { spec: entry.spec, at, covers });
|
|
1813
1833
|
const base = ["rules", "conformance", "carried", g, "findings", f];
|
|
1814
1834
|
const named2 = { code: String(entry.code), spec: entry.spec, at: at ?? "" };
|
|
1815
|
-
const change = (
|
|
1835
|
+
const change = (path82, field, from, to) => {
|
|
1816
1836
|
if (from === to) return;
|
|
1817
|
-
edits.push({ path: [...base, ...
|
|
1837
|
+
edits.push({ path: [...base, ...path82], from, to });
|
|
1818
1838
|
rekeys.push({ ...named2, field, from, to });
|
|
1819
1839
|
};
|
|
1820
1840
|
change(["spec"], "spec", entry.spec, next.spec);
|
|
@@ -2365,18 +2385,26 @@ var init_canonical_json = __esm({
|
|
|
2365
2385
|
function inIdentityOrder(specs) {
|
|
2366
2386
|
return specs.map((spec) => ({ spec, content: canonicalize(spec) })).sort((a, b) => compareOrdinal(a.spec.id, b.spec.id) || compareOrdinal(a.content, b.content)).map((entry) => entry.spec);
|
|
2367
2387
|
}
|
|
2368
|
-
function
|
|
2388
|
+
function digestTree(keep) {
|
|
2389
|
+
const kind = (specs) => inIdentityOrder(specs.filter((s) => keep(s.id)));
|
|
2369
2390
|
const tree = {
|
|
2370
2391
|
system: loadSystemSpec(),
|
|
2371
|
-
subsystems:
|
|
2372
|
-
components:
|
|
2373
|
-
interfaces:
|
|
2374
|
-
implementations:
|
|
2375
|
-
types:
|
|
2392
|
+
subsystems: kind(loadSubsystemSpecs()),
|
|
2393
|
+
components: kind(loadComponentSpecs()),
|
|
2394
|
+
interfaces: kind(loadInterfaceSpecs()),
|
|
2395
|
+
implementations: kind(loadImplementationSpecs()),
|
|
2396
|
+
types: kind(loadTypeSpecs())
|
|
2376
2397
|
};
|
|
2377
2398
|
const digest2 = crypto.createHash("sha256").update(canonicalize(tree)).digest("hex");
|
|
2378
2399
|
return { algorithm: CONTENT_ALGORITHM, digest: digest2 };
|
|
2379
2400
|
}
|
|
2401
|
+
function computeStateId() {
|
|
2402
|
+
return digestTree(() => true);
|
|
2403
|
+
}
|
|
2404
|
+
function computeOwnStateId() {
|
|
2405
|
+
const owners = graph().owners;
|
|
2406
|
+
return digestTree((id) => (owners.get(id) ?? "") === "");
|
|
2407
|
+
}
|
|
2380
2408
|
function stateIdEquals(a, b) {
|
|
2381
2409
|
return !!a && !!b && a.algorithm === b.algorithm && a.digest === b.digest;
|
|
2382
2410
|
}
|
|
@@ -2435,12 +2463,13 @@ function withSortedMaps(record2) {
|
|
|
2435
2463
|
for (const k of Object.keys(m).sort()) out[k] = m[k];
|
|
2436
2464
|
return out;
|
|
2437
2465
|
};
|
|
2438
|
-
const
|
|
2439
|
-
const
|
|
2466
|
+
const { children: _legacy, ...rest } = record2;
|
|
2467
|
+
const specs = sorted(rest.specs);
|
|
2468
|
+
const members = sorted(rest.members);
|
|
2440
2469
|
return {
|
|
2441
|
-
...
|
|
2470
|
+
...rest,
|
|
2442
2471
|
...specs ? { specs } : {},
|
|
2443
|
-
...
|
|
2472
|
+
...members ? { members } : {}
|
|
2444
2473
|
};
|
|
2445
2474
|
}
|
|
2446
2475
|
var fs6, path8;
|
|
@@ -4313,12 +4342,12 @@ var init_exports = __esm({
|
|
|
4313
4342
|
});
|
|
4314
4343
|
|
|
4315
4344
|
// src/models/project-family.ts
|
|
4316
|
-
function walkMemberPath(
|
|
4345
|
+
function walkMemberPath(byKey2, start2, segments) {
|
|
4317
4346
|
let node = start2;
|
|
4318
4347
|
for (const segment of segments.slice(0, -1)) {
|
|
4319
4348
|
const next = node.aliases.get(segment);
|
|
4320
4349
|
if (next !== void 0) {
|
|
4321
|
-
const found =
|
|
4350
|
+
const found = byKey2.get(next);
|
|
4322
4351
|
if (!found) return null;
|
|
4323
4352
|
node = found;
|
|
4324
4353
|
continue;
|
|
@@ -4330,13 +4359,13 @@ function walkMemberPath(byKey, start2, segments) {
|
|
|
4330
4359
|
return { project: node, name: segments[segments.length - 1] };
|
|
4331
4360
|
}
|
|
4332
4361
|
function landReference(projects, reference, from) {
|
|
4333
|
-
const
|
|
4334
|
-
const origin =
|
|
4335
|
-
const root =
|
|
4362
|
+
const byKey2 = new Map(projects.map((p) => [p.namespace, p]));
|
|
4363
|
+
const origin = byKey2.get(from);
|
|
4364
|
+
const root = byKey2.get("");
|
|
4336
4365
|
if (!reference.includes("::")) return { kind: "project", project: from, name: reference };
|
|
4337
4366
|
const land = (form, start2, segments2) => {
|
|
4338
4367
|
if (!start2) return { kind: "unresolved", form };
|
|
4339
|
-
const walked = walkMemberPath(
|
|
4368
|
+
const walked = walkMemberPath(byKey2, start2, segments2);
|
|
4340
4369
|
if (walked === "outside") return { kind: "outside", form };
|
|
4341
4370
|
if (walked) return { kind: "project", form, project: walked.project.namespace, name: walked.name };
|
|
4342
4371
|
return { kind: "unresolved", form };
|
|
@@ -4348,7 +4377,7 @@ function landReference(projects, reference, from) {
|
|
|
4348
4377
|
while (segments[0] === "super") {
|
|
4349
4378
|
segments.shift();
|
|
4350
4379
|
if (!node || node.parent === void 0) return { kind: "outside", form: "super" };
|
|
4351
|
-
node =
|
|
4380
|
+
node = byKey2.get(node.parent);
|
|
4352
4381
|
}
|
|
4353
4382
|
if (segments.length === 0 || !node) return { kind: "unresolved", form: "super" };
|
|
4354
4383
|
return land("super", node, segments);
|
|
@@ -4360,11 +4389,11 @@ function landReference(projects, reference, from) {
|
|
|
4360
4389
|
return land(segments.length === 2 ? "alias" : "path", origin, segments);
|
|
4361
4390
|
}
|
|
4362
4391
|
if (origin.subsystems.has(first)) return { kind: "project", project: from, name: segments[segments.length - 1] };
|
|
4363
|
-
const fromRoot = root ? walkMemberPath(
|
|
4392
|
+
const fromRoot = root ? walkMemberPath(byKey2, root, segments) : null;
|
|
4364
4393
|
if (fromRoot && fromRoot !== "outside") return { kind: "project", form: "path", project: fromRoot.project.namespace, name: fromRoot.name };
|
|
4365
4394
|
const byId = projects.filter((p) => p.id === first || p.namespace !== "" && p.namespace === first);
|
|
4366
4395
|
if (byId.length === 1) {
|
|
4367
|
-
const walked = walkMemberPath(
|
|
4396
|
+
const walked = walkMemberPath(byKey2, byId[0], segments.slice(1));
|
|
4368
4397
|
if (walked === "outside") return { kind: "outside", form: "path" };
|
|
4369
4398
|
if (walked) return { kind: "project", form: "path", project: walked.project.namespace, name: walked.name };
|
|
4370
4399
|
}
|
|
@@ -4474,9 +4503,9 @@ function shortestPath(from, to, members, edges, rank) {
|
|
|
4474
4503
|
const v = queue.shift();
|
|
4475
4504
|
for (const w of [...edges.get(v) ?? []].filter((x) => members.has(x)).sort((a, b) => rank(a) - rank(b))) {
|
|
4476
4505
|
if (w === to) {
|
|
4477
|
-
const
|
|
4478
|
-
for (let at = v; at !== from; at = prev.get(at))
|
|
4479
|
-
return
|
|
4506
|
+
const path82 = [w];
|
|
4507
|
+
for (let at = v; at !== from; at = prev.get(at)) path82.unshift(at);
|
|
4508
|
+
return path82;
|
|
4480
4509
|
}
|
|
4481
4510
|
if (seen.has(w)) continue;
|
|
4482
4511
|
seen.add(w);
|
|
@@ -10863,38 +10892,102 @@ function cycleFindings(family, config) {
|
|
|
10863
10892
|
}
|
|
10864
10893
|
return out;
|
|
10865
10894
|
}
|
|
10866
|
-
function approvalFinding(
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
10885
|
-
|
|
10886
|
-
|
|
10887
|
-
|
|
10895
|
+
function approvalFinding(entry, config) {
|
|
10896
|
+
if (entry.state === "never") {
|
|
10897
|
+
return finding(
|
|
10898
|
+
config,
|
|
10899
|
+
"MEMBER_UNAPPROVED",
|
|
10900
|
+
`The member ${named(entry.key)} has never been locked: nobody has approved its design. Lock it at its own root (\`wairon lock\`).`,
|
|
10901
|
+
entry.key
|
|
10902
|
+
);
|
|
10903
|
+
}
|
|
10904
|
+
if (entry.state === "drifted" && entry.upgraded) {
|
|
10905
|
+
return finding(
|
|
10906
|
+
config,
|
|
10907
|
+
"MEMBER_DRIFTED",
|
|
10908
|
+
`The member ${named(entry.key)} was approved under an earlier gate identity \u2014 the gate identity gained inputs in stage 5 (members' composition subjects, \`composition\`; code conformance moved beside the claim), which says nothing about its design. Re-lock it once at its own root (\`wairon lock\`).`,
|
|
10909
|
+
entry.key
|
|
10910
|
+
);
|
|
10911
|
+
}
|
|
10912
|
+
if (entry.state === "drifted") {
|
|
10913
|
+
return finding(
|
|
10914
|
+
config,
|
|
10915
|
+
"MEMBER_DRIFTED",
|
|
10916
|
+
`The member ${named(entry.key)} changed since it was approved: its lock no longer matches its own gate identity. Re-lock it at its own root (\`wairon lock\`).`,
|
|
10917
|
+
entry.key
|
|
10918
|
+
);
|
|
10919
|
+
}
|
|
10920
|
+
return null;
|
|
10888
10921
|
}
|
|
10889
10922
|
function checkMembers(family) {
|
|
10890
10923
|
const config = configOrNull();
|
|
10891
10924
|
const out = [...problemFindings(family, config), ...cycleFindings(family, config)];
|
|
10892
|
-
|
|
10893
|
-
|
|
10894
|
-
out.push(approvalFinding(
|
|
10925
|
+
const present = new Set(family.nodes.filter((n) => n.namespace !== "").map((n) => n.namespace));
|
|
10926
|
+
for (const entry of projectApprovals()) {
|
|
10927
|
+
if (present.has(entry.key)) out.push(approvalFinding(entry, config));
|
|
10895
10928
|
}
|
|
10896
10929
|
return out.filter((f) => f !== null);
|
|
10897
10930
|
}
|
|
10931
|
+
function subjectOf(record2) {
|
|
10932
|
+
return `${record2.stateId.algorithm}:${record2.stateId.digest}`;
|
|
10933
|
+
}
|
|
10934
|
+
function ownApproval(node, root) {
|
|
10935
|
+
return within(node.directory, { topRoot: root, narrowed: true }, () => {
|
|
10936
|
+
const current2 = computeGateStateId();
|
|
10937
|
+
const record2 = readLockRecord();
|
|
10938
|
+
const base = {
|
|
10939
|
+
key: node.namespace,
|
|
10940
|
+
...node.mountAlias !== void 0 ? { alias: node.mountAlias } : {},
|
|
10941
|
+
...node.parent !== void 0 ? { parent: node.parent } : {},
|
|
10942
|
+
...node.id !== void 0 ? { projectId: node.id } : {},
|
|
10943
|
+
state: "never"
|
|
10944
|
+
};
|
|
10945
|
+
if (!record2) return base;
|
|
10946
|
+
const matches = record2.stateId.algorithm === current2.algorithm && record2.stateId.digest === current2.digest;
|
|
10947
|
+
return {
|
|
10948
|
+
...base,
|
|
10949
|
+
state: matches ? "approved" : "drifted",
|
|
10950
|
+
subject: subjectOf(record2),
|
|
10951
|
+
...!matches && record2.stateId.algorithm !== current2.algorithm ? { upgraded: true } : {}
|
|
10952
|
+
};
|
|
10953
|
+
});
|
|
10954
|
+
}
|
|
10955
|
+
function pinOf(parent, alias, subject) {
|
|
10956
|
+
if (!parent) return "unpinned";
|
|
10957
|
+
const recorded = parent.members ? alias in parent.members ? parent.members[alias].subject ?? "never" : void 0 : parent.children?.[alias];
|
|
10958
|
+
if (recorded === void 0) return "unpinned";
|
|
10959
|
+
return recorded === (subject ?? "never") ? "matches" : "moved";
|
|
10960
|
+
}
|
|
10961
|
+
function projectApprovals(depth) {
|
|
10962
|
+
const family = graph();
|
|
10963
|
+
const root = path14.resolve(getProjectRoot());
|
|
10964
|
+
const limit = depth ?? Infinity;
|
|
10965
|
+
const selected = family.nodes.filter((n) => depthOf(family, n) <= limit);
|
|
10966
|
+
const directoryOf = new Map(family.nodes.map((n) => [n.namespace, n.directory]));
|
|
10967
|
+
const parentRecord = (key) => {
|
|
10968
|
+
const dir = directoryOf.get(key);
|
|
10969
|
+
return dir === void 0 ? null : approvalRecord(dir);
|
|
10970
|
+
};
|
|
10971
|
+
const entries = selected.map((node) => {
|
|
10972
|
+
const own = ownApproval(node, root);
|
|
10973
|
+
if (node.parent === void 0 || node.mountAlias === void 0) return own;
|
|
10974
|
+
return { ...own, pinned: pinOf(parentRecord(node.parent), node.mountAlias, own.subject) };
|
|
10975
|
+
});
|
|
10976
|
+
for (const problem of family.problems) {
|
|
10977
|
+
if (problem.kind !== "member-absent" || !problem.id) continue;
|
|
10978
|
+
const parentKey = problem.projects[0] ?? "";
|
|
10979
|
+
const parentNode = family.nodes.find((n) => n.namespace === parentKey);
|
|
10980
|
+
if (!parentNode || depthOf(family, parentNode) + 1 > limit) continue;
|
|
10981
|
+
entries.push({
|
|
10982
|
+
key: parentKey === "" ? problem.id : `${parentKey}::${problem.id}`,
|
|
10983
|
+
alias: problem.id,
|
|
10984
|
+
parent: parentKey,
|
|
10985
|
+
state: "never",
|
|
10986
|
+
pinned: pinOf(parentRecord(parentKey), problem.id, void 0)
|
|
10987
|
+
});
|
|
10988
|
+
}
|
|
10989
|
+
return entries;
|
|
10990
|
+
}
|
|
10898
10991
|
function requiredBy(key) {
|
|
10899
10992
|
return key === "" ? "the family root" : `"${key}"`;
|
|
10900
10993
|
}
|
|
@@ -11783,6 +11876,9 @@ var init_extensions = __esm({
|
|
|
11783
11876
|
});
|
|
11784
11877
|
|
|
11785
11878
|
// src/core/rules/types.ts
|
|
11879
|
+
function judgesCode(rule) {
|
|
11880
|
+
return rule.judges === "code";
|
|
11881
|
+
}
|
|
11786
11882
|
var BUILTIN_PROFILES, PROJECT_KINDS;
|
|
11787
11883
|
var init_types = __esm({
|
|
11788
11884
|
"src/core/rules/types.ts"() {
|
|
@@ -11823,21 +11919,21 @@ function buildCodeIndex(model) {
|
|
|
11823
11919
|
const declarations = /* @__PURE__ */ new Map();
|
|
11824
11920
|
const anchors = /* @__PURE__ */ new Map();
|
|
11825
11921
|
const findingAnchors = /* @__PURE__ */ new Map();
|
|
11826
|
-
const namesAt = (cache2,
|
|
11827
|
-
const cached = cache2.get(
|
|
11922
|
+
const namesAt = (cache2, path82, make) => {
|
|
11923
|
+
const cached = cache2.get(path82);
|
|
11828
11924
|
if (cached) return cached;
|
|
11829
|
-
const f = facts.get(
|
|
11925
|
+
const f = facts.get(path82);
|
|
11830
11926
|
if (!f) return NO_NAMES;
|
|
11831
11927
|
const built = make(f);
|
|
11832
|
-
cache2.set(
|
|
11928
|
+
cache2.set(path82, built);
|
|
11833
11929
|
return built;
|
|
11834
11930
|
};
|
|
11835
11931
|
const republished = /* @__PURE__ */ new Map();
|
|
11836
|
-
const republishedFrom = (
|
|
11837
|
-
const cached = republished.get(
|
|
11932
|
+
const republishedFrom = (path82) => {
|
|
11933
|
+
const cached = republished.get(path82);
|
|
11838
11934
|
if (cached) return cached;
|
|
11839
|
-
const out = /* @__PURE__ */ new Set([
|
|
11840
|
-
const queue = [
|
|
11935
|
+
const out = /* @__PURE__ */ new Set([path82]);
|
|
11936
|
+
const queue = [path82];
|
|
11841
11937
|
while (queue.length) {
|
|
11842
11938
|
const from = queue.pop();
|
|
11843
11939
|
const f = facts.get(from);
|
|
@@ -11850,7 +11946,7 @@ function buildCodeIndex(model) {
|
|
|
11850
11946
|
}
|
|
11851
11947
|
}
|
|
11852
11948
|
}
|
|
11853
|
-
republished.set(
|
|
11949
|
+
republished.set(path82, out);
|
|
11854
11950
|
return out;
|
|
11855
11951
|
};
|
|
11856
11952
|
const NO_ORIGIN = /* @__PURE__ */ new Set();
|
|
@@ -11859,9 +11955,9 @@ function buildCodeIndex(model) {
|
|
|
11859
11955
|
return to ? republishedFrom(to) : NO_ORIGIN;
|
|
11860
11956
|
};
|
|
11861
11957
|
const scopeOf2 = (site, from) => {
|
|
11862
|
-
const
|
|
11863
|
-
const f = facts.get(
|
|
11864
|
-
return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path:
|
|
11958
|
+
const path82 = pathKey(site.from ?? from);
|
|
11959
|
+
const f = facts.get(path82);
|
|
11960
|
+
return f?.status === "analyzed" && f.analysisGrade === "exact" ? { path: path82, facts: f } : void 0;
|
|
11865
11961
|
};
|
|
11866
11962
|
const originOf2 = (site, from) => {
|
|
11867
11963
|
const resolved = scopeOf2(site, from);
|
|
@@ -11900,7 +11996,7 @@ function buildCodeIndex(model) {
|
|
|
11900
11996
|
if (site.via) for (const typeName of localTypesOf(f, site.via)) widenThroughType(typeName);
|
|
11901
11997
|
return out;
|
|
11902
11998
|
};
|
|
11903
|
-
const declarationsAt = (
|
|
11999
|
+
const declarationsAt = (path82) => namesAt(declarations, pathKey(path82), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames]));
|
|
11904
12000
|
const forwardsOf = (start2, name) => {
|
|
11905
12001
|
const out = [];
|
|
11906
12002
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -11926,10 +12022,10 @@ function buildCodeIndex(model) {
|
|
|
11926
12022
|
return {
|
|
11927
12023
|
paths,
|
|
11928
12024
|
exactPaths,
|
|
11929
|
-
factsAt: (
|
|
12025
|
+
factsAt: (path82) => facts.get(pathKey(path82)),
|
|
11930
12026
|
declarationsAt,
|
|
11931
|
-
anchorsAt: (
|
|
11932
|
-
findingAnchorsAt: (
|
|
12027
|
+
anchorsAt: (path82) => namesAt(anchors, pathKey(path82), (f) => /* @__PURE__ */ new Set([...f.declaredNames, ...f.exportedNames, ...f.anchoredNames])),
|
|
12028
|
+
findingAnchorsAt: (path82) => namesAt(findingAnchors, pathKey(path82), (f) => new Set(f.anchoredNames)),
|
|
11933
12029
|
originOf: originOf2,
|
|
11934
12030
|
possibleOriginsOf,
|
|
11935
12031
|
forwardsOf
|
|
@@ -11968,10 +12064,10 @@ function buildRealizationIndex(ctx) {
|
|
|
11968
12064
|
return {
|
|
11969
12065
|
paths,
|
|
11970
12066
|
implementationsOf: (compId) => implsByComponent.get(compId) ?? NONE_IMPL,
|
|
11971
|
-
implementationsAt: (
|
|
12067
|
+
implementationsAt: (path82) => implsByPath.get(pathKey(path82)) ?? NONE_IMPL,
|
|
11972
12068
|
filesOf: (compId) => filesByComponent.get(compId) ?? NONE_PATH,
|
|
11973
12069
|
filesIn: (subsystemId) => filesBySubsystem.get(subsystemId) ?? NONE_PATH,
|
|
11974
|
-
componentsAt: (
|
|
12070
|
+
componentsAt: (path82) => componentsByPath.get(pathKey(path82)) ?? NONE_COMP
|
|
11975
12071
|
};
|
|
11976
12072
|
}
|
|
11977
12073
|
function buildImportGraph(index, universe) {
|
|
@@ -11983,22 +12079,22 @@ function buildImportGraph(index, universe) {
|
|
|
11983
12079
|
if (to && to !== from) into.add(to);
|
|
11984
12080
|
}
|
|
11985
12081
|
};
|
|
11986
|
-
const importsOf = (
|
|
11987
|
-
const cached = imports.get(
|
|
12082
|
+
const importsOf = (path82) => {
|
|
12083
|
+
const cached = imports.get(path82);
|
|
11988
12084
|
if (cached) return cached;
|
|
11989
|
-
const facts = index.factsAt(
|
|
12085
|
+
const facts = index.factsAt(path82);
|
|
11990
12086
|
const out = /* @__PURE__ */ new Set();
|
|
11991
|
-
if (facts?.status === "analyzed") resolveAll(
|
|
11992
|
-
imports.set(
|
|
12087
|
+
if (facts?.status === "analyzed") resolveAll(path82, facts.imports, out);
|
|
12088
|
+
imports.set(path82, out);
|
|
11993
12089
|
return out;
|
|
11994
12090
|
};
|
|
11995
|
-
const tracesOf = (
|
|
11996
|
-
const cached = traces.get(
|
|
12091
|
+
const tracesOf = (path82) => {
|
|
12092
|
+
const cached = traces.get(path82);
|
|
11997
12093
|
if (cached) return cached;
|
|
11998
|
-
const facts = index.factsAt(
|
|
11999
|
-
const out = new Set(importsOf(
|
|
12000
|
-
if (facts?.status === "analyzed") resolveAll(
|
|
12001
|
-
traces.set(
|
|
12094
|
+
const facts = index.factsAt(path82);
|
|
12095
|
+
const out = new Set(importsOf(path82));
|
|
12096
|
+
if (facts?.status === "analyzed") resolveAll(path82, facts.reexports, out);
|
|
12097
|
+
traces.set(path82, out);
|
|
12002
12098
|
return out;
|
|
12003
12099
|
};
|
|
12004
12100
|
return {
|
|
@@ -12134,6 +12230,7 @@ var init_hierarchy_integrity = __esm({
|
|
|
12134
12230
|
init_models();
|
|
12135
12231
|
hierarchyRule = {
|
|
12136
12232
|
name: "hierarchy-integrity",
|
|
12233
|
+
judges: "design",
|
|
12137
12234
|
description: "Every subsystem references the system, every component an existing subsystem, every interface an existing component, and every implementation an existing interface contract. Draft/design specs are reported informationally.",
|
|
12138
12235
|
codes: [
|
|
12139
12236
|
{ code: "DRAFT_SUBSYSTEM_WARNING", defaultSeverity: "warning", summary: "Subsystem is in draft/design status" },
|
|
@@ -12213,6 +12310,7 @@ var init_reserved_id_segments = __esm({
|
|
|
12213
12310
|
"use strict";
|
|
12214
12311
|
reservedIdSegmentsRule = {
|
|
12215
12312
|
name: "reserved-id-segments",
|
|
12313
|
+
judges: "design",
|
|
12216
12314
|
description: 'No segment of a spec id may be the reserved namespace keyword "super" \u2014 the :: grammar consumes it as a namespace hop, so a stored reference to such an id resolves to a different spec.',
|
|
12217
12315
|
codes: [
|
|
12218
12316
|
{ code: "RESERVED_ID_SEGMENT", defaultSeverity: "error", summary: 'Id uses the reserved namespace keyword "super"' }
|
|
@@ -12253,6 +12351,7 @@ var init_local_ids = __esm({
|
|
|
12253
12351
|
init_models();
|
|
12254
12352
|
localIdsRule = {
|
|
12255
12353
|
name: "local-ids",
|
|
12354
|
+
judges: "design",
|
|
12256
12355
|
description: "Inside one project an id names one spec, and an id without `::` is always local. Two spec files of one project declaring one key are DUPLICATE_SPEC_ID: whichever the loader kept, every reference to that key \u2014 local, or `alias::name` from another project \u2014 could mean either, so the tree has no single reading. A local id equal to one of the project's aliases (a `members` or `externals` key) is LOCAL_ID_SHADOWS_PROJECT: the grammar keeps them apart (a bare id is local, `alias::name` crosses), but a reader seeing `billing` beside `billing::invoice_portal` cannot tell a local subsystem from a member, so the shadow is made visible. It reads the project graph's owners, alias tables and duplicate-spec problems.",
|
|
12257
12356
|
codes: [
|
|
12258
12357
|
{ code: "DUPLICATE_SPEC_ID", defaultSeverity: "error", summary: "Two spec files of one project declare the same id" },
|
|
@@ -12329,6 +12428,7 @@ var init_project_identity = __esm({
|
|
|
12329
12428
|
"use strict";
|
|
12330
12429
|
projectIdentityRule = {
|
|
12331
12430
|
name: "project-identity",
|
|
12431
|
+
judges: "design",
|
|
12332
12432
|
description: "The bound project is keyed on its id, so the id must be declared, well-formed, stable and its own: a project that declares none answers to its display name slugified until it writes one (PROJECT_ID_DEFAULTED), a project whose name yields no id or whose declared id breaks the grammar has nothing reliable to key on (PROJECT_ID_AMBIGUOUS), and an id that differs from the one the lock approved has moved under everything that keyed on it (PROJECT_ID_CHANGED). It judges the bound project's own identity only. The family half \u2014 a member with a defaulted id (named with its alias as the id to declare), a member with no id, and two members resolving to one id \u2014 is a family check since stage 4 (family_validator.checkMembers), which reads the family root's graph.",
|
|
12333
12433
|
codes: [
|
|
12334
12434
|
{ code: "PROJECT_ID_AMBIGUOUS", defaultSeverity: "warning", summary: "Project has no usable id: its name yields no slug, or its declared id breaks the grammar" },
|
|
@@ -12350,6 +12450,7 @@ var init_roundtrip_serialization = __esm({
|
|
|
12350
12450
|
"use strict";
|
|
12351
12451
|
roundtripRule = {
|
|
12352
12452
|
name: "roundtrip-serialization",
|
|
12453
|
+
judges: "design",
|
|
12353
12454
|
description: "Every loaded spec must re-serialize through the exact writer pipeline (same relativization, same schema, no I/O) \u2014 validate must predict every refusal that lock's status promotion or any later save would otherwise raise mid-write.",
|
|
12354
12455
|
codes: [
|
|
12355
12456
|
{ code: "ROUNDTRIP_SERIALIZATION", defaultSeverity: "error", summary: "Spec cannot be re-serialized through the writer schema (any save or lock would refuse it)" }
|
|
@@ -12370,6 +12471,7 @@ var init_type_declarations = __esm({
|
|
|
12370
12471
|
"use strict";
|
|
12371
12472
|
typeDeclarationsRule = {
|
|
12372
12473
|
name: "type-declarations",
|
|
12474
|
+
judges: "design",
|
|
12373
12475
|
description: "A type owned by a subsystem must reference an existing one, and a type declaring neither fields nor methods is a placeholder that can inform neither implementers nor the ERD.",
|
|
12374
12476
|
codes: [
|
|
12375
12477
|
{ code: "INVALID_SUBSYSTEM_REFERENCE", defaultSeverity: "error", summary: "Type references a non-existent owning subsystem" },
|
|
@@ -12411,6 +12513,7 @@ var init_field_type_references = __esm({
|
|
|
12411
12513
|
init_models();
|
|
12412
12514
|
fieldTypeReferencesRule = {
|
|
12413
12515
|
name: "field-type-references",
|
|
12516
|
+
judges: "design",
|
|
12414
12517
|
description: "Every type identifier a type field's declared type names must resolve to a builtin or a defined entity/value-object type \u2014 the type's own generic parameters are not field references.",
|
|
12415
12518
|
codes: [
|
|
12416
12519
|
{ code: "UNDEFINED_TYPE_REFERENCE", defaultSeverity: "error", summary: "Reference to a type that is not defined anywhere" }
|
|
@@ -12449,6 +12552,7 @@ var init_signature_type_references = __esm({
|
|
|
12449
12552
|
init_models();
|
|
12450
12553
|
signatureTypeReferencesRule = {
|
|
12451
12554
|
name: "signature-type-references",
|
|
12555
|
+
judges: "design",
|
|
12452
12556
|
description: "Every type identifier an interface method signature names must resolve to a builtin, a generic parameter in scope on the interface or the method, or a defined entity/value-object type.",
|
|
12453
12557
|
codes: [
|
|
12454
12558
|
{ code: "UNDEFINED_TYPE_REFERENCE", defaultSeverity: "error", summary: "Reference to a type that is not defined anywhere" }
|
|
@@ -12492,6 +12596,7 @@ var init_public_surface_binding = __esm({
|
|
|
12492
12596
|
init_models();
|
|
12493
12597
|
publicSurfaceBindingRule = {
|
|
12494
12598
|
name: "public-surface-binding",
|
|
12599
|
+
judges: "design",
|
|
12495
12600
|
description: "Every declared publicInterface names a backing component, that component exists, and the subsystem owns it \u2014 a subsystem may only publish its own components.",
|
|
12496
12601
|
codes: [
|
|
12497
12602
|
{ code: "PUBLIC_INTERFACE_UNBOUND", defaultSeverity: "error", summary: "Public interface with no backing component" },
|
|
@@ -12530,6 +12635,7 @@ var init_public_surface_consumers = __esm({
|
|
|
12530
12635
|
init_models();
|
|
12531
12636
|
publicSurfaceConsumersRule = {
|
|
12532
12637
|
name: "public-surface-consumers",
|
|
12638
|
+
judges: "design",
|
|
12533
12639
|
description: "Every subsystem a published surface names as its consumer must exist in the tree. A consumers list restricts who may depend on the surface, so an id that names nothing locks the surface against a caller nobody can be \u2014 and a typo there silently locks out the caller that was meant, which the boundary rule would then report against the wrong party.",
|
|
12534
12640
|
codes: [
|
|
12535
12641
|
{ code: "PUBLIC_INTERFACE_UNKNOWN_CONSUMER", defaultSeverity: "error", summary: "A published surface names as its consumer a subsystem the tree does not have" }
|
|
@@ -12594,6 +12700,7 @@ var init_public_surface_declared_type = __esm({
|
|
|
12594
12700
|
};
|
|
12595
12701
|
publicSurfaceDeclaredTypeRule = {
|
|
12596
12702
|
name: "public-surface-declared-type",
|
|
12703
|
+
judges: "design",
|
|
12597
12704
|
description: "A publicInterface entry's declared type must be realizable by the stereotype of its backing component; Custom entries whose prose implies eventing must be backed by an event-capable component.",
|
|
12598
12705
|
codes: [
|
|
12599
12706
|
{ code: "PUBLIC_INTERFACE_TYPE_MISMATCH", defaultSeverity: "error", summary: "Backing component cannot realize the declared interface type" },
|
|
@@ -12631,6 +12738,7 @@ var init_public_surface_bound_contract = __esm({
|
|
|
12631
12738
|
init_models();
|
|
12632
12739
|
publicSurfaceBoundContractRule = {
|
|
12633
12740
|
name: "public-surface-bound-contract",
|
|
12741
|
+
judges: "design",
|
|
12634
12742
|
description: "A publicInterface entry that binds an L3 interface must bind one that exists and that belongs to the component the entry publishes.",
|
|
12635
12743
|
codes: [
|
|
12636
12744
|
{ code: "PUBLIC_INTERFACE_INVALID_INTERFACE", defaultSeverity: "error", summary: "Bound L3 interface missing or belonging to another component" }
|
|
@@ -12662,6 +12770,7 @@ var init_export_tables = __esm({
|
|
|
12662
12770
|
init_models();
|
|
12663
12771
|
exportTablesRule = {
|
|
12664
12772
|
name: "export-tables",
|
|
12773
|
+
judges: "design",
|
|
12665
12774
|
description: "Every level's export table must resolve the way a module resolver resolves `export \u2026 from`: one public name binds one target (EXPORT_ID_DUPLICATE when two wildcards \u2014 or two explicit entries \u2014 bind a name to different targets, or two public names share one nameKey, since a bare import could not tell them apart; the name is left out), no chain leads back to itself (EXPORT_CYCLE \u2014 an error for a named chain that never reaches a component or type, a warning for a wildcard cycle, which resolves to its union), every entry names a source and an item that exist, that the source exports and, for an own type, that the level owns \u2014 a subsystem-owned type at L1, a project-level type (no subsystem) for an L0 `{ typeDef }` without `from` \u2014 under a public name of [a-z0-9-_]+ (EXPORT_INVALID), every re-exported component can serve a boundary caller \u2014 a Portal, a gateway being a Portal variant, or an Observer for events (EXPORT_UNCONSUMABLE, at L0 and for L1 re-exports; an error since stage 4), and an L0 entry that re-exports a member's or an external's export never declares a wider audience than that export has (EXPORT_WIDENS_AUDIENCE): a re-export can narrow reach, never widen it. The rule judges the facts the export resolver recorded in the scan \u2014 for the bound root's table and every contained member's \u2014 and resolves nothing itself.",
|
|
12666
12775
|
codes: [
|
|
12667
12776
|
{ code: "EXPORT_ID_DUPLICATE", defaultSeverity: "error", summary: "One public name bound to different targets by two wildcard re-exports or two explicit entries" },
|
|
@@ -12735,6 +12844,7 @@ var init_external_declarations = __esm({
|
|
|
12735
12844
|
init_models();
|
|
12736
12845
|
externalDeclarationsRule = {
|
|
12737
12846
|
name: "external-declarations",
|
|
12847
|
+
judges: "design",
|
|
12738
12848
|
description: "Every external a project declares in project.yaml must be usable: its alias fits [a-z0-9-_]+ (a dotted producer id needs an explicit alias) and is not also a `members` key (one alias names one project), its producer id fits the project-id grammar, and it has something to be judged against \u2014 a project of the scan answers to the id, source.path names a directory (whose project, when it is in the scan, answers to the declared id), or the project holds a pin for the alias. A declaration that fails is reported (EXTERNAL_UNRESOLVED) with its reason, so a misspelled declaration never passes as declared. A member validated from its own root that declares its parent or a sibling is not reported when it has pinned it: the pin is what its gate judges against, and nothing is climbed to find the producer. Every name an external's or a member's `use` lists by name must be a public name the producer exports to this project \u2014 a contained member's live L0 table, an external's pin \u2014 compared by nameKey (IMPORT_UNRESOLVED, naming the closest public names; a `use` on an unpinned external is left to the pin's own EXTERNAL_CHECK_UNAVAILABLE), and must not share its nameKey with a spec of the project itself (IMPORT_SHADOWED_BY_LOCAL: the local spec wins every bare reference, so the named import can never take effect and says something false \u2014 rename one or drop the import). A `*` import shadowed by locals is silent, as a glob import is in Rust. One code, one meaning: IMPORT_UNRESOLVED is only a name the producer does not export, IMPORT_SHADOWED_BY_LOCAL only a named import a local spec hides. A malformed `use` entry is the declaration's problem (EXTERNAL_UNRESOLVED for an external, the member's for a member). The findings name no spec: externals and members live in configuration. It reads the bound root's node of the project graph, its alias-conflict problems, the members' export tables, the project's own spec ids and ctx.pinnedExternals.",
|
|
12739
12849
|
codes: [
|
|
12740
12850
|
{ code: "EXTERNAL_UNRESOLVED", defaultSeverity: "notice", summary: "A declared external whose alias is malformed or taken by a member, whose producer id is malformed, or whose producer the family and its source.path do not provide" },
|
|
@@ -12836,6 +12946,7 @@ var init_reference_forms = __esm({
|
|
|
12836
12946
|
init_models();
|
|
12837
12947
|
referenceFormsRule = {
|
|
12838
12948
|
name: "reference-forms",
|
|
12949
|
+
judges: "design",
|
|
12839
12950
|
description: "A cross-project reference is `alias::name` (or a bare name a `use` imports): the alias is one of the referring project's members or externals, the name a public name of that project's L0 table. The older forms still bind for one release and are reported (DEPRECATED_REFERENCE_FORM), each naming the spec, where in it, the form as written and the text to write instead (the scan's rewrite: a bare local id, or `alias::publicName`): a leading `::`, which escapes to the root of whatever checkout loads it; `super::`, which climbs the containing projects \u2014 a place in one family, not a name; a member path (`desktop::shell` written inside desktop, `waffler_core::transpiler::x` written inside transpiler), a first segment the referring project does not declare, read from the bound root; and a self-prefix \u2014 the referring project's own id as the first segment, with `::` or `.` (`registry.advisory-channel` inside registry), which stage 3's migration did not rewrite and stage 4's positional step does. Wairon's own writer emits none of them. A form with no rewrite (its target is out of reach) is reported with the reason. It reads the project graph's authored references, which the scan records before binding erases the form.",
|
|
12840
12951
|
codes: [
|
|
12841
12952
|
{ code: "DEPRECATED_REFERENCE_FORM", defaultSeverity: "notice", summary: "A reference written with a leading ::, super:: or a member path where a local id or alias::name belongs" }
|
|
@@ -12872,6 +12983,7 @@ var init_member_declarations = __esm({
|
|
|
12872
12983
|
DECLARATION_FIELDS = /* @__PURE__ */ new Set(["id", "name", "description", "parentSystem", "projectPath", "status", "createdAt", "updatedAt"]);
|
|
12873
12984
|
memberDeclarationsRule = {
|
|
12874
12985
|
name: "member-declarations",
|
|
12986
|
+
judges: "design",
|
|
12875
12987
|
description: "A project declares its members in project.yaml `members`; a member is not a subsystem and carries no content in its parent. The L1 form \u2014 a subsystem carrying projectPath \u2014 still loads for one release and is reported (DEPRECATED_MOUNT_FORM), naming the mount, its path, the `members` entry that replaces it, and every field it carries that `doctor --fix` will move into the member (or stop on); a mount whose alias `members` also declares is reported as ignored. It reads the project graph's nodes and their legacy mounts.",
|
|
12876
12988
|
codes: [
|
|
12877
12989
|
{ code: "DEPRECATED_MOUNT_FORM", defaultSeverity: "notice", summary: "A member declared as an L1 subsystem carrying projectPath instead of a project.yaml `members` entry" }
|
|
@@ -12935,6 +13047,7 @@ var init_project_boundaries = __esm({
|
|
|
12935
13047
|
init_models();
|
|
12936
13048
|
projectBoundariesRule = {
|
|
12937
13049
|
name: "project-boundaries",
|
|
13050
|
+
judges: "design",
|
|
12938
13051
|
description: "The one judge of every cross-project reference the bound project writes (decision 9 \u2014 every project is a crate of its own, reached only through its L0 exports and only as a dependency it declared). It reads each of the project's own references from the project graph \u2014 `::` forms and bare names bound through `use` imports alike \u2014 takes its resolution from resolveCrossProject, decided before any severity from the project's own files alone, and reports every outcome but resolved once, carrying the resolution: forbidden is EXTERNAL_UNDECLARED (the reference reaches a project the referrer declares neither as a member nor as an external, or names a first segment that is none of its subsystems, aliases or foreign providers); missing is EXTERNAL_NOT_EXPORTED (the producer's table \u2014 a contained member's live one, or the external's pin \u2014 has no public name covering the referrer, or not the used method, or exports the type only inside a signature's closure); an ambiguous import is IMPORT_AMBIGUOUS (two imports supply one bare name: two explicit `use` names, or two `*` with no explicit one \u2014 name it `alias::name` or narrow a `use`); unavailable is EXTERNAL_CHECK_UNAVAILABLE (no pin, an unreadable pin, a member absent on disk, or a deprecated form naming no alias \u2014 never a pass). A bare name no import supplies is not this rule's: it is the unresolved-reference finding it always was, with rule_context.importHint appended. A trustedLinks entry naming a subsystem of another project (`alias::sub`) is TRUSTED_LINK_CROSSES_PROJECT: a sanctioned fast lane stays inside one project. Stage 4 makes the three stage-2 notices errors now that the owner's gate judges them exactly; a reference is never left to a parent's verdict.",
|
|
12939
13052
|
codes: [
|
|
12940
13053
|
{ code: "EXTERNAL_UNDECLARED", defaultSeverity: "error", summary: "A reference reaches another project the referring project declares neither as a member nor as an external, or names a first segment that is none of its subsystems, aliases or foreign providers" },
|
|
@@ -13032,6 +13145,7 @@ var init_lint_allows = __esm({
|
|
|
13032
13145
|
"use strict";
|
|
13033
13146
|
lintAllowsRule = {
|
|
13034
13147
|
name: "lint-allows",
|
|
13148
|
+
judges: "design",
|
|
13035
13149
|
description: "Per-spec lint suppressions (lint.allow) must name real issue codes and actually suppress a finding \u2014 unknown codes and stale allows are flagged. An allow covers exactly the occurrence it names: a finding that reports a site is silenced only by an allow whose `at` is that site, a finding that reports none only by an allow that names none, and an aggregating finding only by an allow whose `covers` lists every unit it reports \u2014 a unit nobody listed is named back as new instead of inheriting a decision taken about its neighbours. So a coarse allow left on a rule that names sites, and an allow whose site the run no longer reports, are both UNUSED_LINT_ALLOW, and the finding names the sites that did fire. Allows silence warnings and notices; errors always surface.",
|
|
13036
13150
|
codes: [
|
|
13037
13151
|
{ code: "UNKNOWN_LINT_ALLOW_CODE", defaultSeverity: "warning", summary: "lint.allow names an issue code no registered rule emits" },
|
|
@@ -13079,6 +13193,7 @@ var init_contract_symmetry = __esm({
|
|
|
13079
13193
|
"use strict";
|
|
13080
13194
|
contractSymmetryRule = {
|
|
13081
13195
|
name: "contract-symmetry",
|
|
13196
|
+
judges: "design",
|
|
13082
13197
|
description: "Implementations mirror their contract method-for-method: every method an implementation declares is defined on the contract it realizes, and every contract method has an implementation.",
|
|
13083
13198
|
codes: [
|
|
13084
13199
|
{ code: "UNEXPECTED_IMPLEMENTATION_METHOD", defaultSeverity: "error", summary: "Implementation method not present on the contract" },
|
|
@@ -13145,6 +13260,7 @@ var init_narrative_target_references = __esm({
|
|
|
13145
13260
|
};
|
|
13146
13261
|
narrativeTargetReferencesRule = {
|
|
13147
13262
|
name: "narrative-target-references",
|
|
13263
|
+
judges: "design",
|
|
13148
13264
|
description: `A narrative call, dispatch or register step must name a target component, and a target method unless it dispatches; a method's declared calls name the same pair as one "<component>.<method>" reference, the spelling the conformance debt register and a lint allow name a unit with. A target this tree contains must be a collaborator the calling component declares, must carry the named method on one of its interfaces, and that method must declare every semantic guarantee the entry asserts.`,
|
|
13149
13265
|
codes: [
|
|
13150
13266
|
{ code: "MISSING_TARGET_COMPONENT", defaultSeverity: "error", summary: "Call/register step missing targetComponent" },
|
|
@@ -13253,6 +13369,7 @@ var init_cross_tree_references = __esm({
|
|
|
13253
13369
|
init_models();
|
|
13254
13370
|
crossTreeReferencesRule = {
|
|
13255
13371
|
name: "cross-tree-references",
|
|
13372
|
+
judges: "design",
|
|
13256
13373
|
description: `A narrative call, dispatch or register target that is not a component of this tree either leaves the project \u2014 and then its resolution (resolveCrossProject) decides: resolved is surface-reference-backing's, foreign snapshots that disagree are SURFACE_REF_AMBIGUOUS here, and every other outcome is project-boundaries' single finding \u2014 or it is a local id that names nothing, the INVALID_TARGET_COMPONENT_REFERENCE typo it always was. The CROSS_TREE_REF_UNRESOLVED warning ("only the parent project can verify it") is retired: no reference is left for a parent to judge.`,
|
|
13257
13374
|
codes: [
|
|
13258
13375
|
{ code: "INVALID_TARGET_COMPONENT_REFERENCE", defaultSeverity: "error", summary: "Call/register step targets a non-existent component" },
|
|
@@ -13315,6 +13432,7 @@ var init_surface_reference_backing = __esm({
|
|
|
13315
13432
|
"use strict";
|
|
13316
13433
|
surfaceReferenceBackingRule = {
|
|
13317
13434
|
name: "surface-reference-backing",
|
|
13435
|
+
judges: "design",
|
|
13318
13436
|
description: "The contract entry a narrative target in another project resolves to \u2014 a contained member's live export table, a declared external's pinned snapshot, or a foreign surface snapshot \u2014 must back what the step asks of it: the calling component declares the collaborator, the entry exposes the called method (or serves the dispatched capability), and it declares every semantic guarantee the step asserts. These are contract verdicts on a resolved reference, and each carries that resolution.",
|
|
13319
13437
|
codes: [
|
|
13320
13438
|
{ code: "SURFACE_REF_NOT_EXPOSED", defaultSeverity: "error", summary: "Cross-tree reference resolves to a surface snapshot that does not expose the called method/capability" },
|
|
@@ -13404,6 +13522,7 @@ var init_guarantee_tokens = __esm({
|
|
|
13404
13522
|
init_models();
|
|
13405
13523
|
guaranteeTokensRule = {
|
|
13406
13524
|
name: "guarantee-tokens",
|
|
13525
|
+
judges: "design",
|
|
13407
13526
|
description: "Every semantic-guarantee token an L3 method declares or a narrative step asserts must be a builtin guarantee or one a loaded extension pack declares in its `guarantees` list. The vocabulary is open for packs, not for typos \u2014 an undeclared token is matched by nothing and silently escapes the narrative\u2194contract consistency checks.",
|
|
13408
13527
|
codes: [
|
|
13409
13528
|
{ code: "UNKNOWN_GUARANTEE", defaultSeverity: "warning", summary: "Guarantee token is neither builtin nor pack-declared" }
|
|
@@ -13458,6 +13577,7 @@ var init_narrative_step_config = __esm({
|
|
|
13458
13577
|
init_models();
|
|
13459
13578
|
narrativeStepConfigRule = {
|
|
13460
13579
|
name: "narrative-step-config",
|
|
13580
|
+
judges: "design",
|
|
13461
13581
|
description: 'The step configuration of L5 narratives: every flow step (branch, switch, loop, try, parallel, jump) carries the config its type requires and no simple step carries flow config, "detach" (fire-and-forget) is legal on call/dispatch steps only, no step carries a field foreign to its type, a parallel body is covered by contiguous, ordered arms (>= 2), no two steps share a step number or a label, and every jump field targets an existing step number in the same narrative.',
|
|
13462
13582
|
codes: [
|
|
13463
13583
|
{ code: "MALFORMED_FLOW_STEP", defaultSeverity: "error", summary: "Flow step missing required config (or flow config on a local/call step)" },
|
|
@@ -13499,6 +13619,7 @@ var init_narrative_reachability = __esm({
|
|
|
13499
13619
|
init_models();
|
|
13500
13620
|
narrativeReachabilityRule = {
|
|
13501
13621
|
name: "narrative-reachability",
|
|
13622
|
+
judges: "design",
|
|
13502
13623
|
description: "Every step of an L5 narrative must be reachable from the first step following fall-through and jumps, and the loop/try/parallel regions it declares must nest or stay disjoint \u2014 interleaved regions map onto no structured code. Judged over the step graph, and only for a narrative whose step configuration is sound (narrative-step-config reports the rest).",
|
|
13503
13624
|
codes: [
|
|
13504
13625
|
{ code: "UNREACHABLE_STEP", defaultSeverity: "warning", summary: "Step not reachable from the first step following fall-through and jumps" },
|
|
@@ -13588,6 +13709,7 @@ var init_narrative_jump_edges = __esm({
|
|
|
13588
13709
|
init_models();
|
|
13589
13710
|
narrativeJumpEdgesRule = {
|
|
13590
13711
|
name: "narrative-jump-edges",
|
|
13712
|
+
judges: "design",
|
|
13591
13713
|
description: "Where an L5 narrative's jump edges land: a loop/try/parallel region is entered through its header, never into the middle of its body; a backward jump is only idiomatic as a continue to an enclosing loop header, and otherwise models repetition that belongs in a loop step; and a try body must not fall through into its own catch/finally region on the success path. Judged only for a narrative whose step configuration is sound (narrative-step-config reports the rest).",
|
|
13592
13714
|
codes: [
|
|
13593
13715
|
{ code: "JUMP_INTO_REGION", defaultSeverity: "warning", summary: "Jump lands in the middle of a loop/try body from outside \u2014 regions are entered through their header" },
|
|
@@ -13776,6 +13898,7 @@ var init_meaningless_branches = __esm({
|
|
|
13776
13898
|
init_completed_step_graph();
|
|
13777
13899
|
meaninglessBranchesRule = {
|
|
13778
13900
|
name: "meaningless-branches",
|
|
13901
|
+
judges: "design",
|
|
13779
13902
|
description: "A branch or switch whose arms all target the same step decides nothing: the condition is evaluated and flow continues at one place whatever it answers. A decision's targets are its explicit ones plus, where it leaves its fall-through field unset, the steps the step graph continues it at \u2014 so the classic authoring slip of pointing both arms at the next step is caught. Structure-only \u2014 a warning, lint.allow-suppressible.",
|
|
13780
13903
|
codes: [
|
|
13781
13904
|
{ code: "MEANINGLESS_BRANCH", defaultSeverity: "warning", summary: "Branch/switch whose arms all target the same step \u2014 the decision changes nothing" }
|
|
@@ -13835,6 +13958,7 @@ var init_inescapable_cycles = __esm({
|
|
|
13835
13958
|
init_completed_step_graph();
|
|
13836
13959
|
inescapableCyclesRule = {
|
|
13837
13960
|
name: "inescapable-cycles",
|
|
13961
|
+
judges: "design",
|
|
13838
13962
|
description: "A cycle in a narrative's step graph with no exit edge and no return or throw member never terminates, by construction: every path that enters it stays inside it forever. Read over the step graph closed by a completion step, so falling off the end of the narrative counts as leaving. Structure-only \u2014 prose conditions are never judged \u2014 and reported as a warning, lint.allow-suppressible.",
|
|
13839
13963
|
codes: [
|
|
13840
13964
|
{ code: "INESCAPABLE_CYCLE", defaultSeverity: "warning", summary: "Step cycle with no exit edge and no return/throw member \u2014 never terminates by construction" }
|
|
@@ -13883,6 +14007,7 @@ var init_unconditional_call_cycles = __esm({
|
|
|
13883
14007
|
byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
13884
14008
|
unconditionalCallCyclesRule = {
|
|
13885
14009
|
name: "unconditional-call-cycles",
|
|
14010
|
+
judges: "design",
|
|
13886
14011
|
description: "A method-level call cycle across components in which every call edge is unavoidable on every entry-to-exit path of its narrative is unbounded recursion, by construction. Dispatch steps are resolved through their portal's table; register steps are never edges (a handoff defers the invocation to the runtime, so it cannot recurse). A cycle with even one guarded edge is NOT flagged, since prose conditions are never judged. Structure-only \u2014 a warning, lint.allow-suppressible, anchored on the member edge that sorts first so one allow covers the cycle.",
|
|
13887
14012
|
codes: [
|
|
13888
14013
|
{ code: "UNCONDITIONAL_CALL_CYCLE", defaultSeverity: "warning", summary: "Cross-component call cycle in which every call edge is unavoidable \u2014 unbounded recursion by construction" }
|
|
@@ -13942,11 +14067,11 @@ var init_unconditional_call_cycles = __esm({
|
|
|
13942
14067
|
const memberEdges = keys.flatMap((k) => (adjacency.get(k) ?? []).filter((e) => inScc.has(e.toKey)));
|
|
13943
14068
|
if (memberEdges.length === 0) continue;
|
|
13944
14069
|
const anchor = [...memberEdges].sort((a, b) => byCodeUnit(a.fromKey, b.fromKey) || byCodeUnit(a.impl.id, b.impl.id) || a.stepNumber - b.stepNumber)[0];
|
|
13945
|
-
const
|
|
14070
|
+
const path82 = [...keys].sort(byCodeUnit).join(" \u2192 ");
|
|
13946
14071
|
ctx.addIssue(
|
|
13947
14072
|
"warning",
|
|
13948
14073
|
"UNCONDITIONAL_CALL_CYCLE",
|
|
13949
|
-
`Call cycle with no guard: ${
|
|
14074
|
+
`Call cycle with no guard: ${path82} \u2014 every call edge in this cycle is unavoidable on all paths of its narrative (e.g. step ${anchor.stepNumber} of "${anchor.methodName}" in "${anchor.impl.id}" always calls ${anchor.toLabel}). This recurses without a base case, by construction. Guard at least one edge with a branch/return before the call, or lint.allow with the termination argument.`,
|
|
13950
14075
|
anchor.impl.id,
|
|
13951
14076
|
memberEdges.some((e) => ctx.isImplementationDraft(e.impl))
|
|
13952
14077
|
);
|
|
@@ -13964,6 +14089,7 @@ var init_narrative_detail = __esm({
|
|
|
13964
14089
|
init_models();
|
|
13965
14090
|
narrativeDetailRule = {
|
|
13966
14091
|
name: "narrative-detail",
|
|
14092
|
+
judges: "design",
|
|
13967
14093
|
description: "The narrative detail dial: each method resolves to full | calls-only | intent (method override \u2192 spec default \u2192 stereotype default). full requires a narrative; intent-level methods without a narrative must specify behavior as non-trivial prose (L4 intent or L3 description) \u2014 dialing detail down never means leaving behavior unspecified. Explicit declarations are held to their promise as errors; stereotype-defaulted gaps surface as warnings. An honest lint over declarations \u2014 it never claims the narrative or the prose is CORRECT.",
|
|
13968
14094
|
codes: [
|
|
13969
14095
|
{ code: "MISSING_NARRATIVE", defaultSeverity: "warning", summary: "Method resolved to detail: full but has no narrative (error when the level was declared explicitly)" },
|
|
@@ -14016,6 +14142,7 @@ var init_detail_sufficiency = __esm({
|
|
|
14016
14142
|
DEFAULT_MAX_UNNARRATED_COMPLEXITY = 8;
|
|
14017
14143
|
detailSufficiencyRule = {
|
|
14018
14144
|
name: "detail-sufficiency",
|
|
14145
|
+
judges: "code",
|
|
14019
14146
|
description: "A method dialed below detail: full with no narrative may not be hiding behavior it owes a reader. A method whose realized function \u2014 in the method's own source file, else the implementation's \u2014 measures real branching (cyclomatic complexity above rules.complexity.maxUnnarratedComplexity, exact AST grade only) is reported (UNNARRATED_COMPLEXITY), and an explicit dial below a full-floor logic stereotype without a narrative is a visible, lint.allow-justifiable choice (DETAIL_BELOW_STEREOTYPE) \u2014 reported only where the measured finding did not already fire, since evidence outranks expectation. Both are honest lints over declarations: they never claim a narrative would be correct, only that the dial hides something.",
|
|
14020
14147
|
codes: [
|
|
14021
14148
|
{ code: "UNNARRATED_COMPLEXITY", defaultSeverity: "warning", summary: "Method below detail: full with no narrative whose realized function, in the method's source file, has real branching (cyclomatic complexity over the configured threshold, exact-grade analysis only)" },
|
|
@@ -14079,6 +14206,7 @@ var init_portal_fields = __esm({
|
|
|
14079
14206
|
"use strict";
|
|
14080
14207
|
portalFieldsRule = {
|
|
14081
14208
|
name: "portal-fields",
|
|
14209
|
+
judges: "design",
|
|
14082
14210
|
scope: "spec",
|
|
14083
14211
|
description: "A Portal declares its portalType. Non-Portal components carry no portalType, basePath, or auth (auth is inbound transport auth \u2014 it belongs on the Portal that exposes the surface). Intrinsic to one component: no tree required.",
|
|
14084
14212
|
codes: [
|
|
@@ -14132,6 +14260,7 @@ var init_durability_declaration = __esm({
|
|
|
14132
14260
|
"use strict";
|
|
14133
14261
|
durabilityDeclarationRule = {
|
|
14134
14262
|
name: "durability-declaration",
|
|
14263
|
+
judges: "design",
|
|
14135
14264
|
scope: "spec",
|
|
14136
14265
|
description: "Every Store declares its durability (MISSING_DURABILITY) and nothing but a Store may declare one (DURABILITY_ON_NON_STORE). Intrinsic to one component: no tree required. The round-trip consequences of the declaration are enforced by durability-round-trip.",
|
|
14137
14266
|
codes: [
|
|
@@ -14176,6 +14305,7 @@ var init_logic_declaration = __esm({
|
|
|
14176
14305
|
init_models();
|
|
14177
14306
|
logicDeclarationRule = {
|
|
14178
14307
|
name: "logic-declaration",
|
|
14308
|
+
judges: "design",
|
|
14179
14309
|
scope: "spec",
|
|
14180
14310
|
description: "Only an Orchestrator may declare a dependencyClass (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR); what the class allows is judged by logic-dependency-class. Intrinsic to one component: no tree required.",
|
|
14181
14311
|
codes: [
|
|
@@ -14207,6 +14337,7 @@ var init_retired_stereotypes = __esm({
|
|
|
14207
14337
|
init_models();
|
|
14208
14338
|
retiredStereotypesRule = {
|
|
14209
14339
|
name: "retired-stereotypes",
|
|
14340
|
+
judges: "design",
|
|
14210
14341
|
scope: "spec",
|
|
14211
14342
|
description: "A component typed with a retired stereotype is an error until it is migrated (STEREOTYPE_RETIRED). A Specialist becomes an Orchestrator with the dependencyClass its dependencies give it, which `wairon doctor --fix` applies. A Gateway becomes the Portal it owns, with the gateway variant: its other members are depended on rather than owned, its consumers depend on that Portal, and the Gateway spec is deleted, steps the finding lists for the author. Intrinsic to one component: no tree required.",
|
|
14212
14343
|
codes: [
|
|
@@ -14256,6 +14387,7 @@ var init_portal_endpoints = __esm({
|
|
|
14256
14387
|
};
|
|
14257
14388
|
portalsRule = {
|
|
14258
14389
|
name: "portal-endpoints",
|
|
14390
|
+
judges: "design",
|
|
14259
14391
|
description: "A Portal binds every interface method to a concrete endpoint of the transport its portalType expects.",
|
|
14260
14392
|
codes: [
|
|
14261
14393
|
{ code: "MISSING_ENDPOINT", defaultSeverity: "error", summary: "Portal method without a wire endpoint binding" },
|
|
@@ -14302,6 +14434,7 @@ var init_non_portal_endpoints = __esm({
|
|
|
14302
14434
|
"use strict";
|
|
14303
14435
|
nonPortalEndpointsRule = {
|
|
14304
14436
|
name: "non-portal-endpoints",
|
|
14437
|
+
judges: "design",
|
|
14305
14438
|
description: "Only a Portal may carry endpoints: a method on a non-Portal component's interface that declares one is publishing a block that is not the system's boundary as if it were.",
|
|
14306
14439
|
codes: [
|
|
14307
14440
|
{ code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
|
|
@@ -14337,6 +14470,7 @@ var init_portal_call_auth = __esm({
|
|
|
14337
14470
|
"use strict";
|
|
14338
14471
|
portalCallAuthRule = {
|
|
14339
14472
|
name: "portal-call-auth",
|
|
14473
|
+
judges: "design",
|
|
14340
14474
|
description: "Hardens authenticated cross-service calls. An OUTBOUND narrative `call` into ANOTHER component's Portal whose auth is not `none` must (a) be made by an Adapter \u2014 the only block that does external I/O \u2014 and (b) declare the credential source it presents via the step's `auth.from`. Absence of a source warns PORTAL_AUTH_UNMET; a non-Adapter presenter warns AUTH_PRESENTER_NOT_ADAPTER. The actual secret is never stored in the spec. Dispatch steps (a portal's OWN inbound routing) and self-calls are not cross-service calls and are excluded.",
|
|
14341
14475
|
codes: [
|
|
14342
14476
|
{ code: "PORTAL_AUTH_UNMET", defaultSeverity: "warning", summary: "A narrative call into another component's authed Portal does not declare where its credential loads from" },
|
|
@@ -14388,6 +14522,7 @@ var init_auth_source_wiring = __esm({
|
|
|
14388
14522
|
COMPONENT_REF_PREFIX = "component:";
|
|
14389
14523
|
authSourceWiringRule = {
|
|
14390
14524
|
name: "auth-source-wiring",
|
|
14525
|
+
judges: "design",
|
|
14391
14526
|
description: "Resolves the credential source a narrative call presents to an authenticated Portal. When the step's `auth.from` is a modeled reference (`component:<id>`) it must name a component that exists (UNKNOWN_AUTH_SOURCE), that can hold or load a secret \u2014 an Adapter or a Store (AUTH_SOURCE_NOT_PROVIDER) \u2014 and that the presenting component depends on or owns (AUTH_SOURCE_UNWIRED). Opaque sources (env:/config:/vault:/free note) are design notes and are never resolved; a missing source is portal-call-auth's finding.",
|
|
14392
14527
|
codes: [
|
|
14393
14528
|
{ code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
|
|
@@ -14460,6 +14595,7 @@ var init_subsystem_boundary_dependencies = __esm({
|
|
|
14460
14595
|
init_models();
|
|
14461
14596
|
subsystemBoundaryDepsRule = {
|
|
14462
14597
|
name: "subsystem-boundary-dependencies",
|
|
14598
|
+
judges: "design",
|
|
14463
14599
|
description: "Judges every dependsOn edge that leaves its own subsystem, and every one that resolves nowhere. Across subsystems the shape is client Adapter \u2192 published remote Portal, OR a direct in-process edge licensed by a trustedLink declared on the SOURCE subsystem (the published-Portal target requirement applies either way). A published surface may also name the subsystems it serves (a publicInterfaces entry's consumers): a component every one of whose entries names consumers may be depended on only from those subsystems. An edge into another project is judged on the contract entry it resolves to (resolveSurfaceRef \u2014 a contained member's live table, a declared external's pin, or a foreign snapshot): the same Adapter requirement applies to it, and foreign snapshots of several providers that disagree make it SURFACE_REF_AMBIGUOUS. An edge into another project that does not resolve is reported once, by project-boundaries, with its resolution \u2014 never here, and never as the retired CROSS_TREE_REF_UNRESOLVED warning. A bare id that names nothing is INVALID_DEPENDENCY_REFERENCE. Boundary rules are never relaxed by a pack profile.",
|
|
14464
14600
|
codes: [
|
|
14465
14601
|
{ code: "INVALID_DEPENDENCY_REFERENCE", defaultSeverity: "error", summary: "dependsOn names a non-existent component" },
|
|
@@ -14588,6 +14724,7 @@ var init_logic_dependency_class = __esm({
|
|
|
14588
14724
|
init_stereotype_terms();
|
|
14589
14725
|
logicDependencyClassRule = {
|
|
14590
14726
|
name: "logic-dependency-class",
|
|
14727
|
+
judges: "design",
|
|
14591
14728
|
description: "Bounds an Orchestrator's dependencies by the dependencyClass it declares: pure logic depends only on pure Orchestrators; read logic also on read Orchestrators, Repositories, Indexes and Adapters; an Orchestrator that declares no class is a workflow and may depend on anything the rest of the matrix allows. Whether read logic calls a WRITE method on one of those is judged only once facade methods carry effect tags.",
|
|
14592
14729
|
codes: [
|
|
14593
14730
|
{ code: "DEPENDENCY_CLASS_VIOLATION", defaultSeverity: "error", summary: "Orchestrator depending on a component its dependencyClass does not allow: pure logic depends only on pure Orchestrators; read logic also on read Orchestrators, Repositories, Indexes and Adapters" }
|
|
@@ -14652,6 +14789,7 @@ var init_data_block_dependencies = __esm({
|
|
|
14652
14789
|
init_stereotype_terms();
|
|
14653
14790
|
dataBlockDepsRule = {
|
|
14654
14791
|
name: "data-block-dependencies",
|
|
14792
|
+
judges: "design",
|
|
14655
14793
|
description: "Keeps the data layer facing down. A Store may depend only on another Store, a backend Adapter or pure logic \u2014 Registries and Indexes depend on the Store, never the reverse. A Registry is the write path to its Store and may depend on that Store, a backend Adapter, or the pure logic that validates the write. An Index is a read projection and a Query a computed read, each over its Store through a backend Adapter where one serves it; as an exceptional case an Index may project another Index of the same Repository, never in a cycle. An Adapter is a sink toward the system: it may use pure logic, but never a Store or any other Orchestrator.",
|
|
14656
14794
|
codes: [
|
|
14657
14795
|
{ code: "ARCHITECTURE_VIOLATION_STORE_DEP", defaultSeverity: "error", summary: "Store depending on anything but another Store, a backend Adapter or pure logic" },
|
|
@@ -14728,6 +14866,7 @@ var init_entrypoint_dependencies = __esm({
|
|
|
14728
14866
|
init_stereotype_terms();
|
|
14729
14867
|
entrypointDepsRule = {
|
|
14730
14868
|
name: "entrypoint-dependencies",
|
|
14869
|
+
judges: "design",
|
|
14731
14870
|
description: "Judges the edges at the system's entry points and its process layer. Portals and Observers are top-level entry points and subscribers, so nothing may depend on them \u2014 and that is the edge's one finding, which is why no other matrix rule judges it. Downward, a Portal dispatches to Orchestrators and may READ through Indexes and Repository facades but never reaches Store/Registry/Query or an Adapter, while an Observer forwards to one Orchestrator or Supervisor over a message-bus Adapter. A View stays a passive presenter. A Supervisor reaches data only through workflows, and a component depending on a live Actor must also depend on a Supervisor that supervises it.",
|
|
14732
14871
|
codes: [
|
|
14733
14872
|
{ code: "ARCHITECTURE_VIOLATION_PORTAL_DEP", defaultSeverity: "error", summary: "Component depending on a Portal/Observer" },
|
|
@@ -14811,6 +14950,7 @@ var init_portal_write_shortcut = __esm({
|
|
|
14811
14950
|
"use strict";
|
|
14812
14951
|
portalWriteShortcutRule = {
|
|
14813
14952
|
name: "portal-write-shortcut",
|
|
14953
|
+
judges: "design",
|
|
14814
14954
|
description: "A Portal narrative call step or dispatch-table binding that reaches a write-effect method on a Repository or Index directly is the write shortcut: the Portal\u2192data-facade edge is licensed for reads only, and a write routes through an Orchestrator that owns the workflow. A dispatch step reaches its server only through a table binding, so judging every binding judges each dispatch step that takes it, once, where the route is declared. Methods that carry no effect tag are not judged.",
|
|
14815
14955
|
codes: [
|
|
14816
14956
|
{ code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative call or dispatch-table binding reaches a write-effect method on a Repository/Index directly \u2014 reads may shortcut, writes route through an Orchestrator (judged on effect-tagged facade methods; untagged methods are not yet judged)" }
|
|
@@ -14868,6 +15008,7 @@ var init_pattern_membership = __esm({
|
|
|
14868
15008
|
init_models();
|
|
14869
15009
|
patternMembershipRule = {
|
|
14870
15010
|
name: "pattern-membership",
|
|
15011
|
+
judges: "design",
|
|
14871
15012
|
description: "Only patterns (Repository/FeatureComponent/RouterComponent) own member blocks, and every pattern owns at least one. Each claim must name a component that exists and is itself a building block \u2014 patterns compose at the subsystem (L1) level, never by owning one another \u2014 and a block has exactly one owner, the first pattern to claim it.",
|
|
14872
15013
|
codes: [
|
|
14873
15014
|
{ code: "EMPTY_PATTERN", defaultSeverity: "error", summary: "Pattern with no owned member blocks" },
|
|
@@ -14924,6 +15065,7 @@ var init_pattern_containment = __esm({
|
|
|
14924
15065
|
REPOSITORY_MEMBERS = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Query", "Adapter"]);
|
|
14925
15066
|
patternContainmentRule = {
|
|
14926
15067
|
name: "pattern-containment",
|
|
15068
|
+
judges: "design",
|
|
14927
15069
|
description: "Holds each pattern to the containment its definition prescribes. A Repository may own only Store, Registry, Index, Query and (optionally) Adapter, judged member by member. A FeatureComponent owns exactly one Orchestrator (the logic side) and one or more Views (its faces \u2014 list, detail, form \u2014 sharing the one logic component), and nothing else. A RouterComponent owns exactly one Portal as its facade and at least one other child to route to. A counting pattern that still owns a retired member is not judged until that member is migrated: its counts change with the migration, and STEREOTYPE_RETIRED is the one finding.",
|
|
14928
15070
|
codes: [
|
|
14929
15071
|
{ code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Query/Adapter member" },
|
|
@@ -14990,6 +15132,7 @@ var init_unowned_blocks = __esm({
|
|
|
14990
15132
|
"use strict";
|
|
14991
15133
|
unownedBlocksRule = {
|
|
14992
15134
|
name: "unowned-blocks",
|
|
15135
|
+
judges: "design",
|
|
14993
15136
|
description: `Judges the data blocks no pattern owns. A Store may stand alone deliberately \u2014 the sanctioned lightweight form for genuinely simple state, acknowledged with a lint.allow \u2014 while the recommended shape stays a Repository; what must never happen is folding the state into a consuming component, where no spec, diagram or conformance check can see it again. A Registry standing alone with no Store to write to is either mistyped (the "file-backed Registry" idiom, a fused persistent store that belongs typed Store) or orphaned. A Query has no standalone form at all: it computes reads over its own Repository's Store.`,
|
|
14994
15137
|
codes: [
|
|
14995
15138
|
{ code: "UNOWNED_STORE", defaultSeverity: "warning", summary: "Store not owned by any pattern \u2014 recommended shape is a Repository; a deliberate standalone Store needs a lint.allow" },
|
|
@@ -15042,6 +15185,7 @@ var init_member_visibility = __esm({
|
|
|
15042
15185
|
init_models();
|
|
15043
15186
|
memberVisibilityRule = {
|
|
15044
15187
|
name: "member-visibility",
|
|
15188
|
+
judges: "design",
|
|
15045
15189
|
description: "A component may depend on a block within its own group (it is the owning pattern, or a sibling member of the same pattern), on any pattern facade, or on a standalone block \u2014 never on a block privately owned by ANOTHER pattern, which must be reached through that pattern's facade. A retired depending component is skipped, as everywhere in this family.",
|
|
15046
15190
|
codes: [
|
|
15047
15191
|
{ code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" }
|
|
@@ -15070,6 +15214,7 @@ var init_facade_forwarding = __esm({
|
|
|
15070
15214
|
"use strict";
|
|
15071
15215
|
facadeForwardingRule = {
|
|
15072
15216
|
name: "facade-forwarding",
|
|
15217
|
+
judges: "design",
|
|
15073
15218
|
description: "A Repository facade method with an authored narrative must be pure 1:1 forwarding: exactly one call step, targeting one of the pattern's owned members. Anything else is logic living on the facade \u2014 move it into a member block, or acknowledge a deliberate exception via lint.allow.",
|
|
15074
15219
|
codes: [
|
|
15075
15220
|
{ code: "FACADE_FORWARDING", defaultSeverity: "warning", summary: "Pattern facade narrative is not a single call to an owned member" }
|
|
@@ -15118,6 +15263,7 @@ var init_profile_registration = __esm({
|
|
|
15118
15263
|
PROJECT_KINDS2 = new Set(PROJECT_KINDS);
|
|
15119
15264
|
profileRegistrationRule = {
|
|
15120
15265
|
name: "profile-registration",
|
|
15266
|
+
judges: "design",
|
|
15121
15267
|
description: "Every profile name in play \u2014 the project's own projectType and each subsystem's declared profile \u2014 must be a built-in profile, a composite project kind, or one a loaded extension pack registers. An unregistered name is flagged rather than guessed at: no doctrine is governing it.",
|
|
15122
15268
|
codes: [
|
|
15123
15269
|
{ code: "UNKNOWN_PROFILE", defaultSeverity: "warning", summary: "Profile name is neither built-in nor registered by an extension pack" }
|
|
@@ -15155,6 +15301,7 @@ var init_profile_stereotype_fencing = __esm({
|
|
|
15155
15301
|
FRONTEND_LIKE = /* @__PURE__ */ new Set(["frontend-reactive", "frontend-controller"]);
|
|
15156
15302
|
profileStereotypeFencingRule = {
|
|
15157
15303
|
name: "profile-stereotype-fencing",
|
|
15304
|
+
judges: "design",
|
|
15158
15305
|
description: "The built-in profile families fence which stereotypes may run under them: View/FeatureComponent/RouterComponent only in a frontend-like profile; Actor/Supervisor forbidden in plc-cyclic, whose logic runs single-threaded inside one execution scan cycle; Actor/Supervisor in a frontend-like profile warned. A pack profile opts in by declaring its family; an unregistered profile is neutral, with no doctrine to guess.",
|
|
15159
15306
|
codes: [
|
|
15160
15307
|
{ code: "FRONTEND_STEREOTYPE_IN_BACKEND", defaultSeverity: "error", summary: "Frontend stereotype in a backend-oriented subsystem" },
|
|
@@ -15214,6 +15361,7 @@ var init_pack_profile_stereotypes = __esm({
|
|
|
15214
15361
|
"use strict";
|
|
15215
15362
|
packProfileStereotypesRule = {
|
|
15216
15363
|
name: "pack-profile-stereotypes",
|
|
15364
|
+
judges: "design",
|
|
15217
15365
|
description: "A pack-registered profile carries its own stereotype doctrine on top of the built-in family fencing: the stereotypes it lists as forbidden are errors and the ones it discourages are warnings, each reported with the reason the pack states for it.",
|
|
15218
15366
|
codes: [
|
|
15219
15367
|
{ code: "PROFILE_FORBIDDEN_STEREOTYPE", defaultSeverity: "error", summary: "Stereotype forbidden by the governing pack-defined profile" },
|
|
@@ -15260,6 +15408,7 @@ var init_pattern_references = __esm({
|
|
|
15260
15408
|
"use strict";
|
|
15261
15409
|
patternReferencesRule = {
|
|
15262
15410
|
name: "pattern-references",
|
|
15411
|
+
judges: "design",
|
|
15263
15412
|
description: "Every component pattern reference resolves to a reusable pattern declared by a loaded extension pack (UNKNOWN_PATTERN_REF); a pinned version with no matching pack pattern is warned (PATTERN_VERSION_MISMATCH). Pattern constraint enforcement itself is delegated to the declaring pack's rules.",
|
|
15264
15413
|
codes: [
|
|
15265
15414
|
{ code: "UNKNOWN_PATTERN_REF", defaultSeverity: "warning", summary: "Component references a pattern no loaded pack declares" },
|
|
@@ -15310,6 +15459,7 @@ var init_component_variants = __esm({
|
|
|
15310
15459
|
"use strict";
|
|
15311
15460
|
variantReferencesRule = {
|
|
15312
15461
|
name: "component-variants",
|
|
15462
|
+
judges: "design",
|
|
15313
15463
|
description: "A component's variant resolves to a declared registry variant (UNKNOWN_VARIANT), and the component's stereotype equals the variant's declared base (VARIANT_BASE_MISMATCH). A variant is a base-anchored specialization (a kind of the base stereotype), not a cross-cutting attribute \u2014 those stay method guarantees.",
|
|
15314
15464
|
codes: [
|
|
15315
15465
|
{ code: "UNKNOWN_VARIANT", defaultSeverity: "warning", summary: "Component references a variant no registry declares" },
|
|
@@ -15381,6 +15531,7 @@ var init_assertion_forbidden_edges = __esm({
|
|
|
15381
15531
|
init_declared_assertion();
|
|
15382
15532
|
assertionForbiddenEdgesRule = {
|
|
15383
15533
|
name: "assertion-forbidden-edges",
|
|
15534
|
+
judges: "design",
|
|
15384
15535
|
description: "Evaluates the `forbid-edge` assertions loaded packs declare: for every component matching the assertion's from-selector, every dependsOn/owns id whose target matches the to-selector is reported. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
|
|
15385
15536
|
// Static codes are unknown here — packs bring their own. The validator
|
|
15386
15537
|
// gathers every loaded assertion's fullCode into knownIssueCodes.
|
|
@@ -15452,6 +15603,7 @@ var init_assertion_required_fields = __esm({
|
|
|
15452
15603
|
init_declared_assertion();
|
|
15453
15604
|
assertionRequiredFieldsRule = {
|
|
15454
15605
|
name: "assertion-required-fields",
|
|
15606
|
+
judges: "design",
|
|
15455
15607
|
description: "Evaluates the `require-field` assertions loaded packs declare: at the assertion's level (component / interface / implementation), every spec whose owning component matches the on-selector must declare the named field \u2014 a top-level spec field or an `ext.*` path \u2014 and, when the assertion states a closed value set, hold one of those values. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
|
|
15456
15608
|
// Static codes are unknown here — packs bring their own. The validator
|
|
15457
15609
|
// gathers every loaded assertion's fullCode into knownIssueCodes.
|
|
@@ -15516,6 +15668,7 @@ var init_assertion_endpoint_shapes = __esm({
|
|
|
15516
15668
|
init_declared_assertion();
|
|
15517
15669
|
assertionEndpointShapesRule = {
|
|
15518
15670
|
name: "assertion-endpoint-shapes",
|
|
15671
|
+
judges: "design",
|
|
15519
15672
|
description: "Evaluates the `endpoint-shape` assertions loaded packs declare: every endpoint bound on a contract whose component matches the on-selector must use an allowed transport and bind an address (path / topic / command / pipe / channel) matching the assertion's anchored pattern. Findings carry the pack's namespaced code (<PACK>_<CODE>) and its stated reason; severity is the pack's declaration (project sddRuleSeverity still wins, and error downgrades to warning in draft context). The codes are the packs' own, so no fixed code list applies.",
|
|
15520
15673
|
// Static codes are unknown here — packs bring their own. The validator
|
|
15521
15674
|
// gathers every loaded assertion's fullCode into knownIssueCodes.
|
|
@@ -15547,6 +15700,7 @@ var init_pack_resolution = __esm({
|
|
|
15547
15700
|
"use strict";
|
|
15548
15701
|
packResolutionRule = {
|
|
15549
15702
|
name: "pack-resolution",
|
|
15703
|
+
judges: "design",
|
|
15550
15704
|
description: "Every pack a project declares must resolve \u2014 from a committed bundle under .wai/packs/, or from this wairon install's pack store \u2014 and must be the content it claims. An unresolvable selection is an error reported under the code that names the remedy: PACK_NOT_INSTALLED (absent entirely), PACK_VERSION_UNSATISFIED (installed, but no version satisfies the pin), or PACK_INTEGRITY_MISMATCH (resolved content does not match the pinned integrity digest, recomputed from the files rather than read from metadata, on whichever path won). Those never downgrade to warnings: a project whose declared doctrine is missing or altered is misconfigured, and a gate running without it must not look clean. PACK_STORE_DRIFT is the one warning \u2014 a committed bundle and the store hold different content for the same name@version; the bundle applies, so the store copy is silently ignored and that deserves saying.",
|
|
15551
15705
|
codes: [
|
|
15552
15706
|
{ code: "PACK_NOT_INSTALLED", defaultSeverity: "error", summary: "A declared pack is neither bundled nor installed in the pack store" },
|
|
@@ -15570,6 +15724,7 @@ var init_pack_reproducibility = __esm({
|
|
|
15570
15724
|
"use strict";
|
|
15571
15725
|
reproducibilityRule = {
|
|
15572
15726
|
name: "pack-reproducibility",
|
|
15727
|
+
judges: "design",
|
|
15573
15728
|
description: "Under rules.enforceReproducibility (default true), every by-name pack selection must be reproducible elsewhere: pinned to an exact version (ideally with an integrity digest) or bundled into the repository. A floating selection resolves against whatever the local machine happens to have installed, so a clone or CI can validate the same tree against a different rule set (UNPINNED_PACK_SELECTION). A selection that records no fetchable source cannot be obtained by CI at all (PACK_SOURCE_UNFETCHABLE).",
|
|
15574
15729
|
codes: [
|
|
15575
15730
|
{ code: "UNPINNED_PACK_SELECTION", defaultSeverity: "warning", summary: "Pack selection floats to the latest installed version instead of pinning one" },
|
|
@@ -15607,6 +15762,7 @@ var init_pack_requirements = __esm({
|
|
|
15607
15762
|
init_project();
|
|
15608
15763
|
packRequirementsRule = {
|
|
15609
15764
|
name: "pack-requirements",
|
|
15765
|
+
judges: "design",
|
|
15610
15766
|
description: "Every requirement in the bound project's own composition.requirePolicies must be readable: a pack name and a semver range that parses (pack_requirement.rangeProblem). One that does not is POLICY_REQUIREMENT_INVALID, quoting the range and the unreadable token \u2014 an error on the requiring project's own gate, because the requirement is its own configuration and the family run cannot judge a member against it. It judges only the syntax: whether members adopt the requirement is the family run's question (family_validator.adoption), and nothing here reads a parent's requirements.",
|
|
15611
15767
|
codes: [
|
|
15612
15768
|
{ code: "POLICY_REQUIREMENT_INVALID", defaultSeverity: "error", summary: "A project's composition.requirePolicies names a pack range that does not parse" }
|
|
@@ -15634,6 +15790,7 @@ var init_dependency_cycles = __esm({
|
|
|
15634
15790
|
"use strict";
|
|
15635
15791
|
cyclesRule = {
|
|
15636
15792
|
name: "dependency-cycles",
|
|
15793
|
+
judges: "design",
|
|
15637
15794
|
description: "The component dependsOn graph must be a DAG; the first cycle through a component in the run's scope is reported with its full path.",
|
|
15638
15795
|
codes: [
|
|
15639
15796
|
{ code: "CIRCULAR_DEPENDENCY", defaultSeverity: "error", summary: "Circular dependency between components" }
|
|
@@ -15684,6 +15841,7 @@ var init_dispatch_table_bindings = __esm({
|
|
|
15684
15841
|
"use strict";
|
|
15685
15842
|
dispatchTableBindingsRule = {
|
|
15686
15843
|
name: "dispatch-table-bindings",
|
|
15844
|
+
judges: "design",
|
|
15687
15845
|
description: "A dispatch table belongs to a Portal, binds each capability once, and binds it to an existing component.method inside the portal's own subsystem (the portal dispatches inward) that the portal declares under dependsOn or owns \u2014 the table is a real runtime invocation path.",
|
|
15688
15846
|
codes: [
|
|
15689
15847
|
{ code: "DISPATCH_ON_NON_PORTAL", defaultSeverity: "error", summary: "Dispatch table declared on a component that is not a Portal" },
|
|
@@ -15769,6 +15927,7 @@ var init_dispatch_step_routing = __esm({
|
|
|
15769
15927
|
"use strict";
|
|
15770
15928
|
dispatchStepRoutingRule = {
|
|
15771
15929
|
name: "dispatch-step-routing",
|
|
15930
|
+
judges: "design",
|
|
15772
15931
|
description: "A dispatch narrative step must name the capability it routes and route it through a Portal whose dispatch table actually serves that capability; a guarantee the step asserts must be declared by the contract method the capability resolves to.",
|
|
15773
15932
|
codes: [
|
|
15774
15933
|
{ code: "MALFORMED_DISPATCH_STEP", defaultSeverity: "error", summary: "Dispatch step missing its capability (targetComponent presence is narrative-target-references' finding)" },
|
|
@@ -15846,6 +16005,7 @@ var init_lifecycle_entrypoints = __esm({
|
|
|
15846
16005
|
init_models();
|
|
15847
16006
|
lifecycleRule = {
|
|
15848
16007
|
name: "lifecycle-entrypoints",
|
|
16008
|
+
judges: "design",
|
|
15849
16009
|
description: "Declared subsystem lifecycle entrypoints (init/shutdown flows) must name an existing component and a method on one of its interfaces \u2014 they are reachability roots, so a dangling entrypoint would silently detach every flow rooted in it.",
|
|
15850
16010
|
codes: [
|
|
15851
16011
|
{ code: "INVALID_LIFECYCLE_ENTRYPOINT", defaultSeverity: "error", summary: "Lifecycle entrypoint names a missing component or method" },
|
|
@@ -15892,8 +16052,8 @@ var init_lifecycle_entrypoints = __esm({
|
|
|
15892
16052
|
});
|
|
15893
16053
|
|
|
15894
16054
|
// src/core/rules/wiring/portal-mounts.ts
|
|
15895
|
-
function liesUnder(
|
|
15896
|
-
return
|
|
16055
|
+
function liesUnder(path82, prefix) {
|
|
16056
|
+
return path82 === prefix || path82.startsWith(prefix + "/");
|
|
15897
16057
|
}
|
|
15898
16058
|
var portalMountsRule;
|
|
15899
16059
|
var init_portal_mounts = __esm({
|
|
@@ -15901,6 +16061,7 @@ var init_portal_mounts = __esm({
|
|
|
15901
16061
|
"use strict";
|
|
15902
16062
|
portalMountsRule = {
|
|
15903
16063
|
name: "portal-mounts",
|
|
16064
|
+
judges: "design",
|
|
15904
16065
|
description: "Which listener serves which portal: a portal's routes are its methods' endpoint bindings, but nothing said which listener hands it its requests, so a portal no listener serves was unreachable without anyone noticing, and the call that mounts one crossed a boundary no contract described. A listener Portal declares its mounts \u2014 each a portal, the path prefixes routed to it, and optionally the router entry it calls \u2014 and this rule checks them against the tree: a mount must name a Portal, every HTTP endpoint of a mounted portal must lie under one of its prefixes, and every portal with HTTP endpoints must be mounted by some listener unless it is one. A path lies under a prefix when it equals it or continues it past a slash, so `/` covers only the root. Whether the named entry is really exported is the code's question, and export-conformance asks it.",
|
|
15905
16066
|
codes: [
|
|
15906
16067
|
{
|
|
@@ -16087,6 +16248,7 @@ var init_unused_detection = __esm({
|
|
|
16087
16248
|
init_narrative_graph_projector();
|
|
16088
16249
|
reachabilityRule = {
|
|
16089
16250
|
name: "unused-detection",
|
|
16251
|
+
judges: "design",
|
|
16090
16252
|
description: "Walks the narrative execution graph (call steps, register handoffs, dispatch-table routing, lifecycle flows) from every entrypoint (Portal/Observer/published component/lifecycle entrypoint/invokedBy-declared method) and flags components and methods no execution chain reaches. An interface method declaring invokedBy (a real caller outside the modeled graph) seeds the walk, so its narrative propagates reachability; a declaration the INTERNAL walk (without those seeds) already reaches is stale and is reported.",
|
|
16091
16253
|
codes: [
|
|
16092
16254
|
{ code: "UNUSED_COMPONENT", defaultSeverity: "warning", summary: "Component never reached by any narrative call chain" },
|
|
@@ -16178,6 +16340,7 @@ var init_invoked_by_description = __esm({
|
|
|
16178
16340
|
init_models();
|
|
16179
16341
|
invokedByDescriptionRule = {
|
|
16180
16342
|
name: "invoked-by-description",
|
|
16343
|
+
judges: "design",
|
|
16181
16344
|
description: 'An invokedBy declaration names a caller outside the modeled graph and silences unused-detection for its method, so the claim must stay reviewable: its "caller" prose is held to the intent floor (missing or placeholder-thin prose is reported), stating WHO invokes the method and when.',
|
|
16182
16345
|
codes: [
|
|
16183
16346
|
{ code: "INVOKED_BY_UNDESCRIBED", defaultSeverity: "warning", summary: "invokedBy declaration whose caller prose is missing or placeholder-thin" }
|
|
@@ -16212,6 +16375,7 @@ var init_unused_types = __esm({
|
|
|
16212
16375
|
init_models();
|
|
16213
16376
|
unusedTypesRule = {
|
|
16214
16377
|
name: "unused-types",
|
|
16378
|
+
judges: "design",
|
|
16215
16379
|
description: "Flags types no field of any type, no interface method signature and no other type's method signature references. References are matched through the type-reference grammar (generic arguments, collections, qualified ids), and the declaring type's own generic parameters are left out \u2014 a type parameter is not a reference to a type. A type named only by its OWN methods stays unused, the way a function that only calls itself is.",
|
|
16216
16380
|
codes: [
|
|
16217
16381
|
{ code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields, signatures or type methods" }
|
|
@@ -16278,6 +16442,7 @@ var init_durability_round_trip = __esm({
|
|
|
16278
16442
|
init_narrative_graph_projector();
|
|
16279
16443
|
durabilityRule = {
|
|
16280
16444
|
name: "durability-round-trip",
|
|
16445
|
+
judges: "design",
|
|
16281
16446
|
description: "A durable Store (persisted RAM projection) must carry effect-tagged contract methods, and its writes require a hydration read-back reachable from a lifecycle init entrypoint. read-through is exempt (every read IS the read-back), as are ram-projection (rebuilt not restored) and cache (evictable, loss-safe). The flagship semantic check is opt-out by declaration, never silently absent \u2014 the declaration itself is enforced by durability-declaration.",
|
|
16282
16447
|
codes: [
|
|
16283
16448
|
{ code: "MISSING_EFFECT_TAG", defaultSeverity: "warning", summary: "Durable Store contract method lacks an effect: read | write tag" },
|
|
@@ -16340,6 +16505,7 @@ var init_untyped_seams = __esm({
|
|
|
16340
16505
|
BARE_SEAM_TYPES = /* @__PURE__ */ new Set(["json", "any", "unknown", "object"]);
|
|
16341
16506
|
untypedSeamRule = {
|
|
16342
16507
|
name: "untyped-seams",
|
|
16508
|
+
judges: "design",
|
|
16343
16509
|
description: "Methods on a subsystem's published components (its public surface) should not take or return bare Json/any/unknown/object, judged through each method's type references so a prose signature is judged like structured params: cross-subsystem contracts are the swap seam and must be typed. Generic-dispatch portals carry per-capability types via their dispatch table instead.",
|
|
16344
16510
|
codes: [
|
|
16345
16511
|
{ code: "UNTYPED_SEAM", defaultSeverity: "warning", summary: "Bare Json/any/unknown/object parameter or return crossing a subsystem public surface" }
|
|
@@ -16403,6 +16569,7 @@ var init_prose_claims = __esm({
|
|
|
16403
16569
|
QUOTED_SPAN = /"[^"]*"|“[^”]*”|`[^`]*`|(?<![\p{L}\p{N}])['‘](?:[^'‘’]|(?<=[\p{L}\p{N}])['’](?=[\p{L}\p{N}]))*['’](?![\p{L}\p{N}])/gu;
|
|
16404
16570
|
proseClaimRule = {
|
|
16405
16571
|
name: "prose-claims",
|
|
16572
|
+
judges: "design",
|
|
16406
16573
|
description: 'Flags durability/side-effect claims that exist only in prose: a local step description or an intent paragraph claiming persistence ("persisted", "persistence", "survives restart", "registered into") on a logic component whose narrative has no call, register or dispatch edge to any data-layer component (Store/Registry/Index/Adapter/Repository). Text inside quotes or backticks names a value or quotes a message and claims nothing. Data-layer components are exempt \u2014 they ARE the persistence.',
|
|
16407
16574
|
codes: [
|
|
16408
16575
|
{ code: "UNREALIZED_CLAIM", defaultSeverity: "warning", summary: "Durability/side-effect claim in prose with no matching structural edge" }
|
|
@@ -16467,6 +16634,7 @@ var init_unique_invariant_ids = __esm({
|
|
|
16467
16634
|
init_models();
|
|
16468
16635
|
uniqueInvariantIdsRule = {
|
|
16469
16636
|
name: "unique-invariant-ids",
|
|
16637
|
+
judges: "design",
|
|
16470
16638
|
description: `An entity's declared invariant ids must be unique within the entity: a narrative step asserts an invariant by "<type-id>.<invariant-id>", so a repeated id makes one assertion stand for two properties and satisfying either silences both.`,
|
|
16471
16639
|
codes: [
|
|
16472
16640
|
{ code: "DUPLICATE_INVARIANT_ID", defaultSeverity: "error", summary: "An entity declares two invariants with the same id" }
|
|
@@ -16571,6 +16739,7 @@ var init_invariant_backing = __esm({
|
|
|
16571
16739
|
init_invariant_ref();
|
|
16572
16740
|
invariantBackingRule = {
|
|
16573
16741
|
name: "invariant-backing",
|
|
16742
|
+
judges: "design",
|
|
16574
16743
|
description: 'The invariant registry: entities may declare domain invariants (type.invariants), anchored through their componentClass. Every write-effect contract method of the owning component must carry a narrative step asserting each invariant (step.assertsInvariants: "<type-id>.<invariant-id>") \u2014 the same declared-and-backed shape as semantic guarantees. This is an HONEST lint over declarations: a green run means every write path visibly claims the invariant, never that the narrative or code actually enforces it. An invariant with no resolvable owner or no declared write path is unanchored, and nothing further is claimed about it.',
|
|
16575
16744
|
codes: [
|
|
16576
16745
|
{ code: "INVARIANT_UNANCHORED", defaultSeverity: "warning", summary: "An entity declares invariants but has no componentClass, its componentClass does not resolve, or the owning component declares no write-effect contract methods" },
|
|
@@ -16636,6 +16805,7 @@ var init_invariant_references = __esm({
|
|
|
16636
16805
|
init_invariant_ref();
|
|
16637
16806
|
invariantReferencesRule = {
|
|
16638
16807
|
name: "invariant-references",
|
|
16808
|
+
judges: "design",
|
|
16639
16809
|
description: "Every invariant a narrative step asserts (step.assertsInvariants) must resolve to a declared entity invariant: the reference splits at its last dot into a type reference and an invariant id, and some entity matching that type reference must declare that id. A reference nothing declares is an error \u2014 the step claims a property that exists nowhere.",
|
|
16640
16810
|
codes: [
|
|
16641
16811
|
{ code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
|
|
@@ -16702,6 +16872,7 @@ var init_event_topology = __esm({
|
|
|
16702
16872
|
"use strict";
|
|
16703
16873
|
eventTopologyRule = {
|
|
16704
16874
|
name: "event-topology",
|
|
16875
|
+
judges: "design",
|
|
16705
16876
|
description: "Bipartite completeness of the declared pub/sub graph: every topic a component emits (emits declarations, MessageBus publish endpoints) must have at least one subscriber (subscribesTo declarations, MessageBus subscribe endpoints), and every subscription must have at least one source. Pairing is by exact topic name, and a topic is reported once per component however many declarations bind it there. Trees that declare no event edges see nothing \u2014 the rule costs request/response systems zero noise. Warnings + lint.allow: a topic produced for (or consumed from) an EXTERNAL system is a legitimate, declarable exception.",
|
|
16706
16877
|
codes: [
|
|
16707
16878
|
{ code: "UNCONSUMED_TOPIC", defaultSeverity: "warning", summary: "Topic is emitted but nothing in the tree subscribes to it" },
|
|
@@ -16746,6 +16917,7 @@ var init_source_file_linkage = __esm({
|
|
|
16746
16917
|
quoteList = (names) => names.map((n) => `"${n}"`).join(", ");
|
|
16747
16918
|
sourceFileLinkageRule = {
|
|
16748
16919
|
name: "source-file-linkage",
|
|
16920
|
+
judges: "code",
|
|
16749
16921
|
description: "Code\u2194spec Level 1: every source file an implementation names \u2014 its own sourcePath and each method's \u2014 must resolve to a real, readable file inside the project root, and an implementation that names no file at all, or leaves a contract method without one, is linked to no code. A file that escapes the root, is missing or cannot be analyzed is reported once and blocks only the methods realized in it. A run in which TypeScript/JavaScript files were analyzed below exact grade reports that once (CONFORMANCE_DEGRADED), since dependency conformance then skips them. Implementations under chained subsystems (projectPath) validate standalone in their own project run and are skipped here.",
|
|
16750
16922
|
codes: [
|
|
16751
16923
|
{ code: "MISSING_SOURCE_PATH", defaultSeverity: "warning", summary: "An implementation names no source file at all, or a contract method is left without one \u2014 structural conformance cannot link it to code" },
|
|
@@ -16863,6 +17035,7 @@ var init_method_realization = __esm({
|
|
|
16863
17035
|
init_models();
|
|
16864
17036
|
methodRealizationRule = {
|
|
16865
17037
|
name: "method-realization",
|
|
17038
|
+
judges: "code",
|
|
16866
17039
|
description: "Code\u2194spec Level 1: every L3 contract method must be realized in its own source file (the method's sourcePath, else the implementation's) at its conformance tier \u2014 declared | anchored | off; Portals default to anchored, everything else to declared, and a per-method `symbol` maps an intent-language name onto the code name. A method realized by a DECLARATION owes a function BODY as well: at exact grade one must be reachable under the symbol, here or through the imports and republications this file forwards it by, so a signature, an ambient or interface declaration or a plain value binding stops reading as an implementation (METHOD_BODY_NOT_FOUND). Findings carry the analysis grade (exact AST | pattern table | generic scan) so weaker analysis is visible. Methods whose file escapes the root, is missing or could not be analyzed are left to source-file-linkage, and implementations under chained subsystems (projectPath) validate standalone in their own project run.",
|
|
16867
17040
|
codes: [
|
|
16868
17041
|
{ code: "UNREALIZED_METHOD", defaultSeverity: "warning", summary: "An L3 contract method has no anchor in its own source file (the method's sourcePath, else the implementation's) at the required conformance tier" },
|
|
@@ -16930,6 +17103,7 @@ var init_finding_realization = __esm({
|
|
|
16930
17103
|
init_models();
|
|
16931
17104
|
findingRealizationRule = {
|
|
16932
17105
|
name: "finding-realization",
|
|
17106
|
+
judges: "code",
|
|
16933
17107
|
description: "Code\u2194spec Level 1: every finding code an L3 contract method declares must be anchored in the method's own source file (the method's sourcePath, else the implementation's) \u2014 as a string literal or, at exact grade, a property-access name, so a code reported through a constants object counts. Below exact grade any identifier or word counts, and the grade rides on the finding. Respects the conformance dial (off skips), leaves unresolvable files to source-file-linkage, and skips implementations under chained subsystems (projectPath), which validate standalone in their own project run.",
|
|
16934
17108
|
codes: [
|
|
16935
17109
|
{ code: "UNREALIZED_FINDING", defaultSeverity: "warning", summary: "A finding code a contract method declares appears in the method's source file neither as a string literal nor as a property-access name (below exact grade, any identifier counts)" }
|
|
@@ -17062,6 +17236,7 @@ var init_call_conformance = __esm({
|
|
|
17062
17236
|
claimNoun = (implMethod) => implMethod.narrative.length ? "narrative call step(s)" : "declared call(s)";
|
|
17063
17237
|
callConformanceRule = {
|
|
17064
17238
|
name: "call-conformance",
|
|
17239
|
+
judges: "code",
|
|
17065
17240
|
description: "Code\u2194spec Level 3: the CLAIMED call \u2194 realized call relation, judged both ways against the method's own source file (its sourcePath, else the implementation's), at exact analysis grade only. A method claims a call two ways and they face one check: a narrative `call` step, and an entry of the declared `calls` a narrative-less method reaches its collaborators through. They assert the same thing, and the step number is no part of the claim \u2014 this check never reads order, so it is only how a finding points at one. Forward: every claim must be realized by a call whose callee RESOLVES TO one of the target method's own source files \u2014 the target's contract name or a per-method `symbol` override, closed transitively over the named helpers the realized function calls, and resolved against every file the call CAN have reached: a `this.<field>.<method>()` receiver followed through the field's DECLARED TYPE, a `new Class(...).<method>()` receiver through the module its CLASS NAME came from, and a plain `<name>.<method>()` receiver through the type the file ANNOTATES that name with \u2014 a parameter's, or an annotated variable's, which is how a module that wires its collaborators as closures writes every one of its calls. A matching call whose origin a pure model cannot resolve is reported apart as CALL_ORIGIN_UNRESOLVED, which NAMES the shape it could not follow and asks for nothing \u2014 a coverage hole in the reader, reported for the reason CONFORMANCE_DEGRADED is: a silently degraded gate is worse than a degraded one. A finding names a landing only from the PROVEN tier so that widening what a call reached can accept a claim but never accuse one, and a target that names no file of its own falls back to name membership. Converse: a call that resolves to a modelled method of ANOTHER component in the SAME file crosses a component boundary while looking local, so the method must claim it \u2014 in a step or in `calls` (UNDECLARED_COLOCATED_CALL) \u2014 judged on the proven tier alone, and a same-file private helper is no modelled method and is never reported. A method that claims nothing at all is judged in neither direction: what it leaves unsaid is UNUSED_*'s subject. Order, arguments and conditions stay unverified, dispatch steps (runtime-table routed) are no claim about a call site, a malformed declared reference is MALFORMED_DECLARED_CALL's finding and names no target here, the conformance dial (off) skips, and weaker analysis grades never guess.",
|
|
17066
17241
|
codes: [
|
|
17067
17242
|
{ code: "CALL_STEP_UNREALIZED", defaultSeverity: "warning", summary: "Narrative call step or declared call realized by no call that resolves to the target method's own source file \u2014 the call is absent, or it lands in another module", carryable: true },
|
|
@@ -17194,6 +17369,7 @@ var init_hidden_state = __esm({
|
|
|
17194
17369
|
isStatelessLogic = (component) => isLogic(component) && !holdsState(component);
|
|
17195
17370
|
hiddenStateRule = {
|
|
17196
17371
|
name: "hidden-state",
|
|
17372
|
+
judges: "code",
|
|
17197
17373
|
description: "The fields-vs-Store criterion, statically approximated: module-scope mutable bindings (let/var) in a source file mapped EXCLUSIVELY to logic that holds no state (a component whose type isLogic() and not holdsState(): an Orchestrator, or a Specialist until it is retired) are flagged as hidden held state. A component maps every file its implementations and their methods name. State a logic component keeps for itself is invisible to the spec, the canvas, and every persistence rule: promote it to a Store (durability: cache for loss-safe memo state), or lint.allow with the reason it is genuinely wiring or ephemeral. Supervisor and Actor files may hold runtime state. Exact analysis grade only; files also mapped to data or boundary components are exempt (N:1 collapse); const-bound container mutation is beyond this check and the finding says so.",
|
|
17198
17374
|
codes: [
|
|
17199
17375
|
{ code: "HIDDEN_STATE", defaultSeverity: "warning", summary: "Module-scope mutable binding in a file mapped only to stateless logic (Orchestrators) \u2014 held state hiding outside a Store, Supervisor or Actor" }
|
|
@@ -17221,11 +17397,11 @@ var init_hidden_state = __esm({
|
|
|
17221
17397
|
byPath.set(key, list2);
|
|
17222
17398
|
}
|
|
17223
17399
|
}
|
|
17224
|
-
for (const
|
|
17225
|
-
const facts = code.factsAt(
|
|
17400
|
+
for (const path82 of code.exactPaths) {
|
|
17401
|
+
const facts = code.factsAt(path82);
|
|
17226
17402
|
const bindings = facts.topLevelMutableBindings ?? [];
|
|
17227
17403
|
if (bindings.length === 0) continue;
|
|
17228
|
-
const mapped = byPath.get(
|
|
17404
|
+
const mapped = byPath.get(path82) ?? [];
|
|
17229
17405
|
if (mapped.length === 0) continue;
|
|
17230
17406
|
if (!mapped.every((m) => isStatelessLogic(m.component))) continue;
|
|
17231
17407
|
const anchor = [...mapped].sort((a, b) => a.impl.id.localeCompare(b.impl.id))[0];
|
|
@@ -17251,6 +17427,7 @@ var init_dependency_conformance = __esm({
|
|
|
17251
17427
|
init_models();
|
|
17252
17428
|
dependencyConformanceRule = {
|
|
17253
17429
|
name: "dependency-conformance",
|
|
17430
|
+
judges: "code",
|
|
17254
17431
|
description: "Code\u2194spec Level 2: runtime import edges between component-mapped source files (a component maps to every file its implementations and their methods name) must be justified by declared relations \u2014 a direct dependsOn/owns pair, a shared component, membership in a depended-on pattern, or (across subsystems) a declared edge to the target subsystem's published surface (UNDECLARED_DEPENDENCY). Conversely, a declared dependsOn/owns edge between components realized in different files should be visible as an import between any file of the source and any file of the target (UNREALIZED_DEPENDENCY \u2014 DI indirection can defeat this, hence warning). Only exact-grade analyzed files participate; type-only imports are exempt; chained subprojects validate standalone.",
|
|
17255
17432
|
codes: [
|
|
17256
17433
|
{ code: "UNDECLARED_DEPENDENCY", defaultSeverity: "warning", summary: "A runtime import between component-mapped files has no declared dependsOn/owns (or published-surface) justification" },
|
|
@@ -17298,7 +17475,7 @@ var init_dependency_conformance = __esm({
|
|
|
17298
17475
|
}
|
|
17299
17476
|
return false;
|
|
17300
17477
|
};
|
|
17301
|
-
const draftAt = (
|
|
17478
|
+
const draftAt = (path82) => realization.implementationsAt(path82).some((impl) => ctx.isImplementationDraft(impl));
|
|
17302
17479
|
for (const fromPath of mappedPaths) {
|
|
17303
17480
|
const fromComponents = realization.componentsAt(fromPath);
|
|
17304
17481
|
for (const toPath of graph2.importsOf(fromPath)) {
|
|
@@ -17363,6 +17540,7 @@ var init_portal_imports = __esm({
|
|
|
17363
17540
|
"use strict";
|
|
17364
17541
|
portalImportsRule = {
|
|
17365
17542
|
name: "portal-imports",
|
|
17543
|
+
judges: "code",
|
|
17366
17544
|
description: "A runtime import that crosses a subsystem boundary must land on a file that realizes one of the target subsystem's PUBLISHED components \u2014 its portal file. dependency-conformance asks whether the hop is declared; this asks where it lands. An import that reaches past the portal into a concrete module can use anything that module exports, published or not, so the portal stops being the boundary and names no contract ever promised leak across it \u2014 which is how a surface grows that nobody designed. In-process it is cosmetic at runtime; it is not cosmetic for the contract. Only exact-grade component-mapped files participate; type-only imports are exempt; chained subprojects validate standalone.",
|
|
17367
17545
|
codes: [
|
|
17368
17546
|
{
|
|
@@ -17389,7 +17567,7 @@ var init_portal_imports = __esm({
|
|
|
17389
17567
|
}
|
|
17390
17568
|
return files;
|
|
17391
17569
|
};
|
|
17392
|
-
const draftAt = (
|
|
17570
|
+
const draftAt = (path82) => realization.implementationsAt(path82).some((impl) => ctx.isImplementationDraft(impl));
|
|
17393
17571
|
for (const fromPath of mappedPaths) {
|
|
17394
17572
|
const fromSubsystems = new Set(realization.componentsAt(fromPath).map((c) => c.subsystem));
|
|
17395
17573
|
for (const toPath of graph2.importsOf(fromPath)) {
|
|
@@ -17423,6 +17601,7 @@ var init_integration_sim_declaration = __esm({
|
|
|
17423
17601
|
"use strict";
|
|
17424
17602
|
integrationSimDeclarationRule = {
|
|
17425
17603
|
name: "integration-sim-declaration",
|
|
17604
|
+
judges: "code",
|
|
17426
17605
|
description: "Once a subsystem has adopted integration sims (its first declared L4 simPath), every OTHER complete, non-leaf implementation of that subsystem must declare one too: its unit suite proves contract shape against mocks, not that the wired components run together. A leaf component's unit suite IS its sim, and a subsystem that has adopted nothing is never flooded.",
|
|
17427
17606
|
codes: [
|
|
17428
17607
|
{ code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" }
|
|
@@ -17466,6 +17645,7 @@ var init_integration_sim_file = __esm({
|
|
|
17466
17645
|
init_models();
|
|
17467
17646
|
integrationSimFileRule = {
|
|
17468
17647
|
name: "integration-sim-file",
|
|
17648
|
+
judges: "code",
|
|
17469
17649
|
description: "A declared L4 simPath must resolve to a readable, committed file inside the project root \u2014 the integration harness is a re-runnable file, not a claim. Execution stays CI's job.",
|
|
17470
17650
|
codes: [
|
|
17471
17651
|
{ code: "SIM_FILE_MISSING", defaultSeverity: "warning", summary: "Declared simPath resolves to no file inside the project root" }
|
|
@@ -17502,6 +17682,7 @@ var init_integration_sim_wiring = __esm({
|
|
|
17502
17682
|
init_models();
|
|
17503
17683
|
integrationSimWiringRule = {
|
|
17504
17684
|
name: "integration-sim-wiring",
|
|
17685
|
+
judges: "code",
|
|
17505
17686
|
description: "A committed integration harness's import graph \u2014 closed transitively over the analyzed module set, exact grade only \u2014 must reach at least one of the component's own source modules (its implementation's or a method's) and at least one source module of each direct dependsOn/owns component (technology-boundary dependencies exempt: their contract-faithful fakes are sanctioned). Execution is CI's job \u2014 this proves wiring, never that the sim passes.",
|
|
17506
17687
|
codes: [
|
|
17507
17688
|
{ code: "UNWIRED_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Sim file does not import the real modules it claims to wire" }
|
|
@@ -17566,6 +17747,7 @@ var init_integration_sim_coverage = __esm({
|
|
|
17566
17747
|
init_models();
|
|
17567
17748
|
integrationSimCoverageRule = {
|
|
17568
17749
|
name: "integration-sim-coverage",
|
|
17750
|
+
judges: "code",
|
|
17569
17751
|
description: "Once a committed integration harness carries sim:<component-id>. anchors, that component claims path coverage: every narrated method's happy path and every labeled throw step needs its exact sim:<component>.<method>[:<label>] string-literal anchor. Anchors prove the path is NAMED \u2014 assertion quality and execution stay CI's job.",
|
|
17570
17752
|
codes: [
|
|
17571
17753
|
{ code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
|
|
@@ -17646,6 +17828,7 @@ var init_type_realization = __esm({
|
|
|
17646
17828
|
};
|
|
17647
17829
|
typeRealizationRule = {
|
|
17648
17830
|
name: "type-realization",
|
|
17831
|
+
judges: "code",
|
|
17649
17832
|
description: "Code\u2194spec Level 1 for the data model: a type that names a sourcePath is claiming code, so every file it names \u2014 its own and each method's \u2014 must resolve to a real, readable file inside the project root, its file must PUBLISH its declaration (an exported name at exact grade, the declaration tier below it, under its `symbol` when the code-level name differs from the type's name \u2014 and a pure re-export barrel publishes nothing of its own, so a claim on one is never realized), and each of its pure methods must appear in its own file (the method's sourcePath, else the type's) at the declaration tier, under the method's `symbol`, else the method's name. A type that names no sourcePath claims nothing and is never reported. Findings carry the analysis grade (exact AST | pattern table | generic scan) so weaker analysis is visible, a file that escapes the root, is missing or could not be analyzed is reported once and blocks only what it would have realized, and types under chained subsystems (projectPath) validate standalone in their own project run.",
|
|
17650
17833
|
codes: [
|
|
17651
17834
|
{ code: "UNREALIZED_TYPE", defaultSeverity: "warning", summary: "A type names a sourcePath but its declaration is nowhere in that file \u2014 the claim points at code that does not hold it" },
|
|
@@ -17721,6 +17904,7 @@ var init_type_shape = __esm({
|
|
|
17721
17904
|
init_models();
|
|
17722
17905
|
typeShapeRule = {
|
|
17723
17906
|
name: "type-shape",
|
|
17907
|
+
judges: "code",
|
|
17724
17908
|
description: "Code-to-contract for the DATA: a type spec's `fields` are compared, name by name, against the shape its `sourcePath` actually declares. `typeRealization` asks whether a type EXISTS in code; nothing asked whether it is the shape the spec claims, so a type spec could describe two fields of a six-field record \u2014 and call the two that are optional required \u2014 straight through a lock and a CI gate, while the ERD, the agent briefs and every implementer read it as truth. That is the worst of the code-to-spec gaps, because a wrong signature eventually breaks at a call site and a type spec that lies is only ever read by humans and agents. Two origins answer at exact grade: a DECLARED shape, whose members the file lists, and a DERIVED one, an alias followed one hop to the object literal its schema is built from, where the keys are the members. A shape that extends another is judged on what it shows and never on what it omits, since its inherited members are not in this file to count.",
|
|
17725
17909
|
codes: [
|
|
17726
17910
|
{
|
|
@@ -17861,6 +18045,7 @@ var init_param_conformance = __esm({
|
|
|
17861
18045
|
init_models();
|
|
17862
18046
|
paramConformanceRule = {
|
|
17863
18047
|
name: "param-conformance",
|
|
18048
|
+
judges: "code",
|
|
17864
18049
|
description: "Code-to-contract for the SIGNATURE, the last of the three readings a spec-driven gate never made: a contract declares `params`, and nothing ever compared them to the parameters of the function that realizes the method. A contract could promise an argument the code does not take, take one the contract never mentions \u2014 including a secret \u2014 or name the same argument two different things, and the brief handed to an implementer would carry the contract's version. Parameters are matched by POSITION against the tail of the realization's list, and the declared type is what tells a rename from a dropped argument: `seed(config: HostConfig)` realized as `bootstrapInstance(cfg: HostConfig)` is one parameter under two names, which anything matching on names alone reads as a parameter the code lost. What a realization takes BEFORE the contract's own parameters is wiring, and it is declared on the implementation as `injectedParams` rather than inferred, because an inferred prefix cannot be told from a renamed first argument. A method the named file only CALLS is left to `methodRealization`, which already reports that the body is not here.",
|
|
17865
18050
|
codes: [
|
|
17866
18051
|
{
|
|
@@ -17975,8 +18160,8 @@ var init_param_conformance = __esm({
|
|
|
17975
18160
|
});
|
|
17976
18161
|
|
|
17977
18162
|
// src/core/rules/conformance/route-coverage.ts
|
|
17978
|
-
function liesUnder2(
|
|
17979
|
-
return
|
|
18163
|
+
function liesUnder2(path82, prefix) {
|
|
18164
|
+
return path82 === prefix || path82.startsWith(prefix + "/");
|
|
17980
18165
|
}
|
|
17981
18166
|
function normalizeSegment(segment) {
|
|
17982
18167
|
return /^\{.+\}$/.test(segment) || segment.startsWith(":") ? "*" : segment;
|
|
@@ -18002,6 +18187,7 @@ var init_route_coverage = __esm({
|
|
|
18002
18187
|
init_models();
|
|
18003
18188
|
routeCoverageRule = {
|
|
18004
18189
|
name: "route-coverage",
|
|
18190
|
+
judges: "code",
|
|
18005
18191
|
description: "Code-to-contract for the ROUTES: does every route a router actually answers have a contract endpoint, and does every contract endpoint have a route that answers it? A portal's endpoints are what its contract promises; the router is what the code serves; nothing compared the two, so a route with no contract \u2014 including a write \u2014 could run for months without a single rule noticing. The listener's mount names the router function (its `via`), and the routes are read out of that function's guards; the path segment the router never checks is the one the mount's prefix guarantees. One idiom is read \u2014 a method comparison with comparisons on the path's split segments \u2014 and a mounted router that yields no route in it is reported as unread, never passed: a check that cannot see a router must say so rather than stay quiet.",
|
|
18006
18192
|
codes: [
|
|
18007
18193
|
{
|
|
@@ -18111,6 +18297,7 @@ var init_unclaimed_source = __esm({
|
|
|
18111
18297
|
init_models();
|
|
18112
18298
|
unclaimedSourceRule = {
|
|
18113
18299
|
name: "unclaimed-source",
|
|
18300
|
+
judges: "code",
|
|
18114
18301
|
description: "Code\u2194spec Level 1, asked the other way round: every source file under the project's declared source roots must be named by some spec \u2014 an implementation's sourcePath, a method's, a simPath, or a type's \u2014 or be carried in the frozen `rules.conformance.unclaimed` list. A pure re-export barrel declares nothing of its own and only republishes other modules, so it has no code to claim and is exempt (exact grade only: a weaker grade cannot tell a barrel from a file it failed to parse). Declaring no source roots leaves the check silent, which is what makes it opt-in; the unclaimed list is a one-way debt register, so an entry the walk no longer finds unclaimed must be deleted and the list can only shrink.",
|
|
18115
18302
|
codes: [
|
|
18116
18303
|
{ code: "UNCLAIMED_SOURCE_FILE", defaultSeverity: "warning", summary: "A source file under a declared source root that no spec names and the frozen unclaimed list does not carry \u2014 code nobody designed" },
|
|
@@ -18165,6 +18352,7 @@ var init_export_conformance = __esm({
|
|
|
18165
18352
|
NOTHING_PROMISED = /* @__PURE__ */ new Set();
|
|
18166
18353
|
exportConformanceRule = {
|
|
18167
18354
|
name: "export-conformance",
|
|
18355
|
+
judges: "code",
|
|
18168
18356
|
description: "Code-to-contract for the SURFACE: does this file publish anything the components it realizes never promised, that another component then takes? The rest of the conformance set reads contract-to-code \u2014 it asks whether the code holds what a spec claims \u2014 so a file could export whatever it liked under a component's name and nothing looked. That is one half of the same hole as a contract promising a parameter it never passes. A method's `exportedVia` names the export a consumer imports to REACH it \u2014 the value that composes it, which `symbol` cannot name because `symbol` names the function inside \u2014 and a listener's mount names the router entry it calls to hand a portal its requests; both are declared publication, and a declared handle the file does not actually export is itself a finding, so naming one can never become a free-text suppression. Both questions are asked only of a file read at exact grade, and a taker counts only when its own import specifier resolves to the file it is accused of taking from: below that nothing separates an export from a mention, or a shared word from a shared module.",
|
|
18169
18357
|
codes: [
|
|
18170
18358
|
{
|
|
@@ -18329,6 +18517,7 @@ var init_carried_debt = __esm({
|
|
|
18329
18517
|
"use strict";
|
|
18330
18518
|
carriedDebtRule = {
|
|
18331
18519
|
name: "carried-debt",
|
|
18520
|
+
judges: "code",
|
|
18332
18521
|
description: "The conformance debt register (`rules.conformance.carried`) holds the code\u2194spec findings this tree carries as declared, classified debt \u2014 `unclaimed`'s one-way shape, for findings about code a spec does name. It is not a second lint.allow: an allow says a finding is wrong here by design, an entry here says the finding is right and unpaid, and each entry states which of the three kinds it is (drift: spec and code disagree; undecided: the fix waits on a modelling decision nobody has taken; unreadable: the analysis cannot follow the shape the code is written in) and why. Only a code a rule declares CARRYABLE may appear, and naming any other is an error, so the register can never widen into general-purpose suppression. An entry carries a finding only when it lists EVERY unit that finding reports, so an aggregating finding cannot grow behind it; an entry that matched nothing, that duplicates another, that names a spec the tree does not hold, or that lists a unit no longer reported is STALE_CARRIED_FINDING, so the register only shrinks. The audit is silent in a run that could not have fired the findings at all: one that built no code model, the specs outside a scoped run, and a code the project switched off. Nothing can check a reason for TRUTH \u2014 a stale entry is one that stopped applying, never one that still applies for a reason that has become false \u2014 so a reason group may declare its own classification provisional (`revisit`: what would settle it), and every run counts those findings beside the kind totals.",
|
|
18333
18522
|
codes: [
|
|
18334
18523
|
{ code: "UNCARRYABLE_FINDING", defaultSeverity: "error", summary: "The conformance debt register names an issue code no rule emits, or one no rule declares carryable \u2014 the register holds measured code\u2194spec debt, never a design rule somebody wants quiet" },
|
|
@@ -18446,6 +18635,7 @@ var init_method_cohesion = __esm({
|
|
|
18446
18635
|
"use strict";
|
|
18447
18636
|
methodCohesionRule = {
|
|
18448
18637
|
name: "method-cohesion",
|
|
18638
|
+
judges: "design",
|
|
18449
18639
|
description: "An Orchestrator's methods share collaborators; when they split into groups that call no component in common, the component holds more than one responsibility. Reported only where two or more groups each hold two or more methods, so a single specialized method is not a finding \u2014 and never on a pure forwarder, whose every narrated method is a single hand-off and whose responsibility therefore lives in what it forwards to.",
|
|
18450
18640
|
codes: [
|
|
18451
18641
|
{ code: "INCOHESIVE_METHODS", defaultSeverity: "warning", summary: "Orchestrator's methods form two or more groups of two or more that share no called component" }
|
|
@@ -18498,6 +18688,7 @@ var init_coupling_health = __esm({
|
|
|
18498
18688
|
DEFAULT_GOD_COMPONENT_THRESHOLD = 8;
|
|
18499
18689
|
couplingRule = {
|
|
18500
18690
|
name: "coupling-health",
|
|
18691
|
+
judges: "design",
|
|
18501
18692
|
description: "Two subsystems depending on each other is a real architectural commitment (deployment affinity, backpressure, scaling coupling). It is allowed but must be acknowledged with a trustedLinks declaration on either side, stating the reason (e.g. a latency fast lane that bypasses the bus). A trustedLink on the SOURCE subsystem additionally licenses its direct edges into the peer without the client-Adapter shim; the published-Portal target requirement stays either way. Also flags components whose dependsOn fan-out suggests a god component.",
|
|
18502
18693
|
codes: [
|
|
18503
18694
|
{ code: "MUTUAL_SUBSYSTEM_DEPENDENCY", defaultSeverity: "warning", summary: "Subsystems depend on each other without a declared trusted link" },
|
|
@@ -18633,6 +18824,7 @@ var init_signature_language_builtins = __esm({
|
|
|
18633
18824
|
};
|
|
18634
18825
|
signatureLanguageBuiltinsRule = {
|
|
18635
18826
|
name: "signature-language-builtins",
|
|
18827
|
+
judges: "design",
|
|
18636
18828
|
description: "Contracts must speak the declared target language: builtin types that unambiguously belong to another language family are flagged in method signatures (e.g. `usize` in a TypeScript system, `Promise` in a Rust one). Set targetLanguage on the system (L0) or override per subsystem (L1).",
|
|
18637
18829
|
codes: [
|
|
18638
18830
|
{ code: "LANGUAGE_FOREIGN_BUILTIN", defaultSeverity: "warning", summary: "Signature uses a builtin from a different language family" }
|
|
@@ -18714,6 +18906,7 @@ var init_narrative_language_constructs = __esm({
|
|
|
18714
18906
|
};
|
|
18715
18907
|
narrativeLanguageConstructsRule = {
|
|
18716
18908
|
name: "narrative-language-constructs",
|
|
18909
|
+
judges: "design",
|
|
18717
18910
|
description: "Narratives must describe flows the declared target language can express: steps using constructs the language lacks (e.g. exceptions in Rust/Go, do-while in Python) are flagged with the idiomatic alternative. Set targetLanguage on the system (L0) or override per subsystem (L1).",
|
|
18718
18911
|
codes: [
|
|
18719
18912
|
{ code: "LANGUAGE_FOREIGN_FLOW", defaultSeverity: "warning", summary: "Narrative flow step uses a construct the target language does not have" }
|
|
@@ -18763,6 +18956,7 @@ var init_technology_binding = __esm({
|
|
|
18763
18956
|
DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
|
|
18764
18957
|
technologyBindingRule = {
|
|
18765
18958
|
name: "technology-binding",
|
|
18959
|
+
judges: "design",
|
|
18766
18960
|
description: "Only data-layer stereotypes (Adapter/Store/Registry/Index) should bind a technology directly: an L4 that declares `technologies` on logic has no swap seam. No hardcoded vendor lists \u2014 only declared tokens are policed, so the rule never fires on a tree that doesn't opt in.",
|
|
18767
18961
|
codes: [
|
|
18768
18962
|
{ code: "TECH_ON_LOGIC_COMPONENT", defaultSeverity: "warning", summary: "Technology bound by a non-data-layer stereotype" }
|
|
@@ -18827,6 +19021,7 @@ var init_technology_boundaries = __esm({
|
|
|
18827
19021
|
init_models();
|
|
18828
19022
|
technologyRule = {
|
|
18829
19023
|
name: "technology-boundaries",
|
|
19024
|
+
judges: "design",
|
|
18830
19025
|
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.",
|
|
18831
19026
|
codes: [
|
|
18832
19027
|
{ code: "TECH_LEAKAGE", defaultSeverity: "warning", summary: "Technology referenced outside its owning boundary" },
|
|
@@ -19039,6 +19234,7 @@ var init_naming_conventions = __esm({
|
|
|
19039
19234
|
isAllUppercase = (s) => /^[A-Z0-9_]+$/.test(s);
|
|
19040
19235
|
namingRule = {
|
|
19041
19236
|
name: "naming-conventions",
|
|
19237
|
+
judges: "design",
|
|
19042
19238
|
description: "Enforces naming conventions (casing styles or regular expressions) for subsystem, component, interface, type (differentiating entities and value-objects), method, variables/parameters, fields, and constants names/IDs, plus stereotype-specific naming patterns.",
|
|
19043
19239
|
codes: [
|
|
19044
19240
|
{ code: "NAMING_CONVENTION_VIOLATION", defaultSeverity: "warning", summary: "Item name or ID does not match the configured casing pattern or regex" },
|
|
@@ -19205,6 +19401,7 @@ var init_complexity_and_metadata = __esm({
|
|
|
19205
19401
|
init_method_cohesion();
|
|
19206
19402
|
complexityRule = {
|
|
19207
19403
|
name: "complexity-and-metadata",
|
|
19404
|
+
judges: "design",
|
|
19208
19405
|
description: "Enforces metadata documentation completeness (checking if descriptions are missing or too short) and structural complexity caps (limits on methods per interface, parameters per method, dependencies per component, and components per subsystem) configured globally or in architectural profiles. A narrative's own length and shape are judged by narrative-complexity.",
|
|
19209
19406
|
codes: [
|
|
19210
19407
|
{ code: "MISSING_DESCRIPTION", defaultSeverity: "warning", summary: "Required description field is missing or empty" },
|
|
@@ -19353,6 +19550,7 @@ var init_narrative_complexity = __esm({
|
|
|
19353
19550
|
DEFAULT_MAX_STEPS = 25;
|
|
19354
19551
|
narrativeComplexityRule = {
|
|
19355
19552
|
name: "narrative-complexity",
|
|
19553
|
+
judges: "design",
|
|
19356
19554
|
description: "Judges a narrative on two independent axes: the cognitive band its shape earns, from branching and nesting, and the number of steps it lists. A flat list of registrations is long but linear; a deeply nested guard is short but severe. Each axis warns above its configured level, and reports an error only where a maximum is configured.",
|
|
19357
19555
|
codes: [
|
|
19358
19556
|
{ code: "NARRATIVE_COMPLEXITY", defaultSeverity: "warning", summary: "Narrative's cognitive band is above the configured level (default: above moderate)" },
|
|
@@ -19473,6 +19671,7 @@ var init_naming_discipline = __esm({
|
|
|
19473
19671
|
MIN_CONCEPT_LENGTH = 4;
|
|
19474
19672
|
namingDisciplineRule = {
|
|
19475
19673
|
name: "naming-discipline",
|
|
19674
|
+
judges: "design",
|
|
19476
19675
|
description: "A name says what a thing is and never repeats itself: a component's head noun names its own block, generic role words say nothing, a method does not repeat its component's head noun, and a component with one method is not merely that method's name. Adapters and Portals are exempt from the repetition check, because a forwarder's method mirrors the command or route it exposes.",
|
|
19477
19676
|
codes: [
|
|
19478
19677
|
{ code: "MISLEADING_BLOCK_WORD", defaultSeverity: "warning", summary: "Component's head noun names a building block that is not its componentType" },
|
|
@@ -21736,8 +21935,25 @@ var init_pack_impact2 = __esm({
|
|
|
21736
21935
|
});
|
|
21737
21936
|
|
|
21738
21937
|
// src/core/rules/gate-identity.ts
|
|
21938
|
+
function ruleIdentities(rules) {
|
|
21939
|
+
return byKey(
|
|
21940
|
+
rules.map((r) => ({
|
|
21941
|
+
name: r.name,
|
|
21942
|
+
codes: byKey(r.codes.map((c) => ({ code: c.code, severity: c.defaultSeverity })), (c) => c.code)
|
|
21943
|
+
})),
|
|
21944
|
+
(r) => r.name
|
|
21945
|
+
);
|
|
21946
|
+
}
|
|
21947
|
+
function codeJudgedCodes(builtinRules) {
|
|
21948
|
+
return new Set(builtinRules.filter(judgesCode).flatMap((r) => r.codes.map((c) => c.code)));
|
|
21949
|
+
}
|
|
21950
|
+
function designRulesConfig(rules, codeCodes) {
|
|
21951
|
+
if (!rules) return null;
|
|
21952
|
+
const { conformance: _codeTuning, sddRuleSeverity, ...rest } = rules;
|
|
21953
|
+
const severities = Object.fromEntries(Object.entries(sddRuleSeverity ?? {}).filter(([code]) => !codeCodes.has(code)));
|
|
21954
|
+
return { ...rest, ...Object.keys(severities).length > 0 ? { sddRuleSeverity: severities } : {} };
|
|
21955
|
+
}
|
|
21739
21956
|
function doctrineIdentity(doctrine2, builtinRules, gate) {
|
|
21740
|
-
const byKey = (items, key) => [...items].sort((a, b) => compareOrdinal(key(a), key(b)));
|
|
21741
21957
|
return {
|
|
21742
21958
|
/**
|
|
21743
21959
|
* The BUILTIN rule set, by identity rather than by wairon version.
|
|
@@ -21754,20 +21970,23 @@ function doctrineIdentity(doctrine2, builtinRules, gate) {
|
|
|
21754
21970
|
* in the wairon version would catch it at the cost of churning every lock on
|
|
21755
21971
|
* every release — the trade this projection deliberately declines.
|
|
21756
21972
|
*/
|
|
21757
|
-
|
|
21758
|
-
|
|
21759
|
-
|
|
21760
|
-
|
|
21761
|
-
})),
|
|
21762
|
-
(r) => r.name
|
|
21763
|
-
),
|
|
21973
|
+
//
|
|
21974
|
+
// Only the rules that judge DESIGN: a code analyzer that gains a code or a
|
|
21975
|
+
// stricter check must never stale an approval (analyzer-upgrade-keeps-approval).
|
|
21976
|
+
builtinRules: ruleIdentities(builtinRules.filter((r) => !judgesCode(r))),
|
|
21764
21977
|
/**
|
|
21765
|
-
* Which profile GOVERNS, and the project's own rule tuning. Both
|
|
21766
|
-
* verdicts — a projectType switch changes the doctrine family
|
|
21767
|
-
* `rules` carries severity overrides, complexity caps
|
|
21978
|
+
* Which profile GOVERNS, and the project's own design rule tuning. Both
|
|
21979
|
+
* decide verdicts — a projectType switch changes the doctrine family
|
|
21980
|
+
* outright, and `rules` carries design severity overrides, complexity caps
|
|
21981
|
+
* and designDepth. Its code-conformance tuning is the analyzer's.
|
|
21768
21982
|
*/
|
|
21769
21983
|
projectType: gate.projectType ?? null,
|
|
21770
|
-
rulesConfig: gate.rules
|
|
21984
|
+
rulesConfig: designRulesConfig(gate.rules, codeJudgedCodes(builtinRules)),
|
|
21985
|
+
/**
|
|
21986
|
+
* What the project requires of the projects it contains (stage 5): a
|
|
21987
|
+
* changed requirement stales the requiring project's lock.
|
|
21988
|
+
*/
|
|
21989
|
+
composition: gate.composition ?? null,
|
|
21771
21990
|
packs: byKey(
|
|
21772
21991
|
doctrine2.packs.map((p) => ({ name: p.name, version: p.version ?? null, scope: p.scope })),
|
|
21773
21992
|
(p) => `${p.name}@${p.version ?? ""}#${p.scope}`
|
|
@@ -21788,22 +22007,35 @@ function doctrineIdentity(doctrine2, builtinRules, gate) {
|
|
|
21788
22007
|
)
|
|
21789
22008
|
};
|
|
21790
22009
|
}
|
|
21791
|
-
function computeGateIdentity(content, doctrine2, builtinRules, inputs, gate) {
|
|
22010
|
+
function computeGateIdentity(content, doctrine2, builtinRules, inputs, gate, members) {
|
|
21792
22011
|
const payload = {
|
|
21793
22012
|
content: content.digest,
|
|
21794
22013
|
doctrine: doctrineIdentity(doctrine2, builtinRules, gate),
|
|
21795
|
-
inputs: [...inputs].sort()
|
|
22014
|
+
inputs: [...inputs].sort(compareOrdinal),
|
|
22015
|
+
members: byKey(Object.entries(members), ([alias]) => alias).map(([alias, subject]) => ({ alias, subject }))
|
|
21796
22016
|
};
|
|
21797
22017
|
const digest2 = crypto3.createHash("sha256").update(canonicalize(payload)).digest("hex");
|
|
21798
22018
|
return { algorithm: GATE_ALGORITHM, digest: digest2 };
|
|
21799
22019
|
}
|
|
21800
|
-
|
|
22020
|
+
function analyzerDigest(builtinRules, gate) {
|
|
22021
|
+
const codeCodes = codeJudgedCodes(builtinRules);
|
|
22022
|
+
const overrides = Object.entries(gate.rules?.sddRuleSeverity ?? {}).filter(([code]) => codeCodes.has(code));
|
|
22023
|
+
const payload = {
|
|
22024
|
+
rules: ruleIdentities(builtinRules.filter(judgesCode)),
|
|
22025
|
+
conformance: gate.rules?.conformance ?? null,
|
|
22026
|
+
severities: byKey(overrides, ([code]) => code).map(([code, severity]) => ({ code, severity }))
|
|
22027
|
+
};
|
|
22028
|
+
return crypto3.createHash("sha256").update(canonicalize(payload)).digest("hex");
|
|
22029
|
+
}
|
|
22030
|
+
var crypto3, GATE_ALGORITHM, byKey;
|
|
21801
22031
|
var init_gate_identity = __esm({
|
|
21802
22032
|
"src/core/rules/gate-identity.ts"() {
|
|
21803
22033
|
"use strict";
|
|
21804
22034
|
crypto3 = __toESM(require("crypto"));
|
|
21805
22035
|
init_canonical_json();
|
|
21806
|
-
|
|
22036
|
+
init_types();
|
|
22037
|
+
GATE_ALGORITHM = "sha256+content+doctrine+inputs+members";
|
|
22038
|
+
byKey = (items, key) => [...items].sort((a, b) => compareOrdinal(key(a), key(b)));
|
|
21807
22039
|
}
|
|
21808
22040
|
});
|
|
21809
22041
|
|
|
@@ -21934,6 +22166,9 @@ function memberOwning(owners, memberKeys, key) {
|
|
|
21934
22166
|
return best;
|
|
21935
22167
|
}
|
|
21936
22168
|
function validateProject(rulesOrOptions, projectType = "backend") {
|
|
22169
|
+
return runOwnersGate(rulesOrOptions, projectType).result;
|
|
22170
|
+
}
|
|
22171
|
+
function runOwnersGate(rulesOrOptions, projectType = "backend") {
|
|
21937
22172
|
let rules = rulesOrOptions;
|
|
21938
22173
|
let scopeSubsystem;
|
|
21939
22174
|
let extensions;
|
|
@@ -21984,7 +22219,7 @@ function validateProject(rulesOrOptions, projectType = "backend") {
|
|
|
21984
22219
|
if (!system) {
|
|
21985
22220
|
const pending = extensions.errors.length > 0 || extensions.selectionFailures.length > 0 ? " Extension packs were not checked yet either, and at least one problem is already known there \u2014 re-run once the L0 exists." : " Extension-pack configuration is not checked until the L0 exists.";
|
|
21986
22221
|
issues.push(issue("error", "MISSING_SYSTEM_SPEC", `L0 System specification (.system.yaml) is missing.${pending}`));
|
|
21987
|
-
return { valid: false, issues };
|
|
22222
|
+
return { result: { valid: false, issues }, codeModel };
|
|
21988
22223
|
}
|
|
21989
22224
|
if (scopeSubsystem) {
|
|
21990
22225
|
const scopeMatches = subsystems.some(
|
|
@@ -22002,7 +22237,7 @@ function validateProject(rulesOrOptions, projectType = "backend") {
|
|
|
22002
22237
|
scopeSubsystem
|
|
22003
22238
|
)
|
|
22004
22239
|
);
|
|
22005
|
-
return { valid: false, issues };
|
|
22240
|
+
return { result: { valid: false, issues }, codeModel };
|
|
22006
22241
|
}
|
|
22007
22242
|
}
|
|
22008
22243
|
for (const err of extensions.errors) {
|
|
@@ -22064,9 +22299,12 @@ function validateProject(rulesOrOptions, projectType = "backend") {
|
|
|
22064
22299
|
const count = pinnedExternals.length;
|
|
22065
22300
|
const hint = count > 0 ? `${count} external${count === 1 ? " was" : "s were"} judged against ${count === 1 ? "its pin" : "their pins"} alone; \`wairon validate --family\` composes ${count === 1 ? "it" : "them"} against the live producer${count === 1 ? "" : "s"}.` : void 0;
|
|
22066
22301
|
return {
|
|
22067
|
-
|
|
22068
|
-
|
|
22069
|
-
|
|
22302
|
+
result: {
|
|
22303
|
+
valid: issues.every((i) => i.severity !== "error"),
|
|
22304
|
+
issues,
|
|
22305
|
+
...hint ? { hint } : {}
|
|
22306
|
+
},
|
|
22307
|
+
codeModel
|
|
22070
22308
|
};
|
|
22071
22309
|
} finally {
|
|
22072
22310
|
statusBearing.forEach((s, i) => {
|
|
@@ -22099,7 +22337,33 @@ function settledStatusBearing(loaded) {
|
|
|
22099
22337
|
return out;
|
|
22100
22338
|
}
|
|
22101
22339
|
function validateAsComplete(options) {
|
|
22102
|
-
|
|
22340
|
+
const { result, codeModel } = runOwnersGate({ ...options ?? {}, treatAllAsComplete: true });
|
|
22341
|
+
const sequence = ruleSequence();
|
|
22342
|
+
const doctrineDigest = analyzerDigest(sequence, { rules: options?.rules ?? projectRules() });
|
|
22343
|
+
return { ...result, analysis: codeAnalysisOf(result, sequence, codeModel, doctrineDigest) };
|
|
22344
|
+
}
|
|
22345
|
+
function projectRules() {
|
|
22346
|
+
try {
|
|
22347
|
+
return loadProjectConfig()?.rules;
|
|
22348
|
+
} catch {
|
|
22349
|
+
return void 0;
|
|
22350
|
+
}
|
|
22351
|
+
}
|
|
22352
|
+
function weakestGrade(codeModel) {
|
|
22353
|
+
const grades = codeModel.files.filter((f) => f.status === "analyzed" && f.analysisGrade).map((f) => GRADE_ORDER.indexOf(f.analysisGrade));
|
|
22354
|
+
return grades.length ? GRADE_ORDER[Math.min(...grades)] : "none";
|
|
22355
|
+
}
|
|
22356
|
+
function codeAnalysisOf(result, sequence, codeModel, doctrineDigest) {
|
|
22357
|
+
const codes = [...new Set(sequence.filter(judgesCode).flatMap((r) => r.codes.map((c) => c.code)))].sort();
|
|
22358
|
+
const codeSet = new Set(codes);
|
|
22359
|
+
const count = (severity) => result.issues.filter((i) => codeSet.has(i.code) && i.severity === severity).length;
|
|
22360
|
+
return {
|
|
22361
|
+
analyzer: { validatorVersion: WAIRON_VERSION, doctrineDigest, grade: weakestGrade(codeModel) },
|
|
22362
|
+
codes,
|
|
22363
|
+
errors: count("error"),
|
|
22364
|
+
warnings: count("warning"),
|
|
22365
|
+
notices: count("notice")
|
|
22366
|
+
};
|
|
22103
22367
|
}
|
|
22104
22368
|
function validateFamily(options) {
|
|
22105
22369
|
return run(options);
|
|
@@ -22117,19 +22381,37 @@ function builtinProjectKinds() {
|
|
|
22117
22381
|
return [...PROJECT_KINDS];
|
|
22118
22382
|
}
|
|
22119
22383
|
function computeGateStateId() {
|
|
22120
|
-
|
|
22384
|
+
scanAllSpecs({ recursive: true });
|
|
22385
|
+
const content = computeOwnStateId();
|
|
22121
22386
|
const extensions = loadProjectExtensions();
|
|
22122
22387
|
let gate = {};
|
|
22123
22388
|
try {
|
|
22124
22389
|
const config = loadProjectConfig();
|
|
22125
|
-
if (config) gate = { projectType: config.projectType, rules: config.rules };
|
|
22390
|
+
if (config) gate = { projectType: config.projectType, rules: config.rules, composition: config.composition ?? null };
|
|
22126
22391
|
} catch {
|
|
22127
22392
|
}
|
|
22128
22393
|
const inputs = consumedContractInputs();
|
|
22394
|
+
const members = directMemberSubjects();
|
|
22129
22395
|
registerBuiltinRules();
|
|
22130
|
-
return computeGateIdentity(content, extensions, ruleSequence(), inputs, gate);
|
|
22396
|
+
return computeGateIdentity(content, extensions, ruleSequence(), inputs, gate, members);
|
|
22131
22397
|
}
|
|
22132
|
-
|
|
22398
|
+
function directMemberSubjects() {
|
|
22399
|
+
const family = graph();
|
|
22400
|
+
const subjects = {};
|
|
22401
|
+
for (const node of family.nodes) {
|
|
22402
|
+
if (node.parent !== "" || node.mountAlias === void 0) continue;
|
|
22403
|
+
const record2 = approvalRecord(node.directory);
|
|
22404
|
+
subjects[node.mountAlias] = record2 ? `${record2.stateId.algorithm}:${record2.stateId.digest}` : NEVER_SUBJECT;
|
|
22405
|
+
}
|
|
22406
|
+
for (const problem of family.problems) {
|
|
22407
|
+
if (problem.kind === "member-absent" && problem.projects[0] === "" && problem.id) subjects[problem.id] = NEVER_SUBJECT;
|
|
22408
|
+
}
|
|
22409
|
+
return subjects;
|
|
22410
|
+
}
|
|
22411
|
+
function familyApprovals(depth) {
|
|
22412
|
+
return projectApprovals(depth);
|
|
22413
|
+
}
|
|
22414
|
+
var path19, GRADE_ORDER, NEVER_SUBJECT;
|
|
22133
22415
|
var init_validation = __esm({
|
|
22134
22416
|
"src/core/validation.ts"() {
|
|
22135
22417
|
"use strict";
|
|
@@ -22143,9 +22425,12 @@ var init_validation = __esm({
|
|
|
22143
22425
|
init_pack_impact2();
|
|
22144
22426
|
init_gate_identity();
|
|
22145
22427
|
init_types();
|
|
22428
|
+
init_defaults();
|
|
22146
22429
|
init_source_analysis();
|
|
22147
22430
|
init_fs();
|
|
22148
22431
|
path19 = __toESM(require("path"));
|
|
22432
|
+
GRADE_ORDER = ["generic", "pattern", "exact"];
|
|
22433
|
+
NEVER_SUBJECT = "never";
|
|
22149
22434
|
}
|
|
22150
22435
|
});
|
|
22151
22436
|
|
|
@@ -23085,10 +23370,10 @@ function settle(owner, level, candidates, problems) {
|
|
|
23085
23370
|
detail: `"${name}" is bound to ${targets.length} different targets (${targets.join(", ")}) by ${explicit.length ? "explicit entries" : "wildcard re-exports"}; it is left out of the table`
|
|
23086
23371
|
});
|
|
23087
23372
|
}
|
|
23088
|
-
const
|
|
23089
|
-
for (const e of entries)
|
|
23373
|
+
const byKey2 = /* @__PURE__ */ new Map();
|
|
23374
|
+
for (const e of entries) byKey2.set(nameKey(e.publicName), [...byKey2.get(nameKey(e.publicName)) ?? [], e]);
|
|
23090
23375
|
const clashing = /* @__PURE__ */ new Set();
|
|
23091
|
-
for (const group of
|
|
23376
|
+
for (const group of byKey2.values()) {
|
|
23092
23377
|
const targets = [...new Set(group.map((e) => exportTargetKey(e)))];
|
|
23093
23378
|
if (group.length < 2 || targets.length < 2) continue;
|
|
23094
23379
|
group.forEach((e) => clashing.add(e));
|
|
@@ -23564,9 +23849,6 @@ function inspectChainedRoots(rootDir = getProjectRoot()) {
|
|
|
23564
23849
|
walk2(root, "", /* @__PURE__ */ new Set([chainDirKey(root)]), 0);
|
|
23565
23850
|
return inspection;
|
|
23566
23851
|
}
|
|
23567
|
-
function listChainedRoots(rootDir = getProjectRoot()) {
|
|
23568
|
-
return inspectChainedRoots(rootDir).roots;
|
|
23569
|
-
}
|
|
23570
23852
|
function mapSpecReferences(kind, spec, map) {
|
|
23571
23853
|
switch (kind) {
|
|
23572
23854
|
case "subsystem": {
|
|
@@ -23824,10 +24106,10 @@ function keyedSystem(system, key, subsystems) {
|
|
|
23824
24106
|
};
|
|
23825
24107
|
}
|
|
23826
24108
|
function resolveFamilyTables(raws) {
|
|
23827
|
-
const
|
|
24109
|
+
const byKey2 = new Map(raws.map((r) => [r.record.namespace, r]));
|
|
23828
24110
|
const done = /* @__PURE__ */ new Set();
|
|
23829
24111
|
const resolving = /* @__PURE__ */ new Set();
|
|
23830
|
-
const
|
|
24112
|
+
const resolve40 = (raw) => {
|
|
23831
24113
|
const key = raw.record.namespace;
|
|
23832
24114
|
if (done.has(key)) return raw.record.exports;
|
|
23833
24115
|
if (resolving.has(key)) return null;
|
|
@@ -23841,8 +24123,8 @@ function resolveFamilyTables(raws) {
|
|
|
23841
24123
|
const source = e.from ?? e.subsystem;
|
|
23842
24124
|
const producerKey = source !== void 0 ? raw.record.aliases.get(source) : void 0;
|
|
23843
24125
|
if (producerKey === void 0 || producerTables.has(producerKey)) continue;
|
|
23844
|
-
const producer =
|
|
23845
|
-
producerTables.set(producerKey, producer ?
|
|
24126
|
+
const producer = byKey2.get(producerKey);
|
|
24127
|
+
producerTables.set(producerKey, producer ? resolve40(producer) : null);
|
|
23846
24128
|
audienceOf.set(producerKey, "project");
|
|
23847
24129
|
}
|
|
23848
24130
|
raw.record.subsystemExports = subsystemTables;
|
|
@@ -23858,7 +24140,7 @@ function resolveFamilyTables(raws) {
|
|
|
23858
24140
|
resolving.delete(key);
|
|
23859
24141
|
}
|
|
23860
24142
|
};
|
|
23861
|
-
raws.forEach((raw) =>
|
|
24143
|
+
raws.forEach((raw) => resolve40(raw));
|
|
23862
24144
|
}
|
|
23863
24145
|
function bindAuthored(raw, raws, readables, authored) {
|
|
23864
24146
|
const key = raw.record.namespace;
|
|
@@ -24467,13 +24749,13 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
|
|
|
24467
24749
|
const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
24468
24750
|
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
24469
24751
|
const at = (prefix, key) => prefix ? `${prefix}.${key}` : key;
|
|
24470
|
-
const say = (
|
|
24471
|
-
out.push(`${
|
|
24752
|
+
const say = (path82, why) => {
|
|
24753
|
+
out.push(`${path82} \u2014 ${why}`);
|
|
24472
24754
|
};
|
|
24473
|
-
const walkValue = (d, m, s,
|
|
24755
|
+
const walkValue = (d, m, s, path82, field) => {
|
|
24474
24756
|
if (Array.isArray(d)) {
|
|
24475
24757
|
if (!Array.isArray(m)) {
|
|
24476
|
-
say(
|
|
24758
|
+
say(path82, "the level does not have this field, so the write dropped it");
|
|
24477
24759
|
return;
|
|
24478
24760
|
}
|
|
24479
24761
|
if (field === "narrative" && d.some(carriesEditMarker)) return;
|
|
@@ -24481,32 +24763,32 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
|
|
|
24481
24763
|
if (carriesEditMarker(element)) continue;
|
|
24482
24764
|
const key = reportIdentityOf(field, element);
|
|
24483
24765
|
if (key === null) {
|
|
24484
|
-
if (!same(m, d)) say(
|
|
24485
|
-
else if (same(s, d)) say(
|
|
24766
|
+
if (!same(m, d)) say(path82, "the level does not keep this value, so the write dropped it");
|
|
24767
|
+
else if (same(s, d)) say(path82, "the stored spec already held this value");
|
|
24486
24768
|
return;
|
|
24487
24769
|
}
|
|
24488
24770
|
const mMatch = m.find((i) => reportIdentityOf(field, i) === key);
|
|
24489
24771
|
const sMatch = Array.isArray(s) ? s.find((i) => reportIdentityOf(field, i) === key) : void 0;
|
|
24490
24772
|
if (mMatch === void 0) {
|
|
24491
|
-
say(at(
|
|
24773
|
+
say(at(path82, key), "the write did not store this element");
|
|
24492
24774
|
continue;
|
|
24493
24775
|
}
|
|
24494
|
-
walk2(element, mMatch, sMatch, at(
|
|
24776
|
+
walk2(element, mMatch, sMatch, at(path82, key), field);
|
|
24495
24777
|
}
|
|
24496
24778
|
return;
|
|
24497
24779
|
}
|
|
24498
24780
|
if (isPlain(d)) {
|
|
24499
24781
|
if (!isPlain(m)) {
|
|
24500
|
-
say(
|
|
24782
|
+
say(path82, "the level does not have this field, so the write dropped it");
|
|
24501
24783
|
return;
|
|
24502
24784
|
}
|
|
24503
|
-
walk2(d, m, s,
|
|
24785
|
+
walk2(d, m, s, path82, field);
|
|
24504
24786
|
return;
|
|
24505
24787
|
}
|
|
24506
|
-
if (!same(m, d)) say(
|
|
24507
|
-
else if (same(s, d)) say(
|
|
24788
|
+
if (!same(m, d)) say(path82, "the write did not store it \u2014 the level's schema does not keep this value");
|
|
24789
|
+
else if (same(s, d)) say(path82, "the stored spec already held this value");
|
|
24508
24790
|
};
|
|
24509
|
-
function walk2(d, m, s,
|
|
24791
|
+
function walk2(d, m, s, path82, container) {
|
|
24510
24792
|
for (const [key, value] of Object.entries(d)) {
|
|
24511
24793
|
if (DELTA_VERBS.has(key) || JUMP_LABEL_TWINS.has(key)) continue;
|
|
24512
24794
|
if (key === "label" && LABEL_TWIN_CONTAINERS.has(container)) continue;
|
|
@@ -24516,13 +24798,13 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
|
|
|
24516
24798
|
if (typeof name !== "string") continue;
|
|
24517
24799
|
const had = isPlain(s) && s[name] !== void 0;
|
|
24518
24800
|
const still = isPlain(m) && m[name] !== void 0;
|
|
24519
|
-
if (!had) say(at(
|
|
24520
|
-
else if (still) say(at(
|
|
24801
|
+
if (!had) say(at(path82, name), "unset named a field the stored spec did not have");
|
|
24802
|
+
else if (still) say(at(path82, name), "unset did not remove it");
|
|
24521
24803
|
}
|
|
24522
24804
|
continue;
|
|
24523
24805
|
}
|
|
24524
24806
|
if (value === void 0 || value === null) continue;
|
|
24525
|
-
const here = at(
|
|
24807
|
+
const here = at(path82, key);
|
|
24526
24808
|
const mValue = isPlain(m) ? m[key] : void 0;
|
|
24527
24809
|
const sValue = isPlain(s) ? s[key] : void 0;
|
|
24528
24810
|
if (mValue === void 0) {
|
|
@@ -24822,9 +25104,7 @@ function consumedSurfaceInputsAt(rootDir) {
|
|
|
24822
25104
|
return keys;
|
|
24823
25105
|
}
|
|
24824
25106
|
function consumedContractInputs() {
|
|
24825
|
-
|
|
24826
|
-
const roots = [root, ...listChainedRoots(root).map((rel2) => path21.join(root, rel2))];
|
|
24827
|
-
return roots.flatMap(consumedSurfaceInputsAt);
|
|
25107
|
+
return consumedSurfaceInputsAt(getProjectRoot());
|
|
24828
25108
|
}
|
|
24829
25109
|
function readLockState(current2) {
|
|
24830
25110
|
const record2 = readLockRecord();
|
|
@@ -25773,10 +26053,10 @@ var init_specs2 = __esm({
|
|
|
25773
26053
|
return this.relativizeSpec("interface", spec);
|
|
25774
26054
|
}
|
|
25775
26055
|
prepareImplementationForWrite(spec, carry = true) {
|
|
25776
|
-
const
|
|
26056
|
+
const relative18 = this.relativizeSpec("implementation", spec, carry);
|
|
25777
26057
|
const member = this.getSubprojectPrefix(spec.id);
|
|
25778
26058
|
const memberRoot = member ? this.resolveSubprojectForNamespace(member) : null;
|
|
25779
|
-
return memberRoot ? childRelativeFilePaths(
|
|
26059
|
+
return memberRoot ? childRelativeFilePaths(relative18, this.rootDir, memberRoot) : relative18;
|
|
25780
26060
|
}
|
|
25781
26061
|
prepareTypeForWrite(spec) {
|
|
25782
26062
|
return this.relativizeSpec("type", spec);
|
|
@@ -27932,13 +28212,13 @@ function scaffoldRequirements(requirements, installed) {
|
|
|
27932
28212
|
}
|
|
27933
28213
|
return { adopted, unadopted, ...profiles.length === 1 ? { projectType: profiles[0] } : {} };
|
|
27934
28214
|
}
|
|
27935
|
-
function createMember(alias,
|
|
27936
|
-
if (!EXTERNAL_ALIAS_RE.test(alias) || typeof
|
|
28215
|
+
function createMember(alias, path82, description) {
|
|
28216
|
+
if (!EXTERNAL_ALIAS_RE.test(alias) || typeof path82 !== "string" || path82.trim() === "") {
|
|
27937
28217
|
throw new WaironError(
|
|
27938
28218
|
`an alias and a path are required to create a member: the alias must fit [a-z0-9-_]+ (got "${alias}") and the path must not be empty.`
|
|
27939
28219
|
);
|
|
27940
28220
|
}
|
|
27941
|
-
const relPath = toPosixPath(
|
|
28221
|
+
const relPath = toPosixPath(path82);
|
|
27942
28222
|
const memberDir = assertContainedProjectPath(getProjectRoot(), relPath);
|
|
27943
28223
|
const requirements = requiredPolicies(projectConfigRepository.load() ?? {});
|
|
27944
28224
|
const parentName = loadSystemSpec()?.name;
|
|
@@ -29280,6 +29560,16 @@ function buildVariantGuidance(comps, allComponents, variantsById) {
|
|
|
29280
29560
|
return composeVariantGuidance(comps, allComponents, variantsById);
|
|
29281
29561
|
}
|
|
29282
29562
|
function resolveAgentTopology() {
|
|
29563
|
+
return resolveLayer(true);
|
|
29564
|
+
}
|
|
29565
|
+
function memberAgentIds(alias, memberDir) {
|
|
29566
|
+
try {
|
|
29567
|
+
return runWithProjectRoot(memberDir, () => resolveLayer(false).map((r) => `${alias}::${r.id}`));
|
|
29568
|
+
} catch {
|
|
29569
|
+
return [];
|
|
29570
|
+
}
|
|
29571
|
+
}
|
|
29572
|
+
function resolveLayer(delegate) {
|
|
29283
29573
|
projectFilesCache.clear();
|
|
29284
29574
|
const system = loadSystemSpec();
|
|
29285
29575
|
if (!system) return [];
|
|
@@ -29335,6 +29625,8 @@ function resolveAgentTopology() {
|
|
|
29335
29625
|
writePaths: [mountSpecPath],
|
|
29336
29626
|
tags: ["owner", "subproject", "delegate", "sdd"],
|
|
29337
29627
|
dependencies: [],
|
|
29628
|
+
// The member's own agents BY REFERENCE, never copied into this layer.
|
|
29629
|
+
...delegate ? { delegatesTo: memberAgentIds(sub.id, path28.resolve(getProjectRoot(), sub.projectPath)) } : {},
|
|
29338
29630
|
status: "active",
|
|
29339
29631
|
targets: activeTargets,
|
|
29340
29632
|
createdAt: sub.createdAt,
|
|
@@ -29470,6 +29762,11 @@ function resolveAgentTopology() {
|
|
|
29470
29762
|
return agents;
|
|
29471
29763
|
}
|
|
29472
29764
|
function composeAgentBrief(agentId) {
|
|
29765
|
+
const hop = memberHop(agentId);
|
|
29766
|
+
if (hop) {
|
|
29767
|
+
const brief = runWithProjectRoot(hop.directory, () => composeAgentBrief(hop.rest));
|
|
29768
|
+
return throughMount(brief, hop.alias, hop.relative);
|
|
29769
|
+
}
|
|
29473
29770
|
const records = resolveAgentTopology();
|
|
29474
29771
|
const record2 = records.find((r) => r.id === agentId);
|
|
29475
29772
|
if (!record2) {
|
|
@@ -29514,6 +29811,25 @@ ${guidance.trim()}
|
|
|
29514
29811
|
budget
|
|
29515
29812
|
};
|
|
29516
29813
|
}
|
|
29814
|
+
function memberHop(agentId) {
|
|
29815
|
+
const at = agentId.indexOf("::");
|
|
29816
|
+
if (at < 0) return null;
|
|
29817
|
+
const alias = agentId.slice(0, at);
|
|
29818
|
+
const member = graph().nodes.find((n) => n.parent === "" && n.mountAlias === alias);
|
|
29819
|
+
if (!member) return null;
|
|
29820
|
+
const relative18 = path28.relative(getProjectRoot(), member.directory).split(path28.sep).join("/");
|
|
29821
|
+
return { alias, rest: agentId.slice(at + 2), directory: member.directory, relative: relative18 };
|
|
29822
|
+
}
|
|
29823
|
+
function throughMount(brief, alias, relative18) {
|
|
29824
|
+
const under = (p) => path28.posix.join(relative18, p);
|
|
29825
|
+
return {
|
|
29826
|
+
...brief,
|
|
29827
|
+
agentId: `${alias}::${brief.agentId}`,
|
|
29828
|
+
ownedPaths: brief.ownedPaths.map(under),
|
|
29829
|
+
...brief.readPaths ? { readPaths: brief.readPaths.map(under) } : {},
|
|
29830
|
+
root: brief.root ? under(brief.root) : relative18
|
|
29831
|
+
};
|
|
29832
|
+
}
|
|
29517
29833
|
function loadRegistry() {
|
|
29518
29834
|
assertProjectInitialized();
|
|
29519
29835
|
if (!pathExists(AI_PATHS.specsSystem())) return createEmptyRegistry();
|
|
@@ -32846,7 +33162,7 @@ var require_dist = __commonJS({
|
|
|
32846
33162
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
32847
33163
|
}
|
|
32848
33164
|
var fs60 = __toESM2(require("fs"));
|
|
32849
|
-
var
|
|
33165
|
+
var path82 = __toESM2(require("path"));
|
|
32850
33166
|
var import_fflate = require_node();
|
|
32851
33167
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".svn"]);
|
|
32852
33168
|
function listEntries(archive) {
|
|
@@ -32884,8 +33200,8 @@ var require_dist = __commonJS({
|
|
|
32884
33200
|
}
|
|
32885
33201
|
function writeTree(destDir, files) {
|
|
32886
33202
|
for (const file of files) {
|
|
32887
|
-
const absolute =
|
|
32888
|
-
fs60.mkdirSync(
|
|
33203
|
+
const absolute = path82.join(destDir, file.path);
|
|
33204
|
+
fs60.mkdirSync(path82.dirname(absolute), { recursive: true });
|
|
32889
33205
|
fs60.writeFileSync(absolute, file.contents);
|
|
32890
33206
|
}
|
|
32891
33207
|
}
|
|
@@ -32898,10 +33214,10 @@ var require_dist = __commonJS({
|
|
|
32898
33214
|
for (const entry of fs60.readdirSync(current2, { withFileTypes: true })) {
|
|
32899
33215
|
if (entry.isDirectory()) {
|
|
32900
33216
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
32901
|
-
walkPackDir(root,
|
|
33217
|
+
walkPackDir(root, path82.join(current2, entry.name), out);
|
|
32902
33218
|
} else if (entry.isFile()) {
|
|
32903
|
-
const absolute =
|
|
32904
|
-
const relative22 =
|
|
33219
|
+
const absolute = path82.join(current2, entry.name);
|
|
33220
|
+
const relative22 = path82.relative(root, absolute).split(path82.sep).join("/");
|
|
32905
33221
|
out.push({ path: relative22, contents: fs60.readFileSync(absolute) });
|
|
32906
33222
|
}
|
|
32907
33223
|
}
|
|
@@ -33102,8 +33418,8 @@ var require_dist = __commonJS({
|
|
|
33102
33418
|
return normalized === "." ? ".wai/" : `${normalized}/.wai/`;
|
|
33103
33419
|
}
|
|
33104
33420
|
function normalizeRootPath(relativePath) {
|
|
33105
|
-
const
|
|
33106
|
-
return
|
|
33421
|
+
const posix3 = relativePath.split(path210.sep).join("/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
33422
|
+
return posix3 === "" || posix3 === "." ? "." : posix3;
|
|
33107
33423
|
}
|
|
33108
33424
|
function slugify2(projectName) {
|
|
33109
33425
|
const slug2 = projectName.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -33209,19 +33525,19 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
|
|
|
33209
33525
|
const backupDir = path33.join(root, BACKUP_DIR, timestampSlug());
|
|
33210
33526
|
let backedUp = false;
|
|
33211
33527
|
for (const rootRel of roots) {
|
|
33212
|
-
const
|
|
33213
|
-
const stagedSpecDir = path33.join(stagingDir,
|
|
33528
|
+
const relative18 = rootRel === "." ? "" : rootRel;
|
|
33529
|
+
const stagedSpecDir = path33.join(stagingDir, relative18, ".wai");
|
|
33214
33530
|
if (!fs19.existsSync(stagedSpecDir)) continue;
|
|
33215
|
-
const liveSpecDir = liveSpecDirFor(path33.join(root,
|
|
33531
|
+
const liveSpecDir = liveSpecDirFor(path33.join(root, relative18));
|
|
33216
33532
|
if (backupExisting && liveSpecDir && fs19.existsSync(liveSpecDir)) {
|
|
33217
|
-
const backupTarget = path33.join(backupDir,
|
|
33533
|
+
const backupTarget = path33.join(backupDir, relative18, path33.basename(liveSpecDir));
|
|
33218
33534
|
fs19.mkdirSync(path33.dirname(backupTarget), { recursive: true });
|
|
33219
33535
|
fs19.renameSync(liveSpecDir, backupTarget);
|
|
33220
33536
|
backedUp = true;
|
|
33221
33537
|
} else if (liveSpecDir && fs19.existsSync(liveSpecDir)) {
|
|
33222
33538
|
fs19.rmSync(liveSpecDir, { recursive: true, force: true });
|
|
33223
33539
|
}
|
|
33224
|
-
const target = path33.join(root,
|
|
33540
|
+
const target = path33.join(root, relative18, ".wai");
|
|
33225
33541
|
fs19.mkdirSync(path33.dirname(target), { recursive: true });
|
|
33226
33542
|
if (fs19.existsSync(target)) fs19.rmSync(target, { recursive: true, force: true });
|
|
33227
33543
|
fs19.renameSync(stagedSpecDir, target);
|
|
@@ -33486,6 +33802,12 @@ function describeApprover(who) {
|
|
|
33486
33802
|
const label4 = who.name ? `${who.name} (${who.id})` : who.id;
|
|
33487
33803
|
return who.source === "hosted" ? `${label4} [authenticated]` : label4;
|
|
33488
33804
|
}
|
|
33805
|
+
function designOnly(result) {
|
|
33806
|
+
if (!result.analysis) return result;
|
|
33807
|
+
const codeCodes = new Set(result.analysis.codes ?? []);
|
|
33808
|
+
const issues = result.issues.filter((i) => !codeCodes.has(i.code));
|
|
33809
|
+
return { ...result, issues, valid: issues.every((i) => i.severity !== "error") };
|
|
33810
|
+
}
|
|
33489
33811
|
var init_lock = __esm({
|
|
33490
33812
|
"src/models/lock.ts"() {
|
|
33491
33813
|
"use strict";
|
|
@@ -33557,48 +33879,22 @@ function settledSpecPaths(root = getProjectRoot()) {
|
|
|
33557
33879
|
const diff = diffAgainstApproval(root);
|
|
33558
33880
|
return diff ? new Set(diff.unchangedPaths) : null;
|
|
33559
33881
|
}
|
|
33560
|
-
function
|
|
33561
|
-
return `${stateId.algorithm}:${stateId.digest}`;
|
|
33562
|
-
}
|
|
33563
|
-
function currentChildPins(mounts, root = getProjectRoot()) {
|
|
33564
|
-
const pins = {};
|
|
33565
|
-
for (const mount of mounts) {
|
|
33566
|
-
if (!mount.projectPath || mount.id.includes("::")) continue;
|
|
33567
|
-
const child = readLockRecordAt(path34.resolve(root, mount.projectPath));
|
|
33568
|
-
if (child) pins[mount.id] = pinOf(child.stateId);
|
|
33569
|
-
}
|
|
33570
|
-
return pins;
|
|
33571
|
-
}
|
|
33572
|
-
function movedChildren(mounts, root = getProjectRoot()) {
|
|
33573
|
-
const pinned = approvalRecord(root)?.children;
|
|
33574
|
-
if (!pinned) return [];
|
|
33575
|
-
const now = currentChildPins(mounts, root);
|
|
33576
|
-
const moved = [];
|
|
33577
|
-
for (const [id, was] of Object.entries(pinned)) {
|
|
33578
|
-
const current2 = now[id] ?? null;
|
|
33579
|
-
if (current2 !== was) moved.push({ id, pinned: was, now: current2 });
|
|
33580
|
-
}
|
|
33581
|
-
return moved;
|
|
33582
|
-
}
|
|
33583
|
-
function memberMounts(root = getProjectRoot()) {
|
|
33584
|
-
return graph().nodes.filter((n) => n.parent === "" && n.mountAlias !== void 0).map((n) => ({ id: n.mountAlias, projectPath: path34.relative(root, n.directory).split(path34.sep).join("/") }));
|
|
33585
|
-
}
|
|
33586
|
-
function approvalVerdict() {
|
|
33882
|
+
function approvalVerdict(approvals) {
|
|
33587
33883
|
const quiet = { text: "", drifted: false };
|
|
33588
33884
|
try {
|
|
33589
33885
|
const approval = approvalRecord();
|
|
33590
33886
|
if (!approval) return quiet;
|
|
33591
33887
|
const by = describeApprover(approval.lockedBy);
|
|
33592
|
-
const
|
|
33593
|
-
const
|
|
33594
|
-
|
|
33888
|
+
const notes = memberNotes(approvals ?? []);
|
|
33889
|
+
const pinDrift = notes.drift;
|
|
33890
|
+
const childNote = notes.text;
|
|
33595
33891
|
const diff = diffAgainstApproval();
|
|
33596
33892
|
if (!diff) {
|
|
33597
33893
|
return {
|
|
33598
33894
|
text: `
|
|
33599
33895
|
Approved: ${approval.lockedAt} by ${by} \u2014 this lock predates per-spec approval, so drift is only visible at whole-tree level. Re-lock to record it.${childNote}
|
|
33600
33896
|
`,
|
|
33601
|
-
drifted:
|
|
33897
|
+
drifted: pinDrift
|
|
33602
33898
|
};
|
|
33603
33899
|
}
|
|
33604
33900
|
if (diffSize(diff) === 0) {
|
|
@@ -33606,7 +33902,7 @@ Approved: ${approval.lockedAt} by ${by} \u2014 this lock predates per-spec appro
|
|
|
33606
33902
|
text: `
|
|
33607
33903
|
Approved: ${approval.lockedAt} by ${by} \u2014 no spec has changed since.${childNote}
|
|
33608
33904
|
`,
|
|
33609
|
-
drifted:
|
|
33905
|
+
drifted: pinDrift
|
|
33610
33906
|
};
|
|
33611
33907
|
}
|
|
33612
33908
|
const parts = [];
|
|
@@ -33629,7 +33925,28 @@ ${headline} \u2014 approved ${approval.lockedAt} by ${by}:
|
|
|
33629
33925
|
return quiet;
|
|
33630
33926
|
}
|
|
33631
33927
|
}
|
|
33632
|
-
|
|
33928
|
+
function memberNotes(approvals) {
|
|
33929
|
+
const own = approvals.find((a) => a.key === "");
|
|
33930
|
+
const direct = approvals.filter((a) => a.parent === "");
|
|
33931
|
+
const moved = direct.filter((a) => a.pinned === "moved");
|
|
33932
|
+
const unapproved = direct.filter((a) => a.state !== "approved");
|
|
33933
|
+
const lines = [];
|
|
33934
|
+
if (own?.upgraded) {
|
|
33935
|
+
lines.push(`This approval was taken under an earlier gate identity \u2014 ${GATE_UPGRADE_NOTE}. Re-lock once.`);
|
|
33936
|
+
}
|
|
33937
|
+
if (moved.length) {
|
|
33938
|
+
lines.push(`${moved.length} member pin(s) moved since approval: ${moved.map((a) => a.alias ?? a.key).join(", ")} \u2014 re-lock to approve them.`);
|
|
33939
|
+
}
|
|
33940
|
+
if (unapproved.length) {
|
|
33941
|
+
lines.push(`Member(s) not approved at their own root: ${unapproved.map((a) => `${a.alias ?? a.key} (${a.state}${a.upgraded ? ", re-lock once" : ""})`).join(", ")}.`);
|
|
33942
|
+
}
|
|
33943
|
+
return {
|
|
33944
|
+
text: lines.map((l) => `
|
|
33945
|
+
${l}`).join(""),
|
|
33946
|
+
drift: !!own?.upgraded || moved.length > 0
|
|
33947
|
+
};
|
|
33948
|
+
}
|
|
33949
|
+
var crypto4, path34, GATE_UPGRADE_NOTE;
|
|
33633
33950
|
var init_approval = __esm({
|
|
33634
33951
|
"src/core/approval.ts"() {
|
|
33635
33952
|
"use strict";
|
|
@@ -33639,6 +33956,7 @@ var init_approval = __esm({
|
|
|
33639
33956
|
init_specs2();
|
|
33640
33957
|
init_lockfile();
|
|
33641
33958
|
init_lock();
|
|
33959
|
+
GATE_UPGRADE_NOTE = "the gate identity gained inputs in stage 5 (members' composition subjects, `composition`; code conformance moved beside the claim)";
|
|
33642
33960
|
}
|
|
33643
33961
|
});
|
|
33644
33962
|
|
|
@@ -34002,6 +34320,11 @@ var init_context = __esm({
|
|
|
34002
34320
|
});
|
|
34003
34321
|
|
|
34004
34322
|
// src/core/status.ts
|
|
34323
|
+
function approvalTag(entry) {
|
|
34324
|
+
if (!entry) return "";
|
|
34325
|
+
const state = entry.upgraded ? `${entry.state}, re-lock once` : entry.state;
|
|
34326
|
+
return entry.pinned ? ` [${state} \xB7 pin ${entry.pinned}]` : ` [${state}]`;
|
|
34327
|
+
}
|
|
34005
34328
|
function fillDecor(decor) {
|
|
34006
34329
|
return {
|
|
34007
34330
|
structure: decor?.structure ?? asWritten,
|
|
@@ -34161,12 +34484,21 @@ function getStatusReport(options = {}, decor) {
|
|
|
34161
34484
|
const isLast = ownSubs.length + j === total - 1;
|
|
34162
34485
|
const form = member.mountForm === "mount" ? mark.draft(" [mount form]") : "";
|
|
34163
34486
|
const label4 = `[Project] ${member.mountAlias ?? member.namespace} (${member.id ?? "no id"})`;
|
|
34164
|
-
|
|
34487
|
+
const approval = approvalTag(options.approvals?.find((a) => a.key === member.namespace));
|
|
34488
|
+
output += `${mark.structure(indent + (isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "))}${mark.layer("project", label4)}${form}${approval}
|
|
34165
34489
|
`;
|
|
34166
34490
|
renderProject(member.namespace, indent + (isLast ? " " : "\u2502 "));
|
|
34167
34491
|
});
|
|
34168
34492
|
};
|
|
34169
34493
|
renderProject("", "");
|
|
34494
|
+
for (const absent of (options.approvals ?? []).filter((a) => a.key !== "" && !family.nodes.some((n) => n.namespace === a.key))) {
|
|
34495
|
+
output += `${mark.structure(" ")}${mark.missing(`[Project] ${absent.alias ?? absent.key} (no project on disk)`)}${approvalTag(absent)}
|
|
34496
|
+
`;
|
|
34497
|
+
}
|
|
34498
|
+
const own = options.approvals?.find((a) => a.key === "");
|
|
34499
|
+
const hasMembers = (options.approvals ?? []).some((a) => a.key !== "");
|
|
34500
|
+
if (own && (own.state !== "never" || hasMembers)) output += `${mark.layer("system", "Approval:")} this project is ${own.state}${own.upgraded ? " (locked under the pre-stage-5 gate identity \u2014 re-lock once)" : ""}
|
|
34501
|
+
`;
|
|
34170
34502
|
return { text: output, failed: false };
|
|
34171
34503
|
}
|
|
34172
34504
|
var path36, asWritten;
|
|
@@ -35454,7 +35786,7 @@ function renderAgentBriefMarkdown(brief) {
|
|
|
35454
35786
|
}
|
|
35455
35787
|
function registerSkillResources(server, listChanged) {
|
|
35456
35788
|
server.server.registerCapabilities({ resources: listChanged ? { listChanged: true } : {} });
|
|
35457
|
-
server.server.setRequestHandler(
|
|
35789
|
+
server.server.setRequestHandler(import_types5.ListResourcesRequestSchema, () => ({
|
|
35458
35790
|
resources: [
|
|
35459
35791
|
...listResources().map((d) => ({
|
|
35460
35792
|
uri: d.resourceUri,
|
|
@@ -35465,14 +35797,14 @@ function registerSkillResources(server, listChanged) {
|
|
|
35465
35797
|
...listAgentBriefResources()
|
|
35466
35798
|
]
|
|
35467
35799
|
}));
|
|
35468
|
-
server.server.setRequestHandler(
|
|
35800
|
+
server.server.setRequestHandler(import_types5.ReadResourceRequestSchema, (request) => {
|
|
35469
35801
|
const uri = request.params.uri;
|
|
35470
35802
|
if (uri.startsWith(AGENT_BRIEF_SCHEME)) {
|
|
35471
35803
|
try {
|
|
35472
35804
|
const brief = composeAgentBrief(uri.slice(AGENT_BRIEF_SCHEME.length));
|
|
35473
35805
|
return { contents: [{ uri, mimeType: SKILL_RESOURCE_MIME, text: renderAgentBriefMarkdown(brief) }] };
|
|
35474
35806
|
} catch (e) {
|
|
35475
|
-
throw new
|
|
35807
|
+
throw new import_types5.McpError(import_types5.ErrorCode.InvalidParams, e instanceof Error ? e.message : String(e));
|
|
35476
35808
|
}
|
|
35477
35809
|
}
|
|
35478
35810
|
try {
|
|
@@ -35480,20 +35812,20 @@ function registerSkillResources(server, listChanged) {
|
|
|
35480
35812
|
return { contents: [{ uri, mimeType: SKILL_RESOURCE_MIME, text: content }] };
|
|
35481
35813
|
} catch (e) {
|
|
35482
35814
|
const message = e instanceof Error ? e.message : String(e);
|
|
35483
|
-
throw new
|
|
35815
|
+
throw new import_types5.McpError(import_types5.ErrorCode.InvalidParams, message);
|
|
35484
35816
|
}
|
|
35485
35817
|
});
|
|
35486
35818
|
}
|
|
35487
35819
|
function registerSkillPrompts(server, listChanged) {
|
|
35488
35820
|
server.server.registerCapabilities({ prompts: listChanged ? { listChanged: true } : {} });
|
|
35489
|
-
server.server.setRequestHandler(
|
|
35821
|
+
server.server.setRequestHandler(import_types5.ListPromptsRequestSchema, () => ({
|
|
35490
35822
|
prompts: listResources().map((d) => ({
|
|
35491
35823
|
name: d.id,
|
|
35492
35824
|
title: d.name,
|
|
35493
35825
|
description: d.description
|
|
35494
35826
|
}))
|
|
35495
35827
|
}));
|
|
35496
|
-
server.server.setRequestHandler(
|
|
35828
|
+
server.server.setRequestHandler(import_types5.GetPromptRequestSchema, (request) => {
|
|
35497
35829
|
const name = request.params.name;
|
|
35498
35830
|
try {
|
|
35499
35831
|
const content = readResource(name);
|
|
@@ -35502,7 +35834,7 @@ function registerSkillPrompts(server, listChanged) {
|
|
|
35502
35834
|
messages: [{ role: "user", content: { type: "text", text: content } }]
|
|
35503
35835
|
};
|
|
35504
35836
|
} catch (e) {
|
|
35505
|
-
throw new
|
|
35837
|
+
throw new import_types5.McpError(import_types5.ErrorCode.InvalidParams, e instanceof Error ? e.message : String(e));
|
|
35506
35838
|
}
|
|
35507
35839
|
});
|
|
35508
35840
|
}
|
|
@@ -36510,12 +36842,19 @@ ${testsBlock}` : ""}`, deletion);
|
|
|
36510
36842
|
},
|
|
36511
36843
|
({ subsystem, recursive }) => {
|
|
36512
36844
|
try {
|
|
36845
|
+
let approvals;
|
|
36846
|
+
try {
|
|
36847
|
+
approvals = familyApprovals(recursive === false ? 0 : void 0);
|
|
36848
|
+
} catch {
|
|
36849
|
+
approvals = void 0;
|
|
36850
|
+
}
|
|
36513
36851
|
const report3 = getStatusReport({
|
|
36514
36852
|
subsystem,
|
|
36515
|
-
recursive: recursive ?? true
|
|
36853
|
+
recursive: recursive ?? true,
|
|
36854
|
+
approvals
|
|
36516
36855
|
});
|
|
36517
36856
|
const family = statusFamilyContext();
|
|
36518
|
-
const verdict = approvalVerdict();
|
|
36857
|
+
const verdict = approvalVerdict(approvals);
|
|
36519
36858
|
return text2(`${family}${report3.text}${verdict.text}`);
|
|
36520
36859
|
} catch (e) {
|
|
36521
36860
|
return errText(String(e));
|
|
@@ -36720,20 +37059,20 @@ async function startMcpServer() {
|
|
|
36720
37059
|
await server.connect(transport);
|
|
36721
37060
|
await scopeToClientWorkspace(server);
|
|
36722
37061
|
try {
|
|
36723
|
-
server.server.setNotificationHandler(
|
|
37062
|
+
server.server.setNotificationHandler(import_types5.RootsListChangedNotificationSchema, async () => {
|
|
36724
37063
|
await scopeToClientWorkspace(server);
|
|
36725
37064
|
});
|
|
36726
37065
|
} catch {
|
|
36727
37066
|
}
|
|
36728
37067
|
}
|
|
36729
|
-
var import_mcp, import_stdio, import_zod11,
|
|
37068
|
+
var import_mcp, import_stdio, import_zod11, import_types5, fs23, path40, import_url, crypto5, TYPE_REF_GRAMMAR, staleServerOutput, externalPinsOutput, externalStatusesOutput, SPEC_KINDS, STATUS_VALUES, testsToRevisitOutput, specWriteReceiptOutput, specDeletionOutput, specChangeOutput, specChangeReportOutput, validationIssueOutput, memberCreationOutput, doctrineChangeOutput, impactTotalsOutput, packImpactOutput, validateTreeOutput, getSpecOutput, FINGERPRINT_VERSION, runningFingerprint, RECONNECT, assessedAsChanged, serverBuildStamps, SERVER_BUILD_STAMP, SPEC_WRITE_TOOLS, listChangedEmitters, statusInput, SKILL_RESOURCE_MIME, AGENT_BRIEF_SCHEME;
|
|
36730
37069
|
var init_server = __esm({
|
|
36731
37070
|
"src/mcp/server.ts"() {
|
|
36732
37071
|
"use strict";
|
|
36733
37072
|
import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
36734
37073
|
import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
36735
37074
|
import_zod11 = require("zod");
|
|
36736
|
-
|
|
37075
|
+
import_types5 = require("@modelcontextprotocol/sdk/types.js");
|
|
36737
37076
|
fs23 = __toESM(require("fs"));
|
|
36738
37077
|
path40 = __toESM(require("path"));
|
|
36739
37078
|
import_url = require("url");
|
|
@@ -37324,7 +37663,7 @@ var init_mcp = __esm({
|
|
|
37324
37663
|
});
|
|
37325
37664
|
|
|
37326
37665
|
// src/cli/index.ts
|
|
37327
|
-
var
|
|
37666
|
+
var path81 = __toESM(require("path"));
|
|
37328
37667
|
var import_commander = require("commander");
|
|
37329
37668
|
init_defaults();
|
|
37330
37669
|
init_logger();
|
|
@@ -38140,7 +38479,7 @@ function pruneStaleAgents(expectedPaths, scanDirs) {
|
|
|
38140
38479
|
async function runGenerate(options = {}) {
|
|
38141
38480
|
await generateLayer(options);
|
|
38142
38481
|
const scoped = options.root || options.domain || options.domains;
|
|
38143
|
-
if (options.
|
|
38482
|
+
if (!options.family || options.dryRun || scoped) return;
|
|
38144
38483
|
const children = listDirectChainedSubprojects(getProjectRoot());
|
|
38145
38484
|
for (const child of children) {
|
|
38146
38485
|
logger.blank();
|
|
@@ -38255,7 +38594,9 @@ init_core();
|
|
|
38255
38594
|
init_project();
|
|
38256
38595
|
init_lock();
|
|
38257
38596
|
init_fs();
|
|
38597
|
+
init_errors();
|
|
38258
38598
|
init_validation();
|
|
38599
|
+
init_lock();
|
|
38259
38600
|
function checkApproval(strict) {
|
|
38260
38601
|
if (!loadSystemSpec()) {
|
|
38261
38602
|
return {
|
|
@@ -38266,6 +38607,7 @@ function checkApproval(strict) {
|
|
|
38266
38607
|
}
|
|
38267
38608
|
const lock = readLockState(computeGateStateId());
|
|
38268
38609
|
const record2 = lock.record;
|
|
38610
|
+
if (lock.state === "stale" && record2.stateId.algorithm !== lock.current.algorithm) return upgradedCheck(record2);
|
|
38269
38611
|
switch (lock.state) {
|
|
38270
38612
|
case "locked":
|
|
38271
38613
|
return {
|
|
@@ -38277,7 +38619,7 @@ function checkApproval(strict) {
|
|
|
38277
38619
|
return {
|
|
38278
38620
|
state: "stale",
|
|
38279
38621
|
approved: false,
|
|
38280
|
-
message: `The design changed after it was approved (the approval on record is ${record2.lockedAt} by ${describeApprover(record2.lockedBy)}). Nothing says a human has seen what is about to merge. Fix: run \`wairon lock\` on this branch and commit the updated .wai/lock.json.`
|
|
38622
|
+
message: `The design, or something it was approved under (doctrine, declared inputs, \`composition\`, a direct member's approval), changed after it was approved (the approval on record is ${record2.lockedAt} by ${describeApprover(record2.lockedBy)}). Nothing says a human has seen what is about to merge. Fix: run \`wairon lock\` on this branch and commit the updated .wai/lock.json.`
|
|
38281
38623
|
};
|
|
38282
38624
|
default:
|
|
38283
38625
|
return {
|
|
@@ -38287,7 +38629,29 @@ function checkApproval(strict) {
|
|
|
38287
38629
|
};
|
|
38288
38630
|
}
|
|
38289
38631
|
}
|
|
38290
|
-
|
|
38632
|
+
function upgradedCheck(record2) {
|
|
38633
|
+
const diff = diffAgainstApproval();
|
|
38634
|
+
const specs = !diff ? "This approval predates per-spec digests, so whether a spec file moved since cannot be said." : diffSize(diff) === 0 ? "No own spec file has changed since the approval." : `${diffSize(diff)} own spec file(s) changed since the approval.`;
|
|
38635
|
+
return {
|
|
38636
|
+
state: "stale",
|
|
38637
|
+
approved: false,
|
|
38638
|
+
message: `The approval on record (${record2.lockedAt} by ${describeApprover(record2.lockedBy)}) was taken under an earlier gate identity \u2014 the gate identity gained inputs in stage 5: members' composition subjects, \`composition\`; code conformance moved beside the claim. ${specs} Fix: re-lock once (\`wairon lock\`) and commit the updated .wai/lock.json.`
|
|
38639
|
+
};
|
|
38640
|
+
}
|
|
38641
|
+
var LockRefusedError = class extends WaironError {
|
|
38642
|
+
constructor(message) {
|
|
38643
|
+
super(message);
|
|
38644
|
+
this.name = "LockRefusedError";
|
|
38645
|
+
}
|
|
38646
|
+
};
|
|
38647
|
+
function describeMemberState(entry) {
|
|
38648
|
+
return `${entry.alias ?? entry.key} (${entry.state}${entry.upgraded ? " \u2014 approved under the pre-stage-5 identity, re-lock it once" : ""})`;
|
|
38649
|
+
}
|
|
38650
|
+
function codeLine(code) {
|
|
38651
|
+
if (!code) return "code: no code analysis recorded";
|
|
38652
|
+
return `code: ${code.errors} error(s), ${code.warnings} warning(s) recorded beside the claim (analyzer ${code.analyzer.validatorVersion}, grade ${code.analyzer.grade}) \u2014 CI enforces them, not the lock`;
|
|
38653
|
+
}
|
|
38654
|
+
function summarize3(gate, members) {
|
|
38291
38655
|
const diff = diffAgainstApproval();
|
|
38292
38656
|
if (!diff) {
|
|
38293
38657
|
logger.info("First approval of this tree \u2014 the whole spec tree becomes the approved baseline.");
|
|
@@ -38299,12 +38663,21 @@ async function runLock(options = {}, gate) {
|
|
|
38299
38663
|
for (const p of diff.added) logger.info(` + ${p}`);
|
|
38300
38664
|
for (const p of diff.removed) logger.info(` - ${p}`);
|
|
38301
38665
|
}
|
|
38302
|
-
|
|
38303
|
-
|
|
38304
|
-
|
|
38305
|
-
|
|
38306
|
-
|
|
38307
|
-
|
|
38666
|
+
logger.info(codeLine(gate.analysis));
|
|
38667
|
+
for (const m of members) {
|
|
38668
|
+
const pin2 = m.pinned === "moved" ? " \u2014 its pin moved since the last approval" : "";
|
|
38669
|
+
logger.info(` member ${m.alias ?? m.key}: ${m.state}${m.upgraded ? " (pre-stage-5 lock, re-lock once)" : ""}${pin2}`);
|
|
38670
|
+
}
|
|
38671
|
+
}
|
|
38672
|
+
async function runLock(options, gate, captured) {
|
|
38673
|
+
const members = familyApprovals(1).filter((a) => a.parent === "");
|
|
38674
|
+
summarize3(gate, members);
|
|
38675
|
+
const config = loadProjectConfig();
|
|
38676
|
+
const unapproved = members.filter((m) => m.state !== "approved");
|
|
38677
|
+
if (config?.composition?.requireApprovedMembers && unapproved.length > 0) {
|
|
38678
|
+
throw new LockRefusedError(
|
|
38679
|
+
`composition.requireApprovedMembers: direct member(s) not approved \u2014 ${unapproved.map(describeMemberState).join(", ")}. Lock each at its own root first (\`wairon lock\` there); a parent never approves below itself. Nothing was written.`
|
|
38680
|
+
);
|
|
38308
38681
|
}
|
|
38309
38682
|
logger.blank();
|
|
38310
38683
|
logger.warn("This records the current design as approved and (re)generates the agent topology.");
|
|
@@ -38329,30 +38702,48 @@ async function runLock(options = {}, gate) {
|
|
|
38329
38702
|
specPathsInScope(options.subsystem).map((p) => path44.relative(root, p).split(path44.sep).join("/"))
|
|
38330
38703
|
)
|
|
38331
38704
|
} : void 0;
|
|
38332
|
-
const config = loadProjectConfig();
|
|
38333
38705
|
const projectId = config ? effectiveProjectId(config) : null;
|
|
38706
|
+
const specs = captureApprovedSpecs(root, scope);
|
|
38707
|
+
const lockedBy = localApprover();
|
|
38708
|
+
const now = computeGateStateId();
|
|
38709
|
+
if (now.algorithm !== captured.algorithm || now.digest !== captured.digest) {
|
|
38710
|
+
throw new LockRefusedError(
|
|
38711
|
+
"The lock's inputs changed while it ran \u2014 a spec, the doctrine, an input, the composition block or a member's approval moved between the validation and the write. Nothing was written; run `wairon lock` again."
|
|
38712
|
+
);
|
|
38713
|
+
}
|
|
38714
|
+
const design = designOnly(gate);
|
|
38715
|
+
const count = (severity) => design.issues.filter((i) => i.severity === severity).length;
|
|
38334
38716
|
const record2 = {
|
|
38335
|
-
|
|
38717
|
+
// The record format: 2 from stage 5 on (members, code, no children).
|
|
38718
|
+
format: 2,
|
|
38719
|
+
stateId: captured,
|
|
38336
38720
|
lockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
38337
|
-
lockedBy
|
|
38721
|
+
lockedBy,
|
|
38338
38722
|
validatorVersion: WAIRON_VERSION,
|
|
38339
|
-
validationResult: {
|
|
38340
|
-
valid: true,
|
|
38341
|
-
errors: 0,
|
|
38342
|
-
warnings: gate ? gate.issues.filter((i) => i.severity === "warning").length : 0,
|
|
38343
|
-
notices: gate ? gate.issues.filter((i) => i.severity === "notice").length : 0
|
|
38344
|
-
},
|
|
38723
|
+
validationResult: { valid: design.valid, errors: count("error"), warnings: count("warning"), notices: count("notice") },
|
|
38345
38724
|
status: "ready",
|
|
38346
38725
|
...projectId !== null ? { projectId } : {},
|
|
38347
|
-
specs
|
|
38348
|
-
|
|
38726
|
+
specs,
|
|
38727
|
+
members: memberPins(members),
|
|
38728
|
+
...gate.analysis ? { code: withoutCodes(gate.analysis) } : {}
|
|
38349
38729
|
};
|
|
38350
38730
|
writeLockRecord(record2);
|
|
38351
38731
|
return record2;
|
|
38352
38732
|
}
|
|
38353
|
-
function
|
|
38354
|
-
const
|
|
38355
|
-
|
|
38733
|
+
function memberPins(members) {
|
|
38734
|
+
const pins = {};
|
|
38735
|
+
for (const m of members) {
|
|
38736
|
+
pins[m.alias ?? m.key] = {
|
|
38737
|
+
...m.projectId !== void 0 ? { project: m.projectId } : {},
|
|
38738
|
+
...m.subject !== void 0 ? { subject: m.subject } : {},
|
|
38739
|
+
state: m.state
|
|
38740
|
+
};
|
|
38741
|
+
}
|
|
38742
|
+
return pins;
|
|
38743
|
+
}
|
|
38744
|
+
function withoutCodes(analysis) {
|
|
38745
|
+
const { codes: _codes, ...rest } = analysis;
|
|
38746
|
+
return rest;
|
|
38356
38747
|
}
|
|
38357
38748
|
|
|
38358
38749
|
// src/commands/validate.ts
|
|
@@ -38551,6 +38942,7 @@ async function runValidate(options = {}) {
|
|
|
38551
38942
|
}
|
|
38552
38943
|
|
|
38553
38944
|
// src/cli/index.ts
|
|
38945
|
+
init_lock();
|
|
38554
38946
|
init_paths();
|
|
38555
38947
|
init_fs();
|
|
38556
38948
|
|
|
@@ -38681,14 +39073,14 @@ var http = __toESM(require("http"));
|
|
|
38681
39073
|
var https = __toESM(require("https"));
|
|
38682
39074
|
init_defaults();
|
|
38683
39075
|
function downloadFile(url, dest) {
|
|
38684
|
-
return new Promise((
|
|
39076
|
+
return new Promise((resolve40, reject) => {
|
|
38685
39077
|
const file = fs26.createWriteStream(dest);
|
|
38686
39078
|
const get4 = url.startsWith("https://") ? https.get : http.get;
|
|
38687
39079
|
get4(url, { headers: { "User-Agent": `wairon/${WAIRON_VERSION}` }, agent: false }, (res) => {
|
|
38688
39080
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
38689
39081
|
file.close();
|
|
38690
39082
|
res.destroy();
|
|
38691
|
-
downloadFile(res.headers.location, dest).then(
|
|
39083
|
+
downloadFile(res.headers.location, dest).then(resolve40).catch(reject);
|
|
38692
39084
|
return;
|
|
38693
39085
|
}
|
|
38694
39086
|
if (res.statusCode !== 200) {
|
|
@@ -38700,7 +39092,7 @@ function downloadFile(url, dest) {
|
|
|
38700
39092
|
res.pipe(file);
|
|
38701
39093
|
file.on("finish", () => {
|
|
38702
39094
|
res.destroy();
|
|
38703
|
-
file.close(() =>
|
|
39095
|
+
file.close(() => resolve40());
|
|
38704
39096
|
});
|
|
38705
39097
|
file.on("error", (err) => {
|
|
38706
39098
|
res.destroy();
|
|
@@ -38877,7 +39269,7 @@ function releaseChannelLabel(tag) {
|
|
|
38877
39269
|
return version.slice(dash + 1).replace(/\.\d+$/, "") || "stable";
|
|
38878
39270
|
}
|
|
38879
39271
|
function fetchReleases(repo) {
|
|
38880
|
-
return new Promise((
|
|
39272
|
+
return new Promise((resolve40, reject) => {
|
|
38881
39273
|
const url = `https://api.github.com/repos/${repo}/releases?per_page=100`;
|
|
38882
39274
|
const options = {
|
|
38883
39275
|
headers: {
|
|
@@ -38897,7 +39289,7 @@ function fetchReleases(repo) {
|
|
|
38897
39289
|
return;
|
|
38898
39290
|
}
|
|
38899
39291
|
try {
|
|
38900
|
-
|
|
39292
|
+
resolve40(JSON.parse(data));
|
|
38901
39293
|
} catch {
|
|
38902
39294
|
reject(new Error("Failed to parse GitHub API response"));
|
|
38903
39295
|
}
|
|
@@ -39040,16 +39432,26 @@ function refuseUnreadableTree(explanation) {
|
|
|
39040
39432
|
function withoutTrailingNewline(text3) {
|
|
39041
39433
|
return text3.endsWith("\n") ? text3.slice(0, -1) : text3;
|
|
39042
39434
|
}
|
|
39435
|
+
function depthOf2(recursive) {
|
|
39436
|
+
if (recursive === false) return 0;
|
|
39437
|
+
return typeof recursive === "number" ? recursive : void 0;
|
|
39438
|
+
}
|
|
39043
39439
|
async function runStatus(options = {}) {
|
|
39044
39440
|
assertProjectInitialized();
|
|
39045
|
-
|
|
39441
|
+
let approvals;
|
|
39442
|
+
try {
|
|
39443
|
+
approvals = familyApprovals(depthOf2(options.recursive));
|
|
39444
|
+
} catch {
|
|
39445
|
+
approvals = void 0;
|
|
39446
|
+
}
|
|
39447
|
+
const report3 = getStatusReport({ ...options, approvals }, TERMINAL_DECOR);
|
|
39046
39448
|
if (report3.failed) {
|
|
39047
39449
|
refuseUnreadableTree(report3.text);
|
|
39048
39450
|
}
|
|
39049
39451
|
logger.header("Architecture Status Dashboard");
|
|
39050
39452
|
logger.blank();
|
|
39051
39453
|
console.log(withoutTrailingNewline(report3.text));
|
|
39052
|
-
const lock = approvalVerdict();
|
|
39454
|
+
const lock = approvalVerdict(approvals);
|
|
39053
39455
|
if (lock.text.trim()) {
|
|
39054
39456
|
logger.blank();
|
|
39055
39457
|
if (lock.drifted) logger.warn(lock.text.trim());
|
|
@@ -39125,7 +39527,7 @@ async function filteredCheckbox(config) {
|
|
|
39125
39527
|
32,
|
|
39126
39528
|
Math.max(...items.map((i) => i.label.length))
|
|
39127
39529
|
);
|
|
39128
|
-
return new Promise((
|
|
39530
|
+
return new Promise((resolve40) => {
|
|
39129
39531
|
const checked = /* @__PURE__ */ new Set();
|
|
39130
39532
|
let cursor = 0;
|
|
39131
39533
|
let filterIdx = 0;
|
|
@@ -39192,7 +39594,7 @@ async function filteredCheckbox(config) {
|
|
|
39192
39594
|
);
|
|
39193
39595
|
teardown();
|
|
39194
39596
|
const result = items.filter((_, idx) => checked.has(idx)).map((i) => i.value);
|
|
39195
|
-
|
|
39597
|
+
resolve40(result);
|
|
39196
39598
|
}
|
|
39197
39599
|
function abort() {
|
|
39198
39600
|
process.stdout.write("\n");
|
|
@@ -40537,7 +40939,13 @@ function coverProject(node, family) {
|
|
|
40537
40939
|
const line2 = {
|
|
40538
40940
|
key: node.namespace,
|
|
40539
40941
|
...record2 ? { lockedBy: record2.validatorVersion } : {},
|
|
40540
|
-
|
|
40942
|
+
// A format-2 record counts the design half and the code half apart; the
|
|
40943
|
+
// run below counts both, so the locked totals add them back together.
|
|
40944
|
+
...record2 ? { lockedTotals: totals2(
|
|
40945
|
+
record2.validationResult.errors + (record2.code?.errors ?? 0),
|
|
40946
|
+
record2.validationResult.warnings + (record2.code?.warnings ?? 0),
|
|
40947
|
+
record2.validationResult.notices === void 0 ? void 0 : record2.validationResult.notices + (record2.code?.notices ?? 0)
|
|
40948
|
+
) } : {},
|
|
40541
40949
|
currentTotals: totals2(count("error"), count("warning"), count("notice")),
|
|
40542
40950
|
predatesStage4: record2 ? predatesStage4(record2.validatorVersion) : false
|
|
40543
40951
|
};
|
|
@@ -40841,12 +41249,19 @@ async function runDoctor(options = {}) {
|
|
|
40841
41249
|
line(tally, "warn", "this lock predates per-spec approval \u2014 re-lock so `wairon status` can name what drifts");
|
|
40842
41250
|
}
|
|
40843
41251
|
logger.blank();
|
|
41252
|
+
} else if (lock.state === "stale" && lock.record.stateId.algorithm !== lock.current.algorithm) {
|
|
41253
|
+
console.log(import_chalk12.default.bold("Lock"));
|
|
41254
|
+
line(
|
|
41255
|
+
tally,
|
|
41256
|
+
"warn",
|
|
41257
|
+
`stale \u2014 the lock of ${lock.record.lockedAt} was taken under an earlier gate identity: the gate identity gained inputs in stage 5 (members' composition subjects, \`composition\`; code conformance moved beside the claim). Re-lock once (\`wairon lock\`).`
|
|
41258
|
+
);
|
|
40844
41259
|
} else if (lock.state === "stale") {
|
|
40845
41260
|
console.log(import_chalk12.default.bold("Lock"));
|
|
40846
41261
|
line(
|
|
40847
41262
|
tally,
|
|
40848
41263
|
"warn",
|
|
40849
|
-
`stale \u2014 the
|
|
41264
|
+
`stale \u2014 the design or something it was approved under changed since ${lock.record.lockedAt}, so this lock no longer holds. Re-run \`wairon lock\` to freeze the current state.`
|
|
40850
41265
|
);
|
|
40851
41266
|
logger.blank();
|
|
40852
41267
|
}
|
|
@@ -42182,7 +42597,7 @@ async function impactPack(spec) {
|
|
|
42182
42597
|
|
|
42183
42598
|
// src/commands/host.ts
|
|
42184
42599
|
var fs57 = __toESM(require("fs"));
|
|
42185
|
-
var
|
|
42600
|
+
var path78 = __toESM(require("path"));
|
|
42186
42601
|
var os11 = __toESM(require("os"));
|
|
42187
42602
|
var crypto24 = __toESM(require("crypto"));
|
|
42188
42603
|
var import_child_process7 = require("child_process");
|
|
@@ -42193,9 +42608,9 @@ init_errors();
|
|
|
42193
42608
|
|
|
42194
42609
|
// src/server/admin.ts
|
|
42195
42610
|
var crypto11 = __toESM(require("crypto"));
|
|
42196
|
-
var path64 = __toESM(require("path"));
|
|
42197
42611
|
init_fs();
|
|
42198
42612
|
init_defaults();
|
|
42613
|
+
init_lock();
|
|
42199
42614
|
init_project();
|
|
42200
42615
|
|
|
42201
42616
|
// src/server/auth.ts
|
|
@@ -44463,6 +44878,7 @@ function list() {
|
|
|
44463
44878
|
init_validation();
|
|
44464
44879
|
init_errors();
|
|
44465
44880
|
init_validation();
|
|
44881
|
+
init_validation();
|
|
44466
44882
|
function validateAsComplete2() {
|
|
44467
44883
|
const config = loadProjectConfig();
|
|
44468
44884
|
if (!config) throw new ProjectNotInitializedError();
|
|
@@ -44609,57 +45025,115 @@ function hostedApprover(subject) {
|
|
|
44609
45025
|
source: "hosted"
|
|
44610
45026
|
};
|
|
44611
45027
|
}
|
|
44612
|
-
function directMembers(family, root) {
|
|
44613
|
-
return family.nodes.filter((n) => n.parent === "" && n.mountAlias !== void 0).map((n) => ({ id: n.mountAlias, projectPath: path64.relative(root, n.directory).split(path64.sep).join("/") }));
|
|
44614
|
-
}
|
|
44615
45028
|
function boundLifecycleRoot(cfg, projectId, subproject) {
|
|
44616
45029
|
const root = existingProjectRoot(cfg.dataDir, projectId);
|
|
44617
45030
|
if (!root) throw new Error(`Unknown project "${projectId}".`);
|
|
44618
45031
|
if (!subproject) return root;
|
|
44619
45032
|
return resolveSubprojectMounts(projectId, root, subproject.split(SUBPROJECT_SEPARATOR));
|
|
44620
45033
|
}
|
|
44621
|
-
|
|
45034
|
+
var LockRefusedError2 = class extends Error {
|
|
45035
|
+
constructor(message) {
|
|
45036
|
+
super(message);
|
|
45037
|
+
this.name = "LockRefusedError";
|
|
45038
|
+
}
|
|
45039
|
+
};
|
|
45040
|
+
var RequestedDesignMovedError = class extends LockRefusedError2 {
|
|
45041
|
+
constructor(requested, now) {
|
|
45042
|
+
super(`The design changed since the lock was requested (requested ${requested}, now ${now}); request the lock again. Nothing was written.`);
|
|
45043
|
+
this.requested = requested;
|
|
45044
|
+
this.now = now;
|
|
45045
|
+
}
|
|
45046
|
+
};
|
|
45047
|
+
function renderIdentity(stateId) {
|
|
45048
|
+
return `${stateId.algorithm}:${stateId.digest}`;
|
|
45049
|
+
}
|
|
45050
|
+
function gateIdentity(cfg, projectId, subproject) {
|
|
45051
|
+
const root = boundLifecycleRoot(cfg, projectId, subproject);
|
|
45052
|
+
return runWithProjectRoot(root, () => renderIdentity(computeGateStateId()));
|
|
45053
|
+
}
|
|
45054
|
+
function executeApprovedLock(cfg, projectId, approver, subproject, expected) {
|
|
44622
45055
|
const root = boundLifecycleRoot(cfg, projectId, subproject);
|
|
44623
45056
|
return runWithProjectRoot(root, () => {
|
|
44624
45057
|
sync();
|
|
44625
|
-
const
|
|
44626
|
-
|
|
44627
|
-
|
|
44628
|
-
|
|
44629
|
-
|
|
44630
|
-
const stateId = computeGateStateId();
|
|
44631
|
-
const specs = captureApprovedSpecs();
|
|
44632
|
-
const children = currentChildPins(directMembers(graph(), root));
|
|
45058
|
+
const captured = computeGateStateId();
|
|
45059
|
+
refuseUnrequestedDesign(captured, expected);
|
|
45060
|
+
const gate = validateAsComplete2();
|
|
45061
|
+
const design = refuseDesignErrors(gate);
|
|
45062
|
+
const members = familyApprovals(1).filter((a) => a.parent === "");
|
|
44633
45063
|
const config = loadProjectConfig();
|
|
44634
|
-
|
|
44635
|
-
const
|
|
44636
|
-
|
|
44637
|
-
|
|
44638
|
-
lockedBy: approver,
|
|
44639
|
-
validatorVersion: WAIRON_VERSION,
|
|
44640
|
-
validationResult: {
|
|
44641
|
-
valid: true,
|
|
44642
|
-
errors: 0,
|
|
44643
|
-
warnings: result.issues.filter((i) => i.severity === "warning").length,
|
|
44644
|
-
notices: result.issues.filter((i) => i.severity === "notice").length
|
|
44645
|
-
},
|
|
44646
|
-
status: "ready",
|
|
44647
|
-
...projectId2 !== null ? { projectId: projectId2 } : {},
|
|
44648
|
-
specs,
|
|
44649
|
-
children
|
|
44650
|
-
};
|
|
45064
|
+
refuseUnapprovedMembers(config?.composition?.requireApprovedMembers === true, members);
|
|
45065
|
+
const specs = captureApprovedSpecs();
|
|
45066
|
+
refuseMovedInputs(captured, computeGateStateId());
|
|
45067
|
+
const record2 = lockRecordOf(captured, approver, design, gate, config, specs, members);
|
|
44651
45068
|
writeLockRecord(record2);
|
|
44652
|
-
|
|
44653
|
-
if (!publish2.published) return record2;
|
|
44654
|
-
const published = {
|
|
44655
|
-
...record2,
|
|
44656
|
-
commitSha: publish2.commitSha,
|
|
44657
|
-
compareUrl: publish2.compareUrl
|
|
44658
|
-
};
|
|
44659
|
-
writeLockRecord(published);
|
|
44660
|
-
return published;
|
|
45069
|
+
return publishLock(record2);
|
|
44661
45070
|
});
|
|
44662
45071
|
}
|
|
45072
|
+
function refuseUnrequestedDesign(captured, expected) {
|
|
45073
|
+
if (expected === void 0 || renderIdentity(captured) === expected) return;
|
|
45074
|
+
throw new RequestedDesignMovedError(expected, renderIdentity(captured));
|
|
45075
|
+
}
|
|
45076
|
+
function refuseDesignErrors(gate) {
|
|
45077
|
+
const design = designOnly(gate);
|
|
45078
|
+
const errors = design.issues.filter((i) => i.severity === "error");
|
|
45079
|
+
if (errors.length) {
|
|
45080
|
+
throw new LockValidationError(errors.map((e) => ({ code: e.code, message: e.message, specId: e.specId })));
|
|
45081
|
+
}
|
|
45082
|
+
return design;
|
|
45083
|
+
}
|
|
45084
|
+
function refuseUnapprovedMembers(required, members) {
|
|
45085
|
+
const unapproved = members.filter((m) => m.state !== "approved");
|
|
45086
|
+
if (!required || unapproved.length === 0) return;
|
|
45087
|
+
const named2 = unapproved.map((m) => `${m.alias ?? m.key} (${m.state}${m.upgraded ? " \u2014 approved under the pre-stage-5 identity, re-lock it once" : ""})`);
|
|
45088
|
+
throw new LockRefusedError2(
|
|
45089
|
+
`composition.requireApprovedMembers: direct member(s) not approved \u2014 ${named2.join(", ")}. Lock each at its own root first; a parent never approves below itself. Nothing was written.`
|
|
45090
|
+
);
|
|
45091
|
+
}
|
|
45092
|
+
function refuseMovedInputs(captured, now) {
|
|
45093
|
+
if (now.algorithm === captured.algorithm && now.digest === captured.digest) return;
|
|
45094
|
+
throw new LockRefusedError2(
|
|
45095
|
+
"The lock's inputs changed while it ran \u2014 a spec, the doctrine, an input, the composition block or a member's approval moved between the validation and the write. Nothing was written; run the lock again."
|
|
45096
|
+
);
|
|
45097
|
+
}
|
|
45098
|
+
function lockRecordOf(captured, approver, design, gate, config, specs, members) {
|
|
45099
|
+
const count = (severity) => design.issues.filter((i) => i.severity === severity).length;
|
|
45100
|
+
const projectId = config ? effectiveProjectId(config) : null;
|
|
45101
|
+
return {
|
|
45102
|
+
format: 2,
|
|
45103
|
+
stateId: captured,
|
|
45104
|
+
lockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
45105
|
+
lockedBy: approver,
|
|
45106
|
+
validatorVersion: WAIRON_VERSION,
|
|
45107
|
+
validationResult: { valid: design.valid, errors: count("error"), warnings: count("warning"), notices: count("notice") },
|
|
45108
|
+
status: "ready",
|
|
45109
|
+
...projectId !== null ? { projectId } : {},
|
|
45110
|
+
specs,
|
|
45111
|
+
members: memberPins2(members),
|
|
45112
|
+
...gate.analysis ? { code: withoutCodes2(gate.analysis) } : {}
|
|
45113
|
+
};
|
|
45114
|
+
}
|
|
45115
|
+
function memberPins2(members) {
|
|
45116
|
+
const pins = {};
|
|
45117
|
+
for (const m of members) {
|
|
45118
|
+
pins[m.alias ?? m.key] = {
|
|
45119
|
+
...m.projectId !== void 0 ? { project: m.projectId } : {},
|
|
45120
|
+
...m.subject !== void 0 ? { subject: m.subject } : {},
|
|
45121
|
+
state: m.state
|
|
45122
|
+
};
|
|
45123
|
+
}
|
|
45124
|
+
return pins;
|
|
45125
|
+
}
|
|
45126
|
+
function withoutCodes2(analysis) {
|
|
45127
|
+
const { codes: _codes, ...rest } = analysis;
|
|
45128
|
+
return rest;
|
|
45129
|
+
}
|
|
45130
|
+
function publishLock(record2) {
|
|
45131
|
+
const publish2 = publish(`wairon lock: ${record2.projectId ?? "project"} @ ${renderIdentity(record2.stateId)}`);
|
|
45132
|
+
if (!publish2.published) return record2;
|
|
45133
|
+
const published = { ...record2, commitSha: publish2.commitSha, compareUrl: publish2.compareUrl };
|
|
45134
|
+
writeLockRecord(published);
|
|
45135
|
+
return published;
|
|
45136
|
+
}
|
|
44663
45137
|
function projectGitCredentialKey(project2) {
|
|
44664
45138
|
return `git-project:${project2}`;
|
|
44665
45139
|
}
|
|
@@ -44834,7 +45308,7 @@ function diagramViewLink(cfg, credential, project2) {
|
|
|
44834
45308
|
|
|
44835
45309
|
// src/server/packs.ts
|
|
44836
45310
|
var fs43 = __toESM(require("fs"));
|
|
44837
|
-
var
|
|
45311
|
+
var path64 = __toESM(require("path"));
|
|
44838
45312
|
init_fs();
|
|
44839
45313
|
init_yaml();
|
|
44840
45314
|
|
|
@@ -44890,7 +45364,7 @@ function assertNotCodeArchive(info) {
|
|
|
44890
45364
|
function extractDeclarativeArchive(archive, destDir) {
|
|
44891
45365
|
const result = extractArchive(archive, destDir, HOSTED_STRICT_LIMITS);
|
|
44892
45366
|
try {
|
|
44893
|
-
assertDeclarative(fs43.readFileSync(
|
|
45367
|
+
assertDeclarative(fs43.readFileSync(path64.join(result.directory, result.entryPath), "utf8"));
|
|
44894
45368
|
} catch (e) {
|
|
44895
45369
|
fs43.rmSync(destDir, { recursive: true, force: true });
|
|
44896
45370
|
throw e;
|
|
@@ -44899,10 +45373,10 @@ function extractDeclarativeArchive(archive, destDir) {
|
|
|
44899
45373
|
function probe(loadRef, baseRoot, scope, displayRef) {
|
|
44900
45374
|
const loaded = loadExtensionPacks([{ ref: loadRef, scope }], baseRoot);
|
|
44901
45375
|
if (loaded.errors.length) {
|
|
44902
|
-
return { name:
|
|
45376
|
+
return { name: path64.basename(displayRef), scope, ref: displayRef, profiles: 0, languages: 0, rules: 0, error: loaded.errors[0] };
|
|
44903
45377
|
}
|
|
44904
45378
|
return {
|
|
44905
|
-
name: loaded.packNames[0] ??
|
|
45379
|
+
name: loaded.packNames[0] ?? path64.basename(displayRef),
|
|
44906
45380
|
scope,
|
|
44907
45381
|
ref: displayRef,
|
|
44908
45382
|
profiles: Object.keys(loaded.profiles).length,
|
|
@@ -44916,20 +45390,20 @@ function probe(loadRef, baseRoot, scope, displayRef) {
|
|
|
44916
45390
|
};
|
|
44917
45391
|
}
|
|
44918
45392
|
function writeFileAtomic(file, content) {
|
|
44919
|
-
fs43.mkdirSync(
|
|
45393
|
+
fs43.mkdirSync(path64.dirname(file), { recursive: true });
|
|
44920
45394
|
const tmp = `${file}.tmp`;
|
|
44921
45395
|
fs43.writeFileSync(tmp, content);
|
|
44922
45396
|
fs43.renameSync(tmp, file);
|
|
44923
45397
|
}
|
|
44924
45398
|
function stem(ref) {
|
|
44925
|
-
return
|
|
45399
|
+
return path64.basename(ref).replace(PACK_EXT_RE2, "");
|
|
44926
45400
|
}
|
|
44927
45401
|
function imagePacksDir() {
|
|
44928
45402
|
return process.env.WAIRON_IMAGE_PACKS_DIR ?? "/opt/wairon/packs";
|
|
44929
45403
|
}
|
|
44930
45404
|
function probeTier(dir, tier) {
|
|
44931
45405
|
return discoverPacks(dir).map((full) => {
|
|
44932
|
-
const d = probe(full,
|
|
45406
|
+
const d = probe(full, path64.dirname(full), "global", path64.basename(full));
|
|
44933
45407
|
d.tier = tier;
|
|
44934
45408
|
return d;
|
|
44935
45409
|
});
|
|
@@ -44947,9 +45421,9 @@ function scanGlobalPackProfiles() {
|
|
|
44947
45421
|
for (const dir of [globalPacksDir(), imagePacksDir()]) {
|
|
44948
45422
|
for (const full of discoverPacks(dir)) {
|
|
44949
45423
|
try {
|
|
44950
|
-
const loaded = loadExtensionPacks([{ ref: full, scope: "global" }],
|
|
45424
|
+
const loaded = loadExtensionPacks([{ ref: full, scope: "global" }], path64.dirname(full));
|
|
44951
45425
|
if (loaded.errors.length) continue;
|
|
44952
|
-
const source = loaded.packNames[0] ??
|
|
45426
|
+
const source = loaded.packNames[0] ?? path64.basename(full);
|
|
44953
45427
|
for (const [id, def] of Object.entries(loaded.profiles)) out.push({ id, source, family: def.family });
|
|
44954
45428
|
} catch {
|
|
44955
45429
|
}
|
|
@@ -45005,7 +45479,7 @@ function readPackContent(full) {
|
|
|
45005
45479
|
if (st.isFile()) return fs43.readFileSync(full, "utf8");
|
|
45006
45480
|
if (st.isDirectory()) {
|
|
45007
45481
|
for (const entry of ["pack.yaml", "pack.yml"]) {
|
|
45008
|
-
const p =
|
|
45482
|
+
const p = path64.join(full, entry);
|
|
45009
45483
|
if (fs43.existsSync(p) && fs43.statSync(p).isFile()) return fs43.readFileSync(p, "utf8");
|
|
45010
45484
|
}
|
|
45011
45485
|
}
|
|
@@ -45015,9 +45489,9 @@ function storeResolveGlobalPacks(names) {
|
|
|
45015
45489
|
const index = /* @__PURE__ */ new Map();
|
|
45016
45490
|
const indexTier = (dir, tier) => {
|
|
45017
45491
|
for (const full of discoverPacks(dir)) {
|
|
45018
|
-
const manifestName = probe(full,
|
|
45492
|
+
const manifestName = probe(full, path64.dirname(full), "global", path64.basename(full)).name;
|
|
45019
45493
|
const candidate = { full, manifestName, tier };
|
|
45020
|
-
for (const key of [manifestName, stem(full),
|
|
45494
|
+
for (const key of [manifestName, stem(full), path64.basename(full)]) {
|
|
45021
45495
|
if (!index.has(key)) index.set(key, candidate);
|
|
45022
45496
|
}
|
|
45023
45497
|
}
|
|
@@ -45044,7 +45518,7 @@ function storeInstallGlobalPack(name, content) {
|
|
|
45044
45518
|
assertName(name);
|
|
45045
45519
|
assertDeclarative(content);
|
|
45046
45520
|
const dir = globalPacksDir();
|
|
45047
|
-
const file =
|
|
45521
|
+
const file = path64.join(dir, `${name}.yaml`);
|
|
45048
45522
|
writeFileAtomic(file, content);
|
|
45049
45523
|
const descriptor = probe(file, dir, "global", `${name}.yaml`);
|
|
45050
45524
|
descriptor.tier = "instance";
|
|
@@ -45055,7 +45529,7 @@ function storeInstallGlobalPackArchive(archive, name) {
|
|
|
45055
45529
|
assertNotCodeArchive(info);
|
|
45056
45530
|
const packName = name ?? info.name;
|
|
45057
45531
|
assertName(packName);
|
|
45058
|
-
const dir =
|
|
45532
|
+
const dir = path64.join(globalPacksDir(), packName);
|
|
45059
45533
|
extractDeclarativeArchive(archive, dir);
|
|
45060
45534
|
const descriptor = probe(dir, globalPacksDir(), "global", packName);
|
|
45061
45535
|
descriptor.tier = "instance";
|
|
@@ -45066,7 +45540,7 @@ function declaredName2(loadRef, baseRoot, scope) {
|
|
|
45066
45540
|
return d.error ? void 0 : d.name;
|
|
45067
45541
|
}
|
|
45068
45542
|
function pickByName(candidates, name, fileOf, manifestOf2) {
|
|
45069
|
-
const byFile = candidates.find((c) => stem(fileOf(c)) === name ||
|
|
45543
|
+
const byFile = candidates.find((c) => stem(fileOf(c)) === name || path64.basename(fileOf(c)) === name);
|
|
45070
45544
|
if (byFile !== void 0) return byFile;
|
|
45071
45545
|
const byManifest = candidates.filter((c) => manifestOf2(c) === name);
|
|
45072
45546
|
if (byManifest.length > 1) {
|
|
@@ -45079,11 +45553,11 @@ function pickByName(candidates, name, fileOf, manifestOf2) {
|
|
|
45079
45553
|
function storeRemoveGlobalPack(name) {
|
|
45080
45554
|
assertName(name);
|
|
45081
45555
|
const dir = globalPacksDir();
|
|
45082
|
-
const manifestOf2 = (ref) => declaredName2(ref,
|
|
45083
|
-
const match2 = pickByName(discoverPacks(dir), name,
|
|
45556
|
+
const manifestOf2 = (ref) => declaredName2(ref, path64.dirname(ref), "global");
|
|
45557
|
+
const match2 = pickByName(discoverPacks(dir), name, path64.basename, manifestOf2);
|
|
45084
45558
|
if (!match2) {
|
|
45085
45559
|
const inImage = discoverPacks(imagePacksDir()).some(
|
|
45086
|
-
(ref) => stem(ref) === name ||
|
|
45560
|
+
(ref) => stem(ref) === name || path64.basename(ref) === name || manifestOf2(ref) === name
|
|
45087
45561
|
);
|
|
45088
45562
|
throw new Error(
|
|
45089
45563
|
inImage ? `Pack "${name}" is an immutable image-layer pack (WAIRON_IMAGE_PACKS_DIR) and cannot be removed at runtime; rebuild the extension image without it.` : `No server-global instance pack named "${name}".`
|
|
@@ -45105,7 +45579,7 @@ function storeInstallProjectPack(name, content) {
|
|
|
45105
45579
|
assertDeclarative(content);
|
|
45106
45580
|
const root = getProjectRoot();
|
|
45107
45581
|
const relRef = `.wai/packs/${name}.yaml`;
|
|
45108
|
-
writeFileAtomic(
|
|
45582
|
+
writeFileAtomic(path64.join(root, ".wai", "packs", `${name}.yaml`), content);
|
|
45109
45583
|
return probe(relRef, root, "project", relRef);
|
|
45110
45584
|
}
|
|
45111
45585
|
function storeInstallProjectPackArchive(archive, name) {
|
|
@@ -45115,15 +45589,15 @@ function storeInstallProjectPackArchive(archive, name) {
|
|
|
45115
45589
|
assertName(packName);
|
|
45116
45590
|
const root = getProjectRoot();
|
|
45117
45591
|
const relRef = `.wai/packs/${packName}`;
|
|
45118
|
-
extractDeclarativeArchive(archive,
|
|
45592
|
+
extractDeclarativeArchive(archive, path64.join(root, ".wai", "packs", packName));
|
|
45119
45593
|
return probe(relRef, root, "project", relRef);
|
|
45120
45594
|
}
|
|
45121
45595
|
function storeRemoveProjectPack(name, ref) {
|
|
45122
45596
|
assertName(name);
|
|
45123
45597
|
const root = getProjectRoot();
|
|
45124
|
-
const resolved =
|
|
45125
|
-
const vendorDir =
|
|
45126
|
-
if (resolved.startsWith(vendorDir +
|
|
45598
|
+
const resolved = path64.resolve(root, ref ?? path64.join(".wai", "packs", name));
|
|
45599
|
+
const vendorDir = path64.resolve(root, ".wai", "packs");
|
|
45600
|
+
if (resolved.startsWith(vendorDir + path64.sep)) {
|
|
45127
45601
|
fs43.rmSync(resolved, { recursive: true, force: true });
|
|
45128
45602
|
}
|
|
45129
45603
|
}
|
|
@@ -45146,7 +45620,7 @@ function boundProject2(cfg, project2) {
|
|
|
45146
45620
|
function findPackRegistration(config, name) {
|
|
45147
45621
|
const relRef = `.wai/packs/${name}.yaml`;
|
|
45148
45622
|
const entries = config?.extensions?.packs ?? [];
|
|
45149
|
-
const direct = entries.find((entry) => typeof entry === "string" ? entry === relRef || stem(entry) === name ||
|
|
45623
|
+
const direct = entries.find((entry) => typeof entry === "string" ? entry === relRef || stem(entry) === name || path64.basename(entry) === name : entry.name === name);
|
|
45150
45624
|
if (direct !== void 0) return direct;
|
|
45151
45625
|
const root = getProjectRoot();
|
|
45152
45626
|
const refs = entries.filter((entry) => typeof entry === "string");
|
|
@@ -45290,7 +45764,7 @@ function installProjectPackArchive(cfg, credential, project2, archive, name) {
|
|
|
45290
45764
|
|
|
45291
45765
|
// src/server/audit.ts
|
|
45292
45766
|
var fs44 = __toESM(require("fs"));
|
|
45293
|
-
var
|
|
45767
|
+
var path65 = __toESM(require("path"));
|
|
45294
45768
|
var crypto12 = __toESM(require("crypto"));
|
|
45295
45769
|
var LEVEL_ORDER = {
|
|
45296
45770
|
debug: 0,
|
|
@@ -45313,7 +45787,7 @@ var DEFAULT_AUDIT_POLICY = {
|
|
|
45313
45787
|
metadataMode: "redacted"
|
|
45314
45788
|
};
|
|
45315
45789
|
function storePath9(dataDir) {
|
|
45316
|
-
return
|
|
45790
|
+
return path65.join(dataDir, "audit-events.json");
|
|
45317
45791
|
}
|
|
45318
45792
|
function readEvents(dataDir) {
|
|
45319
45793
|
const p = storePath9(dataDir);
|
|
@@ -45334,7 +45808,7 @@ function readEvents(dataDir) {
|
|
|
45334
45808
|
}
|
|
45335
45809
|
function persistEvents(dataDir, events) {
|
|
45336
45810
|
const p = storePath9(dataDir);
|
|
45337
|
-
fs44.mkdirSync(
|
|
45811
|
+
fs44.mkdirSync(path65.dirname(p), { recursive: true });
|
|
45338
45812
|
const tmp = `${p}.tmp`;
|
|
45339
45813
|
fs44.writeFileSync(tmp, JSON.stringify(events, null, 2) + "\n");
|
|
45340
45814
|
fs44.renameSync(tmp, p);
|
|
@@ -46026,7 +46500,7 @@ function assertAllowedRedirectUri(provider, redirectUri) {
|
|
|
46026
46500
|
|
|
46027
46501
|
// src/server/policy.ts
|
|
46028
46502
|
var fs45 = __toESM(require("fs"));
|
|
46029
|
-
var
|
|
46503
|
+
var path66 = __toESM(require("path"));
|
|
46030
46504
|
init_fs();
|
|
46031
46505
|
init_yaml();
|
|
46032
46506
|
init_pack_impact();
|
|
@@ -46045,7 +46519,7 @@ function sendJson(res, status2, body) {
|
|
|
46045
46519
|
|
|
46046
46520
|
// src/server/policy.ts
|
|
46047
46521
|
function packPolicyPath(dataDir) {
|
|
46048
|
-
return
|
|
46522
|
+
return path66.join(dataDir, "pack-policy.json");
|
|
46049
46523
|
}
|
|
46050
46524
|
function getPackPolicyRecord(dataDir) {
|
|
46051
46525
|
const p = packPolicyPath(dataDir);
|
|
@@ -46065,14 +46539,14 @@ function getPackPolicyRecord(dataDir) {
|
|
|
46065
46539
|
function setPackPolicyRecord(dataDir, policy) {
|
|
46066
46540
|
const stored = { ...policy, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
46067
46541
|
const p = packPolicyPath(dataDir);
|
|
46068
|
-
fs45.mkdirSync(
|
|
46542
|
+
fs45.mkdirSync(path66.dirname(p), { recursive: true });
|
|
46069
46543
|
const tmp = `${p}.tmp`;
|
|
46070
46544
|
fs45.writeFileSync(tmp, JSON.stringify(stored, null, 2) + "\n");
|
|
46071
46545
|
fs45.renameSync(tmp, p);
|
|
46072
46546
|
return stored;
|
|
46073
46547
|
}
|
|
46074
46548
|
function exposurePolicyPath(dataDir) {
|
|
46075
|
-
return
|
|
46549
|
+
return path66.join(dataDir, "exposure-policy.json");
|
|
46076
46550
|
}
|
|
46077
46551
|
var COMPATIBLE_DEFAULT_EXPOSURE = {
|
|
46078
46552
|
adminApiMode: "local_only",
|
|
@@ -46102,7 +46576,7 @@ function getExposurePolicyRecord(dataDir) {
|
|
|
46102
46576
|
}
|
|
46103
46577
|
function setExposurePolicyRecord(dataDir, policy) {
|
|
46104
46578
|
const p = exposurePolicyPath(dataDir);
|
|
46105
|
-
fs45.mkdirSync(
|
|
46579
|
+
fs45.mkdirSync(path66.dirname(p), { recursive: true });
|
|
46106
46580
|
const tmp = `${p}.tmp`;
|
|
46107
46581
|
fs45.writeFileSync(tmp, JSON.stringify(policy, null, 2) + "\n");
|
|
46108
46582
|
fs45.renameSync(tmp, p);
|
|
@@ -46123,7 +46597,7 @@ function effectivePolicy(dataDir) {
|
|
|
46123
46597
|
return getPackPolicyRecord(dataDir) ?? PERMISSIVE_DEFAULT_POLICY;
|
|
46124
46598
|
}
|
|
46125
46599
|
function identityProvidersPath(dataDir) {
|
|
46126
|
-
return
|
|
46600
|
+
return path66.join(dataDir, "identity-providers.json");
|
|
46127
46601
|
}
|
|
46128
46602
|
function sanitizeIdentityProvider(config) {
|
|
46129
46603
|
const clean = {
|
|
@@ -46147,7 +46621,7 @@ function sanitizeIdentityProvider(config) {
|
|
|
46147
46621
|
}
|
|
46148
46622
|
function writeIdentityProviderRecords(dataDir, records) {
|
|
46149
46623
|
const p = identityProvidersPath(dataDir);
|
|
46150
|
-
fs45.mkdirSync(
|
|
46624
|
+
fs45.mkdirSync(path66.dirname(p), { recursive: true });
|
|
46151
46625
|
const tmp = `${p}.tmp`;
|
|
46152
46626
|
fs45.writeFileSync(tmp, JSON.stringify(records, null, 2) + "\n");
|
|
46153
46627
|
fs45.renameSync(tmp, p);
|
|
@@ -47239,10 +47713,10 @@ init_fs();
|
|
|
47239
47713
|
|
|
47240
47714
|
// src/server/relations.ts
|
|
47241
47715
|
var fs46 = __toESM(require("fs"));
|
|
47242
|
-
var
|
|
47716
|
+
var path67 = __toESM(require("path"));
|
|
47243
47717
|
var crypto15 = __toESM(require("crypto"));
|
|
47244
47718
|
function storePath10(dataDir) {
|
|
47245
|
-
return
|
|
47719
|
+
return path67.join(dataDir, "relations.json");
|
|
47246
47720
|
}
|
|
47247
47721
|
function readRelations(dataDir) {
|
|
47248
47722
|
const p = storePath10(dataDir);
|
|
@@ -47263,7 +47737,7 @@ function readRelations(dataDir) {
|
|
|
47263
47737
|
}
|
|
47264
47738
|
function persistRelations(dataDir, relations) {
|
|
47265
47739
|
const p = storePath10(dataDir);
|
|
47266
|
-
fs46.mkdirSync(
|
|
47740
|
+
fs46.mkdirSync(path67.dirname(p), { recursive: true });
|
|
47267
47741
|
const tmp = `${p}.tmp`;
|
|
47268
47742
|
fs46.writeFileSync(tmp, JSON.stringify(relations, null, 2) + "\n");
|
|
47269
47743
|
fs46.renameSync(tmp, p);
|
|
@@ -47377,9 +47851,9 @@ function listProjectRelations(dataDir, sourceProjectId, targetProjectId, status2
|
|
|
47377
47851
|
|
|
47378
47852
|
// src/server/surfaces.ts
|
|
47379
47853
|
var fs47 = __toESM(require("fs"));
|
|
47380
|
-
var
|
|
47854
|
+
var path68 = __toESM(require("path"));
|
|
47381
47855
|
function storePath11(dataDir) {
|
|
47382
|
-
return
|
|
47856
|
+
return path68.join(dataDir, "public-surfaces.json");
|
|
47383
47857
|
}
|
|
47384
47858
|
function readSnapshots(dataDir) {
|
|
47385
47859
|
const p = storePath11(dataDir);
|
|
@@ -47400,7 +47874,7 @@ function readSnapshots(dataDir) {
|
|
|
47400
47874
|
}
|
|
47401
47875
|
function persistSnapshots(dataDir, snapshots) {
|
|
47402
47876
|
const p = storePath11(dataDir);
|
|
47403
|
-
fs47.mkdirSync(
|
|
47877
|
+
fs47.mkdirSync(path68.dirname(p), { recursive: true });
|
|
47404
47878
|
const tmp = `${p}.tmp`;
|
|
47405
47879
|
fs47.writeFileSync(tmp, JSON.stringify(snapshots, null, 2) + "\n");
|
|
47406
47880
|
fs47.renameSync(tmp, p);
|
|
@@ -47546,12 +48020,12 @@ function resolveVisibility(observerProjectId, units, placements) {
|
|
|
47546
48020
|
const best = /* @__PURE__ */ new Map();
|
|
47547
48021
|
for (const placement of placements) {
|
|
47548
48022
|
if (placement.projectId === observerProjectId) continue;
|
|
47549
|
-
const
|
|
47550
|
-
if (!
|
|
47551
|
-
const closedOk =
|
|
48023
|
+
const path82 = chainOf(placement.unitId, unitById);
|
|
48024
|
+
if (!path82.length) continue;
|
|
48025
|
+
const closedOk = path82.every((u) => effectivePosture(u, unitById) !== "closed" || observerInside(u.id) || grantedTo(u));
|
|
47552
48026
|
if (!closedOk) continue;
|
|
47553
|
-
const crossTenant = !tenantRoots.has(
|
|
47554
|
-
if (crossTenant && !
|
|
48027
|
+
const crossTenant = !tenantRoots.has(path82[path82.length - 1].id);
|
|
48028
|
+
if (crossTenant && !path82.some(grantedTo)) continue;
|
|
47555
48029
|
if (crossTenant && directUnits.length === 0) continue;
|
|
47556
48030
|
const distance = crossTenant ? "partner" : sameBranch(placement.unitId) ? "department" : "instance";
|
|
47557
48031
|
const existing = best.get(placement.projectId);
|
|
@@ -48171,7 +48645,7 @@ function handleLandscapeRequest(cfg, credential, req, res, body, url) {
|
|
|
48171
48645
|
|
|
48172
48646
|
// src/server/migration.ts
|
|
48173
48647
|
var fs48 = __toESM(require("fs"));
|
|
48174
|
-
var
|
|
48648
|
+
var path69 = __toESM(require("path"));
|
|
48175
48649
|
var LEGACY_CAPABILITY_MAP = {
|
|
48176
48650
|
"mcp:read": "project:read",
|
|
48177
48651
|
"operations:read": "project:read",
|
|
@@ -48209,7 +48683,7 @@ function readRaw(file) {
|
|
|
48209
48683
|
}
|
|
48210
48684
|
}
|
|
48211
48685
|
function writeRaw(file, value) {
|
|
48212
|
-
fs48.mkdirSync(
|
|
48686
|
+
fs48.mkdirSync(path69.dirname(file), { recursive: true });
|
|
48213
48687
|
const tmp = `${file}.tmp`;
|
|
48214
48688
|
fs48.writeFileSync(tmp, JSON.stringify(value, null, 2) + "\n");
|
|
48215
48689
|
fs48.renameSync(tmp, file);
|
|
@@ -48246,13 +48720,13 @@ function migratePermissionModel(dataDir, apply3) {
|
|
|
48246
48720
|
findings.push({ area, detail });
|
|
48247
48721
|
};
|
|
48248
48722
|
const grid = readRaw(
|
|
48249
|
-
|
|
48723
|
+
path69.join(dataDir, "permissions.json")
|
|
48250
48724
|
) ?? [];
|
|
48251
48725
|
if (!getInstanceIdentity(dataDir)) {
|
|
48252
48726
|
found("instance", "instance identity is not seeded \u2014 the built-in admin/dev subjects have no persisted UUIDs");
|
|
48253
48727
|
if (apply3) ensureInstanceIdentity(dataDir);
|
|
48254
48728
|
}
|
|
48255
|
-
const orgPath =
|
|
48729
|
+
const orgPath = path69.join(dataDir, "organization.json");
|
|
48256
48730
|
const org = readRaw(orgPath);
|
|
48257
48731
|
if (org?.units?.length) {
|
|
48258
48732
|
const units = org.units;
|
|
@@ -48306,7 +48780,7 @@ function migratePermissionModel(dataDir, apply3) {
|
|
|
48306
48780
|
found("units", `${remap.length} unit id(s) would be remapped across placements, exposeTo, assignments, and users`);
|
|
48307
48781
|
}
|
|
48308
48782
|
}
|
|
48309
|
-
const usersPath =
|
|
48783
|
+
const usersPath = path69.join(dataDir, "users.json");
|
|
48310
48784
|
const users = readRaw(usersPath);
|
|
48311
48785
|
if (users) {
|
|
48312
48786
|
let changed = false;
|
|
@@ -48333,7 +48807,7 @@ function migratePermissionModel(dataDir, apply3) {
|
|
|
48333
48807
|
}
|
|
48334
48808
|
if (apply3 && changed) writeRaw(usersPath, users);
|
|
48335
48809
|
}
|
|
48336
|
-
const keysPath =
|
|
48810
|
+
const keysPath = path69.join(dataDir, "auth", "credentials.json");
|
|
48337
48811
|
const keys = readRaw(keysPath);
|
|
48338
48812
|
if (keys) {
|
|
48339
48813
|
let changed = false;
|
|
@@ -48383,7 +48857,7 @@ function migratePermissionModel(dataDir, apply3) {
|
|
|
48383
48857
|
}
|
|
48384
48858
|
if (apply3 && changed) writeRaw(keysPath, keys);
|
|
48385
48859
|
}
|
|
48386
|
-
const sessionsPath =
|
|
48860
|
+
const sessionsPath = path69.join(dataDir, "web-sessions.json");
|
|
48387
48861
|
const sessions = readRaw(sessionsPath);
|
|
48388
48862
|
if (sessions) {
|
|
48389
48863
|
let changed = false;
|
|
@@ -48463,7 +48937,7 @@ function migratePermissionModel(dataDir, apply3) {
|
|
|
48463
48937
|
// src/server/http.ts
|
|
48464
48938
|
var http2 = __toESM(require("http"));
|
|
48465
48939
|
var fs56 = __toESM(require("fs"));
|
|
48466
|
-
var
|
|
48940
|
+
var path77 = __toESM(require("path"));
|
|
48467
48941
|
|
|
48468
48942
|
// src/server/request.ts
|
|
48469
48943
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
@@ -48477,10 +48951,10 @@ function createScopedServer() {
|
|
|
48477
48951
|
|
|
48478
48952
|
// src/server/approvals.ts
|
|
48479
48953
|
var fs49 = __toESM(require("fs"));
|
|
48480
|
-
var
|
|
48954
|
+
var path70 = __toESM(require("path"));
|
|
48481
48955
|
var crypto16 = __toESM(require("crypto"));
|
|
48482
48956
|
function storePath12(dataDir) {
|
|
48483
|
-
return
|
|
48957
|
+
return path70.join(dataDir, "approvals.json");
|
|
48484
48958
|
}
|
|
48485
48959
|
function readRequests(dataDir) {
|
|
48486
48960
|
const p = storePath12(dataDir);
|
|
@@ -48501,7 +48975,7 @@ function readRequests(dataDir) {
|
|
|
48501
48975
|
}
|
|
48502
48976
|
function persistRequests(dataDir, requests) {
|
|
48503
48977
|
const p = storePath12(dataDir);
|
|
48504
|
-
fs49.mkdirSync(
|
|
48978
|
+
fs49.mkdirSync(path70.dirname(p), { recursive: true });
|
|
48505
48979
|
const tmp = `${p}.tmp`;
|
|
48506
48980
|
fs49.writeFileSync(tmp, JSON.stringify(requests, null, 2) + "\n");
|
|
48507
48981
|
fs49.renameSync(tmp, p);
|
|
@@ -48634,6 +49108,33 @@ var ApprovalRegistry = class {
|
|
|
48634
49108
|
this.store.replaceAll(next);
|
|
48635
49109
|
return completed2;
|
|
48636
49110
|
}
|
|
49111
|
+
/**
|
|
49112
|
+
* Locate the request by id (not-found error when absent) and transition it to
|
|
49113
|
+
* cancelled only from an approved status (any other status is a validation
|
|
49114
|
+
* error), recording the reason as its executionSummary, then persist and
|
|
49115
|
+
* refresh the store. Used when the approved workflow refused because the
|
|
49116
|
+
* request no longer describes what would be done; a cancelled request is
|
|
49117
|
+
* final and is never executed.
|
|
49118
|
+
*/
|
|
49119
|
+
cancel(id, reason) {
|
|
49120
|
+
const requests = this.store.all();
|
|
49121
|
+
const idx = requests.findIndex((r) => r.id === id);
|
|
49122
|
+
if (idx === -1) {
|
|
49123
|
+
throw new Error(`Approval request "${id}" not found.`);
|
|
49124
|
+
}
|
|
49125
|
+
const current2 = requests[idx];
|
|
49126
|
+
if (current2.status !== "approved") {
|
|
49127
|
+
throw new Error(
|
|
49128
|
+
`Approval request "${id}" is ${current2.status}, not approved; only an approved request can be cancelled.`
|
|
49129
|
+
);
|
|
49130
|
+
}
|
|
49131
|
+
const cancelled = { ...current2, status: "cancelled", executionSummary: reason };
|
|
49132
|
+
const next = [...requests];
|
|
49133
|
+
next[idx] = cancelled;
|
|
49134
|
+
persistRequests(this.dataDir, next);
|
|
49135
|
+
this.store.replaceAll(next);
|
|
49136
|
+
return cancelled;
|
|
49137
|
+
}
|
|
48637
49138
|
/**
|
|
48638
49139
|
* Flip every pending request whose expiresAt is at or before the supplied
|
|
48639
49140
|
* now-timestamp to expired and return the count flipped. A set with no overdue
|
|
@@ -48705,6 +49206,11 @@ function markApprovalCompleted(dataDir, id, executionSummary) {
|
|
|
48705
49206
|
store.load();
|
|
48706
49207
|
return new ApprovalRegistry(dataDir, store).markCompleted(id, executionSummary);
|
|
48707
49208
|
}
|
|
49209
|
+
function cancelApprovalRequest(dataDir, id, reason) {
|
|
49210
|
+
const store = new ApprovalStore(dataDir);
|
|
49211
|
+
store.load();
|
|
49212
|
+
return new ApprovalRegistry(dataDir, store).cancel(id, reason);
|
|
49213
|
+
}
|
|
48708
49214
|
function expirePendingApprovals(dataDir, now) {
|
|
48709
49215
|
const store = new ApprovalStore(dataDir);
|
|
48710
49216
|
store.load();
|
|
@@ -48863,12 +49369,16 @@ function lockProject2(cfg, credential, projectId, subproject) {
|
|
|
48863
49369
|
return {
|
|
48864
49370
|
status: "completed",
|
|
48865
49371
|
action: "project:lock",
|
|
48866
|
-
summary: `Locked project "${projectId}"${subprojectSuffix(subproject)} (status: ${lock.status}).`,
|
|
49372
|
+
summary: `Locked project "${projectId}"${subprojectSuffix(subproject)} (status: ${lock.status}). ${codeBesideClaim(lock)}.`,
|
|
48867
49373
|
lock
|
|
48868
49374
|
};
|
|
48869
49375
|
}
|
|
48870
49376
|
});
|
|
48871
49377
|
}
|
|
49378
|
+
function codeBesideClaim(lock) {
|
|
49379
|
+
if (!lock.code) return "code: no code analysis recorded";
|
|
49380
|
+
return `code: ${lock.code.errors} errors recorded beside the claim (${lock.code.warnings} warnings; analyzer ${lock.code.analyzer.validatorVersion}, grade ${lock.code.analyzer.grade})`;
|
|
49381
|
+
}
|
|
48872
49382
|
function subprojectSuffix(subproject) {
|
|
48873
49383
|
return subproject ? ` subproject "${subproject}"` : "";
|
|
48874
49384
|
}
|
|
@@ -48909,12 +49419,13 @@ function lifecycleAction(cfg, credential, projectId, opts) {
|
|
|
48909
49419
|
if (!existingProjectRoot(cfg.dataDir, projectId)) {
|
|
48910
49420
|
throw new Error(`Unknown project "${projectId}".`);
|
|
48911
49421
|
}
|
|
49422
|
+
const gateStateId = gateIdentity(cfg, projectId, opts.subproject);
|
|
48912
49423
|
const pending = buildPendingRequest(
|
|
48913
49424
|
principal,
|
|
48914
49425
|
opts.action,
|
|
48915
49426
|
`${opts.verb} project ${projectId}${subprojectSuffix(opts.subproject)}`,
|
|
48916
49427
|
projectId,
|
|
48917
|
-
subprojectScopePayload(opts.subproject)
|
|
49428
|
+
{ ...subprojectScopePayload(opts.subproject), gateStateId }
|
|
48918
49429
|
);
|
|
48919
49430
|
return createPendingOutcome(cfg, principal, pending, opts.action);
|
|
48920
49431
|
}
|
|
@@ -48997,13 +49508,31 @@ function executeApproved(cfg, req) {
|
|
|
48997
49508
|
}
|
|
48998
49509
|
case "project:lock": {
|
|
48999
49510
|
const scope = readSubprojectScope(req);
|
|
49000
|
-
const lock =
|
|
49001
|
-
return {
|
|
49511
|
+
const lock = lockAsRequested(cfg, req, scope);
|
|
49512
|
+
return {
|
|
49513
|
+
outcome: `Locked project "${req.projectId}"${subprojectSuffix(scope)} (status: ${lock.status}). ${codeBesideClaim(lock)}.`
|
|
49514
|
+
};
|
|
49002
49515
|
}
|
|
49003
49516
|
default:
|
|
49004
49517
|
throw new Error(`Unsupported approval kind "${req.kind}".`);
|
|
49005
49518
|
}
|
|
49006
49519
|
}
|
|
49520
|
+
function lockAsRequested(cfg, req, scope) {
|
|
49521
|
+
try {
|
|
49522
|
+
return executeApprovedLock(cfg, req.projectId ?? "", hostedApprover(req.decidedBy), scope, req.gateStateId);
|
|
49523
|
+
} catch (err) {
|
|
49524
|
+
const moved = designMovedSince(err);
|
|
49525
|
+
if (!moved) throw err;
|
|
49526
|
+
const reason = `the design changed since it was requested (requested ${moved.requested}, now ${moved.now}); request the lock again`;
|
|
49527
|
+
cancelApprovalRequest(cfg.dataDir, req.id, reason);
|
|
49528
|
+
throw new Error(`Approval request "${req.id}" was cancelled: ${reason}.`);
|
|
49529
|
+
}
|
|
49530
|
+
}
|
|
49531
|
+
function designMovedSince(err) {
|
|
49532
|
+
if (!(err instanceof Error) || err.name !== "LockRefusedError") return null;
|
|
49533
|
+
const { requested, now } = err;
|
|
49534
|
+
return typeof requested === "string" && typeof now === "string" ? { requested, now } : null;
|
|
49535
|
+
}
|
|
49007
49536
|
function executeApprovedRequest(cfg, credential, requestId) {
|
|
49008
49537
|
const principal = requirePrincipal7(cfg, credential);
|
|
49009
49538
|
const req = getApprovalRequestById(cfg.dataDir, requestId);
|
|
@@ -49042,7 +49571,7 @@ async function awaitApproval(cfg, credential, requestId, timeoutSeconds) {
|
|
|
49042
49571
|
let current2 = req;
|
|
49043
49572
|
while (current2.status === "pending" && Date.now() < deadline) {
|
|
49044
49573
|
const remaining = deadline - Date.now();
|
|
49045
|
-
await new Promise((
|
|
49574
|
+
await new Promise((resolve40) => setTimeout(resolve40, Math.min(AWAIT_POLL_INTERVAL_MS, remaining)));
|
|
49046
49575
|
expirePendingApprovals(cfg.dataDir, (/* @__PURE__ */ new Date()).toISOString());
|
|
49047
49576
|
current2 = getApprovalRequestById(cfg.dataDir, requestId) ?? current2;
|
|
49048
49577
|
}
|
|
@@ -49330,11 +49859,11 @@ function handleOperationsRequest(cfg, credential, req, res, url) {
|
|
|
49330
49859
|
|
|
49331
49860
|
// src/server/gitbacking.ts
|
|
49332
49861
|
var fs50 = __toESM(require("fs"));
|
|
49333
|
-
var
|
|
49862
|
+
var path71 = __toESM(require("path"));
|
|
49334
49863
|
var crypto17 = __toESM(require("crypto"));
|
|
49335
49864
|
var import_child_process6 = require("child_process");
|
|
49336
49865
|
function storePath13(dataDir) {
|
|
49337
|
-
return
|
|
49866
|
+
return path71.join(dataDir, "git-backing.json");
|
|
49338
49867
|
}
|
|
49339
49868
|
var GitBackingStore = class {
|
|
49340
49869
|
constructor(dataDir) {
|
|
@@ -49365,7 +49894,7 @@ var GitBackingStore = class {
|
|
|
49365
49894
|
/** Swap the persisted collection wholesale via write-temp-then-rename. */
|
|
49366
49895
|
replaceAll(bindings) {
|
|
49367
49896
|
const p = storePath13(this.dataDir);
|
|
49368
|
-
fs50.mkdirSync(
|
|
49897
|
+
fs50.mkdirSync(path71.dirname(p), { recursive: true });
|
|
49369
49898
|
const tmp = `${p}.tmp`;
|
|
49370
49899
|
fs50.writeFileSync(tmp, JSON.stringify(bindings, null, 2) + "\n");
|
|
49371
49900
|
fs50.renameSync(tmp, p);
|
|
@@ -49473,7 +50002,7 @@ function checkoutBranch(workdir, branch) {
|
|
|
49473
50002
|
}
|
|
49474
50003
|
}
|
|
49475
50004
|
function cloneOrOpen(remote, branch, workdir, credentialRef) {
|
|
49476
|
-
if (!fs50.existsSync(
|
|
50005
|
+
if (!fs50.existsSync(path71.join(workdir, ".git"))) {
|
|
49477
50006
|
fs50.mkdirSync(workdir, { recursive: true });
|
|
49478
50007
|
git2(["clone", authRemote2(remote, credentialRef), "."], workdir);
|
|
49479
50008
|
git2(["config", "user.name", process.env["WAIRON_GIT_NAME"] || "wairon-bot"], workdir);
|
|
@@ -49489,7 +50018,7 @@ function mirrorTree(sourceDir, targetDir) {
|
|
|
49489
50018
|
fs50.rmSync(targetDir, { recursive: true, force: true });
|
|
49490
50019
|
const stat = fs50.statSync(sourceDir, { throwIfNoEntry: false });
|
|
49491
50020
|
if (!stat) return;
|
|
49492
|
-
fs50.mkdirSync(
|
|
50021
|
+
fs50.mkdirSync(path71.dirname(targetDir), { recursive: true });
|
|
49493
50022
|
fs50.cpSync(sourceDir, targetDir, { recursive: true });
|
|
49494
50023
|
}
|
|
49495
50024
|
function commitAndPush(workdir, message) {
|
|
@@ -49612,7 +50141,7 @@ function unbindScope(cfg, credential, bindingId) {
|
|
|
49612
50141
|
tryAppendAudit7(cfg, buildAuditEvent6(principal, "git.backing.unbind", "security", bindingId));
|
|
49613
50142
|
}
|
|
49614
50143
|
function runMirrorSync(cfg, binding) {
|
|
49615
|
-
const workdir =
|
|
50144
|
+
const workdir = path71.join(cfg.dataDir, "git-backing", binding.id);
|
|
49616
50145
|
cloneOrOpen(binding.remote, binding.branch, workdir, binding.credentialRef);
|
|
49617
50146
|
if (binding.scopeKind === "unit") {
|
|
49618
50147
|
const units = listOrganizationUnits(cfg.dataDir);
|
|
@@ -49620,23 +50149,23 @@ function runMirrorSync(cfg, binding) {
|
|
|
49620
50149
|
const placedIds = new Set(
|
|
49621
50150
|
listProjectPlacements(cfg.dataDir).filter((p) => subtree.has(p.unitId)).map((p) => p.projectId)
|
|
49622
50151
|
);
|
|
49623
|
-
const projectsDir =
|
|
50152
|
+
const projectsDir = path71.join(workdir, "projects");
|
|
49624
50153
|
fs50.rmSync(projectsDir, { recursive: true, force: true });
|
|
49625
50154
|
for (const rec of listProjectRecords(cfg.dataDir)) {
|
|
49626
50155
|
if (!placedIds.has(rec.id)) continue;
|
|
49627
|
-
mirrorTree(
|
|
50156
|
+
mirrorTree(path71.join(rec.rootPath, ".wai"), path71.join(projectsDir, rec.id, ".wai"));
|
|
49628
50157
|
}
|
|
49629
50158
|
} else {
|
|
49630
|
-
const instanceDir =
|
|
50159
|
+
const instanceDir = path71.join(workdir, "instance");
|
|
49631
50160
|
fs50.rmSync(instanceDir, { recursive: true, force: true });
|
|
49632
50161
|
const files = binding.includeCredentials ? INSTANCE_STRUCTURE_FILES : INSTANCE_STRUCTURE_FILES.filter((rel2) => rel2 !== "auth/credentials.json");
|
|
49633
50162
|
for (const rel2 of files) {
|
|
49634
|
-
mirrorTree(
|
|
50163
|
+
mirrorTree(path71.join(cfg.dataDir, rel2), path71.join(instanceDir, rel2));
|
|
49635
50164
|
}
|
|
49636
|
-
const projectsDir =
|
|
50165
|
+
const projectsDir = path71.join(workdir, "projects");
|
|
49637
50166
|
fs50.rmSync(projectsDir, { recursive: true, force: true });
|
|
49638
50167
|
for (const rec of listProjectRecords(cfg.dataDir)) {
|
|
49639
|
-
mirrorTree(
|
|
50168
|
+
mirrorTree(path71.join(rec.rootPath, ".wai"), path71.join(projectsDir, rec.id, ".wai"));
|
|
49640
50169
|
}
|
|
49641
50170
|
}
|
|
49642
50171
|
const published = commitAndPush(
|
|
@@ -49960,7 +50489,7 @@ function encodeFrame(opcode, payload) {
|
|
|
49960
50489
|
// src/server/web.ts
|
|
49961
50490
|
var crypto23 = __toESM(require("crypto"));
|
|
49962
50491
|
var fs55 = __toESM(require("fs"));
|
|
49963
|
-
var
|
|
50492
|
+
var path76 = __toESM(require("path"));
|
|
49964
50493
|
|
|
49965
50494
|
// src/server/webadmin.ts
|
|
49966
50495
|
function listUsers3(cfg, sessionId, project2) {
|
|
@@ -50215,14 +50744,14 @@ var crypto22 = __toESM(require("crypto"));
|
|
|
50215
50744
|
|
|
50216
50745
|
// src/server/sharesnapshots.ts
|
|
50217
50746
|
var fs51 = __toESM(require("fs"));
|
|
50218
|
-
var
|
|
50747
|
+
var path72 = __toESM(require("path"));
|
|
50219
50748
|
var crypto19 = __toESM(require("crypto"));
|
|
50220
50749
|
init_fs();
|
|
50221
50750
|
function snapshotDir(dataDir) {
|
|
50222
|
-
return
|
|
50751
|
+
return path72.join(dataDir, "share-snapshots");
|
|
50223
50752
|
}
|
|
50224
50753
|
function snapshotPath2(dataDir, id) {
|
|
50225
|
-
return
|
|
50754
|
+
return path72.join(snapshotDir(dataDir), `${id}.json`);
|
|
50226
50755
|
}
|
|
50227
50756
|
var ShareSnapshotStore = class {
|
|
50228
50757
|
constructor(dataDir) {
|
|
@@ -50231,7 +50760,7 @@ var ShareSnapshotStore = class {
|
|
|
50231
50760
|
write(snapshot) {
|
|
50232
50761
|
const p = snapshotPath2(this.dataDir, snapshot.id);
|
|
50233
50762
|
if (fs51.existsSync(p)) return;
|
|
50234
|
-
fs51.mkdirSync(
|
|
50763
|
+
fs51.mkdirSync(path72.dirname(p), { recursive: true });
|
|
50235
50764
|
const tmp = `${p}.tmp`;
|
|
50236
50765
|
fs51.writeFileSync(tmp, JSON.stringify(snapshot, null, 2) + "\n");
|
|
50237
50766
|
fs51.renameSync(tmp, p);
|
|
@@ -50333,10 +50862,10 @@ function captureSnapshot(dataDir, principal, projectId, view, artifacts) {
|
|
|
50333
50862
|
|
|
50334
50863
|
// src/server/sharelinks.ts
|
|
50335
50864
|
var fs52 = __toESM(require("fs"));
|
|
50336
|
-
var
|
|
50865
|
+
var path73 = __toESM(require("path"));
|
|
50337
50866
|
var crypto20 = __toESM(require("crypto"));
|
|
50338
50867
|
function storePath14(dataDir) {
|
|
50339
|
-
return
|
|
50868
|
+
return path73.join(dataDir, "share-links.json");
|
|
50340
50869
|
}
|
|
50341
50870
|
var ShareLinkStore = class {
|
|
50342
50871
|
constructor(dataDir) {
|
|
@@ -50364,7 +50893,7 @@ var ShareLinkStore = class {
|
|
|
50364
50893
|
}
|
|
50365
50894
|
replaceAll(links) {
|
|
50366
50895
|
const p = storePath14(this.dataDir);
|
|
50367
|
-
fs52.mkdirSync(
|
|
50896
|
+
fs52.mkdirSync(path73.dirname(p), { recursive: true });
|
|
50368
50897
|
const tmp = `${p}.tmp`;
|
|
50369
50898
|
fs52.writeFileSync(tmp, JSON.stringify(links, null, 2) + "\n");
|
|
50370
50899
|
fs52.renameSync(tmp, p);
|
|
@@ -50436,10 +50965,10 @@ function listProjectLinks(dataDir, projectId) {
|
|
|
50436
50965
|
|
|
50437
50966
|
// src/server/shareaccesslog.ts
|
|
50438
50967
|
var fs53 = __toESM(require("fs"));
|
|
50439
|
-
var
|
|
50968
|
+
var path74 = __toESM(require("path"));
|
|
50440
50969
|
var crypto21 = __toESM(require("crypto"));
|
|
50441
50970
|
function storePath15(dataDir) {
|
|
50442
|
-
return
|
|
50971
|
+
return path74.join(dataDir, "share-access.json");
|
|
50443
50972
|
}
|
|
50444
50973
|
var ShareAccessStore = class {
|
|
50445
50974
|
constructor(dataDir) {
|
|
@@ -50467,7 +50996,7 @@ var ShareAccessStore = class {
|
|
|
50467
50996
|
}
|
|
50468
50997
|
append(entry) {
|
|
50469
50998
|
const p = storePath15(this.dataDir);
|
|
50470
|
-
fs53.mkdirSync(
|
|
50999
|
+
fs53.mkdirSync(path74.dirname(p), { recursive: true });
|
|
50471
51000
|
const next = [...this.load(), entry];
|
|
50472
51001
|
const tmp = `${p}.tmp`;
|
|
50473
51002
|
fs53.writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n");
|
|
@@ -50623,15 +51152,15 @@ init_fs();
|
|
|
50623
51152
|
|
|
50624
51153
|
// src/server/swagger.ts
|
|
50625
51154
|
var fs54 = __toESM(require("fs"));
|
|
50626
|
-
var
|
|
51155
|
+
var path75 = __toESM(require("path"));
|
|
50627
51156
|
var cache;
|
|
50628
51157
|
function loadAssets() {
|
|
50629
51158
|
if (cache !== void 0) return cache;
|
|
50630
51159
|
try {
|
|
50631
|
-
const dir =
|
|
51160
|
+
const dir = path75.dirname(require.resolve("swagger-ui-dist/package.json"));
|
|
50632
51161
|
cache = {
|
|
50633
|
-
css: fs54.readFileSync(
|
|
50634
|
-
js: fs54.readFileSync(
|
|
51162
|
+
css: fs54.readFileSync(path75.join(dir, "swagger-ui.css"), "utf8"),
|
|
51163
|
+
js: fs54.readFileSync(path75.join(dir, "swagger-ui-bundle.js"), "utf8")
|
|
50635
51164
|
};
|
|
50636
51165
|
} catch {
|
|
50637
51166
|
cache = null;
|
|
@@ -51043,11 +51572,11 @@ function nonceMatches(a, b) {
|
|
|
51043
51572
|
}
|
|
51044
51573
|
function loadReactBundle() {
|
|
51045
51574
|
const candidates = [
|
|
51046
|
-
|
|
51575
|
+
path76.resolve(__dirname, "webapp.html"),
|
|
51047
51576
|
// dist/index.js -> dist/webapp.html
|
|
51048
|
-
|
|
51577
|
+
path76.resolve(__dirname, "..", "webapp.html"),
|
|
51049
51578
|
// dist/cli/index.js -> dist/webapp.html
|
|
51050
|
-
|
|
51579
|
+
path76.resolve(__dirname, "..", "..", "web", "dist", "index.html")
|
|
51051
51580
|
// tsx dev: src/server -> web/dist
|
|
51052
51581
|
];
|
|
51053
51582
|
for (const candidate of candidates) {
|
|
@@ -53618,6 +54147,7 @@ async function handleWebRequest(cfg, req, res, body, url, ctx) {
|
|
|
53618
54147
|
const errors = err.errors;
|
|
53619
54148
|
return sendJson(res, 409, { error: err.message, errors });
|
|
53620
54149
|
}
|
|
54150
|
+
if (err instanceof Error && err.name === "LockRefusedError") return sendJson(res, 409, { error: err.message });
|
|
53621
54151
|
const msg = err instanceof Error ? err.message : String(err);
|
|
53622
54152
|
if (/unsupported graph tier/i.test(msg)) return sendJson(res, 400, { error: msg });
|
|
53623
54153
|
if (/not found|unknown/i.test(msg)) return sendJson(res, 404, { error: msg });
|
|
@@ -53644,8 +54174,8 @@ var RealtimeHub = class {
|
|
|
53644
54174
|
* complete the handshake, and register the connection. A bad path or session
|
|
53645
54175
|
* destroys the socket. */
|
|
53646
54176
|
handleUpgrade(cfg, req, socket) {
|
|
53647
|
-
const
|
|
53648
|
-
if (
|
|
54177
|
+
const path82 = (req.url ?? "/").split("?")[0];
|
|
54178
|
+
if (path82 !== REALTIME_PATH || !isWebSocketUpgrade(req)) {
|
|
53649
54179
|
socket.destroy();
|
|
53650
54180
|
return;
|
|
53651
54181
|
}
|
|
@@ -54653,7 +55183,7 @@ function routeData(cfg, req, res) {
|
|
|
54653
55183
|
}
|
|
54654
55184
|
function readExposurePolicyFile(dataDir) {
|
|
54655
55185
|
try {
|
|
54656
|
-
const raw = fs56.readFileSync(
|
|
55186
|
+
const raw = fs56.readFileSync(path77.join(dataDir, "exposure-policy.json"), "utf8");
|
|
54657
55187
|
const parsed = JSON.parse(raw);
|
|
54658
55188
|
return parsed && typeof parsed === "object" ? parsed : void 0;
|
|
54659
55189
|
} catch {
|
|
@@ -54842,6 +55372,7 @@ async function routeAdmin(cfg, req, res) {
|
|
|
54842
55372
|
if (err instanceof ForbiddenError) return sendJson(res, 403, { error: err.message });
|
|
54843
55373
|
if (err instanceof AdminAuthError) return sendJson(res, 403, { error: "forbidden" });
|
|
54844
55374
|
if (err instanceof LockValidationError) return sendJson(res, 409, { error: err.message, errors: err.errors });
|
|
55375
|
+
if (err instanceof Error && err.name === "LockRefusedError") return sendJson(res, 409, { error: err.message });
|
|
54845
55376
|
sendJson(res, 400, { error: err instanceof Error ? err.message : String(err) });
|
|
54846
55377
|
}
|
|
54847
55378
|
}
|
|
@@ -55352,9 +55883,9 @@ function seedDemoTree() {
|
|
|
55352
55883
|
|
|
55353
55884
|
// src/commands/host.ts
|
|
55354
55885
|
function resolveHostConfig(options) {
|
|
55355
|
-
const dataDir = options.dataDir || process.env["WAIRON_DATA_DIR"] ||
|
|
55886
|
+
const dataDir = options.dataDir || process.env["WAIRON_DATA_DIR"] || path78.join(os11.homedir(), ".wairon", "data");
|
|
55356
55887
|
if (!process.env["WAIRON_PACKS_DIR"]) {
|
|
55357
|
-
process.env["WAIRON_PACKS_DIR"] =
|
|
55888
|
+
process.env["WAIRON_PACKS_DIR"] = path78.join(dataDir, "packs");
|
|
55358
55889
|
}
|
|
55359
55890
|
const cfg = {
|
|
55360
55891
|
host: options.host || "0.0.0.0",
|
|
@@ -55411,11 +55942,11 @@ async function runServe(options = {}) {
|
|
|
55411
55942
|
logger.info(` data dir: ${import_chalk17.default.gray(cfg.dataDir)}`);
|
|
55412
55943
|
logger.blank();
|
|
55413
55944
|
logger.info("Press Ctrl+C to stop.");
|
|
55414
|
-
await new Promise((
|
|
55945
|
+
await new Promise((resolve40) => {
|
|
55415
55946
|
const shutdown = () => {
|
|
55416
55947
|
logger.info("Shutting down\u2026");
|
|
55417
55948
|
handle.close();
|
|
55418
|
-
|
|
55949
|
+
resolve40();
|
|
55419
55950
|
};
|
|
55420
55951
|
process.on("SIGINT", shutdown);
|
|
55421
55952
|
process.on("SIGTERM", shutdown);
|
|
@@ -55435,13 +55966,13 @@ function openBrowser(url) {
|
|
|
55435
55966
|
}
|
|
55436
55967
|
async function runDev(options = {}) {
|
|
55437
55968
|
const cwd = process.cwd();
|
|
55438
|
-
if (!fs57.existsSync(
|
|
55969
|
+
if (!fs57.existsSync(path78.join(cwd, ".wai"))) {
|
|
55439
55970
|
throw new WaironError(
|
|
55440
55971
|
"No .wai/ found in the current directory. Run `wairon dev` from a wairon project root (or run `wairon init` first)."
|
|
55441
55972
|
);
|
|
55442
55973
|
}
|
|
55443
55974
|
const hash = crypto24.createHash("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
55444
|
-
const dataDir =
|
|
55975
|
+
const dataDir = path78.join(os11.tmpdir(), "wairon-dev", hash);
|
|
55445
55976
|
fs57.mkdirSync(dataDir, { recursive: true });
|
|
55446
55977
|
const port = options.port ? Number(options.port) : 8080;
|
|
55447
55978
|
const exposurePolicy = {
|
|
@@ -55480,11 +56011,11 @@ async function runDev(options = {}) {
|
|
|
55480
56011
|
logger.blank();
|
|
55481
56012
|
logger.info("An agent edits specs; refresh the page to see the live graph. Press Ctrl+C to stop.");
|
|
55482
56013
|
if (options.open) openBrowser(url);
|
|
55483
|
-
await new Promise((
|
|
56014
|
+
await new Promise((resolve40) => {
|
|
55484
56015
|
const shutdown = () => {
|
|
55485
56016
|
logger.info("Shutting down\u2026");
|
|
55486
56017
|
handle.close();
|
|
55487
|
-
|
|
56018
|
+
resolve40();
|
|
55488
56019
|
};
|
|
55489
56020
|
process.on("SIGINT", shutdown);
|
|
55490
56021
|
process.on("SIGTERM", shutdown);
|
|
@@ -55880,8 +56411,8 @@ async function runHostPacks(action, options = {}) {
|
|
|
55880
56411
|
}
|
|
55881
56412
|
case "install": {
|
|
55882
56413
|
if (!options.file) throw new WaironError("`--file <path>` (a declarative pack YAML) is required for install.");
|
|
55883
|
-
const name = options.name ??
|
|
55884
|
-
const content = fs57.readFileSync(
|
|
56414
|
+
const name = options.name ?? path78.basename(options.file).replace(/\.(ya?ml)$/i, "");
|
|
56415
|
+
const content = fs57.readFileSync(path78.resolve(options.file), "utf8");
|
|
55885
56416
|
if (project2 && !hostPackWriteUnattended(options.yes)) {
|
|
55886
56417
|
printHostPackImpact(previewProjectPack(cfg, cred, project2, name, content));
|
|
55887
56418
|
if (!await askHostPackWrite(`Install the pack "${name}" into ${scope}?`)) {
|
|
@@ -56184,7 +56715,7 @@ async function runExternals(action, aliases, options = {}) {
|
|
|
56184
56715
|
|
|
56185
56716
|
// src/commands/remote.ts
|
|
56186
56717
|
var fs59 = __toESM(require("fs"));
|
|
56187
|
-
var
|
|
56718
|
+
var path80 = __toESM(require("path"));
|
|
56188
56719
|
var import_chalk21 = __toESM(require("chalk"));
|
|
56189
56720
|
init_logger();
|
|
56190
56721
|
init_errors();
|
|
@@ -56195,15 +56726,15 @@ init_mcp();
|
|
|
56195
56726
|
// src/commands/remotebinding.ts
|
|
56196
56727
|
var fs58 = __toESM(require("fs"));
|
|
56197
56728
|
var os12 = __toESM(require("os"));
|
|
56198
|
-
var
|
|
56729
|
+
var path79 = __toESM(require("path"));
|
|
56199
56730
|
init_paths();
|
|
56200
56731
|
var BINDING_FILE = "remote.json";
|
|
56201
56732
|
var CREDENTIALS_FILE = "credentials.json";
|
|
56202
56733
|
function bindingPath(root) {
|
|
56203
|
-
return
|
|
56734
|
+
return path79.join(aiPathsAt(root).root(), BINDING_FILE);
|
|
56204
56735
|
}
|
|
56205
56736
|
function credentialsPath() {
|
|
56206
|
-
return
|
|
56737
|
+
return path79.join(os12.homedir(), ".wairon", CREDENTIALS_FILE);
|
|
56207
56738
|
}
|
|
56208
56739
|
function normalizeInstanceUrl(url) {
|
|
56209
56740
|
return url.trim().replace(/\/+$/, "").toLowerCase();
|
|
@@ -56217,7 +56748,7 @@ function readJson(file) {
|
|
|
56217
56748
|
}
|
|
56218
56749
|
}
|
|
56219
56750
|
function writeJsonAtomic(file, value, mode) {
|
|
56220
|
-
fs58.mkdirSync(
|
|
56751
|
+
fs58.mkdirSync(path79.dirname(file), { recursive: true });
|
|
56221
56752
|
const tmp = `${file}.tmp`;
|
|
56222
56753
|
fs58.writeFileSync(tmp, JSON.stringify(value, null, 2) + "\n", mode !== void 0 ? { mode } : void 0);
|
|
56223
56754
|
fs58.renameSync(tmp, file);
|
|
@@ -56559,7 +57090,7 @@ async function runRemote(action, options = {}) {
|
|
|
56559
57090
|
allowPartial: options.allowPartial === true
|
|
56560
57091
|
};
|
|
56561
57092
|
if (options.archive) transfer.archivePath = options.archive;
|
|
56562
|
-
if (options.dir) transfer.destDir =
|
|
57093
|
+
if (options.dir) transfer.destDir = path80.resolve(options.dir);
|
|
56563
57094
|
report2(await pullTree(target, transfer));
|
|
56564
57095
|
return;
|
|
56565
57096
|
}
|
|
@@ -56643,9 +57174,9 @@ function topProjectId2(selector) {
|
|
|
56643
57174
|
}
|
|
56644
57175
|
function writeArchiveIfAsked(archive, archivePath, suggestedFileName) {
|
|
56645
57176
|
if (!archivePath) return void 0;
|
|
56646
|
-
const resolved =
|
|
56647
|
-
const target = fs59.existsSync(resolved) && fs59.statSync(resolved).isDirectory() ?
|
|
56648
|
-
fs59.mkdirSync(
|
|
57177
|
+
const resolved = path80.resolve(archivePath);
|
|
57178
|
+
const target = fs59.existsSync(resolved) && fs59.statSync(resolved).isDirectory() ? path80.join(resolved, suggestedFileName) : resolved;
|
|
57179
|
+
fs59.mkdirSync(path80.dirname(target), { recursive: true });
|
|
56649
57180
|
fs59.writeFileSync(target, Buffer.from(archive));
|
|
56650
57181
|
return target;
|
|
56651
57182
|
}
|
|
@@ -56791,14 +57322,15 @@ program.command("init").description("Initialize wairon and bootstrap the SDD Spe
|
|
|
56791
57322
|
await addPack(source, false, opts.yes);
|
|
56792
57323
|
}
|
|
56793
57324
|
});
|
|
56794
|
-
program.command("generate").description("Generate agent output files from the spec tree").option("--target <type>", "limit to a specific target (claude|gemini|custom)").option("--domain <id>", "limit to agents in a single domain").option("--domains <ids>", "limit to a comma-separated list of domain ids").option("--root", "only generate root-level agents").option("--no-recurse", "only
|
|
57325
|
+
program.command("generate").description("Generate agent output files from the spec tree").option("--target <type>", "limit to a specific target (claude|gemini|custom)").option("--domain <id>", "limit to agents in a single domain").option("--domains <ids>", "limit to a comma-separated list of domain ids").option("--root", "only generate root-level agents").option("--family", "also generate each member's own layer in its own root (generate never cascades otherwise)").option("--no-recurse", "accepted for one release: generating only this project's layer is now the default").option("--no-prune", "do not remove wairon-managed agent files that are no longer in the topology").option("--dry-run", "preview what would be generated without writing files").action(async (opts) => {
|
|
56795
57326
|
await runGenerate({
|
|
56796
57327
|
target: opts.target,
|
|
56797
57328
|
domain: opts.domain,
|
|
56798
57329
|
domains: opts.domains,
|
|
56799
57330
|
root: opts.root,
|
|
56800
|
-
//
|
|
56801
|
-
|
|
57331
|
+
// --family walks the members explicitly; --no-recurse is the default now
|
|
57332
|
+
// and is accepted for one release. commander maps --no-prune to opts.prune === false.
|
|
57333
|
+
family: opts.family === true,
|
|
56802
57334
|
prune: opts.prune,
|
|
56803
57335
|
dryRun: opts.dryRun
|
|
56804
57336
|
});
|
|
@@ -56811,16 +57343,17 @@ async function runLock2(options) {
|
|
|
56811
57343
|
}
|
|
56812
57344
|
const projectConfig = loadProjectConfig();
|
|
56813
57345
|
if (!projectConfig) throw new ProjectNotInitializedError();
|
|
57346
|
+
const captured = computeGateStateId();
|
|
56814
57347
|
logger.info("Analyzing and validating specifications in-memory...");
|
|
56815
57348
|
const dry = validateAsComplete({
|
|
56816
57349
|
rules: projectConfig.rules,
|
|
56817
57350
|
projectType: projectConfig.projectType,
|
|
56818
|
-
scopeSubsystem: options.subsystem
|
|
56819
|
-
recursive: options.recursive ?? true
|
|
57351
|
+
scopeSubsystem: options.subsystem
|
|
56820
57352
|
});
|
|
56821
|
-
const
|
|
57353
|
+
const design = designOnly(dry);
|
|
57354
|
+
const errors = design.issues.filter((i) => i.severity === "error");
|
|
56822
57355
|
if (errors.length > 0) {
|
|
56823
|
-
logger.header("Cannot lock \u2014 the
|
|
57356
|
+
logger.header("Cannot lock \u2014 the design does not validate as complete");
|
|
56824
57357
|
let errorCount = 0;
|
|
56825
57358
|
const MAX_PRINT = 100;
|
|
56826
57359
|
let skippedErrors = 0;
|
|
@@ -56840,7 +57373,7 @@ async function runLock2(options) {
|
|
|
56840
57373
|
process.exit(1);
|
|
56841
57374
|
}
|
|
56842
57375
|
logger.header("Lock SDD specs");
|
|
56843
|
-
const record2 = await runLock(options, dry);
|
|
57376
|
+
const record2 = await runLock(options, dry, captured);
|
|
56844
57377
|
if (!record2) {
|
|
56845
57378
|
logger.info("Cancelled. Nothing was changed.");
|
|
56846
57379
|
return;
|
|
@@ -56850,6 +57383,11 @@ async function runLock2(options) {
|
|
|
56850
57383
|
logger.blank();
|
|
56851
57384
|
logger.success("Specs locked and generated outputs reconciled.");
|
|
56852
57385
|
logger.info(`Lock record written (.wai/lock.json): stateId ${record2.stateId.algorithm}:${record2.stateId.digest} \u2014 status ${record2.status}.`);
|
|
57386
|
+
for (const [alias, pin2] of Object.entries(record2.members ?? {})) {
|
|
57387
|
+
logger.info(` member ${alias}: ${pin2.state}${pin2.subject ? ` (${pin2.subject.slice(0, 26)}\u2026)` : ""}`);
|
|
57388
|
+
}
|
|
57389
|
+
const code = record2.code;
|
|
57390
|
+
logger.info(code ? `code: ${code.errors} error(s), ${code.warnings} warning(s) recorded beside the claim (analyzer ${code.analyzer.validatorVersion}, grade ${code.analyzer.grade}) \u2014 CI enforces them, not the lock` : "code: no code analysis recorded");
|
|
56853
57391
|
logger.info(
|
|
56854
57392
|
"Live agent briefs (sdd_get_agent_brief / wairon-agent://) are composed per call and already current \u2014 no session restart needed."
|
|
56855
57393
|
);
|
|
@@ -57067,7 +57605,7 @@ async function runAgent(action, id) {
|
|
|
57067
57605
|
}
|
|
57068
57606
|
case "customize": {
|
|
57069
57607
|
const brief = composeAgentBrief(id);
|
|
57070
|
-
const guidancePath =
|
|
57608
|
+
const guidancePath = path81.join(AI_PATHS.root(), "agents", `${id}.md`);
|
|
57071
57609
|
const relPath = `.wai/agents/${id}.md`;
|
|
57072
57610
|
if (pathExists(guidancePath)) {
|
|
57073
57611
|
throw new GuidanceFileExistsError(relPath);
|