@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/index.js CHANGED
@@ -3484,6 +3484,10 @@ __export(src_exports, {
3484
3484
  RETIRED_STEREOTYPES: () => RETIRED_STEREOTYPES,
3485
3485
  RegistrySchema: () => RegistrySchema,
3486
3486
  RequirementItemSchema: () => RequirementItemSchema,
3487
+ ResolvedInterfaceSpecSchema: () => ResolvedInterfaceSpecSchema,
3488
+ ResolvedMethodSignatureSchema: () => ResolvedMethodSignatureSchema,
3489
+ ResolvedTypeMethodSchema: () => ResolvedTypeMethodSchema,
3490
+ ResolvedTypeSpecSchema: () => ResolvedTypeSpecSchema,
3487
3491
  RulesConfigSchema: () => RulesConfigSchema,
3488
3492
  SCAN_EXCLUDE_DIRS: () => SCAN_EXCLUDE_DIRS,
3489
3493
  SDD_RULES: () => SDD_RULES,
@@ -3495,6 +3499,8 @@ __export(src_exports, {
3495
3499
  SURFACE_AUDIENCES: () => SURFACE_AUDIENCES,
3496
3500
  SpecIdSchema: () => SpecIdSchema,
3497
3501
  SpecStatusSchema: () => SpecStatusSchema,
3502
+ StoredMethodSignatureSchema: () => StoredMethodSignatureSchema,
3503
+ StoredTypeMethodSchema: () => StoredTypeMethodSchema,
3498
3504
  SubsystemSpecSchema: () => SubsystemSpecSchema,
3499
3505
  SurfaceAudienceSchema: () => SurfaceAudienceSchema,
3500
3506
  SurfaceContractEntrySchema: () => SurfaceContractEntrySchema,
@@ -3579,6 +3585,8 @@ __export(src_exports, {
3579
3585
  dependencyCycles: () => dependencyCycles,
3580
3586
  deregisterPackRef: () => deregisterPackRef,
3581
3587
  deriveExecutionProfile: () => deriveExecutionProfile,
3588
+ deriveMethodSignature: () => deriveMethodSignature,
3589
+ deriveTypeSignature: () => deriveTypeSignature,
3582
3590
  derivedDocPaths: () => derivedDocPaths,
3583
3591
  describeProject: () => describeProject,
3584
3592
  detectDomainCandidates: () => detectDomainCandidates,
@@ -3738,6 +3746,7 @@ __export(src_exports, {
3738
3746
  renameMethod: () => renameMethod,
3739
3747
  renderDiagram: () => renderDiagram,
3740
3748
  repairForeignStepFields: () => repairForeignStepFields,
3749
+ repairSignatures: () => repairSignatures,
3741
3750
  repointExternal: () => repointExternal,
3742
3751
  requiredPolicies: () => requiredPolicies,
3743
3752
  resolveAgentTopology: () => resolveAgentTopology,
@@ -3749,6 +3758,7 @@ __export(src_exports, {
3749
3758
  resolveImport: () => resolveImport,
3750
3759
  resolveInstalledPack: () => resolveInstalledPack,
3751
3760
  resolveProjectExports: () => resolveProjectExports,
3761
+ resolveSignatures: () => resolveTree,
3752
3762
  resolveSubprojectForNamespace: () => resolveSubprojectForNamespace,
3753
3763
  resolveSubsystemExports: () => resolveSubsystemExports,
3754
3764
  resolveVariantGuidance: () => resolveVariantGuidance,
@@ -3770,11 +3780,15 @@ __export(src_exports, {
3770
3780
  setProjectRoot: () => setProjectRoot,
3771
3781
  setProjectType: () => setProjectType,
3772
3782
  settledSpecPaths: () => settledSpecPaths,
3783
+ signatureFacts: () => signatureFacts,
3784
+ signatureTypeRefs: () => signatureTypeRefs,
3773
3785
  specPathsInScope: () => specPathsInScope,
3774
3786
  splitNamespace: () => splitNamespace,
3775
3787
  stepConfigVerdict: () => stepConfigVerdict,
3776
3788
  stepFieldsFor: () => stepFieldsFor,
3777
3789
  stepGraph: () => stepGraph,
3790
+ storedMethodSignature: () => storedMethodSignature,
3791
+ storedTypeMethod: () => storedTypeMethod,
3778
3792
  syncContextFiles: () => syncContextFiles,
3779
3793
  technologyName: () => technologyName,
3780
3794
  technologyTokens: () => technologyTokens,
@@ -7094,13 +7108,21 @@ var FindingDeclarationSchema = import_zod8.z.object({
7094
7108
  var MethodSignatureSchema = import_zod8.z.object({
7095
7109
  name: import_zod8.z.string().regex(/^[a-zA-Z0-9_]+$/, "Method name must be alphanumeric"),
7096
7110
  description: import_zod8.z.string(),
7097
- signature: import_zod8.z.string(),
7111
+ signature: import_zod8.z.string().optional(),
7098
7112
  // e.g. "save(key: string, data: Buffer): Promise<void>"
7099
7113
  // e.g. "Promise<void>", or a union: "Invoice | null" — the commonest shape in
7100
7114
  // any real tree. See the grammar on src/models/type-references.ts.
7101
- returns: import_zod8.z.string(),
7115
+ returns: import_zod8.z.string().optional(),
7102
7116
  /** Structured parameters (authoritative for type checking when present). */
7103
7117
  params: import_zod8.z.array(MethodParamSchema).optional(),
7118
+ /**
7119
+ * Where this method takes its params and returns from instead of stating
7120
+ * them: a signature type (`billing.change_listener`, `alias::name`), or a
7121
+ * contract method `component.method` its component reaches along a
7122
+ * dependsOn/owns edge. Resolved both ways by the loader (signature_resolver);
7123
+ * the writer stores a sourced method with only its source.
7124
+ */
7125
+ signatureFrom: import_zod8.z.string().min(1).optional(),
7104
7126
  /** Concrete wire binding for this method when its component is a Portal (set via sdd_set_endpoints). */
7105
7127
  endpoint: EndpointSchema.optional(),
7106
7128
  /**
@@ -7153,6 +7175,54 @@ var MethodSignatureSchema = import_zod8.z.object({
7153
7175
  /** Opaque pack/tool extension data (see ExtDataSchema) — preserved verbatim. */
7154
7176
  ext: ExtDataSchema.optional()
7155
7177
  });
7178
+ function requireStoredSignature(method, ctx) {
7179
+ if (method.signatureFrom !== void 0) return;
7180
+ if (method.returns === void 0) {
7181
+ ctx.addIssue({ code: import_zod8.z.ZodIssueCode.custom, path: ["returns"], message: "Required: a method without a signatureFrom states its returns" });
7182
+ }
7183
+ if (method.params === void 0 && method.signature === void 0) {
7184
+ ctx.addIssue({ code: import_zod8.z.ZodIssueCode.custom, path: ["signature"], message: "Required: a method without params or a signatureFrom states a prose signature" });
7185
+ }
7186
+ }
7187
+ var StoredMethodSignatureSchema = MethodSignatureSchema.superRefine(requireStoredSignature);
7188
+ var ResolvedMethodSignatureSchema = MethodSignatureSchema.extend({
7189
+ signature: import_zod8.z.string(),
7190
+ returns: import_zod8.z.string()
7191
+ });
7192
+ function paramText(p) {
7193
+ return `${p.name}${p.optional ? "?" : ""}: ${p.type}`;
7194
+ }
7195
+ function leadingGenericList(name, stored) {
7196
+ if (!stored) return "";
7197
+ const text3 = stored.trimStart();
7198
+ if (!text3.startsWith(name)) return "";
7199
+ let i = name.length;
7200
+ while (text3[i] === " ") i++;
7201
+ if (text3[i] !== "<") return "";
7202
+ let depth = 0;
7203
+ for (let j = i; j < text3.length; j++) {
7204
+ if (text3[j] === "<") depth++;
7205
+ else if (text3[j] === ">") depth--;
7206
+ if (depth === 0) {
7207
+ const rest = text3.slice(j + 1).trimStart();
7208
+ return rest.startsWith("(") ? text3.slice(i, j + 1) : "";
7209
+ }
7210
+ }
7211
+ return "";
7212
+ }
7213
+ function deriveMethodSignature(method) {
7214
+ if (!method.params) return void 0;
7215
+ const generics = leadingGenericList(method.name, method.signature);
7216
+ return `${method.name}${generics}(${method.params.map(paramText).join(", ")}): ${method.returns ?? "unknown"}`;
7217
+ }
7218
+ function storedMethodSignature(method) {
7219
+ if (method.signatureFrom !== void 0) {
7220
+ const { params: _params, returns: _returns, signature: _signature, ...rest } = method;
7221
+ return rest;
7222
+ }
7223
+ const derived = deriveMethodSignature(method);
7224
+ return derived === void 0 ? method : { ...method, signature: derived };
7225
+ }
7156
7226
  var INTENT_FLOOR_MIN_CHARS = 40;
7157
7227
  function passesIntentFloor(text3, methodName) {
7158
7228
  if (!text3) return false;
@@ -7168,7 +7238,7 @@ var InterfaceSpecSchema = import_zod8.z.object({
7168
7238
  description: import_zod8.z.string(),
7169
7239
  component: import_zod8.z.string(),
7170
7240
  // References L2 Component id
7171
- methods: import_zod8.z.array(MethodSignatureSchema).default([]),
7241
+ methods: import_zod8.z.array(StoredMethodSignatureSchema).default([]),
7172
7242
  /** Per-spec lint suppressions (see LintConfigSchema). */
7173
7243
  lint: LintConfigSchema.optional(),
7174
7244
  /** Opaque pack/tool extension data (see ExtDataSchema) — preserved verbatim. */
@@ -7177,6 +7247,9 @@ var InterfaceSpecSchema = import_zod8.z.object({
7177
7247
  createdAt: import_zod8.z.string().datetime(),
7178
7248
  updatedAt: import_zod8.z.string().datetime()
7179
7249
  });
7250
+ var ResolvedInterfaceSpecSchema = InterfaceSpecSchema.extend({
7251
+ methods: import_zod8.z.array(ResolvedMethodSignatureSchema).default([])
7252
+ });
7180
7253
  var NarrativeStepTypeSchema = import_zod8.z.enum([
7181
7254
  "local",
7182
7255
  // in-component work
@@ -7514,7 +7587,7 @@ function implementationSourceFiles(impl) {
7514
7587
  for (const method of impl.methods ?? []) add2(method.sourcePath);
7515
7588
  return files;
7516
7589
  }
7517
- var TypeKindSchema = import_zod8.z.enum(["entity", "value-object"]);
7590
+ var TypeKindSchema = import_zod8.z.enum(["entity", "value-object", "signature"]);
7518
7591
  var TypeFieldSchema = import_zod8.z.object({
7519
7592
  name: import_zod8.z.string(),
7520
7593
  // A primitive, or another type id (qualified across subsystems, e.g.
@@ -7538,7 +7611,9 @@ var TypeFieldSchema = import_zod8.z.object({
7538
7611
  });
7539
7612
  var TypeMethodSchema = import_zod8.z.object({
7540
7613
  name: import_zod8.z.string(),
7541
- signature: import_zod8.z.string(),
7614
+ signature: import_zod8.z.string().optional(),
7615
+ /** Structured parameters in the contract method's param shape — authoritative for type checking when present. */
7616
+ params: import_zod8.z.array(MethodParamSchema).optional(),
7542
7617
  returns: import_zod8.z.string(),
7543
7618
  description: import_zod8.z.string().optional(),
7544
7619
  /**
@@ -7557,6 +7632,14 @@ var TypeMethodSchema = import_zod8.z.object({
7557
7632
  */
7558
7633
  symbol: import_zod8.z.string().optional()
7559
7634
  });
7635
+ var ResolvedTypeMethodSchema = TypeMethodSchema.extend({
7636
+ signature: import_zod8.z.string()
7637
+ });
7638
+ var StoredTypeMethodSchema = TypeMethodSchema.superRefine((method, ctx) => {
7639
+ if (method.params === void 0 && method.signature === void 0) {
7640
+ ctx.addIssue({ code: import_zod8.z.ZodIssueCode.custom, path: ["signature"], message: "Required: a type method without params states a prose signature" });
7641
+ }
7642
+ });
7560
7643
  var InvariantSchema = import_zod8.z.object({
7561
7644
  /** Stable invariant id, unique within the entity (referenced as "<type-id>.<invariant-id>"). */
7562
7645
  id: SpecIdSchema,
@@ -7565,7 +7648,7 @@ var InvariantSchema = import_zod8.z.object({
7565
7648
  });
7566
7649
  var TypeSpecSchema = import_zod8.z.object({
7567
7650
  kind: TypeKindSchema,
7568
- // discriminator — entity | value-object
7651
+ // discriminator — entity | value-object | signature
7569
7652
  id: SpecIdSchema,
7570
7653
  name: import_zod8.z.string(),
7571
7654
  description: import_zod8.z.string().optional(),
@@ -7575,7 +7658,7 @@ var TypeSpecSchema = import_zod8.z.object({
7575
7658
  group: import_zod8.z.string().optional(),
7576
7659
  fields: import_zod8.z.array(TypeFieldSchema).default([]),
7577
7660
  /** Pure intrinsic behaviour only — anything needing a collaborator belongs on a component. */
7578
- methods: import_zod8.z.array(TypeMethodSchema).default([]),
7661
+ methods: import_zod8.z.array(StoredTypeMethodSchema).default([]),
7579
7662
  /**
7580
7663
  * Linked Component ID if this system entity is implemented as a class Component
7581
7664
  * (e.g., a Store or Registry that owns this entity's lifecycle and methods).
@@ -7621,9 +7704,27 @@ var TypeSpecSchema = import_zod8.z.object({
7621
7704
  lint: LintConfigSchema.optional(),
7622
7705
  /** Opaque pack/tool extension data (see ExtDataSchema) — preserved verbatim. */
7623
7706
  ext: ExtDataSchema.optional(),
7707
+ /**
7708
+ * A signature's parameters, in the contract method's param shape; only on
7709
+ * kind signature (SIGNATURE_TYPE_MEMBERS otherwise).
7710
+ */
7711
+ params: import_zod8.z.array(MethodParamSchema).optional(),
7712
+ /** A signature's one output type; required on kind signature and only there (SIGNATURE_TYPE_MEMBERS). */
7713
+ returns: import_zod8.z.string().optional(),
7624
7714
  createdAt: import_zod8.z.string().datetime(),
7625
7715
  updatedAt: import_zod8.z.string().datetime()
7626
7716
  });
7717
+ var ResolvedTypeSpecSchema = TypeSpecSchema.extend({
7718
+ methods: import_zod8.z.array(ResolvedTypeMethodSchema).default([])
7719
+ });
7720
+ function deriveTypeSignature(type) {
7721
+ if (type.kind !== "signature") return void 0;
7722
+ return `(${(type.params ?? []).map(paramText).join(", ")}): ${type.returns ?? "unknown"}`;
7723
+ }
7724
+ function storedTypeMethod(method) {
7725
+ const derived = deriveMethodSignature(method);
7726
+ return derived === void 0 ? method : { ...method, signature: derived };
7727
+ }
7627
7728
  function typeSourceFiles(type) {
7628
7729
  const files = [];
7629
7730
  const add2 = (file) => {
@@ -7643,7 +7744,16 @@ var SurfaceTypeDefSchema = import_zod8.z.object({
7643
7744
  type: import_zod8.z.string(),
7644
7745
  description: import_zod8.z.string().optional(),
7645
7746
  optional: import_zod8.z.boolean().optional()
7646
- })).default([])
7747
+ })).default([]),
7748
+ /** A signature's parameters (name, type, optional, description) in declared order; only on kind signature. */
7749
+ params: import_zod8.z.array(import_zod8.z.object({
7750
+ name: import_zod8.z.string(),
7751
+ type: import_zod8.z.string(),
7752
+ description: import_zod8.z.string().optional(),
7753
+ optional: import_zod8.z.boolean().optional()
7754
+ })).optional(),
7755
+ /** A signature's one output type; only on kind signature. */
7756
+ returns: import_zod8.z.string().optional()
7647
7757
  });
7648
7758
  var SurfaceTypeExportSchema = import_zod8.z.object({
7649
7759
  /** The type's public name in the producer's export table. */
@@ -7662,8 +7772,8 @@ var SurfaceContractEntrySchema = import_zod8.z.object({
7662
7772
  type: import_zod8.z.string().default("Custom"),
7663
7773
  /** Local name of the backing Portal in the producing project. */
7664
7774
  component: import_zod8.z.string(),
7665
- /** Full contract methods (params, returns, guarantees, effect, endpoint). */
7666
- methods: import_zod8.z.array(MethodSignatureSchema).default([]),
7775
+ /** Full contract methods (params, returns, guarantees, effect, endpoint), resolved: a snapshot names no producer-internal source. */
7776
+ methods: import_zod8.z.array(ResolvedMethodSignatureSchema).default([]),
7667
7777
  /** The backing portal's capability dispatch table, when generic-dispatch. */
7668
7778
  dispatch: import_zod8.z.array(DispatchBindingSchema).optional(),
7669
7779
  details: import_zod8.z.string().default(""),
@@ -7913,13 +8023,18 @@ function methodTypeRefs(m) {
7913
8023
  for (const p of m.params) {
7914
8024
  refs.push(...extractTypeIdentifiers(p.type));
7915
8025
  }
7916
- refs.push(...extractTypeIdentifiers(m.returns));
8026
+ refs.push(...extractTypeIdentifiers(m.returns ?? ""));
7917
8027
  return Array.from(new Set(refs));
7918
8028
  }
7919
- return extractTypesFromSignature(m.signature, m.returns);
8029
+ return extractTypesFromSignature(m.signature ?? "", m.returns ?? "");
8030
+ }
8031
+ function signatureTypeRefs(type) {
8032
+ if (type.kind !== "signature") return [];
8033
+ const refs = [...(type.params ?? []).flatMap((p) => extractTypeIdentifiers(p.type)), ...extractTypeIdentifiers(type.returns ?? "")];
8034
+ return Array.from(new Set(refs));
7920
8035
  }
7921
8036
  function methodGenericParameters(method) {
7922
- return extractGenericTypeVariables(method.signature);
8037
+ return extractGenericTypeVariables(method.signature ?? "");
7923
8038
  }
7924
8039
  function normalizePart(part) {
7925
8040
  return part.toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -8306,9 +8421,21 @@ function typeShape(snapshot, def) {
8306
8421
  return {
8307
8422
  id: def.id,
8308
8423
  kind: def.kind,
8309
- fields: sortedBy(def.fields, (f) => f.name).map((f) => ({ name: f.name, type: canonicalTypeRef(snapshot, f.type), optional: f.optional === true }))
8424
+ fields: sortedBy(def.fields, (f) => f.name).map((f) => ({ name: f.name, type: canonicalTypeRef(snapshot, f.type), optional: f.optional === true })),
8425
+ // A signature's shape: its params' types and optionality in order (never their names, as a method's), and its returns.
8426
+ ...def.kind === "signature" ? {
8427
+ params: (def.params ?? []).map((p) => ({ type: canonicalTypeRef(snapshot, p.type), optional: p.optional === true })),
8428
+ returns: canonicalTypeRef(snapshot, def.returns ?? "unknown")
8429
+ } : {}
8310
8430
  };
8311
8431
  }
8432
+ function typeDefExprs(def) {
8433
+ return [
8434
+ ...def.fields.map((f) => f.type),
8435
+ ...(def.params ?? []).map((p) => p.type),
8436
+ ...def.returns !== void 0 ? [def.returns] : []
8437
+ ];
8438
+ }
8312
8439
  function closureShapes(snapshot, exprs) {
8313
8440
  const byId = new Map(snapshot.types.map((def) => [def.id, def]));
8314
8441
  const seen = /* @__PURE__ */ new Map();
@@ -8317,7 +8444,7 @@ function closureShapes(snapshot, exprs) {
8317
8444
  const def = byId.get(queue.shift());
8318
8445
  if (!def || seen.has(def.id)) continue;
8319
8446
  seen.set(def.id, def);
8320
- for (const field of def.fields) queue.push(...extractTypeIdentifiers(canonicalTypeRef(snapshot, field.type)));
8447
+ for (const expr of typeDefExprs(def)) queue.push(...extractTypeIdentifiers(canonicalTypeRef(snapshot, expr)));
8321
8448
  }
8322
8449
  return [...seen.keys()].sort().map((id) => typeShape(snapshot, seen.get(id)));
8323
8450
  }
@@ -9229,6 +9356,7 @@ function buildCanvasModel(issues = [], relations) {
9229
9356
  signature: m.signature,
9230
9357
  returns: m.returns,
9231
9358
  ...m.params && m.params.length ? { params: m.params } : {},
9359
+ ...m.signatureFrom ? { signatureFrom: m.signatureFrom } : {},
9232
9360
  ...m.endpoint ? { endpoint: m.endpoint } : {},
9233
9361
  ...m.guarantees && m.guarantees.length ? { guarantees: m.guarantees } : {}
9234
9362
  }))
@@ -9294,6 +9422,7 @@ function buildCanvasModel(issues = [], relations) {
9294
9422
  ...f.references ? { references: f.references } : {}
9295
9423
  })),
9296
9424
  methods: t.methods.map((m) => ({ name: m.name, signature: m.signature, returns: m.returns, ...m.description ? { description: m.description } : {} })),
9425
+ ...t.kind === "signature" ? { signature: deriveTypeSignature(t) } : {},
9297
9426
  usedBy: usedByFor(t),
9298
9427
  ...t.componentClass ? { componentClass: t.componentClass } : {},
9299
9428
  ...t.database ? { database: t.database } : {},
@@ -11028,15 +11157,17 @@ var MODEL = __MODEL_JSON__;
11028
11157
  function tableShape(t) {
11029
11158
  var fields = visibleFields(t);
11030
11159
  var meths = det === 'full' ? t.methods : [];
11160
+ // A signature type is drawn with its derived text in place of a field list.
11161
+ var sig = t.signature && det !== 'names' ? t.signature : '';
11031
11162
  var head = t.name + ' \\u00AB' + t.kind + '\\u00BB';
11032
- var rows = fields.map(function (f) { return rowText(t, f); })
11163
+ var rows = (sig ? [sig] : []).concat(fields.map(function (f) { return rowText(t, f); }))
11033
11164
  .concat(meths.map(function (m) { return '\\u0192 ' + m.name + '(): ' + m.returns; }));
11034
11165
  var longest = head.length + 4;
11035
11166
  rows.forEach(function (r) { if (r.length > longest) longest = r.length; });
11036
11167
  var plain = rows.length === 0;
11037
11168
  var pw = Math.max(170, head.length * 6.8 + 26);
11038
11169
  var W = Math.max(210, Math.min(400, longest * 6.6 + 30));
11039
- 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 };
11170
+ 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 };
11040
11171
  }
11041
11172
 
11042
11173
  // Emit one type table with its top-left at (ax, ay); returns its size.
@@ -11060,6 +11191,13 @@ var MODEL = __MODEL_JSON__;
11060
11191
  position: { x: ax + sh.w / 2, y: ay + TH_H / 2 }, classes: 'typeHead ' + kindCls + (dim ? ' dimmed' : ''), grabbable: false,
11061
11192
  });
11062
11193
  var ry = ay + TH_H;
11194
+ if (sh.sig) {
11195
+ eles.push({
11196
+ data: { id: 'TS~' + t.id, parent: 'T~' + t.id, label: sh.sig, w: sh.w, h: ROW_H, tw: sh.w - 14 },
11197
+ position: { x: ax + sh.w / 2, y: ry + ROW_H / 2 }, classes: 'typeRow methRow' + (dim ? ' dimmed' : ''), grabbable: false,
11198
+ });
11199
+ ry += ROW_H;
11200
+ }
11063
11201
  sh.fields.forEach(function (f) {
11064
11202
  var rid = 'TF~' + t.id + '~' + f.name;
11065
11203
  rowIds[rid] = 1;
@@ -13146,7 +13284,7 @@ var MODEL = __MODEL_JSON__;
13146
13284
  var mi = methodInfo(top.comp, top.method);
13147
13285
  var parts = ['<div class="fstep" style="opacity:.7">No step-by-step narrative \\u2014 showing intent / contract:</div>'];
13148
13286
  if (mi) {
13149
- parts.push('<div class="fstep"><code>' + escText(mi.m.signature) + '</code></div>');
13287
+ parts.push('<div class="fstep"><code>' + escText(mi.m.signature) + '</code>' + (mi.m.signatureFrom ? ' from <code>' + escText(mi.m.signatureFrom) + '</code>' : '') + '</div>');
13150
13288
  parts.push('<div class="fstep">' + escText(mi.m.description) + (mi.m.returns ? ' \\u2014 returns ' + escText(mi.m.returns) : '') + '</div>');
13151
13289
  }
13152
13290
  if (intent) parts.push('<div class="fstep" style="font-style:italic">' + escText(intent) + '</div>');
@@ -13487,6 +13625,7 @@ var MODEL = __MODEL_JSON__;
13487
13625
  : '<span class="chip" style="opacity:.6">no narrative</span>')
13488
13626
  + '</div>'
13489
13627
  + '<code>' + esc(m.signature) + '</code>'
13628
+ + (m.signatureFrom ? '<div class="mdesc">signature from <code style="display:inline">' + esc(m.signatureFrom) + '</code></div>' : '')
13490
13629
  + '<div class="mdesc">' + esc(m.description) + ' \\u2014 returns ' + typeRefHtml(m.returns) + '</div>'
13491
13630
  + (mIntent && !hasNarr ? '<div class="mdesc" style="font-style:italic">' + esc(mIntent) + '</div>' : '')
13492
13631
  + (m.params ? '<div class="mdesc">params: ' + m.params.map(function (p) { return esc(p.name) + ': ' + typeRefHtml(p.type); }).join(', ') + '</div>' : '')
@@ -13590,7 +13729,11 @@ var MODEL = __MODEL_JSON__;
13590
13729
  + '</div>';
13591
13730
  }).join('')
13592
13731
  : '<span class="desc">no fields</span>';
13593
- body += section('Fields', ty.fields.length, fieldsInner, true);
13732
+ if (ty.signature) {
13733
+ body += section('Signature', 1, '<div class="method"><code>' + esc(ty.signature) + '</code></div>', true);
13734
+ } else {
13735
+ body += section('Fields', ty.fields.length, fieldsInner, true);
13736
+ }
13594
13737
  if (ty.usedBy && ty.usedBy.length) {
13595
13738
  body += section('Used by methods', ty.usedBy.length, ty.usedBy.map(function (u) {
13596
13739
  return chip(u.component + '.' + u.method + '()', 'component', u.component);
@@ -13929,21 +14072,30 @@ function computeTypeClosure(entries, types, exported = []) {
13929
14072
  for (const field of spec.fields) {
13930
14073
  for (const ref of extractTypeIdentifiers(field.type)) enqueueRef(ref);
13931
14074
  }
14075
+ for (const param of spec.params ?? []) {
14076
+ for (const ref of extractTypeIdentifiers(param.type)) enqueueRef(ref);
14077
+ }
14078
+ if (spec.returns) {
14079
+ for (const ref of extractTypeIdentifiers(spec.returns)) enqueueRef(ref);
14080
+ }
13932
14081
  }
13933
14082
  const usedIds = /* @__PURE__ */ new Set();
13934
14083
  return [...included.entries()].map(([qualified, t]) => {
13935
14084
  const id = usedIds.has(t.id) ? qualified : t.id;
13936
14085
  usedIds.add(id);
14086
+ const describe2 = (v) => ({
14087
+ name: v.name,
14088
+ type: v.type,
14089
+ ...v.description ? { description: v.description } : {},
14090
+ ...v.optional ? { optional: true } : {}
14091
+ });
13937
14092
  return {
13938
14093
  id,
13939
14094
  name: t.name,
13940
14095
  kind: t.kind,
13941
- fields: t.fields.map((f) => ({
13942
- name: f.name,
13943
- type: f.type,
13944
- ...f.description ? { description: f.description } : {},
13945
- ...f.optional ? { optional: true } : {}
13946
- }))
14096
+ fields: t.fields.map(describe2),
14097
+ // A signature type travels complete: its params and returns with it.
14098
+ ...t.kind === "signature" ? { params: (t.params ?? []).map(describe2), returns: t.returns ?? "unknown" } : {}
13947
14099
  };
13948
14100
  });
13949
14101
  }
@@ -13956,7 +14108,7 @@ function boundProjectId() {
13956
14108
  }
13957
14109
  }
13958
14110
  function contractEntry(entry, comp, interfaces) {
13959
- const methods = interfaces.filter((i) => i.component === comp.id && (!entry.interface || i.id === entry.interface)).flatMap((i) => i.methods);
14111
+ const methods = interfaces.filter((i) => i.component === comp.id && (!entry.interface || i.id === entry.interface)).flatMap((i) => i.methods).map(({ signatureFrom: _source, ...method }) => method);
13960
14112
  return {
13961
14113
  id: entry.publicName,
13962
14114
  name: entry.name ?? comp.name,
@@ -14026,7 +14178,12 @@ function canonicalReferences(snapshot) {
14026
14178
  ...m.params ? { params: m.params.map((p) => ({ ...p, type: canon(p.type) })) } : {}
14027
14179
  }))
14028
14180
  })),
14029
- types: snapshot.types.map((def) => ({ ...def, fields: def.fields.map((f) => ({ ...f, type: canon(f.type) })) }))
14181
+ types: snapshot.types.map((def) => ({
14182
+ ...def,
14183
+ fields: def.fields.map((f) => ({ ...f, type: canon(f.type) })),
14184
+ ...def.params ? { params: def.params.map((p) => ({ ...p, type: canon(p.type) })) } : {},
14185
+ ...def.returns !== void 0 ? { returns: canon(def.returns) } : {}
14186
+ }))
14030
14187
  };
14031
14188
  }
14032
14189
  function listSnapshots() {
@@ -15085,7 +15242,7 @@ function defaultTargetConfig(type) {
15085
15242
  enabled: true
15086
15243
  };
15087
15244
  }
15088
- var WAIRON_VERSION = "5.1.1-dev.94";
15245
+ var WAIRON_VERSION = "5.1.1-dev.95";
15089
15246
  var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
15090
15247
  var ARCHITECT_AGENT_ID = "agent-architect";
15091
15248
  var ARCHITECT_TEMPLATE_ID = "architect";
@@ -16118,7 +16275,7 @@ function buildImplementationMethods(ctx) {
16118
16275
  const component = ctx.componentMap.get(contract.component);
16119
16276
  if (!component) continue;
16120
16277
  if (ctx.isInChainedSubproject(component.subsystem)) continue;
16121
- const draftContext = ctx.isImplementationDraft(impl);
16278
+ const draftContext3 = ctx.isImplementationDraft(impl);
16122
16279
  for (const method of impl.methods) {
16123
16280
  const sourceFile = methodSourceFile(method, impl.sourcePath);
16124
16281
  out.push({
@@ -16126,7 +16283,7 @@ function buildImplementationMethods(ctx) {
16126
16283
  method,
16127
16284
  component,
16128
16285
  ...sourceFile !== void 0 ? { sourceFile } : {},
16129
- draftContext
16286
+ draftContext: draftContext3
16130
16287
  });
16131
16288
  }
16132
16289
  }
@@ -16431,7 +16588,7 @@ var fieldTypeReferencesRule = {
16431
16588
  var signatureTypeReferencesRule = {
16432
16589
  name: "signature-type-references",
16433
16590
  judges: "design",
16434
- 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.",
16591
+ 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.",
16435
16592
  codes: [
16436
16593
  { code: "UNDEFINED_TYPE_REFERENCE", defaultSeverity: "error", summary: "Reference to a type that is not defined anywhere" }
16437
16594
  ],
@@ -16442,6 +16599,7 @@ var signatureTypeReferencesRule = {
16442
16599
  Array.from(interfaceGenericParameters(intf)).map((g) => g.toLowerCase())
16443
16600
  );
16444
16601
  for (const m of intf.methods) {
16602
+ if (m.signatureFrom !== void 0) continue;
16445
16603
  const methodGenerics = new Set(
16446
16604
  Array.from(methodGenericParameters(m)).map((g) => g.toLowerCase())
16447
16605
  );
@@ -16461,6 +16619,206 @@ var signatureTypeReferencesRule = {
16461
16619
  }
16462
16620
  }
16463
16621
  }
16622
+ for (const t of ctx.types) {
16623
+ const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
16624
+ const isDraftCtx = !!sub && (sub.status === "draft" || sub.status === "design");
16625
+ const typeGenerics = new Set(Array.from(typeGenericParameters(t)).map((g) => g.toLowerCase()));
16626
+ const named2 = [
16627
+ ...t.methods.filter((m) => m.params !== void 0).flatMap((m) => methodTypeRefs(m).map((ref) => ({ ref, by: `Method "${m.name}" on type "${t.id}"` }))),
16628
+ ...signatureTypeRefs(t).map((ref) => ({ ref, by: `Signature type "${t.id}"` }))
16629
+ ];
16630
+ for (const { ref, by } of named2) {
16631
+ if (ctx.isTypeResolved(ref, typeGenerics)) continue;
16632
+ const hint = ctx.importHint(ref);
16633
+ ctx.addIssue(
16634
+ "error",
16635
+ "UNDEFINED_TYPE_REFERENCE",
16636
+ `${by} references undefined type "${ref}" in signature.${hint ? ` A declared dependency exports it without this project importing it \u2014 add \`${hint}\`.` : ""}`,
16637
+ t.id,
16638
+ isDraftCtx
16639
+ );
16640
+ }
16641
+ }
16642
+ }
16643
+ };
16644
+
16645
+ // src/core/rules/integrity/signature-sources.ts
16646
+ function draftContext(ctx, interfaceId, component) {
16647
+ const intf = ctx.interfaceMap.get(interfaceId);
16648
+ return ctx.isComponentDraft(component) || intf?.status === "draft" || intf?.status === "design";
16649
+ }
16650
+ function targetComponent(target) {
16651
+ return target.slice(0, target.lastIndexOf("."));
16652
+ }
16653
+ var signatureSourcesRule = {
16654
+ name: "signature-sources",
16655
+ judges: "design",
16656
+ 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.",
16657
+ codes: [
16658
+ { code: "SIGNATURE_SOURCE_UNRESOLVED", defaultSeverity: "error", summary: "A method's signatureFrom names no contract method and no signature type" },
16659
+ { code: "SIGNATURE_SOURCE_AMBIGUOUS", defaultSeverity: "error", summary: "A method's signatureFrom resolves both as a contract method and as a signature type; qualify it" },
16660
+ { code: "SIGNATURE_SOURCE_CHAINED", defaultSeverity: "error", summary: "A method's signature source takes its own signature from a source; sources do not chain" },
16661
+ { 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" },
16662
+ { 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" }
16663
+ ],
16664
+ check(ctx) {
16665
+ for (const fact of ctx.signatureFacts?.sources ?? []) {
16666
+ if (!ctx.isSpecInScope(fact.interfaceId)) continue;
16667
+ const isDraft = draftContext(ctx, fact.interfaceId, fact.component);
16668
+ const where = `Method "${fact.method}" on interface "${fact.interfaceId}"`;
16669
+ const at = { at: fact.method };
16670
+ switch (fact.outcome) {
16671
+ case "unresolved":
16672
+ ctx.addIssue(
16673
+ "error",
16674
+ "SIGNATURE_SOURCE_UNRESOLVED",
16675
+ `${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.`,
16676
+ fact.interfaceId,
16677
+ isDraft,
16678
+ void 0,
16679
+ at
16680
+ );
16681
+ continue;
16682
+ case "ambiguous":
16683
+ ctx.addIssue(
16684
+ "error",
16685
+ "SIGNATURE_SOURCE_AMBIGUOUS",
16686
+ `${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.`,
16687
+ fact.interfaceId,
16688
+ isDraft,
16689
+ void 0,
16690
+ at
16691
+ );
16692
+ continue;
16693
+ case "chained": {
16694
+ const own = fact.detail ?? "";
16695
+ const direct = ctx.types.some((t) => t.kind === "signature" && (t.id === own || typeMatchesRef(t, own)));
16696
+ ctx.addIssue(
16697
+ "error",
16698
+ "SIGNATURE_SOURCE_CHAINED",
16699
+ `${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."}`,
16700
+ fact.interfaceId,
16701
+ isDraft,
16702
+ void 0,
16703
+ at
16704
+ );
16705
+ continue;
16706
+ }
16707
+ case "restated":
16708
+ if (fact.differs) {
16709
+ ctx.addIssue(
16710
+ "error",
16711
+ "SIGNATURE_SOURCE_RESTATED",
16712
+ `${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.`,
16713
+ fact.interfaceId,
16714
+ isDraft,
16715
+ void 0,
16716
+ at
16717
+ );
16718
+ }
16719
+ continue;
16720
+ default:
16721
+ break;
16722
+ }
16723
+ if (fact.form !== "method" || !fact.target) continue;
16724
+ const source = targetComponent(fact.target);
16725
+ const owner = ctx.componentMap.get(fact.component);
16726
+ const reached = /* @__PURE__ */ new Set([...owner?.dependsOn ?? [], ...owner?.owns ?? []]);
16727
+ if (reached.has(source)) continue;
16728
+ ctx.addIssue(
16729
+ "error",
16730
+ "SIGNATURE_SOURCE_OFF_EDGE",
16731
+ `${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.`,
16732
+ fact.interfaceId,
16733
+ isDraft,
16734
+ void 0,
16735
+ at
16736
+ );
16737
+ }
16738
+ }
16739
+ };
16740
+
16741
+ // src/core/rules/integrity/signature-types.ts
16742
+ var signatureTypesRule = {
16743
+ name: "signature-types",
16744
+ judges: "design",
16745
+ 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.",
16746
+ codes: [
16747
+ { 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" }
16748
+ ],
16749
+ check(ctx) {
16750
+ for (const t of ctx.types) {
16751
+ if (!ctx.isSpecInScope(t.id)) continue;
16752
+ const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
16753
+ const isDraft = !!sub && (sub.status === "draft" || sub.status === "design");
16754
+ if (t.kind === "signature") {
16755
+ const members = [
16756
+ ...t.fields.length > 0 ? ["fields"] : [],
16757
+ ...t.methods.length > 0 ? ["methods"] : [],
16758
+ ...(t.invariants?.length ?? 0) > 0 ? ["invariants"] : [],
16759
+ ...t.componentClass !== void 0 ? ["componentClass"] : [],
16760
+ ...t.database !== void 0 ? ["database"] : [],
16761
+ ...t.table !== void 0 ? ["table"] : [],
16762
+ ...t.linkedEntity !== void 0 ? ["linkedEntity"] : []
16763
+ ];
16764
+ const missingReturns = t.returns === void 0;
16765
+ if (members.length === 0 && !missingReturns) continue;
16766
+ const problems = [
16767
+ ...members.length > 0 ? [`carries ${members.join(", ")}`] : [],
16768
+ ...missingReturns ? ["states no returns"] : []
16769
+ ];
16770
+ ctx.addIssue(
16771
+ "error",
16772
+ "SIGNATURE_TYPE_MEMBERS",
16773
+ `Signature type "${t.id}" ${problems.join(" and ")}. A signature is a named function type: params and one returns, nothing else.`,
16774
+ t.id,
16775
+ isDraft
16776
+ );
16777
+ continue;
16778
+ }
16779
+ const stated = [...t.params !== void 0 ? ["params"] : [], ...t.returns !== void 0 ? ["returns"] : []];
16780
+ if (stated.length === 0) continue;
16781
+ ctx.addIssue(
16782
+ "error",
16783
+ "SIGNATURE_TYPE_MEMBERS",
16784
+ `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.`,
16785
+ t.id,
16786
+ isDraft
16787
+ );
16788
+ }
16789
+ }
16790
+ };
16791
+
16792
+ // src/core/rules/integrity/signature-text.ts
16793
+ function draftContext2(ctx, specId, kind) {
16794
+ if (kind === "interface") {
16795
+ const intf = ctx.interfaceMap.get(specId);
16796
+ return !!intf && (ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design");
16797
+ }
16798
+ const type = ctx.types.find((t) => t.id === specId);
16799
+ const sub = type?.subsystem ? ctx.subsystems.find((s) => s.id === type.subsystem) : void 0;
16800
+ return !!sub && (sub.status === "draft" || sub.status === "design");
16801
+ }
16802
+ var signatureTextRule = {
16803
+ name: "signature-text",
16804
+ judges: "design",
16805
+ 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.",
16806
+ codes: [
16807
+ { code: "SIGNATURE_TEXT_STALE", defaultSeverity: "warning", summary: "A stored signature text differs from the text its params derive; doctor --fix regenerates it" }
16808
+ ],
16809
+ check(ctx) {
16810
+ for (const stale of ctx.signatureFacts?.staleTexts ?? []) {
16811
+ if (!ctx.isSpecInScope(stale.specId)) continue;
16812
+ ctx.addIssue(
16813
+ "warning",
16814
+ "SIGNATURE_TEXT_STALE",
16815
+ `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).`,
16816
+ stale.specId,
16817
+ draftContext2(ctx, stale.specId, stale.kind),
16818
+ void 0,
16819
+ { at: stale.method }
16820
+ );
16821
+ }
16464
16822
  }
16465
16823
  };
16466
16824
 
@@ -17892,11 +18250,11 @@ var detailSufficiencyRule = {
17892
18250
  const contract = ctx.interfaceMap.get(impl.contract);
17893
18251
  if (!contract) continue;
17894
18252
  const component = ctx.componentMap.get(contract.component);
17895
- const draftContext = ctx.isImplementationDraft(impl);
18253
+ const draftContext3 = ctx.isImplementationDraft(impl);
17896
18254
  for (const method of impl.methods) {
17897
18255
  const detail = effectiveDetail(method, impl, component);
17898
18256
  if (contract.methods.some((m) => m.name === method.name) && method.narrative.length === 0 && detail.level !== "full") {
17899
- dialedDown.push({ implementation: impl, method, component, detail, draftContext });
18257
+ dialedDown.push({ implementation: impl, method, component, detail, draftContext: draftContext3 });
17900
18258
  }
17901
18259
  }
17902
18260
  }
@@ -19863,7 +20221,7 @@ var unusedTypesRule = {
19863
20221
  // Stage 8: its verdict needs the whole system's specs, so a part judged alone skips it.
19864
20222
  needsWholeTree: true,
19865
20223
  judges: "design",
19866
- 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.",
20224
+ 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.",
19867
20225
  codes: [
19868
20226
  { code: "UNUSED_TYPE", defaultSeverity: "warning", summary: "Type never referenced by fields, signatures or type methods" }
19869
20227
  ],
@@ -19885,6 +20243,7 @@ var unusedTypesRule = {
19885
20243
  markTypeReferenced(ref);
19886
20244
  }
19887
20245
  }
20246
+ for (const ref of signatureTypeRefs(t)) markTypeReferenced(ref, t.id);
19888
20247
  }
19889
20248
  for (const intf of ctx.interfaces) {
19890
20249
  for (const m of intf.methods) {
@@ -19894,6 +20253,13 @@ var unusedTypesRule = {
19894
20253
  }
19895
20254
  }
19896
20255
  }
20256
+ for (const fact of ctx.signatureFacts?.sources ?? []) {
20257
+ const named2 = fact.form === "signature" && fact.target ? fact.target : fact.outcome === "ambiguous" ? fact.candidates?.[1] : void 0;
20258
+ if (!named2) continue;
20259
+ for (const spec of ctx.types) {
20260
+ if (spec.id === named2 || typeMatchesRef(spec, named2)) referencedTypes.add(spec.id);
20261
+ }
20262
+ }
19897
20263
  for (const t of ctx.types) {
19898
20264
  for (const m of t.methods) {
19899
20265
  const refs = methodTypeRefs(m);
@@ -21253,7 +21619,7 @@ function absence(optional) {
21253
21619
  var typeShapeRule = {
21254
21620
  name: "type-shape",
21255
21621
  judges: "code",
21256
- 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.",
21622
+ 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.",
21257
21623
  codes: [
21258
21624
  {
21259
21625
  code: "UNREALIZED_TYPE_FIELD",
@@ -21430,7 +21796,7 @@ var paramConformanceRule = {
21430
21796
  const stated = normalize3(declared.type);
21431
21797
  return stated === realized.type || codeNameOf.get(stated) === realized.type;
21432
21798
  };
21433
- for (const { implementation, method, sourceFile, draftContext } of ctx.implementationMethods()) {
21799
+ for (const { implementation, method, sourceFile, draftContext: draftContext3 } of ctx.implementationMethods()) {
21434
21800
  const contract = ctx.interfaceMap.get(implementation.contract);
21435
21801
  const declared = contract?.methods.find((m) => m.name === method.name)?.params ?? [];
21436
21802
  if (declared.length === 0 || !sourceFile) continue;
@@ -21453,7 +21819,7 @@ var paramConformanceRule = {
21453
21819
  "UNREALIZED_PARAM",
21454
21820
  `Method "${method.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.`,
21455
21821
  implementation.id,
21456
- draftContext,
21822
+ draftContext3,
21457
21823
  void 0,
21458
21824
  { at: method.name, covers: unrealized.map((found) => found.unit) }
21459
21825
  );
@@ -21465,7 +21831,7 @@ var paramConformanceRule = {
21465
21831
  "UNDECLARED_PARAM",
21466
21832
  `${opening} realizing method "${method.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.`,
21467
21833
  implementation.id,
21468
- draftContext,
21834
+ draftContext3,
21469
21835
  void 0,
21470
21836
  { at: method.name, covers: undeclared.map((found) => found.unit) }
21471
21837
  );
@@ -21477,7 +21843,7 @@ var paramConformanceRule = {
21477
21843
  "PARAM_NAME_MISMATCH",
21478
21844
  `Method "${method.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.`,
21479
21845
  implementation.id,
21480
- draftContext,
21846
+ draftContext3,
21481
21847
  void 0,
21482
21848
  { at: method.name, covers: renamed.map((found) => found.unit) }
21483
21849
  );
@@ -21489,7 +21855,7 @@ var paramConformanceRule = {
21489
21855
  "PARAM_OPTIONALITY",
21490
21856
  `Method "${method.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.`,
21491
21857
  implementation.id,
21492
- draftContext,
21858
+ draftContext3,
21493
21859
  void 0,
21494
21860
  { at: method.name, covers: disagreed.map((found) => found.unit) }
21495
21861
  );
@@ -21566,7 +21932,7 @@ var routeCoverageRule = {
21566
21932
  const routes = code.factsAt(file).functionRoutes;
21567
21933
  return routes && Object.prototype.hasOwnProperty.call(routes, via) ? routes[via] : [];
21568
21934
  });
21569
- const draftContext = ctx.isComponentDraft(listener.id) || ctx.isComponentDraft(portal.id);
21935
+ const draftContext3 = ctx.isComponentDraft(listener.id) || ctx.isComponentDraft(portal.id);
21570
21936
  const where = holders.map((file) => `"${file}"`).join(", ");
21571
21937
  if (read2.length === 0) {
21572
21938
  ctx.addIssue(
@@ -21574,7 +21940,7 @@ var routeCoverageRule = {
21574
21940
  "UNREADABLE_ROUTER",
21575
21941
  `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.`,
21576
21942
  anchor,
21577
- draftContext,
21943
+ draftContext3,
21578
21944
  void 0,
21579
21945
  { at: via }
21580
21946
  );
@@ -21599,7 +21965,7 @@ var routeCoverageRule = {
21599
21965
  "UNDECLARED_ROUTE",
21600
21966
  `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.`,
21601
21967
  anchor,
21602
- draftContext,
21968
+ draftContext3,
21603
21969
  void 0,
21604
21970
  { at: via, covers: undeclared }
21605
21971
  );
@@ -21611,7 +21977,7 @@ var routeCoverageRule = {
21611
21977
  "UNROUTED_ENDPOINT",
21612
21978
  `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.`,
21613
21979
  anchor,
21614
- draftContext,
21980
+ draftContext3,
21615
21981
  void 0,
21616
21982
  { at: via, covers: unrouted }
21617
21983
  );
@@ -21714,7 +22080,7 @@ var exportConformanceRule = {
21714
22080
  }
21715
22081
  }
21716
22082
  }
21717
- for (const { implementation, method, sourceFile, draftContext } of ctx.implementationMethods()) {
22083
+ for (const { implementation, method, sourceFile, draftContext: draftContext3 } of ctx.implementationMethods()) {
21718
22084
  const handle = method.exportedVia;
21719
22085
  if (!handle || !sourceFile) continue;
21720
22086
  const file = pathKey(sourceFile);
@@ -21729,7 +22095,7 @@ var exportConformanceRule = {
21729
22095
  "UNREALIZED_EXPORT_HANDLE",
21730
22096
  `Method "${method.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.`,
21731
22097
  implementation.id,
21732
- draftContext
22098
+ draftContext3
21733
22099
  );
21734
22100
  }
21735
22101
  for (const listener of ctx.components) {
@@ -23019,6 +23385,68 @@ var namingDisciplineRule = {
23019
23385
  }
23020
23386
  };
23021
23387
 
23388
+ // src/core/rules/heuristic/signature-source-suggestions.ts
23389
+ function sameParams(a, b) {
23390
+ if (!b || a.length !== b.length) return false;
23391
+ return a.every((p, i) => p.name === b[i].name && p.type === b[i].type && !!p.optional === !!b[i].optional);
23392
+ }
23393
+ function localName(id) {
23394
+ return id.split("::").pop();
23395
+ }
23396
+ var signatureSourceSuggestionsRule = {
23397
+ name: "signature-source-suggestions",
23398
+ judges: "design",
23399
+ 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.",
23400
+ codes: [
23401
+ { 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" }
23402
+ ],
23403
+ check(ctx) {
23404
+ for (const impl of ctx.implementations) {
23405
+ const contract = ctx.interfaceMap.get(impl.contract);
23406
+ if (!contract || !ctx.isSpecInScope(contract.id)) continue;
23407
+ const repository = ctx.componentMap.get(contract.component);
23408
+ if (!repository || repository.componentType !== "Repository") continue;
23409
+ const owned = new Set(repository.owns);
23410
+ const restating = [];
23411
+ for (const realized of impl.methods) {
23412
+ const steps = realized.narrative ?? [];
23413
+ let member;
23414
+ let memberMethod;
23415
+ if (steps.length === 1 && steps[0].type === "call") {
23416
+ member = steps[0].targetComponent;
23417
+ memberMethod = steps[0].targetMethod;
23418
+ } else if (steps.length === 0 && realized.calls?.length === 1) {
23419
+ const entry = realized.calls[0];
23420
+ const dot = entry.lastIndexOf(".");
23421
+ if (dot > 0) {
23422
+ member = entry.slice(0, dot);
23423
+ memberMethod = entry.slice(dot + 1);
23424
+ }
23425
+ }
23426
+ if (!member || !memberMethod) continue;
23427
+ const memberKey = [...owned].find((o) => o === member || localName(o) === localName(member));
23428
+ if (!memberKey) continue;
23429
+ const own = contract.methods.find((m) => m.name === realized.name);
23430
+ if (!own || own.signatureFrom !== void 0 || !own.params) continue;
23431
+ const target = (ctx.interfacesByComponent.get(memberKey) ?? []).flatMap((i) => i.methods).find((m) => m.name === memberMethod);
23432
+ if (!target || target.signatureFrom !== void 0) continue;
23433
+ if (!sameParams(own.params, target.params) || own.returns !== target.returns) continue;
23434
+ restating.push(`${own.name} \u2190 ${localName(memberKey)}.${memberMethod}`);
23435
+ }
23436
+ if (restating.length === 0) continue;
23437
+ ctx.addIssue(
23438
+ "notice",
23439
+ "SIGNATURE_SOURCE_AVAILABLE",
23440
+ `${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.`,
23441
+ contract.id,
23442
+ ctx.isImplementationDraft(impl),
23443
+ void 0,
23444
+ { at: contract.id, covers: restating }
23445
+ );
23446
+ }
23447
+ }
23448
+ };
23449
+
23022
23450
  // src/core/rules/repository.ts
23023
23451
  var SDD_RULES = [
23024
23452
  hierarchyRule,
@@ -23038,6 +23466,9 @@ var SDD_RULES = [
23038
23466
  typeDeclarationsRule,
23039
23467
  fieldTypeReferencesRule,
23040
23468
  signatureTypeReferencesRule,
23469
+ signatureSourcesRule,
23470
+ signatureTypesRule,
23471
+ signatureTextRule,
23041
23472
  // Contracts and the targets narratives name, in four questions with one
23042
23473
  // owner each: does the implementation mirror its contract, does a target
23043
23474
  // inside this tree resolve, does a target that leaves it pin to exactly one
@@ -23223,6 +23654,7 @@ var SDD_RULES = [
23223
23654
  narrativeComplexityRule,
23224
23655
  namingDisciplineRule,
23225
23656
  methodCohesionRule,
23657
+ signatureSourceSuggestionsRule,
23226
23658
  // Pack resolution and reproducibility run late: they are about project
23227
23659
  // CONFIGURATION (does the declared pack set resolve, and can it be reproduced
23228
23660
  // elsewhere?) rather than spec content.
@@ -23590,24 +24022,6 @@ function walkExact(ts, sourceText, fileName) {
23590
24022
  if (!argument || !ts.isTypeQueryNode(argument) || !ts.isIdentifier(argument.exprName)) return void 0;
23591
24023
  return argument.exprName.text;
23592
24024
  };
23593
- const baseObjectLiteral = (expression) => {
23594
- let node = expression;
23595
- const walked = /* @__PURE__ */ new Set();
23596
- while (node && !walked.has(node)) {
23597
- walked.add(node);
23598
- if (ts.isCallExpression(node)) {
23599
- const callee = node.expression;
23600
- if (ts.isPropertyAccessExpression(callee) && callee.name.text === "object") {
23601
- const literal = node.arguments.find((argument) => ts.isObjectLiteralExpression(argument));
23602
- if (literal) return literal;
23603
- }
23604
- node = callee;
23605
- } else if (ts.isPropertyAccessExpression(node)) {
23606
- node = node.expression;
23607
- } else break;
23608
- }
23609
- return void 0;
23610
- };
23611
24025
  const chainedCombinators = (expression) => {
23612
24026
  const applied = /* @__PURE__ */ new Set();
23613
24027
  let node = expression;
@@ -23877,13 +24291,48 @@ function walkExact(ts, sourceText, fileName) {
23877
24291
  ts.forEachChild(node, visit);
23878
24292
  };
23879
24293
  visit(sf);
24294
+ const composedMembers = (expression, seen) => {
24295
+ const extensions = [];
24296
+ const compose2 = (members) => {
24297
+ const byName = new Map(members.map((member) => [member.name, member]));
24298
+ for (const extension of [...extensions].reverse()) {
24299
+ for (const member of schemaMembers(extension)) byName.set(member.name, member);
24300
+ }
24301
+ return [...byName.values()];
24302
+ };
24303
+ let node = expression;
24304
+ const walked = /* @__PURE__ */ new Set();
24305
+ while (node && !walked.has(node)) {
24306
+ walked.add(node);
24307
+ if (ts.isCallExpression(node)) {
24308
+ const callee = node.expression;
24309
+ if (ts.isPropertyAccessExpression(callee) && callee.name.text === "object") {
24310
+ const literal = node.arguments.find((argument) => ts.isObjectLiteralExpression(argument));
24311
+ if (literal) return compose2(schemaMembers(literal));
24312
+ } else if (ts.isPropertyAccessExpression(callee) && callee.name.text === "extend") {
24313
+ const extension = node.arguments.find((argument) => ts.isObjectLiteralExpression(argument));
24314
+ if (!extension) return void 0;
24315
+ extensions.push(extension);
24316
+ }
24317
+ node = callee;
24318
+ } else if (ts.isPropertyAccessExpression(node)) {
24319
+ node = node.expression;
24320
+ } else if (ts.isIdentifier(node)) {
24321
+ const base = schemaConstants.get(node.text);
24322
+ if (!base || seen.has(node.text)) return void 0;
24323
+ const members = composedMembers(base, /* @__PURE__ */ new Set([...seen, node.text]));
24324
+ return members ? compose2(members) : void 0;
24325
+ } else break;
24326
+ }
24327
+ return void 0;
24328
+ };
23880
24329
  for (const alias of derivedAliases) {
23881
24330
  if (typeShapes.has(alias.name)) continue;
23882
24331
  const initializer = schemaConstants.get(alias.constant);
23883
24332
  if (!initializer) continue;
23884
- const literal = baseObjectLiteral(initializer);
23885
- if (!literal) continue;
23886
- typeShapes.set(alias.name, { origin: "derived", fields: schemaMembers(literal), methods: [] });
24333
+ const fields = composedMembers(initializer, /* @__PURE__ */ new Set([alias.constant]));
24334
+ if (!fields) continue;
24335
+ typeShapes.set(alias.name, { origin: "derived", fields, methods: [] });
23887
24336
  }
23888
24337
  const reexportOnly = sf.statements.length > 0 && sf.statements.every((st) => ts.isExportDeclaration(st) && !!st.moduleSpecifier);
23889
24338
  return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, importBindings, typeOnlyBindings, fieldTypes, localTypes, typeShapes, functionParams, functionRoutes, mutableBindings, reexportOnly };
@@ -24904,6 +25353,7 @@ function buildRuleContext(opts) {
24904
25353
  ...opts.exportTables ? { exportTables: opts.exportTables } : {},
24905
25354
  ...opts.projectFamily ? { projectFamily: opts.projectFamily } : {},
24906
25355
  ...opts.exportUsages ? { exportUsages: opts.exportUsages } : {},
25356
+ ...opts.signatureFacts ? { signatureFacts: opts.signatureFacts } : {},
24907
25357
  pinnedExternals,
24908
25358
  codeModel: opts.codeModel ?? emptyCodeModel(),
24909
25359
  roundTripIssues: opts.roundTripIssues,
@@ -25345,6 +25795,7 @@ function runOwnersGate(rulesOrOptions, projectType = "backend") {
25345
25795
  const memberTables = family.nodes.filter((n) => n.namespace !== "").map((n) => resolveProjectExports(n.namespace));
25346
25796
  const producers = new Set(family.references.filter((r) => r.consumer === "").map((r) => r.producer));
25347
25797
  const exportUsages = [...producers].map((producer) => exportUsage("", producer));
25798
+ const signatures = signatureFacts();
25348
25799
  const ctx = buildRuleContext({
25349
25800
  system,
25350
25801
  subsystems,
@@ -25366,6 +25817,7 @@ function runOwnersGate(rulesOrOptions, projectType = "backend") {
25366
25817
  ],
25367
25818
  projectFamily: family,
25368
25819
  exportUsages,
25820
+ signatureFacts: signatures,
25369
25821
  pinnedExternals,
25370
25822
  // By-name selections only: a legacy path ref pins nothing to check. A dry
25371
25823
  // run supplies its candidate's; otherwise the stored ones.
@@ -25485,12 +25937,26 @@ function judgePartAlone(config, scopeSubsystem) {
25485
25937
  if (ownIds.has(key) || context.ids.has(key)) continue;
25486
25938
  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));
25487
25939
  }
25940
+ const ownSignatures = signatureFacts();
25941
+ const resolved = resolveTree(
25942
+ [...own.interfaces, ...context.interfaces],
25943
+ [...own.components, ...context.components],
25944
+ [...own.types, ...context.types]
25945
+ );
25946
+ const ownInterfaceIds = new Set(own.interfaces.map((i) => i.id));
25947
+ const partSignatures = {
25948
+ sources: [
25949
+ ...resolved.facts.sources.filter((f) => ownInterfaceIds.has(f.interfaceId) && f.outcome !== "restated"),
25950
+ ...ownSignatures.sources.filter((f) => f.outcome === "restated")
25951
+ ],
25952
+ staleTexts: ownSignatures.staleTexts
25953
+ };
25488
25954
  const all = {
25489
25955
  subsystems: [...own.subsystems, ...context.subsystems],
25490
25956
  components: [...own.components, ...context.components],
25491
- interfaces: [...own.interfaces, ...context.interfaces],
25957
+ interfaces: resolved.interfaces,
25492
25958
  implementations: [...own.implementations, ...context.implementations],
25493
- types: [...own.types, ...context.types]
25959
+ types: resolved.types
25494
25960
  };
25495
25961
  const system = context.system ?? SystemSpecSchema.parse({ createdAt: EXCERPT_EPOCH, updatedAt: EXCERPT_EPOCH, name: parentId, vision: `The part's parent, "${parentId}" (no L0 pinned).` });
25496
25962
  const codeModel = buildCodeModel(own.implementations, own.types, root, [], []);
@@ -25510,6 +25976,7 @@ function judgePartAlone(config, scopeSubsystem) {
25510
25976
  exportTables: [],
25511
25977
  projectFamily: graph(),
25512
25978
  exportUsages: [],
25979
+ signatureFacts: partSignatures,
25513
25980
  pinnedExternals: [],
25514
25981
  packSelections: packs.filter((p) => typeof p !== "string"),
25515
25982
  packRequirements: [],
@@ -26364,7 +26831,7 @@ function projectFamilyGraph() {
26364
26831
 
26365
26832
  // src/core/exports.ts
26366
26833
  var CONSUMABLE = /* @__PURE__ */ new Set(["Portal", "Observer"]);
26367
- function localName(id) {
26834
+ function localName2(id) {
26368
26835
  return id.split("::").pop() ?? id;
26369
26836
  }
26370
26837
  function ownedBy(subsystemId, declaringSubsystem) {
@@ -26411,7 +26878,7 @@ function findInSource(world, source, table, request) {
26411
26878
  if (request.typeDef) {
26412
26879
  const type = lookupType(world, request.typeDef, source.id);
26413
26880
  if (!type) return {};
26414
- const item2 = { publicName: localName(type.id), kind: "type", source: type.subsystem ?? source.id, typeDef: type.id, via: [] };
26881
+ const item2 = { publicName: localName2(type.id), kind: "type", source: type.subsystem ?? source.id, typeDef: type.id, via: [] };
26415
26882
  return { item: item2, found: table.entries.find((e) => e.kind === "type" && e.typeDef === type.id && e.source === item2.source) };
26416
26883
  }
26417
26884
  const intf = lookup(world.interfaces, request.interface, source.id);
@@ -26419,7 +26886,7 @@ function findInSource(world, source, table, request) {
26419
26886
  if (!comp) return {};
26420
26887
  const narrowed2 = intf?.id ?? request.interface;
26421
26888
  const item = {
26422
- publicName: localName(narrowed2 ?? comp.id),
26889
+ publicName: localName2(narrowed2 ?? comp.id),
26423
26890
  kind: "component",
26424
26891
  source: comp.subsystem,
26425
26892
  component: comp.id,
@@ -26459,7 +26926,7 @@ function bindOwn(world, sub, pi, problems) {
26459
26926
  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"}` });
26460
26927
  return void 0;
26461
26928
  }
26462
- const name2 = pi.as ?? localName(type.id);
26929
+ const name2 = pi.as ?? localName2(type.id);
26463
26930
  checkPublicName(name2, sub.id, problems);
26464
26931
  return { publicName: name2, kind: "type", source: type.subsystem ?? sub.id, typeDef: type.id, via: [] };
26465
26932
  }
@@ -26467,7 +26934,7 @@ function bindOwn(world, sub, pi, problems) {
26467
26934
  if (!comp || !ownedBy(sub.id, comp.subsystem)) return void 0;
26468
26935
  const intf = lookup(world.interfaces, pi.interface, sub.id);
26469
26936
  const narrowed2 = intf?.id ?? pi.interface;
26470
- const name = pi.as ?? localName(narrowed2 ?? comp.id);
26937
+ const name = pi.as ?? localName2(narrowed2 ?? comp.id);
26471
26938
  checkPublicName(name, sub.id, problems);
26472
26939
  return {
26473
26940
  publicName: name,
@@ -26502,7 +26969,7 @@ function subsystemCandidates(world, sub, tables, group, problems) {
26502
26969
  source,
26503
26970
  sourceTable,
26504
26971
  { component: pi.component, interface: pi.interface, typeDef: pi.typeDef },
26505
- (item) => pi.as ?? localName(pi.interface ?? item.typeDef ?? item.component ?? ""),
26972
+ (item) => pi.as ?? localName2(pi.interface ?? item.typeDef ?? item.component ?? ""),
26506
26973
  group.has(source.id),
26507
26974
  problems
26508
26975
  );
@@ -26678,16 +27145,16 @@ function classifySource(world, sourceId, label7, sources) {
26678
27145
  function bindOwnType(world, owner, e, problems) {
26679
27146
  const type = (world.types.get(e.typeDef) ?? []).find((t) => !t.subsystem);
26680
27147
  if (!type) {
26681
- 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` });
27148
+ 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` });
26682
27149
  return void 0;
26683
27150
  }
26684
- const name = e.as ?? e.id ?? localName(type.id);
27151
+ const name = e.as ?? e.id ?? localName2(type.id);
26685
27152
  checkPublicName(name, owner, problems);
26686
27153
  return { publicName: name, kind: "type", source: owner, typeDef: type.id, via: [] };
26687
27154
  }
26688
27155
  function bindFromProject(owner, e, source, table, index, problems) {
26689
27156
  const named2 = e.component !== void 0 || e.typeDef !== void 0 || e.interface !== void 0;
26690
- const requested = named2 ? localName(e.interface ?? e.component ?? e.typeDef) : void 0;
27157
+ const requested = named2 ? localName2(e.interface ?? e.component ?? e.typeDef) : void 0;
26691
27158
  const picked = named2 ? table.entries.filter((x) => x.publicName === requested) : table.entries;
26692
27159
  if (named2 && picked.length === 0) {
26693
27160
  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` });
@@ -26724,7 +27191,7 @@ function bindFromSubsystem(world, owner, e, subsystem, subsystemTables, index, p
26724
27191
  // The default public name is the item's own LOCAL id: a member's L0 is
26725
27192
  // read with its ids keyed under its project, and its entry names the
26726
27193
  // item as the member wrote it.
26727
- () => e.as ?? e.id ?? localName(e.interface ?? e.component ?? e.typeDef),
27194
+ () => e.as ?? e.id ?? localName2(e.interface ?? e.component ?? e.typeDef),
26728
27195
  false,
26729
27196
  problems
26730
27197
  );
@@ -26865,6 +27332,124 @@ function pinnedUsageOf(consumer, alias) {
26865
27332
  };
26866
27333
  }
26867
27334
 
27335
+ // src/core/signature-sources.ts
27336
+ function emptySignatureFacts() {
27337
+ return { sources: [], staleTexts: [] };
27338
+ }
27339
+ function namespaceOf(key) {
27340
+ const at = key.lastIndexOf("::");
27341
+ return at === -1 ? "" : key.slice(0, at);
27342
+ }
27343
+ function unresolvedMethod(method) {
27344
+ const { params: _params, ...rest } = method;
27345
+ return { ...rest, returns: "unknown", signature: `${method.name}(...): unknown` };
27346
+ }
27347
+ function sameParams2(a, b) {
27348
+ const left = a ?? [];
27349
+ const right = b ?? [];
27350
+ return left.length === right.length && left.every((p, i) => p.name === right[i].name && p.type === right[i].type && !!p.optional === !!right[i].optional);
27351
+ }
27352
+ function tablesOf(interfaces, components, types, references) {
27353
+ const methodsOf = /* @__PURE__ */ new Map();
27354
+ for (const intf of interfaces) methodsOf.set(intf.component, [...methodsOf.get(intf.component) ?? [], ...intf.methods]);
27355
+ const boundTypes = /* @__PURE__ */ new Map();
27356
+ for (const ref of references) {
27357
+ if (ref.position !== "type" || ref.binding === "outside" || ref.binding === "unresolved") continue;
27358
+ boundTypes.set(`${ref.specId}|${ref.authored}`, ref.resolved);
27359
+ }
27360
+ return { methodsOf, componentKeys: new Set(components.map((c) => c.id)), types, boundTypes };
27361
+ }
27362
+ function methodReading(tables, intf, value) {
27363
+ const dot = value.lastIndexOf(".");
27364
+ if (dot <= 0 || dot === value.length - 1) return null;
27365
+ const head2 = value.slice(0, dot);
27366
+ const tail = value.slice(dot + 1);
27367
+ const ns = namespaceOf(intf.id);
27368
+ const keys = [head2, ...ns && !head2.startsWith(`${ns}::`) ? [`${ns}::${head2}`] : []];
27369
+ for (const key of keys) {
27370
+ if (!tables.componentKeys.has(key)) continue;
27371
+ const method = (tables.methodsOf.get(key) ?? []).find((m) => m.name === tail);
27372
+ if (method) return { target: `${key}.${tail}`, method };
27373
+ }
27374
+ return null;
27375
+ }
27376
+ function typeReading(tables, intf, value) {
27377
+ const bound2 = value.includes("::") ? tables.boundTypes.get(`${intf.id}|${value}`) : void 0;
27378
+ const named2 = bound2 !== void 0 ? tables.types.filter((t) => t.id === bound2) : tables.types.filter((t) => t.id === value || typeMatchesRef(t, value));
27379
+ const ns = namespaceOf(intf.id);
27380
+ const local = (t) => namespaceOf(t.id) === ns ? 0 : 1;
27381
+ const ranked = [...named2].sort((a, b) => local(a) - local(b));
27382
+ return { signature: ranked.find((t) => t.kind === "signature"), other: ranked.find((t) => t.kind !== "signature") };
27383
+ }
27384
+ function typeTarget(type) {
27385
+ return type.subsystem && !type.id.includes("::") ? `${type.subsystem}::${type.id}` : type.id;
27386
+ }
27387
+ function restatedFact(base, stored, params, returns) {
27388
+ const statesParams = stored.params !== void 0;
27389
+ const statesReturns = stored.returns !== void 0;
27390
+ if (!statesParams && !statesReturns) return null;
27391
+ const paramsDiffer = statesParams && !sameParams2(stored.params, params);
27392
+ const returnsDiffer = statesReturns && stored.returns !== returns;
27393
+ const differs = paramsDiffer || returnsDiffer;
27394
+ 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("; ");
27395
+ return { ...base, outcome: "restated", differs, detail };
27396
+ }
27397
+ function resolveSourced(tables, intf, stored, facts) {
27398
+ const source = stored.signatureFrom;
27399
+ const base = { interfaceId: intf.id, component: intf.component, method: stored.name, source };
27400
+ const hit = methodReading(tables, intf, source);
27401
+ const { signature, other } = typeReading(tables, intf, source);
27402
+ if (hit && signature) {
27403
+ facts.sources.push({ ...base, outcome: "ambiguous", candidates: [hit.target, typeTarget(signature)] });
27404
+ return unresolvedMethod(stored);
27405
+ }
27406
+ if (!hit && !signature) {
27407
+ facts.sources.push({ ...base, outcome: "unresolved", ...other ? { detail: `"${typeTarget(other)}" is a type of kind ${other.kind}, not a signature` } : {} });
27408
+ return unresolvedMethod(stored);
27409
+ }
27410
+ if (hit && hit.method.signatureFrom !== void 0) {
27411
+ facts.sources.push({ ...base, form: "method", target: hit.target, outcome: "chained", detail: hit.method.signatureFrom });
27412
+ return unresolvedMethod(stored);
27413
+ }
27414
+ const form = hit ? "method" : "signature";
27415
+ const target = hit ? hit.target : typeTarget(signature);
27416
+ const params = hit ? hit.method.params : signature.params;
27417
+ const returns = hit ? hit.method.returns : signature.returns ?? "unknown";
27418
+ const restated = restatedFact({ ...base, form, target }, stored, params, returns);
27419
+ if (restated) facts.sources.push(restated);
27420
+ facts.sources.push({ ...base, form, target, outcome: "resolved" });
27421
+ const resolved = { ...stored, returns };
27422
+ if (params) resolved.params = params.map((p) => ({ ...p }));
27423
+ else delete resolved.params;
27424
+ if (!params && hit) resolved.signature = hit.method.signature.replace(new RegExp(`^\\s*${hit.method.name}\\b`), stored.name);
27425
+ return resolved;
27426
+ }
27427
+ function withDerivedText(method, stored, specId, kind, facts) {
27428
+ const derived = deriveMethodSignature({ ...method, signature: stored ?? method.signature });
27429
+ if (derived === void 0) return method;
27430
+ if (stored !== void 0 && stored !== derived) facts.staleTexts.push({ specId, kind, method: method.name, stored, derived });
27431
+ return { ...method, signature: derived };
27432
+ }
27433
+ function resolveTree(interfaces, components, types, references = []) {
27434
+ const intfs = interfaces.map((i) => ({ ...i, methods: i.methods.map((m) => ({ ...m })) }));
27435
+ const typeCopies = types.map((t) => ({ ...t, methods: t.methods.map((m) => ({ ...m })) }));
27436
+ const tables = tablesOf(intfs, [...components], typeCopies, references);
27437
+ const facts = emptySignatureFacts();
27438
+ const sourced = intfs.map((intf) => ({
27439
+ ...intf,
27440
+ methods: intf.methods.map((m) => m.signatureFrom !== void 0 ? resolveSourced(tables, intf, m, facts) : m)
27441
+ }));
27442
+ const resolvedInterfaces = sourced.map((intf, i) => ({
27443
+ ...intf,
27444
+ methods: intf.methods.map((m, j) => withDerivedText(m, intfs[i].methods[j].signature, intf.id, "interface", facts))
27445
+ }));
27446
+ const resolvedTypes = typeCopies.map((t) => ({
27447
+ ...t,
27448
+ methods: t.methods.map((m) => withDerivedText(m, m.signature, t.id, "type", facts))
27449
+ }));
27450
+ return { interfaces: resolvedInterfaces, types: resolvedTypes, facts };
27451
+ }
27452
+
26868
27453
  // src/core/specs.ts
26869
27454
  var PartReadOnly = class extends WaironError {
26870
27455
  constructor(message) {
@@ -26931,7 +27516,8 @@ function emptyIndex() {
26931
27516
  implementation: {},
26932
27517
  type: {},
26933
27518
  group: {}
26934
- }
27519
+ },
27520
+ signatures: { sources: [], staleTexts: [] }
26935
27521
  };
26936
27522
  }
26937
27523
  function isWithin(dir, file) {
@@ -27164,6 +27750,17 @@ function inspectChainedRoots(rootDir = getProjectRoot()) {
27164
27750
  walk2(root, "", /* @__PURE__ */ new Set([chainDirKey(root)]), 0);
27165
27751
  return inspection;
27166
27752
  }
27753
+ function signatureSourceParts(value) {
27754
+ const dot = value.lastIndexOf(".");
27755
+ if (dot <= 0 || dot === value.length - 1) return null;
27756
+ return { head: value.slice(0, dot), tail: value.slice(dot + 1) };
27757
+ }
27758
+ function mapSignatureSource(value, map) {
27759
+ const parts = signatureSourceParts(value);
27760
+ if (!parts) return value;
27761
+ const head2 = map("signatureFrom", parts.head);
27762
+ return head2 === parts.head ? value : `${head2}.${parts.tail}`;
27763
+ }
27167
27764
  function mapSpecReferences(kind, spec, map) {
27168
27765
  switch (kind) {
27169
27766
  case "subsystem": {
@@ -27194,7 +27791,15 @@ function mapSpecReferences(kind, spec, map) {
27194
27791
  }
27195
27792
  case "interface": {
27196
27793
  const i = spec;
27197
- return { ...i, component: map("contract", i.component) };
27794
+ return {
27795
+ ...i,
27796
+ component: map("contract", i.component),
27797
+ // A method source's head is a component reference; a value with no
27798
+ // head is a raw type position the mapper is never asked about.
27799
+ ...i.methods?.some((m) => m.signatureFrom !== void 0) ? {
27800
+ methods: i.methods.map((m) => m.signatureFrom !== void 0 ? { ...m, signatureFrom: mapSignatureSource(m.signatureFrom, map) } : m)
27801
+ } : {}
27802
+ };
27198
27803
  }
27199
27804
  case "implementation": {
27200
27805
  const impl = spec;
@@ -27239,6 +27844,7 @@ function rawReferences(kind, spec) {
27239
27844
  for (const m of spec.methods) {
27240
27845
  qualifiedTypeNames(m.returns).forEach((t) => add2("type", t));
27241
27846
  m.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((t) => add2("type", t)));
27847
+ if (m.signatureFrom !== void 0 && !signatureSourceParts(m.signatureFrom)) add2("type", m.signatureFrom);
27242
27848
  }
27243
27849
  break;
27244
27850
  case "implementation":
@@ -27250,9 +27856,14 @@ function rawReferences(kind, spec) {
27250
27856
  }
27251
27857
  }
27252
27858
  break;
27253
- case "type":
27254
- for (const f of spec.fields) qualifiedTypeNames(f.type).forEach((t) => add2("type", t));
27859
+ case "type": {
27860
+ const t = spec;
27861
+ for (const f of t.fields) qualifiedTypeNames(f.type).forEach((n) => add2("type", n));
27862
+ t.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((n) => add2("type", n)));
27863
+ qualifiedTypeNames(t.returns).forEach((n) => add2("type", n));
27864
+ for (const m of t.methods ?? []) m.params?.forEach((p) => qualifiedTypeNames(p.type).forEach((n) => add2("type", n)));
27255
27865
  break;
27866
+ }
27256
27867
  default:
27257
27868
  break;
27258
27869
  }
@@ -27283,6 +27894,8 @@ function bareRawReferences(kind, spec) {
27283
27894
  case "type": {
27284
27895
  const t = spec;
27285
27896
  for (const f of t.fields) for (const ref of fieldTypeRefs(t, f.type)) add2("type", ref);
27897
+ for (const ref of signatureTypeRefs(t)) add2("type", ref);
27898
+ for (const m of t.methods ?? []) for (const p of m.params ?? []) for (const ref of fieldTypeRefs(t, p.type)) add2("type", ref);
27286
27899
  break;
27287
27900
  }
27288
27901
  case "implementation":
@@ -27349,6 +27962,9 @@ function respellStoredReferences(kind, doc, map) {
27349
27962
  typed(m, "signature");
27350
27963
  typed(m, "returns");
27351
27964
  for (const p of list(m.params)) typed(p, "type");
27965
+ if (typeof m.signatureFrom === "string") {
27966
+ m.signatureFrom = signatureSourceParts(m.signatureFrom) ? mapSignatureSource(m.signatureFrom, map) : mapTypeNames(m.signatureFrom, map);
27967
+ }
27352
27968
  }
27353
27969
  break;
27354
27970
  case "implementation":
@@ -27374,6 +27990,9 @@ function respellStoredReferences(kind, doc, map) {
27374
27990
  at(doc, "subsystem", "subsystem");
27375
27991
  at(doc, "group", "group");
27376
27992
  for (const f of list(doc.fields)) typed(f, "type");
27993
+ for (const p of list(doc.params)) typed(p, "type");
27994
+ typed(doc, "returns");
27995
+ for (const m of list(doc.methods)) for (const p of list(m.params)) typed(p, "type");
27377
27996
  break;
27378
27997
  default:
27379
27998
  break;
@@ -27393,7 +28012,9 @@ function mapRawReferences(kind, spec, map) {
27393
28012
  ...m,
27394
28013
  ...m.signature !== void 0 ? { signature: mapTypeNames(m.signature, map) } : {},
27395
28014
  ...m.returns !== void 0 ? { returns: mapTypeNames(m.returns, map) } : {},
27396
- ...m.params ? { params: m.params.map((p) => ({ ...p, type: mapTypeNames(p.type, map) })) } : {}
28015
+ ...m.params ? { params: m.params.map((p) => ({ ...p, type: mapTypeNames(p.type, map) })) } : {},
28016
+ // A type source is a raw type position; a method source's head is bound, never raw.
28017
+ ...m.signatureFrom !== void 0 && !signatureSourceParts(m.signatureFrom) ? { signatureFrom: mapTypeNames(m.signatureFrom, map) } : {}
27397
28018
  }))
27398
28019
  };
27399
28020
  }
@@ -27421,7 +28042,14 @@ function mapRawReferences(kind, spec, map) {
27421
28042
  }
27422
28043
  case "type": {
27423
28044
  const t = spec;
27424
- return { ...t, fields: t.fields.map((f) => ({ ...f, type: mapTypeNames(f.type, map) })) };
28045
+ const typedParams = (params) => params ? { params: params.map((p) => ({ ...p, type: mapTypeNames(p.type, map) })) } : {};
28046
+ return {
28047
+ ...t,
28048
+ fields: t.fields.map((f) => ({ ...f, type: mapTypeNames(f.type, map) })),
28049
+ ...typedParams(t.params),
28050
+ ...t.returns !== void 0 ? { returns: mapTypeNames(t.returns, map) } : {},
28051
+ methods: (t.methods ?? []).map((m) => ({ ...m, ...typedParams(m.params) }))
28052
+ };
27425
28053
  }
27426
28054
  default:
27427
28055
  return spec;
@@ -27760,6 +28388,7 @@ function specKind(raw) {
27760
28388
  if ("component" in raw && Array.isArray(raw.methods)) return "interface";
27761
28389
  if ("contract" in raw && Array.isArray(raw.methods)) return "implementation";
27762
28390
  if ("kind" in raw && Array.isArray(raw.fields)) return "type";
28391
+ if (raw.kind === "signature") return "type";
27763
28392
  return void 0;
27764
28393
  }
27765
28394
  function rewriteSpecRefs(raw, remap) {
@@ -27814,6 +28443,16 @@ function rewriteSpecRefs(raw, remap) {
27814
28443
  rewrite(raw, "component", "component");
27815
28444
  for (const method of entries(raw.methods)) {
27816
28445
  for (const param of entries(method?.params)) rewrite(param, "type", "type");
28446
+ const source = method?.signatureFrom;
28447
+ if (typeof source !== "string") continue;
28448
+ const parts = signatureSourceParts(source);
28449
+ const name = parts ? remap(parts.tail, "method", parts.head) : void 0;
28450
+ const component = parts ? remap(parts.head, "component", parts.tail) : void 0;
28451
+ const next = parts && (component !== parts.head || name !== parts.tail) ? `${component}.${name}` : remap(source, "type");
28452
+ if (next !== source) {
28453
+ method.signatureFrom = next;
28454
+ changed = true;
28455
+ }
27817
28456
  }
27818
28457
  } else if (kind === "implementation") {
27819
28458
  rewrite(raw, "contract", "interface");
@@ -27844,6 +28483,11 @@ function rewriteSpecRefs(raw, remap) {
27844
28483
  } else if (kind === "type") {
27845
28484
  rewrite(raw, "componentClass", "entity-class");
27846
28485
  for (const field of entries(raw.fields)) rewrite(field, "type", "type");
28486
+ for (const param of entries(raw.params)) rewrite(param, "type", "type");
28487
+ rewrite(raw, "returns", "type");
28488
+ for (const method of entries(raw.methods)) {
28489
+ for (const param of entries(method?.params)) rewrite(param, "type", "type");
28490
+ }
27847
28491
  }
27848
28492
  return changed;
27849
28493
  }
@@ -28362,6 +29006,10 @@ var SpecWorkspace = class {
28362
29006
  index.groups.push(...raw.index.groups);
28363
29007
  for (const kind of Object.keys(index.paths)) Object.assign(index.paths[kind], raw.index.paths[kind]);
28364
29008
  }
29009
+ const signatures = resolveTree(index.interfaces, index.components, index.types, raws.flatMap((r) => r.record.authoredReferences));
29010
+ index.interfaces = signatures.interfaces;
29011
+ index.types = signatures.types;
29012
+ index.signatures = signatures.facts;
28365
29013
  return { index, raws, readables };
28366
29014
  }
28367
29015
  /**
@@ -28787,7 +29435,9 @@ var SpecWorkspace = class {
28787
29435
  rewrite: local
28788
29436
  });
28789
29437
  };
29438
+ const localComponents = new Set(index.components.map((c) => nameKey(c.id.split("::").pop())));
28790
29439
  const bindLocal = (kind, specKey) => (position, value) => {
29440
+ if (position === "signatureFrom" && !value.includes("::") && !localComponents.has(nameKey(value))) return value;
28791
29441
  if (value.includes("::")) {
28792
29442
  raw.pending.push({ kind, specKey, position, authored: value, raw: false });
28793
29443
  return value;
@@ -29122,7 +29772,8 @@ var SpecWorkspace = class {
29122
29772
  this.scanAll();
29123
29773
  const from = this.getSubprojectPrefix(spec.id) ?? "";
29124
29774
  const carrying = carry && !this.carryDisabled ? /* @__PURE__ */ new Map() : null;
29125
- const mapped = mapSpecReferences(kind, spec, (position, value) => this.writeReference(from, spec.id, position, value, carrying));
29775
+ const components = new Set(this.scanAll().components.map((c) => c.id));
29776
+ const mapped = mapSpecReferences(kind, spec, (position, value) => position === "signatureFrom" && !components.has(value) ? value : this.writeReference(from, spec.id, position, value, carrying));
29126
29777
  return { ...mapped, id: localOf(from, spec.id).split("::").pop() };
29127
29778
  }
29128
29779
  // -------------------------------------------------------------------------
@@ -29218,8 +29869,8 @@ var SpecWorkspace = class {
29218
29869
  if (pathExists(this.paths.specsInterfacesDir()) && listFiles(this.paths.specsInterfacesDir(), ".yaml").length > 0) {
29219
29870
  return path21.join(this.paths.specsInterfacesDir(), `${id}.yaml`);
29220
29871
  }
29221
- const targetComponent = componentId || "default";
29222
- return path21.join(this.paths.specsDir(), "default", targetComponent, ".interface.yaml");
29872
+ const targetComponent2 = componentId || "default";
29873
+ return path21.join(this.paths.specsDir(), "default", targetComponent2, ".interface.yaml");
29223
29874
  }
29224
29875
  getImplementationPath(id, contractId) {
29225
29876
  const index = this.scanAll();
@@ -29415,8 +30066,15 @@ var SpecWorkspace = class {
29415
30066
  prepareComponentForWrite(spec) {
29416
30067
  return this.relativizeSpec("component", spec);
29417
30068
  }
30069
+ /**
30070
+ * spec_registry.saveInterfaceSpec steps 1-2: every method in its stored form
30071
+ * (method_signature.storedForm — the text a method's params derive, nothing
30072
+ * a signatureFrom supplies), then every reference written back relative to
30073
+ * the owning project.
30074
+ */
29418
30075
  prepareInterfaceForWrite(spec) {
29419
- return this.relativizeSpec("interface", spec);
30076
+ const stored = { ...spec, methods: (spec.methods ?? []).map((m) => storedMethodSignature(m)) };
30077
+ return this.relativizeSpec("interface", stored);
29420
30078
  }
29421
30079
  prepareImplementationForWrite(spec, carry = true) {
29422
30080
  const relative19 = this.relativizeSpec("implementation", spec, carry);
@@ -29426,8 +30084,10 @@ var SpecWorkspace = class {
29426
30084
  const partDir = this.partHolding(this.getImplementationPath(spec.id, spec.contract))?.part.directory;
29427
30085
  return partDir ? partRelativeFilePaths(relative19, this.rootDir, partDir) : relative19;
29428
30086
  }
30087
+ /** A type written in its stored form — every params-bearing method's text derived — relative to the owning project. */
29429
30088
  prepareTypeForWrite(spec) {
29430
- return this.relativizeSpec("type", spec);
30089
+ const stored = { ...spec, methods: (spec.methods ?? []).map((m) => storedTypeMethod(m)) };
30090
+ return this.relativizeSpec("type", stored);
29431
30091
  }
29432
30092
  prepareGroupForWrite(spec) {
29433
30093
  return this.relativizeSpec("group", spec);
@@ -29637,7 +30297,7 @@ var SpecWorkspace = class {
29637
30297
  if (!pathExists(p)) return null;
29638
30298
  try {
29639
30299
  const raw = readSpecFile(p);
29640
- return InterfaceSpecSchema.parse(raw);
30300
+ return resolveTree([InterfaceSpecSchema.parse(raw)], index.components, index.types).interfaces[0];
29641
30301
  } catch (e) {
29642
30302
  this.loaderIssues.push({
29643
30303
  severity: "error",
@@ -30202,6 +30862,17 @@ var SpecWorkspace = class {
30202
30862
  * dry-run a promotion (write 'complete' → validate → restore) without leaving
30203
30863
  * any change behind if validation fails or the user cancels.
30204
30864
  */
30865
+ /**
30866
+ * ispec_index.signatureFacts — what the current scan's signature resolution
30867
+ * recorded: every signatureFrom met and how it resolved, and every stored
30868
+ * text its params contradict. Read from the cached scan, rescanning first
30869
+ * when the tree changed.
30870
+ */
30871
+ signatureFacts() {
30872
+ const index = this;
30873
+ index.listProjectRoots();
30874
+ return this.cachedIndex.signatures;
30875
+ }
30205
30876
  snapshotSpecFiles() {
30206
30877
  const index = this.scanAll();
30207
30878
  const snapshot = /* @__PURE__ */ new Map();
@@ -30299,7 +30970,7 @@ var SpecWorkspace = class {
30299
30970
  */
30300
30971
  updateSpec(kind, id, delta, hooks, dryRun = false) {
30301
30972
  const notices = [];
30302
- const result = this.load(kind, id);
30973
+ const result = storedFormOf(kind, this.load(kind, id));
30303
30974
  if (!result) {
30304
30975
  throw new Error(`Spec of kind "${kind}" with ID "${id}" does not exist. Define it first.`);
30305
30976
  }
@@ -30812,6 +31483,7 @@ var SpecWorkspace = class {
30812
31483
  const qualifiedDelta = qualifyDeltaRefs(mergeableDelta);
30813
31484
  const mergedResult = mergeDelta(result, qualifiedDelta);
30814
31485
  for (const field of unsetFields) delete mergedResult[field];
31486
+ deriveMergedTexts(kind, mergedResult);
30815
31487
  const withDefaults = deltaSchema.safeParse(mergedResult);
30816
31488
  if (withDefaults.success) {
30817
31489
  for (const [key, value] of Object.entries(withDefaults.data)) {
@@ -31348,6 +32020,26 @@ var PROSE_FIELDS = /* @__PURE__ */ new Set([
31348
32020
  "caller"
31349
32021
  ]);
31350
32022
  var DELTA_JUMP_PINS = /* @__PURE__ */ Symbol("jumps written by this delta");
32023
+ function storedFormOf(kind, spec) {
32024
+ if (!spec) return spec;
32025
+ if (kind === "interface") {
32026
+ const intf = spec;
32027
+ return { ...intf, methods: intf.methods.map((m) => storedMethodSignature(m)) };
32028
+ }
32029
+ if (kind === "type") {
32030
+ const type = spec;
32031
+ return { ...type, methods: type.methods.map((m) => storedTypeMethod(m)) };
32032
+ }
32033
+ return spec;
32034
+ }
32035
+ function deriveMergedTexts(kind, merged) {
32036
+ if (kind !== "interface" && kind !== "type" || !Array.isArray(merged.methods)) return;
32037
+ merged.methods = merged.methods.map((m) => {
32038
+ if (!m || typeof m !== "object" || m.signatureFrom !== void 0 || !Array.isArray(m.params)) return m;
32039
+ const derived = deriveMethodSignature(m);
32040
+ return derived === void 0 ? m : { ...m, signature: derived };
32041
+ });
32042
+ }
31351
32043
  function cloneSpec(spec) {
31352
32044
  return JSON.parse(JSON.stringify(spec));
31353
32045
  }
@@ -31714,6 +32406,9 @@ function specPathsInScope(scopeSubsystem) {
31714
32406
  function snapshotSpecFiles() {
31715
32407
  return current().snapshotSpecFiles();
31716
32408
  }
32409
+ function signatureFacts() {
32410
+ return current().signatureFacts();
32411
+ }
31717
32412
  function findLegacySpecFiles() {
31718
32413
  return current().findLegacySpecFiles();
31719
32414
  }
@@ -31982,7 +32677,7 @@ function referencedKeys(part, types) {
31982
32677
  }
31983
32678
  for (const i of loadInterfaceSpecs().filter((x) => own.has(x.id))) {
31984
32679
  add2(i.component);
31985
- for (const m of i.methods) addTypes(methodTypeRefs(m));
32680
+ for (const m of i.methods) addTypes(sourcedMethodTypeRefs(m));
31986
32681
  }
31987
32682
  for (const impl of loadImplementationSpecs().filter((x) => own.has(x.id))) {
31988
32683
  add2(impl.contract);
@@ -31995,10 +32690,20 @@ function referencedKeys(part, types) {
31995
32690
  }
31996
32691
  for (const t of types.filter((x) => own.has(x.id))) {
31997
32692
  add2(t.subsystem);
31998
- for (const f of t.fields ?? []) addTypes(fieldTypeRefs(t, f.type));
32693
+ addTypes(typeSpecTypeRefs(t));
31999
32694
  }
32000
32695
  return out;
32001
32696
  }
32697
+ function sourcedMethodTypeRefs(m) {
32698
+ return [...methodTypeRefs(m), ...m.signatureFrom !== void 0 ? [m.signatureFrom] : []];
32699
+ }
32700
+ function typeSpecTypeRefs(t) {
32701
+ return [
32702
+ ...(t.fields ?? []).flatMap((f) => fieldTypeRefs(t, f.type)),
32703
+ ...(t.methods ?? []).filter((m) => m.params !== void 0).flatMap((m) => methodTypeRefs(m)),
32704
+ ...signatureTypeRefs(t)
32705
+ ];
32706
+ }
32002
32707
  var KINDS = ["component", "interface", "subsystem", "type", "implementation"];
32003
32708
  function closeOver(keys, types) {
32004
32709
  const out = /* @__PURE__ */ new Map();
@@ -32018,10 +32723,9 @@ function closeOver(keys, types) {
32018
32723
  queue.push(comp.subsystem);
32019
32724
  for (const contract of interfaces.filter((i) => i.component === comp.id)) queue.push(contract.id);
32020
32725
  } else if (found.kind === "interface") {
32021
- for (const m of found.spec.methods) addTypes(methodTypeRefs(m));
32726
+ for (const m of found.spec.methods) addTypes(sourcedMethodTypeRefs(m));
32022
32727
  } else if (found.kind === "type") {
32023
- const type = found.spec;
32024
- for (const f of type.fields ?? []) addTypes(fieldTypeRefs(type, f.type));
32728
+ addTypes(typeSpecTypeRefs(found.spec));
32025
32729
  } else if (found.kind === "implementation") {
32026
32730
  queue.push(found.spec.contract);
32027
32731
  }
@@ -33561,7 +34265,11 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
33561
34265
  for (const contract of moving) {
33562
34266
  saveInterfaceSpec({
33563
34267
  ...contract,
33564
- methods: contract.methods.map((m) => m.name === methodName ? { ...m, name: newName, signature: renameInSignature(m.signature, methodName, newName) } : m)
34268
+ methods: contract.methods.map((m) => m.name === methodName ? {
34269
+ ...m,
34270
+ name: newName,
34271
+ ...m.params === void 0 && m.signatureFrom === void 0 ? { signature: renameInSignature(m.signature, methodName, newName) } : {}
34272
+ } : m)
33565
34273
  });
33566
34274
  renamed.push(contract.id);
33567
34275
  }
@@ -35076,6 +35784,46 @@ function repairForeignStepFields(apply3) {
35076
35784
  return repairs;
35077
35785
  }
35078
35786
 
35787
+ // src/core/signature-repair.ts
35788
+ function repairSignatures(apply3) {
35789
+ const facts = signatureFacts();
35790
+ const repairs = /* @__PURE__ */ new Map();
35791
+ const repairOf = (specId, kind) => {
35792
+ const key = `${kind}:${specId}`;
35793
+ let repair = repairs.get(key);
35794
+ if (!repair) {
35795
+ repair = { specId, kind, regenerated: [], dropped: [] };
35796
+ repairs.set(key, repair);
35797
+ }
35798
+ return repair;
35799
+ };
35800
+ const held = new Set(facts.sources.filter((f) => f.outcome === "restated" && f.differs).map((f) => f.interfaceId));
35801
+ const own = (specId, kind) => !specId.includes("::") && !(kind === "interface" && held.has(specId));
35802
+ for (const stale of facts.staleTexts) {
35803
+ if (own(stale.specId, stale.kind)) repairOf(stale.specId, stale.kind).regenerated.push(stale);
35804
+ }
35805
+ for (const fact of facts.sources) {
35806
+ if (fact.outcome !== "restated" || fact.differs || !own(fact.interfaceId, "interface")) continue;
35807
+ repairOf(fact.interfaceId, "interface").dropped.push(fact.method);
35808
+ }
35809
+ const planned = [...repairs.values()];
35810
+ if (apply3 && planned.length > 0) {
35811
+ const interfaces = new Map(loadInterfaceSpecs().map((i) => [i.id, i]));
35812
+ const types = new Map(loadTypeSpecs().map((t) => [t.id, t]));
35813
+ for (const repair of planned) {
35814
+ if (repair.kind === "interface") {
35815
+ const intf = interfaces.get(repair.specId);
35816
+ if (intf) saveInterfaceSpec(intf);
35817
+ continue;
35818
+ }
35819
+ const type = types.get(repair.specId);
35820
+ if (type) saveSpec("type", type);
35821
+ }
35822
+ invalidateSpecCache();
35823
+ }
35824
+ return planned;
35825
+ }
35826
+
35079
35827
  // src/core/approval.ts
35080
35828
  var crypto7 = __toESM(require("crypto"));
35081
35829
  var path35 = __toESM(require("path"));
@@ -36103,6 +36851,8 @@ function componentCandidateGate(options = candidateOptions(), storedOwner) {
36103
36851
  return {
36104
36852
  gate: (kind, merged) => {
36105
36853
  refuseUnknownOwner(kind, merged, storedOwner);
36854
+ const restated = kind === "interface" ? restatedSources(merged.methods) : [];
36855
+ if (restated.length) throw new Error(`${restatedSourceRefusal(String(merged.id), restated)} Nothing was written.`);
36106
36856
  if (kind !== "component") return;
36107
36857
  const verdict = validateComponentCandidate(merged, options);
36108
36858
  if (verdict.errors.length) throw new Error(formatCandidateRefusal(verdict));
@@ -36110,6 +36860,43 @@ function componentCandidateGate(options = candidateOptions(), storedOwner) {
36110
36860
  }
36111
36861
  };
36112
36862
  }
36863
+ function restatedSources(methods) {
36864
+ if (!Array.isArray(methods)) return [];
36865
+ 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));
36866
+ }
36867
+ function restatedSourceRefusal(id, methods) {
36868
+ 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.`;
36869
+ }
36870
+ function unsignedMethods(methods) {
36871
+ if (!Array.isArray(methods)) return [];
36872
+ 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));
36873
+ }
36874
+ function deriveStatedTexts(kind, candidate) {
36875
+ if (kind !== "interface" && kind !== "type" || !Array.isArray(candidate.methods)) return [];
36876
+ const differed = [];
36877
+ candidate.methods = candidate.methods.map((m) => {
36878
+ if (!m || typeof m !== "object" || m.signatureFrom !== void 0 || !Array.isArray(m.params)) return m;
36879
+ const derived = deriveMethodSignature(m);
36880
+ if (derived === void 0) return m;
36881
+ if (typeof m.signature === "string" && m.signature !== derived) differed.push(`"${m.name}": stated "${m.signature}", written "${derived}"`);
36882
+ return { ...m, signature: derived };
36883
+ });
36884
+ return differed.length === 0 ? [] : [
36885
+ `Signature text derived from params, not taken as stated \u2014 ${differed.join("; ")}. A method with params shows the text its params derive.`
36886
+ ];
36887
+ }
36888
+ function storedForm(kind, spec) {
36889
+ if (!spec) return spec;
36890
+ if (kind === "interface") {
36891
+ const intf = spec;
36892
+ return { ...intf, methods: intf.methods.map((m) => storedMethodSignature(m)) };
36893
+ }
36894
+ if (kind === "type") {
36895
+ const type = spec;
36896
+ return { ...type, methods: type.methods.map((m) => storedTypeMethod(m)) };
36897
+ }
36898
+ return spec;
36899
+ }
36113
36900
  function refuseUnknownOwner(kind, merged, storedOwner) {
36114
36901
  if (kind !== "component" && kind !== "type") return;
36115
36902
  const owner = merged.subsystem;
@@ -36154,7 +36941,8 @@ function restatementParent(restatement) {
36154
36941
  return null;
36155
36942
  }
36156
36943
  }
36157
- function applyRestatement(restatement, existing, parent) {
36944
+ function applyRestatement(restatement, loaded, parent) {
36945
+ const existing = storedForm(restatement.kind, loaded);
36158
36946
  const replacedExisting = existing !== null;
36159
36947
  const parentRef = restatementParent(restatement);
36160
36948
  if (parentRef && !parent) return refused(restatement, replacedExisting, MISSING_PARENT[restatement.kind](parentRef.id));
@@ -36165,6 +36953,19 @@ function applyRestatement(restatement, existing, parent) {
36165
36953
  const carried = carryInto(restatement, existing, candidate);
36166
36954
  const cleared = clearedByOmission(existing, candidate, restatement.fields);
36167
36955
  stampLifecycle(candidate, existing, status.status);
36956
+ if (restatement.kind === "interface") {
36957
+ const restated = restatedSources(candidate.methods);
36958
+ if (restated.length) return refused(restatement, replacedExisting, `${restatedSourceRefusal(String(candidate.id), restated)} Nothing was written.`);
36959
+ }
36960
+ const unsigned = restatement.kind === "interface" || restatement.kind === "type" ? unsignedMethods(candidate.methods) : [];
36961
+ if (unsigned.length) {
36962
+ return refused(
36963
+ restatement,
36964
+ replacedExisting,
36965
+ `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.`
36966
+ );
36967
+ }
36968
+ const derivedNotices = deriveStatedTexts(restatement.kind, candidate);
36168
36969
  const labelErrors = resolveLabelsOf(restatement.kind, candidate);
36169
36970
  if (labelErrors.length) {
36170
36971
  return refused(restatement, replacedExisting, `Unresolved narrative label references \u2014 nothing was saved:
@@ -36176,7 +36977,7 @@ function applyRestatement(restatement, existing, parent) {
36176
36977
  spec: parsed.spec,
36177
36978
  ...status.status ? { status: status.status } : {},
36178
36979
  replacedExisting,
36179
- notices: existing ? rewriteNotices(restatement.kind, existing, candidate, carried, cleared) : [],
36980
+ notices: [...existing ? rewriteNotices(restatement.kind, existing, candidate, carried, cleared) : [], ...derivedNotices],
36180
36981
  changedMethods: changedMethodsOf(restatement.kind, existing, candidate)
36181
36982
  };
36182
36983
  }
@@ -40187,8 +40988,27 @@ var getSpecOutput = {
40187
40988
  }).optional().describe(
40188
40989
  "Derived, read-only guidance for a variant-tagged component \u2014 resolved from the variant registry, not part of the spec. Never write it back."
40189
40990
  ),
40991
+ resolvedSignatures: import_zod11.z.array(import_zod11.z.object({
40992
+ method: import_zod11.z.string().describe("The contract method that takes its signature from a source."),
40993
+ signatureFrom: import_zod11.z.string().describe("The source it names, as the spec holds it."),
40994
+ signature: import_zod11.z.string().describe("The text it shows, derived from the resolved params and returns."),
40995
+ 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)."),
40996
+ returns: import_zod11.z.string().describe("The returns the source supplies (`unknown` when it supplies none).")
40997
+ })).optional().describe(
40998
+ "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."
40999
+ ),
40190
41000
  ...staleServerOutput
40191
41001
  };
41002
+ function storedContractAnswer(loaded) {
41003
+ const resolvedSignatures = loaded.methods.filter((m) => m.signatureFrom !== void 0).map((m) => ({
41004
+ method: m.name,
41005
+ signatureFrom: m.signatureFrom,
41006
+ signature: m.signature,
41007
+ ...m.params ? { params: m.params } : {},
41008
+ returns: m.returns
41009
+ }));
41010
+ return { spec: { ...loaded, methods: loaded.methods.map((m) => storedMethodSignature(m)) }, resolvedSignatures };
41011
+ }
40192
41012
  var FINGERPRINT_VERSION = 1;
40193
41013
  function schemaShape(schema, seen = /* @__PURE__ */ new Map()) {
40194
41014
  const def = schema?._def;
@@ -40966,19 +41786,25 @@ ${renderChangeReport(report2)}`,
40966
41786
  }
40967
41787
  }
40968
41788
  );
41789
+ const methodParamItem = import_zod11.z.object({
41790
+ name: import_zod11.z.string(),
41791
+ type: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The parameter's type")),
41792
+ description: import_zod11.z.string().optional(),
41793
+ optional: import_zod11.z.boolean().optional().describe(
41794
+ '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.'
41795
+ )
41796
+ }).strict();
40969
41797
  const interfaceMethodShape = {
40970
41798
  name: import_zod11.z.string(),
40971
41799
  description: import_zod11.z.string(),
40972
- signature: import_zod11.z.string(),
40973
- returns: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The type the method answers with")),
40974
- params: import_zod11.z.array(import_zod11.z.object({
40975
- name: import_zod11.z.string(),
40976
- type: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The parameter's type")),
40977
- description: import_zod11.z.string().optional(),
40978
- optional: import_zod11.z.boolean().optional().describe(
40979
- '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.'
40980
- )
40981
- }).strict()).optional().describe("Structured parameters \u2014 authoritative for type checking (the prose signature becomes display-only). Strongly preferred."),
41800
+ signature: import_zod11.z.string().optional().describe(
41801
+ "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."
41802
+ ),
41803
+ 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")),
41804
+ signatureFrom: import_zod11.z.string().min(1).optional().describe(
41805
+ "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."
41806
+ ),
41807
+ 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."),
40982
41808
  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)"),
40983
41809
  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"),
40984
41810
  invokedBy: import_zod11.z.object({
@@ -41006,7 +41832,7 @@ ${renderChangeReport(report2)}`,
41006
41832
  server,
41007
41833
  "sdd_define_interface",
41008
41834
  {
41009
- 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.",
41835
+ 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.",
41010
41836
  inputSchema: interfaceInput,
41011
41837
  outputSchema: specWriteReceiptOutput
41012
41838
  },
@@ -41214,7 +42040,7 @@ ${renderChangeReport(report2)}`,
41214
42040
  }
41215
42041
  );
41216
42042
  const typeInput = {
41217
- kind: import_zod11.z.enum(["entity", "value-object"]).describe("entity (owned by a subsystem) or value-object (often system-level shared)"),
42043
+ 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"),
41218
42044
  id: import_zod11.z.string().describe("Lowercase identifier"),
41219
42045
  name: import_zod11.z.string().describe("Human-readable name"),
41220
42046
  description: import_zod11.z.string().optional(),
@@ -41230,7 +42056,8 @@ ${renderChangeReport(report2)}`,
41230
42056
  }).strict()).optional().describe('Data fields (type is a primitive or a qualified type id, e.g. "billing.Invoice")'),
41231
42057
  methods: import_zod11.z.array(import_zod11.z.object({
41232
42058
  name: import_zod11.z.string(),
41233
- signature: import_zod11.z.string(),
42059
+ 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"),
42060
+ params: import_zod11.z.array(methodParamItem).optional().describe("Structured parameters \u2014 authoritative for type checking; the signature text is derived from them"),
41234
42061
  returns: import_zod11.z.string().describe(TYPE_REF_GRAMMAR("The type the method answers with")),
41235
42062
  description: import_zod11.z.string().optional(),
41236
42063
  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"),
@@ -41245,18 +42072,20 @@ ${renderChangeReport(report2)}`,
41245
42072
  table: import_zod11.z.string().optional().describe("Optional database table name for table-schema types"),
41246
42073
  linkedEntity: import_zod11.z.string().optional().describe("Optional logical entity id represented by this table-schema type"),
41247
42074
  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)"),
41248
- 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')
42075
+ 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'),
42076
+ 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)"),
42077
+ returns: import_zod11.z.string().optional().describe(TYPE_REF_GRAMMAR("kind signature only, and required there: the function type's one output"))
41249
42078
  };
41250
42079
  const typeInputFields = Object.keys(typeInput);
41251
42080
  reg(
41252
42081
  server,
41253
42082
  "sdd_add_type",
41254
42083
  {
41255
- 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.",
42084
+ 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.",
41256
42085
  inputSchema: typeInput,
41257
42086
  outputSchema: specWriteReceiptOutput
41258
42087
  },
41259
- ({ kind, id, name, description, subsystem, group, fields, methods, componentClass, invariants, database, table, linkedEntity, sourcePath, symbol }) => {
42088
+ ({ kind, id, name, description, subsystem, group, fields, methods, componentClass, invariants, database, table, linkedEntity, sourcePath, symbol, params, returns }) => {
41260
42089
  try {
41261
42090
  const spec = {
41262
42091
  kind,
@@ -41280,7 +42109,9 @@ ${renderChangeReport(report2)}`,
41280
42109
  ...table ? { table } : {},
41281
42110
  ...linkedEntity ? { linkedEntity } : {},
41282
42111
  ...sourcePath ? { sourcePath } : {},
41283
- ...symbol ? { symbol } : {}
42112
+ ...symbol ? { symbol } : {},
42113
+ ...params ? { params } : {},
42114
+ ...returns ? { returns } : {}
41284
42115
  };
41285
42116
  const receipt = writeSpec({ kind: "type", spec, fields: typeInputFields });
41286
42117
  return writeReceipt(
@@ -41338,7 +42169,7 @@ ${renderChangeReport(report2)}`,
41338
42169
  server,
41339
42170
  "sdd_get_spec",
41340
42171
  {
41341
- 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.`,
42172
+ 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.`,
41342
42173
  inputSchema: {
41343
42174
  kind: import_zod11.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The kind of specification"),
41344
42175
  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")'),
@@ -41371,6 +42202,12 @@ ${renderChangeReport(report2)}`,
41371
42202
  break;
41372
42203
  }
41373
42204
  if (!result) return errText(`Spec of kind "${kind}" with ID "${id}" does not exist.`);
42205
+ let resolvedSignatures = [];
42206
+ if (kind === "interface") {
42207
+ const answer = storedContractAnswer(result);
42208
+ result = answer.spec;
42209
+ resolvedSignatures = answer.resolvedSignatures;
42210
+ }
41374
42211
  if (methods !== void 0) {
41375
42212
  const declared = result.methods;
41376
42213
  if (!Array.isArray(declared)) {
@@ -41392,19 +42229,23 @@ ${renderChangeReport(report2)}`,
41392
42229
  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.`
41393
42230
  };
41394
42231
  result = { ...result, methods: kept };
42232
+ resolvedSignatures = resolvedSignatures.filter((r) => methods.includes(r.method));
41395
42233
  }
41396
42234
  const guidance = kind === "component" ? resolveComponentVariantGuidance(result) : null;
42235
+ const resolved = resolvedSignatures.length > 0 ? { resolvedSignatures } : {};
41397
42236
  const folded = {
41398
42237
  ...result,
41399
42238
  ...partial2 ? { partialResult: partial2 } : {},
41400
- ...guidance ? { variantGuidance: guidance } : {}
42239
+ ...guidance ? { variantGuidance: guidance } : {},
42240
+ ...resolved
41401
42241
  };
41402
42242
  return structured(JSON.stringify(folded, null, 2), {
41403
42243
  kind,
41404
42244
  id,
41405
42245
  spec: result,
41406
42246
  ...partial2 ? { partialResult: partial2 } : {},
41407
- ...guidance ? { variantGuidance: guidance } : {}
42247
+ ...guidance ? { variantGuidance: guidance } : {},
42248
+ ...resolved
41408
42249
  });
41409
42250
  } catch (e) {
41410
42251
  return errText(String(e));
@@ -41663,6 +42504,10 @@ function loadProjectConfig3() {
41663
42504
  RETIRED_STEREOTYPES,
41664
42505
  RegistrySchema,
41665
42506
  RequirementItemSchema,
42507
+ ResolvedInterfaceSpecSchema,
42508
+ ResolvedMethodSignatureSchema,
42509
+ ResolvedTypeMethodSchema,
42510
+ ResolvedTypeSpecSchema,
41666
42511
  RulesConfigSchema,
41667
42512
  SCAN_EXCLUDE_DIRS,
41668
42513
  SDD_RULES,
@@ -41674,6 +42519,8 @@ function loadProjectConfig3() {
41674
42519
  SURFACE_AUDIENCES,
41675
42520
  SpecIdSchema,
41676
42521
  SpecStatusSchema,
42522
+ StoredMethodSignatureSchema,
42523
+ StoredTypeMethodSchema,
41677
42524
  SubsystemSpecSchema,
41678
42525
  SurfaceAudienceSchema,
41679
42526
  SurfaceContractEntrySchema,
@@ -41758,6 +42605,8 @@ function loadProjectConfig3() {
41758
42605
  dependencyCycles,
41759
42606
  deregisterPackRef,
41760
42607
  deriveExecutionProfile,
42608
+ deriveMethodSignature,
42609
+ deriveTypeSignature,
41761
42610
  derivedDocPaths,
41762
42611
  describeProject,
41763
42612
  detectDomainCandidates,
@@ -41917,6 +42766,7 @@ function loadProjectConfig3() {
41917
42766
  renameMethod,
41918
42767
  renderDiagram,
41919
42768
  repairForeignStepFields,
42769
+ repairSignatures,
41920
42770
  repointExternal,
41921
42771
  requiredPolicies,
41922
42772
  resolveAgentTopology,
@@ -41928,6 +42778,7 @@ function loadProjectConfig3() {
41928
42778
  resolveImport,
41929
42779
  resolveInstalledPack,
41930
42780
  resolveProjectExports,
42781
+ resolveSignatures,
41931
42782
  resolveSubprojectForNamespace,
41932
42783
  resolveSubsystemExports,
41933
42784
  resolveVariantGuidance,
@@ -41949,11 +42800,15 @@ function loadProjectConfig3() {
41949
42800
  setProjectRoot,
41950
42801
  setProjectType,
41951
42802
  settledSpecPaths,
42803
+ signatureFacts,
42804
+ signatureTypeRefs,
41952
42805
  specPathsInScope,
41953
42806
  splitNamespace,
41954
42807
  stepConfigVerdict,
41955
42808
  stepFieldsFor,
41956
42809
  stepGraph,
42810
+ storedMethodSignature,
42811
+ storedTypeMethod,
41957
42812
  syncContextFiles,
41958
42813
  technologyName,
41959
42814
  technologyTokens,