@dtmd/temper 0.0.6 → 0.0.8
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/README.md +84 -53
- package/bin/temper.js +52 -0
- package/dist/src/assembly.d.ts +15 -1
- package/dist/src/assembly.js +2 -1
- package/dist/src/builtins.d.ts +765 -48
- package/dist/src/builtins.js +767 -68
- package/dist/src/claude-code.d.ts +2 -2
- package/dist/src/claude-code.js +1 -1
- package/dist/src/contract.d.ts +180 -29
- package/dist/src/contract.js +128 -15
- package/dist/src/declarations.d.ts +92 -6
- package/dist/src/declarations.js +448 -102
- package/dist/src/dial.d.ts +75 -0
- package/dist/src/dial.js +82 -0
- package/dist/src/emit.d.ts +82 -1
- package/dist/src/emit.js +402 -56
- package/dist/src/generated/AssemblyFactRow.d.ts +2 -2
- package/dist/src/generated/BoundRow.d.ts +2 -2
- package/dist/src/generated/ClauseRow.d.ts +73 -3
- package/dist/src/generated/CollectionAddressRow.d.ts +21 -0
- package/dist/src/generated/CollectionAddressRow.js +2 -0
- package/dist/src/generated/Declarations.d.ts +17 -0
- package/dist/src/generated/EmbeddedMember.d.ts +3 -3
- package/dist/src/generated/FeatureValue.d.ts +2 -2
- package/dist/src/generated/Features.d.ts +52 -3
- package/dist/src/generated/KindFactRow.d.ts +38 -7
- package/dist/src/generated/MentionRow.d.ts +5 -3
- package/dist/src/generated/NestedMemberRow.d.ts +32 -0
- package/dist/src/generated/PayloadMember.d.ts +6 -0
- package/dist/src/generated/RegistrationRow.d.ts +35 -0
- package/dist/src/generated/RegistrationRow.js +2 -0
- package/dist/src/generated/RequirementRow.d.ts +4 -2
- package/dist/src/generated/SatisfiesRow.d.ts +2 -1
- package/dist/src/generated/SettingsRow.d.ts +25 -0
- package/dist/src/generated/SettingsRow.js +2 -0
- package/dist/src/generated/Shape.d.ts +15 -0
- package/dist/src/generated/Shape.js +2 -0
- package/dist/src/generated/TemplateRow.d.ts +24 -0
- package/dist/src/generated/TemplateRow.js +2 -0
- package/dist/src/generated/ValueType.d.ts +11 -2
- package/dist/src/generated/Verifier.d.ts +20 -0
- package/dist/src/generated/Verifier.js +2 -0
- package/dist/src/generated/index.d.ts +6 -0
- package/dist/src/index.d.ts +8 -8
- package/dist/src/index.js +3 -3
- package/dist/src/kind.d.ts +160 -29
- package/dist/src/kind.js +47 -16
- package/dist/src/prose.d.ts +81 -25
- package/dist/src/prose.js +91 -21
- package/package.json +10 -2
package/dist/src/declarations.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* in lockstep.
|
|
10
10
|
*/
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
|
-
import { resolveLeaf } from "./prose.js";
|
|
12
|
+
import { isTextSpan, resolveLeaf } from "./prose.js";
|
|
13
13
|
/**
|
|
14
14
|
* Compile one `Clause` into its lock row: the shared `key`/`field`/`severity`/
|
|
15
15
|
* `guidance`/`cite` columns — the clause's four channels surviving erasure
|
|
@@ -17,7 +17,7 @@ import { resolveLeaf } from "./prose.js";
|
|
|
17
17
|
* predicate carries them, the `count`/`target`/`degree` argument columns a
|
|
18
18
|
* requirement's own set-/edge-scope demand needs, and the
|
|
19
19
|
* `bound`/`charset`/`keys`/`values` argument columns a kind's own node-scope
|
|
20
|
-
* floor clause needs (`min_len`/`max_len`/`
|
|
20
|
+
* floor clause needs (`min_len`/`max_len`/`extent`'s bound, `extent`'s unit,
|
|
21
21
|
* `allowed_chars`'s charset, `forbidden_keys`'s keys, `deny`'s values) — so
|
|
22
22
|
* the lock encodes the floor losslessly, not identity+severity alone. `kind`
|
|
23
23
|
* is supplied only for a kind's own `expect` clause; a requirement's nested
|
|
@@ -25,6 +25,37 @@ import { resolveLeaf } from "./prose.js";
|
|
|
25
25
|
*/
|
|
26
26
|
function clauseRow(clause, kind) {
|
|
27
27
|
const { predicate } = clause;
|
|
28
|
+
// For a `when` clause, extract the guard predicate's key and arguments,
|
|
29
|
+
// then convert the body to nested rows.
|
|
30
|
+
if (predicate.key === "when") {
|
|
31
|
+
const guardPredicate = clause.when_guard;
|
|
32
|
+
if (!guardPredicate) {
|
|
33
|
+
throw new Error("when clause missing guard predicate");
|
|
34
|
+
}
|
|
35
|
+
// Build a row for the guard predicate's arguments, then copy those fields
|
|
36
|
+
// into this row alongside the guard_predicate and body.
|
|
37
|
+
const guardRow = clauseRow({ predicate: guardPredicate, severity: clause.severity }, undefined);
|
|
38
|
+
return {
|
|
39
|
+
kind,
|
|
40
|
+
predicate: "when",
|
|
41
|
+
severity: clause.severity,
|
|
42
|
+
guidance: clause.guidance,
|
|
43
|
+
cite: clause.cite,
|
|
44
|
+
field: guardRow.field,
|
|
45
|
+
guard_predicate: guardRow.predicate,
|
|
46
|
+
value_type: guardRow.value_type,
|
|
47
|
+
shape: guardRow.shape,
|
|
48
|
+
bound: guardRow.bound,
|
|
49
|
+
unit: guardRow.unit,
|
|
50
|
+
charset: guardRow.charset,
|
|
51
|
+
keys: guardRow.keys,
|
|
52
|
+
values: guardRow.values,
|
|
53
|
+
range: guardRow.range,
|
|
54
|
+
section: guardRow.section,
|
|
55
|
+
sections: guardRow.sections,
|
|
56
|
+
body: clause.when_body ? clause.when_body.map((c) => clauseRow(c, undefined)) : undefined,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
28
59
|
return {
|
|
29
60
|
kind,
|
|
30
61
|
predicate: predicate.key,
|
|
@@ -42,10 +73,14 @@ function clauseRow(clause, kind) {
|
|
|
42
73
|
outgoing: edgeBoundArgs(predicate.args, "outgoing"),
|
|
43
74
|
}
|
|
44
75
|
: undefined,
|
|
76
|
+
gate: predicate.key === "mention-reachable" ? predicate.gate : undefined,
|
|
77
|
+
value_type: predicate.key === "type" && predicate.value_type ? [...predicate.value_type] : undefined,
|
|
78
|
+
shape: predicate.key === "shape" ? predicate.shape : undefined,
|
|
45
79
|
bound: nodeScopeBoundArgs(predicate),
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
//
|
|
80
|
+
unit: predicate.key === "extent" ? predicate.unit : undefined,
|
|
81
|
+
// The generated rows carry mutable columns; the predicate's `value_type`/
|
|
82
|
+
// `charset`/`keys`/`values` are read-only, so copy each into a fresh
|
|
83
|
+
// array/object — the same bytes, a shape the row will accept.
|
|
49
84
|
charset: predicate.key === "allowed_chars" && predicate.charset !== undefined
|
|
50
85
|
? {
|
|
51
86
|
ranges: predicate.charset.ranges ? [...predicate.charset.ranges] : undefined,
|
|
@@ -62,13 +97,16 @@ function clauseRow(clause, kind) {
|
|
|
62
97
|
section: predicate.key === "section_contains" && predicate.section !== undefined
|
|
63
98
|
? { heading: predicate.section.heading, marker: predicate.section.marker }
|
|
64
99
|
: undefined,
|
|
100
|
+
sections: predicate.key === "require_sections" && predicate.sections
|
|
101
|
+
? [...predicate.sections]
|
|
102
|
+
: undefined,
|
|
65
103
|
};
|
|
66
104
|
}
|
|
67
|
-
/** `min_len`/`max_len`/`
|
|
105
|
+
/** `min_len`/`max_len`/`extent`'s scalar bound off their shared `min`/`max`
|
|
68
106
|
* args keys — `undefined` for every other predicate, and for these three when
|
|
69
107
|
* neither endpoint is present. */
|
|
70
108
|
function nodeScopeBoundArgs(predicate) {
|
|
71
|
-
if (predicate.key !== "min_len" && predicate.key !== "max_len" && predicate.key !== "
|
|
109
|
+
if (predicate.key !== "min_len" && predicate.key !== "max_len" && predicate.key !== "extent") {
|
|
72
110
|
return undefined;
|
|
73
111
|
}
|
|
74
112
|
const min = predicate.args?.min;
|
|
@@ -83,10 +121,10 @@ function edgeBoundArgs(args, direction) {
|
|
|
83
121
|
return min === undefined && max === undefined ? undefined : { min, max };
|
|
84
122
|
}
|
|
85
123
|
/**
|
|
86
|
-
* The lock label for a kind's declared unit shape: `file`/`directory`
|
|
87
|
-
* or `named-field(<identityField>)` for the
|
|
88
|
-
* call syntax [`registrationLabel`] uses, so the id source round-trips
|
|
89
|
-
* row rather than degrading to a bare, unreconstructable `"named-field"`.
|
|
124
|
+
* The lock label for a kind's declared unit shape: `file`/`directory`/`starred-segment`
|
|
125
|
+
* verbatim, or `named-field(<identityField>)` for the field-sourced mode — the same
|
|
126
|
+
* `<name>(<field>)` call syntax [`registrationLabel`] uses, so the id source round-trips
|
|
127
|
+
* through the row rather than degrading to a bare, unreconstructable `"named-field"`.
|
|
90
128
|
*/
|
|
91
129
|
function unitShapeLabel(facts) {
|
|
92
130
|
if (facts.unitShape !== "named-field")
|
|
@@ -108,6 +146,10 @@ function registrationLabel(registration) {
|
|
|
108
146
|
return `event(${registration.field})`;
|
|
109
147
|
case "connection":
|
|
110
148
|
return "connection";
|
|
149
|
+
case "enablement":
|
|
150
|
+
return "enablement";
|
|
151
|
+
case "registry":
|
|
152
|
+
return "registry";
|
|
111
153
|
}
|
|
112
154
|
}
|
|
113
155
|
/** The lock labels for a kind's declared registration **set**, in declaration order —
|
|
@@ -120,16 +162,28 @@ export function compareStrings(a, b) {
|
|
|
120
162
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
121
163
|
}
|
|
122
164
|
/**
|
|
123
|
-
* A host kind's
|
|
124
|
-
*
|
|
125
|
-
*
|
|
165
|
+
* A host kind's nesting templates, from its two declaration loci. The kind's own
|
|
166
|
+
* declared templates carry each layer — child kind, plus a file layer's path pattern.
|
|
167
|
+
* An adopting corpus that admits its own embedded kinds over the host overrides the
|
|
168
|
+
* *embedded* grain only: an admission names a host and a child kind but no path, so it
|
|
169
|
+
* can only speak for the pathless (embedded) layer. The path-carrying file layers the
|
|
170
|
+
* host declares are the host's own facts and stand, joined with the admitted rows —
|
|
171
|
+
* else composing a body over a host wipes its declared file layer and the engine, finding
|
|
172
|
+
* no file template, refuses the host's nested file children.
|
|
173
|
+
*
|
|
174
|
+
* `undefined` when nothing is declared or admitted, so the row omits the column rather
|
|
175
|
+
* than carrying an empty array.
|
|
126
176
|
*/
|
|
127
|
-
function templatesFor(
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
177
|
+
function templatesFor(facts, admissions) {
|
|
178
|
+
const declared = (facts.templates ?? []).map((template) => ({
|
|
179
|
+
kind: template.kind.key,
|
|
180
|
+
path: template.path,
|
|
181
|
+
}));
|
|
182
|
+
const admitted = [...(admissions.get(facts.name) ?? [])].sort(compareStrings);
|
|
183
|
+
if (admitted.length === 0)
|
|
184
|
+
return declared.length > 0 ? declared : undefined;
|
|
185
|
+
const fileLayers = declared.filter((template) => template.path !== undefined);
|
|
186
|
+
return [...fileLayers, ...admitted.map((kind) => ({ kind }))];
|
|
133
187
|
}
|
|
134
188
|
/**
|
|
135
189
|
* Lower a kind's declared {@link Layout} into its `content` row — one flat
|
|
@@ -153,42 +207,100 @@ function contentRow(content) {
|
|
|
153
207
|
return { regions };
|
|
154
208
|
}
|
|
155
209
|
/**
|
|
156
|
-
*
|
|
157
|
-
* `
|
|
158
|
-
*
|
|
210
|
+
* Lower a kind's declared {@link CollectionAddress} into its `collection_address` row —
|
|
211
|
+
* `keyPath` spelled as the wire's snake_case `key_path`, and `entryShape` passed through
|
|
212
|
+
* as a string wire format (`object`, `scalar(field)`, or `group-array(member_key;lifted_fields)`).
|
|
213
|
+
* `undefined` for a file-locus kind, so its row omits the column and stays byte-identical.
|
|
214
|
+
*/
|
|
215
|
+
function collectionAddressRow(facts) {
|
|
216
|
+
if (facts.collectionAddress === undefined)
|
|
217
|
+
return undefined;
|
|
218
|
+
return {
|
|
219
|
+
manifest: facts.collectionAddress.manifest,
|
|
220
|
+
key_path: facts.collectionAddress.keyPath,
|
|
221
|
+
entry_shape: facts.collectionAddress.entryShape,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* One kind's fact row — an `at` locus supplies `governs_root`/`governs_glob` and a
|
|
226
|
+
* nested-file kind neither (its path composes from its host's unit and the host
|
|
227
|
+
* template's pattern, so it governs no glob). A file locus's `commitment` class rides
|
|
228
|
+
* the same spelling, absent for the committed default. `templates` names the embedded kinds the
|
|
229
|
+
* corpus admits over it, and `content` lowers a declared layout (absent for a
|
|
230
|
+
* `file`-content kind). A registration kind extends the row with its `shape` marker and
|
|
231
|
+
* `collection_address`.
|
|
159
232
|
*/
|
|
160
|
-
function kindFactRow(facts,
|
|
161
|
-
if (facts.locus.kind
|
|
162
|
-
// An embedded kind inherits its world residue through its host; it
|
|
163
|
-
//
|
|
233
|
+
function kindFactRow(facts, admissions) {
|
|
234
|
+
if (facts.locus.kind === "embedded") {
|
|
235
|
+
// An embedded kind inherits its world residue through its host; it owns no unit at
|
|
236
|
+
// all, so it takes no kind-fact row. Callers filter these out before this point.
|
|
164
237
|
throw new Error(`kind \`${facts.name}\` is embedded — it carries no locus-bearing kind fact.`);
|
|
165
238
|
}
|
|
239
|
+
const governs = facts.locus.kind === "at" ? facts.locus : undefined;
|
|
166
240
|
return {
|
|
167
241
|
name: facts.name,
|
|
168
242
|
provider: facts.provider,
|
|
169
|
-
governs_root:
|
|
170
|
-
governs_glob:
|
|
243
|
+
governs_root: governs?.root,
|
|
244
|
+
governs_glob: governs?.glob,
|
|
245
|
+
commitment: governs?.commitment,
|
|
171
246
|
format: facts.format,
|
|
172
247
|
unit_shape: unitShapeLabel(facts),
|
|
173
248
|
registration: registrationLabels(facts.registration),
|
|
174
|
-
templates: templatesFor(facts
|
|
249
|
+
templates: templatesFor(facts, admissions),
|
|
175
250
|
content: contentRow(facts.content),
|
|
251
|
+
shape: facts.shape,
|
|
252
|
+
collection_address: collectionAddressRow(facts),
|
|
176
253
|
};
|
|
177
254
|
}
|
|
178
|
-
/**
|
|
255
|
+
/**
|
|
256
|
+
* Every kind in play, at any locus — member kinds ∪ expect kinds ∪ their embedded
|
|
257
|
+
* children — name-sorted, so every family derived from it inherits one stable order.
|
|
258
|
+
*
|
|
259
|
+
* The embedded children close transitively over both channels a host names one through:
|
|
260
|
+
* `admit` (the adopting corpus's declaration) and a path-less `templates` entry (the
|
|
261
|
+
* embedded layer). An embedded kind reaches the lock through its host's `templates`
|
|
262
|
+
* column rather than a row of its own, so those channels are the only way one is in play
|
|
263
|
+
* at all — and its declared edge fields are still owed their assembly facts.
|
|
264
|
+
*
|
|
265
|
+
* Only *embedded* children are drawn in. A path-carrying template is the nested-file
|
|
266
|
+
* layer, whose child owns a unit and reaches the lock through `expect` like any other
|
|
267
|
+
* unit kind; pulling one in here would forge it a kind-fact row it never declared.
|
|
268
|
+
*/
|
|
179
269
|
function kindsInPlay(harness) {
|
|
180
270
|
const byName = new Map();
|
|
271
|
+
const pending = [];
|
|
272
|
+
const admit = (facts) => {
|
|
273
|
+
if (byName.has(facts.name))
|
|
274
|
+
return;
|
|
275
|
+
byName.set(facts.name, facts);
|
|
276
|
+
pending.push(facts);
|
|
277
|
+
};
|
|
181
278
|
for (const member of harness.members)
|
|
182
|
-
|
|
279
|
+
admit(member.facts);
|
|
183
280
|
for (const binding of harness.expect)
|
|
184
|
-
|
|
185
|
-
|
|
281
|
+
admit(binding.kind.facts);
|
|
282
|
+
for (const { admits } of harness.admit)
|
|
283
|
+
for (const child of admits)
|
|
284
|
+
admit(child.facts);
|
|
285
|
+
for (let facts = pending.pop(); facts !== undefined; facts = pending.pop()) {
|
|
286
|
+
for (const template of facts.templates ?? []) {
|
|
287
|
+
if (template.kind.facts.locus.kind === "embedded")
|
|
288
|
+
admit(template.kind.facts);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return [...byName.values()].sort((a, b) => compareStrings(a.name, b.name));
|
|
186
292
|
}
|
|
187
|
-
/** The distinct
|
|
293
|
+
/** The distinct discoverable (`at`) kinds in play. */
|
|
188
294
|
function atLocusKindsInPlay(allKinds) {
|
|
189
|
-
return allKinds
|
|
190
|
-
|
|
191
|
-
|
|
295
|
+
return allKinds.filter((facts) => facts.locus.kind === "at");
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* The distinct unit-owning kinds in play — every locus but `embedded`. The kinds that
|
|
299
|
+
* take a fact row: a nested-file kind owns a file the engine must place, and places it
|
|
300
|
+
* off its row, though it governs no glob to be discovered at.
|
|
301
|
+
*/
|
|
302
|
+
function unitKindsInPlay(allKinds) {
|
|
303
|
+
return allKinds.filter((facts) => facts.locus.kind !== "embedded");
|
|
192
304
|
}
|
|
193
305
|
/** The requirement rows — assembly `require` and every member's `requires`, one namespace. */
|
|
194
306
|
function requirementRows(harness) {
|
|
@@ -214,34 +326,56 @@ function requirementRows(harness) {
|
|
|
214
326
|
.sort(([a], [b]) => compareStrings(a, b))
|
|
215
327
|
.map(([name, requirement]) => ({
|
|
216
328
|
name,
|
|
217
|
-
kind: requirement.kind?.key,
|
|
329
|
+
kind: typeof requirement.kind === "string" ? requirement.kind : requirement.kind?.key,
|
|
218
330
|
required: requirement.required ?? false,
|
|
219
331
|
clauses: (requirement.clauses ?? []).map((clause) => clauseRow(clause)),
|
|
220
|
-
|
|
332
|
+
verifier: verifierRow(requirement.verifier),
|
|
221
333
|
prose: requirement.prose,
|
|
222
334
|
}));
|
|
223
335
|
}
|
|
224
336
|
/**
|
|
225
|
-
*
|
|
226
|
-
* `
|
|
227
|
-
*
|
|
228
|
-
*
|
|
337
|
+
* Lower a typed verifier to its species-tagged wire row — `species` plus the
|
|
338
|
+
* variant's own payload. The generated row carries a mutable `events` column, so
|
|
339
|
+
* the telemetry species copies its read-only names into a fresh array (the same
|
|
340
|
+
* read-only→mutable copy `charset`/`keys`/`values` make above).
|
|
341
|
+
*/
|
|
342
|
+
function verifierRow(verifier) {
|
|
343
|
+
if (verifier === undefined)
|
|
344
|
+
return undefined;
|
|
345
|
+
return verifier.species === "script"
|
|
346
|
+
? { species: "script", path: verifier.path }
|
|
347
|
+
: { species: "telemetry", events: [...verifier.events] };
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* The assembly-scope facts, in a stable order: the root member's declared `mode`, then
|
|
351
|
+
* one edge row per kind edge field.
|
|
352
|
+
*
|
|
353
|
+
* `kinds` is every kind in play at *any* locus, not just the unit-owning ones that take
|
|
354
|
+
* a kind-fact row: an edge is a declared relationship at any grain, so an embedded
|
|
355
|
+
* kind's edge fields are owed their rows even though the kind itself reaches the lock
|
|
356
|
+
* through its host's `templates` column alone.
|
|
229
357
|
*/
|
|
230
358
|
function assemblyFactRows(harness, kinds) {
|
|
231
359
|
const facts = [{ fact: "mode", value: harness.mode }];
|
|
232
360
|
for (const kind of kinds) {
|
|
233
361
|
for (const edge of kind.edgeFields ?? []) {
|
|
234
|
-
facts.push({ fact: "edge", from: kind.name, field: edge.field, to: edge.to });
|
|
362
|
+
facts.push({ fact: "edge", from: kind.name, field: edge.field, to: [...edge.to] });
|
|
235
363
|
}
|
|
236
364
|
}
|
|
237
365
|
return facts;
|
|
238
366
|
}
|
|
239
|
-
/**
|
|
367
|
+
/**
|
|
368
|
+
* The `satisfies` rows — every member's fill claims, member-then-requirement sorted.
|
|
369
|
+
* The `member` is the filler's own `kind:name` address, the same identity
|
|
370
|
+
* `mentionRows` writes, so the read side joins on a kind-qualified label a same-named
|
|
371
|
+
* member of another kind can never collide with.
|
|
372
|
+
*/
|
|
240
373
|
function satisfiesRows(harness) {
|
|
241
374
|
const rows = [];
|
|
242
375
|
for (const member of harness.members) {
|
|
376
|
+
const address = `${member.kind}:${member.name}`;
|
|
243
377
|
for (const requirement of member.satisfies) {
|
|
244
|
-
rows.push({ member:
|
|
378
|
+
rows.push({ member: address, requirement });
|
|
245
379
|
}
|
|
246
380
|
}
|
|
247
381
|
return rows.sort((a, b) => compareStrings(a.member, b.member) || compareStrings(a.requirement, b.requirement));
|
|
@@ -249,25 +383,33 @@ function satisfiesRows(harness) {
|
|
|
249
383
|
/**
|
|
250
384
|
* The `mention` rows — every member's authored `n` targets, member-then-target
|
|
251
385
|
* sorted. `text`-kind prose contributes one row per mention, keyed to the
|
|
252
|
-
* member's own `kind:name` address
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
386
|
+
* member's own `kind:name` address. A `blocks()` composed body keys each child
|
|
387
|
+
* to what it is: a prose span's mentions are host-level, keyed to the member's
|
|
388
|
+
* own `kind:name` address like a `text` body; an embedded value's `Text`-leaf
|
|
389
|
+
* mentions are keyed to that leaf's own `<member>/<kind>/<key>/<child-path>`
|
|
390
|
+
* address ([`embeddedLeafMentionRows`]). A `file()` body names none. Recorded off
|
|
391
|
+
* the raw authored address, unconditionally — resolution is `emit`'s own refusal
|
|
257
392
|
* (`emit.ts`), not this row's concern.
|
|
258
393
|
*/
|
|
259
394
|
function mentionRows(harness) {
|
|
260
395
|
const rows = [];
|
|
261
396
|
for (const member of harness.members) {
|
|
397
|
+
const address = `${member.kind}:${member.name}`;
|
|
262
398
|
if (member.prose?.kind === "text") {
|
|
263
|
-
const address = `${member.kind}:${member.name}`;
|
|
264
399
|
for (const mention of member.prose.mentions) {
|
|
265
400
|
rows.push({ member: address, target: mention.target.address });
|
|
266
401
|
}
|
|
267
402
|
}
|
|
268
403
|
if (member.prose?.kind === "blocks") {
|
|
269
404
|
for (const value of member.prose.values) {
|
|
270
|
-
|
|
405
|
+
if (isTextSpan(value)) {
|
|
406
|
+
for (const mention of value.mentions) {
|
|
407
|
+
rows.push({ member: address, target: mention.target.address });
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
rows.push(...embeddedLeafMentionRows(member.name, value));
|
|
412
|
+
}
|
|
271
413
|
}
|
|
272
414
|
}
|
|
273
415
|
}
|
|
@@ -311,97 +453,263 @@ function embeddedLeafMentionRows(hostName, value) {
|
|
|
311
453
|
* path resolved against the stating module ({@link fileURLToPath} over the include's own
|
|
312
454
|
* `moduleUrl`), never the workspace — the engine reads, splices, and fingerprints it.
|
|
313
455
|
* 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.
|
|
315
|
-
*
|
|
316
|
-
*
|
|
456
|
+
* same order, so the engine pairs the k-th slot with the k-th row. Includes ride a `text`
|
|
457
|
+
* body and a composed body's prose spans alike, in authored order across the interleave; an
|
|
458
|
+
* embedded leaf carries none (refused at {@link resolveLeaf}) and a `file()` body names none.
|
|
317
459
|
*/
|
|
318
460
|
function includeRows(harness) {
|
|
319
461
|
const rows = [];
|
|
462
|
+
const push = (address, include) => {
|
|
463
|
+
rows.push({ member: address, source_path: fileURLToPath(new URL(include.path, include.moduleUrl)) });
|
|
464
|
+
};
|
|
320
465
|
for (const member of harness.members) {
|
|
321
|
-
if (member.prose?.kind !== "text")
|
|
322
|
-
continue;
|
|
323
466
|
const address = `${member.kind}:${member.name}`;
|
|
324
|
-
|
|
325
|
-
|
|
467
|
+
if (member.prose?.kind === "text") {
|
|
468
|
+
for (const include of member.prose.includes)
|
|
469
|
+
push(address, include);
|
|
470
|
+
}
|
|
471
|
+
else if (member.prose?.kind === "blocks") {
|
|
472
|
+
for (const value of member.prose.values) {
|
|
473
|
+
if (!isTextSpan(value))
|
|
474
|
+
continue;
|
|
475
|
+
for (const include of value.includes)
|
|
476
|
+
push(address, include);
|
|
477
|
+
}
|
|
326
478
|
}
|
|
327
479
|
}
|
|
328
480
|
return rows;
|
|
329
481
|
}
|
|
482
|
+
/**
|
|
483
|
+
* One composed embedded value's key in an {@link EdgePlacements} table — its host's
|
|
484
|
+
* `kind:name` address plus the value's own kind and key, the same triple the
|
|
485
|
+
* `nested_member` row it feeds is identified by.
|
|
486
|
+
*/
|
|
487
|
+
export function placementKey(host, kind, key) {
|
|
488
|
+
return `${host}${kind}${key}`;
|
|
489
|
+
}
|
|
330
490
|
/**
|
|
331
491
|
* One host member's declared embedded-member value as its declaration row —
|
|
332
492
|
* each `Text`-authored leaf resolved to its final stored string
|
|
333
|
-
* ([`NestedMemberRow`]), mention-resolution-checked against `
|
|
334
|
-
*
|
|
335
|
-
*
|
|
493
|
+
* ([`NestedMemberRow`]), mention-resolution-checked against `scope` the identical
|
|
494
|
+
* way `emit.ts`'s `renderMemberToml` checks the same leaf on its way into the
|
|
495
|
+
* rendered fence — plus the `placed_edges` record `emit` observed while rendering the
|
|
496
|
+
* same value, which is the only way an edge's placement reaches the engine.
|
|
336
497
|
*/
|
|
337
|
-
function nestedMemberRow(host, value,
|
|
498
|
+
function nestedMemberRow(host, value, scope, placements, extents) {
|
|
338
499
|
const context = (childPath) => `member.${value.kind} ${value.key}: leaf \`${childPath}\``;
|
|
339
500
|
const leaves = {};
|
|
340
501
|
for (const [field, leaf] of Object.entries(value.leaves)) {
|
|
341
|
-
leaves[field] = resolveLeaf(leaf,
|
|
502
|
+
leaves[field] = resolveLeaf(leaf, scope, context(field));
|
|
342
503
|
}
|
|
343
504
|
const collections = {};
|
|
344
505
|
for (const [collection, entries] of Object.entries(value.collections)) {
|
|
345
506
|
collections[collection] = entries.map((entry) => {
|
|
346
507
|
const entryLeaves = {};
|
|
347
508
|
for (const [field, leaf] of Object.entries(entry.leaves)) {
|
|
348
|
-
entryLeaves[field] = resolveLeaf(leaf,
|
|
509
|
+
entryLeaves[field] = resolveLeaf(leaf, scope, context(`${collection}.${entry.key}.${field}`));
|
|
349
510
|
}
|
|
350
511
|
return { key: entry.key, leaves: entryLeaves };
|
|
351
512
|
});
|
|
352
513
|
}
|
|
353
|
-
|
|
514
|
+
const key = placementKey(host, value.kind, value.key);
|
|
515
|
+
const placed = placements?.get(key);
|
|
516
|
+
const extent = extents?.get(key);
|
|
517
|
+
return {
|
|
518
|
+
host,
|
|
519
|
+
kind: value.kind,
|
|
520
|
+
key: value.key,
|
|
521
|
+
leaves,
|
|
522
|
+
collections,
|
|
523
|
+
// Omitted, never `undefined`-valued: an absent column is the wire's own spelling of
|
|
524
|
+
// "no format placement was observed here", so a value with no edge to place keeps
|
|
525
|
+
// the row it has always written. The generated row carries a mutable column and the
|
|
526
|
+
// placement record is read-only, so the copy is a fresh array.
|
|
527
|
+
...(placed === undefined ? {} : { placed_edges: [...placed] }),
|
|
528
|
+
// The rendered span, on the same omitted-not-null discipline: a value no render
|
|
529
|
+
// observed (compiled without `emit`'s measurement) keeps the row it has always
|
|
530
|
+
// written, and its `extent` reads as undecidable rather than a captured zero.
|
|
531
|
+
...(extent === undefined ? {} : { rendered_lines: extent.lines, rendered_chars: extent.chars }),
|
|
532
|
+
};
|
|
354
533
|
}
|
|
355
534
|
/**
|
|
356
|
-
*
|
|
357
|
-
* {@link
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
535
|
+
* The corpus's `admit` declarations, indexed by host kind name — the one source both
|
|
536
|
+
* {@link templatesFor} and {@link nestedMemberRows} read. Admission carries kind values,
|
|
537
|
+
* so an admitted kind is imported and thereby in play; repeated hosts union.
|
|
538
|
+
*
|
|
539
|
+
* # Throws
|
|
540
|
+
* If an admission names a non-embedded kind: a body composes embedded members, and a
|
|
541
|
+
* file-locus kind owns a file instead — admitting one declares a nesting no locus backs.
|
|
361
542
|
*/
|
|
362
|
-
function
|
|
543
|
+
function admissionsByHost(harness) {
|
|
363
544
|
const map = new Map();
|
|
364
|
-
for (const
|
|
365
|
-
|
|
366
|
-
|
|
545
|
+
for (const { host, admits } of harness.admit) {
|
|
546
|
+
const admitted = map.get(host.key) ?? new Set();
|
|
547
|
+
for (const child of admits) {
|
|
548
|
+
if (child.facts.locus.kind !== "embedded") {
|
|
549
|
+
throw new Error(`host kind \`${host.key}\` admits \`${child.key}\`, which is not an embedded kind — ` +
|
|
550
|
+
`a composed body admits embedded members only ` +
|
|
551
|
+
`(specs/model/representation.md, "nesting").`);
|
|
552
|
+
}
|
|
553
|
+
admitted.add(child.key);
|
|
367
554
|
}
|
|
555
|
+
map.set(host.key, admitted);
|
|
368
556
|
}
|
|
369
557
|
return map;
|
|
370
558
|
}
|
|
371
559
|
/**
|
|
372
560
|
* The `nested_member` rows — every host member's `blocks()`-declared embedded-member
|
|
373
|
-
* values, host-then-kind-then-key sorted. Only
|
|
374
|
-
* `file()`/`text` body
|
|
561
|
+
* values, host-then-kind-then-key sorted. Only a composed body's embedded values carry
|
|
562
|
+
* them (a `file()`/`text` body — and a composed body's prose spans — name none); the
|
|
563
|
+
* fence rendering itself is unchanged
|
|
375
564
|
* (`emit.ts`'s `resolveBody`) — this row is a second *read* of the same authored
|
|
376
565
|
* value, never a second copy the engine reads back (0018).
|
|
377
566
|
*
|
|
378
|
-
* Refuses an
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
567
|
+
* Refuses an unadmitted nesting before a byte is written: the corpus must admit the
|
|
568
|
+
* value's kind over the hosting member's kind. `templates` derives from that same
|
|
569
|
+
* admission ({@link templatesFor}), so an unadmitted value would reach the lock as a
|
|
570
|
+
* `nested_member` row no `templates` column admits, to be unmodeled without a word —
|
|
571
|
+
* admission is the adopting corpus's own declaration, so an unadmitted nested member is
|
|
572
|
+
* an unresolved input, not output to write over.
|
|
384
573
|
*/
|
|
385
|
-
function nestedMemberRows(harness,
|
|
386
|
-
const hostsByKind = embeddedHostsByKind(harness);
|
|
574
|
+
function nestedMemberRows(harness, admissions, scope, placements, extents) {
|
|
387
575
|
const rows = [];
|
|
388
576
|
for (const member of harness.members) {
|
|
389
577
|
if (member.prose?.kind !== "blocks")
|
|
390
578
|
continue;
|
|
391
579
|
const host = `${member.kind}:${member.name}`;
|
|
392
580
|
for (const value of member.prose.values) {
|
|
393
|
-
|
|
394
|
-
|
|
581
|
+
if (isTextSpan(value))
|
|
582
|
+
continue;
|
|
583
|
+
if (!admissions.get(member.kind)?.has(value.kind)) {
|
|
395
584
|
throw new Error(`member \`${member.name}\`: embedded value \`${value.key}\` is of kind ` +
|
|
396
585
|
`\`${value.kind}\`, which does not nest within host kind \`${member.kind}\` — a ` +
|
|
397
|
-
`\`blocks()\` value's kind must be
|
|
398
|
-
`
|
|
586
|
+
`\`blocks()\` value's kind must be one the harness \`admit\`s over the host kind ` +
|
|
587
|
+
`(specs/model/representation.md, "nesting").`);
|
|
399
588
|
}
|
|
400
|
-
rows.push(nestedMemberRow(host, value,
|
|
589
|
+
rows.push(nestedMemberRow(host, value, scope, placements, extents));
|
|
401
590
|
}
|
|
402
591
|
}
|
|
403
592
|
return rows.sort((a, b) => compareStrings(a.host, b.host) || compareStrings(a.kind, b.kind) || compareStrings(a.key, b.key));
|
|
404
593
|
}
|
|
594
|
+
/**
|
|
595
|
+
* The `registration` rows — every fields-only registration member (a hook, an MCP server)
|
|
596
|
+
* erased for the manifest write face, kind-then-key sorted so double emit is byte-stable.
|
|
597
|
+
* Each carries its identity (`kind`/`key`), its collection address (`manifest`/`keyPath`,
|
|
598
|
+
* the wire's snake_case `key_path`), and its folded typed fields — the entry value the
|
|
599
|
+
* engine's write face places under `key`. The one source `emit.ts`'s public
|
|
600
|
+
* {@link RegistrationFact} view also maps from, so the seam and the `EmitResult` sibling
|
|
601
|
+
* cannot disagree on what a manifest carries.
|
|
602
|
+
*
|
|
603
|
+
* # Throws
|
|
604
|
+
* If a fields-only member declares no collection address — it surfaces in no host manifest.
|
|
605
|
+
*/
|
|
606
|
+
export function registrationRows(harness) {
|
|
607
|
+
return harness.members
|
|
608
|
+
.filter((member) => member.facts.shape === "fields")
|
|
609
|
+
.map((member) => {
|
|
610
|
+
const address = member.facts.collectionAddress;
|
|
611
|
+
if (address === undefined) {
|
|
612
|
+
throw new Error(`member \`${member.name}\`: a fields-only registration kind declares no ` +
|
|
613
|
+
`collection address — it surfaces in no host manifest (specs/model/pipeline.md, "The SDK").`);
|
|
614
|
+
}
|
|
615
|
+
return {
|
|
616
|
+
kind: member.kind,
|
|
617
|
+
key: member.name,
|
|
618
|
+
manifest: address.manifest,
|
|
619
|
+
key_path: address.keyPath,
|
|
620
|
+
// The generated row carries a mutable field list; the member's is read-only, so
|
|
621
|
+
// copy each pair into a fresh tuple — the same values, a shape the row accepts.
|
|
622
|
+
fields: member.fields.map(([name, value]) => [name, value]),
|
|
623
|
+
};
|
|
624
|
+
})
|
|
625
|
+
.sort((a, b) => compareStrings(a.kind, b.kind) || compareStrings(a.key, b.key));
|
|
626
|
+
}
|
|
627
|
+
/** The tap invocation the synthesized telemetry hooks run — the sibling verb of the
|
|
628
|
+
* session-start reporter, appending one event record to the per-machine log
|
|
629
|
+
* (`src/tap.rs`). Every synthesized hook's `command` field carries it verbatim. */
|
|
630
|
+
const TAP_COMMAND = "temper tap";
|
|
631
|
+
/**
|
|
632
|
+
* The `settings.json` lifecycle event and matcher one documented telemetry event-name
|
|
633
|
+
* projects its tap hook at. The event-name is the author-facing token a telemetry
|
|
634
|
+
* verifier names (`contract.ts`'s `telemetry`, the `roster.rs` admissibility set); the
|
|
635
|
+
* `event` is the `hooks.<Event>` key the tap registers under, and the `matcher` scopes
|
|
636
|
+
* the fire to the telemetry-relevant subset — each an external fact
|
|
637
|
+
* (code.claude.com/docs/en/hooks, retrieved 2026-07-17):
|
|
638
|
+
*
|
|
639
|
+
* - `InstructionsLoaded` fires on a rule/memory load; its matcher filters the load
|
|
640
|
+
* reason, and `path_glob_match` is the lazy per-path load the coverage tap reads.
|
|
641
|
+
* - `Skill` is a skill invocation, surfaced under `PostToolUse` with the tool-name
|
|
642
|
+
* matcher `Skill` — the tap's own read of a skill call.
|
|
643
|
+
* - `UserPromptExpansion` fires on a command expansion; its matcher filters the command
|
|
644
|
+
* name, `.*` capturing every one.
|
|
645
|
+
* - `PostToolUse` fires after any tool call; its matcher filters the tool name, `.*`
|
|
646
|
+
* capturing every one.
|
|
647
|
+
*/
|
|
648
|
+
const TELEMETRY_EVENT_HOOKS = {
|
|
649
|
+
InstructionsLoaded: { event: "InstructionsLoaded", matcher: "path_glob_match" },
|
|
650
|
+
Skill: { event: "PostToolUse", matcher: "Skill" },
|
|
651
|
+
UserPromptExpansion: { event: "UserPromptExpansion", matcher: ".*" },
|
|
652
|
+
PostToolUse: { event: "PostToolUse", matcher: ".*" },
|
|
653
|
+
};
|
|
654
|
+
/**
|
|
655
|
+
* The synthesized tap-hook `registration` rows — one deduped `hooks.<Event>`
|
|
656
|
+
* registration per (lifecycle event, matcher) any telemetry verifier names. Scans the
|
|
657
|
+
* same requirement sources {@link requirementRows} reads (assembly `require` ∪ each
|
|
658
|
+
* member's `requires`), keeps the telemetry-species verifiers ({@link Verifier}), and
|
|
659
|
+
* unions the lifecycle events they name into one dumb registration apiece: the tap
|
|
660
|
+
* records every fire and read time joins raw events to members, so however many
|
|
661
|
+
* verifiers name an event it takes exactly one hook — the derived-aggregate precedent
|
|
662
|
+
* the permission union sets ({@link permissionUnion}). Each row runs {@link TAP_COMMAND}
|
|
663
|
+
* under the event's documented matcher ({@link TELEMETRY_EVENT_HOOKS}); an event-name
|
|
664
|
+
* outside that table is the roster's inadmissibility finding, never a row.
|
|
665
|
+
*/
|
|
666
|
+
export function tapHookRows(harness) {
|
|
667
|
+
const deduped = new Map();
|
|
668
|
+
const collect = (requirement) => {
|
|
669
|
+
if (requirement.verifier?.species !== "telemetry")
|
|
670
|
+
return;
|
|
671
|
+
for (const name of requirement.verifier.events) {
|
|
672
|
+
const mapping = TELEMETRY_EVENT_HOOKS[name];
|
|
673
|
+
if (mapping !== undefined)
|
|
674
|
+
deduped.set(`${mapping.event}${mapping.matcher}`, mapping);
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
for (const requirement of Object.values(harness.require))
|
|
678
|
+
collect(requirement);
|
|
679
|
+
for (const member of harness.members) {
|
|
680
|
+
for (const requirement of Object.values(member.requires))
|
|
681
|
+
collect(requirement);
|
|
682
|
+
}
|
|
683
|
+
return [...deduped.values()]
|
|
684
|
+
.sort((a, b) => compareStrings(a.event, b.event) || compareStrings(a.matcher, b.matcher))
|
|
685
|
+
.map(({ event, matcher }) => ({
|
|
686
|
+
kind: "hook",
|
|
687
|
+
key: event,
|
|
688
|
+
manifest: SETTINGS_MANIFEST,
|
|
689
|
+
key_path: "hooks.<Event>",
|
|
690
|
+
fields: [
|
|
691
|
+
["type", "command"],
|
|
692
|
+
["command", TAP_COMMAND],
|
|
693
|
+
["matcher", matcher],
|
|
694
|
+
],
|
|
695
|
+
}));
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* The manifest Claude Code's harness-level settings reside in — the file the assembly's
|
|
699
|
+
* residual settings keys fold into as opaque residue, the same manifest the `hook` kind's
|
|
700
|
+
* registrations surface inside (code.claude.com/docs/en/settings, retrieved 2026-07-10).
|
|
701
|
+
*/
|
|
702
|
+
const SETTINGS_MANIFEST = "settings.json";
|
|
703
|
+
/**
|
|
704
|
+
* The `settings` rows — the assembly's harness-level residual settings keys, each folded
|
|
705
|
+
* into the settings.json manifest's opaque residue at emit. Key-sorted so double emit is
|
|
706
|
+
* byte-stable. Seam-inbound: the value lives in the projected manifest, never the lock.
|
|
707
|
+
*/
|
|
708
|
+
export function settingsRows(harness) {
|
|
709
|
+
return Object.entries(harness.settings)
|
|
710
|
+
.map(([key, value]) => ({ manifest: SETTINGS_MANIFEST, key, value }))
|
|
711
|
+
.sort((a, b) => compareStrings(a.key, b.key));
|
|
712
|
+
}
|
|
405
713
|
/** Every requirement name a `satisfies` claim may fill — assembly `require` ∪ member `requires`. */
|
|
406
714
|
export function declaredRequirements(harness) {
|
|
407
715
|
const set = new Set();
|
|
@@ -415,20 +723,56 @@ export function declaredRequirements(harness) {
|
|
|
415
723
|
}
|
|
416
724
|
/**
|
|
417
725
|
* Every address a mention may name — declared requirement names ∪ each member's
|
|
418
|
-
* `kind:name
|
|
419
|
-
*
|
|
420
|
-
*
|
|
726
|
+
* `kind:name` ∪ each `blocks()`-declared embedded member's host-scoped
|
|
727
|
+
* `<host-kind>:<host-name>/<kind>/<key>` address. Shared by `emit.ts` (a
|
|
728
|
+
* member-level `Text` body's mentions) and this module (an embedded member's
|
|
729
|
+
* `Text` leaves) — the one resolution-check set, so a leaf mention and a member
|
|
730
|
+
* mention are held to the identical bar. The embedded address is host-scoped,
|
|
731
|
+
* never a flat `<kind>:<key>` — flat would force corpus-wide key uniqueness on
|
|
732
|
+
* embedded kinds.
|
|
421
733
|
*/
|
|
422
734
|
export function declaredAddresses(harness) {
|
|
423
735
|
const set = declaredRequirements(harness);
|
|
424
|
-
for (const member of harness.members)
|
|
736
|
+
for (const member of harness.members) {
|
|
425
737
|
set.add(`${member.kind}:${member.name}`);
|
|
738
|
+
if (member.prose?.kind !== "blocks")
|
|
739
|
+
continue;
|
|
740
|
+
for (const value of member.prose.values) {
|
|
741
|
+
if (isTextSpan(value))
|
|
742
|
+
continue;
|
|
743
|
+
set.add(`${member.kind}:${member.name}/${value.kind}/${value.key}`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
426
746
|
return set;
|
|
427
747
|
}
|
|
428
|
-
/**
|
|
429
|
-
|
|
748
|
+
/**
|
|
749
|
+
* Every discoverable (`at`-locus) kind the program declares — the deferral signal a
|
|
750
|
+
* dangling mention is measured against (`prose.ts`'s `defersToGate`): a mention naming
|
|
751
|
+
* one of these whose member is not a composed value defers to `check`, while a mention
|
|
752
|
+
* naming no declared kind refuses at emit. Member kinds ∪ `expect` kinds; an embedded
|
|
753
|
+
* kind is excluded — its members are composed within a host, never discovered, so a
|
|
754
|
+
* flat `kind:name` mention of one has no discovery locus to defer to.
|
|
755
|
+
*/
|
|
756
|
+
export function declaredAtLocusKinds(harness) {
|
|
757
|
+
return new Set(atLocusKindsInPlay(kindsInPlay(harness)).map((facts) => facts.name));
|
|
758
|
+
}
|
|
759
|
+
/** The full {@link MentionScope} the program resolves a mention against — its addresses and its deferral kinds. */
|
|
760
|
+
export function mentionScope(harness) {
|
|
761
|
+
return { mentionable: declaredAddresses(harness), deferrableKinds: declaredAtLocusKinds(harness) };
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Compile a harness into its seven declaration families — the erased program.
|
|
765
|
+
*
|
|
766
|
+
* `placements` and `extents` are `emit`'s record of what each embedded value's format
|
|
767
|
+
* rendered (`emit.ts`'s `edgePlacements`) and the span it spanned (`renderedExtents`); this
|
|
768
|
+
* pass compiles declarations and never renders, so it observes neither itself. Omitted,
|
|
769
|
+
* every `nested_member` row omits its `placed_edges`/`rendered_lines`/`rendered_chars`
|
|
770
|
+
* columns — honest (nothing observed a render) but undecidable for a `format-places-edges`
|
|
771
|
+
* or `extent` clause, so a whole compile goes through `emit`, never this alone.
|
|
772
|
+
*/
|
|
773
|
+
export function compileDeclarations(harness, placements, extents) {
|
|
430
774
|
const allKinds = kindsInPlay(harness);
|
|
431
|
-
const
|
|
775
|
+
const admissions = admissionsByHost(harness);
|
|
432
776
|
const clauses = [];
|
|
433
777
|
for (const binding of [...harness.expect].sort((a, b) => compareStrings(a.kind.key, b.kind.key))) {
|
|
434
778
|
for (const clause of binding.clauses) {
|
|
@@ -436,14 +780,16 @@ export function compileDeclarations(harness) {
|
|
|
436
780
|
}
|
|
437
781
|
}
|
|
438
782
|
return {
|
|
439
|
-
kinds:
|
|
783
|
+
kinds: unitKindsInPlay(allKinds).map((facts) => kindFactRow(facts, admissions)),
|
|
440
784
|
clauses,
|
|
441
785
|
requirements: requirementRows(harness),
|
|
442
|
-
assembly: assemblyFactRows(harness,
|
|
786
|
+
assembly: assemblyFactRows(harness, allKinds),
|
|
443
787
|
satisfies: satisfiesRows(harness),
|
|
444
788
|
mentions: mentionRows(harness),
|
|
445
789
|
includes: includeRows(harness),
|
|
446
|
-
nested_members: nestedMemberRows(harness,
|
|
790
|
+
nested_members: nestedMemberRows(harness, admissions, mentionScope(harness), placements, extents),
|
|
791
|
+
registrations: [...registrationRows(harness), ...tapHookRows(harness)],
|
|
792
|
+
settings: settingsRows(harness),
|
|
447
793
|
};
|
|
448
794
|
}
|
|
449
795
|
/** The SDK's pinned engine/interchange version — the JSON pipe rides it in lockstep. */
|