@wairon/cli 5.1.1-dev.15 → 5.1.1-dev.16

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.15";
68
+ WAIRON_VERSION = "5.1.1-dev.16";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  SUPPORTED_ALIASES = ["wai"];
71
71
  SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
@@ -500,7 +500,7 @@ This project uses **wairon**. System specs live under \`.wai/specs/\` (L0 System
500
500
  1. **Design before code**: Complete spec and pass validator before writing source code.
501
501
  2. **Human-in-the-loop**: Ask user approval for each spec layer before proceeding.
502
502
  3. **Spec consistency**: If a 1:1 narrative match is incorrect or conflicts with L0 requirements, escalate a spec revision first. Never ship mismatched code.
503
- 4. **No persistence shortcuts & strict layers**: A Portal must never depend directly on a Store, Registry, or Adapter. Portal reads MAY go through a Repository/Index facade (passthrough reads need no per-entity Orchestrator ceremony), but a Portal narrative calling a write-effect facade method is an error (\`PORTAL_WRITE_SHORTCUT\`) \u2014 writes always route through an Orchestrator. Held domain state always lives in a dedicated data component, never as fields inside an Orchestrator or Specialist. Two sanctioned shapes: the RECOMMENDED Repository pattern (owns Store + Registry + Index; consumers depend on the facade), or \u2014 for genuinely simple state \u2014 a deliberately standalone Store (workflow-layer consumers only, acknowledged with a lint.allow reason on the UNOWNED_STORE warning). Never combine Store/Registry/Index roles into one component, and never fold state into a consuming component because a link was refused.
503
+ 4. **No persistence shortcuts & strict layers**: A Portal must never depend directly on a Store, Registry, or Adapter. Portal reads MAY go through a Repository/Index facade (passthrough reads need no per-entity Orchestrator ceremony), but a Portal narrative call or dispatch-table binding that reaches a write-effect facade method is an error (\`PORTAL_WRITE_SHORTCUT\`) \u2014 writes always route through an Orchestrator. Held domain state always lives in a dedicated data component, never as fields inside an Orchestrator or Specialist. Two sanctioned shapes: the RECOMMENDED Repository pattern (owns Store + Registry + Index; consumers depend on the facade), or \u2014 for genuinely simple state \u2014 a deliberately standalone Store (workflow-layer consumers only, acknowledged with a lint.allow reason on the UNOWNED_STORE warning). Never combine Store/Registry/Index roles into one component, and never fold state into a consuming component because a link was refused.
504
504
 
505
505
  ### Component Vocabulary
506
506
  * **Blocks**: Portal, Orchestrator, Supervisor, Actor, Store, Index, Registry, Adapter, Observer, Specialist.
@@ -2822,15 +2822,13 @@ var init_hierarchy_integrity = __esm({
2822
2822
  }
2823
2823
  }
2824
2824
  for (const impl of ctx.implementations) {
2825
- const contract = ctx.interfaces.find((i) => i.id === impl.contract);
2826
- const isDraftCtx = impl.status === "draft" || impl.status === "design" || contract && (ctx.isComponentDraft(contract.component) || contract.status === "draft" || contract.status === "design");
2827
2825
  if (!ctx.interfaceIds.has(impl.contract)) {
2828
2826
  ctx.addIssue(
2829
2827
  "error",
2830
2828
  "INVALID_INTERFACE_REFERENCE",
2831
2829
  `Implementation "${impl.id}" references non-existent interface contract "${impl.contract}".`,
2832
2830
  impl.id,
2833
- isDraftCtx
2831
+ ctx.isImplementationDraft(impl)
2834
2832
  );
2835
2833
  }
2836
2834
  }
@@ -2946,11 +2944,7 @@ var init_type_references2 = __esm({
2946
2944
  for (const field of t.fields) {
2947
2945
  const refs = fieldTypeRefs(t, field.type);
2948
2946
  for (const ref of refs) {
2949
- if (ctx.isBuiltinType(ref)) {
2950
- continue;
2951
- }
2952
- const resolved = ctx.types.find((spec) => typeMatchesRef(spec, ref));
2953
- if (!resolved) {
2947
+ if (!ctx.isTypeResolved(ref, /* @__PURE__ */ new Set())) {
2954
2948
  ctx.addIssue(
2955
2949
  "error",
2956
2950
  "UNDEFINED_TYPE_REFERENCE",
@@ -3182,14 +3176,27 @@ var init_contract_symmetry_and_narratives = __esm({
3182
3176
  );
3183
3177
  continue;
3184
3178
  }
3185
- const isCrossTreeForm = ctx.isExternalNamespaceRef(step.targetComponent);
3186
- const fromSubsystem = ctx.componentMap.get(contract.component)?.subsystem;
3187
- const isCollapsedForm = !isCrossTreeForm && fromSubsystem !== void 0 && ctx.isCollapsedCrossTreeRef(step.targetComponent, fromSubsystem);
3179
+ const target = step.targetComponent;
3180
+ const isCrossTreeForm = ctx.isExternalNamespaceRef(target);
3181
+ const caller = ctx.componentMap.get(contract.component);
3182
+ const fromSubsystem = caller?.subsystem;
3183
+ const isCollapsedForm = !isCrossTreeForm && fromSubsystem !== void 0 && ctx.isCollapsedCrossTreeRef(target, fromSubsystem);
3184
+ const reportUndeclared = (reaches, surfaceResolved) => {
3185
+ if (!caller || target === caller.id || caller.dependsOn.includes(target) || caller.owns.includes(target)) return;
3186
+ ctx.addIssue(
3187
+ "error",
3188
+ "UNDECLARED_DEPENDENCY_CALL",
3189
+ `Method "${implMethod.name}" in implementation "${impl.id}" (component "${caller.id}") ${reaches} component "${target}" (step ${step.stepNumber}) but component "${caller.id}" does not list "${target}" as a dependency.`,
3190
+ impl.id,
3191
+ isDraftCtx || ctx.isComponentDraft(caller.id),
3192
+ surfaceResolved
3193
+ );
3194
+ };
3188
3195
  if (step.type === "dispatch") {
3189
- const dispatchTarget = ctx.componentMap.get(step.targetComponent);
3196
+ const dispatchTarget = ctx.componentMap.get(target);
3190
3197
  if (!dispatchTarget) {
3191
3198
  if (isCrossTreeForm || isCollapsedForm) {
3192
- const resolved = ctx.resolveSurfaceRef(step.targetComponent, fromSubsystem);
3199
+ const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
3193
3200
  if (resolved.kind === "ambiguous") {
3194
3201
  ctx.addIssue(
3195
3202
  "error",
@@ -3197,7 +3204,7 @@ var init_contract_symmetry_and_narratives = __esm({
3197
3204
  ambiguityMessage(
3198
3205
  resolved,
3199
3206
  `Method "${implMethod.name}" in implementation "${impl.id}" dispatches (step ${step.stepNumber}) through`,
3200
- step.targetComponent
3207
+ target
3201
3208
  ),
3202
3209
  impl.id,
3203
3210
  isDraftCtx
@@ -3205,11 +3212,12 @@ var init_contract_symmetry_and_narratives = __esm({
3205
3212
  continue;
3206
3213
  }
3207
3214
  if (resolved.kind === "resolved") {
3215
+ reportUndeclared("dispatches through", true);
3208
3216
  if (step.capability && !(resolved.entry.dispatch ?? []).some((b) => b.capability === step.capability)) {
3209
3217
  ctx.addIssue(
3210
3218
  "error",
3211
3219
  "SURFACE_REF_NOT_EXPOSED",
3212
- `Method "${implMethod.name}" in implementation "${impl.id}" dispatches capability "${step.capability}" through cross-tree portal "${step.targetComponent}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not serve that capability on "${resolved.entry.id}".`,
3220
+ `Method "${implMethod.name}" in implementation "${impl.id}" dispatches capability "${step.capability}" through cross-tree portal "${target}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not serve that capability on "${resolved.entry.id}".`,
3213
3221
  impl.id,
3214
3222
  isDraftCtx,
3215
3223
  true
@@ -3222,7 +3230,7 @@ var init_contract_symmetry_and_narratives = __esm({
3222
3230
  ctx.addIssue(
3223
3231
  "warning",
3224
3232
  "CROSS_TREE_REF_UNRESOLVED",
3225
- `Method "${implMethod.name}" in implementation "${impl.id}" dispatches through cross-tree component "${step.targetComponent}" (step ${step.stepNumber}), and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
3233
+ `Method "${implMethod.name}" in implementation "${impl.id}" dispatches through cross-tree component "${target}" (step ${step.stepNumber}), and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
3226
3234
  impl.id,
3227
3235
  isDraftCtx
3228
3236
  );
@@ -3230,23 +3238,14 @@ var init_contract_symmetry_and_narratives = __esm({
3230
3238
  ctx.addIssue(
3231
3239
  "error",
3232
3240
  "INVALID_TARGET_COMPONENT_REFERENCE",
3233
- `Method "${implMethod.name}" in implementation "${impl.id}" dispatches through component "${step.targetComponent}" which does not exist (step ${step.stepNumber}).`,
3241
+ `Method "${implMethod.name}" in implementation "${impl.id}" dispatches through component "${target}" which does not exist (step ${step.stepNumber}).`,
3234
3242
  impl.id,
3235
3243
  isDraftCtx
3236
3244
  );
3237
3245
  }
3238
3246
  continue;
3239
3247
  }
3240
- const dispatchCaller = ctx.componentMap.get(contract.component);
3241
- if (dispatchCaller && step.targetComponent !== dispatchCaller.id && !dispatchCaller.dependsOn.includes(step.targetComponent) && !dispatchCaller.owns.includes(step.targetComponent)) {
3242
- ctx.addIssue(
3243
- "error",
3244
- "UNDECLARED_DEPENDENCY_CALL",
3245
- `Method "${implMethod.name}" in implementation "${impl.id}" (component "${dispatchCaller.id}") dispatches through component "${step.targetComponent}" (step ${step.stepNumber}) but component "${dispatchCaller.id}" does not list "${step.targetComponent}" as a dependency.`,
3246
- impl.id,
3247
- isDraftCtx || ctx.isComponentDraft(dispatchCaller.id)
3248
- );
3249
- }
3248
+ reportUndeclared("dispatches through");
3250
3249
  continue;
3251
3250
  }
3252
3251
  const verb = step.type === "register" ? "registers callback" : "calls";
@@ -3260,10 +3259,10 @@ var init_contract_symmetry_and_narratives = __esm({
3260
3259
  );
3261
3260
  continue;
3262
3261
  }
3263
- const targetComp = ctx.componentMap.get(step.targetComponent);
3262
+ const targetComp = ctx.componentMap.get(target);
3264
3263
  if (!targetComp) {
3265
3264
  if (isCrossTreeForm || isCollapsedForm) {
3266
- const resolved = ctx.resolveSurfaceRef(step.targetComponent, fromSubsystem);
3265
+ const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
3267
3266
  if (resolved.kind === "ambiguous") {
3268
3267
  ctx.addIssue(
3269
3268
  "error",
@@ -3271,7 +3270,7 @@ var init_contract_symmetry_and_narratives = __esm({
3271
3270
  ambiguityMessage(
3272
3271
  resolved,
3273
3272
  `Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" (step ${step.stepNumber}) on`,
3274
- step.targetComponent
3273
+ target
3275
3274
  ),
3276
3275
  impl.id,
3277
3276
  isDraftCtx
@@ -3279,12 +3278,13 @@ var init_contract_symmetry_and_narratives = __esm({
3279
3278
  continue;
3280
3279
  }
3281
3280
  if (resolved.kind === "resolved") {
3281
+ reportUndeclared(verb, true);
3282
3282
  const surfaceMethod = resolved.entry.methods.find((m) => m.name === step.targetMethod);
3283
3283
  if (!surfaceMethod) {
3284
3284
  ctx.addIssue(
3285
3285
  "error",
3286
3286
  "SURFACE_REF_NOT_EXPOSED",
3287
- `Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" on cross-tree component "${step.targetComponent}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not expose that method on "${resolved.entry.id}".`,
3287
+ `Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" on cross-tree component "${target}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not expose that method on "${resolved.entry.id}".`,
3288
3288
  impl.id,
3289
3289
  isDraftCtx,
3290
3290
  true
@@ -3311,7 +3311,7 @@ var init_contract_symmetry_and_narratives = __esm({
3311
3311
  ctx.addIssue(
3312
3312
  "warning",
3313
3313
  "CROSS_TREE_REF_UNRESOLVED",
3314
- `Method "${implMethod.name}" in implementation "${impl.id}" ${verb} cross-tree component "${step.targetComponent}" (step ${step.stepNumber}), and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
3314
+ `Method "${implMethod.name}" in implementation "${impl.id}" ${verb} cross-tree component "${target}" (step ${step.stepNumber}), and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
3315
3315
  impl.id,
3316
3316
  isDraftCtx
3317
3317
  );
@@ -3319,26 +3319,15 @@ var init_contract_symmetry_and_narratives = __esm({
3319
3319
  ctx.addIssue(
3320
3320
  "error",
3321
3321
  "INVALID_TARGET_COMPONENT_REFERENCE",
3322
- `Method "${implMethod.name}" in implementation "${impl.id}" ${verb} component "${step.targetComponent}" which does not exist (step ${step.stepNumber}).`,
3322
+ `Method "${implMethod.name}" in implementation "${impl.id}" ${verb} component "${target}" which does not exist (step ${step.stepNumber}).`,
3323
3323
  impl.id,
3324
3324
  isDraftCtx
3325
3325
  );
3326
3326
  }
3327
3327
  continue;
3328
3328
  }
3329
- const callingComponent = ctx.componentMap.get(contract.component);
3330
- if (callingComponent && step.targetComponent !== callingComponent.id) {
3331
- if (!callingComponent.dependsOn.includes(step.targetComponent) && !callingComponent.owns.includes(step.targetComponent)) {
3332
- ctx.addIssue(
3333
- "error",
3334
- "UNDECLARED_DEPENDENCY_CALL",
3335
- `Method "${implMethod.name}" in implementation "${impl.id}" (component "${callingComponent.id}") ${verb} component "${step.targetComponent}" (step ${step.stepNumber}) but component "${callingComponent.id}" does not list "${step.targetComponent}" as a dependency.`,
3336
- impl.id,
3337
- isDraftCtx || ctx.isComponentDraft(callingComponent.id)
3338
- );
3339
- }
3340
- }
3341
- const targetInterfaces = ctx.interfacesByComponent.get(step.targetComponent) ?? [];
3329
+ reportUndeclared(verb);
3330
+ const targetInterfaces = ctx.interfacesByComponent.get(target) ?? [];
3342
3331
  let targetMethodSpec;
3343
3332
  for (const targetIntf of targetInterfaces) {
3344
3333
  const found = targetIntf.methods.find((m) => m.name === step.targetMethod);
@@ -3351,9 +3340,9 @@ var init_contract_symmetry_and_narratives = __esm({
3351
3340
  ctx.addIssue(
3352
3341
  "error",
3353
3342
  "INVALID_TARGET_METHOD_REFERENCE",
3354
- `Method "${implMethod.name}" in implementation "${impl.id}" ${verb} method "${step.targetMethod}" on component "${step.targetComponent}" which is not defined on any of its interfaces (step ${step.stepNumber}).`,
3343
+ `Method "${implMethod.name}" in implementation "${impl.id}" ${verb} method "${step.targetMethod}" on component "${target}" which is not defined on any of its interfaces (step ${step.stepNumber}).`,
3355
3344
  impl.id,
3356
- isDraftCtx || ctx.isComponentDraft(step.targetComponent)
3345
+ isDraftCtx || ctx.isComponentDraft(target)
3357
3346
  );
3358
3347
  } else {
3359
3348
  const declared = new Set(targetMethodSpec.guarantees ?? []);
@@ -3363,9 +3352,9 @@ var init_contract_symmetry_and_narratives = __esm({
3363
3352
  ctx.addIssue(
3364
3353
  "warning",
3365
3354
  "NARRATIVE_SEMANTIC_UNBACKED",
3366
- `Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" explicitly asserts guarantee "${g}", but the method it calls \u2014 "${step.targetMethod}" on "${step.targetComponent}" \u2014 does not list "${g}" among its L3 contract guarantees. Declare it on that method (and ensure its shape can deliver it), or revise the narrative.`,
3355
+ `Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" explicitly asserts guarantee "${g}", but the method it calls \u2014 "${step.targetMethod}" on "${target}" \u2014 does not list "${g}" among its L3 contract guarantees. Declare it on that method (and ensure its shape can deliver it), or revise the narrative.`,
3367
3356
  impl.id,
3368
- isDraftCtx || ctx.isComponentDraft(step.targetComponent)
3357
+ isDraftCtx || ctx.isComponentDraft(target)
3369
3358
  );
3370
3359
  }
3371
3360
  }
@@ -3695,43 +3684,47 @@ var init_narrative_flow = __esm({
3695
3684
  });
3696
3685
 
3697
3686
  // src/core/rules/narrative/narrative-antipatterns.ts
3698
- function isTerminal(step, nextOf) {
3699
- if (step.type === "return" || step.type === "throw") return true;
3700
- const n = step.stepNumber;
3701
- switch (step.type) {
3702
- case "local":
3703
- case "call":
3704
- case "register":
3705
- case "dispatch":
3706
- return nextOf(n) === void 0;
3707
- case "branch":
3708
- return step.onTrueStep === void 0 && nextOf(n) === void 0;
3709
- case "switch":
3710
- return step.defaultStep === void 0 && nextOf(n) === void 0;
3711
- case "loop":
3712
- case "try":
3713
- return step.endStep !== void 0 && nextOf(step.endStep) === void 0;
3714
- default:
3715
- return false;
3716
- }
3687
+ function completedStepGraph(steps) {
3688
+ const numbers = steps.flatMap((s) => [
3689
+ s.stepNumber,
3690
+ s.onTrueStep,
3691
+ s.onFalseStep,
3692
+ s.defaultStep,
3693
+ s.endStep,
3694
+ s.finallyStep,
3695
+ s.toStep,
3696
+ ...(s.cases ?? []).map((c) => c.step),
3697
+ ...(s.catches ?? []).map((c) => c.step),
3698
+ ...(s.branches ?? []).map((b) => b.step)
3699
+ ]).filter((n) => n !== void 0);
3700
+ const completion = {
3701
+ stepNumber: Math.max(0, ...numbers) + 1,
3702
+ description: "The method completes by falling off the end of its narrative",
3703
+ type: "return"
3704
+ };
3705
+ return stepGraph({ narrative: [...steps, completion] });
3717
3706
  }
3718
3707
  function isUnavoidable(steps, target) {
3719
- const { stepNumbers: nums, stepAt, nextOf, successorsOf } = stepGraph({ narrative: steps });
3720
- if (nums.length === 0) return false;
3721
- if (nums[0] === target) return true;
3722
- const visited = /* @__PURE__ */ new Set();
3723
- const stack = [nums[0]];
3724
- while (stack.length) {
3725
- const n = stack.pop();
3726
- if (n === target || visited.has(n)) continue;
3727
- visited.add(n);
3728
- const s = stepAt(n);
3729
- if (isTerminal(s, nextOf)) return false;
3730
- for (const t of successorsOf(n)) {
3731
- if (t !== target && !visited.has(t)) stack.push(t);
3708
+ if (steps.length === 0) return false;
3709
+ const graph = completedStepGraph(steps);
3710
+ const completes = /* @__PURE__ */ new Set();
3711
+ const canComplete = (n) => {
3712
+ const s = graph.stepAt(n);
3713
+ if (s.type === "return" || s.type === "throw") return true;
3714
+ const arms = s.type === "parallel" ? (s.branches ?? []).map((b) => b.step).filter((e) => graph.stepAt(e) !== void 0) : [];
3715
+ if (arms.length) return arms.every((e) => completes.has(e));
3716
+ return graph.successorsOf(n).some((t) => completes.has(t));
3717
+ };
3718
+ const order = [...graph.stepNumbers].reverse();
3719
+ for (let grew = true; grew; ) {
3720
+ grew = false;
3721
+ for (const n of order) {
3722
+ if (n === target || completes.has(n) || !canComplete(n)) continue;
3723
+ completes.add(n);
3724
+ grew = true;
3732
3725
  }
3733
3726
  }
3734
- return true;
3727
+ return !completes.has(graph.stepNumbers[0]);
3735
3728
  }
3736
3729
  function stronglyConnected(nums, successorsOf) {
3737
3730
  const index = /* @__PURE__ */ new Map();
@@ -3783,11 +3776,12 @@ function stronglyConnected(nums, successorsOf) {
3783
3776
  }
3784
3777
  return sccs;
3785
3778
  }
3786
- var narrativeAntipatternsRule;
3779
+ var byCodeUnit, narrativeAntipatternsRule;
3787
3780
  var init_narrative_antipatterns = __esm({
3788
3781
  "src/core/rules/narrative/narrative-antipatterns.ts"() {
3789
3782
  "use strict";
3790
3783
  init_models();
3784
+ byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
3791
3785
  narrativeAntipatternsRule = {
3792
3786
  name: "narrative-antipatterns",
3793
3787
  description: "Provable narrative bugs, caught at spec level before implementation: a step-graph cycle with no exit edge and no return/throw member never terminates by construction (INESCAPABLE_CYCLE); a branch or switch whose arms all target the same step decides nothing (MEANINGLESS_BRANCH); a method-level call cycle across components in which every call edge is unavoidable on every entry-to-exit path is unbounded recursion (UNCONDITIONAL_CALL_CYCLE \u2014 dispatch steps resolved through their portal tables; a cycle with even one guarded edge is NOT flagged, since prose conditions are never judged). Structure-only claims \u2014 warnings, lint.allow-suppressible.",
@@ -3806,29 +3800,33 @@ var init_narrative_antipatterns = __esm({
3806
3800
  const steps = implMethod.narrative;
3807
3801
  if (!steps.length) continue;
3808
3802
  const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
3809
- const graph = stepGraph(implMethod);
3803
+ const graph = completedStepGraph(steps);
3804
+ const decisionTargets = (stepNumber, explicit, fallThroughField) => {
3805
+ const targets = new Set(explicit);
3806
+ if (fallThroughField !== void 0) targets.add(fallThroughField);
3807
+ else for (const t of graph.successorsOf(stepNumber)) targets.add(t);
3808
+ return [...targets];
3809
+ };
3810
3810
  for (const s of steps) {
3811
3811
  if (s.type === "branch" && s.onFalseStep !== void 0) {
3812
- const onTrue = s.onTrueStep ?? graph.nextOf(s.stepNumber);
3813
- if (onTrue !== void 0 && onTrue === s.onFalseStep) {
3812
+ const targets = decisionTargets(s.stepNumber, [s.onFalseStep], s.onTrueStep);
3813
+ if (targets.length === 1) {
3814
3814
  ctx.addIssue(
3815
3815
  "warning",
3816
3816
  "MEANINGLESS_BRANCH",
3817
- `${where}branch step ${s.stepNumber} sends both arms to step ${onTrue} \u2014 the condition decides nothing. Point the arms at different steps, or replace the branch with a local step.`,
3817
+ `${where}branch step ${s.stepNumber} sends both arms to step ${targets[0]} \u2014 the condition decides nothing. Point the arms at different steps, or replace the branch with a local step.`,
3818
3818
  impl.id,
3819
3819
  isDraftCtx
3820
3820
  );
3821
3821
  }
3822
3822
  }
3823
3823
  if (s.type === "switch" && s.cases?.length) {
3824
- const targets = new Set(s.cases.map((c) => c.step));
3825
- const def = s.defaultStep ?? graph.nextOf(s.stepNumber);
3826
- if (def !== void 0) targets.add(def);
3827
- if (targets.size === 1) {
3824
+ const targets = decisionTargets(s.stepNumber, s.cases.map((c) => c.step), s.defaultStep);
3825
+ if (targets.length === 1) {
3828
3826
  ctx.addIssue(
3829
3827
  "warning",
3830
3828
  "MEANINGLESS_BRANCH",
3831
- `${where}switch step ${s.stepNumber} sends every case (and the default) to step ${[...targets][0]} \u2014 the dispatch decides nothing.`,
3829
+ `${where}switch step ${s.stepNumber} sends every case (and the default) to step ${targets[0]} \u2014 the dispatch decides nothing.`,
3832
3830
  impl.id,
3833
3831
  isDraftCtx
3834
3832
  );
@@ -3901,8 +3899,8 @@ var init_narrative_antipatterns = __esm({
3901
3899
  if (!isCycle) continue;
3902
3900
  const memberEdges = keys.flatMap((k) => (adjacency.get(k) ?? []).filter((e) => inScc.has(e.toKey)));
3903
3901
  if (memberEdges.length === 0) continue;
3904
- const anchor = [...memberEdges].sort((a, b) => a.fromKey.localeCompare(b.fromKey))[0];
3905
- const path70 = [...keys].sort().join(" \u2192 ");
3902
+ const anchor = [...memberEdges].sort((a, b) => byCodeUnit(a.fromKey, b.fromKey) || byCodeUnit(a.impl.id, b.impl.id) || a.stepNumber - b.stepNumber)[0];
3903
+ const path70 = [...keys].sort(byCodeUnit).join(" \u2192 ");
3906
3904
  ctx.addIssue(
3907
3905
  "warning",
3908
3906
  "UNCONDITIONAL_CALL_CYCLE",
@@ -4154,14 +4152,15 @@ var init_portal_endpoints = __esm({
4154
4152
  continue;
4155
4153
  }
4156
4154
  for (const intf of compInterfaces) {
4155
+ const isIntfDraft = intf.status === "draft" || intf.status === "design";
4157
4156
  for (const m of intf.methods) {
4158
4157
  if (m.endpoint) {
4159
4158
  ctx.addIssue(
4160
4159
  "error",
4161
4160
  "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT",
4162
4161
  `Architectural violation: Component "${comp.id}" is a ${comp.componentType}, but method "${m.name}" on its interface "${intf.id}" declares an endpoint. Only Portal components may carry endpoints.`,
4163
- comp.id,
4164
- isDraftCtx
4162
+ intf.id,
4163
+ isDraftCtx || isIntfDraft
4165
4164
  );
4166
4165
  }
4167
4166
  }
@@ -4292,7 +4291,7 @@ var init_stereotype_dependencies = __esm({
4292
4291
  { code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on Orchestrators or Stores" },
4293
4292
  { code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store or an Adapter" },
4294
4293
  { code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on logic/persistence layers" },
4295
- { code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative calls 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)" }
4294
+ { 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)" }
4296
4295
  ],
4297
4296
  check(ctx) {
4298
4297
  for (const comp of ctx.components) {
@@ -4364,10 +4363,11 @@ var init_stereotype_dependencies = __esm({
4364
4363
  crossDraft
4365
4364
  );
4366
4365
  } else if (depComp.componentType !== "Portal" && depComp.componentType !== "Gateway") {
4366
+ const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
4367
4367
  ctx.addIssue(
4368
4368
  "error",
4369
4369
  "CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
4370
- `Boundary violation: client Adapter "${comp.id}" enters subsystem "${depComp.subsystem}" through "${depComp.id}" (${depComp.componentType}), not its inbound Portal. A cross-subsystem hop must target the remote subsystem's Portal (its front door), which dispatches inward \u2014 publishing/depending on an internal ${depComp.componentType} leaks the boundary and breaks the distribution seam. Expose a Portal for "${depComp.subsystem}" and point this Adapter at it.`,
4370
+ `Boundary violation: ${crosser} "${comp.id}" enters subsystem "${depComp.subsystem}" through "${depComp.id}" (${depComp.componentType}), not its inbound Portal. A cross-subsystem hop must target the remote subsystem's Portal (its front door), which dispatches inward \u2014 publishing/depending on an internal ${depComp.componentType} leaks the boundary and breaks the distribution seam. Expose a Portal for "${depComp.subsystem}" and point "${comp.id}" at it.`,
4371
4371
  comp.id,
4372
4372
  crossDraft
4373
4373
  );
@@ -4491,6 +4491,23 @@ var init_stereotype_dependencies = __esm({
4491
4491
  }
4492
4492
  }
4493
4493
  }
4494
+ for (const comp of ctx.components) {
4495
+ if (comp.componentType !== "Portal" || !comp.dispatch || comp.dispatch.length === 0) continue;
4496
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
4497
+ for (const binding of comp.dispatch) {
4498
+ const target = ctx.componentMap.get(binding.component);
4499
+ if (!target || target.componentType !== "Repository" && target.componentType !== "Index") continue;
4500
+ const targetMethod = ctx.interfaceMethodsOf(target.id).find((m) => m.name === binding.method);
4501
+ if (targetMethod?.effect !== "write") continue;
4502
+ ctx.addIssue(
4503
+ "error",
4504
+ "PORTAL_WRITE_SHORTCUT",
4505
+ `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.`,
4506
+ comp.id,
4507
+ isDraftCtx || ctx.isComponentDraft(target.id)
4508
+ );
4509
+ }
4510
+ }
4494
4511
  }
4495
4512
  };
4496
4513
  }
@@ -4514,7 +4531,7 @@ var init_pattern_ownership = __esm({
4514
4531
  { code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Adapter member" },
4515
4532
  { code: "GATEWAY_CONTAINMENT", defaultSeverity: "error", summary: "Gateway owning a non Portal/Orchestrator/Specialist member" },
4516
4533
  { code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
4517
- { code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent missing its Portal facade or children" },
4534
+ { code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" },
4518
4535
  { code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" },
4519
4536
  { 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" },
4520
4537
  { 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" }
@@ -4536,14 +4553,15 @@ var init_pattern_ownership = __esm({
4536
4553
  ctx.addIssue("error", "INVALID_OWNED_MEMBER", `Component "${comp.id}" owns "${memberId}" which does not exist.`, comp.id, isDraftCtx);
4537
4554
  continue;
4538
4555
  }
4556
+ if (!pattern) continue;
4539
4557
  if (isPattern(member)) {
4540
4558
  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);
4541
4559
  }
4542
- const prev = ownedBy.get(memberId);
4543
- if (prev && prev !== comp.id) {
4544
- ctx.addIssue("error", "SHARED_OWNED_MEMBER", `Block "${memberId}" is owned by both "${prev}" and "${comp.id}"; a block has exactly one owner.`, comp.id, isDraftCtx);
4560
+ const firstOwner = ownedBy.get(memberId);
4561
+ if (firstOwner && firstOwner !== comp.id) {
4562
+ ctx.addIssue("error", "SHARED_OWNED_MEMBER", `Block "${memberId}" is owned by both "${firstOwner}" and "${comp.id}"; a block has exactly one owner.`, comp.id, isDraftCtx);
4545
4563
  }
4546
- ownedBy.set(memberId, comp.id);
4564
+ if (!firstOwner) ownedBy.set(memberId, comp.id);
4547
4565
  }
4548
4566
  if (comp.componentType === "Repository") {
4549
4567
  const allowed = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Adapter"]);
@@ -4578,14 +4596,14 @@ var init_pattern_ownership = __esm({
4578
4596
  }
4579
4597
  }
4580
4598
  if (comp.componentType === "RouterComponent") {
4581
- let hasPortal = false;
4599
+ let portals = 0;
4582
4600
  let hasChildren = false;
4583
4601
  for (const memberId of comp.owns) {
4584
4602
  const t = ctx.componentMap.get(memberId)?.componentType;
4585
- if (t === "Portal") hasPortal = true;
4603
+ if (t === "Portal") portals++;
4586
4604
  else if (t) hasChildren = true;
4587
4605
  }
4588
- if (!hasPortal) {
4606
+ if (portals !== 1) {
4589
4607
  ctx.addIssue("error", "ROUTER_COMPONENT_CONTAINMENT", `RouterComponent "${comp.id}" must own exactly one Portal component to act as its facade.`, comp.id, isDraftCtx);
4590
4608
  }
4591
4609
  if (!hasChildren) {
@@ -5087,54 +5105,43 @@ var init_dependency_cycles = __esm({
5087
5105
  "use strict";
5088
5106
  cyclesRule = {
5089
5107
  name: "dependency-cycles",
5090
- description: "The component dependsOn graph must be a DAG; the first detected cycle is reported with its full path.",
5108
+ description: "The component dependsOn graph must be a DAG; the first cycle through a component in the run's scope is reported with its full path.",
5091
5109
  codes: [
5092
5110
  { code: "CIRCULAR_DEPENDENCY", defaultSeverity: "error", summary: "Circular dependency between components" }
5093
5111
  ],
5094
5112
  check(ctx) {
5095
- const visited = /* @__PURE__ */ new Set();
5096
- const recStack = /* @__PURE__ */ new Set();
5097
- const dfs = (compId, pathTrace) => {
5098
- visited.add(compId);
5099
- recStack.add(compId);
5100
- pathTrace.push(compId);
5101
- const comp = ctx.componentMap.get(compId);
5102
- if (comp) {
5103
- for (const depId of comp.dependsOn) {
5104
- if (!visited.has(depId)) {
5105
- if (dfs(depId, pathTrace)) {
5106
- recStack.delete(compId);
5107
- pathTrace.pop();
5108
- return true;
5109
- }
5110
- } else if (recStack.has(depId)) {
5113
+ const cycleFrom = (startId) => {
5114
+ const visited = /* @__PURE__ */ new Set([startId]);
5115
+ const pathTrace = [startId];
5116
+ const search = (compId) => {
5117
+ for (const depId of ctx.componentMap.get(compId)?.dependsOn ?? []) {
5118
+ if (depId === startId) {
5111
5119
  pathTrace.push(depId);
5112
- const cyclePath = pathTrace.slice(pathTrace.indexOf(depId)).join(" -> ");
5113
- const isDraftCtx = ctx.isComponentDraft(compId) || ctx.isComponentDraft(depId);
5114
- ctx.addIssue(
5115
- "error",
5116
- "CIRCULAR_DEPENDENCY",
5117
- `Circular dependency detected: ${cyclePath}`,
5118
- compId,
5119
- isDraftCtx
5120
- );
5121
- pathTrace.pop();
5122
- recStack.delete(compId);
5123
- pathTrace.pop();
5124
5120
  return true;
5125
5121
  }
5122
+ if (visited.has(depId)) continue;
5123
+ visited.add(depId);
5124
+ pathTrace.push(depId);
5125
+ if (search(depId)) return true;
5126
+ pathTrace.pop();
5126
5127
  }
5127
- }
5128
- recStack.delete(compId);
5129
- pathTrace.pop();
5130
- return false;
5128
+ return false;
5129
+ };
5130
+ return search(startId) ? pathTrace : void 0;
5131
5131
  };
5132
5132
  for (const comp of ctx.components) {
5133
- if (!visited.has(comp.id)) {
5134
- if (dfs(comp.id, [])) {
5135
- break;
5136
- }
5137
- }
5133
+ if (!ctx.isSpecInScope(comp.id)) continue;
5134
+ const cycle = cycleFrom(comp.id);
5135
+ if (!cycle) continue;
5136
+ const closing = cycle[cycle.length - 2];
5137
+ ctx.addIssue(
5138
+ "error",
5139
+ "CIRCULAR_DEPENDENCY",
5140
+ `Circular dependency detected: ${cycle.join(" -> ")}`,
5141
+ comp.id,
5142
+ ctx.isComponentDraft(comp.id) || ctx.isComponentDraft(closing)
5143
+ );
5144
+ return;
5138
5145
  }
5139
5146
  }
5140
5147
  };
@@ -5263,19 +5270,18 @@ var init_dispatch_tables = __esm({
5263
5270
  );
5264
5271
  continue;
5265
5272
  }
5266
- if (step.assertsGuarantees?.length) {
5267
- const boundMethod = ctx.interfaceMethodsOf(binding.component).find((m) => m.name === binding.method);
5268
- const declared = new Set(boundMethod?.guarantees ?? []);
5269
- for (const g of step.assertsGuarantees) {
5270
- if (!declared.has(g)) {
5271
- ctx.addIssue(
5272
- "warning",
5273
- "NARRATIVE_SEMANTIC_UNBACKED",
5274
- `${where} asserts guarantee "${g}", but capability "${step.capability}" resolves to "${binding.component}.${binding.method}", which does not list "${g}" among its L3 contract guarantees. Declare it there (and ensure its shape can deliver it), or revise the narrative.`,
5275
- impl.id,
5276
- isDraftCtx || ctx.isComponentDraft(binding.component)
5277
- );
5278
- }
5273
+ const boundMethod = ctx.interfaceMethodsOf(binding.component).find((m) => m.name === binding.method);
5274
+ if (!boundMethod) continue;
5275
+ const declared = new Set(boundMethod.guarantees ?? []);
5276
+ for (const g of step.assertsGuarantees ?? []) {
5277
+ if (!declared.has(g)) {
5278
+ ctx.addIssue(
5279
+ "warning",
5280
+ "NARRATIVE_SEMANTIC_UNBACKED",
5281
+ `${where} asserts guarantee "${g}", but capability "${step.capability}" resolves to "${binding.component}.${binding.method}", which does not list "${g}" among its L3 contract guarantees. Declare it there (and ensure its shape can deliver it), or revise the narrative.`,
5282
+ impl.id,
5283
+ isDraftCtx || ctx.isComponentDraft(binding.component)
5284
+ );
5279
5285
  }
5280
5286
  }
5281
5287
  }
@@ -5480,12 +5486,11 @@ var init_unused_detection = __esm({
5480
5486
  const invokedBySeeds = [];
5481
5487
  for (const intf of ctx.interfaces) {
5482
5488
  if (!ctx.componentMap.has(intf.component)) continue;
5483
- const comp = ctx.componentMap.get(intf.component);
5484
5489
  for (const m of intf.methods) {
5485
5490
  if (!m.invokedBy) continue;
5486
5491
  invokedBySeeds.push({ compId: intf.component, methodName: m.name });
5487
5492
  if (!ctx.isSpecInScope(intf.id)) continue;
5488
- const isDraftCtx = comp.status === "draft" || comp.status === "design" || intf.status === "draft" || intf.status === "design";
5493
+ const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
5489
5494
  if (!passesIntentFloor(m.invokedBy.caller, m.name)) {
5490
5495
  ctx.addIssue(
5491
5496
  "warning",
@@ -5510,7 +5515,7 @@ var init_unused_detection = __esm({
5510
5515
  for (const comp of ctx.components) {
5511
5516
  if (!ctx.isSpecInScope(comp.id)) continue;
5512
5517
  if (!reach.reachesComponent(comp.id)) {
5513
- const isDraftCtx = comp.status === "draft" || comp.status === "design";
5518
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
5514
5519
  ctx.addIssue(
5515
5520
  "warning",
5516
5521
  "UNUSED_COMPONENT",
@@ -5522,12 +5527,12 @@ var init_unused_detection = __esm({
5522
5527
  for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
5523
5528
  for (const m of intf.methods) {
5524
5529
  if (!reach.reachesMethod(comp.id, m.name)) {
5525
- const isDraftCtx = comp.status === "draft" || comp.status === "design" || intf.status === "draft" || intf.status === "design";
5530
+ const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
5526
5531
  ctx.addIssue(
5527
5532
  "warning",
5528
5533
  "UNUSED_METHOD",
5529
5534
  `Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
5530
- comp.id,
5535
+ intf.id,
5531
5536
  isDraftCtx
5532
5537
  );
5533
5538
  }
@@ -5638,20 +5643,20 @@ var init_durability_round_trip = __esm({
5638
5643
  });
5639
5644
 
5640
5645
  // src/core/rules/wiring/untyped-seams.ts
5641
- function unwrapPromise(typeRef) {
5642
- const m = /^promise\s*<(.+)>$/i.exec(typeRef.trim());
5643
- return (m ? m[1] : typeRef).trim();
5646
+ function namesBareType(refs) {
5647
+ return refs.some((ref) => BARE_SEAM_TYPES.has(ref.toLowerCase()));
5644
5648
  }
5645
5649
  var BARE_SEAM_TYPES, untypedSeamRule;
5646
5650
  var init_untyped_seams = __esm({
5647
5651
  "src/core/rules/wiring/untyped-seams.ts"() {
5648
5652
  "use strict";
5653
+ init_models();
5649
5654
  BARE_SEAM_TYPES = /* @__PURE__ */ new Set(["json", "any", "unknown", "object"]);
5650
5655
  untypedSeamRule = {
5651
5656
  name: "untyped-seams",
5652
- description: "Methods on a subsystem's published components (its public surface) should not take or return bare Json/any/unknown \u2014 cross-subsystem contracts are the swap seam and must be typed. Generic-dispatch portals carry per-capability types via their dispatch table instead.",
5657
+ description: "Methods on a subsystem's published components (its public surface) should not take or return bare Json/any/unknown/object, judged through each method's type references so a prose signature is judged like structured params: cross-subsystem contracts are the swap seam and must be typed. Generic-dispatch portals carry per-capability types via their dispatch table instead.",
5653
5658
  codes: [
5654
- { code: "UNTYPED_SEAM", defaultSeverity: "warning", summary: "Bare Json/any/unknown parameter or return crossing a subsystem public surface" }
5659
+ { code: "UNTYPED_SEAM", defaultSeverity: "warning", summary: "Bare Json/any/unknown/object parameter or return crossing a subsystem public surface" }
5655
5660
  ],
5656
5661
  check(ctx) {
5657
5662
  for (const sub of ctx.subsystems) {
@@ -5665,14 +5670,17 @@ var init_untyped_seams = __esm({
5665
5670
  const isDraftCtx = ctx.isComponentDraft(compId) || intf.status === "draft" || intf.status === "design";
5666
5671
  for (const m of intf.methods) {
5667
5672
  const offenders = [];
5668
- for (const p of m.params ?? []) {
5669
- if (BARE_SEAM_TYPES.has(unwrapPromise(p.type).toLowerCase())) {
5670
- offenders.push(`param "${p.name}: ${p.type}"`);
5673
+ if (m.params && m.params.length > 0) {
5674
+ for (const p of m.params) {
5675
+ if (namesBareType(extractTypeIdentifiers(p.type))) {
5676
+ offenders.push(`param "${p.name}: ${p.type}"`);
5677
+ }
5671
5678
  }
5672
- }
5673
- const ret = unwrapPromise(m.returns ?? "");
5674
- if (BARE_SEAM_TYPES.has(ret.toLowerCase())) {
5675
- offenders.push(`return "${m.returns}"`);
5679
+ if (namesBareType(extractTypeIdentifiers(m.returns ?? ""))) {
5680
+ offenders.push(`return "${m.returns}"`);
5681
+ }
5682
+ } else if (namesBareType(methodTypeRefs(m))) {
5683
+ offenders.push(`signature "${m.signature}"`);
5676
5684
  }
5677
5685
  if (offenders.length) {
5678
5686
  ctx.addIssue(
@@ -5697,15 +5705,19 @@ function componentOfImpl(ctx, implContract) {
5697
5705
  const contract = ctx.interfaceMap.get(implContract);
5698
5706
  return contract ? ctx.componentMap.get(contract.component) : void 0;
5699
5707
  }
5700
- var DATA_STEREOTYPES, CLAIM_PHRASES, proseClaimRule;
5708
+ function withoutQuotedText(text2) {
5709
+ return text2.replace(QUOTED_SPAN, " ");
5710
+ }
5711
+ var DATA_STEREOTYPES, CLAIM_PHRASES, QUOTED_SPAN, proseClaimRule;
5701
5712
  var init_prose_claims = __esm({
5702
5713
  "src/core/rules/wiring/prose-claims.ts"() {
5703
5714
  "use strict";
5704
5715
  DATA_STEREOTYPES = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Adapter", "Repository"]);
5705
- CLAIM_PHRASES = /\b(persist(s|ed|ent)?|survives?\s+(a\s+)?restart|writ(es?|ten)\s+to\s+disk|registered\s+into|durabl[ey])\b/i;
5716
+ CLAIM_PHRASES = /\b(persist(s|ed|ent|ing|ence)?|survives?\s+(a\s+)?restart|writ(es?|ten)\s+to\s+disk|registered\s+into|durabl[ey])\b/i;
5717
+ QUOTED_SPAN = /"[^"]*"|“[^”]*”|`[^`]*`|(?<![\p{L}\p{N}])['‘](?:[^'‘’]|(?<=[\p{L}\p{N}])['’](?=[\p{L}\p{N}]))*['’](?![\p{L}\p{N}])/gu;
5706
5718
  proseClaimRule = {
5707
5719
  name: "prose-claims",
5708
- description: 'Flags durability/side-effect claims that exist only in prose: a local step description or an intent paragraph claiming persistence ("persisted", "survives restart", "registered into") on a logic component whose narrative has no call/dispatch edge to any data-layer component (Store/Registry/Index/Adapter/Repository). Data-layer components are exempt \u2014 they ARE the persistence.',
5720
+ description: 'Flags durability/side-effect claims that exist only in prose: a local step description or an intent paragraph claiming persistence ("persisted", "persistence", "survives restart", "registered into") on a logic component whose narrative has no call, register or dispatch edge to any data-layer component (Store/Registry/Index/Adapter/Repository). Text inside quotes or backticks names a value or quotes a message and claims nothing. Data-layer components are exempt \u2014 they ARE the persistence.',
5709
5721
  codes: [
5710
5722
  { code: "UNREALIZED_CLAIM", defaultSeverity: "warning", summary: "Durability/side-effect claim in prose with no matching structural edge" }
5711
5723
  ],
@@ -5716,11 +5728,11 @@ var init_prose_claims = __esm({
5716
5728
  if (DATA_STEREOTYPES.has(comp.componentType)) continue;
5717
5729
  const isDraftCtx = ctx.isImplementationDraft(impl);
5718
5730
  for (const implMethod of impl.methods) {
5719
- const stepClaims = implMethod.narrative.filter((step) => step.type === "local").map((step) => ({ step, claim: CLAIM_PHRASES.exec(step.description) })).filter((c) => c.claim !== null);
5720
- const intentClaim = implMethod.intent ? CLAIM_PHRASES.exec(implMethod.intent) : null;
5731
+ const stepClaims = implMethod.narrative.filter((step) => step.type === "local").map((step) => ({ step, claim: CLAIM_PHRASES.exec(withoutQuotedText(step.description)) })).filter((c) => c.claim !== null);
5732
+ const intentClaim = implMethod.intent ? CLAIM_PHRASES.exec(withoutQuotedText(implMethod.intent)) : null;
5721
5733
  if (!stepClaims.length && !intentClaim) continue;
5722
5734
  const hasDataEdge = implMethod.narrative.some((step) => {
5723
- if (step.type !== "call" && step.type !== "dispatch") return false;
5735
+ if (step.type !== "call" && step.type !== "register" && step.type !== "dispatch") return false;
5724
5736
  if (!step.targetComponent) return false;
5725
5737
  const target = ctx.componentMap.get(step.targetComponent);
5726
5738
  if (target && DATA_STEREOTYPES.has(target.componentType)) return true;
@@ -5733,7 +5745,7 @@ var init_prose_claims = __esm({
5733
5745
  ctx.addIssue(
5734
5746
  "warning",
5735
5747
  "UNREALIZED_CLAIM",
5736
- `Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" claims "${claim[0]}" but no call/dispatch edge in this narrative reaches a Store/Registry/Index/Adapter \u2014 realize the claim as a structural edge (and durability tags), or reword the prose.`,
5748
+ `Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" claims "${claim[0]}" but no call/register/dispatch edge in this narrative reaches a Store/Registry/Index/Adapter \u2014 realize the claim as a structural edge (and durability tags), or reword the prose.`,
5737
5749
  impl.id,
5738
5750
  isDraftCtx
5739
5751
  );
@@ -5792,6 +5804,10 @@ function stepAsserts(impl, methodName, type, invariantId) {
5792
5804
  (step) => (step.assertsInvariants ?? []).some((ref) => refMatchesInvariant(ref, type, invariantId))
5793
5805
  );
5794
5806
  }
5807
+ function isEntityDraft(t, ctx) {
5808
+ const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
5809
+ return sub !== void 0 && isDraftSubsystem(sub);
5810
+ }
5795
5811
  function resolveComponentClass(t, ctx) {
5796
5812
  if (!t.componentClass) return void 0;
5797
5813
  const direct = ctx.componentMap.get(t.componentClass);
@@ -5818,6 +5834,7 @@ var init_invariant_backing = __esm({
5818
5834
  for (const t of ctx.types) {
5819
5835
  const invariants = t.invariants ?? [];
5820
5836
  if (invariants.length === 0) continue;
5837
+ const entityDraft = isEntityDraft(t, ctx);
5821
5838
  const seen = /* @__PURE__ */ new Set();
5822
5839
  for (const inv of invariants) {
5823
5840
  if (seen.has(inv.id)) {
@@ -5825,7 +5842,8 @@ var init_invariant_backing = __esm({
5825
5842
  "error",
5826
5843
  "DUPLICATE_INVARIANT_ID",
5827
5844
  `Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
5828
- t.id
5845
+ t.id,
5846
+ entityDraft
5829
5847
  );
5830
5848
  }
5831
5849
  seen.add(inv.id);
@@ -5836,7 +5854,8 @@ var init_invariant_backing = __esm({
5836
5854
  "warning",
5837
5855
  "INVARIANT_UNANCHORED",
5838
5856
  `Entity "${t.id}" declares ${invariants.length} invariant(s) but ${t.componentClass ? `its componentClass "${t.componentClass}" does not resolve to a component` : "has no componentClass"} \u2014 without an owning component there is no write path to hold the invariant against. Link the lifecycle owner via componentClass.`,
5839
- t.id
5857
+ t.id,
5858
+ entityDraft
5840
5859
  );
5841
5860
  continue;
5842
5861
  }
@@ -5850,13 +5869,13 @@ var init_invariant_backing = __esm({
5850
5869
  "INVARIANT_UNANCHORED",
5851
5870
  `Entity "${t.id}" declares ${invariants.length} invariant(s) anchored to "${comp.id}", but none of that component's contract methods declare effect: write \u2014 the validator cannot identify the write paths that must assert them. Tag the mutating methods with effect: write.`,
5852
5871
  t.id,
5853
- ctx.isComponentDraft(comp.id)
5872
+ entityDraft || ctx.isComponentDraft(comp.id)
5854
5873
  );
5855
5874
  continue;
5856
5875
  }
5857
5876
  for (const { intf, method: method2 } of writeMethods) {
5858
5877
  for (const impl of ctx.implementationsByContract.get(intf.id) ?? []) {
5859
- const isDraftCtx = ctx.isImplementationDraft(impl);
5878
+ const isDraftCtx = entityDraft || ctx.isImplementationDraft(impl);
5860
5879
  for (const inv of invariants) {
5861
5880
  if (stepAsserts(impl, method2.name, t, inv.id)) continue;
5862
5881
  ctx.addIssue(
@@ -5894,30 +5913,35 @@ var init_invariant_backing = __esm({
5894
5913
 
5895
5914
  // src/core/rules/wiring/event-topology.ts
5896
5915
  function collectEnds(ctx) {
5897
- const emitters = [];
5898
- const subscribers = [];
5916
+ const emitters = /* @__PURE__ */ new Map();
5917
+ const subscribers = /* @__PURE__ */ new Map();
5918
+ const addEnd = (ends, topic, compId, via) => {
5919
+ const key = `${compId}#${topic}`;
5920
+ const end = ends.get(key);
5921
+ if (!end) ends.set(key, { topic, compId, vias: [via] });
5922
+ else if (!end.vias.includes(via)) end.vias.push(via);
5923
+ };
5899
5924
  for (const comp of ctx.components) {
5900
5925
  for (const e of comp.emits ?? []) {
5901
- emitters.push({ topic: e.topic, compId: comp.id, via: "emits declaration" });
5926
+ addEnd(emitters, e.topic, comp.id, "emits declaration");
5902
5927
  }
5903
5928
  for (const s of comp.subscribesTo ?? []) {
5904
- subscribers.push({ topic: s.topic, compId: comp.id, via: "subscribesTo declaration" });
5929
+ addEnd(subscribers, s.topic, comp.id, "subscribesTo declaration");
5905
5930
  }
5906
5931
  }
5907
5932
  for (const intf of ctx.interfaces) {
5908
5933
  for (const method2 of intf.methods) {
5909
5934
  const ep = method2.endpoint;
5910
5935
  if (!ep || ep.transport !== "MessageBus") continue;
5911
- const end = {
5912
- topic: ep.topic,
5913
- compId: intf.component,
5914
- via: `MessageBus endpoint on ${intf.id}.${method2.name}`
5915
- };
5916
- if (ep.direction === "publish") emitters.push(end);
5917
- else subscribers.push(end);
5936
+ addEnd(
5937
+ ep.direction === "publish" ? emitters : subscribers,
5938
+ ep.topic,
5939
+ intf.component,
5940
+ `MessageBus endpoint on ${intf.id}.${method2.name}`
5941
+ );
5918
5942
  }
5919
5943
  }
5920
- return { emitters, subscribers };
5944
+ return { emitters: [...emitters.values()], subscribers: [...subscribers.values()] };
5921
5945
  }
5922
5946
  var eventTopologyRule;
5923
5947
  var init_event_topology = __esm({
@@ -5925,7 +5949,7 @@ var init_event_topology = __esm({
5925
5949
  "use strict";
5926
5950
  eventTopologyRule = {
5927
5951
  name: "event-topology",
5928
- description: "Bipartite completeness of the declared pub/sub graph: every topic a component emits (emits declarations, MessageBus publish endpoints) must have at least one subscriber (subscribesTo declarations, MessageBus subscribe endpoints), and every subscription must have at least one source. Pairing is by exact topic name. Trees that declare no event edges see nothing \u2014 the rule costs request/response systems zero noise. Warnings + lint.allow: a topic produced for (or consumed from) an EXTERNAL system is a legitimate, declarable exception.",
5952
+ description: "Bipartite completeness of the declared pub/sub graph: every topic a component emits (emits declarations, MessageBus publish endpoints) must have at least one subscriber (subscribesTo declarations, MessageBus subscribe endpoints), and every subscription must have at least one source. Pairing is by exact topic name, and a topic is reported once per component however many declarations bind it there. Trees that declare no event edges see nothing \u2014 the rule costs request/response systems zero noise. Warnings + lint.allow: a topic produced for (or consumed from) an EXTERNAL system is a legitimate, declarable exception.",
5929
5953
  codes: [
5930
5954
  { code: "UNCONSUMED_TOPIC", defaultSeverity: "warning", summary: "Topic is emitted but nothing in the tree subscribes to it" },
5931
5955
  { code: "UNSOURCED_SUBSCRIPTION", defaultSeverity: "warning", summary: "Topic is subscribed to but nothing in the tree emits it" }
@@ -5940,7 +5964,7 @@ var init_event_topology = __esm({
5940
5964
  ctx.addIssue(
5941
5965
  "warning",
5942
5966
  "UNCONSUMED_TOPIC",
5943
- `Component "${e.compId}" emits topic "${e.topic}" (${e.via}), but nothing in this tree subscribes to it \u2014 the event goes nowhere. Wire a subscriber (subscribesTo, or a MessageBus subscribe endpoint), fix the topic name, or lint.allow with the external consumer named.`,
5967
+ `Component "${e.compId}" emits topic "${e.topic}" (${e.vias.join(", ")}), but nothing in this tree subscribes to it \u2014 the event goes nowhere. Wire a subscriber (subscribesTo, or a MessageBus subscribe endpoint), fix the topic name, or lint.allow with the external consumer named.`,
5944
5968
  e.compId,
5945
5969
  ctx.isComponentDraft(e.compId)
5946
5970
  );
@@ -5950,7 +5974,7 @@ var init_event_topology = __esm({
5950
5974
  ctx.addIssue(
5951
5975
  "warning",
5952
5976
  "UNSOURCED_SUBSCRIPTION",
5953
- `Component "${s.compId}" subscribes to topic "${s.topic}" (${s.via}), but nothing in this tree emits it \u2014 the handler can never fire from inside this system. Wire the emitter (emits, or a MessageBus publish endpoint), fix the topic name, or lint.allow with the external source named.`,
5977
+ `Component "${s.compId}" subscribes to topic "${s.topic}" (${s.vias.join(", ")}), but nothing in this tree emits it \u2014 the handler can never fire from inside this system. Wire the emitter (emits, or a MessageBus publish endpoint), fix the topic name, or lint.allow with the external source named.`,
5954
5978
  s.compId,
5955
5979
  ctx.isComponentDraft(s.compId)
5956
5980
  );
@@ -5969,13 +5993,13 @@ var init_structural_conformance = __esm({
5969
5993
  quoteList = (names) => names.map((n) => `"${n}"`).join(", ");
5970
5994
  structuralConformanceRule = {
5971
5995
  name: "structural-conformance",
5972
- description: "Code\u2194spec Level 1: every source file an implementation names \u2014 its own sourcePath and each method's \u2014 must resolve to a real file inside the project root, and every L3 contract method must be realized in its own source file (the method's sourcePath, else the implementation's) at its conformance tier (declared | anchored | off; Portals default to anchored, everything else to declared; per-method `symbol` maps intent-language names to code names), and every finding code a contract method declares must appear as a string literal in that file (UNREALIZED_FINDING). A file that escapes the root, is missing or cannot be read is reported once and blocks only the methods realized in it. Findings carry the analysis grade (exact AST | pattern table | generic scan) so weaker analysis is visible. Implementations under chained subsystems (projectPath) validate standalone in their own project run and are skipped here.",
5996
+ description: "Code\u2194spec Level 1: every source file an implementation names \u2014 its own sourcePath and each method's \u2014 must resolve to a real file inside the project root, and every L3 contract method must be realized in its own source file (the method's sourcePath, else the implementation's) at its conformance tier (declared | anchored | off; Portals default to anchored, everything else to declared; per-method `symbol` maps intent-language names to code names), and every finding code a contract method declares must be anchored in that file \u2014 as a string literal or, at exact grade, a property-access name (UNREALIZED_FINDING). A file that escapes the root, is missing or cannot be read is reported once and blocks only the methods realized in it. Findings carry the analysis grade (exact AST | pattern table | generic scan) so weaker analysis is visible. Implementations under chained subsystems (projectPath) validate standalone in their own project run and are skipped here.",
5973
5997
  codes: [
5974
5998
  { code: "MISSING_SOURCE_PATH", defaultSeverity: "warning", summary: "An implementation names no source file at all, or a contract method is left without one \u2014 structural conformance cannot link it to code" },
5975
5999
  { code: "MISSING_SOURCE_FILE", defaultSeverity: "error", summary: "A source file an implementation or one of its methods names does not resolve to a file on disk" },
5976
6000
  { code: "SOURCE_PATH_ESCAPES_ROOT", defaultSeverity: "error", summary: "A source file an implementation or one of its methods names is absolute or escapes the project root (containment refusal)" },
5977
6001
  { code: "UNREALIZED_METHOD", defaultSeverity: "warning", summary: "An L3 contract method has no anchor in its own source file (the method's sourcePath, else the implementation's) at the required conformance tier" },
5978
- { code: "UNREALIZED_FINDING", defaultSeverity: "warning", summary: "A finding code a contract method declares does not appear as a string literal in the method's source file" },
6002
+ { code: "UNREALIZED_FINDING", defaultSeverity: "warning", summary: "A finding code a contract method declares appears in the method's source file neither as a string literal nor as a property-access name (below exact grade, any identifier counts)" },
5979
6003
  { code: "CONFORMANCE_ANALYSIS_SKIPPED", defaultSeverity: "warning", summary: "A source file an implementation or one of its methods names could not be analyzed (binary/unreadable) \u2014 realization of the methods in it was not checked" },
5980
6004
  { code: "CONFORMANCE_DEGRADED", defaultSeverity: "warning", summary: "TypeScript/JavaScript files were analyzed below exact grade (compiler not resolvable) \u2014 dependency conformance skips them" }
5981
6005
  ],
@@ -5985,7 +6009,7 @@ var init_structural_conformance = __esm({
5985
6009
  lookups.set(pathKey(facts.path), {
5986
6010
  declared: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames]),
5987
6011
  anchored: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames, ...facts.anchoredNames]),
5988
- literals: new Set(facts.anchoredNames),
6012
+ findingAnchors: new Set(facts.anchoredNames),
5989
6013
  facts
5990
6014
  });
5991
6015
  }
@@ -6105,11 +6129,11 @@ var init_structural_conformance = __esm({
6105
6129
  if (!checked) continue;
6106
6130
  const { file, lookup } = checked;
6107
6131
  for (const finding of method2.findings) {
6108
- if (lookup.literals.has(finding.code)) continue;
6132
+ if (lookup.findingAnchors.has(finding.code)) continue;
6109
6133
  ctx.addIssue(
6110
6134
  "warning",
6111
6135
  "UNREALIZED_FINDING",
6112
- `Method "${method2.name}" of contract "${impl.contract}" declares finding "${finding.code}", but "${file}" has no string literal "${finding.code}" (analysis grade: ${lookup.facts.analysisGrade}) \u2014 report the finding under its declared code, or remove the declaration.`,
6136
+ `Method "${method2.name}" of contract "${impl.contract}" declares finding "${finding.code}", but "${file}" has no string literal or property-access name "${finding.code}" (analysis grade: ${lookup.facts.analysisGrade}) \u2014 report the finding under its declared code, or remove the declaration.`,
6113
6137
  impl.id,
6114
6138
  draft
6115
6139
  );
@@ -6225,12 +6249,18 @@ var init_hidden_state = __esm({
6225
6249
  check(ctx) {
6226
6250
  const byPath = /* @__PURE__ */ new Map();
6227
6251
  for (const impl of ctx.implementations) {
6228
- if (impl.conformance === "off") continue;
6229
6252
  const contract = ctx.interfaceMap.get(impl.contract);
6230
6253
  const component = contract ? ctx.componentMap.get(contract.component) : void 0;
6231
6254
  if (!component) continue;
6232
6255
  if (ctx.isInChainedSubproject(component.subsystem)) continue;
6233
- for (const file of implementationSourceFiles(impl)) {
6256
+ const trusted = [];
6257
+ if (impl.sourcePath && impl.conformance !== "off") trusted.push(impl.sourcePath);
6258
+ for (const method2 of impl.methods) {
6259
+ if ((method2.conformance ?? impl.conformance) === "off") continue;
6260
+ const file = methodSourceFile(method2, impl.sourcePath);
6261
+ if (file) trusted.push(file);
6262
+ }
6263
+ for (const file of trusted) {
6234
6264
  const key = pathKey(file);
6235
6265
  const list2 = byPath.get(key) ?? [];
6236
6266
  if (list2.some((m) => m.impl === impl)) continue;
@@ -6381,7 +6411,7 @@ var init_dependency_conformance = __esm({
6381
6411
  const fromFiles = filesByComponent.get(component.id);
6382
6412
  if (!fromFiles) continue;
6383
6413
  const isMountingDeclarer = component.componentType === "Portal" || component.componentType === "Observer";
6384
- const declaredTargets = [...component.dependsOn, ...component.owns ?? []];
6414
+ const declaredTargets = [.../* @__PURE__ */ new Set([...component.dependsOn, ...component.owns ?? []])];
6385
6415
  for (const targetId of declaredTargets) {
6386
6416
  const target = ctx.componentMap.get(targetId);
6387
6417
  if (!target) continue;
@@ -6402,11 +6432,14 @@ var init_dependency_conformance = __esm({
6402
6432
  }
6403
6433
  const impls = implsByComponent.get(component.id) ?? [];
6404
6434
  const anchor = impls[0];
6435
+ const declaredDependsOn = component.dependsOn.includes(targetId);
6436
+ const declaredOwns = (component.owns ?? []).includes(targetId);
6437
+ const relation = declaredDependsOn && declaredOwns ? "dependsOn and owns" : declaredDependsOn ? "dependsOn" : "owns";
6405
6438
  const draft = ctx.isComponentDraft(component.id) || ctx.isComponentDraft(targetId) || impls.some((i) => ctx.isImplementationDraft(i));
6406
6439
  ctx.addIssue(
6407
6440
  "warning",
6408
6441
  "UNREALIZED_DEPENDENCY",
6409
- `Component "${component.id}" declares ${component.dependsOn.includes(targetId) ? "dependsOn" : "owns"} "${targetId}", but no runtime import connects their source files (${[...fromFiles].join(", ")} \u219B ${target.subsystem !== component.subsystem ? `subsystem ${target.subsystem}` : [...filesByComponent.get(targetId) ?? []].join(", ")}) \u2014 either the collaboration is wired indirectly (DI) or the declared edge is stale.`,
6442
+ `Component "${component.id}" declares ${relation} "${targetId}", but no runtime import connects their source files (${[...fromFiles].join(", ")} \u219B ${target.subsystem !== component.subsystem ? `subsystem ${target.subsystem}` : [...filesByComponent.get(targetId) ?? []].join(", ")}) \u2014 either the collaboration is wired indirectly (DI) or the declared edge is stale.`,
6410
6443
  anchor?.id ?? component.id,
6411
6444
  draft
6412
6445
  );
@@ -6438,28 +6471,28 @@ var init_integration_conformance = __esm({
6438
6471
  const filesByComponent = /* @__PURE__ */ new Map();
6439
6472
  const filesBySubsystem = /* @__PURE__ */ new Map();
6440
6473
  const implsByComponent = /* @__PURE__ */ new Map();
6474
+ const adopted = /* @__PURE__ */ new Set();
6475
+ const exists = (p) => {
6476
+ const status2 = factsByPath.get(p)?.status;
6477
+ return status2 === "analyzed" || status2 === "unreadable";
6478
+ };
6441
6479
  for (const impl of ctx.implementations) {
6442
6480
  const contract = ctx.interfaceMap.get(impl.contract);
6443
6481
  const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
6444
- if (!comp) continue;
6482
+ if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
6445
6483
  if (!implsByComponent.has(comp.id)) implsByComponent.set(comp.id, []);
6446
6484
  implsByComponent.get(comp.id).push(impl);
6447
6485
  for (const file of implementationSourceFiles(impl)) {
6448
6486
  const p = pathKey(file);
6487
+ if (!exists(p)) continue;
6449
6488
  if (!filesByComponent.has(comp.id)) filesByComponent.set(comp.id, /* @__PURE__ */ new Set());
6450
6489
  filesByComponent.get(comp.id).add(p);
6451
6490
  if (!filesBySubsystem.has(comp.subsystem)) filesBySubsystem.set(comp.subsystem, /* @__PURE__ */ new Set());
6452
6491
  filesBySubsystem.get(comp.subsystem).add(p);
6453
6492
  }
6493
+ if (impl.simPath) adopted.add(comp.subsystem);
6454
6494
  }
6455
6495
  const isTechBoundary = (compId) => (implsByComponent.get(compId) ?? []).some((i) => (i.technologies ?? []).length > 0);
6456
- const adopted = /* @__PURE__ */ new Set();
6457
- for (const impl of ctx.implementations) {
6458
- if (!impl.simPath) continue;
6459
- const contract = ctx.interfaceMap.get(impl.contract);
6460
- const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
6461
- if (comp) adopted.add(comp.subsystem);
6462
- }
6463
6496
  const reachFrom = (start) => {
6464
6497
  const seen = /* @__PURE__ */ new Set([start]);
6465
6498
  const stack = [start];
@@ -6656,8 +6689,8 @@ var init_coupling_health = __esm({
6656
6689
  );
6657
6690
  }
6658
6691
  }
6659
- const threshold = ctx.rules?.complexity?.maxComponentDependencies ?? DEFAULT_GOD_COMPONENT_THRESHOLD;
6660
6692
  for (const comp of ctx.components) {
6693
+ const threshold = ctx.complexityConfigFor(comp.subsystem)?.maxComponentDependencies ?? DEFAULT_GOD_COMPONENT_THRESHOLD;
6661
6694
  if (comp.dependsOn.length > threshold) {
6662
6695
  ctx.addIssue(
6663
6696
  "warning",
@@ -6785,7 +6818,7 @@ var init_target_language = __esm({
6785
6818
  if (!lang) continue;
6786
6819
  const gaps = gapsFor(lang);
6787
6820
  if (Object.keys(gaps).length === 0) continue;
6788
- const isDraftCtx = impl.status === "draft" || impl.status === "design" || contract.status === "draft" || contract.status === "design" || ctx.isComponentDraft(contract.component);
6821
+ const isDraftCtx = ctx.isImplementationDraft(impl);
6789
6822
  for (const implMethod of impl.methods) {
6790
6823
  for (const step of implMethod.narrative) {
6791
6824
  const constructs = [
@@ -6887,7 +6920,7 @@ var init_technology_boundaries = __esm({
6887
6920
  "TECH_ON_LOGIC_COMPONENT",
6888
6921
  `Implementation "${impl.id}" binds technology (${impl.technologies.join(", ")}) on component "${comp.id}" (${comp.componentType}). Technology belongs behind a data-layer seam \u2014 extract an Adapter (or Store/Registry/Index) behind an intent interface and let this component depend on that.`,
6889
6922
  impl.id,
6890
- ctx.isComponentDraft(comp.id)
6923
+ ctx.isImplementationDraft(impl)
6891
6924
  );
6892
6925
  }
6893
6926
  const compSet = ownsClosure(ownershipRoot(comp.id));
@@ -6925,7 +6958,7 @@ var init_technology_boundaries = __esm({
6925
6958
  "VENDOR_NAME_IN_CONTRACT",
6926
6959
  `Interface "${intf.id}" exposes technology "${home.label}" in contract identifiers (method${offending.length > 1 ? "s" : ""}: ${offending.join(", ")}). Name the intent, not the vendor \u2014 the technology is owned by component ${ownersDesc(home)} and must stay swappable behind this contract.`,
6927
6960
  intf.id,
6928
- ctx.isComponentDraft(intf.component)
6961
+ ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design"
6929
6962
  );
6930
6963
  }
6931
6964
  }
@@ -6956,7 +6989,8 @@ var init_technology_boundaries = __esm({
6956
6989
  "warning",
6957
6990
  "TECH_LEAKAGE",
6958
6991
  `Subsystem "${sub.id}" references "${home.label}" outside the technology's owning boundary (component ${ownersDesc(home)}).`,
6959
- sub.id
6992
+ sub.id,
6993
+ isDraftSubsystem(sub)
6960
6994
  );
6961
6995
  }
6962
6996
  }
@@ -6969,7 +7003,7 @@ var init_technology_boundaries = __esm({
6969
7003
  "TECH_LEAKAGE",
6970
7004
  `Interface "${intf.id}" describes "${home.label}" outside its owning boundary (component ${ownersDesc(home)}) \u2014 consumers must not know backend specifics.`,
6971
7005
  intf.id,
6972
- ctx.isComponentDraft(intf.component)
7006
+ ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design"
6973
7007
  );
6974
7008
  }
6975
7009
  }
@@ -6989,7 +7023,8 @@ var init_technology_boundaries = __esm({
6989
7023
  "warning",
6990
7024
  "TECH_LEAKAGE",
6991
7025
  `Implementation "${impl.id}" references "${home.label}" outside its owning boundary \u2014 call the intent interface of component ${ownersDesc(home)} instead of naming its technology.`,
6992
- impl.id
7026
+ impl.id,
7027
+ ctx.isImplementationDraft(impl)
6993
7028
  );
6994
7029
  }
6995
7030
  }
@@ -7052,6 +7087,7 @@ var casingPatterns, isAllUppercase, namingRule;
7052
7087
  var init_naming_conventions = __esm({
7053
7088
  "src/core/rules/heuristic/naming-conventions.ts"() {
7054
7089
  "use strict";
7090
+ init_models();
7055
7091
  casingPatterns = {
7056
7092
  camelCase: /^[a-z][a-zA-Z0-9]*$/,
7057
7093
  PascalCase: /^[A-Z][a-zA-Z0-9]*$/,
@@ -7072,9 +7108,10 @@ var init_naming_conventions = __esm({
7072
7108
  for (const sub of ctx.subsystems) {
7073
7109
  const namingConfig = ctx.namingConfigFor(sub.id);
7074
7110
  if (!namingConfig?.subsystems) continue;
7111
+ const isDraft = isDraftSubsystem(sub);
7075
7112
  const baseId = getBaseId(sub.id);
7076
- checkNamedValue(ctx, baseId, namingConfig.subsystems, `Subsystem ID "${sub.id}" does not match naming convention "${namingConfig.subsystems}".`, sub.id);
7077
- checkNamedValue(ctx, sub.name, namingConfig.subsystems, `Subsystem Name "${sub.name}" does not match naming convention "${namingConfig.subsystems}".`, sub.id);
7113
+ checkNamedValue(ctx, baseId, namingConfig.subsystems, `Subsystem ID "${sub.id}" does not match naming convention "${namingConfig.subsystems}".`, sub.id, isDraft);
7114
+ checkNamedValue(ctx, sub.name, namingConfig.subsystems, `Subsystem Name "${sub.name}" does not match naming convention "${namingConfig.subsystems}".`, sub.id, isDraft);
7078
7115
  }
7079
7116
  for (const comp of ctx.components) {
7080
7117
  const namingConfig = ctx.namingConfigFor(comp.subsystem);
@@ -7159,7 +7196,7 @@ var init_naming_conventions = __esm({
7159
7196
  const intf = ctx.interfaceMap.get(impl.contract);
7160
7197
  const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
7161
7198
  const namingConfig = ctx.namingConfigFor(comp?.subsystem);
7162
- const isDraft = impl.status === "draft" || impl.status === "design";
7199
+ const isDraft = ctx.isImplementationDraft(impl);
7163
7200
  if (namingConfig?.methods) {
7164
7201
  for (const m of impl.methods) {
7165
7202
  checkNamedValue(ctx, m.name, namingConfig.methods, `Implementation method "${m.name}" on "${impl.id}" does not match naming convention "${namingConfig.methods}".`, impl.id, isDraft);
@@ -7223,6 +7260,7 @@ var complexityRule;
7223
7260
  var init_complexity_and_metadata = __esm({
7224
7261
  "src/core/rules/heuristic/complexity-and-metadata.ts"() {
7225
7262
  "use strict";
7263
+ init_models();
7226
7264
  complexityRule = {
7227
7265
  name: "complexity-and-metadata",
7228
7266
  description: "Enforces metadata documentation completeness (checking if descriptions are missing or too short) and structural complexity caps (limits on methods per interface, dependencies per component, and narrative steps per method) configured globally or in architectural profiles.",
@@ -7238,7 +7276,8 @@ var init_complexity_and_metadata = __esm({
7238
7276
  check(ctx) {
7239
7277
  for (const sub of ctx.subsystems) {
7240
7278
  const docConfig = ctx.documentationConfigFor(sub.id);
7241
- checkDescription(ctx, sub.description, docConfig?.requireDescriptions ?? false, docConfig?.minDescriptionLength, sub.id, "Subsystem", false);
7279
+ const isSubDraft = isDraftSubsystem(sub);
7280
+ checkDescription(ctx, sub.description, docConfig?.requireDescriptions ?? false, docConfig?.minDescriptionLength, sub.id, "Subsystem", isSubDraft);
7242
7281
  const complexityConfig = ctx.complexityConfigFor(sub.id);
7243
7282
  const directComponents = ctx.components.filter((c) => c.subsystem === sub.id).length;
7244
7283
  if (complexityConfig?.maxSubsystemComponents !== void 0 && directComponents > complexityConfig.maxSubsystemComponents) {
@@ -7246,7 +7285,8 @@ var init_complexity_and_metadata = __esm({
7246
7285
  "warning",
7247
7286
  "EXCESSIVE_SUBSYSTEM_COMPONENTS",
7248
7287
  `Subsystem "${sub.id}" has ${directComponents} direct components, exceeding the configured limit of ${complexityConfig.maxSubsystemComponents}.`,
7249
- sub.id
7288
+ sub.id,
7289
+ isSubDraft
7250
7290
  );
7251
7291
  }
7252
7292
  }
@@ -7322,7 +7362,7 @@ var init_complexity_and_metadata = __esm({
7322
7362
  const intf = ctx.interfaceMap.get(impl.contract);
7323
7363
  const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
7324
7364
  const complexityConfig = ctx.complexityConfigFor(comp?.subsystem);
7325
- const isDraft = impl.status === "draft" || impl.status === "design";
7365
+ const isDraft = ctx.isImplementationDraft(impl);
7326
7366
  if (complexityConfig?.maxNarrativeSteps !== void 0) {
7327
7367
  for (const m of impl.methods) {
7328
7368
  if (m.narrative.length > complexityConfig.maxNarrativeSteps) {
@@ -14029,7 +14069,7 @@ function buildRuleContext(opts) {
14029
14069
  return rules.sddRuleSeverity[ruleCode];
14030
14070
  }
14031
14071
  const sub = subsystemId ? subsystems.find((s) => s.id === subsystemId) : void 0;
14032
- const profileSeverity = extensions.profiles[sub?.profile ?? projectType]?.rules?.sddRuleSeverity?.[ruleCode];
14072
+ const profileSeverity = extensions.profiles[sub?.profile || projectType]?.rules?.sddRuleSeverity?.[ruleCode];
14033
14073
  if (profileSeverity) {
14034
14074
  return profileSeverity;
14035
14075
  }