@wairon/cli 5.1.1-dev.94 → 5.1.1-dev.95

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.94";
68
+ WAIRON_VERSION = "5.1.1-dev.95";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  SUPPORTED_ALIASES = ["wai"];
71
71
  SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
@@ -3169,6 +3169,49 @@ function conceptNoun(component) {
3169
3169
  }
3170
3170
  return "";
3171
3171
  }
3172
+ function requireStoredSignature(method2, ctx) {
3173
+ if (method2.signatureFrom !== void 0) return;
3174
+ if (method2.returns === void 0) {
3175
+ ctx.addIssue({ code: import_zod8.z.ZodIssueCode.custom, path: ["returns"], message: "Required: a method without a signatureFrom states its returns" });
3176
+ }
3177
+ if (method2.params === void 0 && method2.signature === void 0) {
3178
+ ctx.addIssue({ code: import_zod8.z.ZodIssueCode.custom, path: ["signature"], message: "Required: a method without params or a signatureFrom states a prose signature" });
3179
+ }
3180
+ }
3181
+ function paramText(p) {
3182
+ return `${p.name}${p.optional ? "?" : ""}: ${p.type}`;
3183
+ }
3184
+ function leadingGenericList(name, stored) {
3185
+ if (!stored) return "";
3186
+ const text3 = stored.trimStart();
3187
+ if (!text3.startsWith(name)) return "";
3188
+ let i = name.length;
3189
+ while (text3[i] === " ") i++;
3190
+ if (text3[i] !== "<") return "";
3191
+ let depth = 0;
3192
+ for (let j = i; j < text3.length; j++) {
3193
+ if (text3[j] === "<") depth++;
3194
+ else if (text3[j] === ">") depth--;
3195
+ if (depth === 0) {
3196
+ const rest = text3.slice(j + 1).trimStart();
3197
+ return rest.startsWith("(") ? text3.slice(i, j + 1) : "";
3198
+ }
3199
+ }
3200
+ return "";
3201
+ }
3202
+ function deriveMethodSignature(method2) {
3203
+ if (!method2.params) return void 0;
3204
+ const generics = leadingGenericList(method2.name, method2.signature);
3205
+ return `${method2.name}${generics}(${method2.params.map(paramText).join(", ")}): ${method2.returns ?? "unknown"}`;
3206
+ }
3207
+ function storedMethodSignature(method2) {
3208
+ if (method2.signatureFrom !== void 0) {
3209
+ const { params: _params, returns: _returns, signature: _signature, ...rest } = method2;
3210
+ return rest;
3211
+ }
3212
+ const derived = deriveMethodSignature(method2);
3213
+ return derived === void 0 ? method2 : { ...method2, signature: derived };
3214
+ }
3172
3215
  function passesIntentFloor(text3, methodName) {
3173
3216
  if (!text3) return false;
3174
3217
  const t = text3.trim();
@@ -3238,6 +3281,14 @@ function implementationSourceFiles(impl) {
3238
3281
  for (const method2 of impl.methods ?? []) add2(method2.sourcePath);
3239
3282
  return files;
3240
3283
  }
3284
+ function deriveTypeSignature(type) {
3285
+ if (type.kind !== "signature") return void 0;
3286
+ return `(${(type.params ?? []).map(paramText).join(", ")}): ${type.returns ?? "unknown"}`;
3287
+ }
3288
+ function storedTypeMethod(method2) {
3289
+ const derived = deriveMethodSignature(method2);
3290
+ return derived === void 0 ? method2 : { ...method2, signature: derived };
3291
+ }
3241
3292
  function typeSourceFiles(type) {
3242
3293
  const files = [];
3243
3294
  const add2 = (file) => {
@@ -3253,7 +3304,7 @@ function relationHealth(status2) {
3253
3304
  if (status2.drifted) return "drifted";
3254
3305
  return "ok";
3255
3306
  }
3256
- var import_zod8, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, RETIRED_STEREOTYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, DependencyClassSchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, PortalMountSchema, ComponentSpecSchema, BLOCK_NOUNS, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, FindingDeclarationSchema, MethodSignatureSchema, INTENT_FLOOR_MIN_CHARS, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, TechnologySchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceTypeExportSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, ExternalLockEntrySchema, ExternalsLockSchema, ParentExcerptSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
3307
+ var import_zod8, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, RETIRED_STEREOTYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, DependencyClassSchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, PortalMountSchema, ComponentSpecSchema, BLOCK_NOUNS, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, FindingDeclarationSchema, MethodSignatureSchema, StoredMethodSignatureSchema, ResolvedMethodSignatureSchema, INTENT_FLOOR_MIN_CHARS, InterfaceSpecSchema, ResolvedInterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, TechnologySchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, ResolvedTypeMethodSchema, StoredTypeMethodSchema, InvariantSchema, TypeSpecSchema, ResolvedTypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceTypeExportSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, ExternalLockEntrySchema, ExternalsLockSchema, ParentExcerptSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
3257
3308
  var init_specs = __esm({
3258
3309
  "src/models/specs.ts"() {
3259
3310
  "use strict";
@@ -3651,13 +3702,21 @@ var init_specs = __esm({
3651
3702
  MethodSignatureSchema = import_zod8.z.object({
3652
3703
  name: import_zod8.z.string().regex(/^[a-zA-Z0-9_]+$/, "Method name must be alphanumeric"),
3653
3704
  description: import_zod8.z.string(),
3654
- signature: import_zod8.z.string(),
3705
+ signature: import_zod8.z.string().optional(),
3655
3706
  // e.g. "save(key: string, data: Buffer): Promise<void>"
3656
3707
  // e.g. "Promise<void>", or a union: "Invoice | null" — the commonest shape in
3657
3708
  // any real tree. See the grammar on src/models/type-references.ts.
3658
- returns: import_zod8.z.string(),
3709
+ returns: import_zod8.z.string().optional(),
3659
3710
  /** Structured parameters (authoritative for type checking when present). */
3660
3711
  params: import_zod8.z.array(MethodParamSchema).optional(),
3712
+ /**
3713
+ * Where this method takes its params and returns from instead of stating
3714
+ * them: a signature type (`billing.change_listener`, `alias::name`), or a
3715
+ * contract method `component.method` its component reaches along a
3716
+ * dependsOn/owns edge. Resolved both ways by the loader (signature_resolver);
3717
+ * the writer stores a sourced method with only its source.
3718
+ */
3719
+ signatureFrom: import_zod8.z.string().min(1).optional(),
3661
3720
  /** Concrete wire binding for this method when its component is a Portal (set via sdd_set_endpoints). */
3662
3721
  endpoint: EndpointSchema.optional(),
3663
3722
  /**
@@ -3710,6 +3769,11 @@ var init_specs = __esm({
3710
3769
  /** Opaque pack/tool extension data (see ExtDataSchema) — preserved verbatim. */
3711
3770
  ext: ExtDataSchema.optional()
3712
3771
  });
3772
+ StoredMethodSignatureSchema = MethodSignatureSchema.superRefine(requireStoredSignature);
3773
+ ResolvedMethodSignatureSchema = MethodSignatureSchema.extend({
3774
+ signature: import_zod8.z.string(),
3775
+ returns: import_zod8.z.string()
3776
+ });
3713
3777
  INTENT_FLOOR_MIN_CHARS = 40;
3714
3778
  InterfaceSpecSchema = import_zod8.z.object({
3715
3779
  id: SpecIdSchema.regex(/^i[a-z0-9-_]+$/, 'Interface id must be prefixed with a lowercase "i"'),
@@ -3717,7 +3781,7 @@ var init_specs = __esm({
3717
3781
  description: import_zod8.z.string(),
3718
3782
  component: import_zod8.z.string(),
3719
3783
  // References L2 Component id
3720
- methods: import_zod8.z.array(MethodSignatureSchema).default([]),
3784
+ methods: import_zod8.z.array(StoredMethodSignatureSchema).default([]),
3721
3785
  /** Per-spec lint suppressions (see LintConfigSchema). */
3722
3786
  lint: LintConfigSchema.optional(),
3723
3787
  /** Opaque pack/tool extension data (see ExtDataSchema) — preserved verbatim. */
@@ -3726,6 +3790,9 @@ var init_specs = __esm({
3726
3790
  createdAt: import_zod8.z.string().datetime(),
3727
3791
  updatedAt: import_zod8.z.string().datetime()
3728
3792
  });
3793
+ ResolvedInterfaceSpecSchema = InterfaceSpecSchema.extend({
3794
+ methods: import_zod8.z.array(ResolvedMethodSignatureSchema).default([])
3795
+ });
3729
3796
  NarrativeStepTypeSchema = import_zod8.z.enum([
3730
3797
  "local",
3731
3798
  // in-component work
@@ -4002,7 +4069,7 @@ var init_specs = __esm({
4002
4069
  createdAt: import_zod8.z.string().datetime(),
4003
4070
  updatedAt: import_zod8.z.string().datetime()
4004
4071
  });
4005
- TypeKindSchema = import_zod8.z.enum(["entity", "value-object"]);
4072
+ TypeKindSchema = import_zod8.z.enum(["entity", "value-object", "signature"]);
4006
4073
  TypeFieldSchema = import_zod8.z.object({
4007
4074
  name: import_zod8.z.string(),
4008
4075
  // A primitive, or another type id (qualified across subsystems, e.g.
@@ -4026,7 +4093,9 @@ var init_specs = __esm({
4026
4093
  });
4027
4094
  TypeMethodSchema = import_zod8.z.object({
4028
4095
  name: import_zod8.z.string(),
4029
- signature: import_zod8.z.string(),
4096
+ signature: import_zod8.z.string().optional(),
4097
+ /** Structured parameters in the contract method's param shape — authoritative for type checking when present. */
4098
+ params: import_zod8.z.array(MethodParamSchema).optional(),
4030
4099
  returns: import_zod8.z.string(),
4031
4100
  description: import_zod8.z.string().optional(),
4032
4101
  /**
@@ -4045,6 +4114,14 @@ var init_specs = __esm({
4045
4114
  */
4046
4115
  symbol: import_zod8.z.string().optional()
4047
4116
  });
4117
+ ResolvedTypeMethodSchema = TypeMethodSchema.extend({
4118
+ signature: import_zod8.z.string()
4119
+ });
4120
+ StoredTypeMethodSchema = TypeMethodSchema.superRefine((method2, ctx) => {
4121
+ if (method2.params === void 0 && method2.signature === void 0) {
4122
+ ctx.addIssue({ code: import_zod8.z.ZodIssueCode.custom, path: ["signature"], message: "Required: a type method without params states a prose signature" });
4123
+ }
4124
+ });
4048
4125
  InvariantSchema = import_zod8.z.object({
4049
4126
  /** Stable invariant id, unique within the entity (referenced as "<type-id>.<invariant-id>"). */
4050
4127
  id: SpecIdSchema,
@@ -4053,7 +4130,7 @@ var init_specs = __esm({
4053
4130
  });
4054
4131
  TypeSpecSchema = import_zod8.z.object({
4055
4132
  kind: TypeKindSchema,
4056
- // discriminator — entity | value-object
4133
+ // discriminator — entity | value-object | signature
4057
4134
  id: SpecIdSchema,
4058
4135
  name: import_zod8.z.string(),
4059
4136
  description: import_zod8.z.string().optional(),
@@ -4063,7 +4140,7 @@ var init_specs = __esm({
4063
4140
  group: import_zod8.z.string().optional(),
4064
4141
  fields: import_zod8.z.array(TypeFieldSchema).default([]),
4065
4142
  /** Pure intrinsic behaviour only — anything needing a collaborator belongs on a component. */
4066
- methods: import_zod8.z.array(TypeMethodSchema).default([]),
4143
+ methods: import_zod8.z.array(StoredTypeMethodSchema).default([]),
4067
4144
  /**
4068
4145
  * Linked Component ID if this system entity is implemented as a class Component
4069
4146
  * (e.g., a Store or Registry that owns this entity's lifecycle and methods).
@@ -4109,9 +4186,19 @@ var init_specs = __esm({
4109
4186
  lint: LintConfigSchema.optional(),
4110
4187
  /** Opaque pack/tool extension data (see ExtDataSchema) — preserved verbatim. */
4111
4188
  ext: ExtDataSchema.optional(),
4189
+ /**
4190
+ * A signature's parameters, in the contract method's param shape; only on
4191
+ * kind signature (SIGNATURE_TYPE_MEMBERS otherwise).
4192
+ */
4193
+ params: import_zod8.z.array(MethodParamSchema).optional(),
4194
+ /** A signature's one output type; required on kind signature and only there (SIGNATURE_TYPE_MEMBERS). */
4195
+ returns: import_zod8.z.string().optional(),
4112
4196
  createdAt: import_zod8.z.string().datetime(),
4113
4197
  updatedAt: import_zod8.z.string().datetime()
4114
4198
  });
4199
+ ResolvedTypeSpecSchema = TypeSpecSchema.extend({
4200
+ methods: import_zod8.z.array(ResolvedTypeMethodSchema).default([])
4201
+ });
4115
4202
  SurfaceOriginSchema = import_zod8.z.enum(["generated", "exchanged", "authored"]);
4116
4203
  SurfaceTypeDefSchema = import_zod8.z.object({
4117
4204
  id: import_zod8.z.string(),
@@ -4122,7 +4209,16 @@ var init_specs = __esm({
4122
4209
  type: import_zod8.z.string(),
4123
4210
  description: import_zod8.z.string().optional(),
4124
4211
  optional: import_zod8.z.boolean().optional()
4125
- })).default([])
4212
+ })).default([]),
4213
+ /** A signature's parameters (name, type, optional, description) in declared order; only on kind signature. */
4214
+ params: import_zod8.z.array(import_zod8.z.object({
4215
+ name: import_zod8.z.string(),
4216
+ type: import_zod8.z.string(),
4217
+ description: import_zod8.z.string().optional(),
4218
+ optional: import_zod8.z.boolean().optional()
4219
+ })).optional(),
4220
+ /** A signature's one output type; only on kind signature. */
4221
+ returns: import_zod8.z.string().optional()
4126
4222
  });
4127
4223
  SurfaceTypeExportSchema = import_zod8.z.object({
4128
4224
  /** The type's public name in the producer's export table. */
@@ -4141,8 +4237,8 @@ var init_specs = __esm({
4141
4237
  type: import_zod8.z.string().default("Custom"),
4142
4238
  /** Local name of the backing Portal in the producing project. */
4143
4239
  component: import_zod8.z.string(),
4144
- /** Full contract methods (params, returns, guarantees, effect, endpoint). */
4145
- methods: import_zod8.z.array(MethodSignatureSchema).default([]),
4240
+ /** Full contract methods (params, returns, guarantees, effect, endpoint), resolved: a snapshot names no producer-internal source. */
4241
+ methods: import_zod8.z.array(ResolvedMethodSignatureSchema).default([]),
4146
4242
  /** The backing portal's capability dispatch table, when generic-dispatch. */
4147
4243
  dispatch: import_zod8.z.array(DispatchBindingSchema).optional(),
4148
4244
  details: import_zod8.z.string().default(""),
@@ -4317,13 +4413,18 @@ function methodTypeRefs(m) {
4317
4413
  for (const p of m.params) {
4318
4414
  refs.push(...extractTypeIdentifiers(p.type));
4319
4415
  }
4320
- refs.push(...extractTypeIdentifiers(m.returns));
4416
+ refs.push(...extractTypeIdentifiers(m.returns ?? ""));
4321
4417
  return Array.from(new Set(refs));
4322
4418
  }
4323
- return extractTypesFromSignature(m.signature, m.returns);
4419
+ return extractTypesFromSignature(m.signature ?? "", m.returns ?? "");
4420
+ }
4421
+ function signatureTypeRefs(type) {
4422
+ if (type.kind !== "signature") return [];
4423
+ const refs = [...(type.params ?? []).flatMap((p) => extractTypeIdentifiers(p.type)), ...extractTypeIdentifiers(type.returns ?? "")];
4424
+ return Array.from(new Set(refs));
4324
4425
  }
4325
4426
  function methodGenericParameters(method2) {
4326
- return extractGenericTypeVariables(method2.signature);
4427
+ return extractGenericTypeVariables(method2.signature ?? "");
4327
4428
  }
4328
4429
  function normalizePart(part) {
4329
4430
  return part.toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -4795,9 +4896,21 @@ function typeShape(snapshot, def) {
4795
4896
  return {
4796
4897
  id: def.id,
4797
4898
  kind: def.kind,
4798
- fields: sortedBy(def.fields, (f) => f.name).map((f) => ({ name: f.name, type: canonicalTypeRef(snapshot, f.type), optional: f.optional === true }))
4899
+ fields: sortedBy(def.fields, (f) => f.name).map((f) => ({ name: f.name, type: canonicalTypeRef(snapshot, f.type), optional: f.optional === true })),
4900
+ // A signature's shape: its params' types and optionality in order (never their names, as a method's), and its returns.
4901
+ ...def.kind === "signature" ? {
4902
+ params: (def.params ?? []).map((p) => ({ type: canonicalTypeRef(snapshot, p.type), optional: p.optional === true })),
4903
+ returns: canonicalTypeRef(snapshot, def.returns ?? "unknown")
4904
+ } : {}
4799
4905
  };
4800
4906
  }
4907
+ function typeDefExprs(def) {
4908
+ return [
4909
+ ...def.fields.map((f) => f.type),
4910
+ ...(def.params ?? []).map((p) => p.type),
4911
+ ...def.returns !== void 0 ? [def.returns] : []
4912
+ ];
4913
+ }
4801
4914
  function closureShapes(snapshot, exprs) {
4802
4915
  const byId = new Map(snapshot.types.map((def) => [def.id, def]));
4803
4916
  const seen = /* @__PURE__ */ new Map();
@@ -4806,7 +4919,7 @@ function closureShapes(snapshot, exprs) {
4806
4919
  const def = byId.get(queue.shift());
4807
4920
  if (!def || seen.has(def.id)) continue;
4808
4921
  seen.set(def.id, def);
4809
- for (const field of def.fields) queue.push(...extractTypeIdentifiers(canonicalTypeRef(snapshot, field.type)));
4922
+ for (const expr of typeDefExprs(def)) queue.push(...extractTypeIdentifiers(canonicalTypeRef(snapshot, expr)));
4810
4923
  }
4811
4924
  return [...seen.keys()].sort().map((id) => typeShape(snapshot, seen.get(id)));
4812
4925
  }
@@ -5779,6 +5892,7 @@ function buildCanvasModel(issues = [], relations) {
5779
5892
  signature: m.signature,
5780
5893
  returns: m.returns,
5781
5894
  ...m.params && m.params.length ? { params: m.params } : {},
5895
+ ...m.signatureFrom ? { signatureFrom: m.signatureFrom } : {},
5782
5896
  ...m.endpoint ? { endpoint: m.endpoint } : {},
5783
5897
  ...m.guarantees && m.guarantees.length ? { guarantees: m.guarantees } : {}
5784
5898
  }))
@@ -5844,6 +5958,7 @@ function buildCanvasModel(issues = [], relations) {
5844
5958
  ...f.references ? { references: f.references } : {}
5845
5959
  })),
5846
5960
  methods: t.methods.map((m) => ({ name: m.name, signature: m.signature, returns: m.returns, ...m.description ? { description: m.description } : {} })),
5961
+ ...t.kind === "signature" ? { signature: deriveTypeSignature(t) } : {},
5847
5962
  usedBy: usedByFor(t),
5848
5963
  ...t.componentClass ? { componentClass: t.componentClass } : {},
5849
5964
  ...t.database ? { database: t.database } : {},
@@ -7587,15 +7702,17 @@ var MODEL = __MODEL_JSON__;
7587
7702
  function tableShape(t) {
7588
7703
  var fields = visibleFields(t);
7589
7704
  var meths = det === 'full' ? t.methods : [];
7705
+ // A signature type is drawn with its derived text in place of a field list.
7706
+ var sig = t.signature && det !== 'names' ? t.signature : '';
7590
7707
  var head = t.name + ' \\u00AB' + t.kind + '\\u00BB';
7591
- var rows = fields.map(function (f) { return rowText(t, f); })
7708
+ var rows = (sig ? [sig] : []).concat(fields.map(function (f) { return rowText(t, f); }))
7592
7709
  .concat(meths.map(function (m) { return '\\u0192 ' + m.name + '(): ' + m.returns; }));
7593
7710
  var longest = head.length + 4;
7594
7711
  rows.forEach(function (r) { if (r.length > longest) longest = r.length; });
7595
7712
  var plain = rows.length === 0;
7596
7713
  var pw = Math.max(170, head.length * 6.8 + 26);
7597
7714
  var W = Math.max(210, Math.min(400, longest * 6.6 + 30));
7598
- return { fields: fields, meths: meths, head: head, plain: plain, w: plain ? pw : W, h: plain ? 40 : TH_H + fields.length * ROW_H + meths.length * ROW_H };
7715
+ return { fields: fields, meths: meths, sig: sig, head: head, plain: plain, w: plain ? pw : W, h: plain ? 40 : TH_H + (sig ? ROW_H : 0) + fields.length * ROW_H + meths.length * ROW_H };
7599
7716
  }
7600
7717
 
7601
7718
  // Emit one type table with its top-left at (ax, ay); returns its size.
@@ -7619,6 +7736,13 @@ var MODEL = __MODEL_JSON__;
7619
7736
  position: { x: ax + sh.w / 2, y: ay + TH_H / 2 }, classes: 'typeHead ' + kindCls + (dim ? ' dimmed' : ''), grabbable: false,
7620
7737
  });
7621
7738
  var ry = ay + TH_H;
7739
+ if (sh.sig) {
7740
+ eles.push({
7741
+ data: { id: 'TS~' + t.id, parent: 'T~' + t.id, label: sh.sig, w: sh.w, h: ROW_H, tw: sh.w - 14 },
7742
+ position: { x: ax + sh.w / 2, y: ry + ROW_H / 2 }, classes: 'typeRow methRow' + (dim ? ' dimmed' : ''), grabbable: false,
7743
+ });
7744
+ ry += ROW_H;
7745
+ }
7622
7746
  sh.fields.forEach(function (f) {
7623
7747
  var rid = 'TF~' + t.id + '~' + f.name;
7624
7748
  rowIds[rid] = 1;
@@ -9705,7 +9829,7 @@ var MODEL = __MODEL_JSON__;
9705
9829
  var mi = methodInfo(top.comp, top.method);
9706
9830
  var parts = ['<div class="fstep" style="opacity:.7">No step-by-step narrative \\u2014 showing intent / contract:</div>'];
9707
9831
  if (mi) {
9708
- parts.push('<div class="fstep"><code>' + escText(mi.m.signature) + '</code></div>');
9832
+ parts.push('<div class="fstep"><code>' + escText(mi.m.signature) + '</code>' + (mi.m.signatureFrom ? ' from <code>' + escText(mi.m.signatureFrom) + '</code>' : '') + '</div>');
9709
9833
  parts.push('<div class="fstep">' + escText(mi.m.description) + (mi.m.returns ? ' \\u2014 returns ' + escText(mi.m.returns) : '') + '</div>');
9710
9834
  }
9711
9835
  if (intent) parts.push('<div class="fstep" style="font-style:italic">' + escText(intent) + '</div>');
@@ -10046,6 +10170,7 @@ var MODEL = __MODEL_JSON__;
10046
10170
  : '<span class="chip" style="opacity:.6">no narrative</span>')
10047
10171
  + '</div>'
10048
10172
  + '<code>' + esc(m.signature) + '</code>'
10173
+ + (m.signatureFrom ? '<div class="mdesc">signature from <code style="display:inline">' + esc(m.signatureFrom) + '</code></div>' : '')
10049
10174
  + '<div class="mdesc">' + esc(m.description) + ' \\u2014 returns ' + typeRefHtml(m.returns) + '</div>'
10050
10175
  + (mIntent && !hasNarr ? '<div class="mdesc" style="font-style:italic">' + esc(mIntent) + '</div>' : '')
10051
10176
  + (m.params ? '<div class="mdesc">params: ' + m.params.map(function (p) { return esc(p.name) + ': ' + typeRefHtml(p.type); }).join(', ') + '</div>' : '')
@@ -10149,7 +10274,11 @@ var MODEL = __MODEL_JSON__;
10149
10274
  + '</div>';
10150
10275
  }).join('')
10151
10276
  : '<span class="desc">no fields</span>';
10152
- body += section('Fields', ty.fields.length, fieldsInner, true);
10277
+ if (ty.signature) {
10278
+ body += section('Signature', 1, '<div class="method"><code>' + esc(ty.signature) + '</code></div>', true);
10279
+ } else {
10280
+ body += section('Fields', ty.fields.length, fieldsInner, true);
10281
+ }
10153
10282
  if (ty.usedBy && ty.usedBy.length) {
10154
10283
  body += section('Used by methods', ty.usedBy.length, ty.usedBy.map(function (u) {
10155
10284
  return chip(u.component + '.' + u.method + '()', 'component', u.component);
@@ -10533,6 +10662,46 @@ function operationFor(method2, closureIds) {
10533
10662
  }
10534
10663
  return op;
10535
10664
  }
10665
+ function signatureComponent(t, closureIds) {
10666
+ const params = t.params ?? [];
10667
+ const returns = t.returns ?? "unknown";
10668
+ const text3 = `(${params.map((p) => `${p.name}${p.optional ? "?" : ""}: ${p.type}`).join(", ")}): ${returns}`;
10669
+ return {
10670
+ title: t.name,
10671
+ description: `A function type ${text3}. A function has no JSON form, so no JSON value of this type can be sent; its params and returns are under ${SIGNATURE_EXTENSION}.`,
10672
+ [SIGNATURE_EXTENSION]: {
10673
+ // Each type twice: as wairon wrote it (what a wairon reader decodes, exactly),
10674
+ // and as a $ref or schema (what an OpenAPI reader can follow).
10675
+ params: params.map((p) => ({
10676
+ name: p.name,
10677
+ type: p.type,
10678
+ schema: schemaFor(p.type, closureIds),
10679
+ ...p.optional ? { optional: true } : {},
10680
+ ...p.description ? { description: p.description } : {}
10681
+ })),
10682
+ returns: { type: returns, schema: schemaFor(returns, closureIds) }
10683
+ }
10684
+ };
10685
+ }
10686
+ function signatureFromComponent(id, schema) {
10687
+ const raw = schema[SIGNATURE_EXTENSION];
10688
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
10689
+ const ext = raw;
10690
+ const typeOf = (entry) => {
10691
+ if (typeof entry.type === "string" && entry.type.trim()) return entry.type;
10692
+ const schema2 = entry.schema;
10693
+ if (schema2 && typeof schema2 === "object" && Object.keys(schema2).length === 0) return "unknown";
10694
+ return typeRefFromSchema(schema2);
10695
+ };
10696
+ const params = (Array.isArray(ext.params) ? ext.params : []).filter((p) => !!p && typeof p === "object" && typeof p.name === "string").map((p) => ({
10697
+ name: p.name,
10698
+ type: typeOf(p),
10699
+ ...p.optional === true ? { optional: true } : {},
10700
+ ...typeof p.description === "string" ? { description: p.description } : {}
10701
+ }));
10702
+ const returns = ext.returns && typeof ext.returns === "object" && !Array.isArray(ext.returns) ? typeOf(ext.returns) : "unknown";
10703
+ return { id, name: typeof schema.title === "string" ? schema.title : id, kind: "signature", fields: [], params, returns };
10704
+ }
10536
10705
  function securitySchemeObject(auth) {
10537
10706
  if (auth.scheme === "none") return null;
10538
10707
  const desc = auth.description ? { description: auth.description } : {};
@@ -10604,7 +10773,7 @@ function renderDoc(snapshot, entries, closureIds, opts = {}) {
10604
10773
  }
10605
10774
  const schemas = {};
10606
10775
  for (const t of snapshot.types) {
10607
- schemas[t.id] = {
10776
+ schemas[t.id] = t.kind === "signature" ? signatureComponent(t, closureIds) : {
10608
10777
  type: "object",
10609
10778
  title: t.name,
10610
10779
  properties: Object.fromEntries(t.fields.map((f) => [f.name, schemaFor(f.type, closureIds)])),
@@ -10764,6 +10933,11 @@ function fromOpenApi(document, projectName) {
10764
10933
  const types = [];
10765
10934
  const schemas = parsed.components?.schemas ?? {};
10766
10935
  for (const [id, schema] of Object.entries(schemas)) {
10936
+ const signature = signatureFromComponent(id, schema);
10937
+ if (signature) {
10938
+ types.push(signature);
10939
+ continue;
10940
+ }
10767
10941
  const props = schema.properties ?? {};
10768
10942
  const required = new Set(schema.required ?? []);
10769
10943
  types.push({
@@ -10800,7 +10974,7 @@ function fromOpenApi(document, projectName) {
10800
10974
  types
10801
10975
  });
10802
10976
  }
10803
- var yaml2, PRIMITIVES;
10977
+ var yaml2, PRIMITIVES, SIGNATURE_EXTENSION;
10804
10978
  var init_openapi = __esm({
10805
10979
  "src/core/openapi.ts"() {
10806
10980
  "use strict";
@@ -10824,6 +10998,7 @@ var init_openapi = __esm({
10824
10998
  unknown: {},
10825
10999
  void: {}
10826
11000
  };
11001
+ SIGNATURE_EXTENSION = "x-wairon-signature";
10827
11002
  }
10828
11003
  });
10829
11004
 
@@ -10868,21 +11043,30 @@ function computeTypeClosure(entries, types, exported = []) {
10868
11043
  for (const field of spec.fields) {
10869
11044
  for (const ref of extractTypeIdentifiers(field.type)) enqueueRef(ref);
10870
11045
  }
11046
+ for (const param of spec.params ?? []) {
11047
+ for (const ref of extractTypeIdentifiers(param.type)) enqueueRef(ref);
11048
+ }
11049
+ if (spec.returns) {
11050
+ for (const ref of extractTypeIdentifiers(spec.returns)) enqueueRef(ref);
11051
+ }
10871
11052
  }
10872
11053
  const usedIds = /* @__PURE__ */ new Set();
10873
11054
  return [...included.entries()].map(([qualified, t]) => {
10874
11055
  const id = usedIds.has(t.id) ? qualified : t.id;
10875
11056
  usedIds.add(id);
11057
+ const describe3 = (v) => ({
11058
+ name: v.name,
11059
+ type: v.type,
11060
+ ...v.description ? { description: v.description } : {},
11061
+ ...v.optional ? { optional: true } : {}
11062
+ });
10876
11063
  return {
10877
11064
  id,
10878
11065
  name: t.name,
10879
11066
  kind: t.kind,
10880
- fields: t.fields.map((f) => ({
10881
- name: f.name,
10882
- type: f.type,
10883
- ...f.description ? { description: f.description } : {},
10884
- ...f.optional ? { optional: true } : {}
10885
- }))
11067
+ fields: t.fields.map(describe3),
11068
+ // A signature type travels complete: its params and returns with it.
11069
+ ...t.kind === "signature" ? { params: (t.params ?? []).map(describe3), returns: t.returns ?? "unknown" } : {}
10886
11070
  };
10887
11071
  });
10888
11072
  }
@@ -10895,7 +11079,7 @@ function boundProjectId() {
10895
11079
  }
10896
11080
  }
10897
11081
  function contractEntry(entry, comp, interfaces) {
10898
- const methods = interfaces.filter((i) => i.component === comp.id && (!entry.interface || i.id === entry.interface)).flatMap((i) => i.methods);
11082
+ const methods = interfaces.filter((i) => i.component === comp.id && (!entry.interface || i.id === entry.interface)).flatMap((i) => i.methods).map(({ signatureFrom: _source, ...method2 }) => method2);
10899
11083
  return {
10900
11084
  id: entry.publicName,
10901
11085
  name: entry.name ?? comp.name,
@@ -10965,7 +11149,12 @@ function canonicalReferences(snapshot) {
10965
11149
  ...m.params ? { params: m.params.map((p) => ({ ...p, type: canon(p.type) })) } : {}
10966
11150
  }))
10967
11151
  })),
10968
- types: snapshot.types.map((def) => ({ ...def, fields: def.fields.map((f) => ({ ...f, type: canon(f.type) })) }))
11152
+ types: snapshot.types.map((def) => ({
11153
+ ...def,
11154
+ fields: def.fields.map((f) => ({ ...f, type: canon(f.type) })),
11155
+ ...def.params ? { params: def.params.map((p) => ({ ...p, type: canon(p.type) })) } : {},
11156
+ ...def.returns !== void 0 ? { returns: canon(def.returns) } : {}
11157
+ }))
10969
11158
  };
10970
11159
  }
10971
11160
  function listSnapshots() {
@@ -13257,7 +13446,7 @@ function buildImplementationMethods(ctx) {
13257
13446
  const component = ctx.componentMap.get(contract.component);
13258
13447
  if (!component) continue;
13259
13448
  if (ctx.isInChainedSubproject(component.subsystem)) continue;
13260
- const draftContext = ctx.isImplementationDraft(impl);
13449
+ const draftContext3 = ctx.isImplementationDraft(impl);
13261
13450
  for (const method2 of impl.methods) {
13262
13451
  const sourceFile = methodSourceFile(method2, impl.sourcePath);
13263
13452
  out.push({
@@ -13265,7 +13454,7 @@ function buildImplementationMethods(ctx) {
13265
13454
  method: method2,
13266
13455
  component,
13267
13456
  ...sourceFile !== void 0 ? { sourceFile } : {},
13268
- draftContext
13457
+ draftContext: draftContext3
13269
13458
  });
13270
13459
  }
13271
13460
  }
@@ -13628,7 +13817,7 @@ var init_signature_type_references = __esm({
13628
13817
  signatureTypeReferencesRule = {
13629
13818
  name: "signature-type-references",
13630
13819
  judges: "design",
13631
- description: "Every type identifier an interface method signature names must resolve to a builtin, a generic parameter in scope on the interface or the method, or a defined entity/value-object type.",
13820
+ description: "Every type identifier a method signature names must resolve to a builtin, a generic parameter in scope on the interface or the method, or a defined type: an interface method's params and returns, a type method's params when it has them, and a signature type's params and returns. A method that takes its signature from a source is skipped \u2014 its params are its source's, judged once where the source declares them.",
13632
13821
  codes: [
13633
13822
  { code: "UNDEFINED_TYPE_REFERENCE", defaultSeverity: "error", summary: "Reference to a type that is not defined anywhere" }
13634
13823
  ],
@@ -13639,6 +13828,7 @@ var init_signature_type_references = __esm({
13639
13828
  Array.from(interfaceGenericParameters(intf)).map((g) => g.toLowerCase())
13640
13829
  );
13641
13830
  for (const m of intf.methods) {
13831
+ if (m.signatureFrom !== void 0) continue;
13642
13832
  const methodGenerics = new Set(
13643
13833
  Array.from(methodGenericParameters(m)).map((g) => g.toLowerCase())
13644
13834
  );
@@ -13658,6 +13848,225 @@ var init_signature_type_references = __esm({
13658
13848
  }
13659
13849
  }
13660
13850
  }
13851
+ for (const t of ctx.types) {
13852
+ const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
13853
+ const isDraftCtx = !!sub && (sub.status === "draft" || sub.status === "design");
13854
+ const typeGenerics = new Set(Array.from(typeGenericParameters(t)).map((g) => g.toLowerCase()));
13855
+ const named2 = [
13856
+ ...t.methods.filter((m) => m.params !== void 0).flatMap((m) => methodTypeRefs(m).map((ref) => ({ ref, by: `Method "${m.name}" on type "${t.id}"` }))),
13857
+ ...signatureTypeRefs(t).map((ref) => ({ ref, by: `Signature type "${t.id}"` }))
13858
+ ];
13859
+ for (const { ref, by } of named2) {
13860
+ if (ctx.isTypeResolved(ref, typeGenerics)) continue;
13861
+ const hint = ctx.importHint(ref);
13862
+ ctx.addIssue(
13863
+ "error",
13864
+ "UNDEFINED_TYPE_REFERENCE",
13865
+ `${by} references undefined type "${ref}" in signature.${hint ? ` A declared dependency exports it without this project importing it \u2014 add \`${hint}\`.` : ""}`,
13866
+ t.id,
13867
+ isDraftCtx
13868
+ );
13869
+ }
13870
+ }
13871
+ }
13872
+ };
13873
+ }
13874
+ });
13875
+
13876
+ // src/core/rules/integrity/signature-sources.ts
13877
+ function draftContext(ctx, interfaceId, component) {
13878
+ const intf = ctx.interfaceMap.get(interfaceId);
13879
+ return ctx.isComponentDraft(component) || intf?.status === "draft" || intf?.status === "design";
13880
+ }
13881
+ function targetComponent(target) {
13882
+ return target.slice(0, target.lastIndexOf("."));
13883
+ }
13884
+ var signatureSourcesRule;
13885
+ var init_signature_sources = __esm({
13886
+ "src/core/rules/integrity/signature-sources.ts"() {
13887
+ "use strict";
13888
+ init_models();
13889
+ signatureSourcesRule = {
13890
+ name: "signature-sources",
13891
+ judges: "design",
13892
+ description: "Judges every signatureFrom the loader met, from ctx.signatureFacts (the loaded specs are already resolved). The value is read both ways, as a contract method and as a signature type: neither resolving \u2014 or only a type that is an entity or value-object \u2014 is unresolved; both resolving is ambiguous, and the finding names both candidates and asks the author to qualify the reference; a source that itself names a signatureFrom is a chain, which is never followed (when that source's own source is a signature type, the finding names it as the one to take directly); a method source whose component the method's component does not name in dependsOn or owns is off the design's edges; a stored method that names a source and also states params or returns that differ from the source's restates it, and the source's are in force. Every finding sits at the method.",
13893
+ codes: [
13894
+ { code: "SIGNATURE_SOURCE_UNRESOLVED", defaultSeverity: "error", summary: "A method's signatureFrom names no contract method and no signature type" },
13895
+ { code: "SIGNATURE_SOURCE_AMBIGUOUS", defaultSeverity: "error", summary: "A method's signatureFrom resolves both as a contract method and as a signature type; qualify it" },
13896
+ { code: "SIGNATURE_SOURCE_CHAINED", defaultSeverity: "error", summary: "A method's signature source takes its own signature from a source; sources do not chain" },
13897
+ { code: "SIGNATURE_SOURCE_OFF_EDGE", defaultSeverity: "error", summary: "A method takes its signature from a method of a component its own component neither dependsOn nor owns" },
13898
+ { code: "SIGNATURE_SOURCE_RESTATED", defaultSeverity: "error", summary: "A method names a signature source and also states params or returns that differ from the source's" }
13899
+ ],
13900
+ check(ctx) {
13901
+ for (const fact of ctx.signatureFacts?.sources ?? []) {
13902
+ if (!ctx.isSpecInScope(fact.interfaceId)) continue;
13903
+ const isDraft = draftContext(ctx, fact.interfaceId, fact.component);
13904
+ const where = `Method "${fact.method}" on interface "${fact.interfaceId}"`;
13905
+ const at = { at: fact.method };
13906
+ switch (fact.outcome) {
13907
+ case "unresolved":
13908
+ ctx.addIssue(
13909
+ "error",
13910
+ "SIGNATURE_SOURCE_UNRESOLVED",
13911
+ `${where} takes its signature from "${fact.source}", which names ${fact.detail ? `no contract method, and ${fact.detail}` : "no contract method and no signature type"}. A source is a \`component.method\` the component reaches, or a type of kind signature.`,
13912
+ fact.interfaceId,
13913
+ isDraft,
13914
+ void 0,
13915
+ at
13916
+ );
13917
+ continue;
13918
+ case "ambiguous":
13919
+ ctx.addIssue(
13920
+ "error",
13921
+ "SIGNATURE_SOURCE_AMBIGUOUS",
13922
+ `${where} takes its signature from "${fact.source}", which names both the contract method "${fact.candidates?.[0] ?? "?"}" and the signature type "${fact.candidates?.[1] ?? "?"}". Qualify the reference so only one reading matches.`,
13923
+ fact.interfaceId,
13924
+ isDraft,
13925
+ void 0,
13926
+ at
13927
+ );
13928
+ continue;
13929
+ case "chained": {
13930
+ const own = fact.detail ?? "";
13931
+ const direct = ctx.types.some((t) => t.kind === "signature" && (t.id === own || typeMatchesRef(t, own)));
13932
+ ctx.addIssue(
13933
+ "error",
13934
+ "SIGNATURE_SOURCE_CHAINED",
13935
+ `${where} takes its signature from "${fact.target ?? fact.source}", which takes its own from "${own}"; sources do not chain.${direct ? ` "${own}" is a signature type: name it directly as this method's signatureFrom.` : " State this method's params, or name a source that declares its own."}`,
13936
+ fact.interfaceId,
13937
+ isDraft,
13938
+ void 0,
13939
+ at
13940
+ );
13941
+ continue;
13942
+ }
13943
+ case "restated":
13944
+ if (fact.differs) {
13945
+ ctx.addIssue(
13946
+ "error",
13947
+ "SIGNATURE_SOURCE_RESTATED",
13948
+ `${where} takes its signature from "${fact.source}" and also states its own: ${fact.detail ?? "they differ from the source's"}. The source's are in force \u2014 drop the stated params and returns, or drop the signatureFrom.`,
13949
+ fact.interfaceId,
13950
+ isDraft,
13951
+ void 0,
13952
+ at
13953
+ );
13954
+ }
13955
+ continue;
13956
+ default:
13957
+ break;
13958
+ }
13959
+ if (fact.form !== "method" || !fact.target) continue;
13960
+ const source = targetComponent(fact.target);
13961
+ const owner = ctx.componentMap.get(fact.component);
13962
+ const reached = /* @__PURE__ */ new Set([...owner?.dependsOn ?? [], ...owner?.owns ?? []]);
13963
+ if (reached.has(source)) continue;
13964
+ ctx.addIssue(
13965
+ "error",
13966
+ "SIGNATURE_SOURCE_OFF_EDGE",
13967
+ `${where} takes its signature from "${fact.target}", but "${fact.component}" neither dependsOn nor owns "${source}". Add "${source}" to its dependsOn (or owns, for a member), or name a source it reaches.`,
13968
+ fact.interfaceId,
13969
+ isDraft,
13970
+ void 0,
13971
+ at
13972
+ );
13973
+ }
13974
+ }
13975
+ };
13976
+ }
13977
+ });
13978
+
13979
+ // src/core/rules/integrity/signature-types.ts
13980
+ var signatureTypesRule;
13981
+ var init_signature_types = __esm({
13982
+ "src/core/rules/integrity/signature-types.ts"() {
13983
+ "use strict";
13984
+ signatureTypesRule = {
13985
+ name: "signature-types",
13986
+ judges: "design",
13987
+ description: "A type of kind signature is a named function type and carries params and one returns, nothing else: fields, methods, invariants, componentClass, database, table or linkedEntity on it \u2014 or a returns missing from it \u2014 are reported; so are params or returns on an entity or a value-object, where they mean nothing.",
13988
+ codes: [
13989
+ { code: "SIGNATURE_TYPE_MEMBERS", defaultSeverity: "error", summary: "A signature type carries a member other than params and returns, or lacks returns; or a data type carries params or returns" }
13990
+ ],
13991
+ check(ctx) {
13992
+ for (const t of ctx.types) {
13993
+ if (!ctx.isSpecInScope(t.id)) continue;
13994
+ const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
13995
+ const isDraft = !!sub && (sub.status === "draft" || sub.status === "design");
13996
+ if (t.kind === "signature") {
13997
+ const members = [
13998
+ ...t.fields.length > 0 ? ["fields"] : [],
13999
+ ...t.methods.length > 0 ? ["methods"] : [],
14000
+ ...(t.invariants?.length ?? 0) > 0 ? ["invariants"] : [],
14001
+ ...t.componentClass !== void 0 ? ["componentClass"] : [],
14002
+ ...t.database !== void 0 ? ["database"] : [],
14003
+ ...t.table !== void 0 ? ["table"] : [],
14004
+ ...t.linkedEntity !== void 0 ? ["linkedEntity"] : []
14005
+ ];
14006
+ const missingReturns = t.returns === void 0;
14007
+ if (members.length === 0 && !missingReturns) continue;
14008
+ const problems = [
14009
+ ...members.length > 0 ? [`carries ${members.join(", ")}`] : [],
14010
+ ...missingReturns ? ["states no returns"] : []
14011
+ ];
14012
+ ctx.addIssue(
14013
+ "error",
14014
+ "SIGNATURE_TYPE_MEMBERS",
14015
+ `Signature type "${t.id}" ${problems.join(" and ")}. A signature is a named function type: params and one returns, nothing else.`,
14016
+ t.id,
14017
+ isDraft
14018
+ );
14019
+ continue;
14020
+ }
14021
+ const stated = [...t.params !== void 0 ? ["params"] : [], ...t.returns !== void 0 ? ["returns"] : []];
14022
+ if (stated.length === 0) continue;
14023
+ ctx.addIssue(
14024
+ "error",
14025
+ "SIGNATURE_TYPE_MEMBERS",
14026
+ `Type "${t.id}" is ${t.kind === "entity" ? "an entity" : `a ${t.kind}`} and carries ${stated.join(" and ")}, which only a signature type has. Make it kind signature, or drop them.`,
14027
+ t.id,
14028
+ isDraft
14029
+ );
14030
+ }
14031
+ }
14032
+ };
14033
+ }
14034
+ });
14035
+
14036
+ // src/core/rules/integrity/signature-text.ts
14037
+ function draftContext2(ctx, specId, kind) {
14038
+ if (kind === "interface") {
14039
+ const intf = ctx.interfaceMap.get(specId);
14040
+ return !!intf && (ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design");
14041
+ }
14042
+ const type = ctx.types.find((t) => t.id === specId);
14043
+ const sub = type?.subsystem ? ctx.subsystems.find((s) => s.id === type.subsystem) : void 0;
14044
+ return !!sub && (sub.status === "draft" || sub.status === "design");
14045
+ }
14046
+ var signatureTextRule;
14047
+ var init_signature_text = __esm({
14048
+ "src/core/rules/integrity/signature-text.ts"() {
14049
+ "use strict";
14050
+ signatureTextRule = {
14051
+ name: "signature-text",
14052
+ judges: "design",
14053
+ description: "A stored signature text that differs from the text its method's params derive is stale: every reader is shown the derived text, but the file says something else, and a file read on its own (a diff, a review, a lock approval) is misled. Reported per method, from ctx.signatureFacts, for contract and type methods alike; `wairon doctor --fix` regenerates the stored text, and any save of the spec writes the derived text too.",
14054
+ codes: [
14055
+ { code: "SIGNATURE_TEXT_STALE", defaultSeverity: "warning", summary: "A stored signature text differs from the text its params derive; doctor --fix regenerates it" }
14056
+ ],
14057
+ check(ctx) {
14058
+ for (const stale of ctx.signatureFacts?.staleTexts ?? []) {
14059
+ if (!ctx.isSpecInScope(stale.specId)) continue;
14060
+ ctx.addIssue(
14061
+ "warning",
14062
+ "SIGNATURE_TEXT_STALE",
14063
+ `Method "${stale.method}" on ${stale.kind} "${stale.specId}" stores the signature "${stale.stored}", but its params derive "${stale.derived}" \u2014 which is what every reader is shown. Run \`wairon doctor --fix\` to regenerate the stored text (any save of the spec writes it too).`,
14064
+ stale.specId,
14065
+ draftContext2(ctx, stale.specId, stale.kind),
14066
+ void 0,
14067
+ { at: stale.method }
14068
+ );
14069
+ }
13661
14070
  }
13662
14071
  };
13663
14072
  }
@@ -15252,11 +15661,11 @@ var init_detail_sufficiency = __esm({
15252
15661
  const contract = ctx.interfaceMap.get(impl.contract);
15253
15662
  if (!contract) continue;
15254
15663
  const component = ctx.componentMap.get(contract.component);
15255
- const draftContext = ctx.isImplementationDraft(impl);
15664
+ const draftContext3 = ctx.isImplementationDraft(impl);
15256
15665
  for (const method2 of impl.methods) {
15257
15666
  const detail = effectiveDetail(method2, impl, component);
15258
15667
  if (contract.methods.some((m) => m.name === method2.name) && method2.narrative.length === 0 && detail.level !== "full") {
15259
- dialedDown.push({ implementation: impl, method: method2, component, detail, draftContext });
15668
+ dialedDown.push({ implementation: impl, method: method2, component, detail, draftContext: draftContext3 });
15260
15669
  }
15261
15670
  }
15262
15671
  }
@@ -17481,7 +17890,7 @@ var init_unused_types = __esm({
17481
17890
  // Stage 8: its verdict needs the whole system's specs, so a part judged alone skips it.
17482
17891
  needsWholeTree: true,
17483
17892
  judges: "design",
17484
- description: "Flags types no field of any type, no interface method signature and no other type's method signature references. References are matched through the type-reference grammar (generic arguments, collections, qualified ids), and the declaring type's own generic parameters are left out \u2014 a type parameter is not a reference to a type. A type named only by its OWN methods stays unused, the way a function that only calls itself is.",
17893
+ description: "Flags types no field of any type, no interface method signature, no contract method's signatureFrom and no other type's method signature references. References are matched through the type-reference grammar (generic arguments, collections, qualified ids), and the declaring type's own generic parameters are left out \u2014 a type parameter is not a reference to a type. A signature type is used when a method names it as its signatureFrom or a param is typed by it. A type named only by its OWN methods stays unused, the way a function that only calls itself is.",
17485
17894
  codes: [
17486
17895
  { code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields, signatures or type methods" }
17487
17896
  ],
@@ -17503,6 +17912,7 @@ var init_unused_types = __esm({
17503
17912
  markTypeReferenced(ref);
17504
17913
  }
17505
17914
  }
17915
+ for (const ref of signatureTypeRefs(t)) markTypeReferenced(ref, t.id);
17506
17916
  }
17507
17917
  for (const intf of ctx.interfaces) {
17508
17918
  for (const m of intf.methods) {
@@ -17512,6 +17922,13 @@ var init_unused_types = __esm({
17512
17922
  }
17513
17923
  }
17514
17924
  }
17925
+ for (const fact of ctx.signatureFacts?.sources ?? []) {
17926
+ const named2 = fact.form === "signature" && fact.target ? fact.target : fact.outcome === "ambiguous" ? fact.candidates?.[1] : void 0;
17927
+ if (!named2) continue;
17928
+ for (const spec of ctx.types) {
17929
+ if (spec.id === named2 || typeMatchesRef(spec, named2)) referencedTypes.add(spec.id);
17930
+ }
17931
+ }
17515
17932
  for (const t of ctx.types) {
17516
17933
  for (const m of t.methods) {
17517
17934
  const refs = methodTypeRefs(m);
@@ -19014,7 +19431,7 @@ var init_type_shape = __esm({
19014
19431
  typeShapeRule = {
19015
19432
  name: "type-shape",
19016
19433
  judges: "code",
19017
- description: "Code-to-contract for the DATA: a type spec's `fields` are compared, name by name, against the shape its `sourcePath` actually declares. `typeRealization` asks whether a type EXISTS in code; nothing asked whether it is the shape the spec claims, so a type spec could describe two fields of a six-field record \u2014 and call the two that are optional required \u2014 straight through a lock and a CI gate, while the ERD, the agent briefs and every implementer read it as truth. That is the worst of the code-to-spec gaps, because a wrong signature eventually breaks at a call site and a type spec that lies is only ever read by humans and agents. Two origins answer at exact grade: a DECLARED shape, whose members the file lists, and a DERIVED one, an alias followed one hop to the object literal its schema is built from, where the keys are the members. A shape that extends another is judged on what it shows and never on what it omits, since its inherited members are not in this file to count.",
19434
+ description: "Code-to-contract for the DATA: a type spec's `fields` are compared, name by name, against the shape its `sourcePath` actually declares. `typeRealization` asks whether a type EXISTS in code; nothing asked whether it is the shape the spec claims, so a type spec could describe two fields of a six-field record \u2014 and call the two that are optional required \u2014 straight through a lock and a CI gate, while the ERD, the agent briefs and every implementer read it as truth. That is the worst of the code-to-spec gaps, because a wrong signature eventually breaks at a call site and a type spec that lies is only ever read by humans and agents. Two origins answer at exact grade: a DECLARED shape, whose members the file lists, and a DERIVED one, an alias followed one hop to the object literal its schema is built from, where the keys are the members \u2014 and on through a composition the same file writes out (`Base.extend({\u2026})` on a schema constant it declares), the extension's keys laid over the base's. A shape that extends another is judged on what it shows and never on what it omits, since its inherited members are not in this file to count.",
19018
19435
  codes: [
19019
19436
  {
19020
19437
  code: "UNREALIZED_TYPE_FIELD",
@@ -19198,7 +19615,7 @@ var init_param_conformance = __esm({
19198
19615
  const stated = normalize3(declared.type);
19199
19616
  return stated === realized.type || codeNameOf.get(stated) === realized.type;
19200
19617
  };
19201
- for (const { implementation, method: method2, sourceFile, draftContext } of ctx.implementationMethods()) {
19618
+ for (const { implementation, method: method2, sourceFile, draftContext: draftContext3 } of ctx.implementationMethods()) {
19202
19619
  const contract = ctx.interfaceMap.get(implementation.contract);
19203
19620
  const declared = contract?.methods.find((m) => m.name === method2.name)?.params ?? [];
19204
19621
  if (declared.length === 0 || !sourceFile) continue;
@@ -19221,7 +19638,7 @@ var init_param_conformance = __esm({
19221
19638
  "UNREALIZED_PARAM",
19222
19639
  `Method "${method2.name}" of contract "${implementation.contract}" declares ${unrealized.length} parameter(s) ${subject} does not take \u2014 ${unrealized.map((found) => found.told).join(", ")}. The contract is promising an argument that would go nowhere, and nothing breaks at a call site to correct it: every brief and every caller built from the contract passes one. Take the parameter in the code, or drop it from the contract.`,
19223
19640
  implementation.id,
19224
- draftContext,
19641
+ draftContext3,
19225
19642
  void 0,
19226
19643
  { at: method2.name, covers: unrealized.map((found) => found.unit) }
19227
19644
  );
@@ -19233,7 +19650,7 @@ var init_param_conformance = __esm({
19233
19650
  "UNDECLARED_PARAM",
19234
19651
  `${opening} realizing method "${method2.name}" of contract "${implementation.contract}" takes ${undeclared.length} parameter(s) the contract does not declare and no declared injection accounts for \u2014 ${undeclared.map((found) => found.told).join(", ")}. An argument a caller must supply that the design never mentions is how a credential ends up in a signature nobody has read against its contract. Declare it on the contract, name it in the implementation's \`injectedParams\` when whatever wires this component up supplies it (a LEADING run only), or take it out of the signature.`,
19235
19652
  implementation.id,
19236
- draftContext,
19653
+ draftContext3,
19237
19654
  void 0,
19238
19655
  { at: method2.name, covers: undeclared.map((found) => found.unit) }
19239
19656
  );
@@ -19245,7 +19662,7 @@ var init_param_conformance = __esm({
19245
19662
  "PARAM_NAME_MISMATCH",
19246
19663
  `Method "${method2.name}" of contract "${implementation.contract}" and ${subject} agree on position and type but not on name for ${renamed.length} parameter(s) \u2014 ${renamed.map((f) => f.told).join("; ")}. The contract, the ERD and every brief carry one word and the code answers to another, which is a rename nobody recorded rather than a different argument \u2014 the declared type agreeing is what says so. Rename one side to the other.`,
19247
19664
  implementation.id,
19248
- draftContext,
19665
+ draftContext3,
19249
19666
  void 0,
19250
19667
  { at: method2.name, covers: renamed.map((found) => found.unit) }
19251
19668
  );
@@ -19257,7 +19674,7 @@ var init_param_conformance = __esm({
19257
19674
  "PARAM_OPTIONALITY",
19258
19675
  `Method "${method2.name}" of contract "${implementation.contract}" and ${subject} disagree about whether ${disagreed.length} parameter(s) may be left out \u2014 ${disagreed.map((f) => f.told).join("; ")}. One of them is telling a caller an argument is required when it is not, or the reverse. Omittable is the code's word for it: a default value and a rest parameter make an argument omittable exactly as a question mark does.`,
19259
19676
  implementation.id,
19260
- draftContext,
19677
+ draftContext3,
19261
19678
  void 0,
19262
19679
  { at: method2.name, covers: disagreed.map((found) => found.unit) }
19263
19680
  );
@@ -19341,7 +19758,7 @@ var init_route_coverage = __esm({
19341
19758
  const routes = code.factsAt(file).functionRoutes;
19342
19759
  return routes && Object.prototype.hasOwnProperty.call(routes, via) ? routes[via] : [];
19343
19760
  });
19344
- const draftContext = ctx.isComponentDraft(listener.id) || ctx.isComponentDraft(portal.id);
19761
+ const draftContext3 = ctx.isComponentDraft(listener.id) || ctx.isComponentDraft(portal.id);
19345
19762
  const where = holders.map((file) => `"${file}"`).join(", ");
19346
19763
  if (read2.length === 0) {
19347
19764
  ctx.addIssue(
@@ -19349,7 +19766,7 @@ var init_route_coverage = __esm({
19349
19766
  "UNREADABLE_ROUTER",
19350
19767
  `Listener "${listener.id}" mounts portal "${portal.id}" through "${via}" in ${where}, but no branch of "${via}" reads as a route \u2014 so none of its routes were checked against the contract at all. Only one idiom is read: an \`if\` whose conditions, together with those of every enclosing \`if\`, compare \`<request>.method\` with a string and \`parts[i]\` / \`parts.length\` with literals. Silence here would read as a clean router; it is only one this analysis cannot see. Write the router in that idiom, or carry this finding with the reason it cannot be.`,
19351
19768
  anchor,
19352
- draftContext,
19769
+ draftContext3,
19353
19770
  void 0,
19354
19771
  { at: via }
19355
19772
  );
@@ -19374,7 +19791,7 @@ var init_route_coverage = __esm({
19374
19791
  "UNDECLARED_ROUTE",
19375
19792
  `Portal "${portal.id}"'s router "${via}" (${where}), mounted by listener "${listener.id}", answers ${undeclared.length} route(s) no contract endpoint of "${portal.id}" declares \u2014 ${undeclared.map((key) => `"${key}"`).join(", ")}. A surface the code serves and the design never promised is how a write runs with no contract: no brief, no auth review, no rule reading it. Declare each as a contract method with its endpoint, or take the route out of the router.`,
19376
19793
  anchor,
19377
- draftContext,
19794
+ draftContext3,
19378
19795
  void 0,
19379
19796
  { at: via, covers: undeclared }
19380
19797
  );
@@ -19386,7 +19803,7 @@ var init_route_coverage = __esm({
19386
19803
  "UNROUTED_ENDPOINT",
19387
19804
  `Portal "${portal.id}" binds ${unrouted.length} HTTP endpoint(s) under listener "${listener.id}"'s mount that no route of its router "${via}" (${where}) answers \u2014 ${unrouted.map((key) => `"${key}"`).join(", ")}. The contract promises a route no request can reach. Route it in the router, or drop the endpoint from the contract.`,
19388
19805
  anchor,
19389
- draftContext,
19806
+ draftContext3,
19390
19807
  void 0,
19391
19808
  { at: via, covers: unrouted }
19392
19809
  );
@@ -19503,7 +19920,7 @@ var init_export_conformance = __esm({
19503
19920
  }
19504
19921
  }
19505
19922
  }
19506
- for (const { implementation, method: method2, sourceFile, draftContext } of ctx.implementationMethods()) {
19923
+ for (const { implementation, method: method2, sourceFile, draftContext: draftContext3 } of ctx.implementationMethods()) {
19507
19924
  const handle = method2.exportedVia;
19508
19925
  if (!handle || !sourceFile) continue;
19509
19926
  const file = pathKey(sourceFile);
@@ -19518,7 +19935,7 @@ var init_export_conformance = __esm({
19518
19935
  "UNREALIZED_EXPORT_HANDLE",
19519
19936
  `Method "${method2.name}" in implementation "${implementation.id}" declares exportedVia "${handle}", but "${file}" exports no such name. A handle names the export a consumer imports to REACH the method \u2014 the value that composes it, which \`symbol\` cannot name because \`symbol\` names the function inside \u2014 so one the file does not publish promises a route nobody can import. It allows nothing either: every export of this file is still read against the contracts. Name the binding the file actually exports, export it, or drop the handle.`,
19520
19937
  implementation.id,
19521
- draftContext
19938
+ draftContext3
19522
19939
  );
19523
19940
  }
19524
19941
  for (const listener of ctx.components) {
@@ -20886,6 +21303,74 @@ var init_naming_discipline = __esm({
20886
21303
  }
20887
21304
  });
20888
21305
 
21306
+ // src/core/rules/heuristic/signature-source-suggestions.ts
21307
+ function sameParams(a, b) {
21308
+ if (!b || a.length !== b.length) return false;
21309
+ return a.every((p, i) => p.name === b[i].name && p.type === b[i].type && !!p.optional === !!b[i].optional);
21310
+ }
21311
+ function localName(id) {
21312
+ return id.split("::").pop();
21313
+ }
21314
+ var signatureSourceSuggestionsRule;
21315
+ var init_signature_source_suggestions = __esm({
21316
+ "src/core/rules/heuristic/signature-source-suggestions.ts"() {
21317
+ "use strict";
21318
+ signatureSourceSuggestionsRule = {
21319
+ name: "signature-source-suggestions",
21320
+ judges: "design",
21321
+ description: "A Repository facade method is pure 1:1 forwarding to an owned member by doctrine (facade-forwarding), so its signature IS the member's: where its params (name, type, optional marker, order) and returns equal the owned member method it forwards to, it could name that method as its signatureFrom, and the reference can never be wrong. Suggested only where the adoption would be legal: the member method names no source of its own (no chain). Nothing else is suggested: another forwarder \u2014 a Portal restating an Orchestrator, a client Adapter restating a remote Portal \u2014 is often a public surface that should stay deliberately decoupled from an internal signature, so wairon does not suggest coupling them. Adoption is opt-in, so this is a notice, and ONE per facade contract, the restating methods being the units it covers.",
21322
+ codes: [
21323
+ { code: "SIGNATURE_SOURCE_AVAILABLE", defaultSeverity: "notice", summary: "A Repository facade's methods restate exactly the owned member methods they forward to and could name them as their signatureFrom" }
21324
+ ],
21325
+ check(ctx) {
21326
+ for (const impl of ctx.implementations) {
21327
+ const contract = ctx.interfaceMap.get(impl.contract);
21328
+ if (!contract || !ctx.isSpecInScope(contract.id)) continue;
21329
+ const repository = ctx.componentMap.get(contract.component);
21330
+ if (!repository || repository.componentType !== "Repository") continue;
21331
+ const owned = new Set(repository.owns);
21332
+ const restating = [];
21333
+ for (const realized of impl.methods) {
21334
+ const steps = realized.narrative ?? [];
21335
+ let member;
21336
+ let memberMethod;
21337
+ if (steps.length === 1 && steps[0].type === "call") {
21338
+ member = steps[0].targetComponent;
21339
+ memberMethod = steps[0].targetMethod;
21340
+ } else if (steps.length === 0 && realized.calls?.length === 1) {
21341
+ const entry = realized.calls[0];
21342
+ const dot = entry.lastIndexOf(".");
21343
+ if (dot > 0) {
21344
+ member = entry.slice(0, dot);
21345
+ memberMethod = entry.slice(dot + 1);
21346
+ }
21347
+ }
21348
+ if (!member || !memberMethod) continue;
21349
+ const memberKey = [...owned].find((o) => o === member || localName(o) === localName(member));
21350
+ if (!memberKey) continue;
21351
+ const own = contract.methods.find((m) => m.name === realized.name);
21352
+ if (!own || own.signatureFrom !== void 0 || !own.params) continue;
21353
+ const target = (ctx.interfacesByComponent.get(memberKey) ?? []).flatMap((i) => i.methods).find((m) => m.name === memberMethod);
21354
+ if (!target || target.signatureFrom !== void 0) continue;
21355
+ if (!sameParams(own.params, target.params) || own.returns !== target.returns) continue;
21356
+ restating.push(`${own.name} \u2190 ${localName(memberKey)}.${memberMethod}`);
21357
+ }
21358
+ if (restating.length === 0) continue;
21359
+ ctx.addIssue(
21360
+ "notice",
21361
+ "SIGNATURE_SOURCE_AVAILABLE",
21362
+ `${restating.length === 1 ? "A method" : `${restating.length} methods`} of Repository facade "${contract.id}" restate${restating.length === 1 ? "s" : ""} exactly the owned member method${restating.length === 1 ? "" : "s"} ${restating.length === 1 ? "it forwards" : "they forward"} to: ${restating.map((u) => `"${u}"`).join("; ")}. ${restating.length === 1 ? "It" : "Each"} could name that member method as its signatureFrom (and drop its params and returns), so the signature is stated once. Optional \u2014 a facade's signature is its member's by doctrine.`,
21363
+ contract.id,
21364
+ ctx.isImplementationDraft(impl),
21365
+ void 0,
21366
+ { at: contract.id, covers: restating }
21367
+ );
21368
+ }
21369
+ }
21370
+ };
21371
+ }
21372
+ });
21373
+
20889
21374
  // src/core/rules/repository.ts
20890
21375
  function addRule(rule) {
20891
21376
  ruleSet.push(rule);
@@ -20925,6 +21410,9 @@ var init_repository = __esm({
20925
21410
  init_type_declarations();
20926
21411
  init_field_type_references();
20927
21412
  init_signature_type_references();
21413
+ init_signature_sources();
21414
+ init_signature_types();
21415
+ init_signature_text();
20928
21416
  init_public_surface_binding();
20929
21417
  init_public_surface_consumers();
20930
21418
  init_public_surface_declared_type();
@@ -21020,6 +21508,7 @@ var init_repository = __esm({
21020
21508
  init_narrative_complexity();
21021
21509
  init_naming_discipline();
21022
21510
  init_method_cohesion();
21511
+ init_signature_source_suggestions();
21023
21512
  SDD_RULES = [
21024
21513
  hierarchyRule,
21025
21514
  // Namespace integrity right after hierarchy: unresolvable/unwritable ids
@@ -21038,6 +21527,9 @@ var init_repository = __esm({
21038
21527
  typeDeclarationsRule,
21039
21528
  fieldTypeReferencesRule,
21040
21529
  signatureTypeReferencesRule,
21530
+ signatureSourcesRule,
21531
+ signatureTypesRule,
21532
+ signatureTextRule,
21041
21533
  // Contracts and the targets narratives name, in four questions with one
21042
21534
  // owner each: does the implementation mirror its contract, does a target
21043
21535
  // inside this tree resolve, does a target that leaves it pin to exactly one
@@ -21223,6 +21715,7 @@ var init_repository = __esm({
21223
21715
  narrativeComplexityRule,
21224
21716
  namingDisciplineRule,
21225
21717
  methodCohesionRule,
21718
+ signatureSourceSuggestionsRule,
21226
21719
  // Pack resolution and reproducibility run late: they are about project
21227
21720
  // CONFIGURATION (does the declared pack set resolve, and can it be reproduced
21228
21721
  // elsewhere?) rather than spec content.
@@ -21438,24 +21931,6 @@ function walkExact(ts, sourceText, fileName) {
21438
21931
  if (!argument || !ts.isTypeQueryNode(argument) || !ts.isIdentifier(argument.exprName)) return void 0;
21439
21932
  return argument.exprName.text;
21440
21933
  };
21441
- const baseObjectLiteral = (expression) => {
21442
- let node = expression;
21443
- const walked = /* @__PURE__ */ new Set();
21444
- while (node && !walked.has(node)) {
21445
- walked.add(node);
21446
- if (ts.isCallExpression(node)) {
21447
- const callee = node.expression;
21448
- if (ts.isPropertyAccessExpression(callee) && callee.name.text === "object") {
21449
- const literal = node.arguments.find((argument) => ts.isObjectLiteralExpression(argument));
21450
- if (literal) return literal;
21451
- }
21452
- node = callee;
21453
- } else if (ts.isPropertyAccessExpression(node)) {
21454
- node = node.expression;
21455
- } else break;
21456
- }
21457
- return void 0;
21458
- };
21459
21934
  const chainedCombinators = (expression) => {
21460
21935
  const applied = /* @__PURE__ */ new Set();
21461
21936
  let node = expression;
@@ -21725,13 +22200,48 @@ function walkExact(ts, sourceText, fileName) {
21725
22200
  ts.forEachChild(node, visit);
21726
22201
  };
21727
22202
  visit(sf);
22203
+ const composedMembers = (expression, seen) => {
22204
+ const extensions = [];
22205
+ const compose2 = (members) => {
22206
+ const byName = new Map(members.map((member) => [member.name, member]));
22207
+ for (const extension of [...extensions].reverse()) {
22208
+ for (const member of schemaMembers(extension)) byName.set(member.name, member);
22209
+ }
22210
+ return [...byName.values()];
22211
+ };
22212
+ let node = expression;
22213
+ const walked = /* @__PURE__ */ new Set();
22214
+ while (node && !walked.has(node)) {
22215
+ walked.add(node);
22216
+ if (ts.isCallExpression(node)) {
22217
+ const callee = node.expression;
22218
+ if (ts.isPropertyAccessExpression(callee) && callee.name.text === "object") {
22219
+ const literal = node.arguments.find((argument) => ts.isObjectLiteralExpression(argument));
22220
+ if (literal) return compose2(schemaMembers(literal));
22221
+ } else if (ts.isPropertyAccessExpression(callee) && callee.name.text === "extend") {
22222
+ const extension = node.arguments.find((argument) => ts.isObjectLiteralExpression(argument));
22223
+ if (!extension) return void 0;
22224
+ extensions.push(extension);
22225
+ }
22226
+ node = callee;
22227
+ } else if (ts.isPropertyAccessExpression(node)) {
22228
+ node = node.expression;
22229
+ } else if (ts.isIdentifier(node)) {
22230
+ const base = schemaConstants.get(node.text);
22231
+ if (!base || seen.has(node.text)) return void 0;
22232
+ const members = composedMembers(base, /* @__PURE__ */ new Set([...seen, node.text]));
22233
+ return members ? compose2(members) : void 0;
22234
+ } else break;
22235
+ }
22236
+ return void 0;
22237
+ };
21728
22238
  for (const alias of derivedAliases) {
21729
22239
  if (typeShapes.has(alias.name)) continue;
21730
22240
  const initializer = schemaConstants.get(alias.constant);
21731
22241
  if (!initializer) continue;
21732
- const literal = baseObjectLiteral(initializer);
21733
- if (!literal) continue;
21734
- typeShapes.set(alias.name, { origin: "derived", fields: schemaMembers(literal), methods: [] });
22242
+ const fields = composedMembers(initializer, /* @__PURE__ */ new Set([alias.constant]));
22243
+ if (!fields) continue;
22244
+ typeShapes.set(alias.name, { origin: "derived", fields, methods: [] });
21735
22245
  }
21736
22246
  const reexportOnly = sf.statements.length > 0 && sf.statements.every((st) => ts.isExportDeclaration(st) && !!st.moduleSpecifier);
21737
22247
  return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, importBindings, typeOnlyBindings, fieldTypes, localTypes, typeShapes, functionParams, functionRoutes, mutableBindings, reexportOnly };
@@ -22810,6 +23320,7 @@ function buildRuleContext(opts) {
22810
23320
  ...opts.exportTables ? { exportTables: opts.exportTables } : {},
22811
23321
  ...opts.projectFamily ? { projectFamily: opts.projectFamily } : {},
22812
23322
  ...opts.exportUsages ? { exportUsages: opts.exportUsages } : {},
23323
+ ...opts.signatureFacts ? { signatureFacts: opts.signatureFacts } : {},
22813
23324
  pinnedExternals,
22814
23325
  codeModel: opts.codeModel ?? emptyCodeModel(),
22815
23326
  roundTripIssues: opts.roundTripIssues,
@@ -23405,6 +23916,7 @@ function runOwnersGate(rulesOrOptions, projectType = "backend") {
23405
23916
  const memberTables = family.nodes.filter((n) => n.namespace !== "").map((n) => resolveProjectExports(n.namespace));
23406
23917
  const producers = new Set(family.references.filter((r) => r.consumer === "").map((r) => r.producer));
23407
23918
  const exportUsages = [...producers].map((producer) => exportUsage("", producer));
23919
+ const signatures = signatureFacts();
23408
23920
  const ctx = buildRuleContext({
23409
23921
  system,
23410
23922
  subsystems,
@@ -23426,6 +23938,7 @@ function runOwnersGate(rulesOrOptions, projectType = "backend") {
23426
23938
  ],
23427
23939
  projectFamily: family,
23428
23940
  exportUsages,
23941
+ signatureFacts: signatures,
23429
23942
  pinnedExternals,
23430
23943
  // By-name selections only: a legacy path ref pins nothing to check. A dry
23431
23944
  // run supplies its candidate's; otherwise the stored ones.
@@ -23544,12 +24057,26 @@ function judgePartAlone(config, scopeSubsystem) {
23544
24057
  if (ownIds.has(key) || context.ids.has(key)) continue;
23545
24058
  issues.push(issue("warning", "EXTERNAL_CHECK_UNAVAILABLE", `"${by}" names "${key}", which neither this part nor its pinned excerpt of "${parentId}" holds \u2014 used since the pin: re-pin with \`wairon externals pin\` while the parent is on disk.`, by));
23546
24059
  }
24060
+ const ownSignatures = signatureFacts();
24061
+ const resolved = resolveTree(
24062
+ [...own.interfaces, ...context.interfaces],
24063
+ [...own.components, ...context.components],
24064
+ [...own.types, ...context.types]
24065
+ );
24066
+ const ownInterfaceIds = new Set(own.interfaces.map((i) => i.id));
24067
+ const partSignatures = {
24068
+ sources: [
24069
+ ...resolved.facts.sources.filter((f) => ownInterfaceIds.has(f.interfaceId) && f.outcome !== "restated"),
24070
+ ...ownSignatures.sources.filter((f) => f.outcome === "restated")
24071
+ ],
24072
+ staleTexts: ownSignatures.staleTexts
24073
+ };
23547
24074
  const all = {
23548
24075
  subsystems: [...own.subsystems, ...context.subsystems],
23549
24076
  components: [...own.components, ...context.components],
23550
- interfaces: [...own.interfaces, ...context.interfaces],
24077
+ interfaces: resolved.interfaces,
23551
24078
  implementations: [...own.implementations, ...context.implementations],
23552
- types: [...own.types, ...context.types]
24079
+ types: resolved.types
23553
24080
  };
23554
24081
  const system = context.system ?? SystemSpecSchema.parse({ createdAt: EXCERPT_EPOCH, updatedAt: EXCERPT_EPOCH, name: parentId, vision: `The part's parent, "${parentId}" (no L0 pinned).` });
23555
24082
  const codeModel = buildCodeModel(own.implementations, own.types, root, [], []);
@@ -23569,6 +24096,7 @@ function judgePartAlone(config, scopeSubsystem) {
23569
24096
  exportTables: [],
23570
24097
  projectFamily: graph(),
23571
24098
  exportUsages: [],
24099
+ signatureFacts: partSignatures,
23572
24100
  pinnedExternals: [],
23573
24101
  packSelections: packs.filter((p) => typeof p !== "string"),
23574
24102
  packRequirements: [],
@@ -24501,7 +25029,7 @@ var init_project_family2 = __esm({
24501
25029
  });
24502
25030
 
24503
25031
  // src/core/exports.ts
24504
- function localName(id) {
25032
+ function localName2(id) {
24505
25033
  return id.split("::").pop() ?? id;
24506
25034
  }
24507
25035
  function ownedBy(subsystemId, declaringSubsystem) {
@@ -24548,7 +25076,7 @@ function findInSource(world, source, table, request) {
24548
25076
  if (request.typeDef) {
24549
25077
  const type = lookupType(world, request.typeDef, source.id);
24550
25078
  if (!type) return {};
24551
- const item2 = { publicName: localName(type.id), kind: "type", source: type.subsystem ?? source.id, typeDef: type.id, via: [] };
25079
+ const item2 = { publicName: localName2(type.id), kind: "type", source: type.subsystem ?? source.id, typeDef: type.id, via: [] };
24552
25080
  return { item: item2, found: table.entries.find((e) => e.kind === "type" && e.typeDef === type.id && e.source === item2.source) };
24553
25081
  }
24554
25082
  const intf = lookup(world.interfaces, request.interface, source.id);
@@ -24556,7 +25084,7 @@ function findInSource(world, source, table, request) {
24556
25084
  if (!comp) return {};
24557
25085
  const narrowed2 = intf?.id ?? request.interface;
24558
25086
  const item = {
24559
- publicName: localName(narrowed2 ?? comp.id),
25087
+ publicName: localName2(narrowed2 ?? comp.id),
24560
25088
  kind: "component",
24561
25089
  source: comp.subsystem,
24562
25090
  component: comp.id,
@@ -24596,7 +25124,7 @@ function bindOwn(world, sub, pi, problems) {
24596
25124
  problems.push({ kind: "invalid", owner: sub.id, detail: `exports type "${pi.typeDef}" as its own, but ${type ? `it belongs to "${type.subsystem ?? "the system"}"` : "no such type exists"}` });
24597
25125
  return void 0;
24598
25126
  }
24599
- const name2 = pi.as ?? localName(type.id);
25127
+ const name2 = pi.as ?? localName2(type.id);
24600
25128
  checkPublicName(name2, sub.id, problems);
24601
25129
  return { publicName: name2, kind: "type", source: type.subsystem ?? sub.id, typeDef: type.id, via: [] };
24602
25130
  }
@@ -24604,7 +25132,7 @@ function bindOwn(world, sub, pi, problems) {
24604
25132
  if (!comp || !ownedBy(sub.id, comp.subsystem)) return void 0;
24605
25133
  const intf = lookup(world.interfaces, pi.interface, sub.id);
24606
25134
  const narrowed2 = intf?.id ?? pi.interface;
24607
- const name = pi.as ?? localName(narrowed2 ?? comp.id);
25135
+ const name = pi.as ?? localName2(narrowed2 ?? comp.id);
24608
25136
  checkPublicName(name, sub.id, problems);
24609
25137
  return {
24610
25138
  publicName: name,
@@ -24639,7 +25167,7 @@ function subsystemCandidates(world, sub, tables, group, problems) {
24639
25167
  source,
24640
25168
  sourceTable,
24641
25169
  { component: pi.component, interface: pi.interface, typeDef: pi.typeDef },
24642
- (item) => pi.as ?? localName(pi.interface ?? item.typeDef ?? item.component ?? ""),
25170
+ (item) => pi.as ?? localName2(pi.interface ?? item.typeDef ?? item.component ?? ""),
24643
25171
  group.has(source.id),
24644
25172
  problems
24645
25173
  );
@@ -24815,16 +25343,16 @@ function classifySource(world, sourceId, label7, sources) {
24815
25343
  function bindOwnType(world, owner, e, problems) {
24816
25344
  const type = (world.types.get(e.typeDef) ?? []).find((t) => !t.subsystem);
24817
25345
  if (!type) {
24818
- problems.push({ kind: "invalid", owner, publicName: e.as ?? e.id ?? localName(e.typeDef), detail: `exports type "${e.typeDef}" as its own, but the project owns no project-level type of that id` });
25346
+ problems.push({ kind: "invalid", owner, publicName: e.as ?? e.id ?? localName2(e.typeDef), detail: `exports type "${e.typeDef}" as its own, but the project owns no project-level type of that id` });
24819
25347
  return void 0;
24820
25348
  }
24821
- const name = e.as ?? e.id ?? localName(type.id);
25349
+ const name = e.as ?? e.id ?? localName2(type.id);
24822
25350
  checkPublicName(name, owner, problems);
24823
25351
  return { publicName: name, kind: "type", source: owner, typeDef: type.id, via: [] };
24824
25352
  }
24825
25353
  function bindFromProject(owner, e, source, table, index, problems) {
24826
25354
  const named2 = e.component !== void 0 || e.typeDef !== void 0 || e.interface !== void 0;
24827
- const requested = named2 ? localName(e.interface ?? e.component ?? e.typeDef) : void 0;
25355
+ const requested = named2 ? localName2(e.interface ?? e.component ?? e.typeDef) : void 0;
24828
25356
  const picked = named2 ? table.entries.filter((x) => x.publicName === requested) : table.entries;
24829
25357
  if (named2 && picked.length === 0) {
24830
25358
  problems.push({ kind: "invalid", owner, publicName: e.as ?? e.id ?? requested, detail: `re-exports "${requested}" from ${source.label}, whose export table has no such public name \u2014 another project is reached only through its L0 exports` });
@@ -24861,7 +25389,7 @@ function bindFromSubsystem(world, owner, e, subsystem, subsystemTables, index, p
24861
25389
  // The default public name is the item's own LOCAL id: a member's L0 is
24862
25390
  // read with its ids keyed under its project, and its entry names the
24863
25391
  // item as the member wrote it.
24864
- () => e.as ?? e.id ?? localName(e.interface ?? e.component ?? e.typeDef),
25392
+ () => e.as ?? e.id ?? localName2(e.interface ?? e.component ?? e.typeDef),
24865
25393
  false,
24866
25394
  problems
24867
25395
  );
@@ -25013,6 +25541,130 @@ var init_exports2 = __esm({
25013
25541
  }
25014
25542
  });
25015
25543
 
25544
+ // src/core/signature-sources.ts
25545
+ function emptySignatureFacts() {
25546
+ return { sources: [], staleTexts: [] };
25547
+ }
25548
+ function namespaceOf(key) {
25549
+ const at = key.lastIndexOf("::");
25550
+ return at === -1 ? "" : key.slice(0, at);
25551
+ }
25552
+ function unresolvedMethod(method2) {
25553
+ const { params: _params, ...rest } = method2;
25554
+ return { ...rest, returns: "unknown", signature: `${method2.name}(...): unknown` };
25555
+ }
25556
+ function sameParams2(a, b) {
25557
+ const left = a ?? [];
25558
+ const right = b ?? [];
25559
+ return left.length === right.length && left.every((p, i) => p.name === right[i].name && p.type === right[i].type && !!p.optional === !!right[i].optional);
25560
+ }
25561
+ function tablesOf(interfaces, components, types, references) {
25562
+ const methodsOf = /* @__PURE__ */ new Map();
25563
+ for (const intf of interfaces) methodsOf.set(intf.component, [...methodsOf.get(intf.component) ?? [], ...intf.methods]);
25564
+ const boundTypes = /* @__PURE__ */ new Map();
25565
+ for (const ref of references) {
25566
+ if (ref.position !== "type" || ref.binding === "outside" || ref.binding === "unresolved") continue;
25567
+ boundTypes.set(`${ref.specId}|${ref.authored}`, ref.resolved);
25568
+ }
25569
+ return { methodsOf, componentKeys: new Set(components.map((c) => c.id)), types, boundTypes };
25570
+ }
25571
+ function methodReading(tables, intf, value) {
25572
+ const dot = value.lastIndexOf(".");
25573
+ if (dot <= 0 || dot === value.length - 1) return null;
25574
+ const head2 = value.slice(0, dot);
25575
+ const tail = value.slice(dot + 1);
25576
+ const ns = namespaceOf(intf.id);
25577
+ const keys = [head2, ...ns && !head2.startsWith(`${ns}::`) ? [`${ns}::${head2}`] : []];
25578
+ for (const key of keys) {
25579
+ if (!tables.componentKeys.has(key)) continue;
25580
+ const method2 = (tables.methodsOf.get(key) ?? []).find((m) => m.name === tail);
25581
+ if (method2) return { target: `${key}.${tail}`, method: method2 };
25582
+ }
25583
+ return null;
25584
+ }
25585
+ function typeReading(tables, intf, value) {
25586
+ const bound2 = value.includes("::") ? tables.boundTypes.get(`${intf.id}|${value}`) : void 0;
25587
+ const named2 = bound2 !== void 0 ? tables.types.filter((t) => t.id === bound2) : tables.types.filter((t) => t.id === value || typeMatchesRef(t, value));
25588
+ const ns = namespaceOf(intf.id);
25589
+ const local = (t) => namespaceOf(t.id) === ns ? 0 : 1;
25590
+ const ranked = [...named2].sort((a, b) => local(a) - local(b));
25591
+ return { signature: ranked.find((t) => t.kind === "signature"), other: ranked.find((t) => t.kind !== "signature") };
25592
+ }
25593
+ function typeTarget(type) {
25594
+ return type.subsystem && !type.id.includes("::") ? `${type.subsystem}::${type.id}` : type.id;
25595
+ }
25596
+ function restatedFact(base, stored, params, returns) {
25597
+ const statesParams = stored.params !== void 0;
25598
+ const statesReturns = stored.returns !== void 0;
25599
+ if (!statesParams && !statesReturns) return null;
25600
+ const paramsDiffer = statesParams && !sameParams2(stored.params, params);
25601
+ const returnsDiffer = statesReturns && stored.returns !== returns;
25602
+ const differs = paramsDiffer || returnsDiffer;
25603
+ const detail = !differs ? "restates its source exactly" : [paramsDiffer ? "its params differ from the source's" : "", returnsDiffer ? `its returns "${stored.returns}" differ from the source's "${returns}"` : ""].filter(Boolean).join("; ");
25604
+ return { ...base, outcome: "restated", differs, detail };
25605
+ }
25606
+ function resolveSourced(tables, intf, stored, facts) {
25607
+ const source = stored.signatureFrom;
25608
+ const base = { interfaceId: intf.id, component: intf.component, method: stored.name, source };
25609
+ const hit = methodReading(tables, intf, source);
25610
+ const { signature, other } = typeReading(tables, intf, source);
25611
+ if (hit && signature) {
25612
+ facts.sources.push({ ...base, outcome: "ambiguous", candidates: [hit.target, typeTarget(signature)] });
25613
+ return unresolvedMethod(stored);
25614
+ }
25615
+ if (!hit && !signature) {
25616
+ facts.sources.push({ ...base, outcome: "unresolved", ...other ? { detail: `"${typeTarget(other)}" is a type of kind ${other.kind}, not a signature` } : {} });
25617
+ return unresolvedMethod(stored);
25618
+ }
25619
+ if (hit && hit.method.signatureFrom !== void 0) {
25620
+ facts.sources.push({ ...base, form: "method", target: hit.target, outcome: "chained", detail: hit.method.signatureFrom });
25621
+ return unresolvedMethod(stored);
25622
+ }
25623
+ const form = hit ? "method" : "signature";
25624
+ const target = hit ? hit.target : typeTarget(signature);
25625
+ const params = hit ? hit.method.params : signature.params;
25626
+ const returns = hit ? hit.method.returns : signature.returns ?? "unknown";
25627
+ const restated = restatedFact({ ...base, form, target }, stored, params, returns);
25628
+ if (restated) facts.sources.push(restated);
25629
+ facts.sources.push({ ...base, form, target, outcome: "resolved" });
25630
+ const resolved = { ...stored, returns };
25631
+ if (params) resolved.params = params.map((p) => ({ ...p }));
25632
+ else delete resolved.params;
25633
+ if (!params && hit) resolved.signature = hit.method.signature.replace(new RegExp(`^\\s*${hit.method.name}\\b`), stored.name);
25634
+ return resolved;
25635
+ }
25636
+ function withDerivedText(method2, stored, specId, kind, facts) {
25637
+ const derived = deriveMethodSignature({ ...method2, signature: stored ?? method2.signature });
25638
+ if (derived === void 0) return method2;
25639
+ if (stored !== void 0 && stored !== derived) facts.staleTexts.push({ specId, kind, method: method2.name, stored, derived });
25640
+ return { ...method2, signature: derived };
25641
+ }
25642
+ function resolveTree(interfaces, components, types, references = []) {
25643
+ const intfs = interfaces.map((i) => ({ ...i, methods: i.methods.map((m) => ({ ...m })) }));
25644
+ const typeCopies = types.map((t) => ({ ...t, methods: t.methods.map((m) => ({ ...m })) }));
25645
+ const tables = tablesOf(intfs, [...components], typeCopies, references);
25646
+ const facts = emptySignatureFacts();
25647
+ const sourced = intfs.map((intf) => ({
25648
+ ...intf,
25649
+ methods: intf.methods.map((m) => m.signatureFrom !== void 0 ? resolveSourced(tables, intf, m, facts) : m)
25650
+ }));
25651
+ const resolvedInterfaces = sourced.map((intf, i) => ({
25652
+ ...intf,
25653
+ methods: intf.methods.map((m, j) => withDerivedText(m, intfs[i].methods[j].signature, intf.id, "interface", facts))
25654
+ }));
25655
+ const resolvedTypes = typeCopies.map((t) => ({
25656
+ ...t,
25657
+ methods: t.methods.map((m) => withDerivedText(m, m.signature, t.id, "type", facts))
25658
+ }));
25659
+ return { interfaces: resolvedInterfaces, types: resolvedTypes, facts };
25660
+ }
25661
+ var init_signature_sources2 = __esm({
25662
+ "src/core/signature-sources.ts"() {
25663
+ "use strict";
25664
+ init_models();
25665
+ }
25666
+ });
25667
+
25016
25668
  // src/core/specs.ts
25017
25669
  function deniedOwners(reach2, owners) {
25018
25670
  const seen = /* @__PURE__ */ new Set();
@@ -25067,7 +25719,8 @@ function emptyIndex() {
25067
25719
  implementation: {},
25068
25720
  type: {},
25069
25721
  group: {}
25070
- }
25722
+ },
25723
+ signatures: { sources: [], staleTexts: [] }
25071
25724
  };
25072
25725
  }
25073
25726
  function isWithin(dir, file) {
@@ -25299,6 +25952,17 @@ function inspectChainedRoots(rootDir = getProjectRoot()) {
25299
25952
  walk2(root, "", /* @__PURE__ */ new Set([chainDirKey(root)]), 0);
25300
25953
  return inspection;
25301
25954
  }
25955
+ function signatureSourceParts(value) {
25956
+ const dot = value.lastIndexOf(".");
25957
+ if (dot <= 0 || dot === value.length - 1) return null;
25958
+ return { head: value.slice(0, dot), tail: value.slice(dot + 1) };
25959
+ }
25960
+ function mapSignatureSource(value, map) {
25961
+ const parts = signatureSourceParts(value);
25962
+ if (!parts) return value;
25963
+ const head2 = map("signatureFrom", parts.head);
25964
+ return head2 === parts.head ? value : `${head2}.${parts.tail}`;
25965
+ }
25302
25966
  function mapSpecReferences(kind, spec, map) {
25303
25967
  switch (kind) {
25304
25968
  case "subsystem": {
@@ -25329,7 +25993,15 @@ function mapSpecReferences(kind, spec, map) {
25329
25993
  }
25330
25994
  case "interface": {
25331
25995
  const i = spec;
25332
- return { ...i, component: map("contract", i.component) };
25996
+ return {
25997
+ ...i,
25998
+ component: map("contract", i.component),
25999
+ // A method source's head is a component reference; a value with no
26000
+ // head is a raw type position the mapper is never asked about.
26001
+ ...i.methods?.some((m) => m.signatureFrom !== void 0) ? {
26002
+ methods: i.methods.map((m) => m.signatureFrom !== void 0 ? { ...m, signatureFrom: mapSignatureSource(m.signatureFrom, map) } : m)
26003
+ } : {}
26004
+ };
25333
26005
  }
25334
26006
  case "implementation": {
25335
26007
  const impl = spec;
@@ -25374,6 +26046,7 @@ function rawReferences(kind, spec) {
25374
26046
  for (const m of spec.methods) {
25375
26047
  qualifiedTypeNames(m.returns).forEach((t) => add2("type", t));
25376
26048
  m.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((t) => add2("type", t)));
26049
+ if (m.signatureFrom !== void 0 && !signatureSourceParts(m.signatureFrom)) add2("type", m.signatureFrom);
25377
26050
  }
25378
26051
  break;
25379
26052
  case "implementation":
@@ -25385,9 +26058,14 @@ function rawReferences(kind, spec) {
25385
26058
  }
25386
26059
  }
25387
26060
  break;
25388
- case "type":
25389
- for (const f of spec.fields) qualifiedTypeNames(f.type).forEach((t) => add2("type", t));
26061
+ case "type": {
26062
+ const t = spec;
26063
+ for (const f of t.fields) qualifiedTypeNames(f.type).forEach((n) => add2("type", n));
26064
+ t.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((n) => add2("type", n)));
26065
+ qualifiedTypeNames(t.returns).forEach((n) => add2("type", n));
26066
+ for (const m of t.methods ?? []) m.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((n) => add2("type", n)));
25390
26067
  break;
26068
+ }
25391
26069
  default:
25392
26070
  break;
25393
26071
  }
@@ -25417,6 +26095,8 @@ function bareRawReferences(kind, spec) {
25417
26095
  case "type": {
25418
26096
  const t = spec;
25419
26097
  for (const f of t.fields) for (const ref of fieldTypeRefs(t, f.type)) add2("type", ref);
26098
+ for (const ref of signatureTypeRefs(t)) add2("type", ref);
26099
+ for (const m of t.methods ?? []) for (const p of m.params ?? []) for (const ref of fieldTypeRefs(t, p.type)) add2("type", ref);
25420
26100
  break;
25421
26101
  }
25422
26102
  case "implementation":
@@ -25483,6 +26163,9 @@ function respellStoredReferences(kind, doc, map) {
25483
26163
  typed(m, "signature");
25484
26164
  typed(m, "returns");
25485
26165
  for (const p of list2(m.params)) typed(p, "type");
26166
+ if (typeof m.signatureFrom === "string") {
26167
+ m.signatureFrom = signatureSourceParts(m.signatureFrom) ? mapSignatureSource(m.signatureFrom, map) : mapTypeNames(m.signatureFrom, map);
26168
+ }
25486
26169
  }
25487
26170
  break;
25488
26171
  case "implementation":
@@ -25508,6 +26191,9 @@ function respellStoredReferences(kind, doc, map) {
25508
26191
  at(doc, "subsystem", "subsystem");
25509
26192
  at(doc, "group", "group");
25510
26193
  for (const f of list2(doc.fields)) typed(f, "type");
26194
+ for (const p of list2(doc.params)) typed(p, "type");
26195
+ typed(doc, "returns");
26196
+ for (const m of list2(doc.methods)) for (const p of list2(m.params)) typed(p, "type");
25511
26197
  break;
25512
26198
  default:
25513
26199
  break;
@@ -25527,7 +26213,9 @@ function mapRawReferences(kind, spec, map) {
25527
26213
  ...m,
25528
26214
  ...m.signature !== void 0 ? { signature: mapTypeNames(m.signature, map) } : {},
25529
26215
  ...m.returns !== void 0 ? { returns: mapTypeNames(m.returns, map) } : {},
25530
- ...m.params ? { params: m.params.map((p) => ({ ...p, type: mapTypeNames(p.type, map) })) } : {}
26216
+ ...m.params ? { params: m.params.map((p) => ({ ...p, type: mapTypeNames(p.type, map) })) } : {},
26217
+ // A type source is a raw type position; a method source's head is bound, never raw.
26218
+ ...m.signatureFrom !== void 0 && !signatureSourceParts(m.signatureFrom) ? { signatureFrom: mapTypeNames(m.signatureFrom, map) } : {}
25531
26219
  }))
25532
26220
  };
25533
26221
  }
@@ -25555,7 +26243,14 @@ function mapRawReferences(kind, spec, map) {
25555
26243
  }
25556
26244
  case "type": {
25557
26245
  const t = spec;
25558
- return { ...t, fields: t.fields.map((f) => ({ ...f, type: mapTypeNames(f.type, map) })) };
26246
+ const typedParams = (params) => params ? { params: params.map((p) => ({ ...p, type: mapTypeNames(p.type, map) })) } : {};
26247
+ return {
26248
+ ...t,
26249
+ fields: t.fields.map((f) => ({ ...f, type: mapTypeNames(f.type, map) })),
26250
+ ...typedParams(t.params),
26251
+ ...t.returns !== void 0 ? { returns: mapTypeNames(t.returns, map) } : {},
26252
+ methods: (t.methods ?? []).map((m) => ({ ...m, ...typedParams(m.params) }))
26253
+ };
25559
26254
  }
25560
26255
  default:
25561
26256
  return spec;
@@ -25892,6 +26587,7 @@ function specKind(raw) {
25892
26587
  if ("component" in raw && Array.isArray(raw.methods)) return "interface";
25893
26588
  if ("contract" in raw && Array.isArray(raw.methods)) return "implementation";
25894
26589
  if ("kind" in raw && Array.isArray(raw.fields)) return "type";
26590
+ if (raw.kind === "signature") return "type";
25895
26591
  return void 0;
25896
26592
  }
25897
26593
  function rewriteSpecRefs(raw, remap) {
@@ -25946,6 +26642,16 @@ function rewriteSpecRefs(raw, remap) {
25946
26642
  rewrite(raw, "component", "component");
25947
26643
  for (const method2 of entries(raw.methods)) {
25948
26644
  for (const param of entries(method2?.params)) rewrite(param, "type", "type");
26645
+ const source = method2?.signatureFrom;
26646
+ if (typeof source !== "string") continue;
26647
+ const parts = signatureSourceParts(source);
26648
+ const name = parts ? remap(parts.tail, "method", parts.head) : void 0;
26649
+ const component = parts ? remap(parts.head, "component", parts.tail) : void 0;
26650
+ const next = parts && (component !== parts.head || name !== parts.tail) ? `${component}.${name}` : remap(source, "type");
26651
+ if (next !== source) {
26652
+ method2.signatureFrom = next;
26653
+ changed = true;
26654
+ }
25949
26655
  }
25950
26656
  } else if (kind === "implementation") {
25951
26657
  rewrite(raw, "contract", "interface");
@@ -25976,6 +26682,11 @@ function rewriteSpecRefs(raw, remap) {
25976
26682
  } else if (kind === "type") {
25977
26683
  rewrite(raw, "componentClass", "entity-class");
25978
26684
  for (const field of entries(raw.fields)) rewrite(field, "type", "type");
26685
+ for (const param of entries(raw.params)) rewrite(param, "type", "type");
26686
+ rewrite(raw, "returns", "type");
26687
+ for (const method2 of entries(raw.methods)) {
26688
+ for (const param of entries(method2?.params)) rewrite(param, "type", "type");
26689
+ }
25979
26690
  }
25980
26691
  return changed;
25981
26692
  }
@@ -26371,6 +27082,26 @@ function ineffectiveDeltaPaths(delta, merged, stored) {
26371
27082
  walk2(delta, merged, stored, "", "");
26372
27083
  return out;
26373
27084
  }
27085
+ function storedFormOf(kind, spec) {
27086
+ if (!spec) return spec;
27087
+ if (kind === "interface") {
27088
+ const intf = spec;
27089
+ return { ...intf, methods: intf.methods.map((m) => storedMethodSignature(m)) };
27090
+ }
27091
+ if (kind === "type") {
27092
+ const type = spec;
27093
+ return { ...type, methods: type.methods.map((m) => storedTypeMethod(m)) };
27094
+ }
27095
+ return spec;
27096
+ }
27097
+ function deriveMergedTexts(kind, merged) {
27098
+ if (kind !== "interface" && kind !== "type" || !Array.isArray(merged.methods)) return;
27099
+ merged.methods = merged.methods.map((m) => {
27100
+ if (!m || typeof m !== "object" || m.signatureFrom !== void 0 || !Array.isArray(m.params)) return m;
27101
+ const derived = deriveMethodSignature(m);
27102
+ return derived === void 0 ? m : { ...m, signature: derived };
27103
+ });
27104
+ }
26374
27105
  function cloneSpec(spec) {
26375
27106
  return JSON.parse(JSON.stringify(spec));
26376
27107
  }
@@ -26728,6 +27459,9 @@ function specPathsInScope(scopeSubsystem) {
26728
27459
  function snapshotSpecFiles() {
26729
27460
  return current().snapshotSpecFiles();
26730
27461
  }
27462
+ function signatureFacts() {
27463
+ return current().signatureFacts();
27464
+ }
26731
27465
  function findLegacySpecFiles() {
26732
27466
  return current().findLegacySpecFiles();
26733
27467
  }
@@ -26793,6 +27527,7 @@ var init_specs2 = __esm({
26793
27527
  init_project_family();
26794
27528
  init_project_config();
26795
27529
  init_project_family2();
27530
+ init_signature_sources2();
26796
27531
  PartReadOnly = class extends WaironError {
26797
27532
  constructor(message) {
26798
27533
  super(message);
@@ -26932,6 +27667,10 @@ var init_specs2 = __esm({
26932
27667
  index.groups.push(...raw.index.groups);
26933
27668
  for (const kind of Object.keys(index.paths)) Object.assign(index.paths[kind], raw.index.paths[kind]);
26934
27669
  }
27670
+ const signatures = resolveTree(index.interfaces, index.components, index.types, raws.flatMap((r) => r.record.authoredReferences));
27671
+ index.interfaces = signatures.interfaces;
27672
+ index.types = signatures.types;
27673
+ index.signatures = signatures.facts;
26935
27674
  return { index, raws, readables };
26936
27675
  }
26937
27676
  /**
@@ -27357,7 +28096,9 @@ var init_specs2 = __esm({
27357
28096
  rewrite: local
27358
28097
  });
27359
28098
  };
28099
+ const localComponents = new Set(index.components.map((c) => nameKey(c.id.split("::").pop())));
27360
28100
  const bindLocal = (kind, specKey) => (position, value) => {
28101
+ if (position === "signatureFrom" && !value.includes("::") && !localComponents.has(nameKey(value))) return value;
27361
28102
  if (value.includes("::")) {
27362
28103
  raw.pending.push({ kind, specKey, position, authored: value, raw: false });
27363
28104
  return value;
@@ -27692,7 +28433,8 @@ var init_specs2 = __esm({
27692
28433
  this.scanAll();
27693
28434
  const from = this.getSubprojectPrefix(spec.id) ?? "";
27694
28435
  const carrying = carry && !this.carryDisabled ? /* @__PURE__ */ new Map() : null;
27695
- const mapped = mapSpecReferences(kind, spec, (position, value) => this.writeReference(from, spec.id, position, value, carrying));
28436
+ const components = new Set(this.scanAll().components.map((c) => c.id));
28437
+ const mapped = mapSpecReferences(kind, spec, (position, value) => position === "signatureFrom" && !components.has(value) ? value : this.writeReference(from, spec.id, position, value, carrying));
27696
28438
  return { ...mapped, id: localOf(from, spec.id).split("::").pop() };
27697
28439
  }
27698
28440
  // -------------------------------------------------------------------------
@@ -27788,8 +28530,8 @@ var init_specs2 = __esm({
27788
28530
  if (pathExists(this.paths.specsInterfacesDir()) && listFiles(this.paths.specsInterfacesDir(), ".yaml").length > 0) {
27789
28531
  return path23.join(this.paths.specsInterfacesDir(), `${id}.yaml`);
27790
28532
  }
27791
- const targetComponent = componentId || "default";
27792
- return path23.join(this.paths.specsDir(), "default", targetComponent, ".interface.yaml");
28533
+ const targetComponent2 = componentId || "default";
28534
+ return path23.join(this.paths.specsDir(), "default", targetComponent2, ".interface.yaml");
27793
28535
  }
27794
28536
  getImplementationPath(id, contractId) {
27795
28537
  const index = this.scanAll();
@@ -27985,8 +28727,15 @@ var init_specs2 = __esm({
27985
28727
  prepareComponentForWrite(spec) {
27986
28728
  return this.relativizeSpec("component", spec);
27987
28729
  }
28730
+ /**
28731
+ * spec_registry.saveInterfaceSpec steps 1-2: every method in its stored form
28732
+ * (method_signature.storedForm — the text a method's params derive, nothing
28733
+ * a signatureFrom supplies), then every reference written back relative to
28734
+ * the owning project.
28735
+ */
27988
28736
  prepareInterfaceForWrite(spec) {
27989
- return this.relativizeSpec("interface", spec);
28737
+ const stored = { ...spec, methods: (spec.methods ?? []).map((m) => storedMethodSignature(m)) };
28738
+ return this.relativizeSpec("interface", stored);
27990
28739
  }
27991
28740
  prepareImplementationForWrite(spec, carry = true) {
27992
28741
  const relative26 = this.relativizeSpec("implementation", spec, carry);
@@ -27996,8 +28745,10 @@ var init_specs2 = __esm({
27996
28745
  const partDir = this.partHolding(this.getImplementationPath(spec.id, spec.contract))?.part.directory;
27997
28746
  return partDir ? partRelativeFilePaths(relative26, this.rootDir, partDir) : relative26;
27998
28747
  }
28748
+ /** A type written in its stored form — every params-bearing method's text derived — relative to the owning project. */
27999
28749
  prepareTypeForWrite(spec) {
28000
- return this.relativizeSpec("type", spec);
28750
+ const stored = { ...spec, methods: (spec.methods ?? []).map((m) => storedTypeMethod(m)) };
28751
+ return this.relativizeSpec("type", stored);
28001
28752
  }
28002
28753
  prepareGroupForWrite(spec) {
28003
28754
  return this.relativizeSpec("group", spec);
@@ -28207,7 +28958,7 @@ var init_specs2 = __esm({
28207
28958
  if (!pathExists(p)) return null;
28208
28959
  try {
28209
28960
  const raw = readSpecFile(p);
28210
- return InterfaceSpecSchema.parse(raw);
28961
+ return resolveTree([InterfaceSpecSchema.parse(raw)], index.components, index.types).interfaces[0];
28211
28962
  } catch (e) {
28212
28963
  this.loaderIssues.push({
28213
28964
  severity: "error",
@@ -28772,6 +29523,17 @@ var init_specs2 = __esm({
28772
29523
  * dry-run a promotion (write 'complete' → validate → restore) without leaving
28773
29524
  * any change behind if validation fails or the user cancels.
28774
29525
  */
29526
+ /**
29527
+ * ispec_index.signatureFacts — what the current scan's signature resolution
29528
+ * recorded: every signatureFrom met and how it resolved, and every stored
29529
+ * text its params contradict. Read from the cached scan, rescanning first
29530
+ * when the tree changed.
29531
+ */
29532
+ signatureFacts() {
29533
+ const index = this;
29534
+ index.listProjectRoots();
29535
+ return this.cachedIndex.signatures;
29536
+ }
28775
29537
  snapshotSpecFiles() {
28776
29538
  const index = this.scanAll();
28777
29539
  const snapshot = /* @__PURE__ */ new Map();
@@ -28869,7 +29631,7 @@ var init_specs2 = __esm({
28869
29631
  */
28870
29632
  updateSpec(kind, id, delta, hooks, dryRun = false) {
28871
29633
  const notices = [];
28872
- const result = this.load(kind, id);
29634
+ const result = storedFormOf(kind, this.load(kind, id));
28873
29635
  if (!result) {
28874
29636
  throw new Error(`Spec of kind "${kind}" with ID "${id}" does not exist. Define it first.`);
28875
29637
  }
@@ -29382,6 +30144,7 @@ var init_specs2 = __esm({
29382
30144
  const qualifiedDelta = qualifyDeltaRefs(mergeableDelta);
29383
30145
  const mergedResult = mergeDelta(result, qualifiedDelta);
29384
30146
  for (const field of unsetFields) delete mergedResult[field];
30147
+ deriveMergedTexts(kind, mergedResult);
29385
30148
  const withDefaults = deltaSchema.safeParse(mergedResult);
29386
30149
  if (withDefaults.success) {
29387
30150
  for (const [key, value] of Object.entries(withDefaults.data)) {
@@ -30193,7 +30956,7 @@ function referencedKeys(part, types) {
30193
30956
  }
30194
30957
  for (const i of loadInterfaceSpecs().filter((x) => own.has(x.id))) {
30195
30958
  add2(i.component);
30196
- for (const m of i.methods) addTypes(methodTypeRefs(m));
30959
+ for (const m of i.methods) addTypes(sourcedMethodTypeRefs(m));
30197
30960
  }
30198
30961
  for (const impl of loadImplementationSpecs().filter((x) => own.has(x.id))) {
30199
30962
  add2(impl.contract);
@@ -30206,10 +30969,20 @@ function referencedKeys(part, types) {
30206
30969
  }
30207
30970
  for (const t of types.filter((x) => own.has(x.id))) {
30208
30971
  add2(t.subsystem);
30209
- for (const f of t.fields ?? []) addTypes(fieldTypeRefs(t, f.type));
30972
+ addTypes(typeSpecTypeRefs(t));
30210
30973
  }
30211
30974
  return out;
30212
30975
  }
30976
+ function sourcedMethodTypeRefs(m) {
30977
+ return [...methodTypeRefs(m), ...m.signatureFrom !== void 0 ? [m.signatureFrom] : []];
30978
+ }
30979
+ function typeSpecTypeRefs(t) {
30980
+ return [
30981
+ ...(t.fields ?? []).flatMap((f) => fieldTypeRefs(t, f.type)),
30982
+ ...(t.methods ?? []).filter((m) => m.params !== void 0).flatMap((m) => methodTypeRefs(m)),
30983
+ ...signatureTypeRefs(t)
30984
+ ];
30985
+ }
30213
30986
  function closeOver(keys, types) {
30214
30987
  const out = /* @__PURE__ */ new Map();
30215
30988
  const queue = [...keys];
@@ -30228,10 +31001,9 @@ function closeOver(keys, types) {
30228
31001
  queue.push(comp.subsystem);
30229
31002
  for (const contract of interfaces.filter((i) => i.component === comp.id)) queue.push(contract.id);
30230
31003
  } else if (found.kind === "interface") {
30231
- for (const m of found.spec.methods) addTypes(methodTypeRefs(m));
31004
+ for (const m of found.spec.methods) addTypes(sourcedMethodTypeRefs(m));
30232
31005
  } else if (found.kind === "type") {
30233
- const type = found.spec;
30234
- for (const f of type.fields ?? []) addTypes(fieldTypeRefs(type, f.type));
31006
+ addTypes(typeSpecTypeRefs(found.spec));
30235
31007
  } else if (found.kind === "implementation") {
30236
31008
  queue.push(found.spec.contract);
30237
31009
  }
@@ -31762,7 +32534,11 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
31762
32534
  for (const contract of moving) {
31763
32535
  saveInterfaceSpec({
31764
32536
  ...contract,
31765
- methods: contract.methods.map((m) => m.name === methodName ? { ...m, name: newName, signature: renameInSignature(m.signature, methodName, newName) } : m)
32537
+ methods: contract.methods.map((m) => m.name === methodName ? {
32538
+ ...m,
32539
+ name: newName,
32540
+ ...m.params === void 0 && m.signatureFrom === void 0 ? { signature: renameInSignature(m.signature, methodName, newName) } : {}
32541
+ } : m)
31766
32542
  });
31767
32543
  renamed.push(contract.id);
31768
32544
  }
@@ -36806,6 +37582,52 @@ var init_narrative_repair = __esm({
36806
37582
  }
36807
37583
  });
36808
37584
 
37585
+ // src/core/signature-repair.ts
37586
+ function repairSignatures(apply4) {
37587
+ const facts = signatureFacts();
37588
+ const repairs = /* @__PURE__ */ new Map();
37589
+ const repairOf = (specId, kind) => {
37590
+ const key = `${kind}:${specId}`;
37591
+ let repair = repairs.get(key);
37592
+ if (!repair) {
37593
+ repair = { specId, kind, regenerated: [], dropped: [] };
37594
+ repairs.set(key, repair);
37595
+ }
37596
+ return repair;
37597
+ };
37598
+ const held = new Set(facts.sources.filter((f) => f.outcome === "restated" && f.differs).map((f) => f.interfaceId));
37599
+ const own = (specId, kind) => !specId.includes("::") && !(kind === "interface" && held.has(specId));
37600
+ for (const stale of facts.staleTexts) {
37601
+ if (own(stale.specId, stale.kind)) repairOf(stale.specId, stale.kind).regenerated.push(stale);
37602
+ }
37603
+ for (const fact of facts.sources) {
37604
+ if (fact.outcome !== "restated" || fact.differs || !own(fact.interfaceId, "interface")) continue;
37605
+ repairOf(fact.interfaceId, "interface").dropped.push(fact.method);
37606
+ }
37607
+ const planned = [...repairs.values()];
37608
+ if (apply4 && planned.length > 0) {
37609
+ const interfaces = new Map(loadInterfaceSpecs().map((i) => [i.id, i]));
37610
+ const types = new Map(loadTypeSpecs().map((t) => [t.id, t]));
37611
+ for (const repair of planned) {
37612
+ if (repair.kind === "interface") {
37613
+ const intf = interfaces.get(repair.specId);
37614
+ if (intf) saveInterfaceSpec(intf);
37615
+ continue;
37616
+ }
37617
+ const type = types.get(repair.specId);
37618
+ if (type) saveSpec("type", type);
37619
+ }
37620
+ invalidateSpecCache();
37621
+ }
37622
+ return planned;
37623
+ }
37624
+ var init_signature_repair = __esm({
37625
+ "src/core/signature-repair.ts"() {
37626
+ "use strict";
37627
+ init_specs2();
37628
+ }
37629
+ });
37630
+
36809
37631
  // src/models/lock.ts
36810
37632
  function describeApprover(who) {
36811
37633
  const label7 = who.name ? `${who.name} (${who.id})` : who.id;
@@ -37613,6 +38435,7 @@ var init_core = __esm({
37613
38435
  init_domain_curator();
37614
38436
  init_rules();
37615
38437
  init_specs2();
38438
+ init_signature_sources2();
37616
38439
  init_external_producers();
37617
38440
  init_part_context();
37618
38441
  init_provision();
@@ -37633,6 +38456,7 @@ var init_core = __esm({
37633
38456
  init_provision();
37634
38457
  init_stereotype_migration();
37635
38458
  init_narrative_repair();
38459
+ init_signature_repair();
37636
38460
  init_approval();
37637
38461
  init_approver();
37638
38462
  init_specs2();
@@ -38043,6 +38867,8 @@ function componentCandidateGate(options = candidateOptions(), storedOwner) {
38043
38867
  return {
38044
38868
  gate: (kind, merged) => {
38045
38869
  refuseUnknownOwner(kind, merged, storedOwner);
38870
+ const restated = kind === "interface" ? restatedSources(merged.methods) : [];
38871
+ if (restated.length) throw new Error(`${restatedSourceRefusal(String(merged.id), restated)} Nothing was written.`);
38046
38872
  if (kind !== "component") return;
38047
38873
  const verdict = validateComponentCandidate(merged, options);
38048
38874
  if (verdict.errors.length) throw new Error(formatCandidateRefusal(verdict));
@@ -38050,6 +38876,43 @@ function componentCandidateGate(options = candidateOptions(), storedOwner) {
38050
38876
  }
38051
38877
  };
38052
38878
  }
38879
+ function restatedSources(methods) {
38880
+ if (!Array.isArray(methods)) return [];
38881
+ return methods.filter((m) => m && typeof m === "object" && m.signatureFrom !== void 0 && (m.params !== void 0 || m.returns !== void 0)).map((m) => String(m.name));
38882
+ }
38883
+ function restatedSourceRefusal(id, methods) {
38884
+ return `SIGNATURE_SOURCE_RESTATED: interface "${id}" \u2014 ${methods.map((n) => `"${n}"`).join(", ")} ${methods.length === 1 ? "names" : "name"} a signatureFrom and also ${methods.length === 1 ? "states" : "state"} params or returns. The source supplies them: state the signatureFrom alone, or the params and returns alone. To adopt a source on a method that states its own, unset its params and returns in the same delta.`;
38885
+ }
38886
+ function unsignedMethods(methods) {
38887
+ if (!Array.isArray(methods)) return [];
38888
+ return methods.filter((m) => m && typeof m === "object" && m.signatureFrom === void 0 && m.params === void 0 && (typeof m.signature !== "string" || m.signature.trim() === "")).map((m) => String(m.name));
38889
+ }
38890
+ function deriveStatedTexts(kind, candidate) {
38891
+ if (kind !== "interface" && kind !== "type" || !Array.isArray(candidate.methods)) return [];
38892
+ const differed = [];
38893
+ candidate.methods = candidate.methods.map((m) => {
38894
+ if (!m || typeof m !== "object" || m.signatureFrom !== void 0 || !Array.isArray(m.params)) return m;
38895
+ const derived = deriveMethodSignature(m);
38896
+ if (derived === void 0) return m;
38897
+ if (typeof m.signature === "string" && m.signature !== derived) differed.push(`"${m.name}": stated "${m.signature}", written "${derived}"`);
38898
+ return { ...m, signature: derived };
38899
+ });
38900
+ return differed.length === 0 ? [] : [
38901
+ `Signature text derived from params, not taken as stated \u2014 ${differed.join("; ")}. A method with params shows the text its params derive.`
38902
+ ];
38903
+ }
38904
+ function storedForm(kind, spec) {
38905
+ if (!spec) return spec;
38906
+ if (kind === "interface") {
38907
+ const intf = spec;
38908
+ return { ...intf, methods: intf.methods.map((m) => storedMethodSignature(m)) };
38909
+ }
38910
+ if (kind === "type") {
38911
+ const type = spec;
38912
+ return { ...type, methods: type.methods.map((m) => storedTypeMethod(m)) };
38913
+ }
38914
+ return spec;
38915
+ }
38053
38916
  function refuseUnknownOwner(kind, merged, storedOwner) {
38054
38917
  if (kind !== "component" && kind !== "type") return;
38055
38918
  const owner = merged.subsystem;
@@ -38075,7 +38938,8 @@ function restatementParent(restatement) {
38075
38938
  return null;
38076
38939
  }
38077
38940
  }
38078
- function applyRestatement(restatement, existing, parent) {
38941
+ function applyRestatement(restatement, loaded, parent) {
38942
+ const existing = storedForm(restatement.kind, loaded);
38079
38943
  const replacedExisting = existing !== null;
38080
38944
  const parentRef = restatementParent(restatement);
38081
38945
  if (parentRef && !parent) return refused(restatement, replacedExisting, MISSING_PARENT[restatement.kind](parentRef.id));
@@ -38086,6 +38950,19 @@ function applyRestatement(restatement, existing, parent) {
38086
38950
  const carried = carryInto(restatement, existing, candidate);
38087
38951
  const cleared = clearedByOmission(existing, candidate, restatement.fields);
38088
38952
  stampLifecycle(candidate, existing, status2.status);
38953
+ if (restatement.kind === "interface") {
38954
+ const restated = restatedSources(candidate.methods);
38955
+ if (restated.length) return refused(restatement, replacedExisting, `${restatedSourceRefusal(String(candidate.id), restated)} Nothing was written.`);
38956
+ }
38957
+ const unsigned = restatement.kind === "interface" || restatement.kind === "type" ? unsignedMethods(candidate.methods) : [];
38958
+ if (unsigned.length) {
38959
+ return refused(
38960
+ restatement,
38961
+ replacedExisting,
38962
+ `Refusing to write ${restatement.kind} "${String(candidate.id)}": ${unsigned.map((n) => `"${n}"`).join(", ")} ${unsigned.length === 1 ? "states" : "state"} neither params${restatement.kind === "interface" ? ", a signatureFrom" : ""} nor a prose signature, so nothing says what the method takes. Nothing was written.`
38963
+ );
38964
+ }
38965
+ const derivedNotices = deriveStatedTexts(restatement.kind, candidate);
38089
38966
  const labelErrors = resolveLabelsOf(restatement.kind, candidate);
38090
38967
  if (labelErrors.length) {
38091
38968
  return refused(restatement, replacedExisting, `Unresolved narrative label references \u2014 nothing was saved:
@@ -38097,7 +38974,7 @@ function applyRestatement(restatement, existing, parent) {
38097
38974
  spec: parsed.spec,
38098
38975
  ...status2.status ? { status: status2.status } : {},
38099
38976
  replacedExisting,
38100
- notices: existing ? rewriteNotices(restatement.kind, existing, candidate, carried, cleared) : [],
38977
+ notices: [...existing ? rewriteNotices(restatement.kind, existing, candidate, carried, cleared) : [], ...derivedNotices],
38101
38978
  changedMethods: changedMethodsOf(restatement.kind, existing, candidate)
38102
38979
  };
38103
38980
  }
@@ -42137,6 +43014,16 @@ function pendingBanner() {
42137
43014
  return [];
42138
43015
  }
42139
43016
  }
43017
+ function storedContractAnswer(loaded) {
43018
+ const resolvedSignatures = loaded.methods.filter((m) => m.signatureFrom !== void 0).map((m) => ({
43019
+ method: m.name,
43020
+ signatureFrom: m.signatureFrom,
43021
+ signature: m.signature,
43022
+ ...m.params ? { params: m.params } : {},
43023
+ returns: m.returns
43024
+ }));
43025
+ return { spec: { ...loaded, methods: loaded.methods.map((m) => storedMethodSignature(m)) }, resolvedSignatures };
43026
+ }
42140
43027
  function schemaShape(schema, seen = /* @__PURE__ */ new Map()) {
42141
43028
  const def = schema?._def;
42142
43029
  if (!def) return null;
@@ -42986,19 +43873,25 @@ ${renderChangeReport(report3)}`,
42986
43873
  }
42987
43874
  }
42988
43875
  );
43876
+ const methodParamItem = import_zod11.z.object({
43877
+ name: import_zod11.z.string(),
43878
+ type: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The parameter's type")),
43879
+ description: import_zod11.z.string().optional(),
43880
+ optional: import_zod11.z.boolean().optional().describe(
43881
+ 'Whether the parameter may be OMITTED by a caller. It is not nullability: a parameter that must be passed but may be passed as nothing is a required parameter whose type is a union \u2014 "ProjectConfig | null". Say whichever is true; they are different contracts.'
43882
+ )
43883
+ }).strict();
42989
43884
  const interfaceMethodShape = {
42990
43885
  name: import_zod11.z.string(),
42991
43886
  description: import_zod11.z.string(),
42992
- signature: import_zod11.z.string(),
42993
- returns: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The type the method answers with")),
42994
- params: import_zod11.z.array(import_zod11.z.object({
42995
- name: import_zod11.z.string(),
42996
- type: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The parameter's type")),
42997
- description: import_zod11.z.string().optional(),
42998
- optional: import_zod11.z.boolean().optional().describe(
42999
- 'Whether the parameter may be OMITTED by a caller. It is not nullability: a parameter that must be passed but may be passed as nothing is a required parameter whose type is a union \u2014 "ProjectConfig | null". Say whichever is true; they are different contracts.'
43000
- )
43001
- }).strict()).optional().describe("Structured parameters \u2014 authoritative for type checking (the prose signature becomes display-only). Strongly preferred."),
43887
+ signature: import_zod11.z.string().optional().describe(
43888
+ "Prose signature \u2014 only for a method WITHOUT params. With params the server derives and writes the text (`name(a: T, b?: U): R`) and ignores this one (a notice names one that differed); a method with a signatureFrom states none."
43889
+ ),
43890
+ returns: import_zod11.z.string().optional().describe(TYPE_REF_GRAMMAR("The type the method answers with. Required unless signatureFrom is set, whose source supplies it")),
43891
+ signatureFrom: import_zod11.z.string().min(1).optional().describe(
43892
+ "Take this method's params and returns from ONE source instead of stating them: a signature type id (`billing.change_listener`, `alias::name`) or a contract method `component.method` whose component this one names in dependsOn or owns (no chains). State it ALONE \u2014 params or returns beside it are refused (SIGNATURE_SOURCE_RESTATED). A value that names both a method and a signature type is ambiguous: qualify it."
43893
+ ),
43894
+ params: import_zod11.z.array(methodParamItem).optional().describe("Structured parameters \u2014 authoritative for type checking, and the signature text is derived from them. Strongly preferred."),
43002
43895
  guarantees: import_zod11.z.array(import_zod11.z.string().min(1)).optional().describe("Semantic guarantees the method promises (combinable); any guarantee a narrative step asserts must be declared here. Builtin tokens: idempotent | atomic | transactional | exactly-once; extension packs may declare more (any other token is UNKNOWN_GUARANTEE)"),
43003
43896
  effect: import_zod11.z.enum(["read", "write"]).optional().describe("State-effect direction on the component's held state \u2014 required on a durable Store's contract methods so the durability round-trip rule can pair writes with hydration read-backs"),
43004
43897
  invokedBy: import_zod11.z.object({
@@ -43026,7 +43919,7 @@ ${renderChangeReport(report3)}`,
43026
43919
  server,
43027
43920
  "sdd_define_interface",
43028
43921
  {
43029
- 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, and the stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and the notices a restatement raised, each as its own entry.",
43922
+ description: "Define an L3 Contract / Interface with method signatures for a component. Each method states its signature ONE of three ways: structured `params` with `returns` (strongly preferred \u2014 authoritative for type checking, and the signature text is then DERIVED and written by the server; a text you pass is ignored and a notice names one that differed), a `signatureFrom` naming its source \u2014 a signature type, or `component.method` along a dependsOn/owns edge \u2014 with neither params nor returns (both together are refused, SIGNATURE_SOURCE_RESTATED), or, for a method without params, a prose `signature` with `returns`. 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, and the stored status is kept unless this input states a higher one. The answer carries a write receipt as structured content beside the sentence \u2014 the status written, whether a spec already held the id, and the notices a restatement raised, each as its own entry.",
43030
43923
  inputSchema: interfaceInput,
43031
43924
  outputSchema: specWriteReceiptOutput
43032
43925
  },
@@ -43234,7 +44127,7 @@ ${renderChangeReport(report3)}`,
43234
44127
  }
43235
44128
  );
43236
44129
  const typeInput = {
43237
- kind: import_zod11.z.enum(["entity", "value-object"]).describe("entity (owned by a subsystem) or value-object (often system-level shared)"),
44130
+ kind: import_zod11.z.enum(["entity", "value-object", "signature"]).describe("entity (owned by a subsystem), value-object (often system-level shared), or signature \u2014 a named function type: params and returns, nothing else"),
43238
44131
  id: import_zod11.z.string().describe("Lowercase identifier"),
43239
44132
  name: import_zod11.z.string().describe("Human-readable name"),
43240
44133
  description: import_zod11.z.string().optional(),
@@ -43250,7 +44143,8 @@ ${renderChangeReport(report3)}`,
43250
44143
  }).strict()).optional().describe('Data fields (type is a primitive or a qualified type id, e.g. "billing.Invoice")'),
43251
44144
  methods: import_zod11.z.array(import_zod11.z.object({
43252
44145
  name: import_zod11.z.string(),
43253
- signature: import_zod11.z.string(),
44146
+ signature: import_zod11.z.string().optional().describe("Prose signature \u2014 only for a method WITHOUT params; with params the server derives and writes the text"),
44147
+ params: import_zod11.z.array(methodParamItem).optional().describe("Structured parameters \u2014 authoritative for type checking; the signature text is derived from them"),
43254
44148
  returns: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The type the method answers with")),
43255
44149
  description: import_zod11.z.string().optional(),
43256
44150
  sourcePath: import_zod11.z.string().optional().describe("Source file realizing this method when the type's own sourcePath does not hold it \u2014 a pure type method often lives apart from the declaration"),
@@ -43265,18 +44159,20 @@ ${renderChangeReport(report3)}`,
43265
44159
  table: import_zod11.z.string().optional().describe("Optional database table name for table-schema types"),
43266
44160
  linkedEntity: import_zod11.z.string().optional().describe("Optional logical entity id represented by this table-schema type"),
43267
44161
  sourcePath: import_zod11.z.string().optional().describe("Source file holding the declaration of this type (project-relative). Naming one turns the type into a claim on code: the file must resolve and the declaration must be anchored in it (UNREALIZED_TYPE)"),
43268
- symbol: import_zod11.z.string().optional().describe('Code-level name realizing the declaration when it differs from name, e.g. a type named "Invoice Line" declared as InvoiceLine')
44162
+ symbol: import_zod11.z.string().optional().describe('Code-level name realizing the declaration when it differs from name, e.g. a type named "Invoice Line" declared as InvoiceLine'),
44163
+ params: import_zod11.z.array(methodParamItem).optional().describe("kind signature only: the function type's parameters, in declared order (SIGNATURE_TYPE_MEMBERS on any other kind)"),
44164
+ returns: import_zod11.z.string().optional().describe(TYPE_REF_GRAMMAR("kind signature only, and required there: the function type's one output"))
43269
44165
  };
43270
44166
  const typeInputFields = Object.keys(typeInput);
43271
44167
  reg(
43272
44168
  server,
43273
44169
  "sdd_add_type",
43274
44170
  {
43275
- description: "Define an entity or value-object type (the data components operate on). Entities are owned by a subsystem; shared value objects omit subsystem (system-level). Fields are data; methods are PURE intrinsic behaviour only \u2014 anything needing a collaborator belongs on a component, taking the entity as an argument. A type may also CLAIM code: sourcePath names the file holding its declaration (and each method may name its own), symbol binds the code-level name when it differs \u2014 the file must then resolve and the declaration must be anchored in it. An owning subsystem the tree does not have is refused, writing nothing, exactly as a component under an unknown subsystem is. Re-defining an existing id REPLACES fields/methods/invariants and restates sourcePath/symbol (an omitted list or path is CLEARED, and a dropped member is reported); lint/ext are carried forward. The answer carries a write receipt as structured content beside the sentence \u2014 whether a spec already held the id, and the notices a restatement raised, each as its own entry. A type carries no lifecycle status, so the receipt states none.",
44171
+ description: "Define a type: an entity or value-object (the data components operate on), or a signature \u2014 a named function type. Entities are owned by a subsystem; shared value objects and signatures omit subsystem (system-level). Fields are data; methods are PURE intrinsic behaviour only \u2014 anything needing a collaborator belongs on a component, taking the entity as an argument \u2014 and a type method may carry structured params, which then derive its signature text as a contract method's do. A signature carries top-level params and returns and nothing else (no fields, methods or invariants \u2014 SIGNATURE_TYPE_MEMBERS); a contract method takes it by naming it as its signatureFrom, and a param may be typed by one (a callback). A type may also CLAIM code: sourcePath names the file holding its declaration (and each method may name its own), symbol binds the code-level name when it differs \u2014 the file must then resolve and the declaration must be anchored in it. An owning subsystem the tree does not have is refused, writing nothing, exactly as a component under an unknown subsystem is. Re-defining an existing id REPLACES fields/methods/invariants and restates sourcePath/symbol (an omitted list or path is CLEARED, and a dropped member is reported); lint/ext are carried forward. The answer carries a write receipt as structured content beside the sentence \u2014 whether a spec already held the id, and the notices a restatement raised, each as its own entry. A type carries no lifecycle status, so the receipt states none.",
43276
44172
  inputSchema: typeInput,
43277
44173
  outputSchema: specWriteReceiptOutput
43278
44174
  },
43279
- ({ kind, id, name, description, subsystem, group, fields, methods, componentClass, invariants, database, table, linkedEntity, sourcePath, symbol }) => {
44175
+ ({ kind, id, name, description, subsystem, group, fields, methods, componentClass, invariants, database, table, linkedEntity, sourcePath, symbol, params, returns }) => {
43280
44176
  try {
43281
44177
  const spec = {
43282
44178
  kind,
@@ -43300,7 +44196,9 @@ ${renderChangeReport(report3)}`,
43300
44196
  ...table ? { table } : {},
43301
44197
  ...linkedEntity ? { linkedEntity } : {},
43302
44198
  ...sourcePath ? { sourcePath } : {},
43303
- ...symbol ? { symbol } : {}
44199
+ ...symbol ? { symbol } : {},
44200
+ ...params ? { params } : {},
44201
+ ...returns ? { returns } : {}
43304
44202
  };
43305
44203
  const receipt = writeSpec({ kind: "type", spec, fields: typeInputFields });
43306
44204
  return writeReceipt(
@@ -43358,7 +44256,7 @@ ${renderChangeReport(report3)}`,
43358
44256
  server,
43359
44257
  "sdd_get_spec",
43360
44258
  {
43361
- description: `Get/read the parsed JSON contents of a specific spec from the spec tree. Returns structural contents without file system path searching. Pass "methods" to read only the named methods of a contract, an implementation or a type \u2014 a 45-method spec fetched whole to look at one of them is the read side of the same waste a restatement is on the write side; the answer then carries a "partialResult" marker naming what was left out, and must never be re-authored from. For a variant-tagged COMPONENT the result also carries a derived, read-only "variantGuidance" (the variant's base, its implementation guidance, and the same-variant sibling components to implement alike) \u2014 it is resolved from the variant registry, not part of the spec, so never write it back. The structured content carries the same answer with the two derived markers KEPT SEPARATE from the stored spec ({kind, id, spec, partialResult?, variantGuidance?}), so nothing derived can be mistaken for something stored; the text block folds them in as it always has.`,
44259
+ description: `Get/read the parsed JSON contents of a specific spec from the spec tree. Returns structural contents without file system path searching. Pass "methods" to read only the named methods of a contract, an implementation or a type \u2014 a 45-method spec fetched whole to look at one of them is the read side of the same waste a restatement is on the write side; the answer then carries a "partialResult" marker naming what was left out, and must never be re-authored from. For a variant-tagged COMPONENT the result also carries a derived, read-only "variantGuidance" (the variant's base, its implementation guidance, and the same-variant sibling components to implement alike) \u2014 it is resolved from the variant registry, not part of the spec, so never write it back. A contract's methods come back in their STORED form \u2014 a method that takes its signature from a source carries its signatureFrom, not the params the loader resolves into it \u2014 so the answer can be re-authored from as it is; the resolved params, returns and text of each such method come back as a derived, read-only "resolvedSignatures" marker. The structured content carries the same answer with the derived markers KEPT SEPARATE from the stored spec ({kind, id, spec, partialResult?, variantGuidance?, resolvedSignatures?}), so nothing derived can be mistaken for something stored; the text block folds them in as it always has.`,
43362
44260
  inputSchema: {
43363
44261
  kind: import_zod11.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The kind of specification"),
43364
44262
  id: import_zod11.z.string().describe('The identifier of the spec to fetch (the L0 system spec is a singleton \u2014 pass the system name or "system")'),
@@ -43391,6 +44289,12 @@ ${renderChangeReport(report3)}`,
43391
44289
  break;
43392
44290
  }
43393
44291
  if (!result) return errText(`Spec of kind "${kind}" with ID "${id}" does not exist.`);
44292
+ let resolvedSignatures = [];
44293
+ if (kind === "interface") {
44294
+ const answer = storedContractAnswer(result);
44295
+ result = answer.spec;
44296
+ resolvedSignatures = answer.resolvedSignatures;
44297
+ }
43394
44298
  if (methods !== void 0) {
43395
44299
  const declared = result.methods;
43396
44300
  if (!Array.isArray(declared)) {
@@ -43412,19 +44316,23 @@ ${renderChangeReport(report3)}`,
43412
44316
  warning: `PARTIAL: ${names.length - kept.length} of this spec's ${names.length} methods are not in this answer. Never re-author from it \u2014 sdd_define_interface and sdd_write_narrative REPLACE the method list, so every method missing here would be removed from the spec.`
43413
44317
  };
43414
44318
  result = { ...result, methods: kept };
44319
+ resolvedSignatures = resolvedSignatures.filter((r) => methods.includes(r.method));
43415
44320
  }
43416
44321
  const guidance = kind === "component" ? resolveComponentVariantGuidance(result) : null;
44322
+ const resolved = resolvedSignatures.length > 0 ? { resolvedSignatures } : {};
43417
44323
  const folded = {
43418
44324
  ...result,
43419
44325
  ...partial2 ? { partialResult: partial2 } : {},
43420
- ...guidance ? { variantGuidance: guidance } : {}
44326
+ ...guidance ? { variantGuidance: guidance } : {},
44327
+ ...resolved
43421
44328
  };
43422
44329
  return structured(JSON.stringify(folded, null, 2), {
43423
44330
  kind,
43424
44331
  id,
43425
44332
  spec: result,
43426
44333
  ...partial2 ? { partialResult: partial2 } : {},
43427
- ...guidance ? { variantGuidance: guidance } : {}
44334
+ ...guidance ? { variantGuidance: guidance } : {},
44335
+ ...resolved
43428
44336
  });
43429
44337
  } catch (e) {
43430
44338
  return errText(String(e));
@@ -43971,6 +44879,15 @@ var init_server = __esm({
43971
44879
  }).optional().describe(
43972
44880
  "Derived, read-only guidance for a variant-tagged component \u2014 resolved from the variant registry, not part of the spec. Never write it back."
43973
44881
  ),
44882
+ resolvedSignatures: import_zod11.z.array(import_zod11.z.object({
44883
+ method: import_zod11.z.string().describe("The contract method that takes its signature from a source."),
44884
+ signatureFrom: import_zod11.z.string().describe("The source it names, as the spec holds it."),
44885
+ signature: import_zod11.z.string().describe("The text it shows, derived from the resolved params and returns."),
44886
+ params: import_zod11.z.array(import_zod11.z.record(import_zod11.z.unknown())).optional().describe("The params the source supplies; absent when it supplies none (unresolved, ambiguous or chained)."),
44887
+ returns: import_zod11.z.string().describe("The returns the source supplies (`unknown` when it supplies none).")
44888
+ })).optional().describe(
44889
+ "Derived, read-only: for a contract whose methods take their signature from a source, each such method's RESOLVED params, returns and text. The spec carries those methods in stored form (the signatureFrom alone) so it can be re-authored from as it is; never write these back beside the source."
44890
+ ),
43974
44891
  ...staleServerOutput
43975
44892
  };
43976
44893
  FINGERPRINT_VERSION = 1;
@@ -47068,6 +47985,19 @@ async function runDoctor(options = {}) {
47068
47985
  } catch (e) {
47069
47986
  line(tally, "warn", `Could not plan the narrative-step repair: ${e instanceof Error ? e.message : String(e)}`);
47070
47987
  }
47988
+ try {
47989
+ const signatures = repairSignatures(false);
47990
+ if (signatures.length > 0) {
47991
+ console.log(import_chalk12.default.bold("Signatures"));
47992
+ for (const repair of signatures) {
47993
+ line(tally, "warn", `${repair.kind} ${repair.specId}: ${describeSignatureRepair(repair)}`);
47994
+ }
47995
+ line(tally, "warn", "Run `wairon doctor --fix` to write them in their stored form (any save of the spec does too).");
47996
+ logger.blank();
47997
+ }
47998
+ } catch (e) {
47999
+ line(tally, "warn", `Could not plan the signature repair: ${e instanceof Error ? e.message : String(e)}`);
48000
+ }
47071
48001
  try {
47072
48002
  const planned = plan5({ verb: "chaining", rehearse: false });
47073
48003
  const pending4 = planned.chaining;
@@ -47288,6 +48218,17 @@ async function applyFixes(options, tally) {
47288
48218
  } catch (e) {
47289
48219
  console.log(` ${icon("error")} Narrative-step repair failed: ${e instanceof Error ? e.message : String(e)}`);
47290
48220
  }
48221
+ try {
48222
+ const signatures = repairSignatures(true);
48223
+ if (signatures.length > 0) {
48224
+ console.log(` ${icon("ok")} Rewrote ${signatures.length} spec(s) into their stored signature form:`);
48225
+ for (const repair of signatures) {
48226
+ console.log(` ${repair.kind} ${repair.specId}: ${describeSignatureRepair(repair)}`);
48227
+ }
48228
+ }
48229
+ } catch (e) {
48230
+ console.log(` ${icon("error")} Signature repair failed: ${e instanceof Error ? e.message : String(e)}`);
48231
+ }
47291
48232
  await migrateChaining(options, tally);
47292
48233
  await repairMcpRegistration(options, targets);
47293
48234
  logger.blank();
@@ -47504,6 +48445,12 @@ function printApplied(report3) {
47504
48445
  console.log(` ${icon("warn")} The writes staled the approval of ${report3.relock.length} project(s); nothing was locked here. Re-lock each with \`wairon lock\`:`);
47505
48446
  for (const dir of report3.relock) console.log(` ${dir}`);
47506
48447
  }
48448
+ function describeSignatureRepair(repair) {
48449
+ const parts = [];
48450
+ if (repair.regenerated.length > 0) parts.push(`regenerates the text of ${repair.regenerated.map((s) => s.method).join(", ")}`);
48451
+ if (repair.dropped.length > 0) parts.push(`drops the restated signature of ${repair.dropped.join(", ")}`);
48452
+ return parts.join("; ");
48453
+ }
47507
48454
  function pendingCount(migration) {
47508
48455
  return migration.rewrites.length + migration.projects.reduce((n, p) => n + (p.idToWrite ? 1 : 0) + (p.createsSystem ? 1 : 0) + p.exports.length + p.externals.length + p.imports.length + p.pins.length + p.members.length + p.supersededPins.length + p.locations.length, 0);
47509
48456
  }
@@ -51909,11 +52856,13 @@ function listProducerConfigs() {
51909
52856
  init_core();
51910
52857
 
51911
52858
  // src/producers/projection.ts
52859
+ init_models();
51912
52860
  function project(diagramUrl) {
51913
52861
  const system = loadSystemSpec();
51914
52862
  const subsystems = loadSubsystemSpecs();
51915
52863
  const components = loadComponentSpecs();
51916
52864
  const interfaces = loadInterfaceSpecs();
52865
+ const types = loadTypeSpecs();
51917
52866
  let mermaid = "";
51918
52867
  try {
51919
52868
  mermaid = renderDiagram("mermaid");
@@ -51921,7 +52870,7 @@ function project(diagramUrl) {
51921
52870
  }
51922
52871
  return {
51923
52872
  title: "wairon specs",
51924
- body: systemBody(system, mermaid, diagramUrl),
52873
+ body: systemBody(system, mermaid, diagramUrl, types),
51925
52874
  children: subsystems.map((sub) => ({
51926
52875
  title: sub.name || sub.id,
51927
52876
  body: subsystemBody(sub),
@@ -51950,7 +52899,7 @@ function projectGraph() {
51950
52899
  }
51951
52900
  return { nodes, edges };
51952
52901
  }
51953
- function systemBody(system, mermaid, diagramUrl) {
52902
+ function systemBody(system, mermaid, diagramUrl, types) {
51954
52903
  const parts = ["_Generated by wairon from the spec tree \u2014 edit the specs, not this page._", ""];
51955
52904
  if (system) {
51956
52905
  parts.push(`# ${system.name}`, "");
@@ -51961,6 +52910,14 @@ function systemBody(system, mermaid, diagramUrl) {
51961
52910
  }
51962
52911
  if (diagramUrl) parts.push("## Diagram", `[Open the interactive canvas](${diagramUrl})`, "");
51963
52912
  if (mermaid) parts.push("```mermaid", mermaid.trim(), "```", "");
52913
+ const signatures = types.filter((t) => t.kind === "signature");
52914
+ if (signatures.length) {
52915
+ parts.push("## Signature types");
52916
+ for (const t of signatures) {
52917
+ parts.push(`- **${t.name || t.id}** \`${deriveTypeSignature(t)}\`${t.description ? ` \u2014 ${t.description}` : ""}`);
52918
+ }
52919
+ parts.push("");
52920
+ }
51964
52921
  return parts.join("\n");
51965
52922
  }
51966
52923
  function publicInterfaceLine(p) {
@@ -51988,7 +52945,10 @@ function componentBody(comp, interfaces) {
51988
52945
  const intf = interfaces.find((i) => i.component === comp.id);
51989
52946
  if (intf?.methods?.length) {
51990
52947
  parts.push("## Methods");
51991
- for (const m of intf.methods) parts.push(`- **${m.name}** \`${m.signature}\` \u2192 ${m.returns}`);
52948
+ for (const m of intf.methods) {
52949
+ const from = m.signatureFrom ? ` (from \`${m.signatureFrom}\`)` : "";
52950
+ parts.push(`- **${m.name}** \`${m.signature}\`${from} \u2192 ${m.returns}`);
52951
+ }
51992
52952
  parts.push("");
51993
52953
  }
51994
52954
  return parts.join("\n");
@@ -56838,8 +57798,8 @@ function narrowToScope(projects, scope) {
56838
57798
  if (scope.all) return projects;
56839
57799
  return projects.filter((p) => scope.projectIds.includes(p.id));
56840
57800
  }
56841
- function resolveQuotaPolicy(cfg) {
56842
- return cfg.quotaPolicy ?? DISABLED_QUOTA_POLICY;
57801
+ function effectiveQuotaPolicy(cfg) {
57802
+ return cfg?.quotaPolicy ? { ...DISABLED_QUOTA_POLICY, ...cfg.quotaPolicy } : DISABLED_QUOTA_POLICY;
56843
57803
  }
56844
57804
  function scopeRecords(projects, scope) {
56845
57805
  if (scope && scope.length > 0 && scope !== "instance") {
@@ -57028,7 +57988,7 @@ function evaluateQuota(cfg, credential, scope) {
57028
57988
  const readScope = requireOperationsReadScope(cfg, principal);
57029
57989
  const projects = narrowToScope(listProjectRecords(cfg.dataDir), readScope);
57030
57990
  const usage = collectUsage(projects, scope);
57031
- const policy = resolveQuotaPolicy(cfg);
57991
+ const policy = effectiveQuotaPolicy(cfg);
57032
57992
  return evaluateUsage(usage, policy);
57033
57993
  }
57034
57994
  function principalSubject6(principal) {
@@ -63377,6 +64337,8 @@ function resolveHostConfig(options) {
63377
64337
  if (builtinPassword) cfg.builtinAdminPassword = builtinPassword;
63378
64338
  const auditPolicy = process.env["WAIRON_AUDIT_POLICY"];
63379
64339
  if (auditPolicy !== void 0 && auditPolicy.trim() !== "") cfg.auditPolicy = parseAuditPolicyEnv(auditPolicy);
64340
+ const quotaPolicy = process.env["WAIRON_QUOTA_POLICY"];
64341
+ if (quotaPolicy !== void 0 && quotaPolicy.trim() !== "") cfg.quotaPolicy = parseQuotaPolicyEnv(quotaPolicy);
63380
64342
  return cfg;
63381
64343
  }
63382
64344
  var AUDIT_POLICY_FIELDS = {
@@ -63421,6 +64383,47 @@ function parseAuditPolicyEnv(raw) {
63421
64383
  }
63422
64384
  return policy;
63423
64385
  }
64386
+ var NON_NEGATIVE_INTEGER = {
64387
+ expects: "a non-negative integer",
64388
+ accepts: (v) => typeof v === "number" && Number.isInteger(v) && v >= 0
64389
+ };
64390
+ var QUOTA_POLICY_FIELDS = {
64391
+ enabled: { expects: "a boolean", accepts: (v) => typeof v === "boolean" },
64392
+ // 'block' is accepted, and evaluation still downgrades it to an observation.
64393
+ mode: {
64394
+ expects: 'one of "observe", "warn", "block"',
64395
+ accepts: (v) => typeof v === "string" && ["observe", "warn", "block"].includes(v)
64396
+ },
64397
+ maxProjectsPerUser: NON_NEGATIVE_INTEGER,
64398
+ maxMcpRequestsPerMinute: NON_NEGATIVE_INTEGER,
64399
+ maxProjectBytes: NON_NEGATIVE_INTEGER,
64400
+ maxAuditEventsPerDay: NON_NEGATIVE_INTEGER
64401
+ };
64402
+ function parseQuotaPolicyEnv(raw) {
64403
+ let parsed;
64404
+ try {
64405
+ parsed = JSON.parse(raw);
64406
+ } catch (e) {
64407
+ throw new WaironError(`WAIRON_QUOTA_POLICY is not valid JSON: ${e.message}`);
64408
+ }
64409
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
64410
+ throw new WaironError("WAIRON_QUOTA_POLICY must be a JSON object of quota policy fields.");
64411
+ }
64412
+ const policy = {};
64413
+ for (const [key, value] of Object.entries(parsed)) {
64414
+ const field = QUOTA_POLICY_FIELDS[key];
64415
+ if (!Object.prototype.hasOwnProperty.call(QUOTA_POLICY_FIELDS, key) || !field) {
64416
+ throw new WaironError(
64417
+ `WAIRON_QUOTA_POLICY sets an unknown field "${key}"; the known fields are ${Object.keys(QUOTA_POLICY_FIELDS).join(", ")}.`
64418
+ );
64419
+ }
64420
+ if (!field.accepts(value)) {
64421
+ throw new WaironError(`WAIRON_QUOTA_POLICY field "${key}" must be ${field.expects}.`);
64422
+ }
64423
+ policy[key] = value;
64424
+ }
64425
+ return policy;
64426
+ }
63424
64427
  function masterCredential() {
63425
64428
  return process.env["WAIRON_ADMIN_TOKEN"] ?? null;
63426
64429
  }