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