@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 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.96";
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
- * State-effect direction of this method on its component's held state. Required on a
3741
- * durable Store's contract methods so the durability round-trip rule can pair external
3742
- * writes with hydration read-backs (MISSING_HYDRATION); optional elsewhere.
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 matched no finding this run \u2014 the code never fired, or it fired at sites this allow does not name" }
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 reaches data only through workflows, and a component depending on a live Actor must also depend on a Supervisor that supervises it.",
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 anything but Actors, Orchestrators, Adapters or other Supervisors \u2014 it reaches data only through workflows" },
17606
- { code: "ACTOR_REACHED_WITHOUT_SUPERVISOR", defaultSeverity: "error", summary: "Component depending on a live Actor it does not supervise, without also depending on a Supervisor that supervises it \u2014 a live Actor is reached by id through its Supervisor" }
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" && ["Store", "Registry", "Repository", "Index", "Query", "View", "FeatureComponent", "RouterComponent"].includes(depComp.componentType)) {
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 reaches data only through workflows \u2014 depend on the Orchestrator, Actor or Adapter that does that work instead.` + storeHint,
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 reachedThroughSupervisor = comp.dependsOn.some((id) => {
17655
- const supervisor = ctx.componentMap.get(id);
17656
- return supervisor?.componentType === "Supervisor" && supervisor.dependsOn.includes(depComp.id);
17657
- });
17658
- if (!reachedThroughSupervisor) {
17659
- const supervisors = ctx.components.filter((c) => c.componentType === "Supervisor" && c.dependsOn.includes(depComp.id)).map((c) => `"${c.id}"`);
17660
- const remedy = supervisors.length > 0 ? `also depend on ${supervisors.length === 1 ? "its Supervisor" : "one of its Supervisors"} (${supervisors.join(", ")})` : `no Supervisor depends on "${depComp.id}" yet, so give the Actor a Supervisor that depends on it and depend on that Supervisor`;
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 a Supervisor that supervises it. A live Actor is reached by id through a Supervisor that supervises it \u2014 ${remedy}.`,
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, writes route through an Orchestrator (judged on effect-tagged facade methods; untagged methods are not yet judged)" }
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 write-effect method ${target.id}.${step.targetMethod} directly. The Portal\u2192${target.componentType} shortcut is licensed for READS only \u2014 route the write through an Orchestrator that owns the workflow.`,
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 write-effect method ${target.id}.${binding.method} directly. The Portal\u2192${target.componentType} shortcut is licensed for READS only \u2014 route the write through an Orchestrator that owns the workflow.`,
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
- 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);
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 pattern to the containment its definition prescribes. A Repository may own only Store, Registry, Index, Query and (optionally) Adapter, judged member by member. A FeatureComponent owns exactly one Orchestrator (the logic side) and one or more Views (its faces \u2014 list, detail, form \u2014 sharing the one logic component), and nothing else. A RouterComponent owns exactly one Portal as its facade and at least one other child to route to. A counting pattern that still owns a retired member is not judged until that member is migrated: its counts change with the migration, and STEREOTYPE_RETIRED is the one finding.",
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 owning pattern, or a sibling member of the same pattern), on any pattern facade, or on a standalone block \u2014 never on a block privately owned by ANOTHER pattern, which must be reached through that pattern's facade. A retired depending component is skipped, as everywhere in this family.",
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
- 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, ctx.isComponentDraft(comp.id) || ctx.isComponentDraft(depId));
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 writes require a hydration read-back reachable from a lifecycle init entrypoint. read-through is exempt (every read IS the read-back), as are ram-projection (rebuilt not restored) and cache (evictable, loss-safe). The flagship semantic check is opt-out by declaration, never silently absent \u2014 the declaration itself is enforced by durability-declaration.",
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("State-effect direction on the component's held state \u2014 required on a durable Store's contract methods so the durability round-trip rule can pair writes with hydration read-backs"),
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")