@wairon/cli 5.1.1-dev.26 → 5.1.1-dev.28
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/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
68
|
+
WAIRON_VERSION = "5.1.1-dev.28";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -1966,8 +1966,8 @@ function extractTypeIdentifiers(typeStr) {
|
|
|
1966
1966
|
cleaned = cleaned.replace(/(?<=\S)\s*\([^)]*\)\s*$/, "");
|
|
1967
1967
|
cleaned = cleaned.replace(/(?<=\S)\s+[-—:]\s+[a-z\s_-]+$/, "");
|
|
1968
1968
|
cleaned = cleaned.replace(/(["'`])(?:\\.|[^\\])*?\1/g, " ");
|
|
1969
|
-
const
|
|
1970
|
-
return
|
|
1969
|
+
const matches = cleaned.match(/[a-zA-Z0-9_-]+(?:::[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+)*/g) || [];
|
|
1970
|
+
return matches.filter((t) => {
|
|
1971
1971
|
if (!/[a-zA-Z0-9]/.test(t)) return false;
|
|
1972
1972
|
if (/^\d+$/.test(t)) return false;
|
|
1973
1973
|
return true;
|
|
@@ -8931,6 +8931,37 @@ function buildDependencyEdges(ctx) {
|
|
|
8931
8931
|
}
|
|
8932
8932
|
return { all, matrix };
|
|
8933
8933
|
}
|
|
8934
|
+
function buildSpecIds(ctx) {
|
|
8935
|
+
const out = [];
|
|
8936
|
+
for (const s of ctx.subsystems) out.push({ id: s.id, kind: "Subsystem" });
|
|
8937
|
+
for (const c of ctx.components) out.push({ id: c.id, kind: "Component" });
|
|
8938
|
+
for (const i of ctx.interfaces) out.push({ id: i.id, kind: "Interface" });
|
|
8939
|
+
for (const im of ctx.implementations) out.push({ id: im.id, kind: "Implementation" });
|
|
8940
|
+
for (const t of ctx.types) out.push({ id: t.id, kind: "Type" });
|
|
8941
|
+
return out;
|
|
8942
|
+
}
|
|
8943
|
+
function buildImplementationMethods(ctx) {
|
|
8944
|
+
const out = [];
|
|
8945
|
+
for (const impl of ctx.implementations) {
|
|
8946
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
8947
|
+
if (!contract) continue;
|
|
8948
|
+
const component = ctx.componentMap.get(contract.component);
|
|
8949
|
+
if (!component) continue;
|
|
8950
|
+
if (ctx.isInChainedSubproject(component.subsystem)) continue;
|
|
8951
|
+
const draftContext = ctx.isImplementationDraft(impl);
|
|
8952
|
+
for (const method2 of impl.methods) {
|
|
8953
|
+
const sourceFile = methodSourceFile(method2, impl.sourcePath);
|
|
8954
|
+
out.push({
|
|
8955
|
+
implementation: impl,
|
|
8956
|
+
method: method2,
|
|
8957
|
+
component,
|
|
8958
|
+
...sourceFile !== void 0 ? { sourceFile } : {},
|
|
8959
|
+
draftContext
|
|
8960
|
+
});
|
|
8961
|
+
}
|
|
8962
|
+
}
|
|
8963
|
+
return out;
|
|
8964
|
+
}
|
|
8934
8965
|
function offTreeEdge(ctx, from, ref, fromDraft) {
|
|
8935
8966
|
const base = { from, ref, retired: false, draftContext: fromDraft, licensed: false };
|
|
8936
8967
|
const external = ctx.isExternalNamespaceRef(ref);
|
|
@@ -9031,48 +9062,58 @@ var init_hierarchy_integrity = __esm({
|
|
|
9031
9062
|
}
|
|
9032
9063
|
});
|
|
9033
9064
|
|
|
9034
|
-
// src/core/rules/integrity/
|
|
9035
|
-
var
|
|
9036
|
-
var
|
|
9037
|
-
"src/core/rules/integrity/
|
|
9065
|
+
// src/core/rules/integrity/reserved-id-segments.ts
|
|
9066
|
+
var reservedIdSegmentsRule;
|
|
9067
|
+
var init_reserved_id_segments = __esm({
|
|
9068
|
+
"src/core/rules/integrity/reserved-id-segments.ts"() {
|
|
9038
9069
|
"use strict";
|
|
9039
|
-
|
|
9040
|
-
name: "
|
|
9041
|
-
description: '
|
|
9070
|
+
reservedIdSegmentsRule = {
|
|
9071
|
+
name: "reserved-id-segments",
|
|
9072
|
+
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.',
|
|
9073
|
+
codes: [
|
|
9074
|
+
{ code: "RESERVED_ID_SEGMENT", defaultSeverity: "error", summary: 'Id uses the reserved namespace keyword "super"' }
|
|
9075
|
+
],
|
|
9076
|
+
check(ctx) {
|
|
9077
|
+
for (const spec of ctx.specIds()) {
|
|
9078
|
+
if (!ctx.isSpecInScope(spec.id)) continue;
|
|
9079
|
+
if (!spec.id.split("::").includes("super")) continue;
|
|
9080
|
+
ctx.addIssue(
|
|
9081
|
+
"error",
|
|
9082
|
+
"RESERVED_ID_SEGMENT",
|
|
9083
|
+
`${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.`,
|
|
9084
|
+
spec.id
|
|
9085
|
+
);
|
|
9086
|
+
}
|
|
9087
|
+
}
|
|
9088
|
+
};
|
|
9089
|
+
}
|
|
9090
|
+
});
|
|
9091
|
+
|
|
9092
|
+
// src/core/rules/integrity/namespace-shadowing.ts
|
|
9093
|
+
var namespaceShadowingRule;
|
|
9094
|
+
var init_namespace_shadowing = __esm({
|
|
9095
|
+
"src/core/rules/integrity/namespace-shadowing.ts"() {
|
|
9096
|
+
"use strict";
|
|
9097
|
+
namespaceShadowingRule = {
|
|
9098
|
+
name: "namespace-shadowing",
|
|
9099
|
+
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.",
|
|
9042
9100
|
codes: [
|
|
9043
|
-
{ code: "RESERVED_ID_SEGMENT", defaultSeverity: "error", summary: 'Id uses the reserved namespace keyword "super"' },
|
|
9044
9101
|
{ code: "NAMESPACE_SHADOWING", defaultSeverity: "error", summary: "Subproject-local id shadows a root subsystem id (bare references anchor to the root)" }
|
|
9045
9102
|
],
|
|
9046
9103
|
check(ctx) {
|
|
9047
9104
|
const rootSubs = new Set(ctx.subsystems.filter((s) => !s.id.includes("::")).map((s) => s.id));
|
|
9048
|
-
const
|
|
9049
|
-
if (!ctx.isSpecInScope(id))
|
|
9050
|
-
const segments = id.split("::");
|
|
9051
|
-
|
|
9052
|
-
|
|
9053
|
-
|
|
9054
|
-
|
|
9055
|
-
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
9059
|
-
|
|
9060
|
-
const local = segments[segments.length - 1];
|
|
9061
|
-
if (rootSubs.has(local)) {
|
|
9062
|
-
ctx.addIssue(
|
|
9063
|
-
"error",
|
|
9064
|
-
"NAMESPACE_SHADOWING",
|
|
9065
|
-
`${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.`,
|
|
9066
|
-
id
|
|
9067
|
-
);
|
|
9068
|
-
}
|
|
9069
|
-
}
|
|
9070
|
-
};
|
|
9071
|
-
for (const s of ctx.subsystems) checkId(s.id, "Subsystem");
|
|
9072
|
-
for (const c of ctx.components) checkId(c.id, "Component");
|
|
9073
|
-
for (const i of ctx.interfaces) checkId(i.id, "Interface");
|
|
9074
|
-
for (const im of ctx.implementations) checkId(im.id, "Implementation");
|
|
9075
|
-
for (const t of ctx.types) checkId(t.id, "Type");
|
|
9105
|
+
for (const spec of ctx.specIds()) {
|
|
9106
|
+
if (!ctx.isSpecInScope(spec.id)) continue;
|
|
9107
|
+
const segments = spec.id.split("::");
|
|
9108
|
+
const local = segments[segments.length - 1];
|
|
9109
|
+
if (segments.length <= 1 || !rootSubs.has(local)) continue;
|
|
9110
|
+
ctx.addIssue(
|
|
9111
|
+
"error",
|
|
9112
|
+
"NAMESPACE_SHADOWING",
|
|
9113
|
+
`${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.`,
|
|
9114
|
+
spec.id
|
|
9115
|
+
);
|
|
9116
|
+
}
|
|
9076
9117
|
}
|
|
9077
9118
|
};
|
|
9078
9119
|
}
|
|
@@ -9217,10 +9258,48 @@ var init_signature_type_references = __esm({
|
|
|
9217
9258
|
}
|
|
9218
9259
|
});
|
|
9219
9260
|
|
|
9220
|
-
// src/core/rules/integrity/public-surface.ts
|
|
9221
|
-
var
|
|
9222
|
-
var
|
|
9223
|
-
"src/core/rules/integrity/public-surface.ts"() {
|
|
9261
|
+
// src/core/rules/integrity/public-surface-binding.ts
|
|
9262
|
+
var publicSurfaceBindingRule;
|
|
9263
|
+
var init_public_surface_binding = __esm({
|
|
9264
|
+
"src/core/rules/integrity/public-surface-binding.ts"() {
|
|
9265
|
+
"use strict";
|
|
9266
|
+
init_models();
|
|
9267
|
+
publicSurfaceBindingRule = {
|
|
9268
|
+
name: "public-surface-binding",
|
|
9269
|
+
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.",
|
|
9270
|
+
codes: [
|
|
9271
|
+
{ code: "PUBLIC_INTERFACE_UNBOUND", defaultSeverity: "error", summary: "Public interface with no backing component" },
|
|
9272
|
+
{ code: "PUBLIC_INTERFACE_INVALID_COMPONENT", defaultSeverity: "error", summary: "Public interface references a non-existent component" },
|
|
9273
|
+
{ code: "PUBLIC_INTERFACE_FOREIGN_COMPONENT", defaultSeverity: "error", summary: "Subsystem publishing a component it does not own" }
|
|
9274
|
+
],
|
|
9275
|
+
check(ctx) {
|
|
9276
|
+
for (const sub of ctx.subsystems) {
|
|
9277
|
+
const isDraftCtx = isDraftSubsystem(sub);
|
|
9278
|
+
for (const pi of sub.publicInterfaces) {
|
|
9279
|
+
if (!pi.component) {
|
|
9280
|
+
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);
|
|
9281
|
+
continue;
|
|
9282
|
+
}
|
|
9283
|
+
const backing = ctx.componentMap.get(pi.component);
|
|
9284
|
+
if (!backing) {
|
|
9285
|
+
ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_COMPONENT", `Subsystem "${sub.id}" public interface references component "${pi.component}" which does not exist.`, sub.id, isDraftCtx);
|
|
9286
|
+
continue;
|
|
9287
|
+
}
|
|
9288
|
+
const isSubsystemOwner = backing.subsystem === sub.id || backing.subsystem.startsWith(sub.id + "::");
|
|
9289
|
+
if (!isSubsystemOwner) {
|
|
9290
|
+
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);
|
|
9291
|
+
}
|
|
9292
|
+
}
|
|
9293
|
+
}
|
|
9294
|
+
}
|
|
9295
|
+
};
|
|
9296
|
+
}
|
|
9297
|
+
});
|
|
9298
|
+
|
|
9299
|
+
// src/core/rules/integrity/public-surface-declared-type.ts
|
|
9300
|
+
var pubTypeMatches, EVENT_VOCAB, expectedFor, publicSurfaceDeclaredTypeRule;
|
|
9301
|
+
var init_public_surface_declared_type = __esm({
|
|
9302
|
+
"src/core/rules/integrity/public-surface-declared-type.ts"() {
|
|
9224
9303
|
"use strict";
|
|
9225
9304
|
init_models();
|
|
9226
9305
|
pubTypeMatches = (piType, ct, portalType) => {
|
|
@@ -9254,45 +9333,22 @@ var init_public_surface = __esm({
|
|
|
9254
9333
|
return "a compatible component";
|
|
9255
9334
|
}
|
|
9256
9335
|
};
|
|
9257
|
-
|
|
9258
|
-
name: "public-surface",
|
|
9259
|
-
description: "
|
|
9336
|
+
publicSurfaceDeclaredTypeRule = {
|
|
9337
|
+
name: "public-surface-declared-type",
|
|
9338
|
+
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.",
|
|
9260
9339
|
codes: [
|
|
9261
|
-
{ code: "PUBLIC_INTERFACE_UNBOUND", defaultSeverity: "error", summary: "Public interface with no backing component" },
|
|
9262
|
-
{ code: "PUBLIC_INTERFACE_INVALID_COMPONENT", defaultSeverity: "error", summary: "Public interface references a non-existent component" },
|
|
9263
|
-
{ code: "PUBLIC_INTERFACE_FOREIGN_COMPONENT", defaultSeverity: "error", summary: "Subsystem publishing a component it does not own" },
|
|
9264
9340
|
{ code: "PUBLIC_INTERFACE_TYPE_MISMATCH", defaultSeverity: "error", summary: "Backing component cannot realize the declared interface type" },
|
|
9265
|
-
{ code: "PUBLIC_INTERFACE_INVALID_INTERFACE", defaultSeverity: "error", summary: "Bound L3 interface missing or belonging to another component" },
|
|
9266
9341
|
{ code: "PUBLIC_INTERFACE_EVENT_MISTYPED", defaultSeverity: "warning", summary: "Custom interface describing eventing backed by a non-event component" }
|
|
9267
9342
|
],
|
|
9268
9343
|
check(ctx) {
|
|
9269
9344
|
for (const sub of ctx.subsystems) {
|
|
9270
9345
|
const isDraftCtx = isDraftSubsystem(sub);
|
|
9271
9346
|
for (const pi of sub.publicInterfaces) {
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
continue;
|
|
9275
|
-
}
|
|
9276
|
-
const backing = ctx.componentMap.get(pi.component);
|
|
9277
|
-
if (!backing) {
|
|
9278
|
-
ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_COMPONENT", `Subsystem "${sub.id}" public interface references component "${pi.component}" which does not exist.`, sub.id, isDraftCtx);
|
|
9279
|
-
continue;
|
|
9280
|
-
}
|
|
9281
|
-
const isSubsystemOwner = backing.subsystem === sub.id || backing.subsystem.startsWith(sub.id + "::");
|
|
9282
|
-
if (!isSubsystemOwner) {
|
|
9283
|
-
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);
|
|
9284
|
-
}
|
|
9347
|
+
const backing = pi.component ? ctx.componentMap.get(pi.component) : void 0;
|
|
9348
|
+
if (!backing) continue;
|
|
9285
9349
|
if (!pubTypeMatches(pi.type, backing.componentType, backing.portalType)) {
|
|
9286
9350
|
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);
|
|
9287
9351
|
}
|
|
9288
|
-
if (pi.interface) {
|
|
9289
|
-
const intf = ctx.interfaceMap.get(pi.interface);
|
|
9290
|
-
if (!intf) {
|
|
9291
|
-
ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" public interface references interface "${pi.interface}" which does not exist.`, sub.id, isDraftCtx);
|
|
9292
|
-
} else if (intf.component !== pi.component) {
|
|
9293
|
-
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);
|
|
9294
|
-
}
|
|
9295
|
-
}
|
|
9296
9352
|
if (pi.type === "Custom" && EVENT_VOCAB.test(pi.details)) {
|
|
9297
9353
|
const eventCapable = backing.componentType === "Observer" || backing.componentType === "Portal" && backing.portalType === "MessageBus";
|
|
9298
9354
|
if (!eventCapable) {
|
|
@@ -9306,6 +9362,37 @@ var init_public_surface = __esm({
|
|
|
9306
9362
|
}
|
|
9307
9363
|
});
|
|
9308
9364
|
|
|
9365
|
+
// src/core/rules/integrity/public-surface-bound-contract.ts
|
|
9366
|
+
var publicSurfaceBoundContractRule;
|
|
9367
|
+
var init_public_surface_bound_contract = __esm({
|
|
9368
|
+
"src/core/rules/integrity/public-surface-bound-contract.ts"() {
|
|
9369
|
+
"use strict";
|
|
9370
|
+
init_models();
|
|
9371
|
+
publicSurfaceBoundContractRule = {
|
|
9372
|
+
name: "public-surface-bound-contract",
|
|
9373
|
+
description: "A publicInterface entry that binds an L3 interface must bind one that exists and that belongs to the component the entry publishes.",
|
|
9374
|
+
codes: [
|
|
9375
|
+
{ code: "PUBLIC_INTERFACE_INVALID_INTERFACE", defaultSeverity: "error", summary: "Bound L3 interface missing or belonging to another component" }
|
|
9376
|
+
],
|
|
9377
|
+
check(ctx) {
|
|
9378
|
+
for (const sub of ctx.subsystems) {
|
|
9379
|
+
const isDraftCtx = isDraftSubsystem(sub);
|
|
9380
|
+
for (const pi of sub.publicInterfaces) {
|
|
9381
|
+
if (!pi.interface) continue;
|
|
9382
|
+
if (!pi.component || !ctx.componentMap.has(pi.component)) continue;
|
|
9383
|
+
const intf = ctx.interfaceMap.get(pi.interface);
|
|
9384
|
+
if (!intf) {
|
|
9385
|
+
ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_INTERFACE", `Subsystem "${sub.id}" public interface references interface "${pi.interface}" which does not exist.`, sub.id, isDraftCtx);
|
|
9386
|
+
} else if (intf.component !== pi.component) {
|
|
9387
|
+
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);
|
|
9388
|
+
}
|
|
9389
|
+
}
|
|
9390
|
+
}
|
|
9391
|
+
}
|
|
9392
|
+
};
|
|
9393
|
+
}
|
|
9394
|
+
});
|
|
9395
|
+
|
|
9309
9396
|
// src/core/rules/integrity/lint-allows.ts
|
|
9310
9397
|
var lintAllowsRule;
|
|
9311
9398
|
var init_lint_allows = __esm({
|
|
@@ -9911,7 +9998,7 @@ var init_narrative_jump_edges = __esm({
|
|
|
9911
9998
|
}
|
|
9912
9999
|
});
|
|
9913
10000
|
|
|
9914
|
-
// src/core/rules/narrative/
|
|
10001
|
+
// src/core/rules/narrative/completed-step-graph.ts
|
|
9915
10002
|
function completedStepGraph(steps) {
|
|
9916
10003
|
const numbers = steps.flatMap((s) => [
|
|
9917
10004
|
s.stepNumber,
|
|
@@ -9941,6 +10028,14 @@ function isUnavoidable(steps, target) {
|
|
|
9941
10028
|
if (s.type === "return" || s.type === "throw") return true;
|
|
9942
10029
|
const arms = s.type === "parallel" ? (s.branches ?? []).map((b) => b.step).filter((e) => graph.stepAt(e) !== void 0) : [];
|
|
9943
10030
|
if (arms.length) return arms.every((e) => completes.has(e));
|
|
10031
|
+
const guaranteed = s.endStep !== void 0 && (s.type === "try" || s.type === "loop" && s.loopKind === "doWhile");
|
|
10032
|
+
if (guaranteed) {
|
|
10033
|
+
const bodyEntry = graph.nextOf(n);
|
|
10034
|
+
if (bodyEntry !== void 0 && bodyEntry <= s.endStep) {
|
|
10035
|
+
if (s.type === "loop") return completes.has(bodyEntry);
|
|
10036
|
+
if (bodyEntry === target) return false;
|
|
10037
|
+
}
|
|
10038
|
+
}
|
|
9944
10039
|
return graph.successorsOf(n).some((t) => completes.has(t));
|
|
9945
10040
|
};
|
|
9946
10041
|
const order = [...graph.stepNumbers].reverse();
|
|
@@ -10004,25 +10099,27 @@ function stronglyConnected(nums, successorsOf) {
|
|
|
10004
10099
|
}
|
|
10005
10100
|
return sccs;
|
|
10006
10101
|
}
|
|
10007
|
-
var
|
|
10008
|
-
|
|
10009
|
-
"src/core/rules/narrative/narrative-antipatterns.ts"() {
|
|
10102
|
+
var init_completed_step_graph = __esm({
|
|
10103
|
+
"src/core/rules/narrative/completed-step-graph.ts"() {
|
|
10010
10104
|
"use strict";
|
|
10011
10105
|
init_models();
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10106
|
+
}
|
|
10107
|
+
});
|
|
10108
|
+
|
|
10109
|
+
// src/core/rules/narrative/meaningless-branches.ts
|
|
10110
|
+
var meaninglessBranchesRule;
|
|
10111
|
+
var init_meaningless_branches = __esm({
|
|
10112
|
+
"src/core/rules/narrative/meaningless-branches.ts"() {
|
|
10113
|
+
"use strict";
|
|
10114
|
+
init_completed_step_graph();
|
|
10115
|
+
meaninglessBranchesRule = {
|
|
10116
|
+
name: "meaningless-branches",
|
|
10117
|
+
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.",
|
|
10016
10118
|
codes: [
|
|
10017
|
-
{ code: "
|
|
10018
|
-
{ code: "MEANINGLESS_BRANCH", defaultSeverity: "warning", summary: "Branch/switch whose arms all target the same step \u2014 the decision changes nothing" },
|
|
10019
|
-
{ code: "UNCONDITIONAL_CALL_CYCLE", defaultSeverity: "warning", summary: "Cross-component call cycle in which every call edge is unavoidable \u2014 unbounded recursion by construction" }
|
|
10119
|
+
{ code: "MEANINGLESS_BRANCH", defaultSeverity: "warning", summary: "Branch/switch whose arms all target the same step \u2014 the decision changes nothing" }
|
|
10020
10120
|
],
|
|
10021
10121
|
check(ctx) {
|
|
10022
|
-
const callEdges = [];
|
|
10023
10122
|
for (const impl of ctx.implementations) {
|
|
10024
|
-
const contract = ctx.interfaceMap.get(impl.contract);
|
|
10025
|
-
const component = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
10026
10123
|
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
10027
10124
|
for (const implMethod of impl.methods) {
|
|
10028
10125
|
const steps = implMethod.narrative;
|
|
@@ -10061,6 +10158,33 @@ var init_narrative_antipatterns = __esm({
|
|
|
10061
10158
|
}
|
|
10062
10159
|
}
|
|
10063
10160
|
}
|
|
10161
|
+
}
|
|
10162
|
+
}
|
|
10163
|
+
}
|
|
10164
|
+
};
|
|
10165
|
+
}
|
|
10166
|
+
});
|
|
10167
|
+
|
|
10168
|
+
// src/core/rules/narrative/inescapable-cycles.ts
|
|
10169
|
+
var inescapableCyclesRule;
|
|
10170
|
+
var init_inescapable_cycles = __esm({
|
|
10171
|
+
"src/core/rules/narrative/inescapable-cycles.ts"() {
|
|
10172
|
+
"use strict";
|
|
10173
|
+
init_completed_step_graph();
|
|
10174
|
+
inescapableCyclesRule = {
|
|
10175
|
+
name: "inescapable-cycles",
|
|
10176
|
+
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.",
|
|
10177
|
+
codes: [
|
|
10178
|
+
{ code: "INESCAPABLE_CYCLE", defaultSeverity: "warning", summary: "Step cycle with no exit edge and no return/throw member \u2014 never terminates by construction" }
|
|
10179
|
+
],
|
|
10180
|
+
check(ctx) {
|
|
10181
|
+
for (const impl of ctx.implementations) {
|
|
10182
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
10183
|
+
for (const implMethod of impl.methods) {
|
|
10184
|
+
const steps = implMethod.narrative;
|
|
10185
|
+
if (!steps.length) continue;
|
|
10186
|
+
const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
|
|
10187
|
+
const graph = completedStepGraph(steps);
|
|
10064
10188
|
for (const scc of stronglyConnected(graph.stepNumbers, graph.successorsOf)) {
|
|
10065
10189
|
const inScc = new Set(scc);
|
|
10066
10190
|
const isCycle = scc.length > 1 || graph.successorsOf(scc[0]).includes(scc[0]);
|
|
@@ -10081,7 +10205,35 @@ var init_narrative_antipatterns = __esm({
|
|
|
10081
10205
|
);
|
|
10082
10206
|
}
|
|
10083
10207
|
}
|
|
10084
|
-
|
|
10208
|
+
}
|
|
10209
|
+
}
|
|
10210
|
+
}
|
|
10211
|
+
};
|
|
10212
|
+
}
|
|
10213
|
+
});
|
|
10214
|
+
|
|
10215
|
+
// src/core/rules/narrative/unconditional-call-cycles.ts
|
|
10216
|
+
var byCodeUnit, unconditionalCallCyclesRule;
|
|
10217
|
+
var init_unconditional_call_cycles = __esm({
|
|
10218
|
+
"src/core/rules/narrative/unconditional-call-cycles.ts"() {
|
|
10219
|
+
"use strict";
|
|
10220
|
+
init_completed_step_graph();
|
|
10221
|
+
byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
10222
|
+
unconditionalCallCyclesRule = {
|
|
10223
|
+
name: "unconditional-call-cycles",
|
|
10224
|
+
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.",
|
|
10225
|
+
codes: [
|
|
10226
|
+
{ code: "UNCONDITIONAL_CALL_CYCLE", defaultSeverity: "warning", summary: "Cross-component call cycle in which every call edge is unavoidable \u2014 unbounded recursion by construction" }
|
|
10227
|
+
],
|
|
10228
|
+
check(ctx) {
|
|
10229
|
+
const callEdges = [];
|
|
10230
|
+
for (const impl of ctx.implementations) {
|
|
10231
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
10232
|
+
const component = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
10233
|
+
if (!component) continue;
|
|
10234
|
+
for (const implMethod of impl.methods) {
|
|
10235
|
+
const steps = implMethod.narrative;
|
|
10236
|
+
if (!steps.length) continue;
|
|
10085
10237
|
const fromKey = `${component.id}::${implMethod.name}`;
|
|
10086
10238
|
for (const s of steps) {
|
|
10087
10239
|
let toComponent;
|
|
@@ -10143,34 +10295,29 @@ var init_narrative_antipatterns = __esm({
|
|
|
10143
10295
|
});
|
|
10144
10296
|
|
|
10145
10297
|
// src/core/rules/narrative/narrative-detail.ts
|
|
10146
|
-
var
|
|
10298
|
+
var narrativeDetailRule;
|
|
10147
10299
|
var init_narrative_detail = __esm({
|
|
10148
10300
|
"src/core/rules/narrative/narrative-detail.ts"() {
|
|
10149
10301
|
"use strict";
|
|
10150
10302
|
init_models();
|
|
10151
|
-
DEFAULT_MAX_UNNARRATED_COMPLEXITY = 8;
|
|
10152
10303
|
narrativeDetailRule = {
|
|
10153
10304
|
name: "narrative-detail",
|
|
10154
|
-
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.
|
|
10305
|
+
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.",
|
|
10155
10306
|
codes: [
|
|
10156
10307
|
{ code: "MISSING_NARRATIVE", defaultSeverity: "warning", summary: "Method resolved to detail: full but has no narrative (error when the level was declared explicitly)" },
|
|
10157
|
-
{ code: "INTENT_FLOOR", defaultSeverity: "warning", summary: "Intent-level method whose intent/description prose is missing or placeholder-thin (error when declared explicitly)" }
|
|
10158
|
-
{ 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)" },
|
|
10159
|
-
{ code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
|
|
10308
|
+
{ code: "INTENT_FLOOR", defaultSeverity: "warning", summary: "Intent-level method whose intent/description prose is missing or placeholder-thin (error when declared explicitly)" }
|
|
10160
10309
|
],
|
|
10161
10310
|
check(ctx) {
|
|
10162
|
-
const code = ctx.codeIndex();
|
|
10163
10311
|
for (const impl of ctx.implementations) {
|
|
10164
10312
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
10165
10313
|
if (!contract) continue;
|
|
10166
10314
|
const component = ctx.componentMap.get(contract.component);
|
|
10167
|
-
const isDraftCtx =
|
|
10315
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
10168
10316
|
for (const implMethod of impl.methods) {
|
|
10169
10317
|
const contractMethod = contract.methods.find((m) => m.name === implMethod.name);
|
|
10170
10318
|
if (!contractMethod) continue;
|
|
10319
|
+
if (implMethod.narrative.length > 0) continue;
|
|
10171
10320
|
const eff = effectiveDetail(implMethod, impl, component);
|
|
10172
|
-
const hasNarrative = implMethod.narrative.length > 0;
|
|
10173
|
-
if (hasNarrative) continue;
|
|
10174
10321
|
if (eff.level === "full") {
|
|
10175
10322
|
ctx.addIssue(
|
|
10176
10323
|
eff.explicit ? "error" : "warning",
|
|
@@ -10181,34 +10328,6 @@ var init_narrative_detail = __esm({
|
|
|
10181
10328
|
);
|
|
10182
10329
|
continue;
|
|
10183
10330
|
}
|
|
10184
|
-
let complexityFired = false;
|
|
10185
|
-
const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
|
|
10186
|
-
const file = methodSourceFile(implMethod, impl.sourcePath);
|
|
10187
|
-
if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
|
|
10188
|
-
const facts = code.factsAt(file);
|
|
10189
|
-
const symbol = implMethod.symbol ?? implMethod.name;
|
|
10190
|
-
const complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
|
|
10191
|
-
const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
|
|
10192
|
-
if (complexity !== void 0 && complexity > limit) {
|
|
10193
|
-
complexityFired = true;
|
|
10194
|
-
ctx.addIssue(
|
|
10195
|
-
"warning",
|
|
10196
|
-
"UNNARRATED_COMPLEXITY",
|
|
10197
|
-
`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.`,
|
|
10198
|
-
impl.id,
|
|
10199
|
-
isDraftCtx
|
|
10200
|
-
);
|
|
10201
|
-
}
|
|
10202
|
-
}
|
|
10203
|
-
if (!complexityFired && eff.explicit && component && isLogic(component)) {
|
|
10204
|
-
ctx.addIssue(
|
|
10205
|
-
"warning",
|
|
10206
|
-
"DETAIL_BELOW_STEREOTYPE",
|
|
10207
|
-
`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.`,
|
|
10208
|
-
impl.id,
|
|
10209
|
-
isDraftCtx
|
|
10210
|
-
);
|
|
10211
|
-
}
|
|
10212
10331
|
const prose = implMethod.intent ?? contractMethod.description;
|
|
10213
10332
|
if (!passesIntentFloor(prose, implMethod.name)) {
|
|
10214
10333
|
ctx.addIssue(
|
|
@@ -10226,6 +10345,71 @@ var init_narrative_detail = __esm({
|
|
|
10226
10345
|
}
|
|
10227
10346
|
});
|
|
10228
10347
|
|
|
10348
|
+
// src/core/rules/narrative/detail-sufficiency.ts
|
|
10349
|
+
var DEFAULT_MAX_UNNARRATED_COMPLEXITY, detailSufficiencyRule;
|
|
10350
|
+
var init_detail_sufficiency = __esm({
|
|
10351
|
+
"src/core/rules/narrative/detail-sufficiency.ts"() {
|
|
10352
|
+
"use strict";
|
|
10353
|
+
init_models();
|
|
10354
|
+
DEFAULT_MAX_UNNARRATED_COMPLEXITY = 8;
|
|
10355
|
+
detailSufficiencyRule = {
|
|
10356
|
+
name: "detail-sufficiency",
|
|
10357
|
+
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.",
|
|
10358
|
+
codes: [
|
|
10359
|
+
{ 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)" },
|
|
10360
|
+
{ code: "DETAIL_BELOW_STEREOTYPE", defaultSeverity: "warning", summary: "Method explicitly dialed below the full narrative floor of its logic stereotype, with no narrative" }
|
|
10361
|
+
],
|
|
10362
|
+
check(ctx) {
|
|
10363
|
+
const code = ctx.codeIndex();
|
|
10364
|
+
const dialedDown = [];
|
|
10365
|
+
for (const impl of ctx.implementations) {
|
|
10366
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
10367
|
+
if (!contract) continue;
|
|
10368
|
+
const component = ctx.componentMap.get(contract.component);
|
|
10369
|
+
const draftContext = ctx.isImplementationDraft(impl);
|
|
10370
|
+
for (const method2 of impl.methods) {
|
|
10371
|
+
const detail = effectiveDetail(method2, impl, component);
|
|
10372
|
+
if (contract.methods.some((m) => m.name === method2.name) && method2.narrative.length === 0 && detail.level !== "full") {
|
|
10373
|
+
dialedDown.push({ implementation: impl, method: method2, component, detail, draftContext });
|
|
10374
|
+
}
|
|
10375
|
+
}
|
|
10376
|
+
}
|
|
10377
|
+
for (const entry of dialedDown) {
|
|
10378
|
+
const { implementation: impl, method: method2, component, detail } = entry;
|
|
10379
|
+
const tier = method2.conformance ?? impl.conformance ?? defaultConformanceTier(component);
|
|
10380
|
+
const file = methodSourceFile(method2, impl.sourcePath);
|
|
10381
|
+
const symbol = method2.symbol ?? method2.name;
|
|
10382
|
+
const limit = ctx.complexityConfigFor(component?.subsystem)?.maxUnnarratedComplexity ?? DEFAULT_MAX_UNNARRATED_COMPLEXITY;
|
|
10383
|
+
let complexity;
|
|
10384
|
+
if (file && tier !== "off" && !(component && ctx.isInChainedSubproject(component.subsystem))) {
|
|
10385
|
+
const facts = code.factsAt(file);
|
|
10386
|
+
complexity = facts?.status === "analyzed" && facts.analysisGrade === "exact" && facts.functionComplexity && Object.prototype.hasOwnProperty.call(facts.functionComplexity, symbol) ? facts.functionComplexity[symbol] : void 0;
|
|
10387
|
+
}
|
|
10388
|
+
if (complexity !== void 0 && complexity > limit) {
|
|
10389
|
+
ctx.addIssue(
|
|
10390
|
+
"warning",
|
|
10391
|
+
"UNNARRATED_COMPLEXITY",
|
|
10392
|
+
`Method "${method2.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.`,
|
|
10393
|
+
impl.id,
|
|
10394
|
+
entry.draftContext
|
|
10395
|
+
);
|
|
10396
|
+
continue;
|
|
10397
|
+
}
|
|
10398
|
+
if (detail.explicit && component && isLogic(component)) {
|
|
10399
|
+
ctx.addIssue(
|
|
10400
|
+
"warning",
|
|
10401
|
+
"DETAIL_BELOW_STEREOTYPE",
|
|
10402
|
+
`Method "${method2.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.`,
|
|
10403
|
+
impl.id,
|
|
10404
|
+
entry.draftContext
|
|
10405
|
+
);
|
|
10406
|
+
}
|
|
10407
|
+
}
|
|
10408
|
+
}
|
|
10409
|
+
};
|
|
10410
|
+
}
|
|
10411
|
+
});
|
|
10412
|
+
|
|
10229
10413
|
// src/core/rules/intrinsic/portal-fields.ts
|
|
10230
10414
|
var portalFieldsRule;
|
|
10231
10415
|
var init_portal_fields = __esm({
|
|
@@ -10410,45 +10594,61 @@ var init_portal_endpoints = __esm({
|
|
|
10410
10594
|
};
|
|
10411
10595
|
portalsRule = {
|
|
10412
10596
|
name: "portal-endpoints",
|
|
10413
|
-
description: "A Portal binds every interface method to a concrete endpoint of the
|
|
10597
|
+
description: "A Portal binds every interface method to a concrete endpoint of the transport its portalType expects.",
|
|
10414
10598
|
codes: [
|
|
10415
10599
|
{ code: "MISSING_ENDPOINT", defaultSeverity: "error", summary: "Portal method without a wire endpoint binding" },
|
|
10416
|
-
{ code: "ENDPOINT_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "Endpoint transport does not match the Portal portalType" }
|
|
10417
|
-
{ code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
|
|
10600
|
+
{ code: "ENDPOINT_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "Endpoint transport does not match the Portal portalType" }
|
|
10418
10601
|
],
|
|
10419
10602
|
check(ctx) {
|
|
10420
10603
|
for (const comp of ctx.components) {
|
|
10604
|
+
if (comp.componentType !== "Portal") continue;
|
|
10605
|
+
const expected = comp.portalType ? PORTAL_TRANSPORT[comp.portalType] : void 0;
|
|
10606
|
+
if (!expected) continue;
|
|
10421
10607
|
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
10422
|
-
const
|
|
10423
|
-
|
|
10424
|
-
|
|
10425
|
-
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
|
|
10429
|
-
|
|
10430
|
-
|
|
10431
|
-
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
)
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
|
|
10441
|
-
"ENDPOINT_TRANSPORT_MISMATCH",
|
|
10442
|
-
`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}").`,
|
|
10443
|
-
intf.id,
|
|
10444
|
-
isDraftCtx || isIntfDraft
|
|
10445
|
-
);
|
|
10446
|
-
}
|
|
10608
|
+
for (const intf of ctx.interfaces.filter((i) => i.component === comp.id)) {
|
|
10609
|
+
const isIntfDraft = intf.status === "draft" || intf.status === "design";
|
|
10610
|
+
for (const m of intf.methods) {
|
|
10611
|
+
if (!m.endpoint) {
|
|
10612
|
+
ctx.addIssue(
|
|
10613
|
+
"error",
|
|
10614
|
+
"MISSING_ENDPOINT",
|
|
10615
|
+
`Method "${m.name}" on interface "${intf.id}" (Portal ${comp.portalType}) is missing an "endpoint" mapping. Bind it with sdd_set_endpoints (transport "${expected}").`,
|
|
10616
|
+
intf.id,
|
|
10617
|
+
isDraftCtx || isIntfDraft
|
|
10618
|
+
);
|
|
10619
|
+
} else if (m.endpoint.transport !== expected) {
|
|
10620
|
+
ctx.addIssue(
|
|
10621
|
+
"error",
|
|
10622
|
+
"ENDPOINT_TRANSPORT_MISMATCH",
|
|
10623
|
+
`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}").`,
|
|
10624
|
+
intf.id,
|
|
10625
|
+
isDraftCtx || isIntfDraft
|
|
10626
|
+
);
|
|
10447
10627
|
}
|
|
10448
10628
|
}
|
|
10449
|
-
continue;
|
|
10450
10629
|
}
|
|
10451
|
-
|
|
10630
|
+
}
|
|
10631
|
+
}
|
|
10632
|
+
};
|
|
10633
|
+
}
|
|
10634
|
+
});
|
|
10635
|
+
|
|
10636
|
+
// src/core/rules/doctrine/non-portal-endpoints.ts
|
|
10637
|
+
var nonPortalEndpointsRule;
|
|
10638
|
+
var init_non_portal_endpoints = __esm({
|
|
10639
|
+
"src/core/rules/doctrine/non-portal-endpoints.ts"() {
|
|
10640
|
+
"use strict";
|
|
10641
|
+
nonPortalEndpointsRule = {
|
|
10642
|
+
name: "non-portal-endpoints",
|
|
10643
|
+
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.",
|
|
10644
|
+
codes: [
|
|
10645
|
+
{ code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
|
|
10646
|
+
],
|
|
10647
|
+
check(ctx) {
|
|
10648
|
+
for (const comp of ctx.components) {
|
|
10649
|
+
if (comp.componentType === "Portal") continue;
|
|
10650
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
10651
|
+
for (const intf of ctx.interfaces.filter((i) => i.component === comp.id)) {
|
|
10452
10652
|
const isIntfDraft = intf.status === "draft" || intf.status === "design";
|
|
10453
10653
|
for (const m of intf.methods) {
|
|
10454
10654
|
if (m.endpoint) {
|
|
@@ -10469,20 +10669,16 @@ var init_portal_endpoints = __esm({
|
|
|
10469
10669
|
});
|
|
10470
10670
|
|
|
10471
10671
|
// src/core/rules/doctrine/portal-call-auth.ts
|
|
10472
|
-
var
|
|
10672
|
+
var portalCallAuthRule;
|
|
10473
10673
|
var init_portal_call_auth = __esm({
|
|
10474
10674
|
"src/core/rules/doctrine/portal-call-auth.ts"() {
|
|
10475
10675
|
"use strict";
|
|
10476
|
-
COMPONENT_REF_PREFIX = "component:";
|
|
10477
10676
|
portalCallAuthRule = {
|
|
10478
10677
|
name: "portal-call-auth",
|
|
10479
|
-
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.
|
|
10678
|
+
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.",
|
|
10480
10679
|
codes: [
|
|
10481
10680
|
{ code: "PORTAL_AUTH_UNMET", defaultSeverity: "warning", summary: "A narrative call into another component's authed Portal does not declare where its credential loads from" },
|
|
10482
|
-
{ 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)" }
|
|
10483
|
-
{ code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
|
|
10484
|
-
{ code: "AUTH_SOURCE_NOT_PROVIDER", defaultSeverity: "warning", summary: "auth.from references a component that is not an Adapter or Store" },
|
|
10485
|
-
{ code: "AUTH_SOURCE_UNWIRED", defaultSeverity: "warning", summary: "The presenter declares a component: credential source it does not depend on or own" }
|
|
10681
|
+
{ 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)" }
|
|
10486
10682
|
],
|
|
10487
10683
|
check(ctx) {
|
|
10488
10684
|
for (const impl of ctx.implementations) {
|
|
@@ -10505,8 +10701,7 @@ var init_portal_call_auth = __esm({
|
|
|
10505
10701
|
draft
|
|
10506
10702
|
);
|
|
10507
10703
|
}
|
|
10508
|
-
|
|
10509
|
-
if (!from) {
|
|
10704
|
+
if (!step.auth?.from) {
|
|
10510
10705
|
ctx.addIssue(
|
|
10511
10706
|
"warning",
|
|
10512
10707
|
"PORTAL_AUTH_UNMET",
|
|
@@ -10514,40 +10709,73 @@ var init_portal_call_auth = __esm({
|
|
|
10514
10709
|
impl.id,
|
|
10515
10710
|
draft
|
|
10516
10711
|
);
|
|
10517
|
-
continue;
|
|
10518
10712
|
}
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
10713
|
+
}
|
|
10714
|
+
}
|
|
10715
|
+
}
|
|
10716
|
+
}
|
|
10717
|
+
};
|
|
10718
|
+
}
|
|
10719
|
+
});
|
|
10720
|
+
|
|
10721
|
+
// src/core/rules/doctrine/auth-source-wiring.ts
|
|
10722
|
+
var COMPONENT_REF_PREFIX, authSourceWiringRule;
|
|
10723
|
+
var init_auth_source_wiring = __esm({
|
|
10724
|
+
"src/core/rules/doctrine/auth-source-wiring.ts"() {
|
|
10725
|
+
"use strict";
|
|
10726
|
+
COMPONENT_REF_PREFIX = "component:";
|
|
10727
|
+
authSourceWiringRule = {
|
|
10728
|
+
name: "auth-source-wiring",
|
|
10729
|
+
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.",
|
|
10730
|
+
codes: [
|
|
10731
|
+
{ code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
|
|
10732
|
+
{ code: "AUTH_SOURCE_NOT_PROVIDER", defaultSeverity: "warning", summary: "auth.from references a component that is not an Adapter or Store" },
|
|
10733
|
+
{ code: "AUTH_SOURCE_UNWIRED", defaultSeverity: "warning", summary: "The presenter declares a component: credential source it does not depend on or own" }
|
|
10734
|
+
],
|
|
10735
|
+
check(ctx) {
|
|
10736
|
+
for (const impl of ctx.implementations) {
|
|
10737
|
+
const ownComponent = ctx.interfaceMap.get(impl.contract)?.component;
|
|
10738
|
+
const presenter = ownComponent ? ctx.componentMap.get(ownComponent) : void 0;
|
|
10739
|
+
const draft = ctx.isImplementationDraft(impl);
|
|
10740
|
+
for (const method2 of impl.methods ?? []) {
|
|
10741
|
+
for (const step of method2.narrative ?? []) {
|
|
10742
|
+
if (step.type !== "call") continue;
|
|
10743
|
+
if (!step.targetComponent || step.targetComponent === ownComponent) continue;
|
|
10744
|
+
const target = ctx.componentMap.get(step.targetComponent);
|
|
10745
|
+
if (!target || target.componentType !== "Portal") continue;
|
|
10746
|
+
if (!target.auth || target.auth.scheme === "none") continue;
|
|
10747
|
+
const from = step.auth?.from;
|
|
10748
|
+
if (!from || !from.startsWith(COMPONENT_REF_PREFIX)) continue;
|
|
10749
|
+
const srcId = from.slice(COMPONENT_REF_PREFIX.length);
|
|
10750
|
+
const src = ctx.componentMap.get(srcId);
|
|
10751
|
+
if (!src) {
|
|
10752
|
+
ctx.addIssue(
|
|
10753
|
+
"warning",
|
|
10754
|
+
"UNKNOWN_AUTH_SOURCE",
|
|
10755
|
+
`Narrative step ${step.stepNumber} of "${method2.name}" in "${impl.id}": auth.from references component "${srcId}", which does not exist.`,
|
|
10756
|
+
impl.id,
|
|
10757
|
+
draft
|
|
10758
|
+
);
|
|
10759
|
+
continue;
|
|
10760
|
+
}
|
|
10761
|
+
if (src.componentType !== "Adapter" && src.componentType !== "Store") {
|
|
10762
|
+
ctx.addIssue(
|
|
10763
|
+
"warning",
|
|
10764
|
+
"AUTH_SOURCE_NOT_PROVIDER",
|
|
10765
|
+
`Narrative step ${step.stepNumber} of "${method2.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).`,
|
|
10766
|
+
impl.id,
|
|
10767
|
+
draft
|
|
10768
|
+
);
|
|
10769
|
+
}
|
|
10770
|
+
const wired = (presenter?.dependsOn ?? []).includes(srcId) || (presenter?.owns ?? []).includes(srcId);
|
|
10771
|
+
if (presenter && !wired) {
|
|
10772
|
+
ctx.addIssue(
|
|
10773
|
+
"warning",
|
|
10774
|
+
"AUTH_SOURCE_UNWIRED",
|
|
10775
|
+
`Narrative step ${step.stepNumber} of "${method2.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.`,
|
|
10776
|
+
impl.id,
|
|
10777
|
+
draft
|
|
10778
|
+
);
|
|
10551
10779
|
}
|
|
10552
10780
|
}
|
|
10553
10781
|
}
|
|
@@ -11423,17 +11651,135 @@ var init_component_variants = __esm({
|
|
|
11423
11651
|
}
|
|
11424
11652
|
});
|
|
11425
11653
|
|
|
11426
|
-
// src/core/rules/extension/
|
|
11654
|
+
// src/core/rules/extension/declared-assertion.ts
|
|
11655
|
+
function assertionsOfKind(ctx, kind) {
|
|
11656
|
+
return ctx.ext.assertions.filter(
|
|
11657
|
+
(a) => a.kind === kind
|
|
11658
|
+
);
|
|
11659
|
+
}
|
|
11427
11660
|
function globToRegExp(glob) {
|
|
11428
11661
|
const escaped = glob.replace(/[.*+?^${}()|[\]\\]/g, (ch) => ch === "*" ? ".*" : `\\${ch}`);
|
|
11429
11662
|
return new RegExp(`^${escaped}$`);
|
|
11430
11663
|
}
|
|
11431
|
-
function
|
|
11664
|
+
function matchesSelector(sel, comp, ctx) {
|
|
11432
11665
|
if (sel.componentType?.length && !sel.componentType.includes(comp.componentType)) return false;
|
|
11433
11666
|
if (sel.profile?.length && !sel.profile.includes(ctx.getComponentProfile(comp.id))) return false;
|
|
11434
11667
|
if (sel.id && !globToRegExp(sel.id).test(comp.id)) return false;
|
|
11435
11668
|
return true;
|
|
11436
11669
|
}
|
|
11670
|
+
function reportAssertion(ctx, a, message, specId, isDraftCtx) {
|
|
11671
|
+
const severity = isDraftCtx && a.severity === "error" ? "warning" : a.severity;
|
|
11672
|
+
ctx.addIssue(severity, a.fullCode, `${message} [pack "${a.pack}"]: ${a.reason}`, specId, isDraftCtx);
|
|
11673
|
+
}
|
|
11674
|
+
var init_declared_assertion = __esm({
|
|
11675
|
+
"src/core/rules/extension/declared-assertion.ts"() {
|
|
11676
|
+
"use strict";
|
|
11677
|
+
}
|
|
11678
|
+
});
|
|
11679
|
+
|
|
11680
|
+
// src/core/rules/extension/assertion-forbidden-edges.ts
|
|
11681
|
+
var assertionForbiddenEdgesRule;
|
|
11682
|
+
var init_assertion_forbidden_edges = __esm({
|
|
11683
|
+
"src/core/rules/extension/assertion-forbidden-edges.ts"() {
|
|
11684
|
+
"use strict";
|
|
11685
|
+
init_declared_assertion();
|
|
11686
|
+
assertionForbiddenEdgesRule = {
|
|
11687
|
+
name: "assertion-forbidden-edges",
|
|
11688
|
+
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.",
|
|
11689
|
+
// Static codes are unknown here — packs bring their own. The validator
|
|
11690
|
+
// gathers every loaded assertion's fullCode into knownIssueCodes.
|
|
11691
|
+
codes: [],
|
|
11692
|
+
check(ctx) {
|
|
11693
|
+
for (const a of assertionsOfKind(ctx, "forbid-edge")) {
|
|
11694
|
+
for (const comp of ctx.components) {
|
|
11695
|
+
if (!matchesSelector(a.from, comp, ctx)) continue;
|
|
11696
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
11697
|
+
const edges = a.relation.flatMap(
|
|
11698
|
+
(relation) => comp[relation].map((targetId) => ({ relation, targetId }))
|
|
11699
|
+
);
|
|
11700
|
+
for (const { relation, targetId } of edges) {
|
|
11701
|
+
const target = ctx.componentMap.get(targetId);
|
|
11702
|
+
if (!target || !matchesSelector(a.to, target, ctx)) continue;
|
|
11703
|
+
reportAssertion(
|
|
11704
|
+
ctx,
|
|
11705
|
+
a,
|
|
11706
|
+
`Component "${comp.id}" (${comp.componentType}) ${relation === "owns" ? "owns" : "depends on"} "${target.id}" (${target.componentType}), forbidden by assertion ${a.code}`,
|
|
11707
|
+
comp.id,
|
|
11708
|
+
isDraftCtx
|
|
11709
|
+
);
|
|
11710
|
+
}
|
|
11711
|
+
}
|
|
11712
|
+
}
|
|
11713
|
+
}
|
|
11714
|
+
};
|
|
11715
|
+
}
|
|
11716
|
+
});
|
|
11717
|
+
|
|
11718
|
+
// src/core/rules/extension/assertion-required-fields.ts
|
|
11719
|
+
function fieldValue(spec, field) {
|
|
11720
|
+
const segments = field.split(".");
|
|
11721
|
+
if (segments[0] !== "ext") {
|
|
11722
|
+
return segments.length === 1 ? spec[field] : void 0;
|
|
11723
|
+
}
|
|
11724
|
+
let cur = spec.ext;
|
|
11725
|
+
for (const seg of segments.slice(1)) {
|
|
11726
|
+
if (cur === null || typeof cur !== "object") return void 0;
|
|
11727
|
+
if (!Object.prototype.hasOwnProperty.call(cur, seg)) return void 0;
|
|
11728
|
+
cur = cur[seg];
|
|
11729
|
+
}
|
|
11730
|
+
return cur;
|
|
11731
|
+
}
|
|
11732
|
+
function holdersAt(ctx, level) {
|
|
11733
|
+
const holders = [];
|
|
11734
|
+
if (level === "component") {
|
|
11735
|
+
for (const c of ctx.components) {
|
|
11736
|
+
holders.push({ spec: c, specId: c.id, comp: c, draft: ctx.isComponentDraft(c.id) });
|
|
11737
|
+
}
|
|
11738
|
+
} else if (level === "interface") {
|
|
11739
|
+
for (const i of ctx.interfaces) {
|
|
11740
|
+
const comp = ctx.componentMap.get(i.component);
|
|
11741
|
+
if (comp) holders.push({ spec: i, specId: i.id, comp, draft: ctx.isComponentDraft(comp.id) || i.status === "draft" || i.status === "design" });
|
|
11742
|
+
}
|
|
11743
|
+
} else {
|
|
11744
|
+
for (const impl of ctx.implementations) {
|
|
11745
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
11746
|
+
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
11747
|
+
if (comp) holders.push({ spec: impl, specId: impl.id, comp, draft: ctx.isImplementationDraft(impl) });
|
|
11748
|
+
}
|
|
11749
|
+
}
|
|
11750
|
+
return holders;
|
|
11751
|
+
}
|
|
11752
|
+
var assertionRequiredFieldsRule;
|
|
11753
|
+
var init_assertion_required_fields = __esm({
|
|
11754
|
+
"src/core/rules/extension/assertion-required-fields.ts"() {
|
|
11755
|
+
"use strict";
|
|
11756
|
+
init_declared_assertion();
|
|
11757
|
+
assertionRequiredFieldsRule = {
|
|
11758
|
+
name: "assertion-required-fields",
|
|
11759
|
+
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.",
|
|
11760
|
+
// Static codes are unknown here — packs bring their own. The validator
|
|
11761
|
+
// gathers every loaded assertion's fullCode into knownIssueCodes.
|
|
11762
|
+
codes: [],
|
|
11763
|
+
check(ctx) {
|
|
11764
|
+
for (const a of assertionsOfKind(ctx, "require-field")) {
|
|
11765
|
+
for (const h of holdersAt(ctx, a.level)) {
|
|
11766
|
+
if (!matchesSelector(a.on, h.comp, ctx)) continue;
|
|
11767
|
+
const v = fieldValue(h.spec, a.field);
|
|
11768
|
+
if (v === void 0 || v === null) {
|
|
11769
|
+
reportAssertion(ctx, a, `${a.level} spec "${h.specId}" does not declare "${a.field}", required by assertion ${a.code}`, h.specId, h.draft);
|
|
11770
|
+
continue;
|
|
11771
|
+
}
|
|
11772
|
+
if (a.values && !a.values.includes(String(v))) {
|
|
11773
|
+
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);
|
|
11774
|
+
}
|
|
11775
|
+
}
|
|
11776
|
+
}
|
|
11777
|
+
}
|
|
11778
|
+
};
|
|
11779
|
+
}
|
|
11780
|
+
});
|
|
11781
|
+
|
|
11782
|
+
// src/core/rules/extension/assertion-endpoint-shapes.ts
|
|
11437
11783
|
function endpointAddress(ep) {
|
|
11438
11784
|
switch (ep.transport) {
|
|
11439
11785
|
case "HTTP":
|
|
@@ -11454,92 +11800,42 @@ function endpointAddress(ep) {
|
|
|
11454
11800
|
return ep.address;
|
|
11455
11801
|
}
|
|
11456
11802
|
}
|
|
11457
|
-
function
|
|
11458
|
-
const
|
|
11459
|
-
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
11803
|
+
function boundEndpoints(ctx, on) {
|
|
11804
|
+
const out = [];
|
|
11805
|
+
for (const intf of ctx.interfaces) {
|
|
11806
|
+
const comp = ctx.componentMap.get(intf.component);
|
|
11807
|
+
if (!comp || !matchesSelector(on, comp, ctx)) continue;
|
|
11808
|
+
const draft = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
|
|
11809
|
+
for (const method2 of intf.methods) {
|
|
11810
|
+
if (!method2.endpoint) continue;
|
|
11811
|
+
out.push({ intf, method: method2, endpoint: method2.endpoint, draft });
|
|
11812
|
+
}
|
|
11467
11813
|
}
|
|
11468
|
-
return
|
|
11814
|
+
return out;
|
|
11469
11815
|
}
|
|
11470
|
-
var
|
|
11471
|
-
var
|
|
11472
|
-
"src/core/rules/extension/
|
|
11816
|
+
var assertionEndpointShapesRule;
|
|
11817
|
+
var init_assertion_endpoint_shapes = __esm({
|
|
11818
|
+
"src/core/rules/extension/assertion-endpoint-shapes.ts"() {
|
|
11473
11819
|
"use strict";
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
|
|
11820
|
+
init_declared_assertion();
|
|
11821
|
+
assertionEndpointShapesRule = {
|
|
11822
|
+
name: "assertion-endpoint-shapes",
|
|
11823
|
+
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.",
|
|
11477
11824
|
// Static codes are unknown here — packs bring their own. The validator
|
|
11478
11825
|
// gathers every loaded assertion's fullCode into knownIssueCodes.
|
|
11479
11826
|
codes: [],
|
|
11480
11827
|
check(ctx) {
|
|
11481
|
-
const
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
|
|
11488
|
-
if (a.kind === "forbid-edge") {
|
|
11489
|
-
for (const comp of ctx.components) {
|
|
11490
|
-
if (!matches(a.from, comp, ctx)) continue;
|
|
11491
|
-
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
11492
|
-
for (const relation of a.relation) {
|
|
11493
|
-
for (const targetId of comp[relation]) {
|
|
11494
|
-
const target = ctx.componentMap.get(targetId);
|
|
11495
|
-
if (!target || !matches(a.to, target, ctx)) continue;
|
|
11496
|
-
emit(a, `Component "${comp.id}" (${comp.componentType}) ${relation === "owns" ? "owns" : "depends on"} "${target.id}" (${target.componentType}), forbidden by assertion ${a.code}`, comp.id, isDraftCtx);
|
|
11497
|
-
}
|
|
11498
|
-
}
|
|
11499
|
-
}
|
|
11500
|
-
} else if (a.kind === "require-field") {
|
|
11501
|
-
const holders = [];
|
|
11502
|
-
if (a.level === "component") {
|
|
11503
|
-
for (const c of ctx.components) holders.push({ spec: c, specId: c.id, comp: c, draft: ctx.isComponentDraft(c.id) });
|
|
11504
|
-
} else if (a.level === "interface") {
|
|
11505
|
-
for (const i of ctx.interfaces) {
|
|
11506
|
-
const comp = ctx.componentMap.get(i.component);
|
|
11507
|
-
if (comp) holders.push({ spec: i, specId: i.id, comp, draft: ctx.isComponentDraft(comp.id) || i.status === "draft" || i.status === "design" });
|
|
11508
|
-
}
|
|
11509
|
-
} else {
|
|
11510
|
-
for (const impl of ctx.implementations) {
|
|
11511
|
-
const contract = ctx.interfaceMap.get(impl.contract);
|
|
11512
|
-
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
11513
|
-
if (comp) holders.push({ spec: impl, specId: impl.id, comp, draft: ctx.isImplementationDraft(impl) });
|
|
11514
|
-
}
|
|
11515
|
-
}
|
|
11516
|
-
for (const h of holders) {
|
|
11517
|
-
if (!matches(a.on, h.comp, ctx)) continue;
|
|
11518
|
-
const v = fieldValue(h.spec, a.field);
|
|
11519
|
-
if (v === void 0 || v === null) {
|
|
11520
|
-
emit(a, `${a.level} spec "${h.specId}" does not declare "${a.field}", required by assertion ${a.code}`, h.specId, h.draft);
|
|
11521
|
-
} else if (a.values && !a.values.includes(String(v))) {
|
|
11522
|
-
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);
|
|
11523
|
-
}
|
|
11828
|
+
for (const a of assertionsOfKind(ctx, "endpoint-shape")) {
|
|
11829
|
+
const pattern = a.pathPattern ? new RegExp(a.pathPattern) : void 0;
|
|
11830
|
+
for (const bound2 of boundEndpoints(ctx, a.on)) {
|
|
11831
|
+
const { intf, method: method2, endpoint } = bound2;
|
|
11832
|
+
if (a.transport?.length && !a.transport.includes(endpoint.transport)) {
|
|
11833
|
+
reportAssertion(ctx, a, `Endpoint of "${intf.id}.${method2.name}" uses transport ${endpoint.transport}, outside the allowlist (${a.transport.join(", ")}) of assertion ${a.code}`, intf.id, bound2.draft);
|
|
11834
|
+
continue;
|
|
11524
11835
|
}
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
const comp = ctx.componentMap.get(intf.component);
|
|
11529
|
-
if (!comp || !matches(a.on, comp, ctx)) continue;
|
|
11530
|
-
const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
|
|
11531
|
-
for (const method2 of intf.methods) {
|
|
11532
|
-
if (!method2.endpoint) continue;
|
|
11533
|
-
const ep = method2.endpoint;
|
|
11534
|
-
if (a.transport?.length && !a.transport.includes(ep.transport)) {
|
|
11535
|
-
emit(a, `Endpoint of "${intf.id}.${method2.name}" uses transport ${ep.transport}, outside the allowlist (${a.transport.join(", ")}) of assertion ${a.code}`, intf.id, isDraftCtx);
|
|
11536
|
-
continue;
|
|
11537
|
-
}
|
|
11538
|
-
const address = endpointAddress(ep);
|
|
11539
|
-
if (pattern && !pattern.test(address)) {
|
|
11540
|
-
emit(a, `Endpoint of "${intf.id}.${method2.name}" binds "${address}", which does not match ${a.pathPattern} required by assertion ${a.code}`, intf.id, isDraftCtx);
|
|
11541
|
-
}
|
|
11542
|
-
}
|
|
11836
|
+
const address = endpointAddress(endpoint);
|
|
11837
|
+
if (pattern && !pattern.test(address)) {
|
|
11838
|
+
reportAssertion(ctx, a, `Endpoint of "${intf.id}.${method2.name}" binds "${address}", which does not match ${a.pathPattern} required by assertion ${a.code}`, intf.id, bound2.draft);
|
|
11543
11839
|
}
|
|
11544
11840
|
}
|
|
11545
11841
|
}
|
|
@@ -11979,16 +12275,13 @@ var reachabilityRule;
|
|
|
11979
12275
|
var init_unused_detection = __esm({
|
|
11980
12276
|
"src/core/rules/wiring/unused-detection.ts"() {
|
|
11981
12277
|
"use strict";
|
|
11982
|
-
init_models();
|
|
11983
12278
|
init_narrative_graph_projector();
|
|
11984
12279
|
reachabilityRule = {
|
|
11985
12280
|
name: "unused-detection",
|
|
11986
|
-
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
|
|
12281
|
+
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.",
|
|
11987
12282
|
codes: [
|
|
11988
12283
|
{ code: "UNUSED_COMPONENT", defaultSeverity: "warning", summary: "Component never reached by any narrative call chain" },
|
|
11989
12284
|
{ code: "UNUSED_METHOD", defaultSeverity: "warning", summary: "Method never called by any narrative step" },
|
|
11990
|
-
{ code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields or signatures" },
|
|
11991
|
-
{ code: "INVOKED_BY_UNDESCRIBED", defaultSeverity: "warning", summary: "invokedBy declaration whose caller prose is missing or placeholder-thin" },
|
|
11992
12285
|
{ code: "INVOKED_BY_REDUNDANT", defaultSeverity: "warning", summary: "invokedBy declaration on a method the internal narrative walk already reaches \u2014 stale, remove it" }
|
|
11993
12286
|
],
|
|
11994
12287
|
check(ctx) {
|
|
@@ -12016,17 +12309,8 @@ var init_unused_detection = __esm({
|
|
|
12016
12309
|
if (!m.invokedBy) continue;
|
|
12017
12310
|
invokedBySeeds.push({ compId: intf.component, methodName: m.name });
|
|
12018
12311
|
if (!ctx.isSpecInScope(intf.id)) continue;
|
|
12019
|
-
const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
12020
|
-
if (!passesIntentFloor(m.invokedBy.caller, m.name)) {
|
|
12021
|
-
ctx.addIssue(
|
|
12022
|
-
"warning",
|
|
12023
|
-
"INVOKED_BY_UNDESCRIBED",
|
|
12024
|
-
`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.`,
|
|
12025
|
-
intf.id,
|
|
12026
|
-
isDraftCtx
|
|
12027
|
-
);
|
|
12028
|
-
}
|
|
12029
12312
|
if (baseWalk.reachesMethod(intf.component, m.name)) {
|
|
12313
|
+
const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
12030
12314
|
ctx.addIssue(
|
|
12031
12315
|
"warning",
|
|
12032
12316
|
"INVOKED_BY_REDUNDANT",
|
|
@@ -12049,23 +12333,75 @@ var init_unused_detection = __esm({
|
|
|
12049
12333
|
comp.id,
|
|
12050
12334
|
isDraftCtx
|
|
12051
12335
|
);
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
|
|
12336
|
+
continue;
|
|
12337
|
+
}
|
|
12338
|
+
for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
|
|
12339
|
+
for (const m of intf.methods) {
|
|
12340
|
+
if (!reach.reachesMethod(comp.id, m.name)) {
|
|
12341
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
|
|
12342
|
+
ctx.addIssue(
|
|
12343
|
+
"warning",
|
|
12344
|
+
"UNUSED_METHOD",
|
|
12345
|
+
`Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
|
|
12346
|
+
intf.id,
|
|
12347
|
+
isDraftCtx
|
|
12348
|
+
);
|
|
12065
12349
|
}
|
|
12066
12350
|
}
|
|
12067
12351
|
}
|
|
12068
12352
|
}
|
|
12353
|
+
}
|
|
12354
|
+
};
|
|
12355
|
+
}
|
|
12356
|
+
});
|
|
12357
|
+
|
|
12358
|
+
// src/core/rules/wiring/invoked-by-description.ts
|
|
12359
|
+
var invokedByDescriptionRule;
|
|
12360
|
+
var init_invoked_by_description = __esm({
|
|
12361
|
+
"src/core/rules/wiring/invoked-by-description.ts"() {
|
|
12362
|
+
"use strict";
|
|
12363
|
+
init_models();
|
|
12364
|
+
invokedByDescriptionRule = {
|
|
12365
|
+
name: "invoked-by-description",
|
|
12366
|
+
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.',
|
|
12367
|
+
codes: [
|
|
12368
|
+
{ code: "INVOKED_BY_UNDESCRIBED", defaultSeverity: "warning", summary: "invokedBy declaration whose caller prose is missing or placeholder-thin" }
|
|
12369
|
+
],
|
|
12370
|
+
check(ctx) {
|
|
12371
|
+
for (const intf of ctx.interfaces) {
|
|
12372
|
+
if (!ctx.componentMap.has(intf.component)) continue;
|
|
12373
|
+
if (!ctx.isSpecInScope(intf.id)) continue;
|
|
12374
|
+
for (const m of intf.methods) {
|
|
12375
|
+
if (!m.invokedBy) continue;
|
|
12376
|
+
if (passesIntentFloor(m.invokedBy.caller, m.name)) continue;
|
|
12377
|
+
const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
12378
|
+
ctx.addIssue(
|
|
12379
|
+
"warning",
|
|
12380
|
+
"INVOKED_BY_UNDESCRIBED",
|
|
12381
|
+
`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.`,
|
|
12382
|
+
intf.id,
|
|
12383
|
+
isDraftCtx
|
|
12384
|
+
);
|
|
12385
|
+
}
|
|
12386
|
+
}
|
|
12387
|
+
}
|
|
12388
|
+
};
|
|
12389
|
+
}
|
|
12390
|
+
});
|
|
12391
|
+
|
|
12392
|
+
// src/core/rules/wiring/unused-types.ts
|
|
12393
|
+
var unusedTypesRule;
|
|
12394
|
+
var init_unused_types = __esm({
|
|
12395
|
+
"src/core/rules/wiring/unused-types.ts"() {
|
|
12396
|
+
"use strict";
|
|
12397
|
+
init_models();
|
|
12398
|
+
unusedTypesRule = {
|
|
12399
|
+
name: "unused-types",
|
|
12400
|
+
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.",
|
|
12401
|
+
codes: [
|
|
12402
|
+
{ code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields or signatures" }
|
|
12403
|
+
],
|
|
12404
|
+
check(ctx) {
|
|
12069
12405
|
const referencedTypes = /* @__PURE__ */ new Set();
|
|
12070
12406
|
const markTypeReferenced = (ref) => {
|
|
12071
12407
|
if (ctx.isBuiltinType(ref)) return;
|
|
@@ -12299,7 +12635,44 @@ var init_prose_claims = __esm({
|
|
|
12299
12635
|
}
|
|
12300
12636
|
});
|
|
12301
12637
|
|
|
12302
|
-
// src/core/rules/wiring/invariant-
|
|
12638
|
+
// src/core/rules/wiring/unique-invariant-ids.ts
|
|
12639
|
+
var uniqueInvariantIdsRule;
|
|
12640
|
+
var init_unique_invariant_ids = __esm({
|
|
12641
|
+
"src/core/rules/wiring/unique-invariant-ids.ts"() {
|
|
12642
|
+
"use strict";
|
|
12643
|
+
init_models();
|
|
12644
|
+
uniqueInvariantIdsRule = {
|
|
12645
|
+
name: "unique-invariant-ids",
|
|
12646
|
+
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.`,
|
|
12647
|
+
codes: [
|
|
12648
|
+
{ code: "DUPLICATE_INVARIANT_ID", defaultSeverity: "error", summary: "An entity declares two invariants with the same id" }
|
|
12649
|
+
],
|
|
12650
|
+
check(ctx) {
|
|
12651
|
+
for (const t of ctx.types) {
|
|
12652
|
+
const invariants = t.invariants ?? [];
|
|
12653
|
+
if (invariants.length === 0) continue;
|
|
12654
|
+
const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
|
|
12655
|
+
const entityDraft = sub !== void 0 && isDraftSubsystem(sub);
|
|
12656
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12657
|
+
for (const inv of invariants) {
|
|
12658
|
+
if (seen.has(inv.id)) {
|
|
12659
|
+
ctx.addIssue(
|
|
12660
|
+
"error",
|
|
12661
|
+
"DUPLICATE_INVARIANT_ID",
|
|
12662
|
+
`Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
|
|
12663
|
+
t.id,
|
|
12664
|
+
entityDraft
|
|
12665
|
+
);
|
|
12666
|
+
}
|
|
12667
|
+
seen.add(inv.id);
|
|
12668
|
+
}
|
|
12669
|
+
}
|
|
12670
|
+
}
|
|
12671
|
+
};
|
|
12672
|
+
}
|
|
12673
|
+
});
|
|
12674
|
+
|
|
12675
|
+
// src/core/rules/wiring/invariant-ref.ts
|
|
12303
12676
|
function splitInvariantRef(ref) {
|
|
12304
12677
|
const at = ref.lastIndexOf(".");
|
|
12305
12678
|
if (at <= 0 || at === ref.length - 1) return null;
|
|
@@ -12323,6 +12696,14 @@ function refMatchesInvariant(ref, type, invariantId) {
|
|
|
12323
12696
|
if (!(type.invariants ?? []).some((inv) => inv.id === invariantId)) return false;
|
|
12324
12697
|
return typeMatchesRef(type, parts.typeRef);
|
|
12325
12698
|
}
|
|
12699
|
+
var init_invariant_ref = __esm({
|
|
12700
|
+
"src/core/rules/wiring/invariant-ref.ts"() {
|
|
12701
|
+
"use strict";
|
|
12702
|
+
init_models();
|
|
12703
|
+
}
|
|
12704
|
+
});
|
|
12705
|
+
|
|
12706
|
+
// src/core/rules/wiring/invariant-backing.ts
|
|
12326
12707
|
function stepAsserts(impl, methodName, type, invariantId) {
|
|
12327
12708
|
const method2 = impl.methods.find((m) => m.name === methodName);
|
|
12328
12709
|
if (!method2) return false;
|
|
@@ -12347,33 +12728,19 @@ var init_invariant_backing = __esm({
|
|
|
12347
12728
|
"src/core/rules/wiring/invariant-backing.ts"() {
|
|
12348
12729
|
"use strict";
|
|
12349
12730
|
init_models();
|
|
12731
|
+
init_invariant_ref();
|
|
12350
12732
|
invariantBackingRule = {
|
|
12351
12733
|
name: "invariant-backing",
|
|
12352
|
-
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
|
|
12734
|
+
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.',
|
|
12353
12735
|
codes: [
|
|
12354
|
-
{ code: "DUPLICATE_INVARIANT_ID", defaultSeverity: "error", summary: "An entity declares two invariants with the same id" },
|
|
12355
12736
|
{ 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" },
|
|
12356
|
-
{ code: "UNASSERTED_INVARIANT", defaultSeverity: "warning", summary: "A write-effect method of the invariant's owning component has no narrative step asserting it" }
|
|
12357
|
-
{ code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
|
|
12737
|
+
{ code: "UNASSERTED_INVARIANT", defaultSeverity: "warning", summary: "A write-effect method of the invariant's owning component has no narrative step asserting it" }
|
|
12358
12738
|
],
|
|
12359
12739
|
check(ctx) {
|
|
12360
12740
|
for (const t of ctx.types) {
|
|
12361
12741
|
const invariants = t.invariants ?? [];
|
|
12362
12742
|
if (invariants.length === 0) continue;
|
|
12363
12743
|
const entityDraft = isEntityDraft(t, ctx);
|
|
12364
|
-
const seen = /* @__PURE__ */ new Set();
|
|
12365
|
-
for (const inv of invariants) {
|
|
12366
|
-
if (seen.has(inv.id)) {
|
|
12367
|
-
ctx.addIssue(
|
|
12368
|
-
"error",
|
|
12369
|
-
"DUPLICATE_INVARIANT_ID",
|
|
12370
|
-
`Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
|
|
12371
|
-
t.id,
|
|
12372
|
-
entityDraft
|
|
12373
|
-
);
|
|
12374
|
-
}
|
|
12375
|
-
seen.add(inv.id);
|
|
12376
|
-
}
|
|
12377
12744
|
const comp = resolveComponentClass(t, ctx);
|
|
12378
12745
|
if (!comp) {
|
|
12379
12746
|
ctx.addIssue(
|
|
@@ -12415,6 +12782,24 @@ var init_invariant_backing = __esm({
|
|
|
12415
12782
|
}
|
|
12416
12783
|
}
|
|
12417
12784
|
}
|
|
12785
|
+
}
|
|
12786
|
+
};
|
|
12787
|
+
}
|
|
12788
|
+
});
|
|
12789
|
+
|
|
12790
|
+
// src/core/rules/wiring/invariant-references.ts
|
|
12791
|
+
var invariantReferencesRule;
|
|
12792
|
+
var init_invariant_references = __esm({
|
|
12793
|
+
"src/core/rules/wiring/invariant-references.ts"() {
|
|
12794
|
+
"use strict";
|
|
12795
|
+
init_invariant_ref();
|
|
12796
|
+
invariantReferencesRule = {
|
|
12797
|
+
name: "invariant-references",
|
|
12798
|
+
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.",
|
|
12799
|
+
codes: [
|
|
12800
|
+
{ code: "UNKNOWN_INVARIANT_REF", defaultSeverity: "error", summary: "A narrative step asserts an invariant that no entity declares" }
|
|
12801
|
+
],
|
|
12802
|
+
check(ctx) {
|
|
12418
12803
|
for (const impl of ctx.implementations) {
|
|
12419
12804
|
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
12420
12805
|
for (const method2 of impl.methods) {
|
|
@@ -12746,54 +13131,45 @@ var init_call_conformance = __esm({
|
|
|
12746
13131
|
],
|
|
12747
13132
|
check(ctx) {
|
|
12748
13133
|
const code = ctx.codeIndex();
|
|
12749
|
-
for (const
|
|
12750
|
-
const
|
|
12751
|
-
|
|
12752
|
-
|
|
12753
|
-
if (!
|
|
12754
|
-
if (
|
|
12755
|
-
const
|
|
12756
|
-
|
|
12757
|
-
|
|
12758
|
-
|
|
12759
|
-
|
|
12760
|
-
|
|
12761
|
-
|
|
12762
|
-
if (!
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
const
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
for (const step of implMethod.narrative) {
|
|
12770
|
-
if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
|
|
12771
|
-
if (!ctx.componentMap.has(step.targetComponent)) continue;
|
|
12772
|
-
const accepted = /* @__PURE__ */ new Set([step.targetMethod]);
|
|
12773
|
-
for (const targetIntf of ctx.interfacesByComponent.get(step.targetComponent) ?? []) {
|
|
12774
|
-
for (const targetImpl of ctx.implementationsByContract.get(targetIntf.id) ?? []) {
|
|
12775
|
-
const targetMethod = targetImpl.methods.find((m) => m.name === step.targetMethod);
|
|
12776
|
-
if (targetMethod?.symbol) accepted.add(targetMethod.symbol);
|
|
12777
|
-
}
|
|
13134
|
+
for (const entry of ctx.implementationMethods()) {
|
|
13135
|
+
const { implementation: impl, method: implMethod, component, sourceFile: file } = entry;
|
|
13136
|
+
const tier = implMethod.conformance ?? impl.conformance ?? defaultConformanceTier(component);
|
|
13137
|
+
if (tier === "off") continue;
|
|
13138
|
+
if (!implMethod.narrative.length) continue;
|
|
13139
|
+
if (!file) continue;
|
|
13140
|
+
const facts = code.factsAt(file);
|
|
13141
|
+
if (!facts || facts.status !== "analyzed" || facts.analysisGrade !== "exact") continue;
|
|
13142
|
+
const fnSymbol = implMethod.symbol ?? implMethod.name;
|
|
13143
|
+
const callees = closedCallees(facts, fnSymbol);
|
|
13144
|
+
if (!callees) continue;
|
|
13145
|
+
const missing = [];
|
|
13146
|
+
for (const step of implMethod.narrative) {
|
|
13147
|
+
if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
|
|
13148
|
+
if (!ctx.componentMap.has(step.targetComponent)) continue;
|
|
13149
|
+
const accepted = /* @__PURE__ */ new Set([step.targetMethod]);
|
|
13150
|
+
for (const targetIntf of ctx.interfacesByComponent.get(step.targetComponent) ?? []) {
|
|
13151
|
+
for (const targetImpl of ctx.implementationsByContract.get(targetIntf.id) ?? []) {
|
|
13152
|
+
const targetMethod = targetImpl.methods.find((m) => m.name === step.targetMethod);
|
|
13153
|
+
if (targetMethod?.symbol) accepted.add(targetMethod.symbol);
|
|
12778
13154
|
}
|
|
12779
|
-
if (accepted.has(fnSymbol)) continue;
|
|
12780
|
-
if ([...accepted].some((name) => callees.has(name))) continue;
|
|
12781
|
-
missing.push({
|
|
12782
|
-
step: step.stepNumber,
|
|
12783
|
-
target: `${step.targetComponent}.${step.targetMethod}`,
|
|
12784
|
-
accepted: [...accepted]
|
|
12785
|
-
});
|
|
12786
13155
|
}
|
|
12787
|
-
if (
|
|
12788
|
-
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
12793
|
-
|
|
12794
|
-
isDraftCtx
|
|
12795
|
-
);
|
|
13156
|
+
if (accepted.has(fnSymbol)) continue;
|
|
13157
|
+
if ([...accepted].some((name) => callees.has(name))) continue;
|
|
13158
|
+
missing.push({
|
|
13159
|
+
step: step.stepNumber,
|
|
13160
|
+
target: `${step.targetComponent}.${step.targetMethod}`,
|
|
13161
|
+
accepted: [...accepted]
|
|
13162
|
+
});
|
|
12796
13163
|
}
|
|
13164
|
+
if (missing.length === 0) continue;
|
|
13165
|
+
const detail = missing.map((m) => `step ${m.step} \u2192 ${m.target} (looked for ${m.accepted.map((a) => `"${a}"`).join(" / ")})`).join("; ");
|
|
13166
|
+
ctx.addIssue(
|
|
13167
|
+
"warning",
|
|
13168
|
+
"CALL_STEP_UNREALIZED",
|
|
13169
|
+
`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.`,
|
|
13170
|
+
impl.id,
|
|
13171
|
+
entry.draftContext
|
|
13172
|
+
);
|
|
12797
13173
|
}
|
|
12798
13174
|
}
|
|
12799
13175
|
};
|
|
@@ -12960,20 +13336,95 @@ var init_dependency_conformance = __esm({
|
|
|
12960
13336
|
}
|
|
12961
13337
|
});
|
|
12962
13338
|
|
|
12963
|
-
// src/core/rules/conformance/integration-
|
|
12964
|
-
var
|
|
12965
|
-
var
|
|
12966
|
-
"src/core/rules/conformance/integration-
|
|
13339
|
+
// src/core/rules/conformance/integration-sim-declaration.ts
|
|
13340
|
+
var integrationSimDeclarationRule;
|
|
13341
|
+
var init_integration_sim_declaration = __esm({
|
|
13342
|
+
"src/core/rules/conformance/integration-sim-declaration.ts"() {
|
|
13343
|
+
"use strict";
|
|
13344
|
+
integrationSimDeclarationRule = {
|
|
13345
|
+
name: "integration-sim-declaration",
|
|
13346
|
+
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.",
|
|
13347
|
+
codes: [
|
|
13348
|
+
{ code: "MISSING_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Complete non-leaf implementation in a sim-adopting subsystem declares no simPath" }
|
|
13349
|
+
],
|
|
13350
|
+
check(ctx) {
|
|
13351
|
+
const adopted = /* @__PURE__ */ new Set();
|
|
13352
|
+
for (const impl of ctx.implementations) {
|
|
13353
|
+
if (!impl.simPath) continue;
|
|
13354
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
13355
|
+
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
13356
|
+
if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
13357
|
+
adopted.add(comp.subsystem);
|
|
13358
|
+
}
|
|
13359
|
+
for (const impl of ctx.implementations) {
|
|
13360
|
+
if (impl.simPath) continue;
|
|
13361
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
13362
|
+
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
13363
|
+
if (!comp) continue;
|
|
13364
|
+
if (ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
13365
|
+
if (!adopted.has(comp.subsystem)) continue;
|
|
13366
|
+
if (ctx.isImplementationDraft(impl)) continue;
|
|
13367
|
+
const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
|
|
13368
|
+
if (deps.length === 0) continue;
|
|
13369
|
+
ctx.addIssue(
|
|
13370
|
+
"warning",
|
|
13371
|
+
"MISSING_INTEGRATION_SIM",
|
|
13372
|
+
`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.`,
|
|
13373
|
+
impl.id
|
|
13374
|
+
);
|
|
13375
|
+
}
|
|
13376
|
+
}
|
|
13377
|
+
};
|
|
13378
|
+
}
|
|
13379
|
+
});
|
|
13380
|
+
|
|
13381
|
+
// src/core/rules/conformance/integration-sim-file.ts
|
|
13382
|
+
var integrationSimFileRule;
|
|
13383
|
+
var init_integration_sim_file = __esm({
|
|
13384
|
+
"src/core/rules/conformance/integration-sim-file.ts"() {
|
|
12967
13385
|
"use strict";
|
|
12968
13386
|
init_models();
|
|
12969
|
-
|
|
12970
|
-
name: "integration-
|
|
12971
|
-
description: "A
|
|
13387
|
+
integrationSimFileRule = {
|
|
13388
|
+
name: "integration-sim-file",
|
|
13389
|
+
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.",
|
|
12972
13390
|
codes: [
|
|
12973
|
-
{ code: "
|
|
12974
|
-
|
|
12975
|
-
|
|
12976
|
-
|
|
13391
|
+
{ code: "SIM_FILE_MISSING", defaultSeverity: "warning", summary: "Declared simPath resolves to no file inside the project root" }
|
|
13392
|
+
],
|
|
13393
|
+
check(ctx) {
|
|
13394
|
+
const code = ctx.codeIndex();
|
|
13395
|
+
for (const impl of ctx.implementations) {
|
|
13396
|
+
if (!impl.simPath) continue;
|
|
13397
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
13398
|
+
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
13399
|
+
if (!comp) continue;
|
|
13400
|
+
if (ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
13401
|
+
const facts = code.factsAt(pathKey(impl.simPath));
|
|
13402
|
+
if (facts && facts.status === "analyzed") continue;
|
|
13403
|
+
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";
|
|
13404
|
+
ctx.addIssue(
|
|
13405
|
+
"warning",
|
|
13406
|
+
"SIM_FILE_MISSING",
|
|
13407
|
+
`Implementation "${impl.id}" declares simPath "${impl.simPath}", which ${why}. The integration harness must be a committed, re-runnable file inside the project.`,
|
|
13408
|
+
impl.id,
|
|
13409
|
+
ctx.isImplementationDraft(impl)
|
|
13410
|
+
);
|
|
13411
|
+
}
|
|
13412
|
+
}
|
|
13413
|
+
};
|
|
13414
|
+
}
|
|
13415
|
+
});
|
|
13416
|
+
|
|
13417
|
+
// src/core/rules/conformance/integration-sim-wiring.ts
|
|
13418
|
+
var integrationSimWiringRule;
|
|
13419
|
+
var init_integration_sim_wiring = __esm({
|
|
13420
|
+
"src/core/rules/conformance/integration-sim-wiring.ts"() {
|
|
13421
|
+
"use strict";
|
|
13422
|
+
init_models();
|
|
13423
|
+
integrationSimWiringRule = {
|
|
13424
|
+
name: "integration-sim-wiring",
|
|
13425
|
+
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.",
|
|
13426
|
+
codes: [
|
|
13427
|
+
{ code: "UNWIRED_INTEGRATION_SIM", defaultSeverity: "warning", summary: "Sim file does not import the real modules it claims to wire" }
|
|
12977
13428
|
],
|
|
12978
13429
|
check(ctx) {
|
|
12979
13430
|
const code = ctx.codeIndex();
|
|
@@ -12985,48 +13436,18 @@ var init_integration_conformance = __esm({
|
|
|
12985
13436
|
};
|
|
12986
13437
|
const modulesOf = (compId) => realization.filesOf(compId).filter(exists);
|
|
12987
13438
|
const modulesIn = (subsystemId) => realization.filesIn(subsystemId).filter(exists);
|
|
12988
|
-
const adopted = /* @__PURE__ */ new Set();
|
|
12989
|
-
for (const impl of ctx.implementations) {
|
|
12990
|
-
if (!impl.simPath) continue;
|
|
12991
|
-
const contract = ctx.interfaceMap.get(impl.contract);
|
|
12992
|
-
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
12993
|
-
if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
12994
|
-
adopted.add(comp.subsystem);
|
|
12995
|
-
}
|
|
12996
13439
|
const isTechBoundary = (compId) => realization.implementationsOf(compId).some((i) => (i.technologies ?? []).length > 0);
|
|
12997
13440
|
for (const impl of ctx.implementations) {
|
|
13441
|
+
if (!impl.simPath) continue;
|
|
12998
13442
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
12999
13443
|
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
13000
13444
|
if (!comp) continue;
|
|
13001
13445
|
if (ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
13002
|
-
const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
|
|
13003
|
-
if (!impl.simPath) {
|
|
13004
|
-
if (!adopted.has(comp.subsystem)) continue;
|
|
13005
|
-
if (ctx.isImplementationDraft(impl)) continue;
|
|
13006
|
-
if (deps.length === 0) continue;
|
|
13007
|
-
ctx.addIssue(
|
|
13008
|
-
"warning",
|
|
13009
|
-
"MISSING_INTEGRATION_SIM",
|
|
13010
|
-
`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.`,
|
|
13011
|
-
impl.id
|
|
13012
|
-
);
|
|
13013
|
-
continue;
|
|
13014
|
-
}
|
|
13015
13446
|
const simPath = pathKey(impl.simPath);
|
|
13016
13447
|
const facts = code.factsAt(simPath);
|
|
13017
|
-
|
|
13018
|
-
if (!facts || facts.status === "missing" || facts.status === "escaped" || facts.status === "unreadable") {
|
|
13019
|
-
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";
|
|
13020
|
-
ctx.addIssue(
|
|
13021
|
-
"warning",
|
|
13022
|
-
"SIM_FILE_MISSING",
|
|
13023
|
-
`Implementation "${impl.id}" declares simPath "${impl.simPath}", which ${why}. The integration harness must be a committed, re-runnable file inside the project.`,
|
|
13024
|
-
impl.id,
|
|
13025
|
-
isDraftCtx
|
|
13026
|
-
);
|
|
13027
|
-
continue;
|
|
13028
|
-
}
|
|
13448
|
+
if (!facts || facts.status !== "analyzed") continue;
|
|
13029
13449
|
if (facts.analysisGrade !== "exact") continue;
|
|
13450
|
+
const deps = [.../* @__PURE__ */ new Set([...comp.dependsOn, ...comp.owns])].filter((d) => ctx.componentMap.has(d));
|
|
13030
13451
|
const reach = graph.reachFrom(simPath);
|
|
13031
13452
|
const ownFiles = modulesOf(comp.id);
|
|
13032
13453
|
const missing = [];
|
|
@@ -13043,15 +13464,43 @@ var init_integration_conformance = __esm({
|
|
|
13043
13464
|
missing.push(label);
|
|
13044
13465
|
}
|
|
13045
13466
|
}
|
|
13046
|
-
if (missing.length)
|
|
13047
|
-
|
|
13048
|
-
|
|
13049
|
-
|
|
13050
|
-
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
|
|
13054
|
-
|
|
13467
|
+
if (missing.length === 0) continue;
|
|
13468
|
+
ctx.addIssue(
|
|
13469
|
+
"warning",
|
|
13470
|
+
"UNWIRED_INTEGRATION_SIM",
|
|
13471
|
+
`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.`,
|
|
13472
|
+
impl.id,
|
|
13473
|
+
ctx.isImplementationDraft(impl)
|
|
13474
|
+
);
|
|
13475
|
+
}
|
|
13476
|
+
}
|
|
13477
|
+
};
|
|
13478
|
+
}
|
|
13479
|
+
});
|
|
13480
|
+
|
|
13481
|
+
// src/core/rules/conformance/integration-sim-coverage.ts
|
|
13482
|
+
var integrationSimCoverageRule;
|
|
13483
|
+
var init_integration_sim_coverage = __esm({
|
|
13484
|
+
"src/core/rules/conformance/integration-sim-coverage.ts"() {
|
|
13485
|
+
"use strict";
|
|
13486
|
+
init_models();
|
|
13487
|
+
integrationSimCoverageRule = {
|
|
13488
|
+
name: "integration-sim-coverage",
|
|
13489
|
+
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.",
|
|
13490
|
+
codes: [
|
|
13491
|
+
{ code: "SIM_PATH_UNCOVERED", defaultSeverity: "warning", summary: "A narrative path has no sim:<component>.<method>[:<label>] anchor in the component's coverage-opted harness" }
|
|
13492
|
+
],
|
|
13493
|
+
check(ctx) {
|
|
13494
|
+
const code = ctx.codeIndex();
|
|
13495
|
+
for (const impl of ctx.implementations) {
|
|
13496
|
+
if (!impl.simPath) continue;
|
|
13497
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
13498
|
+
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
13499
|
+
if (!comp) continue;
|
|
13500
|
+
if (ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
13501
|
+
const facts = code.factsAt(pathKey(impl.simPath));
|
|
13502
|
+
if (!facts || facts.status !== "analyzed") continue;
|
|
13503
|
+
if (facts.analysisGrade !== "exact") continue;
|
|
13055
13504
|
const simAnchors = new Set(facts.anchoredNames.filter((a) => a.startsWith("sim:")));
|
|
13056
13505
|
if (simAnchors.size === 0) continue;
|
|
13057
13506
|
const compPrefix = `sim:${comp.id}.`;
|
|
@@ -13072,15 +13521,14 @@ var init_integration_conformance = __esm({
|
|
|
13072
13521
|
}
|
|
13073
13522
|
}
|
|
13074
13523
|
}
|
|
13075
|
-
if (uncovered.length)
|
|
13076
|
-
|
|
13077
|
-
|
|
13078
|
-
|
|
13079
|
-
|
|
13080
|
-
|
|
13081
|
-
|
|
13082
|
-
|
|
13083
|
-
}
|
|
13524
|
+
if (uncovered.length === 0) continue;
|
|
13525
|
+
ctx.addIssue(
|
|
13526
|
+
"warning",
|
|
13527
|
+
"SIM_PATH_UNCOVERED",
|
|
13528
|
+
`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.`,
|
|
13529
|
+
impl.id,
|
|
13530
|
+
ctx.isImplementationDraft(impl)
|
|
13531
|
+
);
|
|
13084
13532
|
}
|
|
13085
13533
|
}
|
|
13086
13534
|
};
|
|
@@ -13190,10 +13638,10 @@ var init_coupling_health = __esm({
|
|
|
13190
13638
|
}
|
|
13191
13639
|
});
|
|
13192
13640
|
|
|
13193
|
-
// src/core/rules/heuristic/
|
|
13194
|
-
var LANGUAGE_MARKERS,
|
|
13195
|
-
var
|
|
13196
|
-
"src/core/rules/heuristic/
|
|
13641
|
+
// src/core/rules/heuristic/signature-language-builtins.ts
|
|
13642
|
+
var LANGUAGE_MARKERS, signatureLanguageBuiltinsRule;
|
|
13643
|
+
var init_signature_language_builtins = __esm({
|
|
13644
|
+
"src/core/rules/heuristic/signature-language-builtins.ts"() {
|
|
13197
13645
|
"use strict";
|
|
13198
13646
|
init_models();
|
|
13199
13647
|
LANGUAGE_MARKERS = {
|
|
@@ -13228,6 +13676,68 @@ var init_target_language = __esm({
|
|
|
13228
13676
|
csharp: /* @__PURE__ */ new Set(["task"]),
|
|
13229
13677
|
go: /* @__PURE__ */ new Set(["chan", "rune"])
|
|
13230
13678
|
};
|
|
13679
|
+
signatureLanguageBuiltinsRule = {
|
|
13680
|
+
name: "signature-language-builtins",
|
|
13681
|
+
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).",
|
|
13682
|
+
codes: [
|
|
13683
|
+
{ code: "LANGUAGE_FOREIGN_BUILTIN", defaultSeverity: "warning", summary: "Signature uses a builtin from a different language family" }
|
|
13684
|
+
],
|
|
13685
|
+
check(ctx) {
|
|
13686
|
+
const markersFor = (family) => {
|
|
13687
|
+
const base = LANGUAGE_MARKERS[family];
|
|
13688
|
+
const extra = ctx.ext.languages[family]?.foreignBuiltins;
|
|
13689
|
+
if (!extra?.length) return base;
|
|
13690
|
+
return /* @__PURE__ */ new Set([...base ?? [], ...extra.map((s) => s.toLowerCase())]);
|
|
13691
|
+
};
|
|
13692
|
+
const families = [.../* @__PURE__ */ new Set([...Object.keys(LANGUAGE_MARKERS), ...Object.keys(ctx.ext.languages)])];
|
|
13693
|
+
const scans = [];
|
|
13694
|
+
for (const intf of ctx.interfaces) {
|
|
13695
|
+
const comp = ctx.componentMap.get(intf.component);
|
|
13696
|
+
const lang = ctx.targetLanguageFor(comp?.subsystem);
|
|
13697
|
+
if (!lang) continue;
|
|
13698
|
+
const ownMarkers = markersFor(lang);
|
|
13699
|
+
if (!ownMarkers || ownMarkers.size === 0) continue;
|
|
13700
|
+
const draft = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
13701
|
+
for (const method2 of intf.methods) {
|
|
13702
|
+
for (const ref of methodTypeRefs(method2)) scans.push({ intf, method: method2, ref, lang, ownMarkers, draft });
|
|
13703
|
+
}
|
|
13704
|
+
}
|
|
13705
|
+
for (const scan of scans) {
|
|
13706
|
+
const refLower = scan.ref.toLowerCase();
|
|
13707
|
+
if (scan.ownMarkers.has(refLower)) continue;
|
|
13708
|
+
const foreign = families.find((family) => family !== scan.lang && markersFor(family)?.has(refLower));
|
|
13709
|
+
if (!foreign) continue;
|
|
13710
|
+
ctx.addIssue(
|
|
13711
|
+
"warning",
|
|
13712
|
+
"LANGUAGE_FOREIGN_BUILTIN",
|
|
13713
|
+
`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.`,
|
|
13714
|
+
scan.intf.id,
|
|
13715
|
+
scan.draft
|
|
13716
|
+
);
|
|
13717
|
+
}
|
|
13718
|
+
}
|
|
13719
|
+
};
|
|
13720
|
+
}
|
|
13721
|
+
});
|
|
13722
|
+
|
|
13723
|
+
// src/core/rules/heuristic/narrative-language-constructs.ts
|
|
13724
|
+
function constructsOf(step) {
|
|
13725
|
+
const constructs = [
|
|
13726
|
+
step.type === "loop" ? step.loopKind ?? (step.over ? "forEach" : "while") : step.type
|
|
13727
|
+
];
|
|
13728
|
+
if (step.detach) constructs.push("detach");
|
|
13729
|
+
return constructs;
|
|
13730
|
+
}
|
|
13731
|
+
function constructLabel(construct) {
|
|
13732
|
+
if (construct === "doWhile") return "a do-while loop";
|
|
13733
|
+
if (construct === "forEach" || construct === "for" || construct === "while") return `a ${construct} loop`;
|
|
13734
|
+
if (construct === "detach") return "a detached (fire-and-forget) call";
|
|
13735
|
+
return `a ${construct} step`;
|
|
13736
|
+
}
|
|
13737
|
+
var UNSUPPORTED_FLOW, narrativeLanguageConstructsRule;
|
|
13738
|
+
var init_narrative_language_constructs = __esm({
|
|
13739
|
+
"src/core/rules/heuristic/narrative-language-constructs.ts"() {
|
|
13740
|
+
"use strict";
|
|
13231
13741
|
UNSUPPORTED_FLOW = {
|
|
13232
13742
|
rust: {
|
|
13233
13743
|
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",
|
|
@@ -13247,53 +13757,18 @@ var init_target_language = __esm({
|
|
|
13247
13757
|
doWhile: "Python has no do-while \u2014 prefer loopKind: while, or state the emulation (while True + break)"
|
|
13248
13758
|
}
|
|
13249
13759
|
};
|
|
13250
|
-
|
|
13251
|
-
name: "
|
|
13252
|
-
description: "
|
|
13760
|
+
narrativeLanguageConstructsRule = {
|
|
13761
|
+
name: "narrative-language-constructs",
|
|
13762
|
+
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).",
|
|
13253
13763
|
codes: [
|
|
13254
|
-
{ code: "LANGUAGE_FOREIGN_BUILTIN", defaultSeverity: "warning", summary: "Signature uses a builtin from a different language family" },
|
|
13255
13764
|
{ code: "LANGUAGE_FOREIGN_FLOW", defaultSeverity: "warning", summary: "Narrative flow step uses a construct the target language does not have" }
|
|
13256
13765
|
],
|
|
13257
13766
|
check(ctx) {
|
|
13258
|
-
const markersFor = (family) => {
|
|
13259
|
-
const base = LANGUAGE_MARKERS[family];
|
|
13260
|
-
const extra = ctx.ext.languages[family]?.foreignBuiltins;
|
|
13261
|
-
if (!extra?.length) return base;
|
|
13262
|
-
return /* @__PURE__ */ new Set([...base ?? [], ...extra.map((s) => s.toLowerCase())]);
|
|
13263
|
-
};
|
|
13264
|
-
const families = /* @__PURE__ */ new Set([...Object.keys(LANGUAGE_MARKERS), ...Object.keys(ctx.ext.languages)]);
|
|
13265
13767
|
const gapsFor = (lang) => ({
|
|
13266
13768
|
...UNSUPPORTED_FLOW[lang] ?? {},
|
|
13267
13769
|
...ctx.ext.languages[lang]?.unsupportedFlow ?? {}
|
|
13268
13770
|
});
|
|
13269
|
-
|
|
13270
|
-
const comp = ctx.componentMap.get(intf.component);
|
|
13271
|
-
const lang = ctx.targetLanguageFor(comp?.subsystem);
|
|
13272
|
-
if (!lang) continue;
|
|
13273
|
-
const ownMarkers = markersFor(lang);
|
|
13274
|
-
if (!ownMarkers || ownMarkers.size === 0) continue;
|
|
13275
|
-
const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
13276
|
-
for (const m of intf.methods) {
|
|
13277
|
-
const refs = methodTypeRefs(m);
|
|
13278
|
-
for (const ref of refs) {
|
|
13279
|
-
const refLower = ref.toLowerCase();
|
|
13280
|
-
if (ownMarkers.has(refLower)) continue;
|
|
13281
|
-
for (const family of families) {
|
|
13282
|
-
if (family === lang) continue;
|
|
13283
|
-
if (markersFor(family)?.has(refLower)) {
|
|
13284
|
-
ctx.addIssue(
|
|
13285
|
-
"warning",
|
|
13286
|
-
"LANGUAGE_FOREIGN_BUILTIN",
|
|
13287
|
-
`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.`,
|
|
13288
|
-
intf.id,
|
|
13289
|
-
isDraftCtx
|
|
13290
|
-
);
|
|
13291
|
-
break;
|
|
13292
|
-
}
|
|
13293
|
-
}
|
|
13294
|
-
}
|
|
13295
|
-
}
|
|
13296
|
-
}
|
|
13771
|
+
const scans = [];
|
|
13297
13772
|
for (const impl of ctx.implementations) {
|
|
13298
13773
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
13299
13774
|
if (!contract) continue;
|
|
@@ -13302,28 +13777,54 @@ var init_target_language = __esm({
|
|
|
13302
13777
|
if (!lang) continue;
|
|
13303
13778
|
const gaps = gapsFor(lang);
|
|
13304
13779
|
if (Object.keys(gaps).length === 0) continue;
|
|
13305
|
-
const
|
|
13306
|
-
for (const
|
|
13307
|
-
for (const step of
|
|
13308
|
-
const constructs = [
|
|
13309
|
-
step.type === "loop" ? step.loopKind ?? (step.over ? "forEach" : "while") : step.type
|
|
13310
|
-
];
|
|
13311
|
-
if (step.detach) constructs.push("detach");
|
|
13312
|
-
for (const construct of constructs) {
|
|
13313
|
-
const guidance = gaps[construct];
|
|
13314
|
-
if (!guidance) continue;
|
|
13315
|
-
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`;
|
|
13316
|
-
ctx.addIssue(
|
|
13317
|
-
"warning",
|
|
13318
|
-
"LANGUAGE_FOREIGN_FLOW",
|
|
13319
|
-
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" uses ${label}, but the target language is ${lang}: ${guidance}.`,
|
|
13320
|
-
impl.id,
|
|
13321
|
-
isDraftCtx
|
|
13322
|
-
);
|
|
13323
|
-
}
|
|
13324
|
-
}
|
|
13780
|
+
const draft = ctx.isImplementationDraft(impl);
|
|
13781
|
+
for (const method2 of impl.methods) {
|
|
13782
|
+
for (const step of method2.narrative) scans.push({ implementation: impl, method: method2, step, lang, gaps, draft });
|
|
13325
13783
|
}
|
|
13326
13784
|
}
|
|
13785
|
+
for (const scan of scans) {
|
|
13786
|
+
for (const construct of constructsOf(scan.step)) {
|
|
13787
|
+
const guidance = scan.gaps[construct];
|
|
13788
|
+
if (!guidance) continue;
|
|
13789
|
+
ctx.addIssue(
|
|
13790
|
+
"warning",
|
|
13791
|
+
"LANGUAGE_FOREIGN_FLOW",
|
|
13792
|
+
`Step ${scan.step.stepNumber} of "${scan.method.name}" in implementation "${scan.implementation.id}" uses ${constructLabel(construct)}, but the target language is ${scan.lang}: ${guidance}.`,
|
|
13793
|
+
scan.implementation.id,
|
|
13794
|
+
scan.draft
|
|
13795
|
+
);
|
|
13796
|
+
}
|
|
13797
|
+
}
|
|
13798
|
+
}
|
|
13799
|
+
};
|
|
13800
|
+
}
|
|
13801
|
+
});
|
|
13802
|
+
|
|
13803
|
+
// src/core/rules/heuristic/technology-binding.ts
|
|
13804
|
+
var DATA_LAYER, technologyBindingRule;
|
|
13805
|
+
var init_technology_binding = __esm({
|
|
13806
|
+
"src/core/rules/heuristic/technology-binding.ts"() {
|
|
13807
|
+
"use strict";
|
|
13808
|
+
DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
|
|
13809
|
+
technologyBindingRule = {
|
|
13810
|
+
name: "technology-binding",
|
|
13811
|
+
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.",
|
|
13812
|
+
codes: [
|
|
13813
|
+
{ code: "TECH_ON_LOGIC_COMPONENT", defaultSeverity: "warning", summary: "Technology bound by a non-data-layer stereotype" }
|
|
13814
|
+
],
|
|
13815
|
+
check(ctx) {
|
|
13816
|
+
for (const impl of ctx.implementations) {
|
|
13817
|
+
const intf = ctx.interfaceMap.get(impl.contract);
|
|
13818
|
+
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
13819
|
+
if (!impl.technologies?.length || !comp || DATA_LAYER.has(comp.componentType)) continue;
|
|
13820
|
+
ctx.addIssue(
|
|
13821
|
+
"warning",
|
|
13822
|
+
"TECH_ON_LOGIC_COMPONENT",
|
|
13823
|
+
`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.`,
|
|
13824
|
+
impl.id,
|
|
13825
|
+
ctx.isImplementationDraft(impl)
|
|
13826
|
+
);
|
|
13827
|
+
}
|
|
13327
13828
|
}
|
|
13328
13829
|
};
|
|
13329
13830
|
}
|
|
@@ -13352,19 +13853,29 @@ function contractIdentifiers(m) {
|
|
|
13352
13853
|
if (m.endpoint) parts.push(Object.values(m.endpoint).map(String).join(" "));
|
|
13353
13854
|
return parts.join(" ");
|
|
13354
13855
|
}
|
|
13355
|
-
|
|
13856
|
+
function implementationText(impl) {
|
|
13857
|
+
const parts = [impl.description, ...implementationSourceFiles(impl)];
|
|
13858
|
+
for (const m of impl.methods) {
|
|
13859
|
+
parts.push(m.intent);
|
|
13860
|
+
for (const s of m.narrative ?? []) {
|
|
13861
|
+
parts.push(s.description, s.targetComponent, s.targetMethod, s.condition, s.over, s.on, s.outcome, s.error);
|
|
13862
|
+
for (const c of s.cases ?? []) parts.push(c.value);
|
|
13863
|
+
for (const c of s.catches ?? []) parts.push(c.error);
|
|
13864
|
+
}
|
|
13865
|
+
}
|
|
13866
|
+
return parts.filter(Boolean).join(" ");
|
|
13867
|
+
}
|
|
13868
|
+
var technologyRule;
|
|
13356
13869
|
var init_technology_boundaries = __esm({
|
|
13357
13870
|
"src/core/rules/heuristic/technology-boundaries.ts"() {
|
|
13358
13871
|
"use strict";
|
|
13359
13872
|
init_models();
|
|
13360
|
-
DATA_LAYER = /* @__PURE__ */ new Set(["Adapter", "Store", "Registry", "Index"]);
|
|
13361
13873
|
technologyRule = {
|
|
13362
13874
|
name: "technology-boundaries",
|
|
13363
|
-
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
|
|
13875
|
+
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.",
|
|
13364
13876
|
codes: [
|
|
13365
13877
|
{ code: "TECH_LEAKAGE", defaultSeverity: "warning", summary: "Technology referenced outside its owning boundary" },
|
|
13366
|
-
{ code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" }
|
|
13367
|
-
{ code: "TECH_ON_LOGIC_COMPONENT", defaultSeverity: "warning", summary: "Technology bound by a non-data-layer stereotype" }
|
|
13878
|
+
{ code: "VENDOR_NAME_IN_CONTRACT", defaultSeverity: "warning", summary: "Technology name in L3 contract identifiers" }
|
|
13368
13879
|
],
|
|
13369
13880
|
check(ctx) {
|
|
13370
13881
|
const ownerOf = /* @__PURE__ */ new Map();
|
|
@@ -13392,21 +13903,18 @@ var init_technology_boundaries = __esm({
|
|
|
13392
13903
|
}
|
|
13393
13904
|
return out;
|
|
13394
13905
|
};
|
|
13395
|
-
const
|
|
13906
|
+
const declarations = [];
|
|
13396
13907
|
for (const impl of ctx.implementations) {
|
|
13397
13908
|
if (!impl.technologies?.length) continue;
|
|
13398
13909
|
const intf = ctx.interfaceMap.get(impl.contract);
|
|
13399
13910
|
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
13400
13911
|
if (!comp) continue;
|
|
13401
|
-
|
|
13402
|
-
|
|
13403
|
-
|
|
13404
|
-
|
|
13405
|
-
|
|
13406
|
-
|
|
13407
|
-
ctx.isImplementationDraft(impl)
|
|
13408
|
-
);
|
|
13409
|
-
}
|
|
13912
|
+
for (const tech of impl.technologies) declarations.push({ comp, tech });
|
|
13913
|
+
}
|
|
13914
|
+
const scopes = /* @__PURE__ */ new Map();
|
|
13915
|
+
const scopeOf2 = (comp) => {
|
|
13916
|
+
const cached = scopes.get(comp.id);
|
|
13917
|
+
if (cached) return cached;
|
|
13410
13918
|
const compSet = ownsClosure(ownershipRoot(comp.id));
|
|
13411
13919
|
const scope = new Set(compSet);
|
|
13412
13920
|
for (const i of ctx.interfaces) if (compSet.has(i.component)) scope.add(i.id);
|
|
@@ -13421,15 +13929,18 @@ var init_technology_boundaries = __esm({
|
|
|
13421
13929
|
if (subId === s.id || subId.startsWith(`${s.id}::`)) scope.add(s.id);
|
|
13422
13930
|
}
|
|
13423
13931
|
}
|
|
13424
|
-
|
|
13425
|
-
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
|
|
13432
|
-
}
|
|
13932
|
+
scopes.set(comp.id, scope);
|
|
13933
|
+
return scope;
|
|
13934
|
+
};
|
|
13935
|
+
const homes = /* @__PURE__ */ new Map();
|
|
13936
|
+
for (const { comp, tech } of declarations) {
|
|
13937
|
+
const match = makeMatcher(tech);
|
|
13938
|
+
if (!match) continue;
|
|
13939
|
+
const key = tech.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).join("");
|
|
13940
|
+
const home = homes.get(key) ?? { label: tech, match, ownerComponents: /* @__PURE__ */ new Set(), scope: /* @__PURE__ */ new Set() };
|
|
13941
|
+
home.ownerComponents.add(comp.id);
|
|
13942
|
+
for (const id of scopeOf2(comp)) home.scope.add(id);
|
|
13943
|
+
homes.set(key, home);
|
|
13433
13944
|
}
|
|
13434
13945
|
if (homes.size === 0) return;
|
|
13435
13946
|
const ownersDesc = (h) => [...h.ownerComponents].map((c) => `"${c}"`).join(", ");
|
|
@@ -13493,16 +14004,7 @@ var init_technology_boundaries = __esm({
|
|
|
13493
14004
|
}
|
|
13494
14005
|
for (const impl of ctx.implementations) {
|
|
13495
14006
|
if (home.scope.has(impl.id)) continue;
|
|
13496
|
-
|
|
13497
|
-
for (const m of impl.methods) {
|
|
13498
|
-
parts.push(m.intent);
|
|
13499
|
-
for (const s of m.narrative ?? []) {
|
|
13500
|
-
parts.push(s.description, s.targetComponent, s.targetMethod, s.condition, s.over, s.on, s.outcome, s.error);
|
|
13501
|
-
for (const c of s.cases ?? []) parts.push(c.value);
|
|
13502
|
-
for (const c of s.catches ?? []) parts.push(c.error);
|
|
13503
|
-
}
|
|
13504
|
-
}
|
|
13505
|
-
if (home.match(parts.filter(Boolean).join(" "))) {
|
|
14007
|
+
if (home.match(implementationText(impl))) {
|
|
13506
14008
|
ctx.addIssue(
|
|
13507
14009
|
"warning",
|
|
13508
14010
|
"TECH_LEAKAGE",
|
|
@@ -14191,12 +14693,15 @@ var init_repository = __esm({
|
|
|
14191
14693
|
"src/core/rules/repository.ts"() {
|
|
14192
14694
|
"use strict";
|
|
14193
14695
|
init_hierarchy_integrity();
|
|
14194
|
-
|
|
14696
|
+
init_reserved_id_segments();
|
|
14697
|
+
init_namespace_shadowing();
|
|
14195
14698
|
init_roundtrip_serialization();
|
|
14196
14699
|
init_type_declarations();
|
|
14197
14700
|
init_field_type_references();
|
|
14198
14701
|
init_signature_type_references();
|
|
14199
|
-
|
|
14702
|
+
init_public_surface_binding();
|
|
14703
|
+
init_public_surface_declared_type();
|
|
14704
|
+
init_public_surface_bound_contract();
|
|
14200
14705
|
init_lint_allows();
|
|
14201
14706
|
init_contract_symmetry();
|
|
14202
14707
|
init_narrative_target_references();
|
|
@@ -14206,14 +14711,19 @@ var init_repository = __esm({
|
|
|
14206
14711
|
init_narrative_step_config();
|
|
14207
14712
|
init_narrative_reachability();
|
|
14208
14713
|
init_narrative_jump_edges();
|
|
14209
|
-
|
|
14714
|
+
init_meaningless_branches();
|
|
14715
|
+
init_inescapable_cycles();
|
|
14716
|
+
init_unconditional_call_cycles();
|
|
14210
14717
|
init_narrative_detail();
|
|
14718
|
+
init_detail_sufficiency();
|
|
14211
14719
|
init_portal_fields();
|
|
14212
14720
|
init_durability_declaration();
|
|
14213
14721
|
init_logic_declaration();
|
|
14214
14722
|
init_retired_stereotypes();
|
|
14215
14723
|
init_portal_endpoints();
|
|
14724
|
+
init_non_portal_endpoints();
|
|
14216
14725
|
init_portal_call_auth();
|
|
14726
|
+
init_auth_source_wiring();
|
|
14217
14727
|
init_subsystem_boundary_dependencies();
|
|
14218
14728
|
init_logic_dependency_class();
|
|
14219
14729
|
init_data_block_dependencies();
|
|
@@ -14229,7 +14739,9 @@ var init_repository = __esm({
|
|
|
14229
14739
|
init_pack_profile_stereotypes();
|
|
14230
14740
|
init_pattern_references();
|
|
14231
14741
|
init_component_variants();
|
|
14232
|
-
|
|
14742
|
+
init_assertion_forbidden_edges();
|
|
14743
|
+
init_assertion_required_fields();
|
|
14744
|
+
init_assertion_endpoint_shapes();
|
|
14233
14745
|
init_pack_resolution();
|
|
14234
14746
|
init_pack_reproducibility();
|
|
14235
14747
|
init_dependency_cycles();
|
|
@@ -14237,10 +14749,14 @@ var init_repository = __esm({
|
|
|
14237
14749
|
init_dispatch_step_routing();
|
|
14238
14750
|
init_lifecycle_entrypoints();
|
|
14239
14751
|
init_unused_detection();
|
|
14752
|
+
init_invoked_by_description();
|
|
14753
|
+
init_unused_types();
|
|
14240
14754
|
init_durability_round_trip();
|
|
14241
14755
|
init_untyped_seams();
|
|
14242
14756
|
init_prose_claims();
|
|
14757
|
+
init_unique_invariant_ids();
|
|
14243
14758
|
init_invariant_backing();
|
|
14759
|
+
init_invariant_references();
|
|
14244
14760
|
init_event_topology();
|
|
14245
14761
|
init_source_file_linkage();
|
|
14246
14762
|
init_method_realization();
|
|
@@ -14248,9 +14764,14 @@ var init_repository = __esm({
|
|
|
14248
14764
|
init_call_conformance();
|
|
14249
14765
|
init_hidden_state();
|
|
14250
14766
|
init_dependency_conformance();
|
|
14251
|
-
|
|
14767
|
+
init_integration_sim_declaration();
|
|
14768
|
+
init_integration_sim_file();
|
|
14769
|
+
init_integration_sim_wiring();
|
|
14770
|
+
init_integration_sim_coverage();
|
|
14252
14771
|
init_coupling_health();
|
|
14253
|
-
|
|
14772
|
+
init_signature_language_builtins();
|
|
14773
|
+
init_narrative_language_constructs();
|
|
14774
|
+
init_technology_binding();
|
|
14254
14775
|
init_technology_boundaries();
|
|
14255
14776
|
init_naming_conventions();
|
|
14256
14777
|
init_complexity_and_metadata();
|
|
@@ -14260,8 +14781,11 @@ var init_repository = __esm({
|
|
|
14260
14781
|
SDD_RULES = [
|
|
14261
14782
|
hierarchyRule,
|
|
14262
14783
|
// Namespace integrity right after hierarchy: unresolvable/unwritable ids
|
|
14263
|
-
// explain many downstream findings, so surface them early in the list.
|
|
14264
|
-
|
|
14784
|
+
// explain many downstream findings, so surface them early in the list. Two
|
|
14785
|
+
// questions of the same ids: the segment no id may spend, and the local name
|
|
14786
|
+
// that would anchor a bare reference to the root instead.
|
|
14787
|
+
reservedIdSegmentsRule,
|
|
14788
|
+
namespaceShadowingRule,
|
|
14265
14789
|
roundtripRule,
|
|
14266
14790
|
// The type vocabulary in three questions: what a type declares about
|
|
14267
14791
|
// itself, then the identifiers its fields name, then the ones its
|
|
@@ -14287,18 +14811,30 @@ var init_repository = __esm({
|
|
|
14287
14811
|
narrativeStepConfigRule,
|
|
14288
14812
|
narrativeReachabilityRule,
|
|
14289
14813
|
narrativeJumpEdgesRule,
|
|
14290
|
-
// Antipatterns right after flow soundness: they
|
|
14291
|
-
//
|
|
14292
|
-
|
|
14814
|
+
// Antipatterns right after flow soundness: they read the same step graphs
|
|
14815
|
+
// and only make sense once the graphs are structurally valid. Three things
|
|
14816
|
+
// structure alone can prove, in widening scope: a decision that decides
|
|
14817
|
+
// nothing, a step cycle nothing leaves, and a call cycle with no guard.
|
|
14818
|
+
meaninglessBranchesRule,
|
|
14819
|
+
inescapableCyclesRule,
|
|
14820
|
+
unconditionalCallCyclesRule,
|
|
14821
|
+
// The detail dial in two questions: does a method carry the detail its level
|
|
14822
|
+
// promises, and is that level low enough to be hiding something.
|
|
14293
14823
|
narrativeDetailRule,
|
|
14824
|
+
detailSufficiencyRule,
|
|
14294
14825
|
// Field shape before endpoint bindings: a Portal-only field on the wrong
|
|
14295
14826
|
// stereotype explains the endpoint findings around it, and this half is
|
|
14296
14827
|
// spec-scoped so the write boundary refuses it first.
|
|
14297
14828
|
portalFieldsRule,
|
|
14829
|
+
// Endpoints from both sides: a Portal binds every method to its transport,
|
|
14830
|
+
// and nothing that is not a Portal may bind one at all.
|
|
14298
14831
|
portalsRule,
|
|
14299
|
-
|
|
14300
|
-
//
|
|
14832
|
+
nonPortalEndpointsRule,
|
|
14833
|
+
// Cross-call auth rides with the portal family, in two questions: does a
|
|
14834
|
+
// call into an authed Portal present a credential properly, and does the
|
|
14835
|
+
// source it names resolve to a provider it is wired to.
|
|
14301
14836
|
portalCallAuthRule,
|
|
14837
|
+
authSourceWiringRule,
|
|
14302
14838
|
// Dependencies in five questions, one owner each: where an edge is
|
|
14303
14839
|
// allowed to LAND (the boundary, and what an unresolved id means), then
|
|
14304
14840
|
// the intra-subsystem matrix by the layer that answers for it — an
|
|
@@ -14328,9 +14864,18 @@ var init_repository = __esm({
|
|
|
14328
14864
|
patternReferencesRule,
|
|
14329
14865
|
variantReferencesRule,
|
|
14330
14866
|
// Pack-instantiated declarative doctrine rides with the pack-reference
|
|
14331
|
-
// family: same data source, same provenance-bearing findings.
|
|
14332
|
-
|
|
14333
|
-
|
|
14867
|
+
// family: same data source, same provenance-bearing findings. One rule per
|
|
14868
|
+
// assertion kind — the kind is what a pack author writes, and each kind
|
|
14869
|
+
// asks its own question of its own collection.
|
|
14870
|
+
assertionForbiddenEdgesRule,
|
|
14871
|
+
assertionRequiredFieldsRule,
|
|
14872
|
+
assertionEndpointShapesRule,
|
|
14873
|
+
// The published surface in three questions: what backs the entry, whether
|
|
14874
|
+
// that component's stereotype can realize the type it declares, and whether
|
|
14875
|
+
// the contract it binds is that component's own.
|
|
14876
|
+
publicSurfaceBindingRule,
|
|
14877
|
+
publicSurfaceDeclaredTypeRule,
|
|
14878
|
+
publicSurfaceBoundContractRule,
|
|
14334
14879
|
cyclesRule,
|
|
14335
14880
|
// Semantic-edge family: dispatch/lifecycle validity BEFORE reachability so a
|
|
14336
14881
|
// reader sees the broken edge finding next to the unused-detection fallout
|
|
@@ -14338,7 +14883,11 @@ var init_repository = __esm({
|
|
|
14338
14883
|
dispatchTableBindingsRule,
|
|
14339
14884
|
dispatchStepRoutingRule,
|
|
14340
14885
|
lifecycleRule,
|
|
14886
|
+
// The declared entrypoint's own prose before the walk that its declaration
|
|
14887
|
+
// silences, then the walk, then the types no walk can reach.
|
|
14888
|
+
invokedByDescriptionRule,
|
|
14341
14889
|
reachabilityRule,
|
|
14890
|
+
unusedTypesRule,
|
|
14342
14891
|
// The declaration (spec-scoped, refused at the write boundary) before the
|
|
14343
14892
|
// round-trip consequences it enables.
|
|
14344
14893
|
durabilityDeclarationRule,
|
|
@@ -14350,8 +14899,13 @@ var init_repository = __esm({
|
|
|
14350
14899
|
untypedSeamRule,
|
|
14351
14900
|
proseClaimRule,
|
|
14352
14901
|
// Invariant registry rides with the semantic-edge family: declared entity
|
|
14353
|
-
// invariants must be asserted on every write path (declarations, not
|
|
14902
|
+
// invariants must be asserted on every write path (declarations, not
|
|
14903
|
+
// proofs). Three questions of one registry: are the entity's ids unique,
|
|
14904
|
+
// does each invariant reach every write path of its owner, and does every
|
|
14905
|
+
// asserted reference name something declared.
|
|
14906
|
+
uniqueInvariantIdsRule,
|
|
14354
14907
|
invariantBackingRule,
|
|
14908
|
+
invariantReferencesRule,
|
|
14355
14909
|
// Pub/sub completeness: emitted topics need subscribers and vice versa.
|
|
14356
14910
|
eventTopologyRule,
|
|
14357
14911
|
// Code↔spec: structural conformance consumes the injected CodeModel (built
|
|
@@ -14370,10 +14924,22 @@ var init_repository = __esm({
|
|
|
14370
14924
|
hiddenStateRule,
|
|
14371
14925
|
dependencyConformanceRule,
|
|
14372
14926
|
// Integration wiring proof rides after the code↔spec family: it consumes
|
|
14373
|
-
// the same code model and speaks about the same sourcePath modules.
|
|
14374
|
-
|
|
14927
|
+
// the same code model and speaks about the same sourcePath modules. Four
|
|
14928
|
+
// questions about one harness, in the order a reader meets them: is one
|
|
14929
|
+
// expected here, does the declared one exist, does it wire the real
|
|
14930
|
+
// modules, does it name every narrated path.
|
|
14931
|
+
integrationSimDeclarationRule,
|
|
14932
|
+
integrationSimFileRule,
|
|
14933
|
+
integrationSimWiringRule,
|
|
14934
|
+
integrationSimCoverageRule,
|
|
14375
14935
|
couplingRule,
|
|
14376
|
-
|
|
14936
|
+
// Target-language fit in two questions: what a CONTRACT may name, and what
|
|
14937
|
+
// a NARRATIVE may describe.
|
|
14938
|
+
signatureLanguageBuiltinsRule,
|
|
14939
|
+
narrativeLanguageConstructsRule,
|
|
14940
|
+
// The declaration before the consequences: which stereotype may bind a
|
|
14941
|
+
// technology at all, then where its name may appear.
|
|
14942
|
+
technologyBindingRule,
|
|
14377
14943
|
technologyRule,
|
|
14378
14944
|
namingRule,
|
|
14379
14945
|
complexityRule,
|
|
@@ -15206,6 +15772,10 @@ function buildRuleContext(opts) {
|
|
|
15206
15772
|
const ownershipIndex = () => ownershipMemo ??= buildOwnershipIndex(ctx);
|
|
15207
15773
|
let dependencyEdgesMemo;
|
|
15208
15774
|
const dependencyEdges = () => dependencyEdgesMemo ??= buildDependencyEdges(ctx);
|
|
15775
|
+
let specIdsMemo;
|
|
15776
|
+
const specIds = () => specIdsMemo ??= buildSpecIds(ctx);
|
|
15777
|
+
let implementationMethodsMemo;
|
|
15778
|
+
const implementationMethods = () => implementationMethodsMemo ??= buildImplementationMethods(ctx);
|
|
15209
15779
|
const ctx = {
|
|
15210
15780
|
system,
|
|
15211
15781
|
subsystems,
|
|
@@ -15240,6 +15810,8 @@ function buildRuleContext(opts) {
|
|
|
15240
15810
|
importGraph,
|
|
15241
15811
|
ownershipIndex,
|
|
15242
15812
|
dependencyEdges,
|
|
15813
|
+
specIds,
|
|
15814
|
+
implementationMethods,
|
|
15243
15815
|
complexityConfigFor,
|
|
15244
15816
|
documentationConfigFor,
|
|
15245
15817
|
namingConfigFor,
|
|
@@ -22194,8 +22766,8 @@ function generateSequenceDiagram(componentId, methodName, options) {
|
|
|
22194
22766
|
const componentById = new Map(graph.components.map((c) => [c.id, c]));
|
|
22195
22767
|
const resolveComponent = (id) => {
|
|
22196
22768
|
if (componentById.has(id)) return componentById.get(id);
|
|
22197
|
-
const
|
|
22198
|
-
return
|
|
22769
|
+
const matches = graph.components.filter((c) => c.id.endsWith(`::${id}`));
|
|
22770
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
22199
22771
|
};
|
|
22200
22772
|
const entry = resolveComponent(componentId);
|
|
22201
22773
|
if (!entry) {
|
|
@@ -23527,9 +24099,9 @@ var init_specs2 = __esm({
|
|
|
23527
24099
|
}
|
|
23528
24100
|
}
|
|
23529
24101
|
const suffix = `::${id}`;
|
|
23530
|
-
const
|
|
23531
|
-
if (
|
|
23532
|
-
return index.paths.subsystem[
|
|
24102
|
+
const matches = Object.keys(index.paths.subsystem).filter((key) => key.endsWith(suffix));
|
|
24103
|
+
if (matches.length === 1) {
|
|
24104
|
+
return index.paths.subsystem[matches[0]];
|
|
23533
24105
|
}
|
|
23534
24106
|
if (pathExists(this.paths.specsSubsystemsDir()) && listFiles(this.paths.specsSubsystemsDir(), ".yaml").length > 0) {
|
|
23535
24107
|
return path23.join(this.paths.specsSubsystemsDir(), `${id}.yaml`);
|
|
@@ -28816,8 +29388,8 @@ function materializeAgentLayer(agents, projectConfig, options) {
|
|
|
28816
29388
|
} else if (options.domains) {
|
|
28817
29389
|
filterDomainIds = options.domains.split(",").map((s) => s.trim()).filter(Boolean);
|
|
28818
29390
|
} else if (options.domain) {
|
|
28819
|
-
const
|
|
28820
|
-
filterDomainIds = Array.from(/* @__PURE__ */ new Set([options.domain, ...
|
|
29391
|
+
const matches = agents.map((a) => a.domainRoot).filter((d) => !!d && (d === options.domain || d.startsWith(`${options.domain}::`)));
|
|
29392
|
+
filterDomainIds = Array.from(/* @__PURE__ */ new Set([options.domain, ...matches]));
|
|
28821
29393
|
}
|
|
28822
29394
|
const agentPool = filterDomainIds ? agents.filter((a) => filterDomainIds.includes(a.domainRoot ?? "root")) : agents;
|
|
28823
29395
|
if (options.dryRun) {
|