@dtmd/temper 0.0.17 → 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.
- package/dist/src/assembly.d.ts +40 -0
- package/dist/src/assembly.js +43 -0
- package/dist/src/builtins.d.ts +110 -4
- package/dist/src/builtins.js +64 -3
- package/dist/src/claude-code.d.ts +2 -2
- package/dist/src/claude-code.js +1 -1
- package/dist/src/contract.d.ts +96 -3
- package/dist/src/contract.js +89 -2
- package/dist/src/declarations.d.ts +3 -3
- package/dist/src/declarations.js +228 -24
- package/dist/src/emit.d.ts +28 -6
- package/dist/src/emit.js +102 -29
- package/dist/src/generated/ClauseRow.d.ts +29 -7
- package/dist/src/generated/Declarations.d.ts +8 -0
- package/dist/src/generated/InputRow.d.ts +21 -0
- package/dist/src/generated/InputRow.js +2 -0
- package/dist/src/generated/KindFactRow.d.ts +12 -0
- package/dist/src/generated/index.d.ts +1 -0
- package/dist/src/index.d.ts +8 -7
- package/dist/src/index.js +9 -5
- package/dist/src/kind.d.ts +314 -15
- package/dist/src/kind.js +184 -9
- package/dist/src/member-address.d.ts +102 -0
- package/dist/src/member-address.js +114 -0
- package/dist/src/prose.d.ts +29 -12
- package/dist/src/prose.js +50 -19
- package/package.json +3 -3
package/dist/src/kind.d.ts
CHANGED
|
@@ -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,16 +186,38 @@ 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
|
-
/**
|
|
172
|
-
|
|
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. */
|
|
176
201
|
readonly provider?: string;
|
|
202
|
+
/**
|
|
203
|
+
* The built-in kind this facts value **relocates** — set only by {@link relocate},
|
|
204
|
+
* from the base kind's own name, never authored. It is the authoring layer's
|
|
205
|
+
* *provenance* fact: the value was derived from the imported built-in, so a
|
|
206
|
+
* second same-named kind in play is a sanctioned relocation rather than a name
|
|
207
|
+
* collision. It never reaches a kind-fact row — the lock reader holds no base
|
|
208
|
+
* value to compare against and re-decides *structurally* instead
|
|
209
|
+
* (`src/compose.rs`'s `row_relocates_builtin`).
|
|
210
|
+
*/
|
|
211
|
+
readonly relocates?: string;
|
|
177
212
|
/** Fact 2, locus — where members live, and for a file locus whether their documents
|
|
178
213
|
* are committed: a `local` commitment class declares the kind reviewed and its
|
|
179
214
|
* members' documents not. */
|
|
180
|
-
readonly locus:
|
|
215
|
+
readonly locus: {
|
|
216
|
+
readonly kind: "at";
|
|
217
|
+
readonly root: string;
|
|
218
|
+
readonly glob: string;
|
|
219
|
+
readonly commitment?: "local";
|
|
220
|
+
};
|
|
181
221
|
/** Fact 3a, projection — the artifact format; omitted for a kind that declares none. */
|
|
182
222
|
readonly format?: Format;
|
|
183
223
|
/** Fact 3b, projection — the on-disk unit shape. */
|
|
@@ -186,15 +226,78 @@ export interface KindFacts {
|
|
|
186
226
|
* the world reaches a member (never rivals — a member is live if any one is). */
|
|
187
227
|
readonly registration: readonly Registration[];
|
|
188
228
|
/**
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
229
|
+
* The frontmatter key the member's name writes under. For `unitShape:
|
|
230
|
+
* "named-field"` this is the id **source** — the declared field a member's
|
|
231
|
+
* identity is read from (an agent's `name`), never the filename or directory.
|
|
232
|
+
* For `"directory"` it is a projection-order detail only (a skill's `name`
|
|
233
|
+
* still writes into frontmatter, but identity is the directory name); absent
|
|
234
|
+
* when identity is the file stem and no field carries it (a rule), or the
|
|
235
|
+
* starred directory segment (`"starred-segment"`) — both path-derived, never a
|
|
236
|
+
* field.
|
|
237
|
+
*/
|
|
238
|
+
readonly identityField?: string;
|
|
239
|
+
/** Fact 5, edge fields — the kind's fields that are references to other members. */
|
|
240
|
+
readonly edgeFields?: readonly EdgeField[];
|
|
241
|
+
/** Fact 6, content — a declared {@link Layout} over the body's heading tree; absent
|
|
242
|
+
* leaves the kind `file`-content (one verbatim prose body, the default). */
|
|
243
|
+
readonly content?: Layout;
|
|
244
|
+
/** Fact 6b, content — the fields-only body shape (`"fields"`, no body slot); absent
|
|
245
|
+
* leaves the kind body-bearing (`file` or a {@link Layout}). */
|
|
246
|
+
readonly shape?: Shape;
|
|
247
|
+
/** The registration member's {@link CollectionAddress} — which manifest and key path
|
|
248
|
+
* its registration surfaces at; absent for a kind that owns its own file locus. */
|
|
249
|
+
readonly collectionAddress?: CollectionAddress;
|
|
250
|
+
/** Fact 7, template — one {@link Template} per inner layer of nested members the kind
|
|
251
|
+
* hosts; absent for a kind that nests nothing. */
|
|
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>;
|
|
258
|
+
/** Advisory authoring counsel for the kind as a whole — teaching at authoring time via
|
|
259
|
+
* `schema` hover or `explain`, carrying no predicate or severity (decision 0045). */
|
|
260
|
+
readonly guidance?: string;
|
|
261
|
+
/** External-fact source backing the guidance — a doc URL plus retrieved date,
|
|
262
|
+
* carried as data. */
|
|
263
|
+
readonly cite?: string;
|
|
264
|
+
} | {
|
|
265
|
+
/** Fact 1, label — the compiled debug label findings speak; the kind's name. */
|
|
266
|
+
readonly name: string;
|
|
267
|
+
/** The declared provider authority, when the kind qualifies by one. */
|
|
268
|
+
readonly provider?: string;
|
|
269
|
+
/**
|
|
270
|
+
* The built-in kind this facts value **relocates** — set only by {@link relocate},
|
|
271
|
+
* from the base kind's own name, never authored. It is the authoring layer's
|
|
272
|
+
* *provenance* fact: the value was derived from the imported built-in, so a
|
|
273
|
+
* second same-named kind in play is a sanctioned relocation rather than a name
|
|
274
|
+
* collision. It never reaches a kind-fact row — the lock reader holds no base
|
|
275
|
+
* value to compare against and re-decides *structurally* instead
|
|
276
|
+
* (`src/compose.rs`'s `row_relocates_builtin`).
|
|
277
|
+
*/
|
|
278
|
+
readonly relocates?: string;
|
|
279
|
+
/** Fact 2, locus — where members live, and for a file locus whether their documents
|
|
280
|
+
* are committed: a `local` commitment class declares the kind reviewed and its
|
|
281
|
+
* members' documents not. */
|
|
282
|
+
readonly locus: {
|
|
283
|
+
readonly kind: "embedded";
|
|
284
|
+
};
|
|
285
|
+
/** Fact 3a, projection — the artifact format; omitted for a kind that declares none. */
|
|
286
|
+
readonly format?: Format;
|
|
287
|
+
/** Fact 3b, projection — the on-disk unit shape. */
|
|
288
|
+
readonly unitShape: UnitShape;
|
|
289
|
+
/** Fact 4, registration — embedded members register nothing. */
|
|
290
|
+
readonly registration: readonly [];
|
|
291
|
+
/**
|
|
292
|
+
* The frontmatter key the member's name writes under. For `unitShape:
|
|
293
|
+
* "named-field"` this is the id **source** — the declared field a member's
|
|
294
|
+
* identity is read from (an agent's `name`), never the filename or directory.
|
|
295
|
+
* For `"directory"` it is a projection-order detail only (a skill's `name`
|
|
296
|
+
* still writes into frontmatter, but identity is the directory name); absent
|
|
297
|
+
* when identity is the file stem and no field carries it (a rule), or the
|
|
298
|
+
* starred directory segment (`"starred-segment"`) — both path-derived, never a
|
|
299
|
+
* field.
|
|
300
|
+
*/
|
|
198
301
|
readonly identityField?: string;
|
|
199
302
|
/** Fact 5, edge fields — the kind's fields that are references to other members. */
|
|
200
303
|
readonly edgeFields?: readonly EdgeField[];
|
|
@@ -210,13 +313,106 @@ export interface KindFacts {
|
|
|
210
313
|
/** Fact 7, template — one {@link Template} per inner layer of nested members the kind
|
|
211
314
|
* hosts; absent for a kind that nests nothing. */
|
|
212
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>;
|
|
213
321
|
/** Advisory authoring counsel for the kind as a whole — teaching at authoring time via
|
|
214
322
|
* `schema` hover or `explain`, carrying no predicate or severity (decision 0045). */
|
|
215
323
|
readonly guidance?: string;
|
|
216
324
|
/** External-fact source backing the guidance — a doc URL plus retrieved date,
|
|
217
325
|
* carried as data. */
|
|
218
326
|
readonly cite?: string;
|
|
327
|
+
} | {
|
|
328
|
+
/** Fact 1, label — the compiled debug label findings speak; the kind's name. */
|
|
329
|
+
readonly name: string;
|
|
330
|
+
/** The declared provider authority, when the kind qualifies by one. */
|
|
331
|
+
readonly provider?: string;
|
|
332
|
+
/**
|
|
333
|
+
* The built-in kind this facts value **relocates** — set only by {@link relocate},
|
|
334
|
+
* from the base kind's own name, never authored. It is the authoring layer's
|
|
335
|
+
* *provenance* fact: the value was derived from the imported built-in, so a
|
|
336
|
+
* second same-named kind in play is a sanctioned relocation rather than a name
|
|
337
|
+
* collision. It never reaches a kind-fact row — the lock reader holds no base
|
|
338
|
+
* value to compare against and re-decides *structurally* instead
|
|
339
|
+
* (`src/compose.rs`'s `row_relocates_builtin`).
|
|
340
|
+
*/
|
|
341
|
+
readonly relocates?: string;
|
|
342
|
+
/** Fact 2, locus — where members live, and for a file locus whether their documents
|
|
343
|
+
* are committed: a `local` commitment class declares the kind reviewed and its
|
|
344
|
+
* members' documents not. */
|
|
345
|
+
readonly locus: {
|
|
346
|
+
readonly kind: "nested-file";
|
|
347
|
+
};
|
|
348
|
+
/** Fact 3a, projection — the artifact format; omitted for a kind that declares none. */
|
|
349
|
+
readonly format?: Format;
|
|
350
|
+
/** Fact 3b, projection — the on-disk unit shape. */
|
|
351
|
+
readonly unitShape: UnitShape;
|
|
352
|
+
/** Fact 4, registration — nested-file members register nothing. */
|
|
353
|
+
readonly registration: readonly [];
|
|
354
|
+
/**
|
|
355
|
+
* The frontmatter key the member's name writes under. For `unitShape:
|
|
356
|
+
* "named-field"` this is the id **source** — the declared field a member's
|
|
357
|
+
* identity is read from (an agent's `name`), never the filename or directory.
|
|
358
|
+
* For `"directory"` it is a projection-order detail only (a skill's `name`
|
|
359
|
+
* still writes into frontmatter, but identity is the directory name); absent
|
|
360
|
+
* when identity is the file stem and no field carries it (a rule), or the
|
|
361
|
+
* starred directory segment (`"starred-segment"`) — both path-derived, never a
|
|
362
|
+
* field.
|
|
363
|
+
*/
|
|
364
|
+
readonly identityField?: string;
|
|
365
|
+
/** Fact 5, edge fields — the kind's fields that are references to other members. */
|
|
366
|
+
readonly edgeFields?: readonly EdgeField[];
|
|
367
|
+
/** Fact 6, content — a declared {@link Layout} over the body's heading tree; absent
|
|
368
|
+
* leaves the kind `file`-content (one verbatim prose body, the default). */
|
|
369
|
+
readonly content?: Layout;
|
|
370
|
+
/** Fact 6b, content — the fields-only body shape (`"fields"`, no body slot); absent
|
|
371
|
+
* leaves the kind body-bearing (`file` or a {@link Layout}). */
|
|
372
|
+
readonly shape?: Shape;
|
|
373
|
+
/** The registration member's {@link CollectionAddress} — which manifest and key path
|
|
374
|
+
* its registration surfaces at; absent for a kind that owns its own file locus. */
|
|
375
|
+
readonly collectionAddress?: CollectionAddress;
|
|
376
|
+
/** Fact 7, template — one {@link Template} per inner layer of nested members the kind
|
|
377
|
+
* hosts; absent for a kind that nests nothing. */
|
|
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>;
|
|
384
|
+
/** Advisory authoring counsel for the kind as a whole — teaching at authoring time via
|
|
385
|
+
* `schema` hover or `explain`, carrying no predicate or severity (decision 0045). */
|
|
386
|
+
readonly guidance?: string;
|
|
387
|
+
/** External-fact source backing the guidance — a doc URL plus retrieved date,
|
|
388
|
+
* carried as data. */
|
|
389
|
+
readonly cite?: string;
|
|
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;
|
|
219
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;
|
|
220
416
|
/**
|
|
221
417
|
* One authored member — a typed value in the library. Kind identity travels by
|
|
222
418
|
* import (`facts`), never by string; the
|
|
@@ -242,8 +438,27 @@ export interface Member {
|
|
|
242
438
|
readonly requires: Readonly<Record<string, Requirement>>;
|
|
243
439
|
/** The capabilities the member's behavior uses — the permission union's source. */
|
|
244
440
|
readonly needs: readonly Capability[];
|
|
441
|
+
/** The files this member's claims rest on — fingerprinted, never moved. */
|
|
442
|
+
readonly inputs: readonly Input[];
|
|
245
443
|
}
|
|
246
|
-
/**
|
|
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
|
+
*/
|
|
247
462
|
export type MemberInit<T> = {
|
|
248
463
|
readonly name: string;
|
|
249
464
|
/** The host member this member's unit composes under — a nested-file child's, and only its. */
|
|
@@ -252,6 +467,12 @@ export type MemberInit<T> = {
|
|
|
252
467
|
readonly satisfies?: readonly string[];
|
|
253
468
|
readonly requires?: Readonly<Record<string, Requirement>>;
|
|
254
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[];
|
|
255
476
|
} & T;
|
|
256
477
|
/**
|
|
257
478
|
* A kind — a callable constructor carrying its seven facts. Calling it builds a
|
|
@@ -283,8 +504,80 @@ export interface KindOptions {
|
|
|
283
504
|
* into emit is the seven facts plus flat field data. `options.render`, when given,
|
|
284
505
|
* rides alongside `facts`/`key` on the returned constructor — never on the member
|
|
285
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`).
|
|
512
|
+
*/
|
|
513
|
+
export declare function kind<T extends object>(facts: KindFacts<T>, options?: KindOptions): KindDefinition<T>;
|
|
514
|
+
/**
|
|
515
|
+
* A **relocation delta** — the facts a relocated built-in kind diverges from its base
|
|
516
|
+
* on. Two faces, either or both:
|
|
517
|
+
*
|
|
518
|
+
* - `edgeFields`, *added* to whatever the base already declares (never replacing them,
|
|
519
|
+
* which would drop a shipped kind's own edges silently). Each added field names a key
|
|
520
|
+
* of the relocated kind's typed surface `T`, so an edge can never be declared over a
|
|
521
|
+
* field the kind does not carry.
|
|
522
|
+
* - `governs`, *replacing* the base's `at` locus root and glob — moving where the kind's
|
|
523
|
+
* members are found, the one fact the engine's own overlay exists to apply
|
|
524
|
+
* (`src/compose.rs`'s `overlay_builtin_kind`).
|
|
525
|
+
*
|
|
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.
|
|
529
|
+
*/
|
|
530
|
+
export interface KindRelocation<T> {
|
|
531
|
+
/** The edge fields this relocation adds, each over a field of the kind's own surface. */
|
|
532
|
+
readonly edgeFields?: readonly {
|
|
533
|
+
readonly field: keyof T & string;
|
|
534
|
+
readonly to: readonly [string, ...string[]];
|
|
535
|
+
}[];
|
|
536
|
+
/**
|
|
537
|
+
* The locus this relocation moves the kind's members to — root and glob, the two
|
|
538
|
+
* columns the lock reader's overlay writes back. A file locus's `commitment` class is
|
|
539
|
+
* deliberately absent: the overlay writes `Governs { root, glob }` and nothing else, so
|
|
540
|
+
* a `commitment` delta would author a fact the reader drops in silence — a built-in's
|
|
541
|
+
* commitment class stays the built-in's.
|
|
542
|
+
*/
|
|
543
|
+
readonly governs?: {
|
|
544
|
+
readonly root: string;
|
|
545
|
+
readonly glob: string;
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* **Relocate** a built-in kind: the sanctioned way an adopting corpus moves a kind it
|
|
550
|
+
* does not own to its own root, adds an edge field to it, or both. Returns a fresh
|
|
551
|
+
* constructor over the widened typed surface `T` (the base's fields plus the added edge
|
|
552
|
+
* fields, spelled by the caller as one interface), carrying the base's facts with
|
|
553
|
+
* `delta`'s edge fields appended, `delta`'s `governs` in place of the base's locus root
|
|
554
|
+
* and glob, and the base's own {@link KindDefinition.render} hook preserved. Ownership,
|
|
555
|
+
* not privilege — a relocated built-in is an ordinary kind value from here on, and its
|
|
556
|
+
* added edge reaches the lock as an assembly `edge` row keyed by `from`, exactly as any
|
|
557
|
+
* kind's does (`declarations.ts`), never as a column on a kind-fact row.
|
|
558
|
+
*
|
|
559
|
+
* A moved locus, by contrast, *is* a kind-fact row column pair: the emitted row carries
|
|
560
|
+
* the delta's `governs_root`/`governs_glob` while `format`, `unit_shape` and
|
|
561
|
+
* `registration` stay the base's — which is precisely the three-fact test the lock
|
|
562
|
+
* reader applies before overlaying the row onto its compiled-in built-in
|
|
563
|
+
* (`src/compose.rs`'s `row_relocates_builtin`), so the engine reads members at the new
|
|
564
|
+
* root rather than treating the row as a colliding kind.
|
|
565
|
+
*
|
|
566
|
+
* The produced facts carry `relocates`, naming the base — the marker that tells a
|
|
567
|
+
* legitimate relocation from a genuine name collision when two same-named kinds are in
|
|
568
|
+
* play. Identity travels by import: the base is the imported built-in value, so the
|
|
569
|
+
* provenance is proven here rather than inferred downstream.
|
|
570
|
+
*
|
|
571
|
+
* # Throws
|
|
572
|
+
* If an added edge field re-declares one the base already carries, or one another
|
|
573
|
+
* entry of the same delta already added — two `edge` rows over one `<from, field>`
|
|
574
|
+
* cross-wire the graph instead of declaring one relationship. If a `governs` delta
|
|
575
|
+
* names a base whose locus is not `at` ({@link relocatedLocus}). And if the delta
|
|
576
|
+
* declares neither face, which would mint a marker-bearing clone of the base — a second
|
|
577
|
+
* same-named kind diverging on nothing, which is a name collision spelled as a
|
|
578
|
+
* relocation.
|
|
286
579
|
*/
|
|
287
|
-
export declare function
|
|
580
|
+
export declare function relocate<T extends object>(base: KindDefinition<any>, delta: KindRelocation<T>): KindDefinition<T>;
|
|
288
581
|
/**
|
|
289
582
|
* One entry in a sibling collection: its own key plus its leaf fields
|
|
290
583
|
* (`rejected."baked-projection"`) — an ordered list element, never a positional
|
|
@@ -390,8 +683,14 @@ export interface ResolvedEmbeddedMemberValue {
|
|
|
390
683
|
* composed value's shape. A bare string names a kind whose facts are out of reach, so
|
|
391
684
|
* such a value renders with no target facts.
|
|
392
685
|
*/
|
|
686
|
+
export declare function embeddedMemberValue<T extends object>(init: {
|
|
687
|
+
kind: KindDefinition<T>;
|
|
688
|
+
key: string;
|
|
689
|
+
leaves: Readonly<Record<keyof T, string | Text>>;
|
|
690
|
+
collections?: EmbeddedMemberValue["collections"];
|
|
691
|
+
}): EmbeddedMemberValue;
|
|
393
692
|
export declare function embeddedMemberValue(init: {
|
|
394
|
-
kind: string
|
|
693
|
+
kind: string;
|
|
395
694
|
key: string;
|
|
396
695
|
leaves: Readonly<Record<string, string | Text>>;
|
|
397
696
|
collections?: EmbeddedMemberValue["collections"];
|
package/dist/src/kind.js
CHANGED
|
@@ -8,8 +8,43 @@
|
|
|
8
8
|
* rows. Identity travels by import, never by string — a `kind` reference is the imported
|
|
9
9
|
* value.
|
|
10
10
|
*/
|
|
11
|
+
/**
|
|
12
|
+
* Declare a file a member's claims rest on — the lock fingerprints it and no byte
|
|
13
|
+
* reaches the projection. The path resolves against the stating module, so the call is
|
|
14
|
+
* `input(import.meta.url, "./schema.json")`.
|
|
15
|
+
*/
|
|
16
|
+
export function input(moduleUrl, path) {
|
|
17
|
+
return { kind: "input", path, moduleUrl };
|
|
18
|
+
}
|
|
11
19
|
/** The framework keys of a member init — everything else is a typed field (flat). */
|
|
12
|
-
const FRAMEWORK_KEYS = new Set(["name", "host", "prose", "satisfies", "requires", "needs"]);
|
|
20
|
+
const FRAMEWORK_KEYS = new Set(["name", "host", "prose", "satisfies", "requires", "needs", "inputs", "residue"]);
|
|
21
|
+
/**
|
|
22
|
+
* The **reserved leaf key** a nested member's own span lands under (0051) —
|
|
23
|
+
* `<host-address>/<kind>/<key>/prose`. On the read half a layout collection member's
|
|
24
|
+
* own paragraph, the text under its heading before the first child heading, lands here
|
|
25
|
+
* (`src/layout.rs`'s `OWN_SPAN_LEAF`); leaves stay one family, so leaf predicates, leaf
|
|
26
|
+
* addresses and `explain`'s narration range over one source.
|
|
27
|
+
*
|
|
28
|
+
* `prose` is already this word at member grain ({@link MemberInit.prose}), so it is a
|
|
29
|
+
* framework key for an init and a reserved name for a leaf — the same thing spelled once.
|
|
30
|
+
* The composed half authors no own span (its leaves are all author-named), so the
|
|
31
|
+
* reservation binds here as a refusal: a composed leaf of this name would address
|
|
32
|
+
* identically to a read member's own span and mean something else.
|
|
33
|
+
*/
|
|
34
|
+
const RESERVED_LEAF = "prose";
|
|
35
|
+
/**
|
|
36
|
+
* The **residue key** — the channel a partially-governed external format's undocumented
|
|
37
|
+
* keys ride under. A kind whose schema is large and version-evolving types what it
|
|
38
|
+
* governs and leaves the remainder opaque and *named* rather than indicted; `residue` is
|
|
39
|
+
* that name, spliced flat into the projected fields by {@link orderedFields}.
|
|
40
|
+
*
|
|
41
|
+
* Like {@link RESERVED_LEAF}, this is a name the framework owns — a kind whose external
|
|
42
|
+
* format documents a top-level key of it cannot spell that key as an ordinary field. The
|
|
43
|
+
* reservation is kindless, but the channel is opt-in by **type**: only a surface `T`
|
|
44
|
+
* declaring `residue?` can spell the bag, so a closed-frontmatter kind (a skill, a rule,
|
|
45
|
+
* an agent) keeps refusing it by excess-property check like any other unknown key.
|
|
46
|
+
*/
|
|
47
|
+
const RESIDUE_KEY = "residue";
|
|
13
48
|
/**
|
|
14
49
|
* Build the ordered projected fields for a member: nothing for a frontmatterless
|
|
15
50
|
* body-bearing kind (memory declares no `format` and is not fields-only), else the
|
|
@@ -17,6 +52,9 @@ const FRAMEWORK_KEYS = new Set(["name", "host", "prose", "satisfies", "requires"
|
|
|
17
52
|
* typed fields in the author's declared order. A fields-only registration kind (a
|
|
18
53
|
* hook, an MCP server) carries its typed fields though it declares no `format` —
|
|
19
54
|
* the fields are the whole member, folded into a manifest entry, never a header.
|
|
55
|
+
*
|
|
56
|
+
* A {@link RESIDUE_KEY} bag splices in last: what the program types, it orders; what it
|
|
57
|
+
* merely carries, it sorts.
|
|
20
58
|
*/
|
|
21
59
|
function orderedFields(facts, init) {
|
|
22
60
|
if (facts.format === undefined && facts.shape !== "fields")
|
|
@@ -27,7 +65,41 @@ function orderedFields(facts, init) {
|
|
|
27
65
|
typed.push([key, value]);
|
|
28
66
|
}
|
|
29
67
|
const head = facts.identityField !== undefined ? [[facts.identityField, init.name]] : [];
|
|
30
|
-
|
|
68
|
+
const projected = [...head, ...typed];
|
|
69
|
+
return [...projected, ...residueFields(facts, init, projected)];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The init's residue bag as projected fields, key-sorted and disjoint from what the
|
|
73
|
+
* member already projects. The bag is a record, so it carries no authored order to
|
|
74
|
+
* preserve — sorting is what makes the projection a function of the keys alone, the same
|
|
75
|
+
* stability the harness-level residue rows already take (`declarations.ts`'s
|
|
76
|
+
* `settingsRows`).
|
|
77
|
+
*
|
|
78
|
+
* # Throws
|
|
79
|
+
* If a residue key is one `projected` already carries. The bag holds what the surface
|
|
80
|
+
* does not type, so a key in both is a mis-spelling, not an override — and a silent one
|
|
81
|
+
* downstream, since the engine's writer collects the field list into a map and the
|
|
82
|
+
* residue pair, spliced last, would take the typed value's place.
|
|
83
|
+
*/
|
|
84
|
+
function residueFields(facts, init, projected) {
|
|
85
|
+
const residue = init[RESIDUE_KEY];
|
|
86
|
+
if (residue === undefined)
|
|
87
|
+
return [];
|
|
88
|
+
const already = new Set(projected.map(([key]) => key));
|
|
89
|
+
const fields = [];
|
|
90
|
+
// Default `sort()` is UTF-16 code-unit order — the same total order `compareStrings`
|
|
91
|
+
// gives every declaration family, reached without importing `declarations.ts` (which
|
|
92
|
+
// imports `builtins.ts`, which imports this module).
|
|
93
|
+
for (const key of Object.keys(residue).sort()) {
|
|
94
|
+
if (already.has(key)) {
|
|
95
|
+
throw new Error(`member \`${init.name}\` of kind \`${facts.name}\`: \`${RESIDUE_KEY}\` key \`${key}\` is ` +
|
|
96
|
+
`already a field this member projects — the bag carries the keys the kind's surface ` +
|
|
97
|
+
`does not type, so spell this one as the field it is ` +
|
|
98
|
+
`(specs/builtins.md, "The shipped kinds").`);
|
|
99
|
+
}
|
|
100
|
+
fields.push([key, residue[key]]);
|
|
101
|
+
}
|
|
102
|
+
return fields;
|
|
31
103
|
}
|
|
32
104
|
/**
|
|
33
105
|
* The host a member init names, checked against its kind's locus: a nested-file child's
|
|
@@ -56,6 +128,11 @@ function hostOf(facts, init) {
|
|
|
56
128
|
* into emit is the seven facts plus flat field data. `options.render`, when given,
|
|
57
129
|
* rides alongside `facts`/`key` on the returned constructor — never on the member
|
|
58
130
|
* it builds, since it is erased before a member reaches emit.
|
|
131
|
+
*
|
|
132
|
+
* The facts are typed over the same `T` the constructor is, which is what binds a
|
|
133
|
+
* declared {@link LeafSet} witness to `keyof T`: the leaf set is the kind's own surface,
|
|
134
|
+
* checked here at the keystroke, and its lowering to the row is the one place it is
|
|
135
|
+
* spelled again (`declarations.ts`).
|
|
59
136
|
*/
|
|
60
137
|
export function kind(facts, options = {}) {
|
|
61
138
|
const construct = (init) => ({
|
|
@@ -68,23 +145,106 @@ export function kind(facts, options = {}) {
|
|
|
68
145
|
satisfies: init.satisfies ?? [],
|
|
69
146
|
requires: init.requires ?? {},
|
|
70
147
|
needs: init.needs ?? [],
|
|
148
|
+
inputs: init.inputs ?? [],
|
|
71
149
|
});
|
|
72
150
|
return Object.assign(construct, { facts, key: facts.name, render: options.render });
|
|
73
151
|
}
|
|
74
152
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
153
|
+
* The locus a `governs` delta moves the base to: the base's `at` root and glob replaced,
|
|
154
|
+
* every other locus fact (its commitment class among them) riding through.
|
|
155
|
+
*
|
|
156
|
+
* # Throws
|
|
157
|
+
* If the base's locus is not `at` — an embedded or nested-file kind governs no glob at
|
|
158
|
+
* all (its members compose their paths from a host's unit, or own no file), so there is
|
|
159
|
+
* no locus to move and its row carries no `governs` columns to move it to.
|
|
160
|
+
*/
|
|
161
|
+
function relocatedLocus(base, governs) {
|
|
162
|
+
if (base.locus.kind !== "at") {
|
|
163
|
+
throw new Error(`relocating kind \`${base.name}\`: a \`governs\` delta moves the ` +
|
|
164
|
+
`path glob a kind's members are found at, and this kind's locus is \`${base.locus.kind}\` ` +
|
|
165
|
+
`— it governs no glob of its own. Drop the \`governs\` face of the delta ` +
|
|
166
|
+
`(specs/model/representation.md, "locus").`);
|
|
167
|
+
}
|
|
168
|
+
return { ...base.locus, root: governs.root, glob: governs.glob };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* **Relocate** a built-in kind: the sanctioned way an adopting corpus moves a kind it
|
|
172
|
+
* does not own to its own root, adds an edge field to it, or both. Returns a fresh
|
|
173
|
+
* constructor over the widened typed surface `T` (the base's fields plus the added edge
|
|
174
|
+
* fields, spelled by the caller as one interface), carrying the base's facts with
|
|
175
|
+
* `delta`'s edge fields appended, `delta`'s `governs` in place of the base's locus root
|
|
176
|
+
* and glob, and the base's own {@link KindDefinition.render} hook preserved. Ownership,
|
|
177
|
+
* not privilege — a relocated built-in is an ordinary kind value from here on, and its
|
|
178
|
+
* added edge reaches the lock as an assembly `edge` row keyed by `from`, exactly as any
|
|
179
|
+
* kind's does (`declarations.ts`), never as a column on a kind-fact row.
|
|
180
|
+
*
|
|
181
|
+
* A moved locus, by contrast, *is* a kind-fact row column pair: the emitted row carries
|
|
182
|
+
* the delta's `governs_root`/`governs_glob` while `format`, `unit_shape` and
|
|
183
|
+
* `registration` stay the base's — which is precisely the three-fact test the lock
|
|
184
|
+
* reader applies before overlaying the row onto its compiled-in built-in
|
|
185
|
+
* (`src/compose.rs`'s `row_relocates_builtin`), so the engine reads members at the new
|
|
186
|
+
* root rather than treating the row as a colliding kind.
|
|
187
|
+
*
|
|
188
|
+
* The produced facts carry `relocates`, naming the base — the marker that tells a
|
|
189
|
+
* legitimate relocation from a genuine name collision when two same-named kinds are in
|
|
190
|
+
* play. Identity travels by import: the base is the imported built-in value, so the
|
|
191
|
+
* provenance is proven here rather than inferred downstream.
|
|
192
|
+
*
|
|
193
|
+
* # Throws
|
|
194
|
+
* If an added edge field re-declares one the base already carries, or one another
|
|
195
|
+
* entry of the same delta already added — two `edge` rows over one `<from, field>`
|
|
196
|
+
* cross-wire the graph instead of declaring one relationship. If a `governs` delta
|
|
197
|
+
* names a base whose locus is not `at` ({@link relocatedLocus}). And if the delta
|
|
198
|
+
* declares neither face, which would mint a marker-bearing clone of the base — a second
|
|
199
|
+
* same-named kind diverging on nothing, which is a name collision spelled as a
|
|
200
|
+
* relocation.
|
|
81
201
|
*/
|
|
202
|
+
export function relocate(base, delta) {
|
|
203
|
+
const declaredEdges = delta.edgeFields ?? [];
|
|
204
|
+
if (declaredEdges.length === 0 && delta.governs === undefined) {
|
|
205
|
+
throw new Error(`relocating kind \`${base.facts.name}\`: a relocation declares at least one diverging ` +
|
|
206
|
+
`fact — \`edgeFields\`, \`governs\`, or both. A delta declaring neither mints a ` +
|
|
207
|
+
`second kind of the base's own name that diverges on nothing, which reads as a name ` +
|
|
208
|
+
`collision rather than a relocation (specs/model/representation.md, "kind").`);
|
|
209
|
+
}
|
|
210
|
+
const locus = delta.governs === undefined ? undefined : relocatedLocus(base.facts, delta.governs);
|
|
211
|
+
const inherited = base.facts.edgeFields ?? [];
|
|
212
|
+
const claimed = new Set(inherited.map((edge) => edge.field));
|
|
213
|
+
const added = [];
|
|
214
|
+
for (const edge of declaredEdges) {
|
|
215
|
+
if (claimed.has(edge.field)) {
|
|
216
|
+
throw new Error(`relocating kind \`${base.facts.name}\`: edge field \`${edge.field}\` is already ` +
|
|
217
|
+
`declared, and a second declaration of one field cross-wires the graph rather than ` +
|
|
218
|
+
`adding a relationship (specs/model/representation.md, "kind").`);
|
|
219
|
+
}
|
|
220
|
+
claimed.add(edge.field);
|
|
221
|
+
added.push({ field: edge.field, to: edge.to });
|
|
222
|
+
}
|
|
223
|
+
const edgeFields = [...inherited, ...added];
|
|
224
|
+
const relocated = { ...base.facts, relocates: base.facts.name, edgeFields };
|
|
225
|
+
// Two spellings, not one with an optional `locus`: `KindFacts` is a union discriminated
|
|
226
|
+
// on the locus, so the moved case must carry the `at` locus as its own literal branch.
|
|
227
|
+
const facts = locus === undefined ? relocated : { ...relocated, locus };
|
|
228
|
+
// The base's leaf-set witness rides through as data, and the assertion is that
|
|
229
|
+
// pass-through spelled: a relocation appends *edge fields* over the kind's own surface,
|
|
230
|
+
// never a leaf, so the set the base's own constructor already bound still names what a
|
|
231
|
+
// member carries. `T` here is the widened surface (the base's fields plus the added
|
|
232
|
+
// edges), so re-binding the witness to `keyof T` would demand the edge fields be leaves.
|
|
233
|
+
return kind(facts, { render: base.render });
|
|
234
|
+
}
|
|
82
235
|
export function embeddedMemberValue(init) {
|
|
83
236
|
const definition = typeof init.kind === "string" ? undefined : init.kind;
|
|
237
|
+
const kindKey = definition?.key ?? init.kind;
|
|
238
|
+
refuseReservedLeaf(kindKey, init.key, init.leaves);
|
|
239
|
+
for (const [collection, entries] of Object.entries(init.collections ?? {})) {
|
|
240
|
+
for (const entry of entries) {
|
|
241
|
+
refuseReservedLeaf(kindKey, `${init.key}.${collection}.${entry.key}`, entry.leaves);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
84
244
|
const render = definition?.render;
|
|
85
245
|
const edgeFields = definition?.facts.edgeFields;
|
|
86
246
|
return {
|
|
87
|
-
kind:
|
|
247
|
+
kind: kindKey,
|
|
88
248
|
key: init.key,
|
|
89
249
|
leaves: init.leaves,
|
|
90
250
|
collections: init.collections ?? {},
|
|
@@ -92,3 +252,18 @@ export function embeddedMemberValue(init) {
|
|
|
92
252
|
...(edgeFields !== undefined ? { edgeFields } : {}),
|
|
93
253
|
};
|
|
94
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Refuse a composed leaf named {@link RESERVED_LEAF} — the key a read member's own span
|
|
257
|
+
* owns, so a second meaning under it is a coincident leaf address, refused at compose
|
|
258
|
+
* rather than resolved by precedence (0051). Loud at the authoring seam, where the author
|
|
259
|
+
* can rename the field, not at emit over bytes already written.
|
|
260
|
+
*
|
|
261
|
+
* # Throws
|
|
262
|
+
* If `leaves` carries the reserved key.
|
|
263
|
+
*/
|
|
264
|
+
function refuseReservedLeaf(kind, key, leaves) {
|
|
265
|
+
if (!Object.hasOwn(leaves, RESERVED_LEAF))
|
|
266
|
+
return;
|
|
267
|
+
throw new Error(`embedded member \`${kind}\` \`${key}\`: leaf \`${RESERVED_LEAF}\` is reserved for a ` +
|
|
268
|
+
`member's own span — rename the field, or author the words as the member's prose`);
|
|
269
|
+
}
|