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

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.17";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  SUPPORTED_ALIASES = ["wai"];
71
71
  SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
@@ -382,10 +382,10 @@ ${GUIDE_MARKER_END}
382
382
  fs4.writeFileSync(filePath, newContent, "utf-8");
383
383
  }
384
384
  function stripGuideSection(content) {
385
- const start = content.indexOf(GUIDE_MARKER_START);
385
+ const start2 = content.indexOf(GUIDE_MARKER_START);
386
386
  const end = content.indexOf(GUIDE_MARKER_END);
387
- if (start === -1 || end === -1) return content;
388
- return content.slice(0, start) + content.slice(end + GUIDE_MARKER_END.length);
387
+ if (start2 === -1 || end === -1) return content;
388
+ return content.slice(0, start2) + content.slice(end + GUIDE_MARKER_END.length);
389
389
  }
390
390
  function writeRootGuideDelegator(projectRoot2, targetType) {
391
391
  if (targetType === "claude") {
@@ -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.
@@ -1118,7 +1118,7 @@ function defaultNarrativeDetail(component) {
1118
1118
  if (componentType === "Portal" || componentType === "Observer" || componentType === "Adapter") {
1119
1119
  return "calls-only";
1120
1120
  }
1121
- if (componentType === "Store" || componentType === "Index" || componentType === "Registry") {
1121
+ if (componentType === "Store" || componentType === "Index" || componentType === "Query" || componentType === "Registry") {
1122
1122
  return "intent";
1123
1123
  }
1124
1124
  return "full";
@@ -1151,7 +1151,7 @@ function implementationSourceFiles(impl) {
1151
1151
  for (const method2 of impl.methods ?? []) add(method2.sourcePath);
1152
1152
  return files;
1153
1153
  }
1154
- var import_zod7, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, ComponentSpecSchema, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, FindingDeclarationSchema, MethodSignatureSchema, INTENT_FLOOR_MIN_CHARS, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
1154
+ var import_zod7, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, DependencyClassSchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, ComponentSpecSchema, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, FindingDeclarationSchema, MethodSignatureSchema, INTENT_FLOOR_MIN_CHARS, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
1155
1155
  var init_specs = __esm({
1156
1156
  "src/models/specs.ts"() {
1157
1157
  "use strict";
@@ -1317,21 +1317,27 @@ var init_specs = __esm({
1317
1317
  "Actor",
1318
1318
  "Store",
1319
1319
  "Index",
1320
+ "Query",
1321
+ // Repository member for computed reads over its Store
1320
1322
  "Registry",
1321
1323
  "Adapter",
1322
1324
  "Observer",
1323
- "Specialist",
1324
1325
  "View",
1325
1326
  // Pure presenter/UI component
1326
1327
  // Patterns (compositions of blocks)
1327
1328
  "Repository",
1328
- "Gateway",
1329
1329
  "FeatureComponent",
1330
1330
  // Logic Hook + View Presenter pattern
1331
- "RouterComponent"
1331
+ "RouterComponent",
1332
1332
  // Switch/routing component pattern
1333
+ // Retired stereotypes: still parsed so a tree using them loads and the
1334
+ // rules can report them (STEREOTYPE_RETIRED), never authored new
1335
+ "Specialist",
1336
+ // logic is an Orchestrator with a dependencyClass
1337
+ "Gateway"
1338
+ // a gateway is a Portal with the gateway variant
1333
1339
  ]);
1334
- PATTERN_TYPES = /* @__PURE__ */ new Set(["Repository", "Gateway", "FeatureComponent", "RouterComponent"]);
1340
+ PATTERN_TYPES = /* @__PURE__ */ new Set(["Repository", "FeatureComponent", "RouterComponent"]);
1335
1341
  PortalTypeSchema = import_zod7.z.enum(["HTTP_API", "gRPC", "GraphQL", "MessageBus", "CLI", "NamedPipe", "IPC", "Custom"]);
1336
1342
  DispatchBindingSchema = import_zod7.z.object({
1337
1343
  /** Capability name exactly as dispatched at runtime (e.g. "shadow_module.get"). */
@@ -1344,6 +1350,7 @@ var init_specs = __esm({
1344
1350
  description: import_zod7.z.string().optional()
1345
1351
  });
1346
1352
  DurabilitySchema = import_zod7.z.enum(["ram-projection", "durable", "read-through", "cache"]);
1353
+ DependencyClassSchema = import_zod7.z.enum(["pure", "read"]);
1347
1354
  PatternRefSchema = import_zod7.z.object({
1348
1355
  id: import_zod7.z.string(),
1349
1356
  version: import_zod7.z.string().optional()
@@ -1398,6 +1405,8 @@ var init_specs = __esm({
1398
1405
  dispatch: import_zod7.z.array(DispatchBindingSchema).optional(),
1399
1406
  /** Store-only: whether held state survives restart (see DurabilitySchema). */
1400
1407
  durability: DurabilitySchema.optional(),
1408
+ /** Orchestrator-only: what the logic may depend on; unset means a workflow (see DependencyClassSchema). */
1409
+ dependencyClass: DependencyClassSchema.optional(),
1401
1410
  /** Topics this component publishes to (see EventBindingSchema). */
1402
1411
  emits: import_zod7.z.array(EventBindingSchema).optional(),
1403
1412
  /** Topics this component consumes (see EventBindingSchema) — typical on Observers. */
@@ -1488,9 +1497,10 @@ var init_specs = __esm({
1488
1497
  }).optional(),
1489
1498
  /**
1490
1499
  * The finding codes this method can report, each with its default severity
1491
- * and summary (see FindingDeclarationSchema). Each declared code must appear
1492
- * as a string literal in the method's source file (UNREALIZED_FINDING). A
1493
- * code is declared once per method.
1500
+ * and summary (see FindingDeclarationSchema). Each declared code must be
1501
+ * anchored in the method's source file as a string literal or a
1502
+ * property-access name (UNREALIZED_FINDING). A code is declared once per
1503
+ * method.
1494
1504
  */
1495
1505
  findings: import_zod7.z.array(FindingDeclarationSchema).superRefine((findings, ctx) => {
1496
1506
  const seen = /* @__PURE__ */ new Set();
@@ -2822,15 +2832,13 @@ var init_hierarchy_integrity = __esm({
2822
2832
  }
2823
2833
  }
2824
2834
  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
2835
  if (!ctx.interfaceIds.has(impl.contract)) {
2828
2836
  ctx.addIssue(
2829
2837
  "error",
2830
2838
  "INVALID_INTERFACE_REFERENCE",
2831
2839
  `Implementation "${impl.id}" references non-existent interface contract "${impl.contract}".`,
2832
2840
  impl.id,
2833
- isDraftCtx
2841
+ ctx.isImplementationDraft(impl)
2834
2842
  );
2835
2843
  }
2836
2844
  }
@@ -2946,11 +2954,7 @@ var init_type_references2 = __esm({
2946
2954
  for (const field of t.fields) {
2947
2955
  const refs = fieldTypeRefs(t, field.type);
2948
2956
  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) {
2957
+ if (!ctx.isTypeResolved(ref, /* @__PURE__ */ new Set())) {
2954
2958
  ctx.addIssue(
2955
2959
  "error",
2956
2960
  "UNDEFINED_TYPE_REFERENCE",
@@ -3182,14 +3186,27 @@ var init_contract_symmetry_and_narratives = __esm({
3182
3186
  );
3183
3187
  continue;
3184
3188
  }
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);
3189
+ const target = step.targetComponent;
3190
+ const isCrossTreeForm = ctx.isExternalNamespaceRef(target);
3191
+ const caller = ctx.componentMap.get(contract.component);
3192
+ const fromSubsystem = caller?.subsystem;
3193
+ const isCollapsedForm = !isCrossTreeForm && fromSubsystem !== void 0 && ctx.isCollapsedCrossTreeRef(target, fromSubsystem);
3194
+ const reportUndeclared = (reaches, surfaceResolved) => {
3195
+ if (!caller || target === caller.id || caller.dependsOn.includes(target) || caller.owns.includes(target)) return;
3196
+ ctx.addIssue(
3197
+ "error",
3198
+ "UNDECLARED_DEPENDENCY_CALL",
3199
+ `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.`,
3200
+ impl.id,
3201
+ isDraftCtx || ctx.isComponentDraft(caller.id),
3202
+ surfaceResolved
3203
+ );
3204
+ };
3188
3205
  if (step.type === "dispatch") {
3189
- const dispatchTarget = ctx.componentMap.get(step.targetComponent);
3206
+ const dispatchTarget = ctx.componentMap.get(target);
3190
3207
  if (!dispatchTarget) {
3191
3208
  if (isCrossTreeForm || isCollapsedForm) {
3192
- const resolved = ctx.resolveSurfaceRef(step.targetComponent, fromSubsystem);
3209
+ const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
3193
3210
  if (resolved.kind === "ambiguous") {
3194
3211
  ctx.addIssue(
3195
3212
  "error",
@@ -3197,7 +3214,7 @@ var init_contract_symmetry_and_narratives = __esm({
3197
3214
  ambiguityMessage(
3198
3215
  resolved,
3199
3216
  `Method "${implMethod.name}" in implementation "${impl.id}" dispatches (step ${step.stepNumber}) through`,
3200
- step.targetComponent
3217
+ target
3201
3218
  ),
3202
3219
  impl.id,
3203
3220
  isDraftCtx
@@ -3205,11 +3222,12 @@ var init_contract_symmetry_and_narratives = __esm({
3205
3222
  continue;
3206
3223
  }
3207
3224
  if (resolved.kind === "resolved") {
3225
+ reportUndeclared("dispatches through", true);
3208
3226
  if (step.capability && !(resolved.entry.dispatch ?? []).some((b) => b.capability === step.capability)) {
3209
3227
  ctx.addIssue(
3210
3228
  "error",
3211
3229
  "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}".`,
3230
+ `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
3231
  impl.id,
3214
3232
  isDraftCtx,
3215
3233
  true
@@ -3222,7 +3240,7 @@ var init_contract_symmetry_and_narratives = __esm({
3222
3240
  ctx.addIssue(
3223
3241
  "warning",
3224
3242
  "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.`,
3243
+ `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
3244
  impl.id,
3227
3245
  isDraftCtx
3228
3246
  );
@@ -3230,23 +3248,14 @@ var init_contract_symmetry_and_narratives = __esm({
3230
3248
  ctx.addIssue(
3231
3249
  "error",
3232
3250
  "INVALID_TARGET_COMPONENT_REFERENCE",
3233
- `Method "${implMethod.name}" in implementation "${impl.id}" dispatches through component "${step.targetComponent}" which does not exist (step ${step.stepNumber}).`,
3251
+ `Method "${implMethod.name}" in implementation "${impl.id}" dispatches through component "${target}" which does not exist (step ${step.stepNumber}).`,
3234
3252
  impl.id,
3235
3253
  isDraftCtx
3236
3254
  );
3237
3255
  }
3238
3256
  continue;
3239
3257
  }
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
- }
3258
+ reportUndeclared("dispatches through");
3250
3259
  continue;
3251
3260
  }
3252
3261
  const verb = step.type === "register" ? "registers callback" : "calls";
@@ -3260,10 +3269,10 @@ var init_contract_symmetry_and_narratives = __esm({
3260
3269
  );
3261
3270
  continue;
3262
3271
  }
3263
- const targetComp = ctx.componentMap.get(step.targetComponent);
3272
+ const targetComp = ctx.componentMap.get(target);
3264
3273
  if (!targetComp) {
3265
3274
  if (isCrossTreeForm || isCollapsedForm) {
3266
- const resolved = ctx.resolveSurfaceRef(step.targetComponent, fromSubsystem);
3275
+ const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
3267
3276
  if (resolved.kind === "ambiguous") {
3268
3277
  ctx.addIssue(
3269
3278
  "error",
@@ -3271,7 +3280,7 @@ var init_contract_symmetry_and_narratives = __esm({
3271
3280
  ambiguityMessage(
3272
3281
  resolved,
3273
3282
  `Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" (step ${step.stepNumber}) on`,
3274
- step.targetComponent
3283
+ target
3275
3284
  ),
3276
3285
  impl.id,
3277
3286
  isDraftCtx
@@ -3279,12 +3288,13 @@ var init_contract_symmetry_and_narratives = __esm({
3279
3288
  continue;
3280
3289
  }
3281
3290
  if (resolved.kind === "resolved") {
3291
+ reportUndeclared(verb, true);
3282
3292
  const surfaceMethod = resolved.entry.methods.find((m) => m.name === step.targetMethod);
3283
3293
  if (!surfaceMethod) {
3284
3294
  ctx.addIssue(
3285
3295
  "error",
3286
3296
  "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}".`,
3297
+ `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
3298
  impl.id,
3289
3299
  isDraftCtx,
3290
3300
  true
@@ -3311,7 +3321,7 @@ var init_contract_symmetry_and_narratives = __esm({
3311
3321
  ctx.addIssue(
3312
3322
  "warning",
3313
3323
  "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.`,
3324
+ `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
3325
  impl.id,
3316
3326
  isDraftCtx
3317
3327
  );
@@ -3319,26 +3329,15 @@ var init_contract_symmetry_and_narratives = __esm({
3319
3329
  ctx.addIssue(
3320
3330
  "error",
3321
3331
  "INVALID_TARGET_COMPONENT_REFERENCE",
3322
- `Method "${implMethod.name}" in implementation "${impl.id}" ${verb} component "${step.targetComponent}" which does not exist (step ${step.stepNumber}).`,
3332
+ `Method "${implMethod.name}" in implementation "${impl.id}" ${verb} component "${target}" which does not exist (step ${step.stepNumber}).`,
3323
3333
  impl.id,
3324
3334
  isDraftCtx
3325
3335
  );
3326
3336
  }
3327
3337
  continue;
3328
3338
  }
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) ?? [];
3339
+ reportUndeclared(verb);
3340
+ const targetInterfaces = ctx.interfacesByComponent.get(target) ?? [];
3342
3341
  let targetMethodSpec;
3343
3342
  for (const targetIntf of targetInterfaces) {
3344
3343
  const found = targetIntf.methods.find((m) => m.name === step.targetMethod);
@@ -3351,9 +3350,9 @@ var init_contract_symmetry_and_narratives = __esm({
3351
3350
  ctx.addIssue(
3352
3351
  "error",
3353
3352
  "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}).`,
3353
+ `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
3354
  impl.id,
3356
- isDraftCtx || ctx.isComponentDraft(step.targetComponent)
3355
+ isDraftCtx || ctx.isComponentDraft(target)
3357
3356
  );
3358
3357
  } else {
3359
3358
  const declared = new Set(targetMethodSpec.guarantees ?? []);
@@ -3363,9 +3362,9 @@ var init_contract_symmetry_and_narratives = __esm({
3363
3362
  ctx.addIssue(
3364
3363
  "warning",
3365
3364
  "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.`,
3365
+ `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
3366
  impl.id,
3368
- isDraftCtx || ctx.isComponentDraft(step.targetComponent)
3367
+ isDraftCtx || ctx.isComponentDraft(target)
3369
3368
  );
3370
3369
  }
3371
3370
  }
@@ -3695,43 +3694,47 @@ var init_narrative_flow = __esm({
3695
3694
  });
3696
3695
 
3697
3696
  // 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
- }
3697
+ function completedStepGraph(steps) {
3698
+ const numbers = steps.flatMap((s) => [
3699
+ s.stepNumber,
3700
+ s.onTrueStep,
3701
+ s.onFalseStep,
3702
+ s.defaultStep,
3703
+ s.endStep,
3704
+ s.finallyStep,
3705
+ s.toStep,
3706
+ ...(s.cases ?? []).map((c) => c.step),
3707
+ ...(s.catches ?? []).map((c) => c.step),
3708
+ ...(s.branches ?? []).map((b) => b.step)
3709
+ ]).filter((n) => n !== void 0);
3710
+ const completion = {
3711
+ stepNumber: Math.max(0, ...numbers) + 1,
3712
+ description: "The method completes by falling off the end of its narrative",
3713
+ type: "return"
3714
+ };
3715
+ return stepGraph({ narrative: [...steps, completion] });
3717
3716
  }
3718
3717
  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);
3718
+ if (steps.length === 0) return false;
3719
+ const graph = completedStepGraph(steps);
3720
+ const completes = /* @__PURE__ */ new Set();
3721
+ const canComplete = (n) => {
3722
+ const s = graph.stepAt(n);
3723
+ if (s.type === "return" || s.type === "throw") return true;
3724
+ const arms = s.type === "parallel" ? (s.branches ?? []).map((b) => b.step).filter((e) => graph.stepAt(e) !== void 0) : [];
3725
+ if (arms.length) return arms.every((e) => completes.has(e));
3726
+ return graph.successorsOf(n).some((t) => completes.has(t));
3727
+ };
3728
+ const order = [...graph.stepNumbers].reverse();
3729
+ for (let grew = true; grew; ) {
3730
+ grew = false;
3731
+ for (const n of order) {
3732
+ if (n === target || completes.has(n) || !canComplete(n)) continue;
3733
+ completes.add(n);
3734
+ grew = true;
3732
3735
  }
3733
3736
  }
3734
- return true;
3737
+ return !completes.has(graph.stepNumbers[0]);
3735
3738
  }
3736
3739
  function stronglyConnected(nums, successorsOf) {
3737
3740
  const index = /* @__PURE__ */ new Map();
@@ -3783,11 +3786,12 @@ function stronglyConnected(nums, successorsOf) {
3783
3786
  }
3784
3787
  return sccs;
3785
3788
  }
3786
- var narrativeAntipatternsRule;
3789
+ var byCodeUnit, narrativeAntipatternsRule;
3787
3790
  var init_narrative_antipatterns = __esm({
3788
3791
  "src/core/rules/narrative/narrative-antipatterns.ts"() {
3789
3792
  "use strict";
3790
3793
  init_models();
3794
+ byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
3791
3795
  narrativeAntipatternsRule = {
3792
3796
  name: "narrative-antipatterns",
3793
3797
  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 +3810,33 @@ var init_narrative_antipatterns = __esm({
3806
3810
  const steps = implMethod.narrative;
3807
3811
  if (!steps.length) continue;
3808
3812
  const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
3809
- const graph = stepGraph(implMethod);
3813
+ const graph = completedStepGraph(steps);
3814
+ const decisionTargets = (stepNumber, explicit, fallThroughField) => {
3815
+ const targets = new Set(explicit);
3816
+ if (fallThroughField !== void 0) targets.add(fallThroughField);
3817
+ else for (const t of graph.successorsOf(stepNumber)) targets.add(t);
3818
+ return [...targets];
3819
+ };
3810
3820
  for (const s of steps) {
3811
3821
  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) {
3822
+ const targets = decisionTargets(s.stepNumber, [s.onFalseStep], s.onTrueStep);
3823
+ if (targets.length === 1) {
3814
3824
  ctx.addIssue(
3815
3825
  "warning",
3816
3826
  "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.`,
3827
+ `${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
3828
  impl.id,
3819
3829
  isDraftCtx
3820
3830
  );
3821
3831
  }
3822
3832
  }
3823
3833
  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) {
3834
+ const targets = decisionTargets(s.stepNumber, s.cases.map((c) => c.step), s.defaultStep);
3835
+ if (targets.length === 1) {
3828
3836
  ctx.addIssue(
3829
3837
  "warning",
3830
3838
  "MEANINGLESS_BRANCH",
3831
- `${where}switch step ${s.stepNumber} sends every case (and the default) to step ${[...targets][0]} \u2014 the dispatch decides nothing.`,
3839
+ `${where}switch step ${s.stepNumber} sends every case (and the default) to step ${targets[0]} \u2014 the dispatch decides nothing.`,
3832
3840
  impl.id,
3833
3841
  isDraftCtx
3834
3842
  );
@@ -3901,8 +3909,8 @@ var init_narrative_antipatterns = __esm({
3901
3909
  if (!isCycle) continue;
3902
3910
  const memberEdges = keys.flatMap((k) => (adjacency.get(k) ?? []).filter((e) => inScc.has(e.toKey)));
3903
3911
  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 ");
3912
+ const anchor = [...memberEdges].sort((a, b) => byCodeUnit(a.fromKey, b.fromKey) || byCodeUnit(a.impl.id, b.impl.id) || a.stepNumber - b.stepNumber)[0];
3913
+ const path70 = [...keys].sort(byCodeUnit).join(" \u2192 ");
3906
3914
  ctx.addIssue(
3907
3915
  "warning",
3908
3916
  "UNCONDITIONAL_CALL_CYCLE",
@@ -4154,14 +4162,15 @@ var init_portal_endpoints = __esm({
4154
4162
  continue;
4155
4163
  }
4156
4164
  for (const intf of compInterfaces) {
4165
+ const isIntfDraft = intf.status === "draft" || intf.status === "design";
4157
4166
  for (const m of intf.methods) {
4158
4167
  if (m.endpoint) {
4159
4168
  ctx.addIssue(
4160
4169
  "error",
4161
4170
  "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT",
4162
4171
  `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
4172
+ intf.id,
4173
+ isDraftCtx || isIntfDraft
4165
4174
  );
4166
4175
  }
4167
4176
  }
@@ -4292,7 +4301,7 @@ var init_stereotype_dependencies = __esm({
4292
4301
  { code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on Orchestrators or Stores" },
4293
4302
  { code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store or an Adapter" },
4294
4303
  { 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)" }
4304
+ { 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
4305
  ],
4297
4306
  check(ctx) {
4298
4307
  for (const comp of ctx.components) {
@@ -4364,10 +4373,11 @@ var init_stereotype_dependencies = __esm({
4364
4373
  crossDraft
4365
4374
  );
4366
4375
  } else if (depComp.componentType !== "Portal" && depComp.componentType !== "Gateway") {
4376
+ const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
4367
4377
  ctx.addIssue(
4368
4378
  "error",
4369
4379
  "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.`,
4380
+ `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
4381
  comp.id,
4372
4382
  crossDraft
4373
4383
  );
@@ -4491,6 +4501,23 @@ var init_stereotype_dependencies = __esm({
4491
4501
  }
4492
4502
  }
4493
4503
  }
4504
+ for (const comp of ctx.components) {
4505
+ if (comp.componentType !== "Portal" || !comp.dispatch || comp.dispatch.length === 0) continue;
4506
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
4507
+ for (const binding of comp.dispatch) {
4508
+ const target = ctx.componentMap.get(binding.component);
4509
+ if (!target || target.componentType !== "Repository" && target.componentType !== "Index") continue;
4510
+ const targetMethod = ctx.interfaceMethodsOf(target.id).find((m) => m.name === binding.method);
4511
+ if (targetMethod?.effect !== "write") continue;
4512
+ ctx.addIssue(
4513
+ "error",
4514
+ "PORTAL_WRITE_SHORTCUT",
4515
+ `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.`,
4516
+ comp.id,
4517
+ isDraftCtx || ctx.isComponentDraft(target.id)
4518
+ );
4519
+ }
4520
+ }
4494
4521
  }
4495
4522
  };
4496
4523
  }
@@ -4514,7 +4541,7 @@ var init_pattern_ownership = __esm({
4514
4541
  { code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Adapter member" },
4515
4542
  { code: "GATEWAY_CONTAINMENT", defaultSeverity: "error", summary: "Gateway owning a non Portal/Orchestrator/Specialist member" },
4516
4543
  { 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" },
4544
+ { code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" },
4518
4545
  { code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" },
4519
4546
  { 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
4547
  { 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 +4563,15 @@ var init_pattern_ownership = __esm({
4536
4563
  ctx.addIssue("error", "INVALID_OWNED_MEMBER", `Component "${comp.id}" owns "${memberId}" which does not exist.`, comp.id, isDraftCtx);
4537
4564
  continue;
4538
4565
  }
4566
+ if (!pattern) continue;
4539
4567
  if (isPattern(member)) {
4540
4568
  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
4569
  }
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);
4570
+ const firstOwner = ownedBy.get(memberId);
4571
+ if (firstOwner && firstOwner !== comp.id) {
4572
+ 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
4573
  }
4546
- ownedBy.set(memberId, comp.id);
4574
+ if (!firstOwner) ownedBy.set(memberId, comp.id);
4547
4575
  }
4548
4576
  if (comp.componentType === "Repository") {
4549
4577
  const allowed = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Adapter"]);
@@ -4578,14 +4606,14 @@ var init_pattern_ownership = __esm({
4578
4606
  }
4579
4607
  }
4580
4608
  if (comp.componentType === "RouterComponent") {
4581
- let hasPortal = false;
4609
+ let portals = 0;
4582
4610
  let hasChildren = false;
4583
4611
  for (const memberId of comp.owns) {
4584
4612
  const t = ctx.componentMap.get(memberId)?.componentType;
4585
- if (t === "Portal") hasPortal = true;
4613
+ if (t === "Portal") portals++;
4586
4614
  else if (t) hasChildren = true;
4587
4615
  }
4588
- if (!hasPortal) {
4616
+ if (portals !== 1) {
4589
4617
  ctx.addIssue("error", "ROUTER_COMPONENT_CONTAINMENT", `RouterComponent "${comp.id}" must own exactly one Portal component to act as its facade.`, comp.id, isDraftCtx);
4590
4618
  }
4591
4619
  if (!hasChildren) {
@@ -5087,54 +5115,43 @@ var init_dependency_cycles = __esm({
5087
5115
  "use strict";
5088
5116
  cyclesRule = {
5089
5117
  name: "dependency-cycles",
5090
- description: "The component dependsOn graph must be a DAG; the first detected cycle is reported with its full path.",
5118
+ 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
5119
  codes: [
5092
5120
  { code: "CIRCULAR_DEPENDENCY", defaultSeverity: "error", summary: "Circular dependency between components" }
5093
5121
  ],
5094
5122
  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)) {
5123
+ const cycleFrom = (startId) => {
5124
+ const visited = /* @__PURE__ */ new Set([startId]);
5125
+ const pathTrace = [startId];
5126
+ const search = (compId) => {
5127
+ for (const depId of ctx.componentMap.get(compId)?.dependsOn ?? []) {
5128
+ if (depId === startId) {
5111
5129
  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
5130
  return true;
5125
5131
  }
5132
+ if (visited.has(depId)) continue;
5133
+ visited.add(depId);
5134
+ pathTrace.push(depId);
5135
+ if (search(depId)) return true;
5136
+ pathTrace.pop();
5126
5137
  }
5127
- }
5128
- recStack.delete(compId);
5129
- pathTrace.pop();
5130
- return false;
5138
+ return false;
5139
+ };
5140
+ return search(startId) ? pathTrace : void 0;
5131
5141
  };
5132
5142
  for (const comp of ctx.components) {
5133
- if (!visited.has(comp.id)) {
5134
- if (dfs(comp.id, [])) {
5135
- break;
5136
- }
5137
- }
5143
+ if (!ctx.isSpecInScope(comp.id)) continue;
5144
+ const cycle = cycleFrom(comp.id);
5145
+ if (!cycle) continue;
5146
+ const closing = cycle[cycle.length - 2];
5147
+ ctx.addIssue(
5148
+ "error",
5149
+ "CIRCULAR_DEPENDENCY",
5150
+ `Circular dependency detected: ${cycle.join(" -> ")}`,
5151
+ comp.id,
5152
+ ctx.isComponentDraft(comp.id) || ctx.isComponentDraft(closing)
5153
+ );
5154
+ return;
5138
5155
  }
5139
5156
  }
5140
5157
  };
@@ -5263,19 +5280,18 @@ var init_dispatch_tables = __esm({
5263
5280
  );
5264
5281
  continue;
5265
5282
  }
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
- }
5283
+ const boundMethod = ctx.interfaceMethodsOf(binding.component).find((m) => m.name === binding.method);
5284
+ if (!boundMethod) continue;
5285
+ const declared = new Set(boundMethod.guarantees ?? []);
5286
+ for (const g of step.assertsGuarantees ?? []) {
5287
+ if (!declared.has(g)) {
5288
+ ctx.addIssue(
5289
+ "warning",
5290
+ "NARRATIVE_SEMANTIC_UNBACKED",
5291
+ `${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.`,
5292
+ impl.id,
5293
+ isDraftCtx || ctx.isComponentDraft(binding.component)
5294
+ );
5279
5295
  }
5280
5296
  }
5281
5297
  }
@@ -5480,12 +5496,11 @@ var init_unused_detection = __esm({
5480
5496
  const invokedBySeeds = [];
5481
5497
  for (const intf of ctx.interfaces) {
5482
5498
  if (!ctx.componentMap.has(intf.component)) continue;
5483
- const comp = ctx.componentMap.get(intf.component);
5484
5499
  for (const m of intf.methods) {
5485
5500
  if (!m.invokedBy) continue;
5486
5501
  invokedBySeeds.push({ compId: intf.component, methodName: m.name });
5487
5502
  if (!ctx.isSpecInScope(intf.id)) continue;
5488
- const isDraftCtx = comp.status === "draft" || comp.status === "design" || intf.status === "draft" || intf.status === "design";
5503
+ const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
5489
5504
  if (!passesIntentFloor(m.invokedBy.caller, m.name)) {
5490
5505
  ctx.addIssue(
5491
5506
  "warning",
@@ -5510,7 +5525,7 @@ var init_unused_detection = __esm({
5510
5525
  for (const comp of ctx.components) {
5511
5526
  if (!ctx.isSpecInScope(comp.id)) continue;
5512
5527
  if (!reach.reachesComponent(comp.id)) {
5513
- const isDraftCtx = comp.status === "draft" || comp.status === "design";
5528
+ const isDraftCtx = ctx.isComponentDraft(comp.id);
5514
5529
  ctx.addIssue(
5515
5530
  "warning",
5516
5531
  "UNUSED_COMPONENT",
@@ -5522,12 +5537,12 @@ var init_unused_detection = __esm({
5522
5537
  for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
5523
5538
  for (const m of intf.methods) {
5524
5539
  if (!reach.reachesMethod(comp.id, m.name)) {
5525
- const isDraftCtx = comp.status === "draft" || comp.status === "design" || intf.status === "draft" || intf.status === "design";
5540
+ const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
5526
5541
  ctx.addIssue(
5527
5542
  "warning",
5528
5543
  "UNUSED_METHOD",
5529
5544
  `Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
5530
- comp.id,
5545
+ intf.id,
5531
5546
  isDraftCtx
5532
5547
  );
5533
5548
  }
@@ -5638,20 +5653,20 @@ var init_durability_round_trip = __esm({
5638
5653
  });
5639
5654
 
5640
5655
  // 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();
5656
+ function namesBareType(refs) {
5657
+ return refs.some((ref) => BARE_SEAM_TYPES.has(ref.toLowerCase()));
5644
5658
  }
5645
5659
  var BARE_SEAM_TYPES, untypedSeamRule;
5646
5660
  var init_untyped_seams = __esm({
5647
5661
  "src/core/rules/wiring/untyped-seams.ts"() {
5648
5662
  "use strict";
5663
+ init_models();
5649
5664
  BARE_SEAM_TYPES = /* @__PURE__ */ new Set(["json", "any", "unknown", "object"]);
5650
5665
  untypedSeamRule = {
5651
5666
  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.",
5667
+ 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
5668
  codes: [
5654
- { code: "UNTYPED_SEAM", defaultSeverity: "warning", summary: "Bare Json/any/unknown parameter or return crossing a subsystem public surface" }
5669
+ { code: "UNTYPED_SEAM", defaultSeverity: "warning", summary: "Bare Json/any/unknown/object parameter or return crossing a subsystem public surface" }
5655
5670
  ],
5656
5671
  check(ctx) {
5657
5672
  for (const sub of ctx.subsystems) {
@@ -5665,14 +5680,17 @@ var init_untyped_seams = __esm({
5665
5680
  const isDraftCtx = ctx.isComponentDraft(compId) || intf.status === "draft" || intf.status === "design";
5666
5681
  for (const m of intf.methods) {
5667
5682
  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}"`);
5683
+ if (m.params && m.params.length > 0) {
5684
+ for (const p of m.params) {
5685
+ if (namesBareType(extractTypeIdentifiers(p.type))) {
5686
+ offenders.push(`param "${p.name}: ${p.type}"`);
5687
+ }
5671
5688
  }
5672
- }
5673
- const ret = unwrapPromise(m.returns ?? "");
5674
- if (BARE_SEAM_TYPES.has(ret.toLowerCase())) {
5675
- offenders.push(`return "${m.returns}"`);
5689
+ if (namesBareType(extractTypeIdentifiers(m.returns ?? ""))) {
5690
+ offenders.push(`return "${m.returns}"`);
5691
+ }
5692
+ } else if (namesBareType(methodTypeRefs(m))) {
5693
+ offenders.push(`signature "${m.signature}"`);
5676
5694
  }
5677
5695
  if (offenders.length) {
5678
5696
  ctx.addIssue(
@@ -5697,15 +5715,19 @@ function componentOfImpl(ctx, implContract) {
5697
5715
  const contract = ctx.interfaceMap.get(implContract);
5698
5716
  return contract ? ctx.componentMap.get(contract.component) : void 0;
5699
5717
  }
5700
- var DATA_STEREOTYPES, CLAIM_PHRASES, proseClaimRule;
5718
+ function withoutQuotedText(text2) {
5719
+ return text2.replace(QUOTED_SPAN, " ");
5720
+ }
5721
+ var DATA_STEREOTYPES, CLAIM_PHRASES, QUOTED_SPAN, proseClaimRule;
5701
5722
  var init_prose_claims = __esm({
5702
5723
  "src/core/rules/wiring/prose-claims.ts"() {
5703
5724
  "use strict";
5704
5725
  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;
5726
+ 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;
5727
+ QUOTED_SPAN = /"[^"]*"|“[^”]*”|`[^`]*`|(?<![\p{L}\p{N}])['‘](?:[^'‘’]|(?<=[\p{L}\p{N}])['’](?=[\p{L}\p{N}]))*['’](?![\p{L}\p{N}])/gu;
5706
5728
  proseClaimRule = {
5707
5729
  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.',
5730
+ 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
5731
  codes: [
5710
5732
  { code: "UNREALIZED_CLAIM", defaultSeverity: "warning", summary: "Durability/side-effect claim in prose with no matching structural edge" }
5711
5733
  ],
@@ -5716,11 +5738,11 @@ var init_prose_claims = __esm({
5716
5738
  if (DATA_STEREOTYPES.has(comp.componentType)) continue;
5717
5739
  const isDraftCtx = ctx.isImplementationDraft(impl);
5718
5740
  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;
5741
+ const stepClaims = implMethod.narrative.filter((step) => step.type === "local").map((step) => ({ step, claim: CLAIM_PHRASES.exec(withoutQuotedText(step.description)) })).filter((c) => c.claim !== null);
5742
+ const intentClaim = implMethod.intent ? CLAIM_PHRASES.exec(withoutQuotedText(implMethod.intent)) : null;
5721
5743
  if (!stepClaims.length && !intentClaim) continue;
5722
5744
  const hasDataEdge = implMethod.narrative.some((step) => {
5723
- if (step.type !== "call" && step.type !== "dispatch") return false;
5745
+ if (step.type !== "call" && step.type !== "register" && step.type !== "dispatch") return false;
5724
5746
  if (!step.targetComponent) return false;
5725
5747
  const target = ctx.componentMap.get(step.targetComponent);
5726
5748
  if (target && DATA_STEREOTYPES.has(target.componentType)) return true;
@@ -5733,7 +5755,7 @@ var init_prose_claims = __esm({
5733
5755
  ctx.addIssue(
5734
5756
  "warning",
5735
5757
  "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.`,
5758
+ `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
5759
  impl.id,
5738
5760
  isDraftCtx
5739
5761
  );
@@ -5792,6 +5814,10 @@ function stepAsserts(impl, methodName, type, invariantId) {
5792
5814
  (step) => (step.assertsInvariants ?? []).some((ref) => refMatchesInvariant(ref, type, invariantId))
5793
5815
  );
5794
5816
  }
5817
+ function isEntityDraft(t, ctx) {
5818
+ const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
5819
+ return sub !== void 0 && isDraftSubsystem(sub);
5820
+ }
5795
5821
  function resolveComponentClass(t, ctx) {
5796
5822
  if (!t.componentClass) return void 0;
5797
5823
  const direct = ctx.componentMap.get(t.componentClass);
@@ -5818,6 +5844,7 @@ var init_invariant_backing = __esm({
5818
5844
  for (const t of ctx.types) {
5819
5845
  const invariants = t.invariants ?? [];
5820
5846
  if (invariants.length === 0) continue;
5847
+ const entityDraft = isEntityDraft(t, ctx);
5821
5848
  const seen = /* @__PURE__ */ new Set();
5822
5849
  for (const inv of invariants) {
5823
5850
  if (seen.has(inv.id)) {
@@ -5825,7 +5852,8 @@ var init_invariant_backing = __esm({
5825
5852
  "error",
5826
5853
  "DUPLICATE_INVARIANT_ID",
5827
5854
  `Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
5828
- t.id
5855
+ t.id,
5856
+ entityDraft
5829
5857
  );
5830
5858
  }
5831
5859
  seen.add(inv.id);
@@ -5836,7 +5864,8 @@ var init_invariant_backing = __esm({
5836
5864
  "warning",
5837
5865
  "INVARIANT_UNANCHORED",
5838
5866
  `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
5867
+ t.id,
5868
+ entityDraft
5840
5869
  );
5841
5870
  continue;
5842
5871
  }
@@ -5850,13 +5879,13 @@ var init_invariant_backing = __esm({
5850
5879
  "INVARIANT_UNANCHORED",
5851
5880
  `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
5881
  t.id,
5853
- ctx.isComponentDraft(comp.id)
5882
+ entityDraft || ctx.isComponentDraft(comp.id)
5854
5883
  );
5855
5884
  continue;
5856
5885
  }
5857
5886
  for (const { intf, method: method2 } of writeMethods) {
5858
5887
  for (const impl of ctx.implementationsByContract.get(intf.id) ?? []) {
5859
- const isDraftCtx = ctx.isImplementationDraft(impl);
5888
+ const isDraftCtx = entityDraft || ctx.isImplementationDraft(impl);
5860
5889
  for (const inv of invariants) {
5861
5890
  if (stepAsserts(impl, method2.name, t, inv.id)) continue;
5862
5891
  ctx.addIssue(
@@ -5894,30 +5923,35 @@ var init_invariant_backing = __esm({
5894
5923
 
5895
5924
  // src/core/rules/wiring/event-topology.ts
5896
5925
  function collectEnds(ctx) {
5897
- const emitters = [];
5898
- const subscribers = [];
5926
+ const emitters = /* @__PURE__ */ new Map();
5927
+ const subscribers = /* @__PURE__ */ new Map();
5928
+ const addEnd = (ends, topic, compId, via) => {
5929
+ const key = `${compId}#${topic}`;
5930
+ const end = ends.get(key);
5931
+ if (!end) ends.set(key, { topic, compId, vias: [via] });
5932
+ else if (!end.vias.includes(via)) end.vias.push(via);
5933
+ };
5899
5934
  for (const comp of ctx.components) {
5900
5935
  for (const e of comp.emits ?? []) {
5901
- emitters.push({ topic: e.topic, compId: comp.id, via: "emits declaration" });
5936
+ addEnd(emitters, e.topic, comp.id, "emits declaration");
5902
5937
  }
5903
5938
  for (const s of comp.subscribesTo ?? []) {
5904
- subscribers.push({ topic: s.topic, compId: comp.id, via: "subscribesTo declaration" });
5939
+ addEnd(subscribers, s.topic, comp.id, "subscribesTo declaration");
5905
5940
  }
5906
5941
  }
5907
5942
  for (const intf of ctx.interfaces) {
5908
5943
  for (const method2 of intf.methods) {
5909
5944
  const ep = method2.endpoint;
5910
5945
  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);
5946
+ addEnd(
5947
+ ep.direction === "publish" ? emitters : subscribers,
5948
+ ep.topic,
5949
+ intf.component,
5950
+ `MessageBus endpoint on ${intf.id}.${method2.name}`
5951
+ );
5918
5952
  }
5919
5953
  }
5920
- return { emitters, subscribers };
5954
+ return { emitters: [...emitters.values()], subscribers: [...subscribers.values()] };
5921
5955
  }
5922
5956
  var eventTopologyRule;
5923
5957
  var init_event_topology = __esm({
@@ -5925,7 +5959,7 @@ var init_event_topology = __esm({
5925
5959
  "use strict";
5926
5960
  eventTopologyRule = {
5927
5961
  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.",
5962
+ 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
5963
  codes: [
5930
5964
  { code: "UNCONSUMED_TOPIC", defaultSeverity: "warning", summary: "Topic is emitted but nothing in the tree subscribes to it" },
5931
5965
  { code: "UNSOURCED_SUBSCRIPTION", defaultSeverity: "warning", summary: "Topic is subscribed to but nothing in the tree emits it" }
@@ -5940,7 +5974,7 @@ var init_event_topology = __esm({
5940
5974
  ctx.addIssue(
5941
5975
  "warning",
5942
5976
  "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.`,
5977
+ `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
5978
  e.compId,
5945
5979
  ctx.isComponentDraft(e.compId)
5946
5980
  );
@@ -5950,7 +5984,7 @@ var init_event_topology = __esm({
5950
5984
  ctx.addIssue(
5951
5985
  "warning",
5952
5986
  "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.`,
5987
+ `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
5988
  s.compId,
5955
5989
  ctx.isComponentDraft(s.compId)
5956
5990
  );
@@ -5969,13 +6003,13 @@ var init_structural_conformance = __esm({
5969
6003
  quoteList = (names) => names.map((n) => `"${n}"`).join(", ");
5970
6004
  structuralConformanceRule = {
5971
6005
  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.",
6006
+ 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
6007
  codes: [
5974
6008
  { 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
6009
  { 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
6010
  { 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
6011
  { 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" },
6012
+ { 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
6013
  { 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
6014
  { code: "CONFORMANCE_DEGRADED", defaultSeverity: "warning", summary: "TypeScript/JavaScript files were analyzed below exact grade (compiler not resolvable) \u2014 dependency conformance skips them" }
5981
6015
  ],
@@ -5985,7 +6019,7 @@ var init_structural_conformance = __esm({
5985
6019
  lookups.set(pathKey(facts.path), {
5986
6020
  declared: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames]),
5987
6021
  anchored: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames, ...facts.anchoredNames]),
5988
- literals: new Set(facts.anchoredNames),
6022
+ findingAnchors: new Set(facts.anchoredNames),
5989
6023
  facts
5990
6024
  });
5991
6025
  }
@@ -6105,11 +6139,11 @@ var init_structural_conformance = __esm({
6105
6139
  if (!checked) continue;
6106
6140
  const { file, lookup } = checked;
6107
6141
  for (const finding of method2.findings) {
6108
- if (lookup.literals.has(finding.code)) continue;
6142
+ if (lookup.findingAnchors.has(finding.code)) continue;
6109
6143
  ctx.addIssue(
6110
6144
  "warning",
6111
6145
  "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.`,
6146
+ `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
6147
  impl.id,
6114
6148
  draft
6115
6149
  );
@@ -6225,12 +6259,18 @@ var init_hidden_state = __esm({
6225
6259
  check(ctx) {
6226
6260
  const byPath = /* @__PURE__ */ new Map();
6227
6261
  for (const impl of ctx.implementations) {
6228
- if (impl.conformance === "off") continue;
6229
6262
  const contract = ctx.interfaceMap.get(impl.contract);
6230
6263
  const component = contract ? ctx.componentMap.get(contract.component) : void 0;
6231
6264
  if (!component) continue;
6232
6265
  if (ctx.isInChainedSubproject(component.subsystem)) continue;
6233
- for (const file of implementationSourceFiles(impl)) {
6266
+ const trusted = [];
6267
+ if (impl.sourcePath && impl.conformance !== "off") trusted.push(impl.sourcePath);
6268
+ for (const method2 of impl.methods) {
6269
+ if ((method2.conformance ?? impl.conformance) === "off") continue;
6270
+ const file = methodSourceFile(method2, impl.sourcePath);
6271
+ if (file) trusted.push(file);
6272
+ }
6273
+ for (const file of trusted) {
6234
6274
  const key = pathKey(file);
6235
6275
  const list2 = byPath.get(key) ?? [];
6236
6276
  if (list2.some((m) => m.impl === impl)) continue;
@@ -6381,7 +6421,7 @@ var init_dependency_conformance = __esm({
6381
6421
  const fromFiles = filesByComponent.get(component.id);
6382
6422
  if (!fromFiles) continue;
6383
6423
  const isMountingDeclarer = component.componentType === "Portal" || component.componentType === "Observer";
6384
- const declaredTargets = [...component.dependsOn, ...component.owns ?? []];
6424
+ const declaredTargets = [.../* @__PURE__ */ new Set([...component.dependsOn, ...component.owns ?? []])];
6385
6425
  for (const targetId of declaredTargets) {
6386
6426
  const target = ctx.componentMap.get(targetId);
6387
6427
  if (!target) continue;
@@ -6402,11 +6442,14 @@ var init_dependency_conformance = __esm({
6402
6442
  }
6403
6443
  const impls = implsByComponent.get(component.id) ?? [];
6404
6444
  const anchor = impls[0];
6445
+ const declaredDependsOn = component.dependsOn.includes(targetId);
6446
+ const declaredOwns = (component.owns ?? []).includes(targetId);
6447
+ const relation = declaredDependsOn && declaredOwns ? "dependsOn and owns" : declaredDependsOn ? "dependsOn" : "owns";
6405
6448
  const draft = ctx.isComponentDraft(component.id) || ctx.isComponentDraft(targetId) || impls.some((i) => ctx.isImplementationDraft(i));
6406
6449
  ctx.addIssue(
6407
6450
  "warning",
6408
6451
  "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.`,
6452
+ `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
6453
  anchor?.id ?? component.id,
6411
6454
  draft
6412
6455
  );
@@ -6438,31 +6481,31 @@ var init_integration_conformance = __esm({
6438
6481
  const filesByComponent = /* @__PURE__ */ new Map();
6439
6482
  const filesBySubsystem = /* @__PURE__ */ new Map();
6440
6483
  const implsByComponent = /* @__PURE__ */ new Map();
6484
+ const adopted = /* @__PURE__ */ new Set();
6485
+ const exists = (p) => {
6486
+ const status2 = factsByPath.get(p)?.status;
6487
+ return status2 === "analyzed" || status2 === "unreadable";
6488
+ };
6441
6489
  for (const impl of ctx.implementations) {
6442
6490
  const contract = ctx.interfaceMap.get(impl.contract);
6443
6491
  const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
6444
- if (!comp) continue;
6492
+ if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
6445
6493
  if (!implsByComponent.has(comp.id)) implsByComponent.set(comp.id, []);
6446
6494
  implsByComponent.get(comp.id).push(impl);
6447
6495
  for (const file of implementationSourceFiles(impl)) {
6448
6496
  const p = pathKey(file);
6497
+ if (!exists(p)) continue;
6449
6498
  if (!filesByComponent.has(comp.id)) filesByComponent.set(comp.id, /* @__PURE__ */ new Set());
6450
6499
  filesByComponent.get(comp.id).add(p);
6451
6500
  if (!filesBySubsystem.has(comp.subsystem)) filesBySubsystem.set(comp.subsystem, /* @__PURE__ */ new Set());
6452
6501
  filesBySubsystem.get(comp.subsystem).add(p);
6453
6502
  }
6503
+ if (impl.simPath) adopted.add(comp.subsystem);
6454
6504
  }
6455
6505
  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
- const reachFrom = (start) => {
6464
- const seen = /* @__PURE__ */ new Set([start]);
6465
- const stack = [start];
6506
+ const reachFrom = (start2) => {
6507
+ const seen = /* @__PURE__ */ new Set([start2]);
6508
+ const stack = [start2];
6466
6509
  while (stack.length) {
6467
6510
  const from = stack.pop();
6468
6511
  const facts = factsByPath.get(from);
@@ -6656,8 +6699,8 @@ var init_coupling_health = __esm({
6656
6699
  );
6657
6700
  }
6658
6701
  }
6659
- const threshold = ctx.rules?.complexity?.maxComponentDependencies ?? DEFAULT_GOD_COMPONENT_THRESHOLD;
6660
6702
  for (const comp of ctx.components) {
6703
+ const threshold = ctx.complexityConfigFor(comp.subsystem)?.maxComponentDependencies ?? DEFAULT_GOD_COMPONENT_THRESHOLD;
6661
6704
  if (comp.dependsOn.length > threshold) {
6662
6705
  ctx.addIssue(
6663
6706
  "warning",
@@ -6785,7 +6828,7 @@ var init_target_language = __esm({
6785
6828
  if (!lang) continue;
6786
6829
  const gaps = gapsFor(lang);
6787
6830
  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);
6831
+ const isDraftCtx = ctx.isImplementationDraft(impl);
6789
6832
  for (const implMethod of impl.methods) {
6790
6833
  for (const step of implMethod.narrative) {
6791
6834
  const constructs = [
@@ -6887,7 +6930,7 @@ var init_technology_boundaries = __esm({
6887
6930
  "TECH_ON_LOGIC_COMPONENT",
6888
6931
  `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
6932
  impl.id,
6890
- ctx.isComponentDraft(comp.id)
6933
+ ctx.isImplementationDraft(impl)
6891
6934
  );
6892
6935
  }
6893
6936
  const compSet = ownsClosure(ownershipRoot(comp.id));
@@ -6925,7 +6968,7 @@ var init_technology_boundaries = __esm({
6925
6968
  "VENDOR_NAME_IN_CONTRACT",
6926
6969
  `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
6970
  intf.id,
6928
- ctx.isComponentDraft(intf.component)
6971
+ ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design"
6929
6972
  );
6930
6973
  }
6931
6974
  }
@@ -6956,7 +6999,8 @@ var init_technology_boundaries = __esm({
6956
6999
  "warning",
6957
7000
  "TECH_LEAKAGE",
6958
7001
  `Subsystem "${sub.id}" references "${home.label}" outside the technology's owning boundary (component ${ownersDesc(home)}).`,
6959
- sub.id
7002
+ sub.id,
7003
+ isDraftSubsystem(sub)
6960
7004
  );
6961
7005
  }
6962
7006
  }
@@ -6969,7 +7013,7 @@ var init_technology_boundaries = __esm({
6969
7013
  "TECH_LEAKAGE",
6970
7014
  `Interface "${intf.id}" describes "${home.label}" outside its owning boundary (component ${ownersDesc(home)}) \u2014 consumers must not know backend specifics.`,
6971
7015
  intf.id,
6972
- ctx.isComponentDraft(intf.component)
7016
+ ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design"
6973
7017
  );
6974
7018
  }
6975
7019
  }
@@ -6989,7 +7033,8 @@ var init_technology_boundaries = __esm({
6989
7033
  "warning",
6990
7034
  "TECH_LEAKAGE",
6991
7035
  `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
7036
+ impl.id,
7037
+ ctx.isImplementationDraft(impl)
6993
7038
  );
6994
7039
  }
6995
7040
  }
@@ -7052,6 +7097,7 @@ var casingPatterns, isAllUppercase, namingRule;
7052
7097
  var init_naming_conventions = __esm({
7053
7098
  "src/core/rules/heuristic/naming-conventions.ts"() {
7054
7099
  "use strict";
7100
+ init_models();
7055
7101
  casingPatterns = {
7056
7102
  camelCase: /^[a-z][a-zA-Z0-9]*$/,
7057
7103
  PascalCase: /^[A-Z][a-zA-Z0-9]*$/,
@@ -7072,9 +7118,10 @@ var init_naming_conventions = __esm({
7072
7118
  for (const sub of ctx.subsystems) {
7073
7119
  const namingConfig = ctx.namingConfigFor(sub.id);
7074
7120
  if (!namingConfig?.subsystems) continue;
7121
+ const isDraft = isDraftSubsystem(sub);
7075
7122
  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);
7123
+ checkNamedValue(ctx, baseId, namingConfig.subsystems, `Subsystem ID "${sub.id}" does not match naming convention "${namingConfig.subsystems}".`, sub.id, isDraft);
7124
+ checkNamedValue(ctx, sub.name, namingConfig.subsystems, `Subsystem Name "${sub.name}" does not match naming convention "${namingConfig.subsystems}".`, sub.id, isDraft);
7078
7125
  }
7079
7126
  for (const comp of ctx.components) {
7080
7127
  const namingConfig = ctx.namingConfigFor(comp.subsystem);
@@ -7159,7 +7206,7 @@ var init_naming_conventions = __esm({
7159
7206
  const intf = ctx.interfaceMap.get(impl.contract);
7160
7207
  const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
7161
7208
  const namingConfig = ctx.namingConfigFor(comp?.subsystem);
7162
- const isDraft = impl.status === "draft" || impl.status === "design";
7209
+ const isDraft = ctx.isImplementationDraft(impl);
7163
7210
  if (namingConfig?.methods) {
7164
7211
  for (const m of impl.methods) {
7165
7212
  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 +7270,7 @@ var complexityRule;
7223
7270
  var init_complexity_and_metadata = __esm({
7224
7271
  "src/core/rules/heuristic/complexity-and-metadata.ts"() {
7225
7272
  "use strict";
7273
+ init_models();
7226
7274
  complexityRule = {
7227
7275
  name: "complexity-and-metadata",
7228
7276
  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 +7286,8 @@ var init_complexity_and_metadata = __esm({
7238
7286
  check(ctx) {
7239
7287
  for (const sub of ctx.subsystems) {
7240
7288
  const docConfig = ctx.documentationConfigFor(sub.id);
7241
- checkDescription(ctx, sub.description, docConfig?.requireDescriptions ?? false, docConfig?.minDescriptionLength, sub.id, "Subsystem", false);
7289
+ const isSubDraft = isDraftSubsystem(sub);
7290
+ checkDescription(ctx, sub.description, docConfig?.requireDescriptions ?? false, docConfig?.minDescriptionLength, sub.id, "Subsystem", isSubDraft);
7242
7291
  const complexityConfig = ctx.complexityConfigFor(sub.id);
7243
7292
  const directComponents = ctx.components.filter((c) => c.subsystem === sub.id).length;
7244
7293
  if (complexityConfig?.maxSubsystemComponents !== void 0 && directComponents > complexityConfig.maxSubsystemComponents) {
@@ -7246,7 +7295,8 @@ var init_complexity_and_metadata = __esm({
7246
7295
  "warning",
7247
7296
  "EXCESSIVE_SUBSYSTEM_COMPONENTS",
7248
7297
  `Subsystem "${sub.id}" has ${directComponents} direct components, exceeding the configured limit of ${complexityConfig.maxSubsystemComponents}.`,
7249
- sub.id
7298
+ sub.id,
7299
+ isSubDraft
7250
7300
  );
7251
7301
  }
7252
7302
  }
@@ -7322,7 +7372,7 @@ var init_complexity_and_metadata = __esm({
7322
7372
  const intf = ctx.interfaceMap.get(impl.contract);
7323
7373
  const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
7324
7374
  const complexityConfig = ctx.complexityConfigFor(comp?.subsystem);
7325
- const isDraft = impl.status === "draft" || impl.status === "design";
7375
+ const isDraft = ctx.isImplementationDraft(impl);
7326
7376
  if (complexityConfig?.maxNarrativeSteps !== void 0) {
7327
7377
  for (const m of impl.methods) {
7328
7378
  if (m.narrative.length > complexityConfig.maxNarrativeSteps) {
@@ -8838,14 +8888,14 @@ function buildExcalidrawScene(model, L) {
8838
8888
  const src = rectById[edge.from], tgt = rectById[edge.to];
8839
8889
  if (!a || !b || !src || !tgt) return;
8840
8890
  const leftToRight = b.x >= a.x + a.w;
8841
- const start = leftToRight ? { x: a.x + a.w, y: a.y + a.h / 2 } : { x: a.x, y: a.y + a.h / 2 };
8891
+ const start2 = leftToRight ? { x: a.x + a.w, y: a.y + a.h / 2 } : { x: a.x, y: a.y + a.h / 2 };
8842
8892
  const end = leftToRight ? { x: b.x, y: b.y + b.h / 2 } : { x: b.x + b.w, y: b.y + b.h / 2 };
8843
8893
  const id = "edge-" + edgeN++;
8844
- const arrow = base(id, "arrow", { x: start.x, y: start.y, w: Math.abs(end.x - start.x), h: Math.abs(end.y - start.y) });
8894
+ const arrow = base(id, "arrow", { x: start2.x, y: start2.y, w: Math.abs(end.x - start2.x), h: Math.abs(end.y - start2.y) });
8845
8895
  arrow.roundness = { type: 2 };
8846
8896
  arrow.strokeColor = edge.cross ? "#c26767" : "#8d97a5";
8847
8897
  arrow.strokeWidth = edge.cross ? 2 : 1;
8848
- arrow.points = [[0, 0], [end.x - start.x, end.y - start.y]];
8898
+ arrow.points = [[0, 0], [end.x - start2.x, end.y - start2.y]];
8849
8899
  arrow.lastCommittedPoint = null;
8850
8900
  arrow.startBinding = { elementId: src.id, focus: 0, gap: 4 };
8851
8901
  arrow.endBinding = { elementId: tgt.id, focus: 0, gap: 4 };
@@ -14029,7 +14079,7 @@ function buildRuleContext(opts) {
14029
14079
  return rules.sddRuleSeverity[ruleCode];
14030
14080
  }
14031
14081
  const sub = subsystemId ? subsystems.find((s) => s.id === subsystemId) : void 0;
14032
- const profileSeverity = extensions.profiles[sub?.profile ?? projectType]?.rules?.sddRuleSeverity?.[ruleCode];
14082
+ const profileSeverity = extensions.profiles[sub?.profile || projectType]?.rules?.sddRuleSeverity?.[ruleCode];
14033
14083
  if (profileSeverity) {
14034
14084
  return profileSeverity;
14035
14085
  }
@@ -14516,28 +14566,52 @@ var init_domains = __esm({
14516
14566
  function globalVariantsDir() {
14517
14567
  return process.env.WAIRON_VARIANTS_DIR ?? path16.join(os5.homedir(), ".wairon", "variants");
14518
14568
  }
14519
- function readVariantsDir(dir) {
14569
+ function builtinVariantsDir() {
14570
+ const candidates = [
14571
+ path16.resolve(__dirname, "..", "templates", "variants"),
14572
+ // src/core & dist/cli
14573
+ path16.resolve(__dirname, "templates", "variants")
14574
+ // dist (library entry)
14575
+ ];
14576
+ return candidates.find((dir) => fs12.existsSync(dir)) ?? candidates[0];
14577
+ }
14578
+ function projectVariantsDir() {
14579
+ return path16.join(getProjectRoot(), ".wai", "variants");
14580
+ }
14581
+ function variantFiles(dir) {
14520
14582
  let entries;
14521
14583
  try {
14522
14584
  entries = fs12.readdirSync(dir, { withFileTypes: true });
14523
14585
  } catch {
14524
14586
  return [];
14525
14587
  }
14588
+ return entries.filter((e) => e.isFile() && /\.ya?ml$/i.test(e.name)).sort((a, b) => a.name.localeCompare(b.name)).map((e) => path16.join(dir, e.name));
14589
+ }
14590
+ function parseVariants(raw) {
14591
+ if (raw == null) return [];
14592
+ return (Array.isArray(raw) ? raw : [raw]).map((item) => VariantDefSchema.parse(item));
14593
+ }
14594
+ function reportSkippedFile(file, err) {
14595
+ console.error(`[variants] skipped "${file}": ${err instanceof Error ? err.message : String(err)}`);
14596
+ }
14597
+ function readVariantsDir(dir) {
14526
14598
  const out = [];
14527
- for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
14528
- if (!e.isFile() || !/\.ya?ml$/i.test(e.name)) continue;
14529
- const full = path16.join(dir, e.name);
14599
+ for (const file of variantFiles(dir)) {
14530
14600
  try {
14531
- const raw = readYamlFile(full);
14532
- if (raw == null) continue;
14533
- const items = Array.isArray(raw) ? raw : [raw];
14534
- for (const item of items) out.push(VariantDefSchema.parse(item));
14601
+ out.push(...parseVariants(readYamlFile(file)));
14535
14602
  } catch (err) {
14536
- console.error(`[variants] skipped "${full}": ${err instanceof Error ? err.message : String(err)}`);
14603
+ reportSkippedFile(file, err);
14537
14604
  }
14538
14605
  }
14539
14606
  return out;
14540
14607
  }
14608
+ function mergeById(...layers) {
14609
+ const byId = /* @__PURE__ */ new Map();
14610
+ for (const layer of layers) {
14611
+ for (const v of layer) byId.set(v.id, v);
14612
+ }
14613
+ return [...byId.values()];
14614
+ }
14541
14615
  function resolveVariantGuidance(component, allComponents, variantsById) {
14542
14616
  if (!component.variant) return null;
14543
14617
  const def = variantsById.get(component.variant);
@@ -14571,10 +14645,11 @@ function composeVariantGuidance(comps, allComponents, variantsById) {
14571
14645
  }
14572
14646
  function loadProjectVariants() {
14573
14647
  try {
14574
- const byId = /* @__PURE__ */ new Map();
14575
- for (const v of readVariantsDir(globalVariantsDir())) byId.set(v.id, v);
14576
- for (const v of readVariantsDir(path16.join(getProjectRoot(), ".wai", "variants"))) byId.set(v.id, v);
14577
- return [...byId.values()];
14648
+ return mergeById(
14649
+ readVariantsDir(builtinVariantsDir()),
14650
+ readVariantsDir(globalVariantsDir()),
14651
+ readVariantsDir(projectVariantsDir())
14652
+ );
14578
14653
  } catch {
14579
14654
  return [];
14580
14655
  }
@@ -14592,7 +14667,7 @@ var init_variants = __esm({
14592
14667
  VariantDefSchema = import_zod10.z.object({
14593
14668
  /** Variant id referenced by a component's `variant` (e.g. "publisher", "org/external-config-adapter"). */
14594
14669
  id: import_zod10.z.string().min(1),
14595
- /** The core stereotype this variant specializes — authoritative for generic semantics (Adapter, Specialist, …). */
14670
+ /** The core stereotype this variant specializes — authoritative for generic semantics (Adapter, Orchestrator, …). */
14596
14671
  base: import_zod10.z.string().min(1),
14597
14672
  /** How to implement a component of this variant — the recipe the implementer follows and reuses across same-variant components. */
14598
14673
  guidance: import_zod10.z.string().min(1),
@@ -16377,10 +16452,10 @@ var require_node = __commonJS({
16377
16452
  0
16378
16453
  ]);
16379
16454
  var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
16380
- var freb = function(eb, start) {
16455
+ var freb = function(eb, start2) {
16381
16456
  var b = new u16(31);
16382
16457
  for (var i2 = 0; i2 < 31; ++i2) {
16383
- b[i2] = start += 1 << eb[i2 - 1];
16458
+ b[i2] = start2 += 1 << eb[i2 - 1];
16384
16459
  }
16385
16460
  var r = new i32(b[30]);
16386
16461
  for (var i2 = 1; i2 < 30; ++i2) {
@@ -24775,7 +24850,7 @@ NOTICE:
24775
24850
  name: import_zod11.z.string().describe("Human-readable display name"),
24776
24851
  description: import_zod11.z.string().describe("Responsibility / internal architecture details"),
24777
24852
  subsystem: import_zod11.z.string().describe("The L1 subsystem ID this component belongs to"),
24778
- componentType: import_zod11.z.enum(["Portal", "Orchestrator", "Supervisor", "Actor", "Store", "Index", "Registry", "Adapter", "Observer", "Specialist", "Repository", "Gateway"]).describe("The building block, or pattern (Repository/Gateway)"),
24853
+ componentType: import_zod11.z.enum(["Portal", "Orchestrator", "Supervisor", "Actor", "Store", "Index", "Query", "Registry", "Adapter", "Observer", "Repository"]).describe("The building block, or the pattern Repository. Specialist and Gateway are retired and cannot be authored: logic is an Orchestrator with a dependencyClass, a gateway is a Portal with the gateway variant"),
24779
24854
  owns: import_zod11.z.array(import_zod11.z.string()).optional().describe("Member block ids privately owned by this component (patterns only)"),
24780
24855
  dependsOn: import_zod11.z.array(import_zod11.z.string()).optional().describe("IDs of other components this collaborates with (facades or standalone blocks)"),
24781
24856
  portalType: import_zod11.z.enum(["HTTP_API", "gRPC", "GraphQL", "MessageBus", "CLI", "NamedPipe", "IPC", "Custom"]).optional().describe("Portal-only, and expected on every Portal. OMIT IT on any other componentType \u2014 passing it there is refused at the write (UNEXPECTED_PORTAL_FIELD), nothing is saved."),
@@ -24787,6 +24862,7 @@ NOTICE:
24787
24862
  description: import_zod11.z.string().optional()
24788
24863
  })).optional().describe("Portal-only: capability \u2192 component.method dispatch table for generic-handle portals. Gives the reachability walker real edges and is validated against target interfaces (UNSERVED_CAPABILITY)."),
24789
24864
  durability: import_zod11.z.enum(["ram-projection", "durable", "read-through", "cache"]).optional().describe("Store-only \u2014 OMIT IT on any other componentType, where it is refused at the write (DURABILITY_ON_NON_STORE) and nothing is saved. Every Store should declare one (MISSING_DURABILITY): durable = persisted RAM projection (hydration read-back from a lifecycle init entrypoint required \u2014 MISSING_HYDRATION); read-through = persisted with no RAM copy (every read is the read-back, hydration exempt); ram-projection = rebuilt not restored; cache = evictable loss-safe memo state."),
24865
+ dependencyClass: import_zod11.z.enum(["pure", "read"]).optional().describe("Orchestrator-only \u2014 OMIT IT on any other componentType, where it is refused at the write (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR) and nothing is saved. Declares what logic may depend on, enforced as a Store's durability is (DEPENDENCY_CLASS_VIOLATION): pure = depends only on pure Orchestrators (a computation over the values it is handed, e.g. an arbiter or codec); read = also on read Orchestrators, Repositories, Indexes and Adapters, never calling their write methods; unset = a workflow."),
24790
24866
  emits: import_zod11.z.array(import_zod11.z.object({
24791
24867
  topic: import_zod11.z.string().describe("Topic/channel name exactly as used on the bus"),
24792
24868
  event: import_zod11.z.string().optional().describe("Optional event name within the topic (informational; pairing is by topic)"),
@@ -24804,10 +24880,10 @@ NOTICE:
24804
24880
  server,
24805
24881
  "sdd_add_component",
24806
24882
  {
24807
- description: `Add an L2 Component under a subsystem. componentType is a building block (Portal, Orchestrator, Supervisor, Actor, Store, Index, Registry, Adapter, Observer, Specialist) or a pattern (Repository, Gateway). Patterns set "owns" (their private member blocks); all components set "dependsOn" (collaborators \u2014 facades or standalone blocks). Held/persisted state (configs, permissions, sessions, caches): model the Repository recipe \u2014 a Store + Registry (write) + Index (read) owned by a Repository facade consumers depend on; a deliberately standalone Store is the sanctioned lightweight form (workflow-layer consumers + lint.allow on UNOWNED_STORE). Never hold state as fields inside an Orchestrator/Specialist because a Store link was refused. Re-running it on an existing id RE-AUTHORS it: the fields above are replaced (an omitted array is CLEARED), while lint.allow, a Portal's auth, variant, patterns and externalLinks are carried forward \u2014 edit those with sdd_update_spec.`,
24883
+ description: `Add an L2 Component under a subsystem. componentType is a building block (Portal, Orchestrator, Supervisor, Actor, Store, Index, Query, Registry, Adapter, Observer) or the pattern Repository. Specialist and Gateway are retired (STEREOTYPE_RETIRED) and cannot be authored: logic is an Orchestrator with a dependencyClass (pure | read; unset = a workflow), and a gateway is a Portal with the gateway variant (set variant with sdd_update_spec). Patterns set "owns" (their private member blocks); all components set "dependsOn" (collaborators \u2014 facades or standalone blocks). Held/persisted state (configs, permissions, sessions, caches): model the Repository recipe \u2014 a Store + Registry (write) + Index (read), plus a Query for computed reads over the Store, owned by a Repository facade consumers depend on; a deliberately standalone Store is the sanctioned lightweight form (workflow-layer consumers + lint.allow on UNOWNED_STORE). Never hold state as fields inside an Orchestrator because a Store link was refused. Re-running it on an existing id RE-AUTHORS it: the fields above are replaced (an omitted array is CLEARED), while lint.allow, a Portal's auth, variant, patterns and externalLinks are carried forward \u2014 edit those with sdd_update_spec.`,
24808
24884
  inputSchema: componentInput
24809
24885
  },
24810
- ({ id, name, description, subsystem, componentType, owns, dependsOn, portalType, basePath, dispatch, durability, emits, subscribesTo, ext }) => {
24886
+ ({ id, name, description, subsystem, componentType, owns, dependsOn, portalType, basePath, dispatch, durability, dependencyClass, emits, subscribesTo, ext }) => {
24811
24887
  try {
24812
24888
  const { loadSubsystemSpec: loadSubsystemSpec2, loadComponentSpec: loadComponentSpec2 } = requireSpecs();
24813
24889
  const sub = loadSubsystemSpec2(subsystem);
@@ -24825,6 +24901,7 @@ NOTICE:
24825
24901
  ...basePath ? { basePath } : {},
24826
24902
  ...dispatch ? { dispatch } : {},
24827
24903
  ...durability ? { durability } : {},
24904
+ ...dependencyClass ? { dependencyClass } : {},
24828
24905
  ...emits ? { emits } : {},
24829
24906
  ...subscribesTo ? { subscribesTo } : {},
24830
24907
  ...ext ? { ext } : {},
@@ -24876,7 +24953,7 @@ NOTICE:
24876
24953
  code: import_zod11.z.string().describe("UPPER_SNAKE finding code, unique within the method; a pack's codes carry the pack prefix (<PACK>_<CODE>)"),
24877
24954
  severity: import_zod11.z.enum(["error", "warning"]).describe("Default severity, before project severity overrides and draft-context downgrades"),
24878
24955
  summary: import_zod11.z.string().describe("One line saying what the finding means")
24879
- })).optional().describe("The finding codes this method can report, each with its default severity and summary. Each declared code must appear as a string literal in the method's source file (UNREALIZED_FINDING). A code is declared once per method; sdd_update_spec upserts and deletes findings by code."),
24956
+ })).optional().describe("The finding codes this method can report, each with its default severity and summary. Each declared code must be anchored in the method's source file, as a string literal or a property-access name (UNREALIZED_FINDING). A code is declared once per method; sdd_update_spec upserts and deletes findings by code."),
24880
24957
  ext: import_zod11.z.record(import_zod11.z.unknown()).optional().describe("Opaque pack/tool extension data for this method (namespaced keys) \u2014 preserved verbatim")
24881
24958
  };
24882
24959
  const interfaceMethodInputFields = Object.keys(interfaceMethodShape);
@@ -24892,7 +24969,7 @@ NOTICE:
24892
24969
  server,
24893
24970
  "sdd_define_interface",
24894
24971
  {
24895
- description: "Define an L3 Contract / Interface with method signatures for a component. Prefer supplying structured `params` per method \u2014 they are the authoritative source for type checking (the free-form signature string then becomes display-only and is never heuristically parsed). A method declares the finding codes it reports in `findings` ({code, severity, summary}); each code must appear as a string literal in the method's source file. Re-defining an existing id REPLACES the method list: a method left out of the input is REMOVED (and reported); spec-level lint/ext and each method's endpoint binding are carried forward.",
24972
+ description: "Define an L3 Contract / Interface with method signatures for a component. Prefer supplying structured `params` per method \u2014 they are the authoritative source for type checking (the free-form signature string then becomes display-only and is never heuristically parsed). A method declares the finding codes it reports in `findings` ({code, severity, summary}); each code must be anchored in the method's source file, as a string literal or a property-access name (UNREALIZED_FINDING). Re-defining an existing id REPLACES the method list: a method left out of the input is REMOVED (and reported); spec-level lint/ext and each method's endpoint binding are carried forward.",
24896
24973
  inputSchema: interfaceInput
24897
24974
  },
24898
24975
  ({ id, name, description, component, methods }) => {
@@ -27526,14 +27603,14 @@ var http = __toESM(require("http"));
27526
27603
  var https = __toESM(require("https"));
27527
27604
  init_defaults();
27528
27605
  function downloadFile(url, dest) {
27529
- return new Promise((resolve31, reject) => {
27606
+ return new Promise((resolve32, reject) => {
27530
27607
  const file = fs23.createWriteStream(dest);
27531
27608
  const get4 = url.startsWith("https://") ? https.get : http.get;
27532
27609
  get4(url, { headers: { "User-Agent": `wairon/${WAIRON_VERSION}` }, agent: false }, (res) => {
27533
27610
  if (res.statusCode === 301 || res.statusCode === 302) {
27534
27611
  file.close();
27535
27612
  res.destroy();
27536
- downloadFile(res.headers.location, dest).then(resolve31).catch(reject);
27613
+ downloadFile(res.headers.location, dest).then(resolve32).catch(reject);
27537
27614
  return;
27538
27615
  }
27539
27616
  if (res.statusCode !== 200) {
@@ -27545,7 +27622,7 @@ function downloadFile(url, dest) {
27545
27622
  res.pipe(file);
27546
27623
  file.on("finish", () => {
27547
27624
  res.destroy();
27548
- file.close(() => resolve31());
27625
+ file.close(() => resolve32());
27549
27626
  });
27550
27627
  file.on("error", (err) => {
27551
27628
  res.destroy();
@@ -27722,7 +27799,7 @@ function releaseChannelLabel(tag) {
27722
27799
  return version.slice(dash + 1).replace(/\.\d+$/, "") || "stable";
27723
27800
  }
27724
27801
  function fetchReleases(repo) {
27725
- return new Promise((resolve31, reject) => {
27802
+ return new Promise((resolve32, reject) => {
27726
27803
  const url = `https://api.github.com/repos/${repo}/releases?per_page=100`;
27727
27804
  const options = {
27728
27805
  headers: {
@@ -27742,7 +27819,7 @@ function fetchReleases(repo) {
27742
27819
  return;
27743
27820
  }
27744
27821
  try {
27745
- resolve31(JSON.parse(data));
27822
+ resolve32(JSON.parse(data));
27746
27823
  } catch {
27747
27824
  reject(new Error("Failed to parse GitHub API response"));
27748
27825
  }
@@ -27917,7 +27994,7 @@ async function filteredCheckbox(config) {
27917
27994
  32,
27918
27995
  Math.max(...items.map((i) => i.label.length))
27919
27996
  );
27920
- return new Promise((resolve31) => {
27997
+ return new Promise((resolve32) => {
27921
27998
  const checked = /* @__PURE__ */ new Set();
27922
27999
  let cursor = 0;
27923
28000
  let filterIdx = 0;
@@ -27984,7 +28061,7 @@ async function filteredCheckbox(config) {
27984
28061
  );
27985
28062
  teardown();
27986
28063
  const result = items.filter((_, idx) => checked.has(idx)).map((i) => i.value);
27987
- resolve31(result);
28064
+ resolve32(result);
27988
28065
  }
27989
28066
  function abort() {
27990
28067
  process.stdout.write("\n");
@@ -36141,7 +36218,7 @@ async function awaitApproval(cfg, credential, requestId, timeoutSeconds) {
36141
36218
  let current2 = req;
36142
36219
  while (current2.status === "pending" && Date.now() < deadline) {
36143
36220
  const remaining = deadline - Date.now();
36144
- await new Promise((resolve31) => setTimeout(resolve31, Math.min(AWAIT_POLL_INTERVAL_MS, remaining)));
36221
+ await new Promise((resolve32) => setTimeout(resolve32, Math.min(AWAIT_POLL_INTERVAL_MS, remaining)));
36145
36222
  expirePendingApprovals(cfg.dataDir, (/* @__PURE__ */ new Date()).toISOString());
36146
36223
  current2 = getApprovalRequestById(cfg.dataDir, requestId) ?? current2;
36147
36224
  }
@@ -41391,6 +41468,52 @@ function serveSharedDownload(cfg, token, kind, req, res) {
41391
41468
  res.end(result.content);
41392
41469
  }
41393
41470
 
41471
+ // src/server/backup-schedule.ts
41472
+ var TICK_MS = 6e4;
41473
+ var timer;
41474
+ function sweep(config) {
41475
+ try {
41476
+ runPeriodicGitSync(config);
41477
+ } catch (err) {
41478
+ console.error("[git-sync] project sweep failed: " + (err instanceof Error ? err.message : String(err)));
41479
+ }
41480
+ try {
41481
+ runPeriodicBackingSync(config);
41482
+ } catch (err) {
41483
+ console.error("[git-backing] backing sweep failed: " + (err instanceof Error ? err.message : String(err)));
41484
+ }
41485
+ }
41486
+ function start(config) {
41487
+ if (timer) clearInterval(timer);
41488
+ timer = setInterval(() => sweep(config), TICK_MS);
41489
+ timer.unref();
41490
+ }
41491
+ function stop() {
41492
+ if (timer) {
41493
+ clearInterval(timer);
41494
+ timer = void 0;
41495
+ }
41496
+ }
41497
+
41498
+ // src/server/instance-bootstrap.ts
41499
+ var DEV_UNIT_ID = "local";
41500
+ function bootstrapInstance(cfg) {
41501
+ ensureInstanceIdentity(cfg.dataDir);
41502
+ if (cfg.devMode && !getOrganizationUnit(cfg.dataDir, DEV_UNIT_ID)) {
41503
+ const system = { userId: "system", kind: "service", issuer: "local" };
41504
+ createUnit(cfg.dataDir, {
41505
+ id: DEV_UNIT_ID,
41506
+ name: "Local Development",
41507
+ kind: "team",
41508
+ slug: DEV_UNIT_ID,
41509
+ // a root unit's qualified id IS its slug
41510
+ status: "active",
41511
+ createdAt: "",
41512
+ createdBy: system
41513
+ });
41514
+ }
41515
+ }
41516
+
41394
41517
  // src/server/http.ts
41395
41518
  var MAX_BODY_BYTES = 8 * 1024 * 1024;
41396
41519
  var PayloadTooLargeError = class extends Error {
@@ -41815,23 +41938,6 @@ async function routeAdmin(cfg, req, res) {
41815
41938
  sendJson(res, 400, { error: err instanceof Error ? err.message : String(err) });
41816
41939
  }
41817
41940
  }
41818
- var DEV_UNIT_ID = "local";
41819
- function initHostInstance(cfg) {
41820
- ensureInstanceIdentity(cfg.dataDir);
41821
- if (cfg.devMode && !getOrganizationUnit(cfg.dataDir, DEV_UNIT_ID)) {
41822
- const system = { userId: "system", kind: "service", issuer: "local" };
41823
- createUnit(cfg.dataDir, {
41824
- id: DEV_UNIT_ID,
41825
- name: "Local Development",
41826
- kind: "team",
41827
- slug: DEV_UNIT_ID,
41828
- // a root unit's qualified id IS its slug
41829
- status: "active",
41830
- createdAt: "",
41831
- createdBy: system
41832
- });
41833
- }
41834
- }
41835
41941
  function warnIfDataDirUnmigrated(cfg) {
41836
41942
  try {
41837
41943
  const report2 = migratePermissionModel(cfg.dataDir, false);
@@ -41866,7 +41972,7 @@ function startHostServer(cfg) {
41866
41972
  );
41867
41973
  }
41868
41974
  }
41869
- initHostInstance(cfg);
41975
+ bootstrapInstance(cfg);
41870
41976
  warnIfDataDirUnmigrated(cfg);
41871
41977
  const dataServer = http2.createServer((req, res) => routeData(cfg, req, res));
41872
41978
  const adminServer = http2.createServer((req, res) => {
@@ -41877,23 +41983,10 @@ function startHostServer(cfg) {
41877
41983
  });
41878
41984
  dataServer.listen(cfg.port, cfg.host);
41879
41985
  adminServer.listen(cfg.adminPort, cfg.adminHost);
41880
- const GIT_SYNC_TICK_MS = 6e4;
41881
- const gitSyncTimer = setInterval(() => {
41882
- try {
41883
- runPeriodicGitSync(cfg);
41884
- } catch (err) {
41885
- console.error("[git-sync] project sweep failed: " + (err instanceof Error ? err.message : String(err)));
41886
- }
41887
- try {
41888
- runPeriodicBackingSync(cfg);
41889
- } catch (err) {
41890
- console.error("[git-backing] backing sweep failed: " + (err instanceof Error ? err.message : String(err)));
41891
- }
41892
- }, GIT_SYNC_TICK_MS);
41893
- gitSyncTimer.unref();
41986
+ start(cfg);
41894
41987
  return {
41895
41988
  close() {
41896
- clearInterval(gitSyncTimer);
41989
+ stop();
41897
41990
  getRealtimeHub().closeAll();
41898
41991
  dataServer.close();
41899
41992
  adminServer.close();
@@ -42454,11 +42547,11 @@ async function runServe(options = {}) {
42454
42547
  logger.info(` data dir: ${import_chalk17.default.gray(cfg.dataDir)}`);
42455
42548
  logger.blank();
42456
42549
  logger.info("Press Ctrl+C to stop.");
42457
- await new Promise((resolve31) => {
42550
+ await new Promise((resolve32) => {
42458
42551
  const shutdown = () => {
42459
42552
  logger.info("Shutting down\u2026");
42460
42553
  handle.close();
42461
- resolve31();
42554
+ resolve32();
42462
42555
  };
42463
42556
  process.on("SIGINT", shutdown);
42464
42557
  process.on("SIGTERM", shutdown);
@@ -42523,11 +42616,11 @@ async function runDev(options = {}) {
42523
42616
  logger.blank();
42524
42617
  logger.info("An agent edits specs; refresh the page to see the live graph. Press Ctrl+C to stop.");
42525
42618
  if (options.open) openBrowser(url);
42526
- await new Promise((resolve31) => {
42619
+ await new Promise((resolve32) => {
42527
42620
  const shutdown = () => {
42528
42621
  logger.info("Shutting down\u2026");
42529
42622
  handle.close();
42530
- resolve31();
42623
+ resolve32();
42531
42624
  };
42532
42625
  process.on("SIGINT", shutdown);
42533
42626
  process.on("SIGTERM", shutdown);