@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/index.js
CHANGED
|
@@ -7162,11 +7162,16 @@ var MethodSignatureSchema = import_zod8.z.object({
|
|
|
7162
7162
|
*/
|
|
7163
7163
|
guarantees: import_zod8.z.array(GuaranteeSchema).optional(),
|
|
7164
7164
|
/**
|
|
7165
|
-
*
|
|
7166
|
-
*
|
|
7167
|
-
*
|
|
7165
|
+
* What this method does to its component's held state: `read` observes it, `write`
|
|
7166
|
+
* modifies an entity's domain fields, and `lifecycle` creates, destroys, or
|
|
7167
|
+
* (un)registers an entity's existence or membership without modifying its fields —
|
|
7168
|
+
* closed under composition (a lifecycle method calls only read and lifecycle methods,
|
|
7169
|
+
* LIFECYCLE_CALLS_WRITE). Required on a durable Store's contract methods so the
|
|
7170
|
+
* durability round-trip rule can pair mutations with hydration read-backs
|
|
7171
|
+
* (MISSING_HYDRATION); a Supervisor may call a data component it does not own only
|
|
7172
|
+
* through read and lifecycle methods (SUPERVISOR_WRITE_SHORTCUT). Optional elsewhere.
|
|
7168
7173
|
*/
|
|
7169
|
-
effect: import_zod8.z.enum(["read", "write"]).optional(),
|
|
7174
|
+
effect: import_zod8.z.enum(["read", "write", "lifecycle"]).optional(),
|
|
7170
7175
|
/**
|
|
7171
7176
|
* Typed acknowledgment of a real caller OUTSIDE the modeled narrative graph
|
|
7172
7177
|
* (runtime timer/hook, external system, sibling subsystem). Unused-detection
|
|
@@ -16150,7 +16155,7 @@ function defaultTargetConfig(type) {
|
|
|
16150
16155
|
enabled: true
|
|
16151
16156
|
};
|
|
16152
16157
|
}
|
|
16153
|
-
var WAIRON_VERSION = "5.1.1-dev.
|
|
16158
|
+
var WAIRON_VERSION = "5.1.1-dev.97";
|
|
16154
16159
|
var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
16155
16160
|
var ARCHITECT_AGENT_ID = "agent-architect";
|
|
16156
16161
|
var ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -17126,7 +17131,7 @@ function buildImportGraph(index, universe) {
|
|
|
17126
17131
|
function buildOwnershipIndex(ctx) {
|
|
17127
17132
|
const ownedBy2 = /* @__PURE__ */ new Map();
|
|
17128
17133
|
for (const comp of ctx.components) {
|
|
17129
|
-
if (isRetired(comp) || !isPattern(comp)) continue;
|
|
17134
|
+
if (isRetired(comp) || !(isPattern(comp) || comp.componentType === "Supervisor")) continue;
|
|
17130
17135
|
for (const memberId of comp.owns) {
|
|
17131
17136
|
const member = ctx.componentMap.get(memberId);
|
|
17132
17137
|
if (!member || isPattern(member)) continue;
|
|
@@ -18423,10 +18428,10 @@ var projectBoundariesRule = {
|
|
|
18423
18428
|
var lintAllowsRule = {
|
|
18424
18429
|
name: "lint-allows",
|
|
18425
18430
|
judges: "design",
|
|
18426
|
-
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.",
|
|
18431
|
+
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.",
|
|
18427
18432
|
codes: [
|
|
18428
18433
|
{ code: "UNKNOWN_LINT_ALLOW_CODE", defaultSeverity: "warning", summary: "lint.allow names an issue code no registered rule emits" },
|
|
18429
|
-
{ code: "UNUSED_LINT_ALLOW", defaultSeverity: "warning", summary: "lint.allow entry
|
|
18434
|
+
{ 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" }
|
|
18430
18435
|
],
|
|
18431
18436
|
check(ctx) {
|
|
18432
18437
|
for (const a of ctx.lintAllows) {
|
|
@@ -18439,9 +18444,20 @@ var lintAllowsRule = {
|
|
|
18439
18444
|
);
|
|
18440
18445
|
continue;
|
|
18441
18446
|
}
|
|
18442
|
-
if (a.used) continue;
|
|
18443
18447
|
const reported = ctx.sitesReported(a.specId, a.code);
|
|
18444
18448
|
const at = a.at ? ` at "${a.at}"` : "";
|
|
18449
|
+
const fired = reported.unsited || reported.sites.length > 0;
|
|
18450
|
+
const isError = reported.errored || !fired && ctx.severityOf(a.code, a.specId) === "error";
|
|
18451
|
+
if (isError) {
|
|
18452
|
+
ctx.addIssue(
|
|
18453
|
+
"warning",
|
|
18454
|
+
"UNUSED_LINT_ALLOW",
|
|
18455
|
+
`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).`,
|
|
18456
|
+
a.specId
|
|
18457
|
+
);
|
|
18458
|
+
continue;
|
|
18459
|
+
}
|
|
18460
|
+
if (a.used) continue;
|
|
18445
18461
|
let why;
|
|
18446
18462
|
if (a.at && reported.unsited && reported.sites.length === 0) {
|
|
18447
18463
|
why = `findings of "${a.code}" on this spec name no site at all, so this allow must not name one \u2014 drop the \`at\``;
|
|
@@ -19964,18 +19980,47 @@ var dataBlockDepsRule = {
|
|
|
19964
19980
|
};
|
|
19965
19981
|
|
|
19966
19982
|
// src/core/rules/doctrine/entrypoint-dependencies.ts
|
|
19983
|
+
function maintainedRegistries(ctx) {
|
|
19984
|
+
const maintained = /* @__PURE__ */ new Map();
|
|
19985
|
+
const add2 = (supervisorId, registryId) => {
|
|
19986
|
+
let set = maintained.get(supervisorId);
|
|
19987
|
+
if (!set) maintained.set(supervisorId, set = /* @__PURE__ */ new Set());
|
|
19988
|
+
set.add(registryId);
|
|
19989
|
+
};
|
|
19990
|
+
for (const comp of ctx.components) {
|
|
19991
|
+
if (comp.componentType !== "Supervisor" || isRetired(comp)) continue;
|
|
19992
|
+
for (const memberId of comp.owns) {
|
|
19993
|
+
if (ctx.componentMap.get(memberId)?.componentType === "Registry") add2(comp.id, memberId);
|
|
19994
|
+
}
|
|
19995
|
+
}
|
|
19996
|
+
for (const impl of ctx.implementations) {
|
|
19997
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
19998
|
+
const supervisor = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
19999
|
+
if (!supervisor || supervisor.componentType !== "Supervisor" || isRetired(supervisor)) continue;
|
|
20000
|
+
for (const implMethod of impl.methods) {
|
|
20001
|
+
for (const step of implMethod.narrative) {
|
|
20002
|
+
if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
|
|
20003
|
+
if (ctx.componentMap.get(step.targetComponent)?.componentType !== "Registry") continue;
|
|
20004
|
+
const called = ctx.interfaceMethodsOf(step.targetComponent).find((m) => m.name === step.targetMethod);
|
|
20005
|
+
if (called?.effect === "lifecycle") add2(supervisor.id, step.targetComponent);
|
|
20006
|
+
}
|
|
20007
|
+
}
|
|
20008
|
+
}
|
|
20009
|
+
return new Map([...maintained].map(([id, set]) => [id, [...set]]));
|
|
20010
|
+
}
|
|
19967
20011
|
var entrypointDepsRule = {
|
|
19968
20012
|
name: "entrypoint-dependencies",
|
|
19969
20013
|
judges: "design",
|
|
19970
|
-
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
|
|
20014
|
+
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.",
|
|
19971
20015
|
codes: [
|
|
19972
20016
|
{ code: "ARCHITECTURE_VIOLATION_PORTAL_DEP", defaultSeverity: "error", summary: "Component depending on a Portal/Observer" },
|
|
19973
20017
|
{ code: "ARCHITECTURE_VIOLATION_PORTAL_FORBIDDEN_DEP", defaultSeverity: "error", summary: "Portal/Observer reaching the data layer directly" },
|
|
19974
20018
|
{ code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on persistence layers or on logic that is not pure" },
|
|
19975
|
-
{ code: "ARCHITECTURE_VIOLATION_SUPERVISOR_DEP", defaultSeverity: "error", summary: "Supervisor depending on
|
|
19976
|
-
{ code: "ACTOR_REACHED_WITHOUT_SUPERVISOR", defaultSeverity: "error", summary: "Component depending on a live Actor it does not supervise,
|
|
20019
|
+
{ 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" },
|
|
20020
|
+
{ 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" }
|
|
19977
20021
|
],
|
|
19978
20022
|
check(ctx) {
|
|
20023
|
+
const lookups = maintainedRegistries(ctx);
|
|
19979
20024
|
for (const edge of ctx.dependencyEdges().matrix) {
|
|
19980
20025
|
const comp = edge.from;
|
|
19981
20026
|
const depComp = edge.to;
|
|
@@ -20011,27 +20056,32 @@ var entrypointDepsRule = {
|
|
|
20011
20056
|
edge.draftContext
|
|
20012
20057
|
);
|
|
20013
20058
|
}
|
|
20014
|
-
if (comp.componentType === "Supervisor" && ["
|
|
20059
|
+
if (comp.componentType === "Supervisor" && ["View", "FeatureComponent", "RouterComponent"].includes(depComp.componentType)) {
|
|
20015
20060
|
ctx.addIssue(
|
|
20016
20061
|
"error",
|
|
20017
20062
|
"ARCHITECTURE_VIOLATION_SUPERVISOR_DEP",
|
|
20018
|
-
`Architectural violation: Supervisor "${comp.id}" cannot depend on ${stereotypeOf(depComp)} "${depComp.id}". A Supervisor
|
|
20063
|
+
`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.`,
|
|
20019
20064
|
comp.id,
|
|
20020
20065
|
edge.draftContext
|
|
20021
20066
|
);
|
|
20022
20067
|
}
|
|
20023
20068
|
if (depComp.componentType === "Actor" && comp.componentType !== "Supervisor") {
|
|
20024
|
-
const
|
|
20025
|
-
|
|
20026
|
-
|
|
20027
|
-
|
|
20028
|
-
|
|
20029
|
-
|
|
20030
|
-
|
|
20069
|
+
const supervisors = ctx.components.filter((c) => c.componentType === "Supervisor" && c.dependsOn.includes(depComp.id));
|
|
20070
|
+
const registries = supervisors.flatMap((s) => (lookups.get(s.id) ?? []).map((registry) => ({ registry, supervisor: s.id })));
|
|
20071
|
+
const reached = supervisors.some((s) => comp.dependsOn.includes(s.id)) || registries.some((r) => comp.dependsOn.includes(r.registry));
|
|
20072
|
+
if (!reached) {
|
|
20073
|
+
let remedy;
|
|
20074
|
+
if (registries.length > 0) {
|
|
20075
|
+
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`;
|
|
20076
|
+
} else if (supervisors.length > 0) {
|
|
20077
|
+
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`;
|
|
20078
|
+
} else {
|
|
20079
|
+
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`;
|
|
20080
|
+
}
|
|
20031
20081
|
ctx.addIssue(
|
|
20032
20082
|
"error",
|
|
20033
20083
|
"ACTOR_REACHED_WITHOUT_SUPERVISOR",
|
|
20034
|
-
`Architectural violation: ${stereotypeOf(comp)} "${comp.id}" depends on the live Actor "${depComp.id}" without
|
|
20084
|
+
`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}.`,
|
|
20035
20085
|
comp.id,
|
|
20036
20086
|
edge.draftContext
|
|
20037
20087
|
);
|
|
@@ -20045,9 +20095,9 @@ var entrypointDepsRule = {
|
|
|
20045
20095
|
var portalWriteShortcutRule = {
|
|
20046
20096
|
name: "portal-write-shortcut",
|
|
20047
20097
|
judges: "design",
|
|
20048
|
-
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.",
|
|
20098
|
+
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.",
|
|
20049
20099
|
codes: [
|
|
20050
|
-
{ 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,
|
|
20100
|
+
{ 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)" }
|
|
20051
20101
|
],
|
|
20052
20102
|
check(ctx) {
|
|
20053
20103
|
for (const impl of ctx.implementations) {
|
|
@@ -20061,11 +20111,11 @@ var portalWriteShortcutRule = {
|
|
|
20061
20111
|
const target = ctx.componentMap.get(step.targetComponent);
|
|
20062
20112
|
if (!target || target.componentType !== "Repository" && target.componentType !== "Index") continue;
|
|
20063
20113
|
const targetMethod = ctx.interfaceMethodsOf(target.id).find((m) => m.name === step.targetMethod);
|
|
20064
|
-
if (targetMethod?.effect !== "write") continue;
|
|
20114
|
+
if (targetMethod?.effect !== "write" && targetMethod?.effect !== "lifecycle") continue;
|
|
20065
20115
|
ctx.addIssue(
|
|
20066
20116
|
"error",
|
|
20067
20117
|
"PORTAL_WRITE_SHORTCUT",
|
|
20068
|
-
`Portal "${component.id}": step ${step.stepNumber} of "${implMethod.name}" calls
|
|
20118
|
+
`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.`,
|
|
20069
20119
|
impl.id,
|
|
20070
20120
|
isDraftCtx || ctx.isComponentDraft(target.id)
|
|
20071
20121
|
);
|
|
@@ -20079,11 +20129,11 @@ var portalWriteShortcutRule = {
|
|
|
20079
20129
|
const target = ctx.componentMap.get(binding.component);
|
|
20080
20130
|
if (!target || target.componentType !== "Repository" && target.componentType !== "Index") continue;
|
|
20081
20131
|
const targetMethod = ctx.interfaceMethodsOf(target.id).find((m) => m.name === binding.method);
|
|
20082
|
-
if (targetMethod?.effect !== "write") continue;
|
|
20132
|
+
if (targetMethod?.effect !== "write" && targetMethod?.effect !== "lifecycle") continue;
|
|
20083
20133
|
ctx.addIssue(
|
|
20084
20134
|
"error",
|
|
20085
20135
|
"PORTAL_WRITE_SHORTCUT",
|
|
20086
|
-
`Portal "${comp.id}": dispatch binding "${binding.capability}" routes to
|
|
20136
|
+
`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.`,
|
|
20087
20137
|
comp.id,
|
|
20088
20138
|
isDraftCtx || ctx.isComponentDraft(target.id)
|
|
20089
20139
|
);
|
|
@@ -20092,28 +20142,101 @@ var portalWriteShortcutRule = {
|
|
|
20092
20142
|
}
|
|
20093
20143
|
};
|
|
20094
20144
|
|
|
20145
|
+
// src/core/rules/doctrine/supervisor-shared-data.ts
|
|
20146
|
+
var DATA_COMPONENTS = /* @__PURE__ */ new Set(["Store", "Registry", "Repository", "Index", "Query"]);
|
|
20147
|
+
var supervisorSharedDataRule = {
|
|
20148
|
+
name: "supervisor-shared-data",
|
|
20149
|
+
judges: "design",
|
|
20150
|
+
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.",
|
|
20151
|
+
codes: [
|
|
20152
|
+
{ 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" }
|
|
20153
|
+
],
|
|
20154
|
+
check(ctx) {
|
|
20155
|
+
const ownership = ctx.ownershipIndex();
|
|
20156
|
+
for (const impl of ctx.implementations) {
|
|
20157
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
20158
|
+
const supervisor = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
20159
|
+
if (!supervisor || supervisor.componentType !== "Supervisor" || isRetired(supervisor)) continue;
|
|
20160
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
20161
|
+
for (const implMethod of impl.methods) {
|
|
20162
|
+
for (const step of implMethod.narrative) {
|
|
20163
|
+
if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
|
|
20164
|
+
const target = ctx.componentMap.get(step.targetComponent);
|
|
20165
|
+
if (!target || !DATA_COMPONENTS.has(target.componentType)) continue;
|
|
20166
|
+
if (ownership.ownerOf(target.id) === supervisor.id) continue;
|
|
20167
|
+
const called = ctx.interfaceMethodsOf(target.id).find((m) => m.name === step.targetMethod);
|
|
20168
|
+
if (!called || called.effect === "read" || called.effect === "lifecycle") continue;
|
|
20169
|
+
const what = called.effect === "write" ? "a write-effect method" : "a method that declares no effect";
|
|
20170
|
+
const untaggedHint = called.effect === "write" ? "" : ` If ${target.id}.${called.name} only reads, or only changes what exists, declare its effect (read or lifecycle).`;
|
|
20171
|
+
ctx.addIssue(
|
|
20172
|
+
"error",
|
|
20173
|
+
"SUPERVISOR_WRITE_SHORTCUT",
|
|
20174
|
+
`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}`,
|
|
20175
|
+
impl.id,
|
|
20176
|
+
isDraftCtx || ctx.isComponentDraft(target.id)
|
|
20177
|
+
);
|
|
20178
|
+
}
|
|
20179
|
+
}
|
|
20180
|
+
}
|
|
20181
|
+
}
|
|
20182
|
+
};
|
|
20183
|
+
|
|
20184
|
+
// src/core/rules/doctrine/lifecycle-effect-closure.ts
|
|
20185
|
+
var lifecycleEffectClosureRule = {
|
|
20186
|
+
name: "lifecycle-effect-closure",
|
|
20187
|
+
judges: "design",
|
|
20188
|
+
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.",
|
|
20189
|
+
codes: [
|
|
20190
|
+
{ 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" }
|
|
20191
|
+
],
|
|
20192
|
+
check(ctx) {
|
|
20193
|
+
for (const impl of ctx.implementations) {
|
|
20194
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
20195
|
+
if (!contract) continue;
|
|
20196
|
+
const declared = ctx.interfaceMethodsOf(contract.component);
|
|
20197
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
20198
|
+
for (const implMethod of impl.methods) {
|
|
20199
|
+
if (declared.find((m) => m.name === implMethod.name)?.effect !== "lifecycle") continue;
|
|
20200
|
+
for (const step of implMethod.narrative) {
|
|
20201
|
+
if (step.type !== "call" || !step.targetComponent || !step.targetMethod) continue;
|
|
20202
|
+
const called = ctx.interfaceMethodsOf(step.targetComponent).find((m) => m.name === step.targetMethod);
|
|
20203
|
+
if (called?.effect !== "write") continue;
|
|
20204
|
+
ctx.addIssue(
|
|
20205
|
+
"error",
|
|
20206
|
+
"LIFECYCLE_CALLS_WRITE",
|
|
20207
|
+
`"${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.`,
|
|
20208
|
+
impl.id,
|
|
20209
|
+
isDraftCtx
|
|
20210
|
+
);
|
|
20211
|
+
}
|
|
20212
|
+
}
|
|
20213
|
+
}
|
|
20214
|
+
}
|
|
20215
|
+
};
|
|
20216
|
+
|
|
20095
20217
|
// src/core/rules/doctrine/pattern-membership.ts
|
|
20096
20218
|
var patternMembershipRule = {
|
|
20097
20219
|
name: "pattern-membership",
|
|
20098
20220
|
judges: "design",
|
|
20099
|
-
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.",
|
|
20221
|
+
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.",
|
|
20100
20222
|
codes: [
|
|
20101
20223
|
{ code: "EMPTY_PATTERN", defaultSeverity: "error", summary: "Pattern with no owned member blocks" },
|
|
20102
|
-
{ code: "BLOCK_OWNS_MEMBERS", defaultSeverity: "error", summary: "Building block using owns" },
|
|
20224
|
+
{ code: "BLOCK_OWNS_MEMBERS", defaultSeverity: "error", summary: "Building block other than a Supervisor using owns" },
|
|
20103
20225
|
{ code: "INVALID_OWNED_MEMBER", defaultSeverity: "error", summary: "owns names a non-existent component" },
|
|
20104
20226
|
{ code: "PATTERN_OWNS_PATTERN", defaultSeverity: "error", summary: "Pattern owning another pattern" },
|
|
20105
|
-
{ code: "SHARED_OWNED_MEMBER", defaultSeverity: "error", summary: "Block owned by two patterns" }
|
|
20227
|
+
{ code: "SHARED_OWNED_MEMBER", defaultSeverity: "error", summary: "Block owned by two owners (patterns or Supervisors)" }
|
|
20106
20228
|
],
|
|
20107
20229
|
check(ctx) {
|
|
20108
20230
|
for (const comp of ctx.components) {
|
|
20109
20231
|
if (isRetired(comp)) continue;
|
|
20110
20232
|
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
20111
20233
|
const pattern = isPattern(comp);
|
|
20234
|
+
const supervisor = comp.componentType === "Supervisor";
|
|
20112
20235
|
if (pattern && comp.owns.length === 0) {
|
|
20113
20236
|
ctx.addIssue("error", "EMPTY_PATTERN", `Pattern "${comp.id}" (${comp.componentType}) must own member blocks via "owns".`, comp.id, isDraftCtx);
|
|
20114
20237
|
}
|
|
20115
|
-
if (!pattern && comp.owns.length > 0) {
|
|
20116
|
-
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);
|
|
20238
|
+
if (!pattern && !supervisor && comp.owns.length > 0) {
|
|
20239
|
+
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);
|
|
20117
20240
|
}
|
|
20118
20241
|
}
|
|
20119
20242
|
const ownership = ctx.ownershipIndex();
|
|
@@ -20121,15 +20244,18 @@ var patternMembershipRule = {
|
|
|
20121
20244
|
if (isRetired(comp)) continue;
|
|
20122
20245
|
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
20123
20246
|
const pattern = isPattern(comp);
|
|
20247
|
+
const supervisor = comp.componentType === "Supervisor";
|
|
20124
20248
|
for (const memberId of comp.owns) {
|
|
20125
20249
|
const member = ctx.componentMap.get(memberId);
|
|
20126
20250
|
if (!member) {
|
|
20127
20251
|
ctx.addIssue("error", "INVALID_OWNED_MEMBER", `Component "${comp.id}" owns "${memberId}" which does not exist.`, comp.id, isDraftCtx);
|
|
20128
20252
|
continue;
|
|
20129
20253
|
}
|
|
20130
|
-
if (!pattern) continue;
|
|
20254
|
+
if (!pattern && !supervisor) continue;
|
|
20131
20255
|
if (isPattern(member)) {
|
|
20132
|
-
|
|
20256
|
+
if (pattern) {
|
|
20257
|
+
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);
|
|
20258
|
+
}
|
|
20133
20259
|
continue;
|
|
20134
20260
|
}
|
|
20135
20261
|
const firstOwner = ownership.ownerOf(memberId);
|
|
@@ -20143,13 +20269,15 @@ var patternMembershipRule = {
|
|
|
20143
20269
|
|
|
20144
20270
|
// src/core/rules/doctrine/pattern-containment.ts
|
|
20145
20271
|
var REPOSITORY_MEMBERS = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Query", "Adapter"]);
|
|
20272
|
+
var SUPERVISION_STATE = /* @__PURE__ */ new Set(["Store", "Registry"]);
|
|
20146
20273
|
var patternContainmentRule = {
|
|
20147
20274
|
name: "pattern-containment",
|
|
20148
20275
|
judges: "design",
|
|
20149
|
-
description: "Holds each
|
|
20276
|
+
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.",
|
|
20150
20277
|
codes: [
|
|
20151
20278
|
{ code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Query/Adapter member" },
|
|
20152
20279
|
{ code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
|
|
20280
|
+
{ code: "SUPERVISOR_CONTAINMENT", defaultSeverity: "error", summary: "Supervisor owning anything but a Store or Registry \u2014 a Supervisor owns only its supervision state" },
|
|
20153
20281
|
{ code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" }
|
|
20154
20282
|
],
|
|
20155
20283
|
check(ctx) {
|
|
@@ -20200,6 +20328,15 @@ var patternContainmentRule = {
|
|
|
20200
20328
|
}
|
|
20201
20329
|
}
|
|
20202
20330
|
}
|
|
20331
|
+
for (const comp of ctx.components) {
|
|
20332
|
+
if (comp.componentType !== "Supervisor" || isRetired(comp)) continue;
|
|
20333
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
20334
|
+
for (const memberId of comp.owns) {
|
|
20335
|
+
const member = ctx.componentMap.get(memberId);
|
|
20336
|
+
if (!member || isRetired(member) || SUPERVISION_STATE.has(member.componentType)) continue;
|
|
20337
|
+
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);
|
|
20338
|
+
}
|
|
20339
|
+
}
|
|
20203
20340
|
}
|
|
20204
20341
|
};
|
|
20205
20342
|
|
|
@@ -20207,7 +20344,7 @@ var patternContainmentRule = {
|
|
|
20207
20344
|
var unownedBlocksRule = {
|
|
20208
20345
|
name: "unowned-blocks",
|
|
20209
20346
|
judges: "design",
|
|
20210
|
-
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.`,
|
|
20347
|
+
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.`,
|
|
20211
20348
|
codes: [
|
|
20212
20349
|
{ 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" },
|
|
20213
20350
|
{ 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" },
|
|
@@ -20253,9 +20390,10 @@ var unownedBlocksRule = {
|
|
|
20253
20390
|
var memberVisibilityRule = {
|
|
20254
20391
|
name: "member-visibility",
|
|
20255
20392
|
judges: "design",
|
|
20256
|
-
description: "A component may depend on a block within its own group (it is the
|
|
20393
|
+
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.",
|
|
20257
20394
|
codes: [
|
|
20258
|
-
{ code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" }
|
|
20395
|
+
{ code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" },
|
|
20396
|
+
{ 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" }
|
|
20259
20397
|
],
|
|
20260
20398
|
check(ctx) {
|
|
20261
20399
|
const ownership = ctx.ownershipIndex();
|
|
@@ -20266,7 +20404,12 @@ var memberVisibilityRule = {
|
|
|
20266
20404
|
if (!owner) continue;
|
|
20267
20405
|
if (owner === comp.id) continue;
|
|
20268
20406
|
if (ownership.ownerOf(comp.id) === owner) continue;
|
|
20269
|
-
|
|
20407
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id) || ctx.isComponentDraft(depId);
|
|
20408
|
+
if (ctx.componentMap.get(owner)?.componentType === "Supervisor") {
|
|
20409
|
+
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);
|
|
20410
|
+
continue;
|
|
20411
|
+
}
|
|
20412
|
+
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);
|
|
20270
20413
|
}
|
|
20271
20414
|
}
|
|
20272
20415
|
}
|
|
@@ -21399,9 +21542,9 @@ var durabilityRule = {
|
|
|
21399
21542
|
// Stage 8: its verdict needs the whole system's specs, so a part judged alone skips it.
|
|
21400
21543
|
needsWholeTree: true,
|
|
21401
21544
|
judges: "design",
|
|
21402
|
-
description: "A durable Store (persisted RAM projection) must carry effect-tagged contract methods, and its
|
|
21545
|
+
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.",
|
|
21403
21546
|
codes: [
|
|
21404
|
-
{ code: "MISSING_EFFECT_TAG", defaultSeverity: "warning", summary: "Durable Store contract method lacks an effect: read | write tag" },
|
|
21547
|
+
{ code: "MISSING_EFFECT_TAG", defaultSeverity: "warning", summary: "Durable Store contract method lacks an effect: read | write | lifecycle tag" },
|
|
21405
21548
|
{ code: "MISSING_HYDRATION", defaultSeverity: "error", summary: "Durable Store is written but no read-back is reachable from any lifecycle init entrypoint" }
|
|
21406
21549
|
],
|
|
21407
21550
|
check(ctx) {
|
|
@@ -21429,7 +21572,7 @@ var durabilityRule = {
|
|
|
21429
21572
|
isDraftCtx
|
|
21430
21573
|
);
|
|
21431
21574
|
}
|
|
21432
|
-
const writes = methods.filter((method) => method.effect === "write");
|
|
21575
|
+
const writes = methods.filter((method) => method.effect === "write" || method.effect === "lifecycle");
|
|
21433
21576
|
const reads = methods.filter((method) => method.effect === "read");
|
|
21434
21577
|
if (writes.length === 0) continue;
|
|
21435
21578
|
const hydrated = initReach !== null && reads.some((method) => initReach.reachesMethod(comp.id, method.name));
|
|
@@ -24722,6 +24865,10 @@ var SDD_RULES = [
|
|
|
24722
24865
|
dataBlockDepsRule,
|
|
24723
24866
|
entrypointDepsRule,
|
|
24724
24867
|
portalWriteShortcutRule,
|
|
24868
|
+
// The process layer's data reach and the lifecycle effect's closure, both
|
|
24869
|
+
// read from narratives against the callee's declared effect.
|
|
24870
|
+
supervisorSharedDataRule,
|
|
24871
|
+
lifecycleEffectClosureRule,
|
|
24725
24872
|
// Patterns in four questions: who may own and what a claim must name,
|
|
24726
24873
|
// what each pattern must contain, which data blocks are left standing
|
|
24727
24874
|
// alone, and who may see a private member.
|
|
@@ -26445,7 +26592,16 @@ function buildRuleContext(opts) {
|
|
|
26445
26592
|
const sitesSeen = /* @__PURE__ */ new Map();
|
|
26446
26593
|
const sitesReported = (specId, code) => {
|
|
26447
26594
|
const seen = sitesSeen.get(`${specId}\0${code}`);
|
|
26448
|
-
return { sites: [...seen?.sites ?? []], unsited: seen?.unsited ?? false };
|
|
26595
|
+
return { sites: [...seen?.sites ?? []], unsited: seen?.unsited ?? false, errored: seen?.errored ?? false };
|
|
26596
|
+
};
|
|
26597
|
+
const codeDefaults = new Map(opts.issueCodeSeverities ?? []);
|
|
26598
|
+
for (const a of opts.extensions?.assertions ?? []) {
|
|
26599
|
+
if (!codeDefaults.has(a.fullCode)) codeDefaults.set(a.fullCode, a.severity);
|
|
26600
|
+
}
|
|
26601
|
+
const severityOf = (code, specId) => {
|
|
26602
|
+
const defaultSeverity = codeDefaults.get(code);
|
|
26603
|
+
if (!defaultSeverity) return void 0;
|
|
26604
|
+
return getRuleSeverity(code, defaultSeverity, false, subsystemOfSpec.get(specId));
|
|
26449
26605
|
};
|
|
26450
26606
|
for (const s of subsystems) collectAllows(s.id, s.lint);
|
|
26451
26607
|
for (const c of components) collectAllows(c.id, c.lint);
|
|
@@ -26488,9 +26644,10 @@ function buildRuleContext(opts) {
|
|
|
26488
26644
|
if (specId) {
|
|
26489
26645
|
const key = `${specId}\0${code}`;
|
|
26490
26646
|
let seen = sitesSeen.get(key);
|
|
26491
|
-
if (!seen) sitesSeen.set(key, seen = { sites: /* @__PURE__ */ new Set(), unsited: false });
|
|
26647
|
+
if (!seen) sitesSeen.set(key, seen = { sites: /* @__PURE__ */ new Set(), unsited: false, errored: false });
|
|
26492
26648
|
if (parts) seen.sites.add(parts.at);
|
|
26493
26649
|
else seen.unsited = true;
|
|
26650
|
+
if (severity === "error") seen.errored = true;
|
|
26494
26651
|
}
|
|
26495
26652
|
let allowClaimed = false;
|
|
26496
26653
|
if (specId) {
|
|
@@ -26607,6 +26764,7 @@ function buildRuleContext(opts) {
|
|
|
26607
26764
|
roundTripIssues: opts.roundTripIssues,
|
|
26608
26765
|
lintAllows: lintAllows2,
|
|
26609
26766
|
sitesReported,
|
|
26767
|
+
severityOf,
|
|
26610
26768
|
knownIssueCodes: opts.knownIssueCodes,
|
|
26611
26769
|
carriedFindings,
|
|
26612
26770
|
carryableIssueCodes: opts.carryableIssueCodes ?? /* @__PURE__ */ new Set(),
|
|
@@ -27039,6 +27197,7 @@ function runOwnersGate(rulesOrOptions, projectType = "backend") {
|
|
|
27039
27197
|
const carryableCodes = new Set(
|
|
27040
27198
|
knownIssueCodes().filter((rc) => rc.carryable).map((rc) => rc.code)
|
|
27041
27199
|
);
|
|
27200
|
+
const codeSeverities = new Map(knownIssueCodes().map((rc) => [rc.code, rc.defaultSeverity]));
|
|
27042
27201
|
const family = graph();
|
|
27043
27202
|
const memberTables = family.nodes.filter((n) => n.namespace !== "").map((n) => resolveProjectExports(n.namespace));
|
|
27044
27203
|
const producers = new Set(family.references.filter((r) => r.consumer === "").map((r) => r.producer));
|
|
@@ -27081,6 +27240,7 @@ function runOwnersGate(rulesOrOptions, projectType = "backend") {
|
|
|
27081
27240
|
codeModel,
|
|
27082
27241
|
roundTripIssues,
|
|
27083
27242
|
knownIssueCodes: knownCodes,
|
|
27243
|
+
issueCodeSeverities: codeSeverities,
|
|
27084
27244
|
carryableIssueCodes: carryableCodes,
|
|
27085
27245
|
issues
|
|
27086
27246
|
});
|
|
@@ -43280,7 +43440,7 @@ ${renderChangeReport(report2)}`,
|
|
|
43280
43440
|
),
|
|
43281
43441
|
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."),
|
|
43282
43442
|
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)"),
|
|
43283
|
-
effect: import_zod11.z.enum(["read", "write"]).optional().describe("
|
|
43443
|
+
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)"),
|
|
43284
43444
|
invokedBy: import_zod11.z.object({
|
|
43285
43445
|
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)"),
|
|
43286
43446
|
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")
|