@dtmd/temper 0.0.4 → 0.0.6

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.
Files changed (84) hide show
  1. package/README.md +10 -12
  2. package/dist/src/assembly.d.ts +32 -16
  3. package/dist/src/assembly.js +11 -10
  4. package/dist/src/builtins.d.ts +104 -40
  5. package/dist/src/builtins.js +130 -53
  6. package/dist/src/claude-code.d.ts +7 -9
  7. package/dist/src/claude-code.js +6 -8
  8. package/dist/src/contract.d.ts +47 -24
  9. package/dist/src/contract.js +35 -9
  10. package/dist/src/declarations.d.ts +25 -103
  11. package/dist/src/declarations.js +311 -47
  12. package/dist/src/emit.d.ts +13 -32
  13. package/dist/src/emit.js +136 -53
  14. package/dist/src/generated/AssemblyFactRow.d.ts +29 -0
  15. package/dist/src/generated/AssemblyFactRow.js +2 -0
  16. package/dist/src/generated/BoundRow.d.ts +14 -0
  17. package/dist/src/generated/BoundRow.js +2 -0
  18. package/dist/src/generated/CharsetRow.d.ts +15 -0
  19. package/dist/src/generated/CharsetRow.js +2 -0
  20. package/dist/src/generated/ClauseRow.d.ts +97 -0
  21. package/dist/src/generated/ClauseRow.js +1 -0
  22. package/dist/src/generated/CollectionEntryRow.d.ts +23 -0
  23. package/dist/src/generated/CollectionEntryRow.js +2 -0
  24. package/dist/src/generated/CollectionEntryWire.d.ts +17 -0
  25. package/dist/src/generated/CollectionEntryWire.js +2 -0
  26. package/dist/src/generated/CountBoundRow.d.ts +13 -0
  27. package/dist/src/generated/CountBoundRow.js +2 -0
  28. package/dist/src/generated/Declarations.d.ts +72 -0
  29. package/dist/src/generated/Declarations.js +1 -0
  30. package/dist/src/generated/DegreeBoundRow.d.ts +15 -0
  31. package/dist/src/generated/DegreeBoundRow.js +1 -0
  32. package/dist/src/generated/EdgeBoundRow.d.ts +13 -0
  33. package/dist/src/generated/EdgeBoundRow.js +2 -0
  34. package/dist/src/generated/EmbeddedMember.d.ts +45 -0
  35. package/dist/src/generated/EmbeddedMember.js +1 -0
  36. package/dist/src/generated/EmbeddedMemberCollectionEntry.d.ts +21 -0
  37. package/dist/src/generated/EmbeddedMemberCollectionEntry.js +1 -0
  38. package/dist/src/generated/FeatureValue.d.ts +23 -0
  39. package/dist/src/generated/FeatureValue.js +1 -0
  40. package/dist/src/generated/Features.d.ts +82 -0
  41. package/dist/src/generated/Features.js +1 -0
  42. package/dist/src/generated/FencedBlock.d.ts +24 -0
  43. package/dist/src/generated/FencedBlock.js +2 -0
  44. package/dist/src/generated/IncludeRow.d.ts +18 -0
  45. package/dist/src/generated/IncludeRow.js +2 -0
  46. package/dist/src/generated/KindFactRow.d.ts +51 -0
  47. package/dist/src/generated/KindFactRow.js +1 -0
  48. package/dist/src/generated/LayoutRegionRow.d.ts +29 -0
  49. package/dist/src/generated/LayoutRegionRow.js +2 -0
  50. package/dist/src/generated/LayoutRow.d.ts +12 -0
  51. package/dist/src/generated/LayoutRow.js +1 -0
  52. package/dist/src/generated/MentionRow.d.ts +17 -0
  53. package/dist/src/generated/MentionRow.js +2 -0
  54. package/dist/src/generated/NestedMemberRow.d.ts +39 -0
  55. package/dist/src/generated/NestedMemberRow.js +1 -0
  56. package/dist/src/generated/Payload.d.ts +25 -0
  57. package/dist/src/generated/Payload.js +1 -0
  58. package/dist/src/generated/PayloadMember.d.ts +31 -0
  59. package/dist/src/generated/PayloadMember.js +2 -0
  60. package/dist/src/generated/RangeBoundRow.d.ts +15 -0
  61. package/dist/src/generated/RangeBoundRow.js +2 -0
  62. package/dist/src/generated/RequirementRow.d.ts +40 -0
  63. package/dist/src/generated/RequirementRow.js +1 -0
  64. package/dist/src/generated/SatisfiesRow.d.ts +14 -0
  65. package/dist/src/generated/SatisfiesRow.js +2 -0
  66. package/dist/src/generated/Section.d.ts +21 -0
  67. package/dist/src/generated/Section.js +2 -0
  68. package/dist/src/generated/SectionContainsRow.d.ts +14 -0
  69. package/dist/src/generated/SectionContainsRow.js +2 -0
  70. package/dist/src/generated/ValueType.d.ts +9 -0
  71. package/dist/src/generated/ValueType.js +2 -0
  72. package/dist/src/generated/index.d.ts +29 -0
  73. package/dist/src/generated/index.js +1 -0
  74. package/dist/src/index.d.ts +14 -18
  75. package/dist/src/index.js +10 -13
  76. package/dist/src/kind.d.ts +163 -41
  77. package/dist/src/kind.js +24 -24
  78. package/dist/src/needs.d.ts +8 -8
  79. package/dist/src/needs.js +7 -7
  80. package/dist/src/prose.d.ts +75 -28
  81. package/dist/src/prose.js +0 -0
  82. package/package.json +1 -1
  83. package/dist/src/genres.d.ts +0 -38
  84. package/dist/src/genres.js +0 -24
@@ -1,113 +1,35 @@
1
1
  /**
2
- * Declaration rows — the composed program's erased declarations
3
- * (`specs/architecture/20-surface.md`, "The seam — one implementation"; "The lock
4
- * and drift"). Every type erases at the seam: kinds, clauses, requirements, and
5
- * assembly facts compile to plain rows the engine reads. The **row shape** matches
6
- * the Rust lock's `[declaration]` families (`src/drift.rs` `Declarations`) — the
7
- * byte-parity lockstep two writers keep until single-writer lands
8
- * (`SDK-RECUT-CORPUS-FACE`). The same rows ride the internal versioned JSON pipe
9
- * ({@link declarationsToJson}) — not a designed IR, versioned in lockstep.
2
+ * Declaration rows — the composed program's erased declarations.
3
+ * Every type erases at the seam: kinds, clauses, requirements, and
4
+ * assembly facts compile to plain rows the engine reads. The **row shapes are the
5
+ * generated `ts-rs` bindings** (`./generated/`, derived from `src/drift.rs`), so a
6
+ * Rust-side row rename is a compile error here, never a silent shape drift. This
7
+ * module authors the builders that fill them; the same rows ride the internal
8
+ * versioned JSON pipe ({@link encodeSeam}) — not a designed IR, versioned
9
+ * in lockstep.
10
10
  */
11
11
  import type { Harness } from "./assembly.js";
12
- import type { Charset } from "./contract.js";
13
- /** One kind's declaration row — its identity and declared runtime facts. */
14
- export interface KindFactRow {
15
- readonly name: string;
16
- readonly provider?: string;
17
- readonly governs_root: string;
18
- readonly governs_glob: string;
19
- readonly format?: string;
20
- readonly unit_shape?: string;
21
- readonly registration?: string;
22
- }
12
+ import type { Declarations, Payload } from "./generated/index.js";
13
+ export type { AssemblyFactRow, ClauseRow, Declarations, KindFactRow, RequirementRow, SatisfiesRow, } from "./generated/index.js";
14
+ /** The stable-sort ordering every declaration row family shares. */
15
+ export declare function compareStrings(a: string, b: string): number;
16
+ /** Every requirement name a `satisfies` claim may fill — assembly `require` ∪ member `requires`. */
17
+ export declare function declaredRequirements(harness: Harness): Set<string>;
23
18
  /**
24
- * One clause of a kind's effective contract, or one of a requirement's own
25
- * set-/edge-scope demands — the same row shape either way (`src/drift.rs`
26
- * `ClauseRow`). `kind` is absent when this row is nested inside a
27
- * `RequirementRow`'s own `clauses`: a requirement's demand names no kind of its
28
- * own (`10-contracts.md`, "Decision: set-scope demands are clauses").
19
+ * Every address a mention may name — declared requirement names ∪ each member's
20
+ * `kind:name`. Shared by `emit.ts` (a member-level `Text` body's mentions) and
21
+ * this module (an embedded member's `Text` leaves) — the one resolution-check
22
+ * set, so a leaf mention and a member mention are held to the identical bar.
29
23
  */
30
- export interface ClauseRow {
31
- readonly kind?: string;
32
- readonly predicate: string;
33
- readonly field?: string;
34
- readonly severity: string;
35
- /** The just-in-time teaching channel the predicate cannot encode (`10-contracts.md`, "guidance"). */
36
- readonly guidance?: string;
37
- /** The external-fact source backing the clause — a doc URL plus retrieved date (`10-contracts.md`, "cite"). */
38
- readonly cite?: string;
39
- /** The `count` predicate's satisfier-set-size bound. */
40
- readonly count?: {
41
- readonly min: number;
42
- readonly max: number;
43
- };
44
- /** The `membership` predicate's target requirement name. */
45
- readonly target?: string;
46
- /** The `degree` predicate's in/out edge-count bound. */
47
- readonly degree?: {
48
- readonly incoming?: {
49
- readonly min?: number;
50
- readonly max?: number;
51
- };
52
- readonly outgoing?: {
53
- readonly min?: number;
54
- readonly max?: number;
55
- };
56
- };
57
- /** The `min_len`/`max_len`/`max_lines` predicate's scalar bound. */
58
- readonly bound?: {
59
- readonly min?: number;
60
- readonly max?: number;
61
- };
62
- /** The `allowed_chars` predicate's declared character class. */
63
- readonly charset?: Charset;
64
- /** The `forbidden_keys` predicate's forbidden key list. */
65
- readonly keys?: readonly string[];
66
- /** The `deny` predicate's forbidden value list. */
67
- readonly values?: readonly string[];
68
- }
69
- /**
70
- * One named requirement's declaration row — the scalar facets plus its own
71
- * `count`/`unique`/`membership`/`degree` clause rows (`10-contracts.md`,
72
- * "Decision: set-scope demands are clauses"): the requirement's `clauses` array
73
- * is the whole of its set-/edge-scope demand, no facet columns beside it.
74
- */
75
- export interface RequirementRow {
76
- readonly name: string;
77
- readonly kind?: string;
78
- readonly required: boolean;
79
- readonly clauses: readonly ClauseRow[];
80
- readonly verified_by?: string;
81
- }
82
- /** One assembly-scope fact — authority or an edge. */
83
- export interface AssemblyFactRow {
84
- readonly fact: string;
85
- readonly value?: string;
86
- readonly from?: string;
87
- readonly field?: string;
88
- readonly to?: string;
89
- }
90
- /** One member→requirement fill edge — a resolved `satisfies` key. */
91
- export interface SatisfiesRow {
92
- readonly member: string;
93
- readonly requirement: string;
94
- }
95
- /** The five declaration families — the whole erased program the lock and pipe carry. */
96
- export interface Declarations {
97
- readonly kinds: readonly KindFactRow[];
98
- readonly clauses: readonly ClauseRow[];
99
- readonly requirements: readonly RequirementRow[];
100
- readonly assembly: readonly AssemblyFactRow[];
101
- readonly satisfies: readonly SatisfiesRow[];
102
- }
103
- /** Compile a harness into its five declaration families — the erased program. */
24
+ export declare function declaredAddresses(harness: Harness): Set<string>;
25
+ /** Compile a harness into its seven declaration families — the erased program. */
104
26
  export declare function compileDeclarations(harness: Harness): Declarations;
105
27
  /** The SDK's pinned engine/interchange version — the JSON pipe rides it in lockstep. */
106
28
  export declare const SEAM_VERSION = 2;
107
29
  /**
108
- * Serialize the declaration rows to the internal versioned JSON pipe
109
- * (`20-surface.md`, "The seam"). Not a designed IR — a stable public interchange
110
- * is admitted only when its consumer lands. Deterministic: insertion-ordered keys
111
- * and a trailing newline, so a re-emit is byte-identical (law 5).
30
+ * Serialize the seam payload to the internal versioned JSON pipe — `encodeSeam`
31
+ * stamps `version`, so the caller supplies the rest of the {@link Payload}.
32
+ * Deterministic: insertion-ordered keys and a trailing newline, so a re-emit is
33
+ * byte-identical.
112
34
  */
113
- export declare function declarationsToJson(declarations: Declarations): string;
35
+ export declare function encodeSeam(payload: Omit<Payload, "version">): string;
@@ -1,20 +1,21 @@
1
1
  /**
2
- * Declaration rows — the composed program's erased declarations
3
- * (`specs/architecture/20-surface.md`, "The seam — one implementation"; "The lock
4
- * and drift"). Every type erases at the seam: kinds, clauses, requirements, and
5
- * assembly facts compile to plain rows the engine reads. The **row shape** matches
6
- * the Rust lock's `[declaration]` families (`src/drift.rs` `Declarations`) — the
7
- * byte-parity lockstep two writers keep until single-writer lands
8
- * (`SDK-RECUT-CORPUS-FACE`). The same rows ride the internal versioned JSON pipe
9
- * ({@link declarationsToJson}) — not a designed IR, versioned in lockstep.
2
+ * Declaration rows — the composed program's erased declarations.
3
+ * Every type erases at the seam: kinds, clauses, requirements, and
4
+ * assembly facts compile to plain rows the engine reads. The **row shapes are the
5
+ * generated `ts-rs` bindings** (`./generated/`, derived from `src/drift.rs`), so a
6
+ * Rust-side row rename is a compile error here, never a silent shape drift. This
7
+ * module authors the builders that fill them; the same rows ride the internal
8
+ * versioned JSON pipe ({@link encodeSeam}) — not a designed IR, versioned
9
+ * in lockstep.
10
10
  */
11
+ import { fileURLToPath } from "node:url";
12
+ import { resolveLeaf } from "./prose.js";
11
13
  /**
12
14
  * Compile one `Clause` into its lock row: the shared `key`/`field`/`severity`/
13
15
  * `guidance`/`cite` columns — the clause's four channels surviving erasure
14
- * (`10-contracts.md`, "The clause — the atom of a contract") — plus, when the
16
+ * — plus, when the
15
17
  * predicate carries them, the `count`/`target`/`degree` argument columns a
16
- * requirement's own set-/edge-scope demand needs (`10-contracts.md`, "Judged
17
- * at the node-set scope" / "Judged at the edge scope"), and the
18
+ * requirement's own set-/edge-scope demand needs, and the
18
19
  * `bound`/`charset`/`keys`/`values` argument columns a kind's own node-scope
19
20
  * floor clause needs (`min_len`/`max_len`/`max_lines`'s bound,
20
21
  * `allowed_chars`'s charset, `forbidden_keys`'s keys, `deny`'s values) — so
@@ -42,9 +43,25 @@ function clauseRow(clause, kind) {
42
43
  }
43
44
  : undefined,
44
45
  bound: nodeScopeBoundArgs(predicate),
45
- charset: predicate.key === "allowed_chars" ? predicate.charset : undefined,
46
- keys: predicate.key === "forbidden_keys" ? predicate.keys : undefined,
47
- values: predicate.key === "deny" ? predicate.values : undefined,
46
+ // The generated rows carry mutable columns; the predicate's `charset`/`keys`/
47
+ // `values` are read-only, so copy each into a fresh array/object — the same
48
+ // bytes, a shape the row will accept.
49
+ charset: predicate.key === "allowed_chars" && predicate.charset !== undefined
50
+ ? {
51
+ ranges: predicate.charset.ranges ? [...predicate.charset.ranges] : undefined,
52
+ chars: predicate.charset.chars,
53
+ }
54
+ : undefined,
55
+ keys: predicate.key === "forbidden_keys" && predicate.keys ? [...predicate.keys] : undefined,
56
+ values: (predicate.key === "deny" || predicate.key === "enum") && predicate.values
57
+ ? [...predicate.values]
58
+ : undefined,
59
+ range: predicate.key === "range" && predicate.range !== undefined
60
+ ? { min: predicate.range.min, max: predicate.range.max }
61
+ : undefined,
62
+ section: predicate.key === "section_contains" && predicate.section !== undefined
63
+ ? { heading: predicate.section.heading, marker: predicate.section.marker }
64
+ : undefined,
48
65
  };
49
66
  }
50
67
  /** `min_len`/`max_len`/`max_lines`'s scalar bound off their shared `min`/`max`
@@ -65,11 +82,24 @@ function edgeBoundArgs(args, direction) {
65
82
  const max = args?.[`${direction}_max`];
66
83
  return min === undefined && max === undefined ? undefined : { min, max };
67
84
  }
68
- /** The lock label for a kind's declared registration. */
85
+ /**
86
+ * The lock label for a kind's declared unit shape: `file`/`directory` verbatim,
87
+ * or `named-field(<identityField>)` for the third mode — the same `<name>(<field>)`
88
+ * call syntax [`registrationLabel`] uses, so the id source round-trips through the
89
+ * row rather than degrading to a bare, unreconstructable `"named-field"`.
90
+ */
91
+ function unitShapeLabel(facts) {
92
+ if (facts.unitShape !== "named-field")
93
+ return facts.unitShape;
94
+ return `named-field(${facts.identityField})`;
95
+ }
96
+ /** The lock label for one declared registration channel. */
69
97
  function registrationLabel(registration) {
70
98
  switch (registration.via) {
71
99
  case "always":
72
100
  return "always";
101
+ case "user-invoked":
102
+ return "user-invoked";
73
103
  case "description-trigger":
74
104
  return `description-trigger(${registration.field})`;
75
105
  case "paths-match":
@@ -80,13 +110,58 @@ function registrationLabel(registration) {
80
110
  return "connection";
81
111
  }
82
112
  }
83
- /** One kind's fact row — the `at` locus supplies `governs_root`/`governs_glob`. */
84
- function kindFactRow(facts) {
113
+ /** The lock labels for a kind's declared registration **set**, in declaration order —
114
+ * `undefined` for an empty set, the same omit-the-column tolerance `templatesFor` takes. */
115
+ function registrationLabels(registration) {
116
+ return registration.length > 0 ? registration.map(registrationLabel) : undefined;
117
+ }
118
+ /** The stable-sort ordering every declaration row family shares. */
119
+ export function compareStrings(a, b) {
120
+ return a < b ? -1 : a > b ? 1 : 0;
121
+ }
122
+ /**
123
+ * A host kind's declared nesting templates — the embedded kinds among `allKinds`
124
+ * whose `withinHosts` names it, name-sorted. `undefined` when the host nests nothing, so the row omits the column
125
+ * rather than carrying an empty array.
126
+ */
127
+ function templatesFor(hostName, allKinds) {
128
+ const names = allKinds
129
+ .filter((facts) => facts.locus.kind === "embedded" && facts.locus.withinHosts.includes(hostName))
130
+ .map((facts) => facts.name)
131
+ .sort(compareStrings);
132
+ return names.length > 0 ? names : undefined;
133
+ }
134
+ /**
135
+ * Lower a kind's declared {@link Layout} into its `content` row — one flat
136
+ * discriminator-plus-columns [`LayoutRegionRow`] per region, `memberKind` spelled as the
137
+ * wire's snake_case `member_kind`. `undefined` for a `file`-content kind (no declared
138
+ * content), so its row omits the column and stays byte-identical.
139
+ */
140
+ function contentRow(content) {
141
+ if (content === undefined)
142
+ return undefined;
143
+ const regions = content.regions.map((region) => {
144
+ switch (region.region) {
145
+ case "prose":
146
+ return { region: "prose", import: region.import };
147
+ case "field":
148
+ return { region: "field", slot: region.slot };
149
+ case "collection":
150
+ return { region: "collection", member_kind: region.memberKind, key: region.key };
151
+ }
152
+ });
153
+ return { regions };
154
+ }
155
+ /**
156
+ * One kind's fact row — the `at` locus supplies `governs_root`/`governs_glob`,
157
+ * `templates` names the embedded kinds (among `allKinds`) declared within it, and
158
+ * `content` lowers a declared layout (absent for a `file`-content kind).
159
+ */
160
+ function kindFactRow(facts, allKinds) {
85
161
  if (facts.locus.kind !== "at") {
86
- // A genre inherits its world residue through its host; it carries no `at`
87
- // locus, so it takes no kind-fact row (`15-kinds.md`). Callers filter these
88
- // out before this point.
89
- throw new Error(`kind \`${facts.name}\` is a genre — it carries no locus-bearing kind fact.`);
162
+ // An embedded kind inherits its world residue through its host; it carries no
163
+ // `at` locus, so it takes no kind-fact row. Callers filter these out before this point.
164
+ throw new Error(`kind \`${facts.name}\` is embedded — it carries no locus-bearing kind fact.`);
90
165
  }
91
166
  return {
92
167
  name: facts.name,
@@ -94,20 +169,26 @@ function kindFactRow(facts) {
94
169
  governs_root: facts.locus.root,
95
170
  governs_glob: facts.locus.glob,
96
171
  format: facts.format,
97
- unit_shape: facts.unitShape,
98
- registration: registrationLabel(facts.registration),
172
+ unit_shape: unitShapeLabel(facts),
173
+ registration: registrationLabels(facts.registration),
174
+ templates: templatesFor(facts.name, allKinds),
175
+ content: contentRow(facts.content),
99
176
  };
100
177
  }
101
- /** The distinct locus-bearing kinds in play — member kinds ∪ expect kinds, name-sorted. */
178
+ /** Every kind in play, at any locus — member kinds ∪ expect kinds ∪ their embedded children. */
102
179
  function kindsInPlay(harness) {
103
180
  const byName = new Map();
104
181
  for (const member of harness.members)
105
182
  byName.set(member.facts.name, member.facts);
106
183
  for (const binding of harness.expect)
107
184
  byName.set(binding.kind.facts.name, binding.kind.facts);
108
- return [...byName.values()]
185
+ return [...byName.values()];
186
+ }
187
+ /** The distinct locus-bearing (`at`) kinds in play, name-sorted. */
188
+ function atLocusKindsInPlay(allKinds) {
189
+ return allKinds
109
190
  .filter((facts) => facts.locus.kind === "at")
110
- .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
191
+ .sort((a, b) => compareStrings(a.name, b.name));
111
192
  }
112
193
  /** The requirement rows — assembly `require` and every member's `requires`, one namespace. */
113
194
  function requirementRows(harness) {
@@ -116,7 +197,7 @@ function requirementRows(harness) {
116
197
  const existing = merged.get(name);
117
198
  if (existing !== undefined && existing !== requirement) {
118
199
  // One namespace, one fill mechanism; a cross-publisher name collision is an
119
- // admissibility finding, never a shadowing rule (`10-contracts.md`).
200
+ // admissibility finding, never a shadowing rule.
120
201
  throw new Error(`requirement \`${name}\` is published twice (${source} collides with an earlier ` +
121
202
  `publisher) — a name collision across publishers is an admissibility finding.`);
122
203
  }
@@ -130,23 +211,24 @@ function requirementRows(harness) {
130
211
  }
131
212
  }
132
213
  return [...merged.entries()]
133
- .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
214
+ .sort(([a], [b]) => compareStrings(a, b))
134
215
  .map(([name, requirement]) => ({
135
216
  name,
136
217
  kind: requirement.kind?.key,
137
218
  required: requirement.required ?? false,
138
219
  clauses: (requirement.clauses ?? []).map((clause) => clauseRow(clause)),
139
220
  verified_by: requirement.verifiedBy,
221
+ prose: requirement.prose,
140
222
  }));
141
223
  }
142
224
  /**
143
- * The assembly-scope facts, in a stable order: authority (always declared — the
144
- * `shared` default anchors every harness until a surface-authority posture is
145
- * authored), then one edge row per kind edge field (`40-composition.md`;
146
- * `45-governance.md`).
225
+ * The assembly-scope facts, in a stable order: the root member's declared
226
+ * `mode` (always present"The root
227
+ * member": harness-wide declarations are root-member fields), then one edge
228
+ * row per kind edge field.
147
229
  */
148
- function assemblyFactRows(kinds) {
149
- const facts = [{ fact: "authority", value: "shared" }];
230
+ function assemblyFactRows(harness, kinds) {
231
+ const facts = [{ fact: "mode", value: harness.mode }];
150
232
  for (const kind of kinds) {
151
233
  for (const edge of kind.edgeFields ?? []) {
152
234
  facts.push({ fact: "edge", from: kind.name, field: edge.field, to: edge.to });
@@ -162,34 +244,216 @@ function satisfiesRows(harness) {
162
244
  rows.push({ member: member.name, requirement });
163
245
  }
164
246
  }
165
- return rows.sort((a, b) => (a.member < b.member ? -1 : a.member > b.member ? 1 : 0) ||
166
- (a.requirement < b.requirement ? -1 : a.requirement > b.requirement ? 1 : 0));
247
+ return rows.sort((a, b) => compareStrings(a.member, b.member) || compareStrings(a.requirement, b.requirement));
248
+ }
249
+ /**
250
+ * The `mention` rows — every member's authored `n` targets, member-then-target
251
+ * sorted. `text`-kind prose contributes one row per mention, keyed to the
252
+ * member's own `kind:name` address; `blocks()`-kind prose additionally
253
+ * contributes one row per mention inside a `Text`-authored leaf, keyed to that
254
+ * leaf's own `<member>/<kind>/<key>/<child-path>` address
255
+ * ([`embeddedLeafMentionRows`]) — a `file()` body names none. Recorded off the
256
+ * raw authored address, unconditionally — resolution is `emit`'s own refusal
257
+ * (`emit.ts`), not this row's concern.
258
+ */
259
+ function mentionRows(harness) {
260
+ const rows = [];
261
+ for (const member of harness.members) {
262
+ if (member.prose?.kind === "text") {
263
+ const address = `${member.kind}:${member.name}`;
264
+ for (const mention of member.prose.mentions) {
265
+ rows.push({ member: address, target: mention.target.address });
266
+ }
267
+ }
268
+ if (member.prose?.kind === "blocks") {
269
+ for (const value of member.prose.values) {
270
+ rows.push(...embeddedLeafMentionRows(member.name, value));
271
+ }
272
+ }
273
+ }
274
+ return rows.sort((a, b) => compareStrings(a.member, b.member) || compareStrings(a.target, b.target));
275
+ }
276
+ /**
277
+ * The mention rows one `blocks()`-declared embedded-member value's `Text` leaves
278
+ * contribute — top-level leaves addressed by their bare field name, a
279
+ * collection entry's leaves addressed `<collection>.<entry>.<field>` (one layer
280
+ * deep, matching the row's own shape) — each row keyed to the leaf's own
281
+ * structural address, the `<member>/<kind>/<key>/<child-path>` grammar
282
+ * `src/read.rs`'s `parse_leaf_address` resolves. A bare-string leaf names no
283
+ * mention.
284
+ */
285
+ function embeddedLeafMentionRows(hostName, value) {
286
+ const rows = [];
287
+ const addressed = (childPath) => `${hostName}/${value.kind}/${value.key}/${childPath}`;
288
+ for (const [field, leaf] of Object.entries(value.leaves)) {
289
+ if (typeof leaf === "string")
290
+ continue;
291
+ for (const mention of leaf.mentions) {
292
+ rows.push({ member: addressed(field), target: mention.target.address });
293
+ }
294
+ }
295
+ for (const [collection, entries] of Object.entries(value.collections)) {
296
+ for (const entry of entries) {
297
+ for (const [field, leaf] of Object.entries(entry.leaves)) {
298
+ if (typeof leaf === "string")
299
+ continue;
300
+ for (const mention of leaf.mentions) {
301
+ rows.push({ member: addressed(`${collection}.${entry.key}.${field}`), target: mention.target.address });
302
+ }
303
+ }
304
+ }
305
+ }
306
+ return rows;
307
+ }
308
+ /**
309
+ * The `include` rows — every member's `text`-body includes, in member-then-authored
310
+ * order. Each carries the host member's `kind:name` address and the include target's
311
+ * path resolved against the stating module ({@link fileURLToPath} over the include's own
312
+ * `moduleUrl`), never the workspace — the engine reads, splices, and fingerprints it.
313
+ * Member order stays authored (never target-sorted): the body's include slots ride the
314
+ * same order, so the engine pairs the k-th slot with the k-th row. Only member-level
315
+ * `text` prose carries includes (a `file()`/`blocks()` body names none; an embedded leaf
316
+ * is refused at {@link resolveLeaf}).
317
+ */
318
+ function includeRows(harness) {
319
+ const rows = [];
320
+ for (const member of harness.members) {
321
+ if (member.prose?.kind !== "text")
322
+ continue;
323
+ const address = `${member.kind}:${member.name}`;
324
+ for (const include of member.prose.includes) {
325
+ rows.push({ member: address, source_path: fileURLToPath(new URL(include.path, include.moduleUrl)) });
326
+ }
327
+ }
328
+ return rows;
329
+ }
330
+ /**
331
+ * One host member's declared embedded-member value as its declaration row —
332
+ * each `Text`-authored leaf resolved to its final stored string
333
+ * ([`NestedMemberRow`]), mention-resolution-checked against `mentionable` the
334
+ * identical way `emit.ts`'s `renderMemberToml` checks the same leaf on its way
335
+ * into the rendered fence.
336
+ */
337
+ function nestedMemberRow(host, value, mentionable) {
338
+ const context = (childPath) => `member.${value.kind} ${value.key}: leaf \`${childPath}\``;
339
+ const leaves = {};
340
+ for (const [field, leaf] of Object.entries(value.leaves)) {
341
+ leaves[field] = resolveLeaf(leaf, mentionable, context(field));
342
+ }
343
+ const collections = {};
344
+ for (const [collection, entries] of Object.entries(value.collections)) {
345
+ collections[collection] = entries.map((entry) => {
346
+ const entryLeaves = {};
347
+ for (const [field, leaf] of Object.entries(entry.leaves)) {
348
+ entryLeaves[field] = resolveLeaf(leaf, mentionable, context(`${collection}.${entry.key}.${field}`));
349
+ }
350
+ return { key: entry.key, leaves: entryLeaves };
351
+ });
352
+ }
353
+ return { host, kind: value.kind, key: value.key, leaves, collections };
354
+ }
355
+ /**
356
+ * Every in-play embedded kind's declared host kinds, keyed by kind name — the map
357
+ * {@link nestedMemberRows} checks a `blocks()` value's kind against. An embedded kind
358
+ * reaches this map through `kindsInPlay` (a member of it, or an `expect` binding), and
359
+ * its `withinHosts` is the set of host kinds it templates within; a value whose kind is
360
+ * absent here, or whose hosts miss the hosting member's kind, is untemplated nesting.
361
+ */
362
+ function embeddedHostsByKind(harness) {
363
+ const map = new Map();
364
+ for (const facts of kindsInPlay(harness)) {
365
+ if (facts.locus.kind === "embedded") {
366
+ map.set(facts.name, new Set(facts.locus.withinHosts));
367
+ }
368
+ }
369
+ return map;
370
+ }
371
+ /**
372
+ * The `nested_member` rows — every host member's `blocks()`-declared embedded-member
373
+ * values, host-then-kind-then-key sorted. Only `blocks`-kind prose carries them (a
374
+ * `file()`/`text` body names none); the fence rendering itself is unchanged
375
+ * (`emit.ts`'s `resolveBody`) — this row is a second *read* of the same authored
376
+ * value, never a second copy the engine reads back (0018).
377
+ *
378
+ * Refuses an untemplated nesting before a byte is written: a value's kind must be an
379
+ * in-play embedded kind whose `withinHosts` names the hosting member's kind. `templates`
380
+ * derives solely from `withinHosts` ({@link templatesFor}), so a value the host never
381
+ * templates would reach the lock as a `nested_member` row no `templates` column admits,
382
+ * to be unmodeled without a word — nesting is the host template's own declaration, so an
383
+ * unadmitted nested member is an unresolved input, not output to write over.
384
+ */
385
+ function nestedMemberRows(harness, mentionable) {
386
+ const hostsByKind = embeddedHostsByKind(harness);
387
+ const rows = [];
388
+ for (const member of harness.members) {
389
+ if (member.prose?.kind !== "blocks")
390
+ continue;
391
+ const host = `${member.kind}:${member.name}`;
392
+ for (const value of member.prose.values) {
393
+ const hosts = hostsByKind.get(value.kind);
394
+ if (hosts === undefined || !hosts.has(member.kind)) {
395
+ throw new Error(`member \`${member.name}\`: embedded value \`${value.key}\` is of kind ` +
396
+ `\`${value.kind}\`, which does not nest within host kind \`${member.kind}\` — a ` +
397
+ `\`blocks()\` value's kind must be an in-play embedded kind whose \`withinHosts\` ` +
398
+ `names the host (specs/model/representation.md, "nesting").`);
399
+ }
400
+ rows.push(nestedMemberRow(host, value, mentionable));
401
+ }
402
+ }
403
+ return rows.sort((a, b) => compareStrings(a.host, b.host) || compareStrings(a.kind, b.kind) || compareStrings(a.key, b.key));
404
+ }
405
+ /** Every requirement name a `satisfies` claim may fill — assembly `require` ∪ member `requires`. */
406
+ export function declaredRequirements(harness) {
407
+ const set = new Set();
408
+ for (const name of Object.keys(harness.require))
409
+ set.add(name);
410
+ for (const member of harness.members) {
411
+ for (const name of Object.keys(member.requires))
412
+ set.add(name);
413
+ }
414
+ return set;
415
+ }
416
+ /**
417
+ * Every address a mention may name — declared requirement names ∪ each member's
418
+ * `kind:name`. Shared by `emit.ts` (a member-level `Text` body's mentions) and
419
+ * this module (an embedded member's `Text` leaves) — the one resolution-check
420
+ * set, so a leaf mention and a member mention are held to the identical bar.
421
+ */
422
+ export function declaredAddresses(harness) {
423
+ const set = declaredRequirements(harness);
424
+ for (const member of harness.members)
425
+ set.add(`${member.kind}:${member.name}`);
426
+ return set;
167
427
  }
168
- /** Compile a harness into its five declaration families — the erased program. */
428
+ /** Compile a harness into its seven declaration families — the erased program. */
169
429
  export function compileDeclarations(harness) {
170
- const kinds = kindsInPlay(harness);
430
+ const allKinds = kindsInPlay(harness);
431
+ const kinds = atLocusKindsInPlay(allKinds);
171
432
  const clauses = [];
172
- for (const binding of [...harness.expect].sort((a, b) => (a.kind.key < b.kind.key ? -1 : a.kind.key > b.kind.key ? 1 : 0))) {
433
+ for (const binding of [...harness.expect].sort((a, b) => compareStrings(a.kind.key, b.kind.key))) {
173
434
  for (const clause of binding.clauses) {
174
435
  clauses.push(clauseRow(clause, binding.kind.key));
175
436
  }
176
437
  }
177
438
  return {
178
- kinds: kinds.map(kindFactRow),
439
+ kinds: kinds.map((facts) => kindFactRow(facts, allKinds)),
179
440
  clauses,
180
441
  requirements: requirementRows(harness),
181
- assembly: assemblyFactRows(kinds),
442
+ assembly: assemblyFactRows(harness, kinds),
182
443
  satisfies: satisfiesRows(harness),
444
+ mentions: mentionRows(harness),
445
+ includes: includeRows(harness),
446
+ nested_members: nestedMemberRows(harness, declaredAddresses(harness)),
183
447
  };
184
448
  }
185
449
  /** The SDK's pinned engine/interchange version — the JSON pipe rides it in lockstep. */
186
450
  export const SEAM_VERSION = 2;
187
451
  /**
188
- * Serialize the declaration rows to the internal versioned JSON pipe
189
- * (`20-surface.md`, "The seam"). Not a designed IR — a stable public interchange
190
- * is admitted only when its consumer lands. Deterministic: insertion-ordered keys
191
- * and a trailing newline, so a re-emit is byte-identical (law 5).
452
+ * Serialize the seam payload to the internal versioned JSON pipe — `encodeSeam`
453
+ * stamps `version`, so the caller supplies the rest of the {@link Payload}.
454
+ * Deterministic: insertion-ordered keys and a trailing newline, so a re-emit is
455
+ * byte-identical.
192
456
  */
193
- export function declarationsToJson(declarations) {
194
- return JSON.stringify({ version: SEAM_VERSION, ...declarations }, null, 2) + "\n";
457
+ export function encodeSeam(payload) {
458
+ return JSON.stringify({ version: SEAM_VERSION, ...payload }, null, 2) + "\n";
195
459
  }