@wairon/cli 5.1.1-dev.26 → 5.1.1-dev.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1124 -552
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1122 -550
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1113,7 +1113,7 @@ var init_defaults = __esm({
|
|
|
1113
1113
|
copilot: ".github/prompts",
|
|
1114
1114
|
codex: ".codex/agents"
|
|
1115
1115
|
};
|
|
1116
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
1116
|
+
WAIRON_VERSION = "5.1.1-dev.27";
|
|
1117
1117
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
1118
1118
|
ARCHITECT_AGENT_ID = "agent-architect";
|
|
1119
1119
|
ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -2253,8 +2253,8 @@ function extractTypeIdentifiers(typeStr) {
|
|
|
2253
2253
|
cleaned = cleaned.replace(/(?<=\S)\s*\([^)]*\)\s*$/, "");
|
|
2254
2254
|
cleaned = cleaned.replace(/(?<=\S)\s+[-—:]\s+[a-z\s_-]+$/, "");
|
|
2255
2255
|
cleaned = cleaned.replace(/(["'`])(?:\\.|[^\\])*?\1/g, " ");
|
|
2256
|
-
const
|
|
2257
|
-
return
|
|
2256
|
+
const matches = cleaned.match(/[a-zA-Z0-9_-]+(?:::[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+)*/g) || [];
|
|
2257
|
+
return matches.filter((t) => {
|
|
2258
2258
|
if (!/[a-zA-Z0-9]/.test(t)) return false;
|
|
2259
2259
|
if (/^\d+$/.test(t)) return false;
|
|
2260
2260
|
return true;
|
|
@@ -8877,6 +8877,37 @@ function buildDependencyEdges(ctx) {
|
|
|
8877
8877
|
}
|
|
8878
8878
|
return { all, matrix };
|
|
8879
8879
|
}
|
|
8880
|
+
function buildSpecIds(ctx) {
|
|
8881
|
+
const out = [];
|
|
8882
|
+
for (const s of ctx.subsystems) out.push({ id: s.id, kind: "Subsystem" });
|
|
8883
|
+
for (const c of ctx.components) out.push({ id: c.id, kind: "Component" });
|
|
8884
|
+
for (const i of ctx.interfaces) out.push({ id: i.id, kind: "Interface" });
|
|
8885
|
+
for (const im of ctx.implementations) out.push({ id: im.id, kind: "Implementation" });
|
|
8886
|
+
for (const t of ctx.types) out.push({ id: t.id, kind: "Type" });
|
|
8887
|
+
return out;
|
|
8888
|
+
}
|
|
8889
|
+
function buildImplementationMethods(ctx) {
|
|
8890
|
+
const out = [];
|
|
8891
|
+
for (const impl of ctx.implementations) {
|
|
8892
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
8893
|
+
if (!contract) continue;
|
|
8894
|
+
const component = ctx.componentMap.get(contract.component);
|
|
8895
|
+
if (!component) continue;
|
|
8896
|
+
if (ctx.isInChainedSubproject(component.subsystem)) continue;
|
|
8897
|
+
const draftContext = ctx.isImplementationDraft(impl);
|
|
8898
|
+
for (const method of impl.methods) {
|
|
8899
|
+
const sourceFile = methodSourceFile(method, impl.sourcePath);
|
|
8900
|
+
out.push({
|
|
8901
|
+
implementation: impl,
|
|
8902
|
+
method,
|
|
8903
|
+
component,
|
|
8904
|
+
...sourceFile !== void 0 ? { sourceFile } : {},
|
|
8905
|
+
draftContext
|
|
8906
|
+
});
|
|
8907
|
+
}
|
|
8908
|
+
}
|
|
8909
|
+
return out;
|
|
8910
|
+
}
|
|
8880
8911
|
function offTreeEdge(ctx, from, ref, fromDraft) {
|
|
8881
8912
|
const base = { from, ref, retired: false, draftContext: fromDraft, licensed: false };
|
|
8882
8913
|
const external = ctx.isExternalNamespaceRef(ref);
|
|
@@ -8977,48 +9008,58 @@ var init_hierarchy_integrity = __esm({
|
|
|
8977
9008
|
}
|
|
8978
9009
|
});
|
|
8979
9010
|
|
|
8980
|
-
// src/core/rules/integrity/
|
|
8981
|
-
var
|
|
8982
|
-
var
|
|
8983
|
-
"src/core/rules/integrity/
|
|
9011
|
+
// src/core/rules/integrity/reserved-id-segments.ts
|
|
9012
|
+
var reservedIdSegmentsRule;
|
|
9013
|
+
var init_reserved_id_segments = __esm({
|
|
9014
|
+
"src/core/rules/integrity/reserved-id-segments.ts"() {
|
|
8984
9015
|
"use strict";
|
|
8985
|
-
|
|
8986
|
-
name: "
|
|
8987
|
-
description: '
|
|
9016
|
+
reservedIdSegmentsRule = {
|
|
9017
|
+
name: "reserved-id-segments",
|
|
9018
|
+
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.',
|
|
9019
|
+
codes: [
|
|
9020
|
+
{ code: "RESERVED_ID_SEGMENT", defaultSeverity: "error", summary: 'Id uses the reserved namespace keyword "super"' }
|
|
9021
|
+
],
|
|
9022
|
+
check(ctx) {
|
|
9023
|
+
for (const spec of ctx.specIds()) {
|
|
9024
|
+
if (!ctx.isSpecInScope(spec.id)) continue;
|
|
9025
|
+
if (!spec.id.split("::").includes("super")) continue;
|
|
9026
|
+
ctx.addIssue(
|
|
9027
|
+
"error",
|
|
9028
|
+
"RESERVED_ID_SEGMENT",
|
|
9029
|
+
`${spec.kind} id "${spec.id}" uses the reserved namespace keyword "super" \u2014 stored references to it would be consumed as a namespace hop and resolve to a different spec.`,
|
|
9030
|
+
spec.id
|
|
9031
|
+
);
|
|
9032
|
+
}
|
|
9033
|
+
}
|
|
9034
|
+
};
|
|
9035
|
+
}
|
|
9036
|
+
});
|
|
9037
|
+
|
|
9038
|
+
// src/core/rules/integrity/namespace-shadowing.ts
|
|
9039
|
+
var namespaceShadowingRule;
|
|
9040
|
+
var init_namespace_shadowing = __esm({
|
|
9041
|
+
"src/core/rules/integrity/namespace-shadowing.ts"() {
|
|
9042
|
+
"use strict";
|
|
9043
|
+
namespaceShadowingRule = {
|
|
9044
|
+
name: "namespace-shadowing",
|
|
9045
|
+
description: "A subproject-local name must not shadow a root-level subsystem id \u2014 inside its subproject a bare reference to the shadowed name anchors to the ROOT subsystem, so the local spec cannot be addressed reliably.",
|
|
8988
9046
|
codes: [
|
|
8989
|
-
{ code: "RESERVED_ID_SEGMENT", defaultSeverity: "error", summary: 'Id uses the reserved namespace keyword "super"' },
|
|
8990
9047
|
{ code: "NAMESPACE_SHADOWING", defaultSeverity: "error", summary: "Subproject-local id shadows a root subsystem id (bare references anchor to the root)" }
|
|
8991
9048
|
],
|
|
8992
9049
|
check(ctx) {
|
|
8993
9050
|
const rootSubs = new Set(ctx.subsystems.filter((s) => !s.id.includes("::")).map((s) => s.id));
|
|
8994
|
-
const
|
|
8995
|
-
if (!ctx.isSpecInScope(id))
|
|
8996
|
-
const segments = id.split("::");
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
const local = segments[segments.length - 1];
|
|
9007
|
-
if (rootSubs.has(local)) {
|
|
9008
|
-
ctx.addIssue(
|
|
9009
|
-
"error",
|
|
9010
|
-
"NAMESPACE_SHADOWING",
|
|
9011
|
-
`${kindLabel} "${id}" shadows root subsystem "${local}" \u2014 inside its subproject a bare reference to "${local}" anchors to the ROOT subsystem, so this spec cannot be addressed reliably. Rename one of them.`,
|
|
9012
|
-
id
|
|
9013
|
-
);
|
|
9014
|
-
}
|
|
9015
|
-
}
|
|
9016
|
-
};
|
|
9017
|
-
for (const s of ctx.subsystems) checkId(s.id, "Subsystem");
|
|
9018
|
-
for (const c of ctx.components) checkId(c.id, "Component");
|
|
9019
|
-
for (const i of ctx.interfaces) checkId(i.id, "Interface");
|
|
9020
|
-
for (const im of ctx.implementations) checkId(im.id, "Implementation");
|
|
9021
|
-
for (const t of ctx.types) checkId(t.id, "Type");
|
|
9051
|
+
for (const spec of ctx.specIds()) {
|
|
9052
|
+
if (!ctx.isSpecInScope(spec.id)) continue;
|
|
9053
|
+
const segments = spec.id.split("::");
|
|
9054
|
+
const local = segments[segments.length - 1];
|
|
9055
|
+
if (segments.length <= 1 || !rootSubs.has(local)) continue;
|
|
9056
|
+
ctx.addIssue(
|
|
9057
|
+
"error",
|
|
9058
|
+
"NAMESPACE_SHADOWING",
|
|
9059
|
+
`${spec.kind} "${spec.id}" shadows root subsystem "${local}" \u2014 inside its subproject a bare reference to "${local}" anchors to the ROOT subsystem, so this spec cannot be addressed reliably. Rename one of them.`,
|
|
9060
|
+
spec.id
|
|
9061
|
+
);
|
|
9062
|
+
}
|
|
9022
9063
|
}
|
|
9023
9064
|
};
|
|
9024
9065
|
}
|
|
@@ -9163,10 +9204,48 @@ var init_signature_type_references = __esm({
|
|
|
9163
9204
|
}
|
|
9164
9205
|
});
|
|
9165
9206
|
|
|
9166
|
-
// src/core/rules/integrity/public-surface.ts
|
|
9167
|
-
var
|
|
9168
|
-
var
|
|
9169
|
-
"src/core/rules/integrity/public-surface.ts"() {
|
|
9207
|
+
// src/core/rules/integrity/public-surface-binding.ts
|
|
9208
|
+
var publicSurfaceBindingRule;
|
|
9209
|
+
var init_public_surface_binding = __esm({
|
|
9210
|
+
"src/core/rules/integrity/public-surface-binding.ts"() {
|
|
9211
|
+
"use strict";
|
|
9212
|
+
init_models();
|
|
9213
|
+
publicSurfaceBindingRule = {
|
|
9214
|
+
name: "public-surface-binding",
|
|
9215
|
+
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.",
|
|
9216
|
+
codes: [
|
|
9217
|
+
{ code: "PUBLIC_INTERFACE_UNBOUND", defaultSeverity: "error", summary: "Public interface with no backing component" },
|
|
9218
|
+
{ code: "PUBLIC_INTERFACE_INVALID_COMPONENT", defaultSeverity: "error", summary: "Public interface references a non-existent component" },
|
|
9219
|
+
{ code: "PUBLIC_INTERFACE_FOREIGN_COMPONENT", defaultSeverity: "error", summary: "Subsystem publishing a component it does not own" }
|
|
9220
|
+
],
|
|
9221
|
+
check(ctx) {
|
|
9222
|
+
for (const sub of ctx.subsystems) {
|
|
9223
|
+
const isDraftCtx = isDraftSubsystem(sub);
|
|
9224
|
+
for (const pi of sub.publicInterfaces) {
|
|
9225
|
+
if (!pi.component) {
|
|
9226
|
+
ctx.addIssue("error", "PUBLIC_INTERFACE_UNBOUND", `Subsystem "${sub.id}" declares a ${pi.type} public interface with no backing component. Bind it to the component that realizes it (publicInterfaces[].component).`, sub.id, isDraftCtx);
|
|
9227
|
+
continue;
|
|
9228
|
+
}
|
|
9229
|
+
const backing = ctx.componentMap.get(pi.component);
|
|
9230
|
+
if (!backing) {
|
|
9231
|
+
ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_COMPONENT", `Subsystem "${sub.id}" public interface references component "${pi.component}" which does not exist.`, sub.id, isDraftCtx);
|
|
9232
|
+
continue;
|
|
9233
|
+
}
|
|
9234
|
+
const isSubsystemOwner = backing.subsystem === sub.id || backing.subsystem.startsWith(sub.id + "::");
|
|
9235
|
+
if (!isSubsystemOwner) {
|
|
9236
|
+
ctx.addIssue("error", "PUBLIC_INTERFACE_FOREIGN_COMPONENT", `Subsystem "${sub.id}" publishes component "${pi.component}", but it belongs to subsystem "${backing.subsystem}". A subsystem may only publish its own components.`, sub.id, isDraftCtx);
|
|
9237
|
+
}
|
|
9238
|
+
}
|
|
9239
|
+
}
|
|
9240
|
+
}
|
|
9241
|
+
};
|
|
9242
|
+
}
|
|
9243
|
+
});
|
|
9244
|
+
|
|
9245
|
+
// src/core/rules/integrity/public-surface-declared-type.ts
|
|
9246
|
+
var pubTypeMatches, EVENT_VOCAB, expectedFor, publicSurfaceDeclaredTypeRule;
|
|
9247
|
+
var init_public_surface_declared_type = __esm({
|
|
9248
|
+
"src/core/rules/integrity/public-surface-declared-type.ts"() {
|
|
9170
9249
|
"use strict";
|
|
9171
9250
|
init_models();
|
|
9172
9251
|
pubTypeMatches = (piType, ct, portalType) => {
|
|
@@ -9200,45 +9279,22 @@ var init_public_surface = __esm({
|
|
|
9200
9279
|
return "a compatible component";
|
|
9201
9280
|
}
|
|
9202
9281
|
};
|
|
9203
|
-
|
|
9204
|
-
name: "public-surface",
|
|
9205
|
-
description: "
|
|
9282
|
+
publicSurfaceDeclaredTypeRule = {
|
|
9283
|
+
name: "public-surface-declared-type",
|
|
9284
|
+
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.",
|
|
9206
9285
|
codes: [
|
|
9207
|
-
{ code: "PUBLIC_INTERFACE_UNBOUND", defaultSeverity: "error", summary: "Public interface with no backing component" },
|
|
9208
|
-
{ code: "PUBLIC_INTERFACE_INVALID_COMPONENT", defaultSeverity: "error", summary: "Public interface references a non-existent component" },
|
|
9209
|
-
{ code: "PUBLIC_INTERFACE_FOREIGN_COMPONENT", defaultSeverity: "error", summary: "Subsystem publishing a component it does not own" },
|
|
9210
9286
|
{ code: "PUBLIC_INTERFACE_TYPE_MISMATCH", defaultSeverity: "error", summary: "Backing component cannot realize the declared interface type" },
|
|
9211
|
-
{ code: "PUBLIC_INTERFACE_INVALID_INTERFACE", defaultSeverity: "error", summary: "Bound L3 interface missing or belonging to another component" },
|
|
9212
9287
|
{ code: "PUBLIC_INTERFACE_EVENT_MISTYPED", defaultSeverity: "warning", summary: "Custom interface describing eventing backed by a non-event component" }
|
|
9213
9288
|
],
|
|
9214
9289
|
check(ctx) {
|
|
9215
9290
|
for (const sub of ctx.subsystems) {
|
|
9216
9291
|
const isDraftCtx = isDraftSubsystem(sub);
|
|
9217
9292
|
for (const pi of sub.publicInterfaces) {
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
continue;
|
|
9221
|
-
}
|
|
9222
|
-
const backing = ctx.componentMap.get(pi.component);
|
|
9223
|
-
if (!backing) {
|
|
9224
|
-
ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_COMPONENT", `Subsystem "${sub.id}" public interface references component "${pi.component}" which does not exist.`, sub.id, isDraftCtx);
|
|
9225
|
-
continue;
|
|
9226
|
-
}
|
|
9227
|
-
const isSubsystemOwner = backing.subsystem === sub.id || backing.subsystem.startsWith(sub.id + "::");
|
|
9228
|
-
if (!isSubsystemOwner) {
|
|
9229
|
-
ctx.addIssue("error", "PUBLIC_INTERFACE_FOREIGN_COMPONENT", `Subsystem "${sub.id}" publishes component "${pi.component}", but it belongs to subsystem "${backing.subsystem}". A subsystem may only publish its own components.`, sub.id, isDraftCtx);
|
|
9230
|
-
}
|
|
9293
|
+
const backing = pi.component ? ctx.componentMap.get(pi.component) : void 0;
|
|
9294
|
+
if (!backing) continue;
|
|
9231
9295
|
if (!pubTypeMatches(pi.type, backing.componentType, backing.portalType)) {
|
|
9232
9296
|
ctx.addIssue("error", "PUBLIC_INTERFACE_TYPE_MISMATCH", `Subsystem "${sub.id}" declares a ${pi.type} public interface backed by "${pi.component}" (${backing.componentType}${backing.portalType ? `/${backing.portalType}` : ""}), which cannot realize ${pi.type}. Expected ${expectedFor(pi.type)}.`, sub.id, isDraftCtx);
|
|
9233
9297
|
}
|
|
9234
|
-
if (pi.interface) {
|
|
9235
|
-
const intf = ctx.interfaceMap.get(pi.interface);
|
|
9236
|
-
if (!intf) {
|
|
9237
|
-
ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" public interface references interface "${pi.interface}" which does not exist.`, sub.id, isDraftCtx);
|
|
9238
|
-
} else if (intf.component !== pi.component) {
|
|
9239
|
-
ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" binds interface "${pi.interface}" to component "${pi.component}", but that interface belongs to component "${intf.component}".`, sub.id, isDraftCtx);
|
|
9240
|
-
}
|
|
9241
|
-
}
|
|
9242
9298
|
if (pi.type === "Custom" && EVENT_VOCAB.test(pi.details)) {
|
|
9243
9299
|
const eventCapable = backing.componentType === "Observer" || backing.componentType === "Portal" && backing.portalType === "MessageBus";
|
|
9244
9300
|
if (!eventCapable) {
|
|
@@ -9252,6 +9308,37 @@ var init_public_surface = __esm({
|
|
|
9252
9308
|
}
|
|
9253
9309
|
});
|
|
9254
9310
|
|
|
9311
|
+
// src/core/rules/integrity/public-surface-bound-contract.ts
|
|
9312
|
+
var publicSurfaceBoundContractRule;
|
|
9313
|
+
var init_public_surface_bound_contract = __esm({
|
|
9314
|
+
"src/core/rules/integrity/public-surface-bound-contract.ts"() {
|
|
9315
|
+
"use strict";
|
|
9316
|
+
init_models();
|
|
9317
|
+
publicSurfaceBoundContractRule = {
|
|
9318
|
+
name: "public-surface-bound-contract",
|
|
9319
|
+
description: "A publicInterface entry that binds an L3 interface must bind one that exists and that belongs to the component the entry publishes.",
|
|
9320
|
+
codes: [
|
|
9321
|
+
{ code: "PUBLIC_INTERFACE_INVALID_INTERFACE", defaultSeverity: "error", summary: "Bound L3 interface missing or belonging to another component" }
|
|
9322
|
+
],
|
|
9323
|
+
check(ctx) {
|
|
9324
|
+
for (const sub of ctx.subsystems) {
|
|
9325
|
+
const isDraftCtx = isDraftSubsystem(sub);
|
|
9326
|
+
for (const pi of sub.publicInterfaces) {
|
|
9327
|
+
if (!pi.interface) continue;
|
|
9328
|
+
if (!pi.component || !ctx.componentMap.has(pi.component)) continue;
|
|
9329
|
+
const intf = ctx.interfaceMap.get(pi.interface);
|
|
9330
|
+
if (!intf) {
|
|
9331
|
+
ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" public interface references interface "${pi.interface}" which does not exist.`, sub.id, isDraftCtx);
|
|
9332
|
+
} else if (intf.component !== pi.component) {
|
|
9333
|
+
ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" binds interface "${pi.interface}" to component "${pi.component}", but that interface belongs to component "${intf.component}".`, sub.id, isDraftCtx);
|
|
9334
|
+
}
|
|
9335
|
+
}
|
|
9336
|
+
}
|
|
9337
|
+
}
|
|
9338
|
+
};
|
|
9339
|
+
}
|
|
9340
|
+
});
|
|
9341
|
+
|
|
9255
9342
|
// src/core/rules/integrity/lint-allows.ts
|
|
9256
9343
|
var lintAllowsRule;
|
|
9257
9344
|
var init_lint_allows = __esm({
|
|
@@ -9857,7 +9944,7 @@ var init_narrative_jump_edges = __esm({
|
|
|
9857
9944
|
}
|
|
9858
9945
|
});
|
|
9859
9946
|
|
|
9860
|
-
// src/core/rules/narrative/
|
|
9947
|
+
// src/core/rules/narrative/completed-step-graph.ts
|
|
9861
9948
|
function completedStepGraph(steps) {
|
|
9862
9949
|
const numbers = steps.flatMap((s) => [
|
|
9863
9950
|
s.stepNumber,
|
|
@@ -9887,6 +9974,14 @@ function isUnavoidable(steps, target) {
|
|
|
9887
9974
|
if (s.type === "return" || s.type === "throw") return true;
|
|
9888
9975
|
const arms = s.type === "parallel" ? (s.branches ?? []).map((b) => b.step).filter((e) => graph.stepAt(e) !== void 0) : [];
|
|
9889
9976
|
if (arms.length) return arms.every((e) => completes.has(e));
|
|
9977
|
+
const guaranteed = s.endStep !== void 0 && (s.type === "try" || s.type === "loop" && s.loopKind === "doWhile");
|
|
9978
|
+
if (guaranteed) {
|
|
9979
|
+
const bodyEntry = graph.nextOf(n);
|
|
9980
|
+
if (bodyEntry !== void 0 && bodyEntry <= s.endStep) {
|
|
9981
|
+
if (s.type === "loop") return completes.has(bodyEntry);
|
|
9982
|
+
if (bodyEntry === target) return false;
|
|
9983
|
+
}
|
|
9984
|
+
}
|
|
9890
9985
|
return graph.successorsOf(n).some((t) => completes.has(t));
|
|
9891
9986
|
};
|
|
9892
9987
|
const order = [...graph.stepNumbers].reverse();
|
|
@@ -9950,25 +10045,27 @@ function stronglyConnected(nums, successorsOf) {
|
|
|
9950
10045
|
}
|
|
9951
10046
|
return sccs;
|
|
9952
10047
|
}
|
|
9953
|
-
var
|
|
9954
|
-
|
|
9955
|
-
"src/core/rules/narrative/narrative-antipatterns.ts"() {
|
|
10048
|
+
var init_completed_step_graph = __esm({
|
|
10049
|
+
"src/core/rules/narrative/completed-step-graph.ts"() {
|
|
9956
10050
|
"use strict";
|
|
9957
10051
|
init_models();
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
10052
|
+
}
|
|
10053
|
+
});
|
|
10054
|
+
|
|
10055
|
+
// src/core/rules/narrative/meaningless-branches.ts
|
|
10056
|
+
var meaninglessBranchesRule;
|
|
10057
|
+
var init_meaningless_branches = __esm({
|
|
10058
|
+
"src/core/rules/narrative/meaningless-branches.ts"() {
|
|
10059
|
+
"use strict";
|
|
10060
|
+
init_completed_step_graph();
|
|
10061
|
+
meaninglessBranchesRule = {
|
|
10062
|
+
name: "meaningless-branches",
|
|
10063
|
+
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.",
|
|
9962
10064
|
codes: [
|
|
9963
|
-
{ code: "
|
|
9964
|
-
{ code: "MEANINGLESS_BRANCH", defaultSeverity: "warning", summary: "Branch/switch whose arms all target the same step \u2014 the decision changes nothing" },
|
|
9965
|
-
{ code: "UNCONDITIONAL_CALL_CYCLE", defaultSeverity: "warning", summary: "Cross-component call cycle in which every call edge is unavoidable \u2014 unbounded recursion by construction" }
|
|
10065
|
+
{ code: "MEANINGLESS_BRANCH", defaultSeverity: "warning", summary: "Branch/switch whose arms all target the same step \u2014 the decision changes nothing" }
|
|
9966
10066
|
],
|
|
9967
10067
|
check(ctx) {
|
|
9968
|
-
const callEdges = [];
|
|
9969
10068
|
for (const impl of ctx.implementations) {
|
|
9970
|
-
const contract = ctx.interfaceMap.get(impl.contract);
|
|
9971
|
-
const component = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
9972
10069
|
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
9973
10070
|
for (const implMethod of impl.methods) {
|
|
9974
10071
|
const steps = implMethod.narrative;
|
|
@@ -10007,6 +10104,33 @@ var init_narrative_antipatterns = __esm({
|
|
|
10007
10104
|
}
|
|
10008
10105
|
}
|
|
10009
10106
|
}
|
|
10107
|
+
}
|
|
10108
|
+
}
|
|
10109
|
+
}
|
|
10110
|
+
};
|
|
10111
|
+
}
|
|
10112
|
+
});
|
|
10113
|
+
|
|
10114
|
+
// src/core/rules/narrative/inescapable-cycles.ts
|
|
10115
|
+
var inescapableCyclesRule;
|
|
10116
|
+
var init_inescapable_cycles = __esm({
|
|
10117
|
+
"src/core/rules/narrative/inescapable-cycles.ts"() {
|
|
10118
|
+
"use strict";
|
|
10119
|
+
init_completed_step_graph();
|
|
10120
|
+
inescapableCyclesRule = {
|
|
10121
|
+
name: "inescapable-cycles",
|
|
10122
|
+
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.",
|
|
10123
|
+
codes: [
|
|
10124
|
+
{ code: "INESCAPABLE_CYCLE", defaultSeverity: "warning", summary: "Step cycle with no exit edge and no return/throw member \u2014 never terminates by construction" }
|
|
10125
|
+
],
|
|
10126
|
+
check(ctx) {
|
|
10127
|
+
for (const impl of ctx.implementations) {
|
|
10128
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
10129
|
+
for (const implMethod of impl.methods) {
|
|
10130
|
+
const steps = implMethod.narrative;
|
|
10131
|
+
if (!steps.length) continue;
|
|
10132
|
+
const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
|
|
10133
|
+
const graph = completedStepGraph(steps);
|
|
10010
10134
|
for (const scc of stronglyConnected(graph.stepNumbers, graph.successorsOf)) {
|
|
10011
10135
|
const inScc = new Set(scc);
|
|
10012
10136
|
const isCycle = scc.length > 1 || graph.successorsOf(scc[0]).includes(scc[0]);
|
|
@@ -10027,7 +10151,35 @@ var init_narrative_antipatterns = __esm({
|
|
|
10027
10151
|
);
|
|
10028
10152
|
}
|
|
10029
10153
|
}
|
|
10030
|
-
|
|
10154
|
+
}
|
|
10155
|
+
}
|
|
10156
|
+
}
|
|
10157
|
+
};
|
|
10158
|
+
}
|
|
10159
|
+
});
|
|
10160
|
+
|
|
10161
|
+
// src/core/rules/narrative/unconditional-call-cycles.ts
|
|
10162
|
+
var byCodeUnit, unconditionalCallCyclesRule;
|
|
10163
|
+
var init_unconditional_call_cycles = __esm({
|
|
10164
|
+
"src/core/rules/narrative/unconditional-call-cycles.ts"() {
|
|
10165
|
+
"use strict";
|
|
10166
|
+
init_completed_step_graph();
|
|
10167
|
+
byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
10168
|
+
unconditionalCallCyclesRule = {
|
|
10169
|
+
name: "unconditional-call-cycles",
|
|
10170
|
+
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.",
|
|
10171
|
+
codes: [
|
|
10172
|
+
{ code: "UNCONDITIONAL_CALL_CYCLE", defaultSeverity: "warning", summary: "Cross-component call cycle in which every call edge is unavoidable \u2014 unbounded recursion by construction" }
|
|
10173
|
+
],
|
|
10174
|
+
check(ctx) {
|
|
10175
|
+
const callEdges = [];
|
|
10176
|
+
for (const impl of ctx.implementations) {
|
|
10177
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
10178
|
+
const component = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
10179
|
+
if (!component) continue;
|
|
10180
|
+
for (const implMethod of impl.methods) {
|
|
10181
|
+
const steps = implMethod.narrative;
|
|
10182
|
+
if (!steps.length) continue;
|
|
10031
10183
|
const fromKey = `${component.id}::${implMethod.name}`;
|
|
10032
10184
|
for (const s of steps) {
|
|
10033
10185
|
let toComponent;
|
|
@@ -10089,34 +10241,29 @@ var init_narrative_antipatterns = __esm({
|
|
|
10089
10241
|
});
|
|
10090
10242
|
|
|
10091
10243
|
// src/core/rules/narrative/narrative-detail.ts
|
|
10092
|
-
var
|
|
10244
|
+
var narrativeDetailRule;
|
|
10093
10245
|
var init_narrative_detail = __esm({
|
|
10094
10246
|
"src/core/rules/narrative/narrative-detail.ts"() {
|
|
10095
10247
|
"use strict";
|
|
10096
10248
|
init_models();
|
|
10097
|
-
DEFAULT_MAX_UNNARRATED_COMPLEXITY = 8;
|
|
10098
10249
|
narrativeDetailRule = {
|
|
10099
10250
|
name: "narrative-detail",
|
|
10100
|
-
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.
|
|
10251
|
+
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.",
|
|
10101
10252
|
codes: [
|
|
10102
10253
|
{ code: "MISSING_NARRATIVE", defaultSeverity: "warning", summary: "Method resolved to detail: full but has no narrative (error when the level was declared explicitly)" },
|
|
10103
|
-
{ code: "INTENT_FLOOR", defaultSeverity: "warning", summary: "Intent-level method whose intent/description prose is missing or placeholder-thin (error when declared explicitly)" }
|
|
10104
|
-
{ 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)" },
|
|
10105
|
-
{ code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
|
|
10254
|
+
{ code: "INTENT_FLOOR", defaultSeverity: "warning", summary: "Intent-level method whose intent/description prose is missing or placeholder-thin (error when declared explicitly)" }
|
|
10106
10255
|
],
|
|
10107
10256
|
check(ctx) {
|
|
10108
|
-
const code = ctx.codeIndex();
|
|
10109
10257
|
for (const impl of ctx.implementations) {
|
|
10110
10258
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
10111
10259
|
if (!contract) continue;
|
|
10112
10260
|
const component = ctx.componentMap.get(contract.component);
|
|
10113
|
-
const isDraftCtx =
|
|
10261
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
10114
10262
|
for (const implMethod of impl.methods) {
|
|
10115
10263
|
const contractMethod = contract.methods.find((m) => m.name === implMethod.name);
|
|
10116
10264
|
if (!contractMethod) continue;
|
|
10265
|
+
if (implMethod.narrative.length > 0) continue;
|
|
10117
10266
|
const eff = effectiveDetail(implMethod, impl, component);
|
|
10118
|
-
const hasNarrative = implMethod.narrative.length > 0;
|
|
10119
|
-
if (hasNarrative) continue;
|
|
10120
10267
|
if (eff.level === "full") {
|
|
10121
10268
|
ctx.addIssue(
|
|
10122
10269
|
eff.explicit ? "error" : "warning",
|
|
@@ -10127,34 +10274,6 @@ var init_narrative_detail = __esm({
|
|
|
10127
10274
|
);
|
|
10128
10275
|
continue;
|
|
10129
10276
|
}
|
|
10130
|
-
let complexityFired = false;
|
|
10131
|
-
const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
|
|
10132
|
-
const file = methodSourceFile(implMethod, impl.sourcePath);
|
|
10133
|
-
if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
|
|
10134
|
-
const facts = code.factsAt(file);
|
|
10135
|
-
const symbol = implMethod.symbol ?? implMethod.name;
|
|
10136
|
-
const complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
|
|
10137
|
-
const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
|
|
10138
|
-
if (complexity !== void 0 && complexity > limit) {
|
|
10139
|
-
complexityFired = true;
|
|
10140
|
-
ctx.addIssue(
|
|
10141
|
-
"warning",
|
|
10142
|
-
"UNNARRATED_COMPLEXITY",
|
|
10143
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" sits at detail: ${eff.level} with no narrative, but its realized function "${symbol}" in "${file}" measures cyclomatic complexity ${complexity} (limit ${limit}) \u2014 real branching is hiding behind ${eff.level}. Write the narrative, or keep the dial with a lint.allow stating why the branching needs no choreography.`,
|
|
10144
|
-
impl.id,
|
|
10145
|
-
isDraftCtx
|
|
10146
|
-
);
|
|
10147
|
-
}
|
|
10148
|
-
}
|
|
10149
|
-
if (!complexityFired && eff.explicit && component && isLogic(component)) {
|
|
10150
|
-
ctx.addIssue(
|
|
10151
|
-
"warning",
|
|
10152
|
-
"DETAIL_BELOW_STEREOTYPE",
|
|
10153
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" is explicitly dialed to detail: ${eff.level}, below the full narrative floor of its ${component.componentType} stereotype, and has no narrative. Logic behavior belongs in a narrative \u2014 write one, or keep the dial with a lint.allow stating why.`,
|
|
10154
|
-
impl.id,
|
|
10155
|
-
isDraftCtx
|
|
10156
|
-
);
|
|
10157
|
-
}
|
|
10158
10277
|
const prose = implMethod.intent ?? contractMethod.description;
|
|
10159
10278
|
if (!passesIntentFloor(prose, implMethod.name)) {
|
|
10160
10279
|
ctx.addIssue(
|
|
@@ -10172,6 +10291,71 @@ var init_narrative_detail = __esm({
|
|
|
10172
10291
|
}
|
|
10173
10292
|
});
|
|
10174
10293
|
|
|
10294
|
+
// src/core/rules/narrative/detail-sufficiency.ts
|
|
10295
|
+
var DEFAULT_MAX_UNNARRATED_COMPLEXITY, detailSufficiencyRule;
|
|
10296
|
+
var init_detail_sufficiency = __esm({
|
|
10297
|
+
"src/core/rules/narrative/detail-sufficiency.ts"() {
|
|
10298
|
+
"use strict";
|
|
10299
|
+
init_models();
|
|
10300
|
+
DEFAULT_MAX_UNNARRATED_COMPLEXITY = 8;
|
|
10301
|
+
detailSufficiencyRule = {
|
|
10302
|
+
name: "detail-sufficiency",
|
|
10303
|
+
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.",
|
|
10304
|
+
codes: [
|
|
10305
|
+
{ 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)" },
|
|
10306
|
+
{ code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
|
|
10307
|
+
],
|
|
10308
|
+
check(ctx) {
|
|
10309
|
+
const code = ctx.codeIndex();
|
|
10310
|
+
const dialedDown = [];
|
|
10311
|
+
for (const impl of ctx.implementations) {
|
|
10312
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
10313
|
+
if (!contract) continue;
|
|
10314
|
+
const component = ctx.componentMap.get(contract.component);
|
|
10315
|
+
const draftContext = ctx.isImplementationDraft(impl);
|
|
10316
|
+
for (const method of impl.methods) {
|
|
10317
|
+
const detail = effectiveDetail(method, impl, component);
|
|
10318
|
+
if (contract.methods.some((m) => m.name === method.name) && method.narrative.length === 0 && detail.level !== "full") {
|
|
10319
|
+
dialedDown.push({ implementation: impl, method, component, detail, draftContext });
|
|
10320
|
+
}
|
|
10321
|
+
}
|
|
10322
|
+
}
|
|
10323
|
+
for (const entry of dialedDown) {
|
|
10324
|
+
const { implementation: impl, method, component, detail } = entry;
|
|
10325
|
+
const tier = method.conformance ?? impl.conformance ?? defaultConformanceTier(component);
|
|
10326
|
+
const file = methodSourceFile(method, impl.sourcePath);
|
|
10327
|
+
const symbol = method.symbol ?? method.name;
|
|
10328
|
+
const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
|
|
10329
|
+
let complexity;
|
|
10330
|
+
if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
|
|
10331
|
+
const facts = code.factsAt(file);
|
|
10332
|
+
complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
|
|
10333
|
+
}
|
|
10334
|
+
if (complexity !== void 0 && complexity > limit) {
|
|
10335
|
+
ctx.addIssue(
|
|
10336
|
+
"warning",
|
|
10337
|
+
"UNNARRATED_COMPLEXITY",
|
|
10338
|
+
`Method "${method.name}" in implementation "${impl.id}" sits at detail: ${detail.level} with no narrative, but its realized function "${symbol}" in "${file}" measures cyclomatic complexity ${complexity} (limit ${limit}) \u2014 real branching is hiding behind ${detail.level}. Write the narrative, or keep the dial with a lint.allow stating why the branching needs no choreography.`,
|
|
10339
|
+
impl.id,
|
|
10340
|
+
entry.draftContext
|
|
10341
|
+
);
|
|
10342
|
+
continue;
|
|
10343
|
+
}
|
|
10344
|
+
if (detail.explicit && component && isLogic(component)) {
|
|
10345
|
+
ctx.addIssue(
|
|
10346
|
+
"warning",
|
|
10347
|
+
"DETAIL_BELOW_STEREOTYPE",
|
|
10348
|
+
`Method "${method.name}" in implementation "${impl.id}" is explicitly dialed to detail: ${detail.level}, below the full narrative floor of its ${component.componentType} stereotype, and has no narrative. Logic behavior belongs in a narrative \u2014 write one, or keep the dial with a lint.allow stating why.`,
|
|
10349
|
+
impl.id,
|
|
10350
|
+
entry.draftContext
|
|
10351
|
+
);
|
|
10352
|
+
}
|
|
10353
|
+
}
|
|
10354
|
+
}
|
|
10355
|
+
};
|
|
10356
|
+
}
|
|
10357
|
+
});
|
|
10358
|
+
|
|
10175
10359
|
// src/core/rules/intrinsic/portal-fields.ts
|
|
10176
10360
|
var portalFieldsRule;
|
|
10177
10361
|
var init_portal_fields = __esm({
|
|
@@ -10356,45 +10540,61 @@ var init_portal_endpoints = __esm({
|
|
|
10356
10540
|
};
|
|
10357
10541
|
portalsRule = {
|
|
10358
10542
|
name: "portal-endpoints",
|
|
10359
|
-
description: "A Portal binds every interface method to a concrete endpoint of the
|
|
10543
|
+
description: "A Portal binds every interface method to a concrete endpoint of the transport its portalType expects.",
|
|
10360
10544
|
codes: [
|
|
10361
10545
|
{ code: "MISSING_ENDPOINT", defaultSeverity: "error", summary: "Portal method without a wire endpoint binding" },
|
|
10362
|
-
{ code: "ENDPOINT_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "Endpoint transport does not match the Portal portalType" }
|
|
10363
|
-
{ code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
|
|
10546
|
+
{ code: "ENDPOINT_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "Endpoint transport does not match the Portal portalType" }
|
|
10364
10547
|
],
|
|
10365
10548
|
check(ctx) {
|
|
10366
10549
|
for (const comp of ctx.components) {
|
|
10550
|
+
if (comp.componentType !== "Portal") continue;
|
|
10551
|
+
const expected = comp.portalType ? PORTAL_TRANSPORT[comp.portalType] : void 0;
|
|
10552
|
+
if (!expected) continue;
|
|
10367
10553
|
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
10368
|
-
const
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
)
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
"ENDPOINT_TRANSPORT_MISMATCH",
|
|
10388
|
-
`Method "${m.name}" on interface "${intf.id}" declares a "${m.endpoint.transport}" endpoint, but its Portal "${comp.id}" is portalType "${comp.portalType}" (expects transport "${expected}").`,
|
|
10389
|
-
intf.id,
|
|
10390
|
-
isDraftCtx || isIntfDraft
|
|
10391
|
-
);
|
|
10392
|
-
}
|
|
10554
|
+
for (const intf of ctx.interfaces.filter((i) => i.component === comp.id)) {
|
|
10555
|
+
const isIntfDraft = intf.status === "draft" || intf.status === "design";
|
|
10556
|
+
for (const m of intf.methods) {
|
|
10557
|
+
if (!m.endpoint) {
|
|
10558
|
+
ctx.addIssue(
|
|
10559
|
+
"error",
|
|
10560
|
+
"MISSING_ENDPOINT",
|
|
10561
|
+
`Method "${m.name}" on interface "${intf.id}" (Portal ${comp.portalType}) is missing an "endpoint" mapping. Bind it with sdd_set_endpoints (transport "${expected}").`,
|
|
10562
|
+
intf.id,
|
|
10563
|
+
isDraftCtx || isIntfDraft
|
|
10564
|
+
);
|
|
10565
|
+
} else if (m.endpoint.transport !== expected) {
|
|
10566
|
+
ctx.addIssue(
|
|
10567
|
+
"error",
|
|
10568
|
+
"ENDPOINT_TRANSPORT_MISMATCH",
|
|
10569
|
+
`Method "${m.name}" on interface "${intf.id}" declares a "${m.endpoint.transport}" endpoint, but its Portal "${comp.id}" is portalType "${comp.portalType}" (expects transport "${expected}").`,
|
|
10570
|
+
intf.id,
|
|
10571
|
+
isDraftCtx || isIntfDraft
|
|
10572
|
+
);
|
|
10393
10573
|
}
|
|
10394
10574
|
}
|
|
10395
|
-
continue;
|
|
10396
10575
|
}
|
|
10397
|
-
|
|
10576
|
+
}
|
|
10577
|
+
}
|
|
10578
|
+
};
|
|
10579
|
+
}
|
|
10580
|
+
});
|
|
10581
|
+
|
|
10582
|
+
// src/core/rules/doctrine/non-portal-endpoints.ts
|
|
10583
|
+
var nonPortalEndpointsRule;
|
|
10584
|
+
var init_non_portal_endpoints = __esm({
|
|
10585
|
+
"src/core/rules/doctrine/non-portal-endpoints.ts"() {
|
|
10586
|
+
"use strict";
|
|
10587
|
+
nonPortalEndpointsRule = {
|
|
10588
|
+
name: "non-portal-endpoints",
|
|
10589
|
+
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.",
|
|
10590
|
+
codes: [
|
|
10591
|
+
{ code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
|
|
10592
|
+
],
|
|
10593
|
+
check(ctx) {
|
|
10594
|
+
for (const comp of ctx.components) {
|
|
10595
|
+
if (comp.componentType === "Portal") continue;
|
|
10596
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
10597
|
+
for (const intf of ctx.interfaces.filter((i) => i.component === comp.id)) {
|
|
10398
10598
|
const isIntfDraft = intf.status === "draft" || intf.status === "design";
|
|
10399
10599
|
for (const m of intf.methods) {
|
|
10400
10600
|
if (m.endpoint) {
|
|
@@ -10415,20 +10615,16 @@ var init_portal_endpoints = __esm({
|
|
|
10415
10615
|
});
|
|
10416
10616
|
|
|
10417
10617
|
// src/core/rules/doctrine/portal-call-auth.ts
|
|
10418
|
-
var
|
|
10618
|
+
var portalCallAuthRule;
|
|
10419
10619
|
var init_portal_call_auth = __esm({
|
|
10420
10620
|
"src/core/rules/doctrine/portal-call-auth.ts"() {
|
|
10421
10621
|
"use strict";
|
|
10422
|
-
COMPONENT_REF_PREFIX = "component:";
|
|
10423
10622
|
portalCallAuthRule = {
|
|
10424
10623
|
name: "portal-call-auth",
|
|
10425
|
-
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.
|
|
10624
|
+
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.",
|
|
10426
10625
|
codes: [
|
|
10427
10626
|
{ code: "PORTAL_AUTH_UNMET", defaultSeverity: "warning", summary: "A narrative call into another component's authed Portal does not declare where its credential loads from" },
|
|
10428
|
-
{ code: "AUTH_PRESENTER_NOT_ADAPTER", defaultSeverity: "warning", summary: "A non-Adapter component authenticates an outbound call to a portal (external I/O must go through an Adapter)" }
|
|
10429
|
-
{ code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
|
|
10430
|
-
{ code: "AUTH_SOURCE_NOT_PROVIDER", defaultSeverity: "warning", summary: "auth.from references a component that is not an Adapter or Store" },
|
|
10431
|
-
{ code: "AUTH_SOURCE_UNWIRED", defaultSeverity: "warning", summary: "The presenter declares a component: credential source it does not depend on or own" }
|
|
10627
|
+
{ code: "AUTH_PRESENTER_NOT_ADAPTER", defaultSeverity: "warning", summary: "A non-Adapter component authenticates an outbound call to a portal (external I/O must go through an Adapter)" }
|
|
10432
10628
|
],
|
|
10433
10629
|
check(ctx) {
|
|
10434
10630
|
for (const impl of ctx.implementations) {
|
|
@@ -10451,8 +10647,7 @@ var init_portal_call_auth = __esm({
|
|
|
10451
10647
|
draft
|
|
10452
10648
|
);
|
|
10453
10649
|
}
|
|
10454
|
-
|
|
10455
|
-
if (!from) {
|
|
10650
|
+
if (!step.auth?.from) {
|
|
10456
10651
|
ctx.addIssue(
|
|
10457
10652
|
"warning",
|
|
10458
10653
|
"PORTAL_AUTH_UNMET",
|
|
@@ -10460,40 +10655,73 @@ var init_portal_call_auth = __esm({
|
|
|
10460
10655
|
impl.id,
|
|
10461
10656
|
draft
|
|
10462
10657
|
);
|
|
10463
|
-
continue;
|
|
10464
10658
|
}
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10659
|
+
}
|
|
10660
|
+
}
|
|
10661
|
+
}
|
|
10662
|
+
}
|
|
10663
|
+
};
|
|
10664
|
+
}
|
|
10665
|
+
});
|
|
10666
|
+
|
|
10667
|
+
// src/core/rules/doctrine/auth-source-wiring.ts
|
|
10668
|
+
var COMPONENT_REF_PREFIX, authSourceWiringRule;
|
|
10669
|
+
var init_auth_source_wiring = __esm({
|
|
10670
|
+
"src/core/rules/doctrine/auth-source-wiring.ts"() {
|
|
10671
|
+
"use strict";
|
|
10672
|
+
COMPONENT_REF_PREFIX = "component:";
|
|
10673
|
+
authSourceWiringRule = {
|
|
10674
|
+
name: "auth-source-wiring",
|
|
10675
|
+
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.",
|
|
10676
|
+
codes: [
|
|
10677
|
+
{ code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
|
|
10678
|
+
{ code: "AUTH_SOURCE_NOT_PROVIDER", defaultSeverity: "warning", summary: "auth.from references a component that is not an Adapter or Store" },
|
|
10679
|
+
{ code: "AUTH_SOURCE_UNWIRED", defaultSeverity: "warning", summary: "The presenter declares a component: credential source it does not depend on or own" }
|
|
10680
|
+
],
|
|
10681
|
+
check(ctx) {
|
|
10682
|
+
for (const impl of ctx.implementations) {
|
|
10683
|
+
const ownComponent = ctx.interfaceMap.get(impl.contract)?.component;
|
|
10684
|
+
const presenter = ownComponent ? ctx.componentMap.get(ownComponent) : void 0;
|
|
10685
|
+
const draft = ctx.isImplementationDraft(impl);
|
|
10686
|
+
for (const method of impl.methods ?? []) {
|
|
10687
|
+
for (const step of method.narrative ?? []) {
|
|
10688
|
+
if (step.type !== "call") continue;
|
|
10689
|
+
if (!step.targetComponent || step.targetComponent === ownComponent) continue;
|
|
10690
|
+
const target = ctx.componentMap.get(step.targetComponent);
|
|
10691
|
+
if (!target || target.componentType !== "Portal") continue;
|
|
10692
|
+
if (!target.auth || target.auth.scheme === "none") continue;
|
|
10693
|
+
const from = step.auth?.from;
|
|
10694
|
+
if (!from || !from.startsWith(COMPONENT_REF_PREFIX)) continue;
|
|
10695
|
+
const srcId = from.slice(COMPONENT_REF_PREFIX.length);
|
|
10696
|
+
const src = ctx.componentMap.get(srcId);
|
|
10697
|
+
if (!src) {
|
|
10698
|
+
ctx.addIssue(
|
|
10699
|
+
"warning",
|
|
10700
|
+
"UNKNOWN_AUTH_SOURCE",
|
|
10701
|
+
`Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": auth.from references component "${srcId}", which does not exist.`,
|
|
10702
|
+
impl.id,
|
|
10703
|
+
draft
|
|
10704
|
+
);
|
|
10705
|
+
continue;
|
|
10706
|
+
}
|
|
10707
|
+
if (src.componentType !== "Adapter" && src.componentType !== "Store") {
|
|
10708
|
+
ctx.addIssue(
|
|
10709
|
+
"warning",
|
|
10710
|
+
"AUTH_SOURCE_NOT_PROVIDER",
|
|
10711
|
+
`Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": auth.from references "${srcId}" (${src.componentType}); a credential source must be an Adapter (loads the secret via external I/O) or a Store (holds it).`,
|
|
10712
|
+
impl.id,
|
|
10713
|
+
draft
|
|
10714
|
+
);
|
|
10715
|
+
}
|
|
10716
|
+
const wired = (presenter?.dependsOn ?? []).includes(srcId) || (presenter?.owns ?? []).includes(srcId);
|
|
10717
|
+
if (presenter && !wired) {
|
|
10718
|
+
ctx.addIssue(
|
|
10719
|
+
"warning",
|
|
10720
|
+
"AUTH_SOURCE_UNWIRED",
|
|
10721
|
+
`Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": "${presenter.id}" loads its credential from "${srcId}" but neither depends on nor owns it \u2014 declare the dependsOn edge so the credential wiring is real.`,
|
|
10722
|
+
impl.id,
|
|
10723
|
+
draft
|
|
10724
|
+
);
|
|
10497
10725
|
}
|
|
10498
10726
|
}
|
|
10499
10727
|
}
|
|
@@ -11369,17 +11597,135 @@ var init_component_variants = __esm({
|
|
|
11369
11597
|
}
|
|
11370
11598
|
});
|
|
11371
11599
|
|
|
11372
|
-
// src/core/rules/extension/
|
|
11600
|
+
// src/core/rules/extension/declared-assertion.ts
|
|
11601
|
+
function assertionsOfKind(ctx, kind) {
|
|
11602
|
+
return ctx.ext.assertions.filter(
|
|
11603
|
+
(a) => a.kind === kind
|
|
11604
|
+
);
|
|
11605
|
+
}
|
|
11373
11606
|
function globToRegExp(glob) {
|
|
11374
11607
|
const escaped = glob.replace(/[.*+?^${}()|[\]\\]/g, (ch) => ch === "*" ? ".*" : `\\${ch}`);
|
|
11375
11608
|
return new RegExp(`^${escaped}$`);
|
|
11376
11609
|
}
|
|
11377
|
-
function
|
|
11610
|
+
function matchesSelector(sel, comp, ctx) {
|
|
11378
11611
|
if (sel.componentType?.length && !sel.componentType.includes(comp.componentType)) return false;
|
|
11379
11612
|
if (sel.profile?.length && !sel.profile.includes(ctx.getComponentProfile(comp.id))) return false;
|
|
11380
11613
|
if (sel.id && !globToRegExp(sel.id).test(comp.id)) return false;
|
|
11381
11614
|
return true;
|
|
11382
11615
|
}
|
|
11616
|
+
function reportAssertion(ctx, a, message, specId, isDraftCtx) {
|
|
11617
|
+
const severity = isDraftCtx && a.severity === "error" ? "warning" : a.severity;
|
|
11618
|
+
ctx.addIssue(severity, a.fullCode, `${message} [pack "${a.pack}"]: ${a.reason}`, specId, isDraftCtx);
|
|
11619
|
+
}
|
|
11620
|
+
var init_declared_assertion = __esm({
|
|
11621
|
+
"src/core/rules/extension/declared-assertion.ts"() {
|
|
11622
|
+
"use strict";
|
|
11623
|
+
}
|
|
11624
|
+
});
|
|
11625
|
+
|
|
11626
|
+
// src/core/rules/extension/assertion-forbidden-edges.ts
|
|
11627
|
+
var assertionForbiddenEdgesRule;
|
|
11628
|
+
var init_assertion_forbidden_edges = __esm({
|
|
11629
|
+
"src/core/rules/extension/assertion-forbidden-edges.ts"() {
|
|
11630
|
+
"use strict";
|
|
11631
|
+
init_declared_assertion();
|
|
11632
|
+
assertionForbiddenEdgesRule = {
|
|
11633
|
+
name: "assertion-forbidden-edges",
|
|
11634
|
+
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.",
|
|
11635
|
+
// Static codes are unknown here — packs bring their own. The validator
|
|
11636
|
+
// gathers every loaded assertion's fullCode into knownIssueCodes.
|
|
11637
|
+
codes: [],
|
|
11638
|
+
check(ctx) {
|
|
11639
|
+
for (const a of assertionsOfKind(ctx, "forbid-edge")) {
|
|
11640
|
+
for (const comp of ctx.components) {
|
|
11641
|
+
if (!matchesSelector(a.from, comp, ctx)) continue;
|
|
11642
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
11643
|
+
const edges = a.relation.flatMap(
|
|
11644
|
+
(relation) => comp[relation].map((targetId) => ({ relation, targetId }))
|
|
11645
|
+
);
|
|
11646
|
+
for (const { relation, targetId } of edges) {
|
|
11647
|
+
const target = ctx.componentMap.get(targetId);
|
|
11648
|
+
if (!target || !matchesSelector(a.to, target, ctx)) continue;
|
|
11649
|
+
reportAssertion(
|
|
11650
|
+
ctx,
|
|
11651
|
+
a,
|
|
11652
|
+
`Component "${comp.id}" (${comp.componentType}) ${relation === "owns" ? "owns" : "depends on"} "${target.id}" (${target.componentType}), forbidden by assertion ${a.code}`,
|
|
11653
|
+
comp.id,
|
|
11654
|
+
isDraftCtx
|
|
11655
|
+
);
|
|
11656
|
+
}
|
|
11657
|
+
}
|
|
11658
|
+
}
|
|
11659
|
+
}
|
|
11660
|
+
};
|
|
11661
|
+
}
|
|
11662
|
+
});
|
|
11663
|
+
|
|
11664
|
+
// src/core/rules/extension/assertion-required-fields.ts
|
|
11665
|
+
function fieldValue(spec, field) {
|
|
11666
|
+
const segments = field.split(".");
|
|
11667
|
+
if (segments[0] !== "ext") {
|
|
11668
|
+
return segments.length === 1 ? spec[field] : void 0;
|
|
11669
|
+
}
|
|
11670
|
+
let cur = spec.ext;
|
|
11671
|
+
for (const seg of segments.slice(1)) {
|
|
11672
|
+
if (cur === null || typeof cur !== "object") return void 0;
|
|
11673
|
+
if (!Object.prototype.hasOwnProperty.call(cur, seg)) return void 0;
|
|
11674
|
+
cur = cur[seg];
|
|
11675
|
+
}
|
|
11676
|
+
return cur;
|
|
11677
|
+
}
|
|
11678
|
+
function holdersAt(ctx, level) {
|
|
11679
|
+
const holders = [];
|
|
11680
|
+
if (level === "component") {
|
|
11681
|
+
for (const c of ctx.components) {
|
|
11682
|
+
holders.push({ spec: c, specId: c.id, comp: c, draft: ctx.isComponentDraft(c.id) });
|
|
11683
|
+
}
|
|
11684
|
+
} else if (level === "interface") {
|
|
11685
|
+
for (const i of ctx.interfaces) {
|
|
11686
|
+
const comp = ctx.componentMap.get(i.component);
|
|
11687
|
+
if (comp) holders.push({ spec: i, specId: i.id, comp, draft: ctx.isComponentDraft(comp.id) || i.status === "draft" || i.status === "design" });
|
|
11688
|
+
}
|
|
11689
|
+
} else {
|
|
11690
|
+
for (const impl of ctx.implementations) {
|
|
11691
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
11692
|
+
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
11693
|
+
if (comp) holders.push({ spec: impl, specId: impl.id, comp, draft: ctx.isImplementationDraft(impl) });
|
|
11694
|
+
}
|
|
11695
|
+
}
|
|
11696
|
+
return holders;
|
|
11697
|
+
}
|
|
11698
|
+
var assertionRequiredFieldsRule;
|
|
11699
|
+
var init_assertion_required_fields = __esm({
|
|
11700
|
+
"src/core/rules/extension/assertion-required-fields.ts"() {
|
|
11701
|
+
"use strict";
|
|
11702
|
+
init_declared_assertion();
|
|
11703
|
+
assertionRequiredFieldsRule = {
|
|
11704
|
+
name: "assertion-required-fields",
|
|
11705
|
+
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.",
|
|
11706
|
+
// Static codes are unknown here — packs bring their own. The validator
|
|
11707
|
+
// gathers every loaded assertion's fullCode into knownIssueCodes.
|
|
11708
|
+
codes: [],
|
|
11709
|
+
check(ctx) {
|
|
11710
|
+
for (const a of assertionsOfKind(ctx, "require-field")) {
|
|
11711
|
+
for (const h of holdersAt(ctx, a.level)) {
|
|
11712
|
+
if (!matchesSelector(a.on, h.comp, ctx)) continue;
|
|
11713
|
+
const v = fieldValue(h.spec, a.field);
|
|
11714
|
+
if (v === void 0 || v === null) {
|
|
11715
|
+
reportAssertion(ctx, a, `${a.level} spec "${h.specId}" does not declare "${a.field}", required by assertion ${a.code}`, h.specId, h.draft);
|
|
11716
|
+
continue;
|
|
11717
|
+
}
|
|
11718
|
+
if (a.values && !a.values.includes(String(v))) {
|
|
11719
|
+
reportAssertion(ctx, a, `${a.level} spec "${h.specId}" declares "${a.field}" = "${String(v)}", outside the allowed set (${a.values.join(", ")}) of assertion ${a.code}`, h.specId, h.draft);
|
|
11720
|
+
}
|
|
11721
|
+
}
|
|
11722
|
+
}
|
|
11723
|
+
}
|
|
11724
|
+
};
|
|
11725
|
+
}
|
|
11726
|
+
});
|
|
11727
|
+
|
|
11728
|
+
// src/core/rules/extension/assertion-endpoint-shapes.ts
|
|
11383
11729
|
function endpointAddress(ep) {
|
|
11384
11730
|
switch (ep.transport) {
|
|
11385
11731
|
case "HTTP":
|
|
@@ -11400,92 +11746,42 @@ function endpointAddress(ep) {
|
|
|
11400
11746
|
return ep.address;
|
|
11401
11747
|
}
|
|
11402
11748
|
}
|
|
11403
|
-
function
|
|
11404
|
-
const
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11749
|
+
function boundEndpoints(ctx, on) {
|
|
11750
|
+
const out = [];
|
|
11751
|
+
for (const intf of ctx.interfaces) {
|
|
11752
|
+
const comp = ctx.componentMap.get(intf.component);
|
|
11753
|
+
if (!comp || !matchesSelector(on, comp, ctx)) continue;
|
|
11754
|
+
const draft = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
|
|
11755
|
+
for (const method of intf.methods) {
|
|
11756
|
+
if (!method.endpoint) continue;
|
|
11757
|
+
out.push({ intf, method, endpoint: method.endpoint, draft });
|
|
11758
|
+
}
|
|
11413
11759
|
}
|
|
11414
|
-
return
|
|
11760
|
+
return out;
|
|
11415
11761
|
}
|
|
11416
|
-
var
|
|
11417
|
-
var
|
|
11418
|
-
"src/core/rules/extension/
|
|
11762
|
+
var assertionEndpointShapesRule;
|
|
11763
|
+
var init_assertion_endpoint_shapes = __esm({
|
|
11764
|
+
"src/core/rules/extension/assertion-endpoint-shapes.ts"() {
|
|
11419
11765
|
"use strict";
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
|
|
11766
|
+
init_declared_assertion();
|
|
11767
|
+
assertionEndpointShapesRule = {
|
|
11768
|
+
name: "assertion-endpoint-shapes",
|
|
11769
|
+
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.",
|
|
11423
11770
|
// Static codes are unknown here — packs bring their own. The validator
|
|
11424
11771
|
// gathers every loaded assertion's fullCode into knownIssueCodes.
|
|
11425
11772
|
codes: [],
|
|
11426
11773
|
check(ctx) {
|
|
11427
|
-
const
|
|
11428
|
-
|
|
11429
|
-
|
|
11430
|
-
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
if (a.kind === "forbid-edge") {
|
|
11435
|
-
for (const comp of ctx.components) {
|
|
11436
|
-
if (!matches(a.from, comp, ctx)) continue;
|
|
11437
|
-
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
11438
|
-
for (const relation of a.relation) {
|
|
11439
|
-
for (const targetId of comp[relation]) {
|
|
11440
|
-
const target = ctx.componentMap.get(targetId);
|
|
11441
|
-
if (!target || !matches(a.to, target, ctx)) continue;
|
|
11442
|
-
emit(a, `Component "${comp.id}" (${comp.componentType}) ${relation === "owns" ? "owns" : "depends on"} "${target.id}" (${target.componentType}), forbidden by assertion ${a.code}`, comp.id, isDraftCtx);
|
|
11443
|
-
}
|
|
11444
|
-
}
|
|
11445
|
-
}
|
|
11446
|
-
} else if (a.kind === "require-field") {
|
|
11447
|
-
const holders = [];
|
|
11448
|
-
if (a.level === "component") {
|
|
11449
|
-
for (const c of ctx.components) holders.push({ spec: c, specId: c.id, comp: c, draft: ctx.isComponentDraft(c.id) });
|
|
11450
|
-
} else if (a.level === "interface") {
|
|
11451
|
-
for (const i of ctx.interfaces) {
|
|
11452
|
-
const comp = ctx.componentMap.get(i.component);
|
|
11453
|
-
if (comp) holders.push({ spec: i, specId: i.id, comp, draft: ctx.isComponentDraft(comp.id) || i.status === "draft" || i.status === "design" });
|
|
11454
|
-
}
|
|
11455
|
-
} else {
|
|
11456
|
-
for (const impl of ctx.implementations) {
|
|
11457
|
-
const contract = ctx.interfaceMap.get(impl.contract);
|
|
11458
|
-
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
11459
|
-
if (comp) holders.push({ spec: impl, specId: impl.id, comp, draft: ctx.isImplementationDraft(impl) });
|
|
11460
|
-
}
|
|
11461
|
-
}
|
|
11462
|
-
for (const h of holders) {
|
|
11463
|
-
if (!matches(a.on, h.comp, ctx)) continue;
|
|
11464
|
-
const v = fieldValue(h.spec, a.field);
|
|
11465
|
-
if (v === void 0 || v === null) {
|
|
11466
|
-
emit(a, `${a.level} spec "${h.specId}" does not declare "${a.field}", required by assertion ${a.code}`, h.specId, h.draft);
|
|
11467
|
-
} else if (a.values && !a.values.includes(String(v))) {
|
|
11468
|
-
emit(a, `${a.level} spec "${h.specId}" declares "${a.field}" = "${String(v)}", outside the allowed set (${a.values.join(", ")}) of assertion ${a.code}`, h.specId, h.draft);
|
|
11469
|
-
}
|
|
11774
|
+
for (const a of assertionsOfKind(ctx, "endpoint-shape")) {
|
|
11775
|
+
const pattern = a.pathPattern ? new RegExp(a.pathPattern) : void 0;
|
|
11776
|
+
for (const bound2 of boundEndpoints(ctx, a.on)) {
|
|
11777
|
+
const { intf, method, endpoint } = bound2;
|
|
11778
|
+
if (a.transport?.length && !a.transport.includes(endpoint.transport)) {
|
|
11779
|
+
reportAssertion(ctx, a, `Endpoint of "${intf.id}.${method.name}" uses transport ${endpoint.transport}, outside the allowlist (${a.transport.join(", ")}) of assertion ${a.code}`, intf.id, bound2.draft);
|
|
11780
|
+
continue;
|
|
11470
11781
|
}
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
const comp = ctx.componentMap.get(intf.component);
|
|
11475
|
-
if (!comp || !matches(a.on, comp, ctx)) continue;
|
|
11476
|
-
const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
|
|
11477
|
-
for (const method of intf.methods) {
|
|
11478
|
-
if (!method.endpoint) continue;
|
|
11479
|
-
const ep = method.endpoint;
|
|
11480
|
-
if (a.transport?.length && !a.transport.includes(ep.transport)) {
|
|
11481
|
-
emit(a, `Endpoint of "${intf.id}.${method.name}" uses transport ${ep.transport}, outside the allowlist (${a.transport.join(", ")}) of assertion ${a.code}`, intf.id, isDraftCtx);
|
|
11482
|
-
continue;
|
|
11483
|
-
}
|
|
11484
|
-
const address = endpointAddress(ep);
|
|
11485
|
-
if (pattern && !pattern.test(address)) {
|
|
11486
|
-
emit(a, `Endpoint of "${intf.id}.${method.name}" binds "${address}", which does not match ${a.pathPattern} required by assertion ${a.code}`, intf.id, isDraftCtx);
|
|
11487
|
-
}
|
|
11488
|
-
}
|
|
11782
|
+
const address = endpointAddress(endpoint);
|
|
11783
|
+
if (pattern && !pattern.test(address)) {
|
|
11784
|
+
reportAssertion(ctx, a, `Endpoint of "${intf.id}.${method.name}" binds "${address}", which does not match ${a.pathPattern} required by assertion ${a.code}`, intf.id, bound2.draft);
|
|
11489
11785
|
}
|
|
11490
11786
|
}
|
|
11491
11787
|
}
|
|
@@ -11925,16 +12221,13 @@ var reachabilityRule;
|
|
|
11925
12221
|
var init_unused_detection = __esm({
|
|
11926
12222
|
"src/core/rules/wiring/unused-detection.ts"() {
|
|
11927
12223
|
"use strict";
|
|
11928
|
-
init_models();
|
|
11929
12224
|
init_narrative_graph_projector();
|
|
11930
12225
|
reachabilityRule = {
|
|
11931
12226
|
name: "unused-detection",
|
|
11932
|
-
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
|
|
12227
|
+
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.",
|
|
11933
12228
|
codes: [
|
|
11934
12229
|
{ code: "UNUSED_COMPONENT", defaultSeverity: "warning", summary: "Component never reached by any narrative call chain" },
|
|
11935
12230
|
{ code: "UNUSED_METHOD", defaultSeverity: "warning", summary: "Method never called by any narrative step" },
|
|
11936
|
-
{ code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields or signatures" },
|
|
11937
|
-
{ code: "INVOKED_BY_UNDESCRIBED", defaultSeverity: "warning", summary: "invokedBy declaration whose caller prose is missing or placeholder-thin" },
|
|
11938
12231
|
{ code: "INVOKED_BY_REDUNDANT", defaultSeverity: "warning", summary: "invokedBy declaration on a method the internal narrative walk already reaches \u2014 stale, remove it" }
|
|
11939
12232
|
],
|
|
11940
12233
|
check(ctx) {
|
|
@@ -11962,17 +12255,8 @@ var init_unused_detection = __esm({
|
|
|
11962
12255
|
if (!m.invokedBy) continue;
|
|
11963
12256
|
invokedBySeeds.push({ compId: intf.component, methodName: m.name });
|
|
11964
12257
|
if (!ctx.isSpecInScope(intf.id)) continue;
|
|
11965
|
-
const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
11966
|
-
if (!passesIntentFloor(m.invokedBy.caller, m.name)) {
|
|
11967
|
-
ctx.addIssue(
|
|
11968
|
-
"warning",
|
|
11969
|
-
"INVOKED_BY_UNDESCRIBED",
|
|
11970
|
-
`Method "${m.name}" on component "${intf.component}" declares invokedBy (${m.invokedBy.kind}) but its "caller" prose is missing or placeholder-thin \u2014 state WHO invokes it and when, so the entrypoint claim stays reviewable.`,
|
|
11971
|
-
intf.id,
|
|
11972
|
-
isDraftCtx
|
|
11973
|
-
);
|
|
11974
|
-
}
|
|
11975
12258
|
if (baseWalk.reachesMethod(intf.component, m.name)) {
|
|
12259
|
+
const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
11976
12260
|
ctx.addIssue(
|
|
11977
12261
|
"warning",
|
|
11978
12262
|
"INVOKED_BY_REDUNDANT",
|
|
@@ -11995,23 +12279,75 @@ var init_unused_detection = __esm({
|
|
|
11995
12279
|
comp.id,
|
|
11996
12280
|
isDraftCtx
|
|
11997
12281
|
);
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
|
|
12282
|
+
continue;
|
|
12283
|
+
}
|
|
12284
|
+
for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
|
|
12285
|
+
for (const m of intf.methods) {
|
|
12286
|
+
if (!reach.reachesMethod(comp.id, m.name)) {
|
|
12287
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
|
|
12288
|
+
ctx.addIssue(
|
|
12289
|
+
"warning",
|
|
12290
|
+
"UNUSED_METHOD",
|
|
12291
|
+
`Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
|
|
12292
|
+
intf.id,
|
|
12293
|
+
isDraftCtx
|
|
12294
|
+
);
|
|
12011
12295
|
}
|
|
12012
12296
|
}
|
|
12013
12297
|
}
|
|
12014
12298
|
}
|
|
12299
|
+
}
|
|
12300
|
+
};
|
|
12301
|
+
}
|
|
12302
|
+
});
|
|
12303
|
+
|
|
12304
|
+
// src/core/rules/wiring/invoked-by-description.ts
|
|
12305
|
+
var invokedByDescriptionRule;
|
|
12306
|
+
var init_invoked_by_description = __esm({
|
|
12307
|
+
"src/core/rules/wiring/invoked-by-description.ts"() {
|
|
12308
|
+
"use strict";
|
|
12309
|
+
init_models();
|
|
12310
|
+
invokedByDescriptionRule = {
|
|
12311
|
+
name: "invoked-by-description",
|
|
12312
|
+
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.',
|
|
12313
|
+
codes: [
|
|
12314
|
+
{ code: "INVOKED_BY_UNDESCRIBED", defaultSeverity: "warning", summary: "invokedBy declaration whose caller prose is missing or placeholder-thin" }
|
|
12315
|
+
],
|
|
12316
|
+
check(ctx) {
|
|
12317
|
+
for (const intf of ctx.interfaces) {
|
|
12318
|
+
if (!ctx.componentMap.has(intf.component)) continue;
|
|
12319
|
+
if (!ctx.isSpecInScope(intf.id)) continue;
|
|
12320
|
+
for (const m of intf.methods) {
|
|
12321
|
+
if (!m.invokedBy) continue;
|
|
12322
|
+
if (passesIntentFloor(m.invokedBy.caller, m.name)) continue;
|
|
12323
|
+
const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
12324
|
+
ctx.addIssue(
|
|
12325
|
+
"warning",
|
|
12326
|
+
"INVOKED_BY_UNDESCRIBED",
|
|
12327
|
+
`Method "${m.name}" on component "${intf.component}" declares invokedBy (${m.invokedBy.kind}) but its "caller" prose is missing or placeholder-thin \u2014 state WHO invokes it and when, so the entrypoint claim stays reviewable.`,
|
|
12328
|
+
intf.id,
|
|
12329
|
+
isDraftCtx
|
|
12330
|
+
);
|
|
12331
|
+
}
|
|
12332
|
+
}
|
|
12333
|
+
}
|
|
12334
|
+
};
|
|
12335
|
+
}
|
|
12336
|
+
});
|
|
12337
|
+
|
|
12338
|
+
// src/core/rules/wiring/unused-types.ts
|
|
12339
|
+
var unusedTypesRule;
|
|
12340
|
+
var init_unused_types = __esm({
|
|
12341
|
+
"src/core/rules/wiring/unused-types.ts"() {
|
|
12342
|
+
"use strict";
|
|
12343
|
+
init_models();
|
|
12344
|
+
unusedTypesRule = {
|
|
12345
|
+
name: "unused-types",
|
|
12346
|
+
description: "Flags types no field of any type and no interface 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.",
|
|
12347
|
+
codes: [
|
|
12348
|
+
{ code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields or signatures" }
|
|
12349
|
+
],
|
|
12350
|
+
check(ctx) {
|
|
12015
12351
|
const referencedTypes = /* @__PURE__ */ new Set();
|
|
12016
12352
|
const markTypeReferenced = (ref) => {
|
|
12017
12353
|
if (ctx.isBuiltinType(ref)) return;
|
|
@@ -12245,7 +12581,44 @@ var init_prose_claims = __esm({
|
|
|
12245
12581
|
}
|
|
12246
12582
|
});
|
|
12247
12583
|
|
|
12248
|
-
// src/core/rules/wiring/invariant-
|
|
12584
|
+
// src/core/rules/wiring/unique-invariant-ids.ts
|
|
12585
|
+
var uniqueInvariantIdsRule;
|
|
12586
|
+
var init_unique_invariant_ids = __esm({
|
|
12587
|
+
"src/core/rules/wiring/unique-invariant-ids.ts"() {
|
|
12588
|
+
"use strict";
|
|
12589
|
+
init_models();
|
|
12590
|
+
uniqueInvariantIdsRule = {
|
|
12591
|
+
name: "unique-invariant-ids",
|
|
12592
|
+
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.`,
|
|
12593
|
+
codes: [
|
|
12594
|
+
{ code: "DUPLICATE_INVARIANT_ID", defaultSeverity: "error", summary: "An entity declares two invariants with the same id" }
|
|
12595
|
+
],
|
|
12596
|
+
check(ctx) {
|
|
12597
|
+
for (const t of ctx.types) {
|
|
12598
|
+
const invariants = t.invariants ?? [];
|
|
12599
|
+
if (invariants.length === 0) continue;
|
|
12600
|
+
const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
|
|
12601
|
+
const entityDraft = sub !== void 0 && isDraftSubsystem(sub);
|
|
12602
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12603
|
+
for (const inv of invariants) {
|
|
12604
|
+
if (seen.has(inv.id)) {
|
|
12605
|
+
ctx.addIssue(
|
|
12606
|
+
"error",
|
|
12607
|
+
"DUPLICATE_INVARIANT_ID",
|
|
12608
|
+
`Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
|
|
12609
|
+
t.id,
|
|
12610
|
+
entityDraft
|
|
12611
|
+
);
|
|
12612
|
+
}
|
|
12613
|
+
seen.add(inv.id);
|
|
12614
|
+
}
|
|
12615
|
+
}
|
|
12616
|
+
}
|
|
12617
|
+
};
|
|
12618
|
+
}
|
|
12619
|
+
});
|
|
12620
|
+
|
|
12621
|
+
// src/core/rules/wiring/invariant-ref.ts
|
|
12249
12622
|
function splitInvariantRef(ref) {
|
|
12250
12623
|
const at = ref.lastIndexOf(".");
|
|
12251
12624
|
if (at <= 0 || at === ref.length - 1) return null;
|
|
@@ -12269,6 +12642,14 @@ function refMatchesInvariant(ref, type, invariantId) {
|
|
|
12269
12642
|
if (!(type.invariants ?? []).some((inv) => inv.id === invariantId)) return false;
|
|
12270
12643
|
return typeMatchesRef(type, parts.typeRef);
|
|
12271
12644
|
}
|
|
12645
|
+
var init_invariant_ref = __esm({
|
|
12646
|
+
"src/core/rules/wiring/invariant-ref.ts"() {
|
|
12647
|
+
"use strict";
|
|
12648
|
+
init_models();
|
|
12649
|
+
}
|
|
12650
|
+
});
|
|
12651
|
+
|
|
12652
|
+
// src/core/rules/wiring/invariant-backing.ts
|
|
12272
12653
|
function stepAsserts(impl, methodName, type, invariantId) {
|
|
12273
12654
|
const method = impl.methods.find((m) => m.name === methodName);
|
|
12274
12655
|
if (!method) return false;
|
|
@@ -12293,33 +12674,19 @@ var init_invariant_backing = __esm({
|
|
|
12293
12674
|
"src/core/rules/wiring/invariant-backing.ts"() {
|
|
12294
12675
|
"use strict";
|
|
12295
12676
|
init_models();
|
|
12677
|
+
init_invariant_ref();
|
|
12296
12678
|
invariantBackingRule = {
|
|
12297
12679
|
name: "invariant-backing",
|
|
12298
|
-
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
|
|
12680
|
+
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.',
|
|
12299
12681
|
codes: [
|
|
12300
|
-
{ code: "DUPLICATE_INVARIANT_ID", defaultSeverity: "error", summary: "An entity declares two invariants with the same id" },
|
|
12301
12682
|
{ 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" },
|
|
12302
|
-
{ code: "UNASSERTED_INVARIANT", defaultSeverity: "warning", summary: "A write-effect method of the invariant's owning component has no narrative step asserting it" }
|
|
12303
|
-
{ code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
|
|
12683
|
+
{ code: "UNASSERTED_INVARIANT", defaultSeverity: "warning", summary: "A write-effect method of the invariant's owning component has no narrative step asserting it" }
|
|
12304
12684
|
],
|
|
12305
12685
|
check(ctx) {
|
|
12306
12686
|
for (const t of ctx.types) {
|
|
12307
12687
|
const invariants = t.invariants ?? [];
|
|
12308
12688
|
if (invariants.length === 0) continue;
|
|
12309
12689
|
const entityDraft = isEntityDraft(t, ctx);
|
|
12310
|
-
const seen = /* @__PURE__ */ new Set();
|
|
12311
|
-
for (const inv of invariants) {
|
|
12312
|
-
if (seen.has(inv.id)) {
|
|
12313
|
-
ctx.addIssue(
|
|
12314
|
-
"error",
|
|
12315
|
-
"DUPLICATE_INVARIANT_ID",
|
|
12316
|
-
`Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
|
|
12317
|
-
t.id,
|
|
12318
|
-
entityDraft
|
|
12319
|
-
);
|
|
12320
|
-
}
|
|
12321
|
-
seen.add(inv.id);
|
|
12322
|
-
}
|
|
12323
12690
|
const comp = resolveComponentClass(t, ctx);
|
|
12324
12691
|
if (!comp) {
|
|
12325
12692
|
ctx.addIssue(
|
|
@@ -12361,6 +12728,24 @@ var init_invariant_backing = __esm({
|
|
|
12361
12728
|
}
|
|
12362
12729
|
}
|
|
12363
12730
|
}
|
|
12731
|
+
}
|
|
12732
|
+
};
|
|
12733
|
+
}
|
|
12734
|
+
});
|
|
12735
|
+
|
|
12736
|
+
// src/core/rules/wiring/invariant-references.ts
|
|
12737
|
+
var invariantReferencesRule;
|
|
12738
|
+
var init_invariant_references = __esm({
|
|
12739
|
+
"src/core/rules/wiring/invariant-references.ts"() {
|
|
12740
|
+
"use strict";
|
|
12741
|
+
init_invariant_ref();
|
|
12742
|
+
invariantReferencesRule = {
|
|
12743
|
+
name: "invariant-references",
|
|
12744
|
+
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.",
|
|
12745
|
+
codes: [
|
|
12746
|
+
{ code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
|
|
12747
|
+
],
|
|
12748
|
+
check(ctx) {
|
|
12364
12749
|
for (const impl of ctx.implementations) {
|
|
12365
12750
|
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
12366
12751
|
for (const method of impl.methods) {
|
|
@@ -12692,54 +13077,45 @@ var init_call_conformance = __esm({
|
|
|
12692
13077
|
],
|
|
12693
13078
|
check(ctx) {
|
|
12694
13079
|
const code = ctx.codeIndex();
|
|
12695
|
-
for (const
|
|
12696
|
-
const
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
if (!
|
|
12700
|
-
if (
|
|
12701
|
-
const
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
if (!
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
const
|
|
12712
|
-
|
|
12713
|
-
|
|
12714
|
-
|
|
12715
|
-
for (const step of implMethod.narrative) {
|
|
12716
|
-
if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
|
|
12717
|
-
if (!ctx.componentMap.has(step.targetComponent)) continue;
|
|
12718
|
-
const accepted = /* @__PURE__ */ new Set([step.targetMethod]);
|
|
12719
|
-
for (const targetIntf of ctx.interfacesByComponent.get(step.targetComponent) ?? []) {
|
|
12720
|
-
for (const targetImpl of ctx.implementationsByContract.get(targetIntf.id) ?? []) {
|
|
12721
|
-
const targetMethod = targetImpl.methods.find((m) => m.name === step.targetMethod);
|
|
12722
|
-
if (targetMethod?.symbol) accepted.add(targetMethod.symbol);
|
|
12723
|
-
}
|
|
13080
|
+
for (const entry of ctx.implementationMethods()) {
|
|
13081
|
+
const { implementation: impl, method: implMethod, component, sourceFile: file } = entry;
|
|
13082
|
+
const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
|
|
13083
|
+
if (tier === "off") continue;
|
|
13084
|
+
if (!implMethod.narrative.length) continue;
|
|
13085
|
+
if (!file) continue;
|
|
13086
|
+
const facts = code.factsAt(file);
|
|
13087
|
+
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
|
|
13088
|
+
const fnSymbol = implMethod.symbol ?? implMethod.name;
|
|
13089
|
+
const callees = closedCallees(facts, fnSymbol);
|
|
13090
|
+
if (!callees) continue;
|
|
13091
|
+
const missing = [];
|
|
13092
|
+
for (const step of implMethod.narrative) {
|
|
13093
|
+
if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
|
|
13094
|
+
if (!ctx.componentMap.has(step.targetComponent)) continue;
|
|
13095
|
+
const accepted = /* @__PURE__ */ new Set([step.targetMethod]);
|
|
13096
|
+
for (const targetIntf of ctx.interfacesByComponent.get(step.targetComponent) ?? []) {
|
|
13097
|
+
for (const targetImpl of ctx.implementationsByContract.get(targetIntf.id) ?? []) {
|
|
13098
|
+
const targetMethod = targetImpl.methods.find((m) => m.name === step.targetMethod);
|
|
13099
|
+
if (targetMethod?.symbol) accepted.add(targetMethod.symbol);
|
|
12724
13100
|
}
|
|
12725
|
-
if (accepted.has(fnSymbol)) continue;
|
|
12726
|
-
if ([...accepted].some((name) => callees.has(name))) continue;
|
|
12727
|
-
missing.push({
|
|
12728
|
-
step: step.stepNumber,
|
|
12729
|
-
target: `${step.targetComponent}.${step.targetMethod}`,
|
|
12730
|
-
accepted: [...accepted]
|
|
12731
|
-
});
|
|
12732
13101
|
}
|
|
12733
|
-
if (
|
|
12734
|
-
|
|
12735
|
-
|
|
12736
|
-
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
|
|
12740
|
-
isDraftCtx
|
|
12741
|
-
);
|
|
13102
|
+
if (accepted.has(fnSymbol)) continue;
|
|
13103
|
+
if ([...accepted].some((name) => callees.has(name))) continue;
|
|
13104
|
+
missing.push({
|
|
13105
|
+
step: step.stepNumber,
|
|
13106
|
+
target: `${step.targetComponent}.${step.targetMethod}`,
|
|
13107
|
+
accepted: [...accepted]
|
|
13108
|
+
});
|
|
12742
13109
|
}
|
|
13110
|
+
if (missing.length === 0) continue;
|
|
13111
|
+
const detail = missing.map((m) => `step ${m.step} \u2192 ${m.target} (looked for ${m.accepted.map((a) => `"${a}"`).join(" / ")})`).join("; ");
|
|
13112
|
+
ctx.addIssue(
|
|
13113
|
+
"warning",
|
|
13114
|
+
"CALL_STEP_UNREALIZED",
|
|
13115
|
+
`Method "${implMethod.name}" in implementation "${impl.id}": ${missing.length} narrative call step(s) are not realized as calls of the function "${fnSymbol}" in "${file}" \u2014 ${detail}. Callees are matched by name, closed over same-file helpers (exact grade, set membership \u2014 order and arguments are not checked). Realize the calls, fix the narrative, or map code names via per-method symbols on the targets.`,
|
|
13116
|
+
impl.id,
|
|
13117
|
+
entry.draftContext
|
|
13118
|
+
);
|
|
12743
13119
|
}
|
|
12744
13120
|
}
|
|
12745
13121
|
};
|
|
@@ -12906,20 +13282,95 @@ var init_dependency_conformance = __esm({
|
|
|
12906
13282
|
}
|
|
12907
13283
|
});
|
|
12908
13284
|
|
|
12909
|
-
// src/core/rules/conformance/integration-
|
|
12910
|
-
var
|
|
12911
|
-
var
|
|
12912
|
-
"src/core/rules/conformance/integration-
|
|
13285
|
+
// src/core/rules/conformance/integration-sim-declaration.ts
|
|
13286
|
+
var integrationSimDeclarationRule;
|
|
13287
|
+
var init_integration_sim_declaration = __esm({
|
|
13288
|
+
"src/core/rules/conformance/integration-sim-declaration.ts"() {
|
|
13289
|
+
"use strict";
|
|
13290
|
+
integrationSimDeclarationRule = {
|
|
13291
|
+
name: "integration-sim-declaration",
|
|
13292
|
+
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.",
|
|
13293
|
+
codes: [
|
|
13294
|
+
{ code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" }
|
|
13295
|
+
],
|
|
13296
|
+
check(ctx) {
|
|
13297
|
+
const adopted = /* @__PURE__ */ new Set();
|
|
13298
|
+
for (const impl of ctx.implementations) {
|
|
13299
|
+
if (!impl.simPath) continue;
|
|
13300
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
13301
|
+
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
13302
|
+
if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
13303
|
+
adopted.add(comp.subsystem);
|
|
13304
|
+
}
|
|
13305
|
+
for (const impl of ctx.implementations) {
|
|
13306
|
+
if (impl.simPath) continue;
|
|
13307
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
13308
|
+
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
13309
|
+
if (!comp) continue;
|
|
13310
|
+
if (ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
13311
|
+
if (!adopted.has(comp.subsystem)) continue;
|
|
13312
|
+
if (ctx.isImplementationDraft(impl)) continue;
|
|
13313
|
+
const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
|
|
13314
|
+
if (deps.length === 0) continue;
|
|
13315
|
+
ctx.addIssue(
|
|
13316
|
+
"warning",
|
|
13317
|
+
"MISSING_INTEGRATION_SIM",
|
|
13318
|
+
`Implementation "${impl.id}" (component "${comp.id}") is complete with ${deps.length} declared dependenc${deps.length === 1 ? "y" : "ies"}, its subsystem "${comp.subsystem}" has adopted integration sims, but it declares no simPath \u2014 its unit suite proves contract shape against mocks, not that the wired components run together. Declare the committed harness that constructs it with its REAL dependencies (N:1 sharing allowed), or lint.allow with the reason.`,
|
|
13319
|
+
impl.id
|
|
13320
|
+
);
|
|
13321
|
+
}
|
|
13322
|
+
}
|
|
13323
|
+
};
|
|
13324
|
+
}
|
|
13325
|
+
});
|
|
13326
|
+
|
|
13327
|
+
// src/core/rules/conformance/integration-sim-file.ts
|
|
13328
|
+
var integrationSimFileRule;
|
|
13329
|
+
var init_integration_sim_file = __esm({
|
|
13330
|
+
"src/core/rules/conformance/integration-sim-file.ts"() {
|
|
12913
13331
|
"use strict";
|
|
12914
13332
|
init_models();
|
|
12915
|
-
|
|
12916
|
-
name: "integration-
|
|
12917
|
-
description: "A
|
|
13333
|
+
integrationSimFileRule = {
|
|
13334
|
+
name: "integration-sim-file",
|
|
13335
|
+
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.",
|
|
12918
13336
|
codes: [
|
|
12919
|
-
{ code: "
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
|
|
13337
|
+
{ code: "SIM_FILE_MISSING", defaultSeverity: "warning", summary: "Declared simPath resolves to no file inside the project root" }
|
|
13338
|
+
],
|
|
13339
|
+
check(ctx) {
|
|
13340
|
+
const code = ctx.codeIndex();
|
|
13341
|
+
for (const impl of ctx.implementations) {
|
|
13342
|
+
if (!impl.simPath) continue;
|
|
13343
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
13344
|
+
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
13345
|
+
if (!comp) continue;
|
|
13346
|
+
if (ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
13347
|
+
const facts = code.factsAt(pathKey(impl.simPath));
|
|
13348
|
+
if (facts && facts.status === "analyzed") continue;
|
|
13349
|
+
const why = !facts || facts.status === "missing" ? "resolves to no file" : facts.status === "escaped" ? "escapes the project root \u2014 sims are project-relative, committed files" : "is not readable as source text";
|
|
13350
|
+
ctx.addIssue(
|
|
13351
|
+
"warning",
|
|
13352
|
+
"SIM_FILE_MISSING",
|
|
13353
|
+
`Implementation "${impl.id}" declares simPath "${impl.simPath}", which ${why}. The integration harness must be a committed, re-runnable file inside the project.`,
|
|
13354
|
+
impl.id,
|
|
13355
|
+
ctx.isImplementationDraft(impl)
|
|
13356
|
+
);
|
|
13357
|
+
}
|
|
13358
|
+
}
|
|
13359
|
+
};
|
|
13360
|
+
}
|
|
13361
|
+
});
|
|
13362
|
+
|
|
13363
|
+
// src/core/rules/conformance/integration-sim-wiring.ts
|
|
13364
|
+
var integrationSimWiringRule;
|
|
13365
|
+
var init_integration_sim_wiring = __esm({
|
|
13366
|
+
"src/core/rules/conformance/integration-sim-wiring.ts"() {
|
|
13367
|
+
"use strict";
|
|
13368
|
+
init_models();
|
|
13369
|
+
integrationSimWiringRule = {
|
|
13370
|
+
name: "integration-sim-wiring",
|
|
13371
|
+
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.",
|
|
13372
|
+
codes: [
|
|
13373
|
+
{ code: "UNWIRED_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Sim file does not import the real modules it claims to wire" }
|
|
12923
13374
|
],
|
|
12924
13375
|
check(ctx) {
|
|
12925
13376
|
const code = ctx.codeIndex();
|
|
@@ -12931,48 +13382,18 @@ var init_integration_conformance = __esm({
|
|
|
12931
13382
|
};
|
|
12932
13383
|
const modulesOf = (compId) => realization.filesOf(compId).filter(exists);
|
|
12933
13384
|
const modulesIn = (subsystemId) => realization.filesIn(subsystemId).filter(exists);
|
|
12934
|
-
const adopted = /* @__PURE__ */ new Set();
|
|
12935
|
-
for (const impl of ctx.implementations) {
|
|
12936
|
-
if (!impl.simPath) continue;
|
|
12937
|
-
const contract = ctx.interfaceMap.get(impl.contract);
|
|
12938
|
-
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
12939
|
-
if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
12940
|
-
adopted.add(comp.subsystem);
|
|
12941
|
-
}
|
|
12942
13385
|
const isTechBoundary = (compId) => realization.implementationsOf(compId).some((i) => (i.technologies ?? []).length > 0);
|
|
12943
13386
|
for (const impl of ctx.implementations) {
|
|
13387
|
+
if (!impl.simPath) continue;
|
|
12944
13388
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
12945
13389
|
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
12946
13390
|
if (!comp) continue;
|
|
12947
13391
|
if (ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
12948
|
-
const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
|
|
12949
|
-
if (!impl.simPath) {
|
|
12950
|
-
if (!adopted.has(comp.subsystem)) continue;
|
|
12951
|
-
if (ctx.isImplementationDraft(impl)) continue;
|
|
12952
|
-
if (deps.length === 0) continue;
|
|
12953
|
-
ctx.addIssue(
|
|
12954
|
-
"warning",
|
|
12955
|
-
"MISSING_INTEGRATION_SIM",
|
|
12956
|
-
`Implementation "${impl.id}" (component "${comp.id}") is complete with ${deps.length} declared dependenc${deps.length === 1 ? "y" : "ies"}, its subsystem "${comp.subsystem}" has adopted integration sims, but it declares no simPath \u2014 its unit suite proves contract shape against mocks, not that the wired components run together. Declare the committed harness that constructs it with its REAL dependencies (N:1 sharing allowed), or lint.allow with the reason.`,
|
|
12957
|
-
impl.id
|
|
12958
|
-
);
|
|
12959
|
-
continue;
|
|
12960
|
-
}
|
|
12961
13392
|
const simPath = pathKey(impl.simPath);
|
|
12962
13393
|
const facts = code.factsAt(simPath);
|
|
12963
|
-
|
|
12964
|
-
if (!facts || facts.status === "missing" || facts.status === "escaped" || facts.status === "unreadable") {
|
|
12965
|
-
const why = !facts || facts.status === "missing" ? "resolves to no file" : facts.status === "escaped" ? "escapes the project root \u2014 sims are project-relative, committed files" : "is not readable as source text";
|
|
12966
|
-
ctx.addIssue(
|
|
12967
|
-
"warning",
|
|
12968
|
-
"SIM_FILE_MISSING",
|
|
12969
|
-
`Implementation "${impl.id}" declares simPath "${impl.simPath}", which ${why}. The integration harness must be a committed, re-runnable file inside the project.`,
|
|
12970
|
-
impl.id,
|
|
12971
|
-
isDraftCtx
|
|
12972
|
-
);
|
|
12973
|
-
continue;
|
|
12974
|
-
}
|
|
13394
|
+
if (!facts || facts.status !== "analyzed") continue;
|
|
12975
13395
|
if (facts.analysisGrade !== "exact") continue;
|
|
13396
|
+
const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
|
|
12976
13397
|
const reach = graph.reachFrom(simPath);
|
|
12977
13398
|
const ownFiles = modulesOf(comp.id);
|
|
12978
13399
|
const missing = [];
|
|
@@ -12989,15 +13410,43 @@ var init_integration_conformance = __esm({
|
|
|
12989
13410
|
missing.push(label);
|
|
12990
13411
|
}
|
|
12991
13412
|
}
|
|
12992
|
-
if (missing.length)
|
|
12993
|
-
|
|
12994
|
-
|
|
12995
|
-
|
|
12996
|
-
|
|
12997
|
-
|
|
12998
|
-
|
|
12999
|
-
|
|
13000
|
-
|
|
13413
|
+
if (missing.length === 0) continue;
|
|
13414
|
+
ctx.addIssue(
|
|
13415
|
+
"warning",
|
|
13416
|
+
"UNWIRED_INTEGRATION_SIM",
|
|
13417
|
+
`Sim "${impl.simPath}" of implementation "${impl.id}" does not reach ${missing.join(", nor ")} through its import graph (closed over the analyzed modules, exact grade) \u2014 the harness is not wiring the real implementations it claims to exercise. Import the real modules (technology-boundary adapters may stay contract-faithfully faked), or fix the simPath.`,
|
|
13418
|
+
impl.id,
|
|
13419
|
+
ctx.isImplementationDraft(impl)
|
|
13420
|
+
);
|
|
13421
|
+
}
|
|
13422
|
+
}
|
|
13423
|
+
};
|
|
13424
|
+
}
|
|
13425
|
+
});
|
|
13426
|
+
|
|
13427
|
+
// src/core/rules/conformance/integration-sim-coverage.ts
|
|
13428
|
+
var integrationSimCoverageRule;
|
|
13429
|
+
var init_integration_sim_coverage = __esm({
|
|
13430
|
+
"src/core/rules/conformance/integration-sim-coverage.ts"() {
|
|
13431
|
+
"use strict";
|
|
13432
|
+
init_models();
|
|
13433
|
+
integrationSimCoverageRule = {
|
|
13434
|
+
name: "integration-sim-coverage",
|
|
13435
|
+
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.",
|
|
13436
|
+
codes: [
|
|
13437
|
+
{ code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
|
|
13438
|
+
],
|
|
13439
|
+
check(ctx) {
|
|
13440
|
+
const code = ctx.codeIndex();
|
|
13441
|
+
for (const impl of ctx.implementations) {
|
|
13442
|
+
if (!impl.simPath) continue;
|
|
13443
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
13444
|
+
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
13445
|
+
if (!comp) continue;
|
|
13446
|
+
if (ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
13447
|
+
const facts = code.factsAt(pathKey(impl.simPath));
|
|
13448
|
+
if (!facts || facts.status !== "analyzed") continue;
|
|
13449
|
+
if (facts.analysisGrade !== "exact") continue;
|
|
13001
13450
|
const simAnchors = new Set(facts.anchoredNames.filter((a) => a.startsWith("sim:")));
|
|
13002
13451
|
if (simAnchors.size === 0) continue;
|
|
13003
13452
|
const compPrefix = `sim:${comp.id}.`;
|
|
@@ -13018,15 +13467,14 @@ var init_integration_conformance = __esm({
|
|
|
13018
13467
|
}
|
|
13019
13468
|
}
|
|
13020
13469
|
}
|
|
13021
|
-
if (uncovered.length)
|
|
13022
|
-
|
|
13023
|
-
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
13027
|
-
|
|
13028
|
-
|
|
13029
|
-
}
|
|
13470
|
+
if (uncovered.length === 0) continue;
|
|
13471
|
+
ctx.addIssue(
|
|
13472
|
+
"warning",
|
|
13473
|
+
"SIM_PATH_UNCOVERED",
|
|
13474
|
+
`Sim "${impl.simPath}" declares path coverage for component "${comp.id}" (it carries sim: anchors) but does not name ${uncovered.join(", nor ")}. Drive the path and anchor it with the exact string literal, or drop the component's sim: anchors to withdraw the coverage claim. Unlabeled throw steps are not expected \u2014 a step's label is the path's identity. Anchors prove the path is NAMED; assertion quality and execution stay CI's job.`,
|
|
13475
|
+
impl.id,
|
|
13476
|
+
ctx.isImplementationDraft(impl)
|
|
13477
|
+
);
|
|
13030
13478
|
}
|
|
13031
13479
|
}
|
|
13032
13480
|
};
|
|
@@ -13136,10 +13584,10 @@ var init_coupling_health = __esm({
|
|
|
13136
13584
|
}
|
|
13137
13585
|
});
|
|
13138
13586
|
|
|
13139
|
-
// src/core/rules/heuristic/
|
|
13140
|
-
var LANGUAGE_MARKERS,
|
|
13141
|
-
var
|
|
13142
|
-
"src/core/rules/heuristic/
|
|
13587
|
+
// src/core/rules/heuristic/signature-language-builtins.ts
|
|
13588
|
+
var LANGUAGE_MARKERS, signatureLanguageBuiltinsRule;
|
|
13589
|
+
var init_signature_language_builtins = __esm({
|
|
13590
|
+
"src/core/rules/heuristic/signature-language-builtins.ts"() {
|
|
13143
13591
|
"use strict";
|
|
13144
13592
|
init_models();
|
|
13145
13593
|
LANGUAGE_MARKERS = {
|
|
@@ -13174,6 +13622,68 @@ var init_target_language = __esm({
|
|
|
13174
13622
|
csharp: /* @__PURE__ */ new Set(["task"]),
|
|
13175
13623
|
go: /* @__PURE__ */ new Set(["chan", "rune"])
|
|
13176
13624
|
};
|
|
13625
|
+
signatureLanguageBuiltinsRule = {
|
|
13626
|
+
name: "signature-language-builtins",
|
|
13627
|
+
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).",
|
|
13628
|
+
codes: [
|
|
13629
|
+
{ code: "LANGUAGE_FOREIGN_BUILTIN", defaultSeverity: "warning", summary: "Signature uses a builtin from a different language family" }
|
|
13630
|
+
],
|
|
13631
|
+
check(ctx) {
|
|
13632
|
+
const markersFor = (family) => {
|
|
13633
|
+
const base = LANGUAGE_MARKERS[family];
|
|
13634
|
+
const extra = ctx.ext.languages[family]?.foreignBuiltins;
|
|
13635
|
+
if (!extra?.length) return base;
|
|
13636
|
+
return /* @__PURE__ */ new Set([...base ?? [], ...extra.map((s) => s.toLowerCase())]);
|
|
13637
|
+
};
|
|
13638
|
+
const families = [.../* @__PURE__ */ new Set([...Object.keys(LANGUAGE_MARKERS), ...Object.keys(ctx.ext.languages)])];
|
|
13639
|
+
const scans = [];
|
|
13640
|
+
for (const intf of ctx.interfaces) {
|
|
13641
|
+
const comp = ctx.componentMap.get(intf.component);
|
|
13642
|
+
const lang = ctx.targetLanguageFor(comp?.subsystem);
|
|
13643
|
+
if (!lang) continue;
|
|
13644
|
+
const ownMarkers = markersFor(lang);
|
|
13645
|
+
if (!ownMarkers || ownMarkers.size === 0) continue;
|
|
13646
|
+
const draft = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
13647
|
+
for (const method of intf.methods) {
|
|
13648
|
+
for (const ref of methodTypeRefs(method)) scans.push({ intf, method, ref, lang, ownMarkers, draft });
|
|
13649
|
+
}
|
|
13650
|
+
}
|
|
13651
|
+
for (const scan of scans) {
|
|
13652
|
+
const refLower = scan.ref.toLowerCase();
|
|
13653
|
+
if (scan.ownMarkers.has(refLower)) continue;
|
|
13654
|
+
const foreign = families.find((family) => family !== scan.lang && markersFor(family)?.has(refLower));
|
|
13655
|
+
if (!foreign) continue;
|
|
13656
|
+
ctx.addIssue(
|
|
13657
|
+
"warning",
|
|
13658
|
+
"LANGUAGE_FOREIGN_BUILTIN",
|
|
13659
|
+
`Method "${scan.method.name}" on interface "${scan.intf.id}" uses "${scan.ref}", a ${foreign} builtin, but the target language here is ${scan.lang}. Use the ${scan.lang} equivalent so implementers generate idiomatic code.`,
|
|
13660
|
+
scan.intf.id,
|
|
13661
|
+
scan.draft
|
|
13662
|
+
);
|
|
13663
|
+
}
|
|
13664
|
+
}
|
|
13665
|
+
};
|
|
13666
|
+
}
|
|
13667
|
+
});
|
|
13668
|
+
|
|
13669
|
+
// src/core/rules/heuristic/narrative-language-constructs.ts
|
|
13670
|
+
function constructsOf(step) {
|
|
13671
|
+
const constructs = [
|
|
13672
|
+
step.type === "loop" ? step.loopKind ?? (step.over ? "forEach" : "while") : step.type
|
|
13673
|
+
];
|
|
13674
|
+
if (step.detach) constructs.push("detach");
|
|
13675
|
+
return constructs;
|
|
13676
|
+
}
|
|
13677
|
+
function constructLabel(construct) {
|
|
13678
|
+
if (construct === "doWhile") return "a do-while loop";
|
|
13679
|
+
if (construct === "forEach" || construct === "for" || construct === "while") return `a ${construct} loop`;
|
|
13680
|
+
if (construct === "detach") return "a detached (fire-and-forget) call";
|
|
13681
|
+
return `a ${construct} step`;
|
|
13682
|
+
}
|
|
13683
|
+
var UNSUPPORTED_FLOW, narrativeLanguageConstructsRule;
|
|
13684
|
+
var init_narrative_language_constructs = __esm({
|
|
13685
|
+
"src/core/rules/heuristic/narrative-language-constructs.ts"() {
|
|
13686
|
+
"use strict";
|
|
13177
13687
|
UNSUPPORTED_FLOW = {
|
|
13178
13688
|
rust: {
|
|
13179
13689
|
try: "Rust models errors as values (Result + ?) \u2014 specify the guarded logic as explicit error branches, or note the Result mapping in the step description",
|
|
@@ -13193,53 +13703,18 @@ var init_target_language = __esm({
|
|
|
13193
13703
|
doWhile: "Python has no do-while \u2014 prefer loopKind: while, or state the emulation (while True + break)"
|
|
13194
13704
|
}
|
|
13195
13705
|
};
|
|
13196
|
-
|
|
13197
|
-
name: "
|
|
13198
|
-
description: "
|
|
13706
|
+
narrativeLanguageConstructsRule = {
|
|
13707
|
+
name: "narrative-language-constructs",
|
|
13708
|
+
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).",
|
|
13199
13709
|
codes: [
|
|
13200
|
-
{ code: "LANGUAGE_FOREIGN_BUILTIN", defaultSeverity: "warning", summary: "Signature uses a builtin from a different language family" },
|
|
13201
13710
|
{ code: "LANGUAGE_FOREIGN_FLOW", defaultSeverity: "warning", summary: "Narrative flow step uses a construct the target language does not have" }
|
|
13202
13711
|
],
|
|
13203
13712
|
check(ctx) {
|
|
13204
|
-
const markersFor = (family) => {
|
|
13205
|
-
const base = LANGUAGE_MARKERS[family];
|
|
13206
|
-
const extra = ctx.ext.languages[family]?.foreignBuiltins;
|
|
13207
|
-
if (!extra?.length) return base;
|
|
13208
|
-
return /* @__PURE__ */ new Set([...base ?? [], ...extra.map((s) => s.toLowerCase())]);
|
|
13209
|
-
};
|
|
13210
|
-
const families = /* @__PURE__ */ new Set([...Object.keys(LANGUAGE_MARKERS), ...Object.keys(ctx.ext.languages)]);
|
|
13211
13713
|
const gapsFor = (lang) => ({
|
|
13212
13714
|
...UNSUPPORTED_FLOW[lang] ?? {},
|
|
13213
13715
|
...ctx.ext.languages[lang]?.unsupportedFlow ?? {}
|
|
13214
13716
|
});
|
|
13215
|
-
|
|
13216
|
-
const comp = ctx.componentMap.get(intf.component);
|
|
13217
|
-
const lang = ctx.targetLanguageFor(comp?.subsystem);
|
|
13218
|
-
if (!lang) continue;
|
|
13219
|
-
const ownMarkers = markersFor(lang);
|
|
13220
|
-
if (!ownMarkers || ownMarkers.size === 0) continue;
|
|
13221
|
-
const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
13222
|
-
for (const m of intf.methods) {
|
|
13223
|
-
const refs = methodTypeRefs(m);
|
|
13224
|
-
for (const ref of refs) {
|
|
13225
|
-
const refLower = ref.toLowerCase();
|
|
13226
|
-
if (ownMarkers.has(refLower)) continue;
|
|
13227
|
-
for (const family of families) {
|
|
13228
|
-
if (family === lang) continue;
|
|
13229
|
-
if (markersFor(family)?.has(refLower)) {
|
|
13230
|
-
ctx.addIssue(
|
|
13231
|
-
"warning",
|
|
13232
|
-
"LANGUAGE_FOREIGN_BUILTIN",
|
|
13233
|
-
`Method "${m.name}" on interface "${intf.id}" uses "${ref}", a ${family} builtin, but the target language here is ${lang}. Use the ${lang} equivalent so implementers generate idiomatic code.`,
|
|
13234
|
-
intf.id,
|
|
13235
|
-
isDraftCtx
|
|
13236
|
-
);
|
|
13237
|
-
break;
|
|
13238
|
-
}
|
|
13239
|
-
}
|
|
13240
|
-
}
|
|
13241
|
-
}
|
|
13242
|
-
}
|
|
13717
|
+
const scans = [];
|
|
13243
13718
|
for (const impl of ctx.implementations) {
|
|
13244
13719
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
13245
13720
|
if (!contract) continue;
|
|
@@ -13248,28 +13723,54 @@ var init_target_language = __esm({
|
|
|
13248
13723
|
if (!lang) continue;
|
|
13249
13724
|
const gaps = gapsFor(lang);
|
|
13250
13725
|
if (Object.keys(gaps).length === 0) continue;
|
|
13251
|
-
const
|
|
13252
|
-
for (const
|
|
13253
|
-
for (const step of
|
|
13254
|
-
const constructs = [
|
|
13255
|
-
step.type === "loop" ? step.loopKind ?? (step.over ? "forEach" : "while") : step.type
|
|
13256
|
-
];
|
|
13257
|
-
if (step.detach) constructs.push("detach");
|
|
13258
|
-
for (const construct of constructs) {
|
|
13259
|
-
const guidance = gaps[construct];
|
|
13260
|
-
if (!guidance) continue;
|
|
13261
|
-
const label = construct === "doWhile" ? "a do-while loop" : construct === "forEach" || construct === "for" || construct === "while" ? `a ${construct} loop` : construct === "detach" ? "a detached (fire-and-forget) call" : `a ${construct} step`;
|
|
13262
|
-
ctx.addIssue(
|
|
13263
|
-
"warning",
|
|
13264
|
-
"LANGUAGE_FOREIGN_FLOW",
|
|
13265
|
-
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" uses ${label}, but the target language is ${lang}: ${guidance}.`,
|
|
13266
|
-
impl.id,
|
|
13267
|
-
isDraftCtx
|
|
13268
|
-
);
|
|
13269
|
-
}
|
|
13270
|
-
}
|
|
13726
|
+
const draft = ctx.isImplementationDraft(impl);
|
|
13727
|
+
for (const method of impl.methods) {
|
|
13728
|
+
for (const step of method.narrative) scans.push({ implementation: impl, method, step, lang, gaps, draft });
|
|
13271
13729
|
}
|
|
13272
13730
|
}
|
|
13731
|
+
for (const scan of scans) {
|
|
13732
|
+
for (const construct of constructsOf(scan.step)) {
|
|
13733
|
+
const guidance = scan.gaps[construct];
|
|
13734
|
+
if (!guidance) continue;
|
|
13735
|
+
ctx.addIssue(
|
|
13736
|
+
"warning",
|
|
13737
|
+
"LANGUAGE_FOREIGN_FLOW",
|
|
13738
|
+
`Step ${scan.step.stepNumber} of "${scan.method.name}" in implementation "${scan.implementation.id}" uses ${constructLabel(construct)}, but the target language is ${scan.lang}: ${guidance}.`,
|
|
13739
|
+
scan.implementation.id,
|
|
13740
|
+
scan.draft
|
|
13741
|
+
);
|
|
13742
|
+
}
|
|
13743
|
+
}
|
|
13744
|
+
}
|
|
13745
|
+
};
|
|
13746
|
+
}
|
|
13747
|
+
});
|
|
13748
|
+
|
|
13749
|
+
// src/core/rules/heuristic/technology-binding.ts
|
|
13750
|
+
var DATA_LAYER, technologyBindingRule;
|
|
13751
|
+
var init_technology_binding = __esm({
|
|
13752
|
+
"src/core/rules/heuristic/technology-binding.ts"() {
|
|
13753
|
+
"use strict";
|
|
13754
|
+
DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
|
|
13755
|
+
technologyBindingRule = {
|
|
13756
|
+
name: "technology-binding",
|
|
13757
|
+
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.",
|
|
13758
|
+
codes: [
|
|
13759
|
+
{ code: "TECH_ON_LOGIC_COMPONENT", defaultSeverity: "warning", summary: "Technology bound by a non-data-layer stereotype" }
|
|
13760
|
+
],
|
|
13761
|
+
check(ctx) {
|
|
13762
|
+
for (const impl of ctx.implementations) {
|
|
13763
|
+
const intf = ctx.interfaceMap.get(impl.contract);
|
|
13764
|
+
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
13765
|
+
if (!impl.technologies?.length || !comp || DATA_LAYER.has(comp.componentType)) continue;
|
|
13766
|
+
ctx.addIssue(
|
|
13767
|
+
"warning",
|
|
13768
|
+
"TECH_ON_LOGIC_COMPONENT",
|
|
13769
|
+
`Implementation "${impl.id}" binds technology (${impl.technologies.join(", ")}) on component "${comp.id}" (${comp.componentType}). Technology belongs behind a data-layer seam \u2014 extract an Adapter (or Store/Registry/Index) behind an intent interface and let this component depend on that.`,
|
|
13770
|
+
impl.id,
|
|
13771
|
+
ctx.isImplementationDraft(impl)
|
|
13772
|
+
);
|
|
13773
|
+
}
|
|
13273
13774
|
}
|
|
13274
13775
|
};
|
|
13275
13776
|
}
|
|
@@ -13298,19 +13799,29 @@ function contractIdentifiers(m) {
|
|
|
13298
13799
|
if (m.endpoint) parts.push(Object.values(m.endpoint).map(String).join(" "));
|
|
13299
13800
|
return parts.join(" ");
|
|
13300
13801
|
}
|
|
13301
|
-
|
|
13802
|
+
function implementationText(impl) {
|
|
13803
|
+
const parts = [impl.description, ...implementationSourceFiles(impl)];
|
|
13804
|
+
for (const m of impl.methods) {
|
|
13805
|
+
parts.push(m.intent);
|
|
13806
|
+
for (const s of m.narrative ?? []) {
|
|
13807
|
+
parts.push(s.description, s.targetComponent, s.targetMethod, s.condition, s.over, s.on, s.outcome, s.error);
|
|
13808
|
+
for (const c of s.cases ?? []) parts.push(c.value);
|
|
13809
|
+
for (const c of s.catches ?? []) parts.push(c.error);
|
|
13810
|
+
}
|
|
13811
|
+
}
|
|
13812
|
+
return parts.filter(Boolean).join(" ");
|
|
13813
|
+
}
|
|
13814
|
+
var technologyRule;
|
|
13302
13815
|
var init_technology_boundaries = __esm({
|
|
13303
13816
|
"src/core/rules/heuristic/technology-boundaries.ts"() {
|
|
13304
13817
|
"use strict";
|
|
13305
13818
|
init_models();
|
|
13306
|
-
DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
|
|
13307
13819
|
technologyRule = {
|
|
13308
13820
|
name: "technology-boundaries",
|
|
13309
|
-
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, L3 contract identifiers must stay intent-language
|
|
13821
|
+
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.",
|
|
13310
13822
|
codes: [
|
|
13311
13823
|
{ code: "TECH_LEAKAGE", defaultSeverity: "warning", summary: "Technology referenced outside its owning boundary" },
|
|
13312
|
-
{ code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" }
|
|
13313
|
-
{ code: "TECH_ON_LOGIC_COMPONENT", defaultSeverity: "warning", summary: "Technology bound by a non-data-layer stereotype" }
|
|
13824
|
+
{ code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" }
|
|
13314
13825
|
],
|
|
13315
13826
|
check(ctx) {
|
|
13316
13827
|
const ownerOf = /* @__PURE__ */ new Map();
|
|
@@ -13338,21 +13849,18 @@ var init_technology_boundaries = __esm({
|
|
|
13338
13849
|
}
|
|
13339
13850
|
return out;
|
|
13340
13851
|
};
|
|
13341
|
-
const
|
|
13852
|
+
const declarations = [];
|
|
13342
13853
|
for (const impl of ctx.implementations) {
|
|
13343
13854
|
if (!impl.technologies?.length) continue;
|
|
13344
13855
|
const intf = ctx.interfaceMap.get(impl.contract);
|
|
13345
13856
|
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
13346
13857
|
if (!comp) continue;
|
|
13347
|
-
|
|
13348
|
-
|
|
13349
|
-
|
|
13350
|
-
|
|
13351
|
-
|
|
13352
|
-
|
|
13353
|
-
ctx.isImplementationDraft(impl)
|
|
13354
|
-
);
|
|
13355
|
-
}
|
|
13858
|
+
for (const tech of impl.technologies) declarations.push({ comp, tech });
|
|
13859
|
+
}
|
|
13860
|
+
const scopes = /* @__PURE__ */ new Map();
|
|
13861
|
+
const scopeOf = (comp) => {
|
|
13862
|
+
const cached = scopes.get(comp.id);
|
|
13863
|
+
if (cached) return cached;
|
|
13356
13864
|
const compSet = ownsClosure(ownershipRoot(comp.id));
|
|
13357
13865
|
const scope = new Set(compSet);
|
|
13358
13866
|
for (const i of ctx.interfaces) if (compSet.has(i.component)) scope.add(i.id);
|
|
@@ -13367,15 +13875,18 @@ var init_technology_boundaries = __esm({
|
|
|
13367
13875
|
if (subId === s.id || subId.startsWith(`${s.id}::`)) scope.add(s.id);
|
|
13368
13876
|
}
|
|
13369
13877
|
}
|
|
13370
|
-
|
|
13371
|
-
|
|
13372
|
-
|
|
13373
|
-
|
|
13374
|
-
|
|
13375
|
-
|
|
13376
|
-
|
|
13377
|
-
|
|
13378
|
-
}
|
|
13878
|
+
scopes.set(comp.id, scope);
|
|
13879
|
+
return scope;
|
|
13880
|
+
};
|
|
13881
|
+
const homes = /* @__PURE__ */ new Map();
|
|
13882
|
+
for (const { comp, tech } of declarations) {
|
|
13883
|
+
const match = makeMatcher(tech);
|
|
13884
|
+
if (!match) continue;
|
|
13885
|
+
const key = tech.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
|
|
13886
|
+
const home = homes.get(key) ?? { label: tech, match, ownerComponents: /* @__PURE__ */ new Set(), scope: /* @__PURE__ */ new Set() };
|
|
13887
|
+
home.ownerComponents.add(comp.id);
|
|
13888
|
+
for (const id of scopeOf(comp)) home.scope.add(id);
|
|
13889
|
+
homes.set(key, home);
|
|
13379
13890
|
}
|
|
13380
13891
|
if (homes.size === 0) return;
|
|
13381
13892
|
const ownersDesc = (h) => [...h.ownerComponents].map((c) => `"${c}"`).join(", ");
|
|
@@ -13439,16 +13950,7 @@ var init_technology_boundaries = __esm({
|
|
|
13439
13950
|
}
|
|
13440
13951
|
for (const impl of ctx.implementations) {
|
|
13441
13952
|
if (home.scope.has(impl.id)) continue;
|
|
13442
|
-
|
|
13443
|
-
for (const m of impl.methods) {
|
|
13444
|
-
parts.push(m.intent);
|
|
13445
|
-
for (const s of m.narrative ?? []) {
|
|
13446
|
-
parts.push(s.description, s.targetComponent, s.targetMethod, s.condition, s.over, s.on, s.outcome, s.error);
|
|
13447
|
-
for (const c of s.cases ?? []) parts.push(c.value);
|
|
13448
|
-
for (const c of s.catches ?? []) parts.push(c.error);
|
|
13449
|
-
}
|
|
13450
|
-
}
|
|
13451
|
-
if (home.match(parts.filter(Boolean).join(" "))) {
|
|
13953
|
+
if (home.match(implementationText(impl))) {
|
|
13452
13954
|
ctx.addIssue(
|
|
13453
13955
|
"warning",
|
|
13454
13956
|
"TECH_LEAKAGE",
|
|
@@ -14137,12 +14639,15 @@ var init_repository = __esm({
|
|
|
14137
14639
|
"src/core/rules/repository.ts"() {
|
|
14138
14640
|
"use strict";
|
|
14139
14641
|
init_hierarchy_integrity();
|
|
14140
|
-
|
|
14642
|
+
init_reserved_id_segments();
|
|
14643
|
+
init_namespace_shadowing();
|
|
14141
14644
|
init_roundtrip_serialization();
|
|
14142
14645
|
init_type_declarations();
|
|
14143
14646
|
init_field_type_references();
|
|
14144
14647
|
init_signature_type_references();
|
|
14145
|
-
|
|
14648
|
+
init_public_surface_binding();
|
|
14649
|
+
init_public_surface_declared_type();
|
|
14650
|
+
init_public_surface_bound_contract();
|
|
14146
14651
|
init_lint_allows();
|
|
14147
14652
|
init_contract_symmetry();
|
|
14148
14653
|
init_narrative_target_references();
|
|
@@ -14152,14 +14657,19 @@ var init_repository = __esm({
|
|
|
14152
14657
|
init_narrative_step_config();
|
|
14153
14658
|
init_narrative_reachability();
|
|
14154
14659
|
init_narrative_jump_edges();
|
|
14155
|
-
|
|
14660
|
+
init_meaningless_branches();
|
|
14661
|
+
init_inescapable_cycles();
|
|
14662
|
+
init_unconditional_call_cycles();
|
|
14156
14663
|
init_narrative_detail();
|
|
14664
|
+
init_detail_sufficiency();
|
|
14157
14665
|
init_portal_fields();
|
|
14158
14666
|
init_durability_declaration();
|
|
14159
14667
|
init_logic_declaration();
|
|
14160
14668
|
init_retired_stereotypes();
|
|
14161
14669
|
init_portal_endpoints();
|
|
14670
|
+
init_non_portal_endpoints();
|
|
14162
14671
|
init_portal_call_auth();
|
|
14672
|
+
init_auth_source_wiring();
|
|
14163
14673
|
init_subsystem_boundary_dependencies();
|
|
14164
14674
|
init_logic_dependency_class();
|
|
14165
14675
|
init_data_block_dependencies();
|
|
@@ -14175,7 +14685,9 @@ var init_repository = __esm({
|
|
|
14175
14685
|
init_pack_profile_stereotypes();
|
|
14176
14686
|
init_pattern_references();
|
|
14177
14687
|
init_component_variants();
|
|
14178
|
-
|
|
14688
|
+
init_assertion_forbidden_edges();
|
|
14689
|
+
init_assertion_required_fields();
|
|
14690
|
+
init_assertion_endpoint_shapes();
|
|
14179
14691
|
init_pack_resolution();
|
|
14180
14692
|
init_pack_reproducibility();
|
|
14181
14693
|
init_dependency_cycles();
|
|
@@ -14183,10 +14695,14 @@ var init_repository = __esm({
|
|
|
14183
14695
|
init_dispatch_step_routing();
|
|
14184
14696
|
init_lifecycle_entrypoints();
|
|
14185
14697
|
init_unused_detection();
|
|
14698
|
+
init_invoked_by_description();
|
|
14699
|
+
init_unused_types();
|
|
14186
14700
|
init_durability_round_trip();
|
|
14187
14701
|
init_untyped_seams();
|
|
14188
14702
|
init_prose_claims();
|
|
14703
|
+
init_unique_invariant_ids();
|
|
14189
14704
|
init_invariant_backing();
|
|
14705
|
+
init_invariant_references();
|
|
14190
14706
|
init_event_topology();
|
|
14191
14707
|
init_source_file_linkage();
|
|
14192
14708
|
init_method_realization();
|
|
@@ -14194,9 +14710,14 @@ var init_repository = __esm({
|
|
|
14194
14710
|
init_call_conformance();
|
|
14195
14711
|
init_hidden_state();
|
|
14196
14712
|
init_dependency_conformance();
|
|
14197
|
-
|
|
14713
|
+
init_integration_sim_declaration();
|
|
14714
|
+
init_integration_sim_file();
|
|
14715
|
+
init_integration_sim_wiring();
|
|
14716
|
+
init_integration_sim_coverage();
|
|
14198
14717
|
init_coupling_health();
|
|
14199
|
-
|
|
14718
|
+
init_signature_language_builtins();
|
|
14719
|
+
init_narrative_language_constructs();
|
|
14720
|
+
init_technology_binding();
|
|
14200
14721
|
init_technology_boundaries();
|
|
14201
14722
|
init_naming_conventions();
|
|
14202
14723
|
init_complexity_and_metadata();
|
|
@@ -14206,8 +14727,11 @@ var init_repository = __esm({
|
|
|
14206
14727
|
SDD_RULES = [
|
|
14207
14728
|
hierarchyRule,
|
|
14208
14729
|
// Namespace integrity right after hierarchy: unresolvable/unwritable ids
|
|
14209
|
-
// explain many downstream findings, so surface them early in the list.
|
|
14210
|
-
|
|
14730
|
+
// explain many downstream findings, so surface them early in the list. Two
|
|
14731
|
+
// questions of the same ids: the segment no id may spend, and the local name
|
|
14732
|
+
// that would anchor a bare reference to the root instead.
|
|
14733
|
+
reservedIdSegmentsRule,
|
|
14734
|
+
namespaceShadowingRule,
|
|
14211
14735
|
roundtripRule,
|
|
14212
14736
|
// The type vocabulary in three questions: what a type declares about
|
|
14213
14737
|
// itself, then the identifiers its fields name, then the ones its
|
|
@@ -14233,18 +14757,30 @@ var init_repository = __esm({
|
|
|
14233
14757
|
narrativeStepConfigRule,
|
|
14234
14758
|
narrativeReachabilityRule,
|
|
14235
14759
|
narrativeJumpEdgesRule,
|
|
14236
|
-
// Antipatterns right after flow soundness: they
|
|
14237
|
-
//
|
|
14238
|
-
|
|
14760
|
+
// Antipatterns right after flow soundness: they read the same step graphs
|
|
14761
|
+
// and only make sense once the graphs are structurally valid. Three things
|
|
14762
|
+
// structure alone can prove, in widening scope: a decision that decides
|
|
14763
|
+
// nothing, a step cycle nothing leaves, and a call cycle with no guard.
|
|
14764
|
+
meaninglessBranchesRule,
|
|
14765
|
+
inescapableCyclesRule,
|
|
14766
|
+
unconditionalCallCyclesRule,
|
|
14767
|
+
// The detail dial in two questions: does a method carry the detail its level
|
|
14768
|
+
// promises, and is that level low enough to be hiding something.
|
|
14239
14769
|
narrativeDetailRule,
|
|
14770
|
+
detailSufficiencyRule,
|
|
14240
14771
|
// Field shape before endpoint bindings: a Portal-only field on the wrong
|
|
14241
14772
|
// stereotype explains the endpoint findings around it, and this half is
|
|
14242
14773
|
// spec-scoped so the write boundary refuses it first.
|
|
14243
14774
|
portalFieldsRule,
|
|
14775
|
+
// Endpoints from both sides: a Portal binds every method to its transport,
|
|
14776
|
+
// and nothing that is not a Portal may bind one at all.
|
|
14244
14777
|
portalsRule,
|
|
14245
|
-
|
|
14246
|
-
//
|
|
14778
|
+
nonPortalEndpointsRule,
|
|
14779
|
+
// Cross-call auth rides with the portal family, in two questions: does a
|
|
14780
|
+
// call into an authed Portal present a credential properly, and does the
|
|
14781
|
+
// source it names resolve to a provider it is wired to.
|
|
14247
14782
|
portalCallAuthRule,
|
|
14783
|
+
authSourceWiringRule,
|
|
14248
14784
|
// Dependencies in five questions, one owner each: where an edge is
|
|
14249
14785
|
// allowed to LAND (the boundary, and what an unresolved id means), then
|
|
14250
14786
|
// the intra-subsystem matrix by the layer that answers for it — an
|
|
@@ -14274,9 +14810,18 @@ var init_repository = __esm({
|
|
|
14274
14810
|
patternReferencesRule,
|
|
14275
14811
|
variantReferencesRule,
|
|
14276
14812
|
// Pack-instantiated declarative doctrine rides with the pack-reference
|
|
14277
|
-
// family: same data source, same provenance-bearing findings.
|
|
14278
|
-
|
|
14279
|
-
|
|
14813
|
+
// family: same data source, same provenance-bearing findings. One rule per
|
|
14814
|
+
// assertion kind — the kind is what a pack author writes, and each kind
|
|
14815
|
+
// asks its own question of its own collection.
|
|
14816
|
+
assertionForbiddenEdgesRule,
|
|
14817
|
+
assertionRequiredFieldsRule,
|
|
14818
|
+
assertionEndpointShapesRule,
|
|
14819
|
+
// The published surface in three questions: what backs the entry, whether
|
|
14820
|
+
// that component's stereotype can realize the type it declares, and whether
|
|
14821
|
+
// the contract it binds is that component's own.
|
|
14822
|
+
publicSurfaceBindingRule,
|
|
14823
|
+
publicSurfaceDeclaredTypeRule,
|
|
14824
|
+
publicSurfaceBoundContractRule,
|
|
14280
14825
|
cyclesRule,
|
|
14281
14826
|
// Semantic-edge family: dispatch/lifecycle validity BEFORE reachability so a
|
|
14282
14827
|
// reader sees the broken edge finding next to the unused-detection fallout
|
|
@@ -14284,7 +14829,11 @@ var init_repository = __esm({
|
|
|
14284
14829
|
dispatchTableBindingsRule,
|
|
14285
14830
|
dispatchStepRoutingRule,
|
|
14286
14831
|
lifecycleRule,
|
|
14832
|
+
// The declared entrypoint's own prose before the walk that its declaration
|
|
14833
|
+
// silences, then the walk, then the types no walk can reach.
|
|
14834
|
+
invokedByDescriptionRule,
|
|
14287
14835
|
reachabilityRule,
|
|
14836
|
+
unusedTypesRule,
|
|
14288
14837
|
// The declaration (spec-scoped, refused at the write boundary) before the
|
|
14289
14838
|
// round-trip consequences it enables.
|
|
14290
14839
|
durabilityDeclarationRule,
|
|
@@ -14296,8 +14845,13 @@ var init_repository = __esm({
|
|
|
14296
14845
|
untypedSeamRule,
|
|
14297
14846
|
proseClaimRule,
|
|
14298
14847
|
// Invariant registry rides with the semantic-edge family: declared entity
|
|
14299
|
-
// invariants must be asserted on every write path (declarations, not
|
|
14848
|
+
// invariants must be asserted on every write path (declarations, not
|
|
14849
|
+
// proofs). Three questions of one registry: are the entity's ids unique,
|
|
14850
|
+
// does each invariant reach every write path of its owner, and does every
|
|
14851
|
+
// asserted reference name something declared.
|
|
14852
|
+
uniqueInvariantIdsRule,
|
|
14300
14853
|
invariantBackingRule,
|
|
14854
|
+
invariantReferencesRule,
|
|
14301
14855
|
// Pub/sub completeness: emitted topics need subscribers and vice versa.
|
|
14302
14856
|
eventTopologyRule,
|
|
14303
14857
|
// Code↔spec: structural conformance consumes the injected CodeModel (built
|
|
@@ -14316,10 +14870,22 @@ var init_repository = __esm({
|
|
|
14316
14870
|
hiddenStateRule,
|
|
14317
14871
|
dependencyConformanceRule,
|
|
14318
14872
|
// Integration wiring proof rides after the code↔spec family: it consumes
|
|
14319
|
-
// the same code model and speaks about the same sourcePath modules.
|
|
14320
|
-
|
|
14873
|
+
// the same code model and speaks about the same sourcePath modules. Four
|
|
14874
|
+
// questions about one harness, in the order a reader meets them: is one
|
|
14875
|
+
// expected here, does the declared one exist, does it wire the real
|
|
14876
|
+
// modules, does it name every narrated path.
|
|
14877
|
+
integrationSimDeclarationRule,
|
|
14878
|
+
integrationSimFileRule,
|
|
14879
|
+
integrationSimWiringRule,
|
|
14880
|
+
integrationSimCoverageRule,
|
|
14321
14881
|
couplingRule,
|
|
14322
|
-
|
|
14882
|
+
// Target-language fit in two questions: what a CONTRACT may name, and what
|
|
14883
|
+
// a NARRATIVE may describe.
|
|
14884
|
+
signatureLanguageBuiltinsRule,
|
|
14885
|
+
narrativeLanguageConstructsRule,
|
|
14886
|
+
// The declaration before the consequences: which stereotype may bind a
|
|
14887
|
+
// technology at all, then where its name may appear.
|
|
14888
|
+
technologyBindingRule,
|
|
14323
14889
|
technologyRule,
|
|
14324
14890
|
namingRule,
|
|
14325
14891
|
complexityRule,
|
|
@@ -15156,6 +15722,10 @@ function buildRuleContext(opts) {
|
|
|
15156
15722
|
const ownershipIndex = () => ownershipMemo ??= buildOwnershipIndex(ctx);
|
|
15157
15723
|
let dependencyEdgesMemo;
|
|
15158
15724
|
const dependencyEdges = () => dependencyEdgesMemo ??= buildDependencyEdges(ctx);
|
|
15725
|
+
let specIdsMemo;
|
|
15726
|
+
const specIds = () => specIdsMemo ??= buildSpecIds(ctx);
|
|
15727
|
+
let implementationMethodsMemo;
|
|
15728
|
+
const implementationMethods = () => implementationMethodsMemo ??= buildImplementationMethods(ctx);
|
|
15159
15729
|
const ctx = {
|
|
15160
15730
|
system,
|
|
15161
15731
|
subsystems,
|
|
@@ -15190,6 +15760,8 @@ function buildRuleContext(opts) {
|
|
|
15190
15760
|
importGraph,
|
|
15191
15761
|
ownershipIndex,
|
|
15192
15762
|
dependencyEdges,
|
|
15763
|
+
specIds,
|
|
15764
|
+
implementationMethods,
|
|
15193
15765
|
complexityConfigFor,
|
|
15194
15766
|
documentationConfigFor,
|
|
15195
15767
|
namingConfigFor,
|
|
@@ -17054,8 +17626,8 @@ function generateSequenceDiagram(componentId, methodName, options) {
|
|
|
17054
17626
|
const componentById = new Map(graph.components.map((c) => [c.id, c]));
|
|
17055
17627
|
const resolveComponent = (id) => {
|
|
17056
17628
|
if (componentById.has(id)) return componentById.get(id);
|
|
17057
|
-
const
|
|
17058
|
-
return
|
|
17629
|
+
const matches = graph.components.filter((c) => c.id.endsWith(`::${id}`));
|
|
17630
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
17059
17631
|
};
|
|
17060
17632
|
const entry = resolveComponent(componentId);
|
|
17061
17633
|
if (!entry) {
|
|
@@ -18316,9 +18888,9 @@ var init_specs2 = __esm({
|
|
|
18316
18888
|
}
|
|
18317
18889
|
}
|
|
18318
18890
|
const suffix = `::${id}`;
|
|
18319
|
-
const
|
|
18320
|
-
if (
|
|
18321
|
-
return index.paths.subsystem[
|
|
18891
|
+
const matches = Object.keys(index.paths.subsystem).filter((key) => key.endsWith(suffix));
|
|
18892
|
+
if (matches.length === 1) {
|
|
18893
|
+
return index.paths.subsystem[matches[0]];
|
|
18322
18894
|
}
|
|
18323
18895
|
if (pathExists(this.paths.specsSubsystemsDir()) && listFiles(this.paths.specsSubsystemsDir(), ".yaml").length > 0) {
|
|
18324
18896
|
return path16.join(this.paths.specsSubsystemsDir(), `${id}.yaml`);
|