@dtmd/temper 0.0.18 → 0.0.19

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.
@@ -69,13 +69,24 @@ function clauseRow(clause, kind) {
69
69
  count: predicate.key === "count"
70
70
  ? { min: predicate.args?.min ?? 0, max: predicate.args?.max ?? Number.MAX_SAFE_INTEGER }
71
71
  : undefined,
72
- target: predicate.key === "membership" ? predicate.target : undefined,
72
+ // The requirement-name column `membership`'s allowed-value target and
73
+ // `reached-from`'s closure roots share — one naming scheme for "the requirement
74
+ // whose satisfiers this clause reads" (`src/contract.rs` `predicate_from_row`).
75
+ target: predicate.key === "membership" || predicate.key === "reached-from"
76
+ ? predicate.target
77
+ : undefined,
73
78
  degree: predicate.key === "degree"
74
79
  ? {
75
80
  incoming: edgeBoundArgs(predicate.args, "incoming"),
76
81
  outgoing: edgeBoundArgs(predicate.args, "outgoing"),
77
82
  }
78
83
  : undefined,
84
+ // The by-incidence field set rides its own shared column, not the direction-only
85
+ // `degree` bound — the slot `reached-from`'s via set joins it. Copied into a
86
+ // fresh array: the predicate's set is read-only, the row's column is not.
87
+ fields: (predicate.key === "degree" || predicate.key === "reached-from") && predicate.fields
88
+ ? [...predicate.fields]
89
+ : undefined,
79
90
  gate: predicate.key === "mention-reachable" ? predicate.gate : undefined,
80
91
  value_type: predicate.key === "type" && predicate.value_type ? [...predicate.value_type] : undefined,
81
92
  shape: predicate.key === "shape" ? predicate.shape : undefined,
@@ -106,17 +117,18 @@ function clauseRow(clause, kind) {
106
117
  };
107
118
  }
108
119
  /**
109
- * The `field` column for one predicate: the field it names, or — for the two
110
- * predicates that name a *section* rather than a field an identity synthesized
111
- * from the arguments the row already carries.
120
+ * The `field` column for one predicate: the field it names, or — for the four
121
+ * predicates that name a *section*, a field *set*, or a *requirement* rather than a
122
+ * field — an identity synthesized from the arguments the row already carries.
112
123
  *
113
- * `section_contains` and `require_sections` set no `field`, and the column is what
114
- * emit stamps a clause's label from (`stamp_clause_label`, `src/drift.rs`), so
115
- * reading `Predicate.field` folds every clause of either predicate on one kind
116
- * into one label — two rows wearing one label, which admissibility refuses as a
117
- * malformed lock. Synthesizing here keeps the whole fix at the lowering: the Rust
118
- * reader reconstructs both predicates from the `section`/`sections` columns and
119
- * never from this one, so nothing round-trips through the synthesized text.
124
+ * `section_contains`, `require_sections`, `degree` and `reached-from` set no `field`,
125
+ * and the column is what emit stamps a clause's label from (`stamp_clause_label`,
126
+ * `src/drift.rs`), so reading `Predicate.field` folds every clause of one of those
127
+ * predicates on one kind into one label — two rows wearing one label, which
128
+ * admissibility refuses as a malformed lock. Synthesizing here keeps the whole fix at
129
+ * the lowering: the Rust reader reconstructs all four predicates from the
130
+ * `section`/`sections`/`fields`/`target` columns and never from this one, so nothing
131
+ * round-trips through the synthesized text.
120
132
  */
121
133
  function clauseField(predicate) {
122
134
  if (predicate.key === "section_contains") {
@@ -128,6 +140,31 @@ function clauseField(predicate) {
128
140
  // set it is and two different heading lists cannot fold to one label.
129
141
  return predicate.sections?.join("+");
130
142
  }
143
+ if (predicate.key === "degree") {
144
+ // A `degree` clause names no field of its own: its by-incidence filter is what
145
+ // distinguishes two bounds on one kind, so the filter is the segment — sorted and
146
+ // `+`-joined by the rule directly above, so two field lists spelling one set land
147
+ // one address and two different sets never fold to one. An unfiltered bound adds
148
+ // no segment: it ranges over every edge at the member, so two of them on one kind
149
+ // are a redundancy the author collapses, never a distinction an address must hold.
150
+ return predicate.fields === undefined ? undefined : [...predicate.fields].sort().join("+");
151
+ }
152
+ if (predicate.key === "reached-from") {
153
+ // A `reached-from` clause names no field either: its identity is the pair it walks.
154
+ // The roots requirement leads, and a declared via set follows after a `.` — the
155
+ // two-argument `section_contains` precedent — sorted and `+`-joined by `degree`'s
156
+ // rule directly above. So two closures rooted at one requirement over different
157
+ // arcs land two addresses instead of folding into one, and two spellings of one via
158
+ // set land one. An unfiltered closure adds no second segment: the roots are its
159
+ // whole identity, and two of them on one kind are a redundancy the author
160
+ // collapses.
161
+ const { target } = predicate;
162
+ if (target === undefined)
163
+ return undefined;
164
+ return predicate.fields === undefined
165
+ ? target
166
+ : `${target}.${[...predicate.fields].sort().join("+")}`;
167
+ }
131
168
  return predicate.field;
132
169
  }
133
170
  /** `min_len`/`max_len`/`extent`'s scalar bound off their shared `min`/`max`
@@ -267,6 +304,16 @@ function collectionAddressRow(facts) {
267
304
  entry_shape: facts.collectionAddress.entryShape,
268
305
  };
269
306
  }
307
+ /**
308
+ * Lower a kind's exhaustive leaf-set witness into the row's `leaves` column: the
309
+ * witness's own keys, in the order its author declared them (`kind.ts`'s `LeafSet`,
310
+ * decision 0053 — the type is the declaration, and TypeScript erases it, so the record is
311
+ * how the corpus hands the key set over). `undefined` for a kind declaring no witness, so
312
+ * its row omits the column.
313
+ */
314
+ function leafSetRow(leaves) {
315
+ return leaves === undefined ? undefined : Object.keys(leaves);
316
+ }
270
317
  /**
271
318
  * One kind's fact row — an `at` locus supplies `governs_root`/`governs_glob` and any
272
319
  * other locus neither (a nested-file kind's path composes from its host's unit and the
@@ -274,7 +321,8 @@ function collectionAddressRow(facts) {
274
321
  * A file locus's `commitment` class rides the same spelling, absent for the committed
275
322
  * default. `templates` names the embedded kinds the corpus admits over it, and `content`
276
323
  * lowers a declared layout (absent for a `file`-content kind). A registration kind
277
- * extends the row with its `shape` marker and `collection_address`. Advisory
324
+ * extends the row with its `shape` marker and `collection_address`. A declared leaf-set
325
+ * witness lowers to `leaves` ([`leafSetRow`]), absent for a kind declaring none. Advisory
278
326
  * `guidance`/`cite` pair rides alongside, locus-optional so an embedded kind's own
279
327
  * counsel reaches the lock the same way a nested-file kind's already does (decision
280
328
  * 0045) — callable for any locus; [`kindFactKindsInPlay`] decides which embedded kinds
@@ -294,6 +342,7 @@ function kindFactRow(facts, admissions) {
294
342
  templates: templatesFor(facts, admissions),
295
343
  content: contentRow(facts.content),
296
344
  shape: facts.shape,
345
+ leaves: leafSetRow(facts.leaves),
297
346
  collection_address: collectionAddressRow(facts),
298
347
  guidance: facts.guidance,
299
348
  cite: facts.cite,
@@ -436,13 +485,20 @@ function atLocusKindsInPlay(allKinds) {
436
485
  /**
437
486
  * The distinct kinds in play that take a kind-fact row: every non-embedded locus
438
487
  * unconditionally (a nested-file kind owns a file the engine must place, and places it
439
- * off its row, though it governs no glob to be discovered at), plus an embedded kind
440
- * only when it declares `guidance` or `cite` of its own (decision 0045) an embedded
441
- * kind with neither has nothing for the row to carry, and its members already reach the
442
- * corpus through their host's `templates` column alone (`kindsInPlay`), never the row.
488
+ * off its row, though it governs no glob to be discovered at), plus an embedded kind that
489
+ * has something of its own for a row to carry: `guidance` or `cite` (decision 0045), or a
490
+ * declared leaf set (decision 0053). The leaf set is on this list for the reason the
491
+ * column exists it teaches what a child carries *before* the surface holds a member of
492
+ * it, and an embedded kind taking no row never reaches the lock to teach it. An embedded
493
+ * kind declaring none of the three has nothing for the row to carry, and its members
494
+ * already reach the corpus through their host's `templates` column alone (`kindsInPlay`),
495
+ * never the row.
443
496
  */
444
497
  function kindFactKindsInPlay(allKinds) {
445
- return allKinds.filter((facts) => facts.locus.kind !== "embedded" || facts.guidance !== undefined || facts.cite !== undefined);
498
+ return allKinds.filter((facts) => facts.locus.kind !== "embedded" ||
499
+ facts.guidance !== undefined ||
500
+ facts.cite !== undefined ||
501
+ facts.leaves !== undefined);
446
502
  }
447
503
  /** The requirement rows — assembly `require` and every member's `requires`, one namespace. */
448
504
  function requirementRows(harness) {
@@ -611,6 +667,27 @@ function includeRows(harness) {
611
667
  }
612
668
  return rows;
613
669
  }
670
+ /**
671
+ * The `input` rows — every member's declared inputs, in member-then-authored order.
672
+ * Each carries the declaring member's `kind:name` address and the input's path resolved
673
+ * against the stating module ({@link fileURLToPath} over the input's own `moduleUrl`),
674
+ * never the workspace — exactly as an include's is; the engine reads and fingerprints
675
+ * it, and splices nothing.
676
+ *
677
+ * Unlike an include, an input pairs with no body slot, so nothing downstream depends on
678
+ * this order — the authored one is kept anyway, so a re-emit is byte-stable and the
679
+ * author reads their own declarations back.
680
+ */
681
+ function inputRows(harness) {
682
+ const rows = [];
683
+ for (const member of harness.members) {
684
+ const address = hostAddress(member.kind, member.name);
685
+ for (const declared of member.inputs) {
686
+ rows.push({ member: address, source_path: fileURLToPath(new URL(declared.path, declared.moduleUrl)) });
687
+ }
688
+ }
689
+ return rows;
690
+ }
614
691
  /**
615
692
  * One composed embedded value's key in an {@link EdgePlacements} table — its host's
616
693
  * `kind:name` address plus the value's own kind and key, the same triple the
@@ -891,6 +968,15 @@ export function compileDeclarations(harness, placements, extents) {
891
968
  clauses.push(clauseRow(clause, binding.kind.key));
892
969
  }
893
970
  }
971
+ // The root member's own clauses — the third source of a clause row, beside `expect`'s
972
+ // kind-keyed rows and a requirement's nested ones. They lower with **no** `kind`
973
+ // column: that absence at the top level is the discriminator
974
+ // `compose::root_contract_from_rows` reads, and `drift::stamp_clause_labels` addresses
975
+ // them under the `root` owner segment. Declaration order within the array, appended
976
+ // past the kind-sorted `expect` rows — a fixed position, so double emit is byte-stable.
977
+ for (const clause of harness.contract) {
978
+ clauses.push(clauseRow(clause, undefined));
979
+ }
894
980
  return {
895
981
  kinds: kindFactKindsInPlay(allKinds).map((facts) => kindFactRow(facts, admissions)),
896
982
  clauses,
@@ -899,6 +985,7 @@ export function compileDeclarations(harness, placements, extents) {
899
985
  satisfies: satisfiesRows(harness),
900
986
  mentions: mentionRows(harness),
901
987
  includes: includeRows(harness),
988
+ inputs: inputRows(harness),
902
989
  nested_members: nestedMemberRows(harness, admissions, mentionScope(harness), placements, extents),
903
990
  registrations: [...registrationRows(harness), ...tapHookRows(harness)],
904
991
  settings: settingsRows(harness),
@@ -38,10 +38,11 @@ export type ClauseRow = {
38
38
  */
39
39
  label?: string;
40
40
  /**
41
- * The kind whose contract carries the clause. `None` when this row is nested
42
- * inside a [`RequirementRow`]'s own [`clauses`](RequirementRow::clauses) — a
43
- * requirement's set-scope demand names no kind of its own; it ranges over
44
- * whatever kind the requirement's own row already carries.
41
+ * The kind whose contract carries the clause. `None` has two homes, distinguished
42
+ * by nesting rather than by a second column: inside a [`RequirementRow`]'s own
43
+ * [`clauses`](RequirementRow::clauses) it is the requirement's set-scope demand,
44
+ * ranging over whatever kind the requirement's own row already carries; at the top
45
+ * level it is the **root member's** clause, ranging over the whole governed forest.
45
46
  */
46
47
  kind?: string;
47
48
  /**
@@ -78,14 +79,28 @@ export type ClauseRow = {
78
79
  */
79
80
  count?: CountBoundRow;
80
81
  /**
81
- * The `membership` clause's target requirement name, when the predicate is
82
- * `membership`.
82
+ * The **requirement name** whose satisfiers a clause reads its second selection
83
+ * from — two owners, one naming scheme: `membership`'s allowed-set source and
84
+ * `reached-from`'s closure roots. Both ask the same question of the same column
85
+ * ("which requirement's satisfiers?"), so a second column would be the residue
86
+ * class ([`crate::contract::predicate_from_row`] decodes either from here).
83
87
  */
84
88
  target?: string;
85
89
  /**
86
90
  * The `degree` clause's in/out edge-count bound, when the predicate is `degree`.
87
91
  */
88
92
  degree?: DegreeBoundRow;
93
+ /**
94
+ * The **field set** a by-incidence clause filters its selection to — `degree`'s
95
+ * bound and `reached-from`'s via set (`specs/decisions/0056-…`). Shared rather
96
+ * than nested inside [`DegreeBoundRow`] because the filter is the *clause's*, not
97
+ * either direction's, and the two consumers name one concept: a lock spelling it
98
+ * twice would be the residue class.
99
+ *
100
+ * Absent ⇒ unfiltered, so no committed lock row moves when a clause declares no
101
+ * filter.
102
+ */
103
+ fields?: Array<string>;
89
104
  /**
90
105
  * The `mention-reachable` clause's **target-side gate field**, when the predicate
91
106
  * is `mention-reachable`. The one predicate taking two field arguments: its
@@ -1,6 +1,7 @@
1
1
  import type { AssemblyFactRow } from "./AssemblyFactRow.js";
2
2
  import type { ClauseRow } from "./ClauseRow.js";
3
3
  import type { IncludeRow } from "./IncludeRow.js";
4
+ import type { InputRow } from "./InputRow.js";
4
5
  import type { KindFactRow } from "./KindFactRow.js";
5
6
  import type { MentionRow } from "./MentionRow.js";
6
7
  import type { NestedMemberRow } from "./NestedMemberRow.js";
@@ -65,6 +66,13 @@ export type Declarations = {
65
66
  * declaration table, so a lock round-trip reads it empty).
66
67
  */
67
68
  includes: Array<IncludeRow>;
69
+ /**
70
+ * The members' declared inputs — the files their claims rest on. Seam-inbound
71
+ * like `includes`: `emit` resolves and fingerprints each as an `input` source
72
+ * dependency without moving a byte into any projection, so a lock round-trip reads
73
+ * this family empty.
74
+ */
75
+ inputs: Array<InputRow>;
68
76
  /**
69
77
  * The host members' declared embedded-member facts — captured as declaration
70
78
  * rows rather than a second copy the engine reads back off the rendered fence
@@ -0,0 +1,21 @@
1
+ /**
2
+ * One **declared input** the SDK declares — a file the member's claims rest on, and the
3
+ * member that rests on it. Its own type, not a reuse of [`IncludeRow`]: an include's
4
+ * path pairs positionally with a body slot, an input's moves nothing, and two concepts
5
+ * sharing two columns are still two concepts.
6
+ *
7
+ * A seam-inbound row only, the same posture an include takes: `emit` resolves it against
8
+ * disk ([`resolve_source_dependency`]) and lowers it to a fingerprinted `input` source
9
+ * dependency — this row itself never reaches the lock. Nothing is spliced and no byte
10
+ * moves, so the target is never decoded and a binary input is legal.
11
+ */
12
+ export type InputRow = {
13
+ /**
14
+ * The declaring member's own `kind:name` address.
15
+ */
16
+ member: string;
17
+ /**
18
+ * The input's SDK-resolved absolute path.
19
+ */
20
+ source_path: string;
21
+ };
@@ -0,0 +1,2 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+ export {};
@@ -73,6 +73,18 @@ export type KindFactRow = {
73
73
  * take, so a body-bearing kind's row stays byte-identical.
74
74
  */
75
75
  shape?: string;
76
+ /**
77
+ * The kind's **leaf set** — the leaf names a member of it carries, derived at emit
78
+ * from the member value type the SDK knows, never authored twice (decision 0053).
79
+ * Empty for a kind whose declaration carries none, the same tolerant round-trip
80
+ * [`registration`](KindFactRow::registration) takes, so a committed lock written
81
+ * before the column existed re-reads byte-identically.
82
+ *
83
+ * The declaration a read verb renders where the surface holds no member yet: the
84
+ * type is the declaration, so a present set outranks the union of what members
85
+ * carry today.
86
+ */
87
+ leaves?: Array<string>;
76
88
  /**
77
89
  * The declared **collection address** — for a registration member surfacing inside a
78
90
  * host manifest, which manifest and which key path it keys at. Absent for a
@@ -15,6 +15,7 @@ export type { FeatureValue } from "./FeatureValue.js";
15
15
  export type { Features } from "./Features.js";
16
16
  export type { FencedBlock } from "./FencedBlock.js";
17
17
  export type { IncludeRow } from "./IncludeRow.js";
18
+ export type { InputRow } from "./InputRow.js";
18
19
  export type { KindFactRow } from "./KindFactRow.js";
19
20
  export type { LayoutRegionRow } from "./LayoutRegionRow.js";
20
21
  export type { LayoutRow } from "./LayoutRow.js";
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * temper's authoring face — the six-noun core as a typed module library.
3
3
  * A harness author imports plain nouns — `harness()`, the generic `kind`
4
- * constructor, the clause and requirement constructors, `needs`, and the three
4
+ * constructor, the clause and requirement constructors, `needs`, and the four
5
5
  * prose constructors — and composes members as typed values. `emit` compiles the
6
6
  * whole into the declaration rows and the projected members' erased payload —
7
7
  * the JSON pipe printed to stdout; the engine is the sole compiler of every
@@ -13,15 +13,16 @@
13
13
  * the `./claude-code` subpath, never here.
14
14
  */
15
15
  export type { Blocks, File, Include, Mention, Mentionable, Prose, Reference, Text } from "./prose.js";
16
- export { blocks, file, include, mentionOf, renderText, text } from "./prose.js";
16
+ export { blocks, file, include, mentionOf, renderText, span, text } from "./prose.js";
17
17
  export type { Capability } from "./needs.js";
18
18
  export { bash, capability, permissionUnion } from "./needs.js";
19
19
  export type { Charset, Clause, ExtentUnit, Predicate, Requirement, Severity, Verifier } from "./contract.js";
20
- export { allowedChars, clause, closedKeys, count, degree, deny, enumOf, extent, forbiddenKeys, formatPlacesEdges, globValid, maxLen, membership, mentionReachable, minLen, mustDefine, nameMatchesDir, optional, range, required, requireSections, requirement, script, sectionContains, shape, telemetry, type, unique, uniqueName, when, } from "./contract.js";
21
- export type { CollectionAddress, EdgeField, EdgeTargetFacts, EmbeddedMemberCollectionEntry, EmbeddedMemberValue, Format, KindDefinition, KindFacts, KindOptions, KindRelocation, Layout, LayoutRegion, Locus, Member, MemberInit, Registration, ResolvedEmbeddedMemberCollectionEntry, ResolvedEmbeddedMemberValue, Shape, Template, UnitShape, } from "./kind.js";
20
+ export { allowedChars, clause, closedKeys, count, degree, deny, enumOf, extent, forbiddenKeys, formatPlacesEdges, fresh, globValid, locusDeclared, maxLen, membership, mentionReachable, minLen, mustDefine, nameMatchesDir, optional, range, reachable, reachedFrom, required, requireSections, requirement, script, sectionContains, shape, telemetry, type, unique, uniqueName, when, } from "./contract.js";
21
+ export type { CollectionAddress, EdgeField, EdgeTargetFacts, EmbeddedMemberCollectionEntry, EmbeddedMemberValue, Format, Input, KindDefinition, KindFacts, KindOptions, KindRelocation, Layout, LayoutRegion, LeafSet, Locus, Member, MemberInit, Registration, Residue, ResolvedEmbeddedMemberCollectionEntry, ResolvedEmbeddedMemberValue, Shape, Template, UnitShape, } from "./kind.js";
22
22
  export { embeddedMemberValue, kind, relocate } from "./kind.js";
23
+ export { input } from "./kind.js";
23
24
  export type { Admission, EnforcementMode, ExpectBinding, Harness } from "./assembly.js";
24
- export { harness } from "./assembly.js";
25
+ export { harness, rootDefaultContract } from "./assembly.js";
25
26
  export type { EdgeTarget, EmbeddedTarget, EmitResult, RegistrationFact, ResolveOptions, SettingsResidue, } from "./emit.js";
26
27
  export { emit } from "./emit.js";
27
28
  export type { Dial, DialEntry } from "./dial.js";
package/dist/src/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * temper's authoring face — the six-noun core as a typed module library.
3
3
  * A harness author imports plain nouns — `harness()`, the generic `kind`
4
- * constructor, the clause and requirement constructors, `needs`, and the three
4
+ * constructor, the clause and requirement constructors, `needs`, and the four
5
5
  * prose constructors — and composes members as typed values. `emit` compiles the
6
6
  * whole into the declaration rows and the projected members' erased payload —
7
7
  * the JSON pipe printed to stdout; the engine is the sole compiler of every
@@ -12,10 +12,14 @@
12
12
  * The first-party Claude Code provider face — the built-in kinds — lives at
13
13
  * the `./claude-code` subpath, never here.
14
14
  */
15
- export { blocks, file, include, mentionOf, renderText, text } from "./prose.js";
15
+ export { blocks, file, include, mentionOf, renderText, span, text } from "./prose.js";
16
16
  export { bash, capability, permissionUnion } from "./needs.js";
17
- export { allowedChars, clause, closedKeys, count, degree, deny, enumOf, extent, forbiddenKeys, formatPlacesEdges, globValid, maxLen, membership, mentionReachable, minLen, mustDefine, nameMatchesDir, optional, range, required, requireSections, requirement, script, sectionContains, shape, telemetry, type, unique, uniqueName, when, } from "./contract.js";
17
+ export { allowedChars, clause, closedKeys, count, degree, deny, enumOf, extent, forbiddenKeys, formatPlacesEdges, fresh, globValid, locusDeclared, maxLen, membership, mentionReachable, minLen, mustDefine, nameMatchesDir, optional, range, reachable, reachedFrom, required, requireSections, requirement, script, sectionContains, shape, telemetry, type, unique, uniqueName, when, } from "./contract.js";
18
18
  export { embeddedMemberValue, kind, relocate } from "./kind.js";
19
- export { harness } from "./assembly.js";
19
+ // Inputs the files a member's claims rest on, fingerprinted by the lock and moved
20
+ // nowhere. A member-grain declaration, not a word in a body, so it rides here rather
21
+ // than on the prose line.
22
+ export { input } from "./kind.js";
23
+ export { harness, rootDefaultContract } from "./assembly.js";
20
24
  export { emit } from "./emit.js";
21
25
  export { dial, dialDefaultContract } from "./dial.js";
@@ -137,6 +137,24 @@ export interface Layout {
137
137
  * {@link Layout}.
138
138
  */
139
139
  export type Shape = "fields";
140
+ /**
141
+ * A kind's **leaf-set witness** — the leaf names a member of the kind carries, handed
142
+ * over by the kind's own typed surface `T` (decision 0053). The type is the declaration:
143
+ * nothing here is a second schema to keep in step with `T`, because the record's keys
144
+ * *are* `keyof T`. It exists at all because TypeScript erases `T` at the seam, so the
145
+ * set the compiler knows has to reach emit as a runtime value.
146
+ *
147
+ * The record is **exhaustive** — `-?` strips optionality, so omitting one key of `T` is a
148
+ * compile error and a key that is not `T`'s own is one too. That binding is what keeps
149
+ * the witness from degrading into the free-hand leaf schema the decision rejected: a
150
+ * partial set is unwritable rather than merely discouraged.
151
+ *
152
+ * Key order is the order the lowered row carries (`declarations.ts`), so a kind's leaves
153
+ * read in the order its author declared them.
154
+ */
155
+ export type LeafSet<T extends object> = {
156
+ readonly [K in keyof T]-?: true;
157
+ };
140
158
  /**
141
159
  * A registration member's **collection address** — where inside a host manifest its
142
160
  * registration surfaces: which `manifest` (`settings.json`, `.mcp.json`) and which
@@ -168,8 +186,15 @@ export interface Template {
168
186
  /** Where a file child's unit sits, relative to the parent's unit; absent for an embedded layer. */
169
187
  readonly path?: string;
170
188
  }
171
- /** The seven facts of a kind's runtime residue. */
172
- export type KindFacts = {
189
+ /**
190
+ * The seven facts of a kind's runtime residue, plus the derived {@link LeafSet} witness
191
+ * its typed surface hands over. `T` is that surface — the interface the kind's
192
+ * constructor is generic over. It defaults to an erased `Record<string, unknown>`, so
193
+ * every signature that only ever *reads* a facts value (`declarations.ts`'s lowering,
194
+ * {@link Member}, {@link KindDefinition}) names `KindFacts` bare and no call site
195
+ * re-spells a type argument.
196
+ */
197
+ export type KindFacts<T extends object = Record<string, unknown>> = {
173
198
  /** Fact 1, label — the compiled debug label findings speak; the kind's name. */
174
199
  readonly name: string;
175
200
  /** The declared provider authority, when the kind qualifies by one. */
@@ -225,6 +250,11 @@ export type KindFacts = {
225
250
  /** Fact 7, template — one {@link Template} per inner layer of nested members the kind
226
251
  * hosts; absent for a kind that nests nothing. */
227
252
  readonly templates?: readonly Template[];
253
+ /** The kind's **leaf set**, witnessed exhaustively over its own typed surface and
254
+ * lowered to the row's `leaves` column at emit ({@link LeafSet}, decision 0053) —
255
+ * what a read verb renders where the surface holds no member of the kind yet.
256
+ * Absent for a kind whose constructor declares no witness. */
257
+ readonly leaves?: LeafSet<T>;
228
258
  /** Advisory authoring counsel for the kind as a whole — teaching at authoring time via
229
259
  * `schema` hover or `explain`, carrying no predicate or severity (decision 0045). */
230
260
  readonly guidance?: string;
@@ -283,6 +313,11 @@ export type KindFacts = {
283
313
  /** Fact 7, template — one {@link Template} per inner layer of nested members the kind
284
314
  * hosts; absent for a kind that nests nothing. */
285
315
  readonly templates?: readonly Template[];
316
+ /** The kind's **leaf set**, witnessed exhaustively over its own typed surface and
317
+ * lowered to the row's `leaves` column at emit ({@link LeafSet}, decision 0053) —
318
+ * what a read verb renders where the surface holds no member of the kind yet.
319
+ * Absent for a kind whose constructor declares no witness. */
320
+ readonly leaves?: LeafSet<T>;
286
321
  /** Advisory authoring counsel for the kind as a whole — teaching at authoring time via
287
322
  * `schema` hover or `explain`, carrying no predicate or severity (decision 0045). */
288
323
  readonly guidance?: string;
@@ -341,6 +376,11 @@ export type KindFacts = {
341
376
  /** Fact 7, template — one {@link Template} per inner layer of nested members the kind
342
377
  * hosts; absent for a kind that nests nothing. */
343
378
  readonly templates?: readonly Template[];
379
+ /** The kind's **leaf set**, witnessed exhaustively over its own typed surface and
380
+ * lowered to the row's `leaves` column at emit ({@link LeafSet}, decision 0053) —
381
+ * what a read verb renders where the surface holds no member of the kind yet.
382
+ * Absent for a kind whose constructor declares no witness. */
383
+ readonly leaves?: LeafSet<T>;
344
384
  /** Advisory authoring counsel for the kind as a whole — teaching at authoring time via
345
385
  * `schema` hover or `explain`, carrying no predicate or severity (decision 0045). */
346
386
  readonly guidance?: string;
@@ -348,6 +388,31 @@ export type KindFacts = {
348
388
  * carried as data. */
349
389
  readonly cite?: string;
350
390
  };
391
+ /**
392
+ * A **declared input** — a file the member's claims rest on, fingerprinted by the lock
393
+ * and moved nowhere (`specs/model/authoring.md`, "The SDK"). The path resolves against
394
+ * the stating module ({@link moduleUrl}), never the workspace — the same anchor
395
+ * `include()` and a `file()` body take.
396
+ *
397
+ * It lives here rather than beside the prose references it rhymes with: an input rides
398
+ * no text span and pairs with no body slot, so it is a member-grain framework key, not a
399
+ * word in a body. When the input's bytes move, the member's own freshness finding routes
400
+ * the author to re-verify the claims *before* re-emitting — temper never judges whether
401
+ * the claim still holds, only names the place to look.
402
+ */
403
+ export interface Input {
404
+ readonly kind: "input";
405
+ /** Path to the input, resolved against {@link moduleUrl}. */
406
+ readonly path: string;
407
+ /** The declaring module's own `import.meta.url` — what {@link path} resolves against. */
408
+ readonly moduleUrl: string;
409
+ }
410
+ /**
411
+ * Declare a file a member's claims rest on — the lock fingerprints it and no byte
412
+ * reaches the projection. The path resolves against the stating module, so the call is
413
+ * `input(import.meta.url, "./schema.json")`.
414
+ */
415
+ export declare function input(moduleUrl: string, path: string): Input;
351
416
  /**
352
417
  * One authored member — a typed value in the library. Kind identity travels by
353
418
  * import (`facts`), never by string; the
@@ -373,8 +438,27 @@ export interface Member {
373
438
  readonly requires: Readonly<Record<string, Requirement>>;
374
439
  /** The capabilities the member's behavior uses — the permission union's source. */
375
440
  readonly needs: readonly Capability[];
441
+ /** The files this member's claims rest on — fingerprinted, never moved. */
442
+ readonly inputs: readonly Input[];
376
443
  }
377
- /** The init a kind constructor takes — the framework keys plus the kind's typed fields `T`. */
444
+ /**
445
+ * The bag a surface `T` declares its {@link RESIDUE_KEY} channel as — the keys `T` does
446
+ * *not* type, and only those. The two halves partition the format's key space, so a key
447
+ * in both is a mis-spelling rather than an override: open by key, then narrowed against
448
+ * `keyof T`, which leaves a typed key's slot here holding nothing a value can fill.
449
+ *
450
+ * The narrowing is the front door, never the whole gate — a bag the program computed
451
+ * arrives as a bare `Record<string, unknown>` and types fine, so the constructor refuses
452
+ * the collision again over the keys it actually holds ({@link orderedFields}).
453
+ */
454
+ export type Residue<T> = Readonly<Record<string, unknown>> & {
455
+ readonly [K in keyof T]?: never;
456
+ };
457
+ /**
458
+ * The init a kind constructor takes — the framework keys plus the kind's typed fields `T`.
459
+ * {@link RESIDUE_KEY} is a framework key too, deliberately not spelled below: a kind opts
460
+ * into that channel through its own surface `T`.
461
+ */
378
462
  export type MemberInit<T> = {
379
463
  readonly name: string;
380
464
  /** The host member this member's unit composes under — a nested-file child's, and only its. */
@@ -383,6 +467,12 @@ export type MemberInit<T> = {
383
467
  readonly satisfies?: readonly string[];
384
468
  readonly requires?: Readonly<Record<string, Requirement>>;
385
469
  readonly needs?: readonly Capability[];
470
+ /**
471
+ * The files this member's claims rest on ({@link input}) — fingerprinted by the lock,
472
+ * with no byte reaching the projection. The surface binds a *member*: an embedded
473
+ * value takes no `MemberInit`, and its claims are its host's.
474
+ */
475
+ readonly inputs?: readonly Input[];
386
476
  } & T;
387
477
  /**
388
478
  * A kind — a callable constructor carrying its seven facts. Calling it builds a
@@ -414,8 +504,13 @@ export interface KindOptions {
414
504
  * into emit is the seven facts plus flat field data. `options.render`, when given,
415
505
  * rides alongside `facts`/`key` on the returned constructor — never on the member
416
506
  * it builds, since it is erased before a member reaches emit.
507
+ *
508
+ * The facts are typed over the same `T` the constructor is, which is what binds a
509
+ * declared {@link LeafSet} witness to `keyof T`: the leaf set is the kind's own surface,
510
+ * checked here at the keystroke, and its lowering to the row is the one place it is
511
+ * spelled again (`declarations.ts`).
417
512
  */
418
- export declare function kind<T extends object>(facts: KindFacts, options?: KindOptions): KindDefinition<T>;
513
+ export declare function kind<T extends object>(facts: KindFacts<T>, options?: KindOptions): KindDefinition<T>;
419
514
  /**
420
515
  * A **relocation delta** — the facts a relocated built-in kind diverges from its base
421
516
  * on. Two faces, either or both:
@@ -428,9 +523,9 @@ export declare function kind<T extends object>(facts: KindFacts, options?: KindO
428
523
  * members are found, the one fact the engine's own overlay exists to apply
429
524
  * (`src/compose.rs`'s `overlay_builtin_kind`).
430
525
  *
431
- * Every other fact — format, unit shape, registration, content, templates rides
432
- * through unchanged, which is exactly what makes the emitted row still read as a
433
- * relocation rather than a name collision on the reading side.
526
+ * Every other fact — format, unit shape, registration, content, templates, the declared
527
+ * leaf set — rides through unchanged, which is exactly what makes the emitted row still
528
+ * read as a relocation rather than a name collision on the reading side.
434
529
  */
435
530
  export interface KindRelocation<T> {
436
531
  /** The edge fields this relocation adds, each over a field of the kind's own surface. */