@wairon/cli 5.1.1-dev.96 → 5.1.1-dev.97
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 +224 -48
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +206 -46
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-architect.md +4 -4
- package/dist/templates/skills/sdd-implement.md +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.97";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -3737,11 +3737,16 @@ var init_specs = __esm({
|
|
|
3737
3737
|
*/
|
|
3738
3738
|
guarantees: import_zod8.z.array(GuaranteeSchema).optional(),
|
|
3739
3739
|
/**
|
|
3740
|
-
*
|
|
3741
|
-
*
|
|
3742
|
-
*
|
|
3740
|
+
* What this method does to its component's held state: `read` observes it, `write`
|
|
3741
|
+
* modifies an entity's domain fields, and `lifecycle` creates, destroys, or
|
|
3742
|
+
* (un)registers an entity's existence or membership without modifying its fields —
|
|
3743
|
+
* closed under composition (a lifecycle method calls only read and lifecycle methods,
|
|
3744
|
+
* LIFECYCLE_CALLS_WRITE). Required on a durable Store's contract methods so the
|
|
3745
|
+
* durability round-trip rule can pair mutations with hydration read-backs
|
|
3746
|
+
* (MISSING_HYDRATION); a Supervisor may call a data component it does not own only
|
|
3747
|
+
* through read and lifecycle methods (SUPERVISOR_WRITE_SHORTCUT). Optional elsewhere.
|
|
3743
3748
|
*/
|
|
3744
|
-
effect: import_zod8.z.enum(["read", "write"]).optional(),
|
|
3749
|
+
effect: import_zod8.z.enum(["read", "write", "lifecycle"]).optional(),
|
|
3745
3750
|
/**
|
|
3746
3751
|
* Typed acknowledgment of a real caller OUTSIDE the modeled narrative graph
|
|
3747
3752
|
* (runtime timer/hook, external system, sibling subsystem). Unused-detection
|
|
@@ -11930,7 +11935,7 @@ function fromOpenApi(document, projectName) {
|
|
|
11930
11935
|
const rawGuarantees = op["x-wairon-guarantees"];
|
|
11931
11936
|
const guarantees = Array.isArray(rawGuarantees) ? rawGuarantees.filter((g) => typeof g === "string" && g.length > 0) : [];
|
|
11932
11937
|
const rawEffect = op["x-wairon-effect"];
|
|
11933
|
-
const effect = rawEffect === "read" || rawEffect === "write" ? rawEffect : void 0;
|
|
11938
|
+
const effect = rawEffect === "read" || rawEffect === "write" || rawEffect === "lifecycle" ? rawEffect : void 0;
|
|
11934
11939
|
const rawExt = op["x-wairon-ext"];
|
|
11935
11940
|
const ext = rawExt && typeof rawExt === "object" && !Array.isArray(rawExt) ? rawExt : void 0;
|
|
11936
11941
|
methods.push({
|
|
@@ -14412,7 +14417,7 @@ function buildImportGraph(index, universe) {
|
|
|
14412
14417
|
function buildOwnershipIndex(ctx) {
|
|
14413
14418
|
const ownedBy2 = /* @__PURE__ */ new Map();
|
|
14414
14419
|
for (const comp of ctx.components) {
|
|
14415
|
-
if (isRetired(comp) || !isPattern(comp)) continue;
|
|
14420
|
+
if (isRetired(comp) || !(isPattern(comp) || comp.componentType === "Supervisor")) continue;
|
|
14416
14421
|
for (const memberId of comp.owns) {
|
|
14417
14422
|
const member = ctx.componentMap.get(memberId);
|
|
14418
14423
|
if (!member || isPattern(member)) continue;
|
|
@@ -15874,10 +15879,10 @@ var init_lint_allows = __esm({
|
|
|
15874
15879
|
lintAllowsRule = {
|
|
15875
15880
|
name: "lint-allows",
|
|
15876
15881
|
judges: "design",
|
|
15877
|
-
description: "Per-spec lint suppressions (lint.allow) must name real issue codes and actually suppress a finding \u2014 unknown codes and stale allows are flagged. An allow covers exactly the occurrence it names: a finding that reports a site is silenced only by an allow whose `at` is that site, a finding that reports none only by an allow that names none, and an aggregating finding only by an allow whose `covers` lists every unit it reports \u2014 a unit nobody listed is named back as new instead of inheriting a decision taken about its neighbours. So a coarse allow left on a rule that names sites, and an allow whose site the run no longer reports, are both UNUSED_LINT_ALLOW, and the finding names the sites that did fire. Allows silence warnings and notices; errors always surface.",
|
|
15882
|
+
description: "Per-spec lint suppressions (lint.allow) must name real issue codes and actually suppress a finding \u2014 unknown codes and stale allows are flagged. An allow covers exactly the occurrence it names: a finding that reports a site is silenced only by an allow whose `at` is that site, a finding that reports none only by an allow that names none, and an aggregating finding only by an allow whose `covers` lists every unit it reports \u2014 a unit nobody listed is named back as new instead of inheriting a decision taken about its neighbours. So a coarse allow left on a rule that names sites, and an allow whose site the run no longer reports, are both UNUSED_LINT_ALLOW, and the finding names the sites that did fire. Allows silence warnings and notices; errors always surface \u2014 so an allow naming a code that is an error on its spec (it fired there as one, or its resolved severity is error) covers nothing, and the finding says plainly that an error cannot be allowed rather than that the code never fired.",
|
|
15878
15883
|
codes: [
|
|
15879
15884
|
{ code: "UNKNOWN_LINT_ALLOW_CODE", defaultSeverity: "warning", summary: "lint.allow names an issue code no registered rule emits" },
|
|
15880
|
-
{ code: "UNUSED_LINT_ALLOW", defaultSeverity: "warning", summary: "lint.allow entry
|
|
15885
|
+
{ code: "UNUSED_LINT_ALLOW", defaultSeverity: "warning", summary: "lint.allow entry covers nothing this run \u2014 the code never fired, it fired at sites this allow does not name, or it is an error, which no allow can cover" }
|
|
15881
15886
|
],
|
|
15882
15887
|
check(ctx) {
|
|
15883
15888
|
for (const a of ctx.lintAllows) {
|
|
@@ -15890,9 +15895,20 @@ var init_lint_allows = __esm({
|
|
|
15890
15895
|
);
|
|
15891
15896
|
continue;
|
|
15892
15897
|
}
|
|
15893
|
-
if (a.used) continue;
|
|
15894
15898
|
const reported = ctx.sitesReported(a.specId, a.code);
|
|
15895
15899
|
const at = a.at ? ` at "${a.at}"` : "";
|
|
15900
|
+
const fired = reported.unsited || reported.sites.length > 0;
|
|
15901
|
+
const isError = reported.errored || !fired && ctx.severityOf(a.code, a.specId) === "error";
|
|
15902
|
+
if (isError) {
|
|
15903
|
+
ctx.addIssue(
|
|
15904
|
+
"warning",
|
|
15905
|
+
"UNUSED_LINT_ALLOW",
|
|
15906
|
+
`Spec "${a.specId}" allows "${a.code}"${at} (reason: ${a.reason}), but "${a.code}" is an error${fired ? " and fired here as one" : ""}, and an error cannot be allowed \u2014 lint.allow silences warnings and notices only. Fix what the finding names, or remove the allow (a project may re-tune the code's severity in rules.sddRuleSeverity).`,
|
|
15907
|
+
a.specId
|
|
15908
|
+
);
|
|
15909
|
+
continue;
|
|
15910
|
+
}
|
|
15911
|
+
if (a.used) continue;
|
|
15896
15912
|
let why;
|
|
15897
15913
|
if (a.at && reported.unsited && reported.sites.length === 0) {
|
|
15898
15914
|
why = `findings of "${a.code}" on this spec name no site at all, so this allow must not name one \u2014 drop the \`at\``;
|
|
@@ -17589,23 +17605,53 @@ var init_data_block_dependencies = __esm({
|
|
|
17589
17605
|
});
|
|
17590
17606
|
|
|
17591
17607
|
// src/core/rules/doctrine/entrypoint-dependencies.ts
|
|
17608
|
+
function maintainedRegistries(ctx) {
|
|
17609
|
+
const maintained = /* @__PURE__ */ new Map();
|
|
17610
|
+
const add2 = (supervisorId, registryId) => {
|
|
17611
|
+
let set = maintained.get(supervisorId);
|
|
17612
|
+
if (!set) maintained.set(supervisorId, set = /* @__PURE__ */ new Set());
|
|
17613
|
+
set.add(registryId);
|
|
17614
|
+
};
|
|
17615
|
+
for (const comp of ctx.components) {
|
|
17616
|
+
if (comp.componentType !== "Supervisor" || isRetired(comp)) continue;
|
|
17617
|
+
for (const memberId of comp.owns) {
|
|
17618
|
+
if (ctx.componentMap.get(memberId)?.componentType === "Registry") add2(comp.id, memberId);
|
|
17619
|
+
}
|
|
17620
|
+
}
|
|
17621
|
+
for (const impl of ctx.implementations) {
|
|
17622
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
17623
|
+
const supervisor = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
17624
|
+
if (!supervisor || supervisor.componentType !== "Supervisor" || isRetired(supervisor)) continue;
|
|
17625
|
+
for (const implMethod of impl.methods) {
|
|
17626
|
+
for (const step of implMethod.narrative) {
|
|
17627
|
+
if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
|
|
17628
|
+
if (ctx.componentMap.get(step.targetComponent)?.componentType !== "Registry") continue;
|
|
17629
|
+
const called = ctx.interfaceMethodsOf(step.targetComponent).find((m) => m.name === step.targetMethod);
|
|
17630
|
+
if (called?.effect === "lifecycle") add2(supervisor.id, step.targetComponent);
|
|
17631
|
+
}
|
|
17632
|
+
}
|
|
17633
|
+
}
|
|
17634
|
+
return new Map([...maintained].map(([id, set]) => [id, [...set]]));
|
|
17635
|
+
}
|
|
17592
17636
|
var entrypointDepsRule;
|
|
17593
17637
|
var init_entrypoint_dependencies = __esm({
|
|
17594
17638
|
"src/core/rules/doctrine/entrypoint-dependencies.ts"() {
|
|
17595
17639
|
"use strict";
|
|
17640
|
+
init_models();
|
|
17596
17641
|
init_stereotype_terms();
|
|
17597
17642
|
entrypointDepsRule = {
|
|
17598
17643
|
name: "entrypoint-dependencies",
|
|
17599
17644
|
judges: "design",
|
|
17600
|
-
description: "Judges the edges at the system's entry points and its process layer. Portals and Observers are top-level entry points and subscribers, so nothing may depend on them \u2014 and that is the edge's one finding, which is why no other matrix rule judges it. Downward, a Portal dispatches to Orchestrators and may READ through Indexes and Repository facades but never reaches Store/Registry/Query or an Adapter, while an Observer forwards to one Orchestrator or Supervisor over a message-bus Adapter. A View stays a passive presenter. A Supervisor
|
|
17645
|
+
description: "Judges the edges at the system's entry points and its process layer. Portals and Observers are top-level entry points and subscribers, so nothing may depend on them \u2014 and that is the edge's one finding, which is why no other matrix rule judges it. Downward, a Portal dispatches to Orchestrators and may READ through Indexes and Repository facades but never reaches Store/Registry/Query or an Adapter, while an Observer forwards to one Orchestrator or Supervisor over a message-bus Adapter. A View stays a passive presenter. A Supervisor stays out of presentation; what it may do to the data it reaches is judged per call (supervisor-shared-data), and what it owns as its supervision state by the ownership rules. A component depending on a live Actor reaches it through the Actor's supervision: by depending on a Supervisor that supervises it, or on a Registry such a Supervisor maintains (owns, or calls with lifecycle-effect methods) \u2014 the lookup hop callers really take.",
|
|
17601
17646
|
codes: [
|
|
17602
17647
|
{ code: "ARCHITECTURE_VIOLATION_PORTAL_DEP", defaultSeverity: "error", summary: "Component depending on a Portal/Observer" },
|
|
17603
17648
|
{ code: "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP", defaultSeverity: "error", summary: "Portal/Observer reaching the data layer directly" },
|
|
17604
17649
|
{ code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on persistence layers or on logic that is not pure" },
|
|
17605
|
-
{ code: "ARCHITECTURE_VIOLATION_SUPERVISOR_DEP", defaultSeverity: "error", summary: "Supervisor depending on
|
|
17606
|
-
{ code: "ACTOR_REACHED_WITHOUT_SUPERVISOR", defaultSeverity: "error", summary: "Component depending on a live Actor it does not supervise,
|
|
17650
|
+
{ code: "ARCHITECTURE_VIOLATION_SUPERVISOR_DEP", defaultSeverity: "error", summary: "Supervisor depending on a presentation block (View, FeatureComponent, RouterComponent) \u2014 a Supervisor manages live processes; the data it reaches is judged per call by supervisor-shared-data" },
|
|
17651
|
+
{ code: "ACTOR_REACHED_WITHOUT_SUPERVISOR", defaultSeverity: "error", summary: "Component depending on a live Actor it does not supervise, reaching it neither through a Supervisor that supervises it nor through a Registry such a Supervisor maintains \u2014 model the real lookup hop" }
|
|
17607
17652
|
],
|
|
17608
17653
|
check(ctx) {
|
|
17654
|
+
const lookups = maintainedRegistries(ctx);
|
|
17609
17655
|
for (const edge of ctx.dependencyEdges().matrix) {
|
|
17610
17656
|
const comp = edge.from;
|
|
17611
17657
|
const depComp = edge.to;
|
|
@@ -17641,27 +17687,32 @@ var init_entrypoint_dependencies = __esm({
|
|
|
17641
17687
|
edge.draftContext
|
|
17642
17688
|
);
|
|
17643
17689
|
}
|
|
17644
|
-
if (comp.componentType === "Supervisor" && ["
|
|
17690
|
+
if (comp.componentType === "Supervisor" && ["View", "FeatureComponent", "RouterComponent"].includes(depComp.componentType)) {
|
|
17645
17691
|
ctx.addIssue(
|
|
17646
17692
|
"error",
|
|
17647
17693
|
"ARCHITECTURE_VIOLATION_SUPERVISOR_DEP",
|
|
17648
|
-
`Architectural violation: Supervisor "${comp.id}" cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". A Supervisor
|
|
17694
|
+
`Architectural violation: Supervisor "${comp.id}" cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". A Supervisor manages live processes and stays out of presentation \u2014 the UI reaches the system through its Portals and workflows, never through a Supervisor's dependencies.`,
|
|
17649
17695
|
comp.id,
|
|
17650
17696
|
edge.draftContext
|
|
17651
17697
|
);
|
|
17652
17698
|
}
|
|
17653
17699
|
if (depComp.componentType === "Actor" && comp.componentType !== "Supervisor") {
|
|
17654
|
-
const
|
|
17655
|
-
|
|
17656
|
-
|
|
17657
|
-
|
|
17658
|
-
|
|
17659
|
-
|
|
17660
|
-
|
|
17700
|
+
const supervisors = ctx.components.filter((c) => c.componentType === "Supervisor" && c.dependsOn.includes(depComp.id));
|
|
17701
|
+
const registries = supervisors.flatMap((s) => (lookups.get(s.id) ?? []).map((registry) => ({ registry, supervisor: s.id })));
|
|
17702
|
+
const reached = supervisors.some((s) => comp.dependsOn.includes(s.id)) || registries.some((r) => comp.dependsOn.includes(r.registry));
|
|
17703
|
+
if (!reached) {
|
|
17704
|
+
let remedy;
|
|
17705
|
+
if (registries.length > 0) {
|
|
17706
|
+
remedy = `depend on ${registries.length === 1 ? "the Registry" : "one of the Registries"} its Supervisor maintains (${registries.map((r) => `"${r.registry}", kept by "${r.supervisor}"`).join("; ")}) and look the Actor up by id there`;
|
|
17707
|
+
} else if (supervisors.length > 0) {
|
|
17708
|
+
remedy = `${supervisors.length === 1 ? "its Supervisor" : "its Supervisors"} (${supervisors.map((s) => `"${s.id}"`).join(", ")}) maintain no Registry of its live handles yet: model that lookup as a Registry the Supervisor keeps through lifecycle-effect methods (registering and unregistering a live handle), and depend on that Registry`;
|
|
17709
|
+
} else {
|
|
17710
|
+
remedy = `no Supervisor depends on "${depComp.id}" yet: give it a Supervisor that depends on it and maintains a Registry of its live handles through lifecycle-effect methods, and depend on that Registry`;
|
|
17711
|
+
}
|
|
17661
17712
|
ctx.addIssue(
|
|
17662
17713
|
"error",
|
|
17663
17714
|
"ACTOR_REACHED_WITHOUT_SUPERVISOR",
|
|
17664
|
-
`Architectural violation: ${stereotypeOf(comp)} "${comp.id}" depends on the live Actor "${depComp.id}" without
|
|
17715
|
+
`Architectural violation: ${stereotypeOf(comp)} "${comp.id}" depends on the live Actor "${depComp.id}" without reaching it through its supervision. A live Actor is found by id through a Registry its Supervisor maintains, or messaged through that Supervisor \u2014 ${remedy}.`,
|
|
17665
17716
|
comp.id,
|
|
17666
17717
|
edge.draftContext
|
|
17667
17718
|
);
|
|
@@ -17681,9 +17732,9 @@ var init_portal_write_shortcut = __esm({
|
|
|
17681
17732
|
portalWriteShortcutRule = {
|
|
17682
17733
|
name: "portal-write-shortcut",
|
|
17683
17734
|
judges: "design",
|
|
17684
|
-
description: "A Portal narrative call step or dispatch-table binding that reaches a write-effect method on a Repository or Index directly is the write shortcut: the Portal\u2192data-facade edge is licensed for reads only, and a write routes through an Orchestrator that owns the workflow. A dispatch step reaches its server only through a table binding, so judging every binding judges each dispatch step that takes it, once, where the route is declared. Methods that carry no effect tag are not judged.",
|
|
17735
|
+
description: "A Portal narrative call step or dispatch-table binding that reaches a write- or lifecycle-effect method on a Repository or Index directly is the write shortcut: the Portal\u2192data-facade edge is licensed for reads only, and a mutation \u2014 a write, or a lifecycle change to what exists \u2014 routes through an Orchestrator that owns the workflow. A dispatch step reaches its server only through a table binding, so judging every binding judges each dispatch step that takes it, once, where the route is declared. Methods that carry no effect tag are not judged.",
|
|
17685
17736
|
codes: [
|
|
17686
|
-
{ code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative call or dispatch-table binding reaches a write-effect method on a Repository/Index directly \u2014 reads may shortcut,
|
|
17737
|
+
{ code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative call or dispatch-table binding reaches a write- or lifecycle-effect method on a Repository/Index directly \u2014 reads may shortcut, mutations route through an Orchestrator (judged on effect-tagged facade methods; untagged methods are not yet judged)" }
|
|
17687
17738
|
],
|
|
17688
17739
|
check(ctx) {
|
|
17689
17740
|
for (const impl of ctx.implementations) {
|
|
@@ -17697,11 +17748,11 @@ var init_portal_write_shortcut = __esm({
|
|
|
17697
17748
|
const target = ctx.componentMap.get(step.targetComponent);
|
|
17698
17749
|
if (!target || target.componentType !== "Repository" && target.componentType !== "Index") continue;
|
|
17699
17750
|
const targetMethod = ctx.interfaceMethodsOf(target.id).find((m) => m.name === step.targetMethod);
|
|
17700
|
-
if (targetMethod?.effect !== "write") continue;
|
|
17751
|
+
if (targetMethod?.effect !== "write" && targetMethod?.effect !== "lifecycle") continue;
|
|
17701
17752
|
ctx.addIssue(
|
|
17702
17753
|
"error",
|
|
17703
17754
|
"PORTAL_WRITE_SHORTCUT",
|
|
17704
|
-
`Portal "${component.id}": step ${step.stepNumber} of "${implMethod.name}" calls
|
|
17755
|
+
`Portal "${component.id}": step ${step.stepNumber} of "${implMethod.name}" calls ${targetMethod.effect}-effect method ${target.id}.${step.targetMethod} directly. The Portal\u2192${target.componentType} shortcut is licensed for READS only \u2014 route the ${targetMethod.effect === "write" ? "write" : "lifecycle change"} through an Orchestrator that owns the workflow.`,
|
|
17705
17756
|
impl.id,
|
|
17706
17757
|
isDraftCtx || ctx.isComponentDraft(target.id)
|
|
17707
17758
|
);
|
|
@@ -17715,11 +17766,11 @@ var init_portal_write_shortcut = __esm({
|
|
|
17715
17766
|
const target = ctx.componentMap.get(binding.component);
|
|
17716
17767
|
if (!target || target.componentType !== "Repository" && target.componentType !== "Index") continue;
|
|
17717
17768
|
const targetMethod = ctx.interfaceMethodsOf(target.id).find((m) => m.name === binding.method);
|
|
17718
|
-
if (targetMethod?.effect !== "write") continue;
|
|
17769
|
+
if (targetMethod?.effect !== "write" && targetMethod?.effect !== "lifecycle") continue;
|
|
17719
17770
|
ctx.addIssue(
|
|
17720
17771
|
"error",
|
|
17721
17772
|
"PORTAL_WRITE_SHORTCUT",
|
|
17722
|
-
`Portal "${comp.id}": dispatch binding "${binding.capability}" routes to
|
|
17773
|
+
`Portal "${comp.id}": dispatch binding "${binding.capability}" routes to ${targetMethod.effect}-effect method ${target.id}.${binding.method} directly. The Portal\u2192${target.componentType} shortcut is licensed for READS only \u2014 route the ${targetMethod.effect === "write" ? "write" : "lifecycle change"} through an Orchestrator that owns the workflow.`,
|
|
17723
17774
|
comp.id,
|
|
17724
17775
|
isDraftCtx || ctx.isComponentDraft(target.id)
|
|
17725
17776
|
);
|
|
@@ -17730,6 +17781,91 @@ var init_portal_write_shortcut = __esm({
|
|
|
17730
17781
|
}
|
|
17731
17782
|
});
|
|
17732
17783
|
|
|
17784
|
+
// src/core/rules/doctrine/supervisor-shared-data.ts
|
|
17785
|
+
var DATA_COMPONENTS, supervisorSharedDataRule;
|
|
17786
|
+
var init_supervisor_shared_data = __esm({
|
|
17787
|
+
"src/core/rules/doctrine/supervisor-shared-data.ts"() {
|
|
17788
|
+
"use strict";
|
|
17789
|
+
init_models();
|
|
17790
|
+
DATA_COMPONENTS = /* @__PURE__ */ new Set(["Store", "Registry", "Repository", "Index", "Query"]);
|
|
17791
|
+
supervisorSharedDataRule = {
|
|
17792
|
+
name: "supervisor-shared-data",
|
|
17793
|
+
judges: "design",
|
|
17794
|
+
description: "A Supervisor keeps its own supervision state (the Stores and Registries it owns) with full read and write, but data it does not own is shared: a Supervisor narrative call step that reaches a method of a Store, Registry, Repository, Index or Query the Supervisor does not own is legal only when that method declares effect read or lifecycle. A write \u2014 or a method that declares no effect \u2014 goes through a workflow: the Supervisor depends on the Orchestrator that does it. A read on behalf of a request still passes; in practice the paired write is what forces the workflow out, and the read moves with it.",
|
|
17795
|
+
codes: [
|
|
17796
|
+
{ code: "SUPERVISOR_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Supervisor narrative call reaches a method of a data component it does not own whose declared effect is neither read nor lifecycle \u2014 writes to shared data route through an Orchestrator" }
|
|
17797
|
+
],
|
|
17798
|
+
check(ctx) {
|
|
17799
|
+
const ownership = ctx.ownershipIndex();
|
|
17800
|
+
for (const impl of ctx.implementations) {
|
|
17801
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
17802
|
+
const supervisor = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
17803
|
+
if (!supervisor || supervisor.componentType !== "Supervisor" || isRetired(supervisor)) continue;
|
|
17804
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
17805
|
+
for (const implMethod of impl.methods) {
|
|
17806
|
+
for (const step of implMethod.narrative) {
|
|
17807
|
+
if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
|
|
17808
|
+
const target = ctx.componentMap.get(step.targetComponent);
|
|
17809
|
+
if (!target || !DATA_COMPONENTS.has(target.componentType)) continue;
|
|
17810
|
+
if (ownership.ownerOf(target.id) === supervisor.id) continue;
|
|
17811
|
+
const called = ctx.interfaceMethodsOf(target.id).find((m) => m.name === step.targetMethod);
|
|
17812
|
+
if (!called || called.effect === "read" || called.effect === "lifecycle") continue;
|
|
17813
|
+
const what = called.effect === "write" ? "a write-effect method" : "a method that declares no effect";
|
|
17814
|
+
const untaggedHint = called.effect === "write" ? "" : ` If ${target.id}.${called.name} only reads, or only changes what exists, declare its effect (read or lifecycle).`;
|
|
17815
|
+
ctx.addIssue(
|
|
17816
|
+
"error",
|
|
17817
|
+
"SUPERVISOR_WRITE_SHORTCUT",
|
|
17818
|
+
`Supervisor "${supervisor.id}": step ${step.stepNumber} of "${implMethod.name}" calls ${target.id}.${called.name}, ${what} on ${target.componentType} "${target.id}", which the Supervisor does not own. A Supervisor reads shared data and changes what exists in it (read and lifecycle effects); a write to its fields goes through a workflow \u2014 depend on the Orchestrator that does it and call that instead.${untaggedHint}`,
|
|
17819
|
+
impl.id,
|
|
17820
|
+
isDraftCtx || ctx.isComponentDraft(target.id)
|
|
17821
|
+
);
|
|
17822
|
+
}
|
|
17823
|
+
}
|
|
17824
|
+
}
|
|
17825
|
+
}
|
|
17826
|
+
};
|
|
17827
|
+
}
|
|
17828
|
+
});
|
|
17829
|
+
|
|
17830
|
+
// src/core/rules/doctrine/lifecycle-effect-closure.ts
|
|
17831
|
+
var lifecycleEffectClosureRule;
|
|
17832
|
+
var init_lifecycle_effect_closure = __esm({
|
|
17833
|
+
"src/core/rules/doctrine/lifecycle-effect-closure.ts"() {
|
|
17834
|
+
"use strict";
|
|
17835
|
+
lifecycleEffectClosureRule = {
|
|
17836
|
+
name: "lifecycle-effect-closure",
|
|
17837
|
+
judges: "design",
|
|
17838
|
+
description: "A lifecycle-effect method may create, destroy, or (un)register an entity's existence or membership, never modify its domain fields \u2014 and the effect is closed under composition: its narrative may call only read- and lifecycle-effect methods besides its construction and local steps. A call step to a write-effect method from a lifecycle-effect method is a declaration error: the method is a write, or the write belongs to a workflow beside it. Called methods that declare no effect are not judged.",
|
|
17839
|
+
codes: [
|
|
17840
|
+
{ code: "LIFECYCLE_CALLS_WRITE", defaultSeverity: "error", summary: "Lifecycle-effect method whose narrative calls a write-effect method \u2014 the lifecycle effect is closed under composition" }
|
|
17841
|
+
],
|
|
17842
|
+
check(ctx) {
|
|
17843
|
+
for (const impl of ctx.implementations) {
|
|
17844
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
17845
|
+
if (!contract) continue;
|
|
17846
|
+
const declared = ctx.interfaceMethodsOf(contract.component);
|
|
17847
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
17848
|
+
for (const implMethod of impl.methods) {
|
|
17849
|
+
if (declared.find((m) => m.name === implMethod.name)?.effect !== "lifecycle") continue;
|
|
17850
|
+
for (const step of implMethod.narrative) {
|
|
17851
|
+
if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
|
|
17852
|
+
const called = ctx.interfaceMethodsOf(step.targetComponent).find((m) => m.name === step.targetMethod);
|
|
17853
|
+
if (called?.effect !== "write") continue;
|
|
17854
|
+
ctx.addIssue(
|
|
17855
|
+
"error",
|
|
17856
|
+
"LIFECYCLE_CALLS_WRITE",
|
|
17857
|
+
`"${contract.component}.${implMethod.name}" declares effect lifecycle, but step ${step.stepNumber} calls write-effect method ${step.targetComponent}.${step.targetMethod}. A lifecycle method creates, destroys or (un)registers what exists and never modifies domain fields, and the effect is closed under composition \u2014 it may call only read and lifecycle methods. Declare the method a write, or move the write to a workflow beside it.`,
|
|
17858
|
+
impl.id,
|
|
17859
|
+
isDraftCtx
|
|
17860
|
+
);
|
|
17861
|
+
}
|
|
17862
|
+
}
|
|
17863
|
+
}
|
|
17864
|
+
}
|
|
17865
|
+
};
|
|
17866
|
+
}
|
|
17867
|
+
});
|
|
17868
|
+
|
|
17733
17869
|
// src/core/rules/doctrine/pattern-membership.ts
|
|
17734
17870
|
var patternMembershipRule;
|
|
17735
17871
|
var init_pattern_membership = __esm({
|
|
@@ -17739,24 +17875,25 @@ var init_pattern_membership = __esm({
|
|
|
17739
17875
|
patternMembershipRule = {
|
|
17740
17876
|
name: "pattern-membership",
|
|
17741
17877
|
judges: "design",
|
|
17742
|
-
description: "Only patterns (Repository/FeatureComponent/RouterComponent) own member blocks, and every pattern owns at least one. Each claim must name a component that exists and is itself a building block \u2014 patterns compose at the subsystem (L1) level, never by owning one another \u2014 and a block has exactly one owner, the first pattern to claim it.",
|
|
17878
|
+
description: "Only patterns (Repository/FeatureComponent/RouterComponent) own member blocks, and every pattern owns at least one. The one building block that may own is a Supervisor, which owns its supervision state and may own nothing at all; what it may own is pattern-containment's question. Each claim must name a component that exists and is itself a building block \u2014 patterns compose at the subsystem (L1) level, never by owning one another \u2014 and a block has exactly one owner, the first pattern or Supervisor to claim it.",
|
|
17743
17879
|
codes: [
|
|
17744
17880
|
{ code: "EMPTY_PATTERN", defaultSeverity: "error", summary: "Pattern with no owned member blocks" },
|
|
17745
|
-
{ code: "BLOCK_OWNS_MEMBERS", defaultSeverity: "error", summary: "Building block using owns" },
|
|
17881
|
+
{ code: "BLOCK_OWNS_MEMBERS", defaultSeverity: "error", summary: "Building block other than a Supervisor using owns" },
|
|
17746
17882
|
{ code: "INVALID_OWNED_MEMBER", defaultSeverity: "error", summary: "owns names a non-existent component" },
|
|
17747
17883
|
{ code: "PATTERN_OWNS_PATTERN", defaultSeverity: "error", summary: "Pattern owning another pattern" },
|
|
17748
|
-
{ code: "SHARED_OWNED_MEMBER", defaultSeverity: "error", summary: "Block owned by two patterns" }
|
|
17884
|
+
{ code: "SHARED_OWNED_MEMBER", defaultSeverity: "error", summary: "Block owned by two owners (patterns or Supervisors)" }
|
|
17749
17885
|
],
|
|
17750
17886
|
check(ctx) {
|
|
17751
17887
|
for (const comp of ctx.components) {
|
|
17752
17888
|
if (isRetired(comp)) continue;
|
|
17753
17889
|
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
17754
17890
|
const pattern = isPattern(comp);
|
|
17891
|
+
const supervisor = comp.componentType === "Supervisor";
|
|
17755
17892
|
if (pattern && comp.owns.length === 0) {
|
|
17756
17893
|
ctx.addIssue("error", "EMPTY_PATTERN", `Pattern "${comp.id}" (${comp.componentType}) must own member blocks via "owns".`, comp.id, isDraftCtx);
|
|
17757
17894
|
}
|
|
17758
|
-
if (!pattern && comp.owns.length > 0) {
|
|
17759
|
-
ctx.addIssue("error", "BLOCK_OWNS_MEMBERS", `Building block "${comp.id}" (${comp.componentType}) cannot own members; only patterns (${Array.from(PATTERN_TYPES).join("/")}) use "owns".`, comp.id, isDraftCtx);
|
|
17895
|
+
if (!pattern && !supervisor && comp.owns.length > 0) {
|
|
17896
|
+
ctx.addIssue("error", "BLOCK_OWNS_MEMBERS", `Building block "${comp.id}" (${comp.componentType}) cannot own members; only patterns (${Array.from(PATTERN_TYPES).join("/")}) use "owns", and a Supervisor for its supervision state.`, comp.id, isDraftCtx);
|
|
17760
17897
|
}
|
|
17761
17898
|
}
|
|
17762
17899
|
const ownership = ctx.ownershipIndex();
|
|
@@ -17764,15 +17901,18 @@ var init_pattern_membership = __esm({
|
|
|
17764
17901
|
if (isRetired(comp)) continue;
|
|
17765
17902
|
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
17766
17903
|
const pattern = isPattern(comp);
|
|
17904
|
+
const supervisor = comp.componentType === "Supervisor";
|
|
17767
17905
|
for (const memberId of comp.owns) {
|
|
17768
17906
|
const member = ctx.componentMap.get(memberId);
|
|
17769
17907
|
if (!member) {
|
|
17770
17908
|
ctx.addIssue("error", "INVALID_OWNED_MEMBER", `Component "${comp.id}" owns "${memberId}" which does not exist.`, comp.id, isDraftCtx);
|
|
17771
17909
|
continue;
|
|
17772
17910
|
}
|
|
17773
|
-
if (!pattern) continue;
|
|
17911
|
+
if (!pattern && !supervisor) continue;
|
|
17774
17912
|
if (isPattern(member)) {
|
|
17775
|
-
|
|
17913
|
+
if (pattern) {
|
|
17914
|
+
ctx.addIssue("error", "PATTERN_OWNS_PATTERN", `Pattern "${comp.id}" owns "${memberId}", which is itself a pattern. Patterns own only building blocks \u2014 compose patterns at the subsystem (L1) level.`, comp.id, isDraftCtx);
|
|
17915
|
+
}
|
|
17776
17916
|
continue;
|
|
17777
17917
|
}
|
|
17778
17918
|
const firstOwner = ownership.ownerOf(memberId);
|
|
@@ -17787,19 +17927,21 @@ var init_pattern_membership = __esm({
|
|
|
17787
17927
|
});
|
|
17788
17928
|
|
|
17789
17929
|
// src/core/rules/doctrine/pattern-containment.ts
|
|
17790
|
-
var REPOSITORY_MEMBERS, patternContainmentRule;
|
|
17930
|
+
var REPOSITORY_MEMBERS, SUPERVISION_STATE, patternContainmentRule;
|
|
17791
17931
|
var init_pattern_containment = __esm({
|
|
17792
17932
|
"src/core/rules/doctrine/pattern-containment.ts"() {
|
|
17793
17933
|
"use strict";
|
|
17794
17934
|
init_models();
|
|
17795
17935
|
REPOSITORY_MEMBERS = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Query", "Adapter"]);
|
|
17936
|
+
SUPERVISION_STATE = /* @__PURE__ */ new Set(["Store", "Registry"]);
|
|
17796
17937
|
patternContainmentRule = {
|
|
17797
17938
|
name: "pattern-containment",
|
|
17798
17939
|
judges: "design",
|
|
17799
|
-
description: "Holds each
|
|
17940
|
+
description: "Holds each owner to the containment its definition prescribes. A Repository may own only Store, Registry, Index, Query and (optionally) Adapter, judged member by member. A Supervisor may own only its supervision state \u2014 Stores and Registries that are its own, one hop \u2014 judged member by member too. A FeatureComponent owns exactly one Orchestrator (the logic side) and one or more Views (its faces \u2014 list, detail, form \u2014 sharing the one logic component), and nothing else. A RouterComponent owns exactly one Portal as its facade and at least one other child to route to. A counting pattern that still owns a retired member is not judged until that member is migrated: its counts change with the migration, and STEREOTYPE_RETIRED is the one finding.",
|
|
17800
17941
|
codes: [
|
|
17801
17942
|
{ code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Query/Adapter member" },
|
|
17802
17943
|
{ code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
|
|
17944
|
+
{ code: "SUPERVISOR_CONTAINMENT", defaultSeverity: "error", summary: "Supervisor owning anything but a Store or Registry \u2014 a Supervisor owns only its supervision state" },
|
|
17803
17945
|
{ code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" }
|
|
17804
17946
|
],
|
|
17805
17947
|
check(ctx) {
|
|
@@ -17850,6 +17992,15 @@ var init_pattern_containment = __esm({
|
|
|
17850
17992
|
}
|
|
17851
17993
|
}
|
|
17852
17994
|
}
|
|
17995
|
+
for (const comp of ctx.components) {
|
|
17996
|
+
if (comp.componentType !== "Supervisor" || isRetired(comp)) continue;
|
|
17997
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
17998
|
+
for (const memberId of comp.owns) {
|
|
17999
|
+
const member = ctx.componentMap.get(memberId);
|
|
18000
|
+
if (!member || isRetired(member) || SUPERVISION_STATE.has(member.componentType)) continue;
|
|
18001
|
+
ctx.addIssue("error", "SUPERVISOR_CONTAINMENT", `Supervisor "${comp.id}" owns "${memberId}" of type ${member.componentType}; a Supervisor owns only its supervision state \u2014 Stores and Registries that are its own. Depend on "${memberId}" as a collaborator instead.`, comp.id, isDraftCtx);
|
|
18002
|
+
}
|
|
18003
|
+
}
|
|
17853
18004
|
}
|
|
17854
18005
|
};
|
|
17855
18006
|
}
|
|
@@ -17863,7 +18014,7 @@ var init_unowned_blocks = __esm({
|
|
|
17863
18014
|
unownedBlocksRule = {
|
|
17864
18015
|
name: "unowned-blocks",
|
|
17865
18016
|
judges: "design",
|
|
17866
|
-
description: `Judges the data blocks no pattern owns. A Store may stand alone deliberately \u2014 the sanctioned lightweight form for genuinely simple state, acknowledged with a lint.allow \u2014 while the recommended shape stays a Repository; what must never happen is folding the state into a consuming component, where no spec, diagram or conformance check can see it again. A Registry standing alone with no Store to write to is either mistyped (the "file-backed Registry" idiom, a fused persistent store that belongs typed Store) or orphaned. A Query has no standalone form at all: it computes reads over its own Repository's Store.`,
|
|
18017
|
+
description: `Judges the data blocks no owner (a pattern, or a Supervisor keeping its supervision state) owns. A Store may stand alone deliberately \u2014 the sanctioned lightweight form for genuinely simple state, acknowledged with a lint.allow \u2014 while the recommended shape stays a Repository; what must never happen is folding the state into a consuming component, where no spec, diagram or conformance check can see it again. A Registry standing alone with no Store to write to is either mistyped (the "file-backed Registry" idiom, a fused persistent store that belongs typed Store) or orphaned. A Query has no standalone form at all: it computes reads over its own Repository's Store.`,
|
|
17867
18018
|
codes: [
|
|
17868
18019
|
{ code: "UNOWNED_STORE", defaultSeverity: "warning", summary: "Store not owned by any pattern \u2014 recommended shape is a Repository; a deliberate standalone Store needs a lint.allow" },
|
|
17869
18020
|
{ code: "REGISTRY_WITHOUT_STORE", defaultSeverity: "warning", summary: "Standalone Registry with no Store to write to \u2014 either mistyped (a fused file-backed store belongs typed Store) or orphaned" },
|
|
@@ -17916,9 +18067,10 @@ var init_member_visibility = __esm({
|
|
|
17916
18067
|
memberVisibilityRule = {
|
|
17917
18068
|
name: "member-visibility",
|
|
17918
18069
|
judges: "design",
|
|
17919
|
-
description: "A component may depend on a block within its own group (it is the
|
|
18070
|
+
description: "A component may depend on a block within its own group (it is the owner, or a sibling member of the same owner), on any pattern facade, or on a standalone block \u2014 never on a block privately owned by ANOTHER pattern, which must be reached through that pattern's facade. A Store or Registry a Supervisor owns is that Supervisor's supervision state and nobody else depends on it: a component that does is the intruder, and the finding is reported on it, naming the Supervisor and the state. A retired depending component is skipped, as everywhere in this family.",
|
|
17920
18071
|
codes: [
|
|
17921
|
-
{ code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" }
|
|
18072
|
+
{ code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" },
|
|
18073
|
+
{ code: "SUPERVISION_STATE_INTRUSION", defaultSeverity: "error", summary: "Dependency on a Store or Registry a Supervisor owns as its supervision state \u2014 reported on the intruder, naming the Supervisor and the state" }
|
|
17922
18074
|
],
|
|
17923
18075
|
check(ctx) {
|
|
17924
18076
|
const ownership = ctx.ownershipIndex();
|
|
@@ -17929,7 +18081,12 @@ var init_member_visibility = __esm({
|
|
|
17929
18081
|
if (!owner) continue;
|
|
17930
18082
|
if (owner === comp.id) continue;
|
|
17931
18083
|
if (ownership.ownerOf(comp.id) === owner) continue;
|
|
17932
|
-
|
|
18084
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id) || ctx.isComponentDraft(depId);
|
|
18085
|
+
if (ctx.componentMap.get(owner)?.componentType === "Supervisor") {
|
|
18086
|
+
ctx.addIssue("error", "SUPERVISION_STATE_INTRUSION", `"${comp.id}" depends on "${depId}", which Supervisor "${owner}" owns as its supervision state. Supervision state is private to its Supervisor \u2014 nobody else depends on it. If "${comp.id}" needs this data it is shared data: move "${depId}" out of "${owner}"'s owns into a data component both depend on (the Supervisor keeping it through read and lifecycle calls), or reach what "${comp.id}" needs through "${owner}".`, comp.id, isDraftCtx);
|
|
18087
|
+
continue;
|
|
18088
|
+
}
|
|
18089
|
+
ctx.addIssue("error", "VISIBILITY_VIOLATION", `Component "${comp.id}" depends on "${depId}", which is privately owned by pattern "${owner}". Depend on the facade "${owner}" instead.`, comp.id, isDraftCtx);
|
|
17933
18090
|
}
|
|
17934
18091
|
}
|
|
17935
18092
|
}
|
|
@@ -19216,9 +19373,9 @@ var init_durability_round_trip = __esm({
|
|
|
19216
19373
|
// Stage 8: its verdict needs the whole system's specs, so a part judged alone skips it.
|
|
19217
19374
|
needsWholeTree: true,
|
|
19218
19375
|
judges: "design",
|
|
19219
|
-
description: "A durable Store (persisted RAM projection) must carry effect-tagged contract methods, and its
|
|
19376
|
+
description: "A durable Store (persisted RAM projection) must carry effect-tagged contract methods, and its mutations \u2014 write-effect methods, and lifecycle-effect methods, which change what exists \u2014 require a hydration read-back reachable from a lifecycle init entrypoint. read-through is exempt (every read IS the read-back), as are ram-projection (rebuilt not restored) and cache (evictable, loss-safe). The flagship semantic check is opt-out by declaration, never silently absent \u2014 the declaration itself is enforced by durability-declaration.",
|
|
19220
19377
|
codes: [
|
|
19221
|
-
{ code: "MISSING_EFFECT_TAG", defaultSeverity: "warning", summary: "Durable Store contract method lacks an effect: read | write tag" },
|
|
19378
|
+
{ code: "MISSING_EFFECT_TAG", defaultSeverity: "warning", summary: "Durable Store contract method lacks an effect: read | write | lifecycle tag" },
|
|
19222
19379
|
{ code: "MISSING_HYDRATION", defaultSeverity: "error", summary: "Durable Store is written but no read-back is reachable from any lifecycle init entrypoint" }
|
|
19223
19380
|
],
|
|
19224
19381
|
check(ctx) {
|
|
@@ -19246,7 +19403,7 @@ var init_durability_round_trip = __esm({
|
|
|
19246
19403
|
isDraftCtx
|
|
19247
19404
|
);
|
|
19248
19405
|
}
|
|
19249
|
-
const writes = methods.filter((method2) => method2.effect === "write");
|
|
19406
|
+
const writes = methods.filter((method2) => method2.effect === "write" || method2.effect === "lifecycle");
|
|
19250
19407
|
const reads = methods.filter((method2) => method2.effect === "read");
|
|
19251
19408
|
if (writes.length === 0) continue;
|
|
19252
19409
|
const hydrated = initReach !== null && reads.some((method2) => initReach.reachesMethod(comp.id, method2.name));
|
|
@@ -22795,6 +22952,8 @@ var init_repository = __esm({
|
|
|
22795
22952
|
init_data_block_dependencies();
|
|
22796
22953
|
init_entrypoint_dependencies();
|
|
22797
22954
|
init_portal_write_shortcut();
|
|
22955
|
+
init_supervisor_shared_data();
|
|
22956
|
+
init_lifecycle_effect_closure();
|
|
22798
22957
|
init_pattern_membership();
|
|
22799
22958
|
init_pattern_containment();
|
|
22800
22959
|
init_unowned_blocks();
|
|
@@ -22936,6 +23095,10 @@ var init_repository = __esm({
|
|
|
22936
23095
|
dataBlockDepsRule,
|
|
22937
23096
|
entrypointDepsRule,
|
|
22938
23097
|
portalWriteShortcutRule,
|
|
23098
|
+
// The process layer's data reach and the lifecycle effect's closure, both
|
|
23099
|
+
// read from narratives against the callee's declared effect.
|
|
23100
|
+
supervisorSharedDataRule,
|
|
23101
|
+
lifecycleEffectClosureRule,
|
|
22939
23102
|
// Patterns in four questions: who may own and what a claim must name,
|
|
22940
23103
|
// what each pattern must contain, which data blocks are left standing
|
|
22941
23104
|
// alone, and who may see a private member.
|
|
@@ -24565,7 +24728,16 @@ function buildRuleContext(opts) {
|
|
|
24565
24728
|
const sitesSeen = /* @__PURE__ */ new Map();
|
|
24566
24729
|
const sitesReported = (specId, code) => {
|
|
24567
24730
|
const seen = sitesSeen.get(`${specId}\0${code}`);
|
|
24568
|
-
return { sites: [...seen?.sites ?? []], unsited: seen?.unsited ?? false };
|
|
24731
|
+
return { sites: [...seen?.sites ?? []], unsited: seen?.unsited ?? false, errored: seen?.errored ?? false };
|
|
24732
|
+
};
|
|
24733
|
+
const codeDefaults = new Map(opts.issueCodeSeverities ?? []);
|
|
24734
|
+
for (const a of opts.extensions?.assertions ?? []) {
|
|
24735
|
+
if (!codeDefaults.has(a.fullCode)) codeDefaults.set(a.fullCode, a.severity);
|
|
24736
|
+
}
|
|
24737
|
+
const severityOf = (code, specId) => {
|
|
24738
|
+
const defaultSeverity = codeDefaults.get(code);
|
|
24739
|
+
if (!defaultSeverity) return void 0;
|
|
24740
|
+
return getRuleSeverity(code, defaultSeverity, false, subsystemOfSpec.get(specId));
|
|
24569
24741
|
};
|
|
24570
24742
|
for (const s of subsystems) collectAllows(s.id, s.lint);
|
|
24571
24743
|
for (const c of components) collectAllows(c.id, c.lint);
|
|
@@ -24608,9 +24780,10 @@ function buildRuleContext(opts) {
|
|
|
24608
24780
|
if (specId) {
|
|
24609
24781
|
const key = `${specId}\0${code}`;
|
|
24610
24782
|
let seen = sitesSeen.get(key);
|
|
24611
|
-
if (!seen) sitesSeen.set(key, seen = { sites: /* @__PURE__ */ new Set(), unsited: false });
|
|
24783
|
+
if (!seen) sitesSeen.set(key, seen = { sites: /* @__PURE__ */ new Set(), unsited: false, errored: false });
|
|
24612
24784
|
if (parts) seen.sites.add(parts.at);
|
|
24613
24785
|
else seen.unsited = true;
|
|
24786
|
+
if (severity === "error") seen.errored = true;
|
|
24614
24787
|
}
|
|
24615
24788
|
let allowClaimed = false;
|
|
24616
24789
|
if (specId) {
|
|
@@ -24727,6 +24900,7 @@ function buildRuleContext(opts) {
|
|
|
24727
24900
|
roundTripIssues: opts.roundTripIssues,
|
|
24728
24901
|
lintAllows: lintAllows2,
|
|
24729
24902
|
sitesReported,
|
|
24903
|
+
severityOf,
|
|
24730
24904
|
knownIssueCodes: opts.knownIssueCodes,
|
|
24731
24905
|
carriedFindings,
|
|
24732
24906
|
carryableIssueCodes: opts.carryableIssueCodes ?? /* @__PURE__ */ new Set(),
|
|
@@ -25313,6 +25487,7 @@ function runOwnersGate(rulesOrOptions, projectType = "backend") {
|
|
|
25313
25487
|
const carryableCodes = new Set(
|
|
25314
25488
|
knownIssueCodes().filter((rc) => rc.carryable).map((rc) => rc.code)
|
|
25315
25489
|
);
|
|
25490
|
+
const codeSeverities = new Map(knownIssueCodes().map((rc) => [rc.code, rc.defaultSeverity]));
|
|
25316
25491
|
const family = graph();
|
|
25317
25492
|
const memberTables = family.nodes.filter((n) => n.namespace !== "").map((n) => resolveProjectExports(n.namespace));
|
|
25318
25493
|
const producers = new Set(family.references.filter((r) => r.consumer === "").map((r) => r.producer));
|
|
@@ -25355,6 +25530,7 @@ function runOwnersGate(rulesOrOptions, projectType = "backend") {
|
|
|
25355
25530
|
codeModel,
|
|
25356
25531
|
roundTripIssues,
|
|
25357
25532
|
knownIssueCodes: knownCodes,
|
|
25533
|
+
issueCodeSeverities: codeSeverities,
|
|
25358
25534
|
carryableIssueCodes: carryableCodes,
|
|
25359
25535
|
issues
|
|
25360
25536
|
});
|
|
@@ -45518,7 +45694,7 @@ ${renderChangeReport(report3)}`,
|
|
|
45518
45694
|
),
|
|
45519
45695
|
params: import_zod11.z.array(methodParamItem).optional().describe("Structured parameters \u2014 authoritative for type checking, and the signature text is derived from them. Strongly preferred."),
|
|
45520
45696
|
guarantees: import_zod11.z.array(import_zod11.z.string().min(1)).optional().describe("Semantic guarantees the method promises (combinable); any guarantee a narrative step asserts must be declared here. Builtin tokens: idempotent | atomic | transactional | exactly-once; extension packs may declare more (any other token is UNKNOWN_GUARANTEE)"),
|
|
45521
|
-
effect: import_zod11.z.enum(["read", "write"]).optional().describe("
|
|
45697
|
+
effect: import_zod11.z.enum(["read", "write", "lifecycle"]).optional().describe("What the method does to the component's held state: read observes it; write modifies an entity's domain fields; lifecycle creates, destroys, or (un)registers an entity's existence or membership without modifying its fields, and calls only read and lifecycle methods (LIFECYCLE_CALLS_WRITE). Required on a durable Store's contract methods so the durability round-trip rule can pair mutations with hydration read-backs; a Supervisor may call a data component it does not own only through read and lifecycle methods (SUPERVISOR_WRITE_SHORTCUT)"),
|
|
45522
45698
|
invokedBy: import_zod11.z.object({
|
|
45523
45699
|
kind: import_zod11.z.enum(["runtime", "external", "sibling-subsystem"]).describe("Who owns the out-of-graph invocation: runtime (timer/signal/shutdown hook), external (a system outside this project), sibling-subsystem (a modeled sibling whose edge is not narrated here)"),
|
|
45524
45700
|
caller: import_zod11.z.string().optional().describe("WHO invokes it and when, as reviewable prose \u2014 missing or placeholder-thin prose is INVOKED_BY_UNDESCRIBED")
|