@dtmd/temper 0.0.4 → 0.0.7
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/bin/temper.js +52 -0
- package/dist/src/assembly.d.ts +32 -16
- package/dist/src/assembly.js +11 -10
- package/dist/src/builtins.d.ts +206 -41
- package/dist/src/builtins.js +258 -54
- package/dist/src/claude-code.d.ts +7 -9
- package/dist/src/claude-code.js +6 -8
- package/dist/src/contract.d.ts +47 -24
- package/dist/src/contract.js +35 -9
- package/dist/src/declarations.d.ts +42 -101
- package/dist/src/declarations.js +375 -47
- package/dist/src/emit.d.ts +56 -28
- package/dist/src/emit.js +177 -54
- 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/CollectionAddressRow.d.ts +17 -0
- package/dist/src/generated/CollectionAddressRow.js +2 -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 +89 -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 +65 -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/RegistrationRow.d.ts +35 -0
- package/dist/src/generated/RegistrationRow.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/SettingsRow.d.ts +25 -0
- package/dist/src/generated/SettingsRow.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 +32 -0
- package/dist/src/generated/index.js +1 -0
- package/dist/src/index.d.ts +14 -18
- package/dist/src/index.js +10 -13
- package/dist/src/kind.d.ts +190 -43
- package/dist/src/kind.js +34 -30
- 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 +9 -1
- package/dist/src/genres.d.ts +0 -38
- package/dist/src/genres.js +0 -24
package/dist/src/emit.js
CHANGED
|
@@ -1,35 +1,135 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Emit — the compile from the six-noun face to the seam's JSON pipe
|
|
3
|
-
*
|
|
4
|
-
* "The seam — one implementation"). The SDK implements **no semantics**: emit
|
|
2
|
+
* Emit — the compile from the six-noun face to the seam's JSON pipe.
|
|
3
|
+
* The SDK implements **no semantics**: emit
|
|
5
4
|
* produces plain data — the declaration rows the engine reads and, per projected
|
|
6
5
|
* member, its ordered typed fields and resolved prose body. The engine is the
|
|
7
6
|
* sole compiler of every projection and the whole lock; the SDK writes neither.
|
|
8
7
|
* Emit is total (members are the only source), refuses before it produces a byte
|
|
9
8
|
* on a broken source, and is byte-reproducible — double-emit verified at every
|
|
10
|
-
* run
|
|
9
|
+
* run.
|
|
11
10
|
*/
|
|
12
|
-
import {
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
13
12
|
import { readFileSync } from "node:fs";
|
|
14
|
-
import { renderText } from "./prose.js";
|
|
13
|
+
import { renderText, resolveLeaf } from "./prose.js";
|
|
15
14
|
import { permissionUnion } from "./needs.js";
|
|
16
|
-
import {
|
|
15
|
+
import { compareStrings, compileDeclarations, declaredAddresses, declaredRequirements, encodeSeam, registrationRows, settingsRows, } from "./declarations.js";
|
|
16
|
+
/**
|
|
17
|
+
* TOML-quote a leaf's authored text into a basic-string literal — the escapes
|
|
18
|
+
* `toml_edit`'s parser reads back (backslash, quote, and the C0 control set),
|
|
19
|
+
* so a leaf survives the write↔read round trip byte-identically.
|
|
20
|
+
*/
|
|
21
|
+
function tomlString(text) {
|
|
22
|
+
let out = '"';
|
|
23
|
+
for (const char of text) {
|
|
24
|
+
switch (char) {
|
|
25
|
+
case "\\":
|
|
26
|
+
out += "\\\\";
|
|
27
|
+
break;
|
|
28
|
+
case '"':
|
|
29
|
+
out += '\\"';
|
|
30
|
+
break;
|
|
31
|
+
case "\b":
|
|
32
|
+
out += "\\b";
|
|
33
|
+
break;
|
|
34
|
+
case "\t":
|
|
35
|
+
out += "\\t";
|
|
36
|
+
break;
|
|
37
|
+
case "\n":
|
|
38
|
+
out += "\\n";
|
|
39
|
+
break;
|
|
40
|
+
case "\f":
|
|
41
|
+
out += "\\f";
|
|
42
|
+
break;
|
|
43
|
+
case "\r":
|
|
44
|
+
out += "\\r";
|
|
45
|
+
break;
|
|
46
|
+
default:
|
|
47
|
+
out += char.charCodeAt(0) < 0x20 ? `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}` : char;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return out + '"';
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve one embedded member's value's leaves — top-level and each
|
|
54
|
+
* collection entry's — to their final stored strings: a `Text`-authored leaf
|
|
55
|
+
* resolves the way `resolveBody` resolves a member-level `Text` body (mention
|
|
56
|
+
* resolution-checked against `mentionable`, loud on a dangling address); a
|
|
57
|
+
* bare-string leaf is unchanged. The one resolution point shared by the
|
|
58
|
+
* default TOML view and a kind's own `render` hook, so refusing on a dangling
|
|
59
|
+
* embedded-kind leaf mention never depends on whether the kind declares
|
|
60
|
+
* `render` (`pipeline.md`, "Emit", the "Refusing" bullet).
|
|
61
|
+
*/
|
|
62
|
+
function resolveMemberLeaves(value, mentionable) {
|
|
63
|
+
const context = (childPath) => `member.${value.kind} ${value.key}: leaf \`${childPath}\``;
|
|
64
|
+
const leaves = {};
|
|
65
|
+
for (const [key, leaf] of Object.entries(value.leaves)) {
|
|
66
|
+
leaves[key] = resolveLeaf(leaf, mentionable, context(key));
|
|
67
|
+
}
|
|
68
|
+
const collections = {};
|
|
69
|
+
for (const [collection, entries] of Object.entries(value.collections)) {
|
|
70
|
+
collections[collection] = entries.map((entry) => {
|
|
71
|
+
const entryLeaves = {};
|
|
72
|
+
for (const [leaf, text] of Object.entries(entry.leaves)) {
|
|
73
|
+
entryLeaves[leaf] = resolveLeaf(text, mentionable, context(`${collection}.${entry.key}.${leaf}`));
|
|
74
|
+
}
|
|
75
|
+
return { key: entry.key, leaves: entryLeaves };
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return { kind: value.kind, key: value.key, leaves, collections };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Render one resolved embedded member's interior TOML: its top-level leaves,
|
|
82
|
+
* then each collection's entries, in authored order, each its own
|
|
83
|
+
* `[collection.entry]` table — the default view a `blocks()` value renders
|
|
84
|
+
* with, when its originating kind declares no `render` hook (`kind.ts`).
|
|
85
|
+
*/
|
|
86
|
+
function renderMemberToml(value) {
|
|
87
|
+
const lines = [];
|
|
88
|
+
for (const [key, leaf] of Object.entries(value.leaves)) {
|
|
89
|
+
lines.push(`${key} = ${tomlString(leaf)}`);
|
|
90
|
+
}
|
|
91
|
+
for (const [collection, entries] of Object.entries(value.collections)) {
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
if (lines.length > 0)
|
|
94
|
+
lines.push("");
|
|
95
|
+
lines.push(`[${collection}.${entry.key}]`);
|
|
96
|
+
for (const [leaf, text] of Object.entries(entry.leaves)) {
|
|
97
|
+
lines.push(`${leaf} = ${tomlString(text)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return lines.join("\n");
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Render one embedded member's value to its `member.<kind> <key>` fenced block:
|
|
105
|
+
* the originating kind's own `render` hook, when declared, in place of the
|
|
106
|
+
* default `[collection.entry]` TOML view — the fence wrapper itself never
|
|
107
|
+
* changes, so a `render`-less kind's projection is byte-unchanged. Leaves
|
|
108
|
+
* resolve once (`resolveMemberLeaves`) before either path sees them, so a
|
|
109
|
+
* hook receives plain strings, never a raw `Text` leaf.
|
|
110
|
+
*/
|
|
111
|
+
function renderMemberFence(value, options) {
|
|
112
|
+
const mentionable = options.mentionable ?? new Set();
|
|
113
|
+
const resolved = resolveMemberLeaves(value, mentionable);
|
|
114
|
+
const body = value.render !== undefined ? value.render(resolved) : renderMemberToml(resolved);
|
|
115
|
+
return `\`\`\`member.${value.kind} ${value.key}\n${body}\n\`\`\``;
|
|
116
|
+
}
|
|
17
117
|
/**
|
|
18
118
|
* Resolve a member's prose to its final body bytes: a `file()` asset is read in
|
|
19
119
|
* byte-for-byte; a `text` body's mentions are resolution-checked (loud on a
|
|
20
|
-
* dangling address) and rendered by the one display rule; a `blocks()` body
|
|
21
|
-
*
|
|
120
|
+
* dangling address) and rendered by the one display rule; a `blocks()` body
|
|
121
|
+
* renders each embedded member as a `member.<kind> <key>` TOML fence. The words
|
|
122
|
+
* are never reworded.
|
|
22
123
|
*
|
|
23
124
|
* # Throws
|
|
24
|
-
* If a `file()` asset does not resolve, a mention names no declared value
|
|
25
|
-
* `blocks()` body is projected before `(genre-fence-format)` lands.
|
|
125
|
+
* If a `file()` asset does not resolve, or a mention names no declared value.
|
|
26
126
|
*/
|
|
27
127
|
function resolveBody(member, options) {
|
|
28
128
|
const prose = member.prose;
|
|
29
129
|
if (prose === undefined)
|
|
30
130
|
return "";
|
|
31
131
|
if (prose.kind === "file") {
|
|
32
|
-
const assetPath = fileSourcePath(member
|
|
132
|
+
const assetPath = fileSourcePath(member);
|
|
33
133
|
try {
|
|
34
134
|
return readFileSync(assetPath, "utf8");
|
|
35
135
|
}
|
|
@@ -39,39 +139,20 @@ function resolveBody(member, options) {
|
|
|
39
139
|
}
|
|
40
140
|
}
|
|
41
141
|
if (prose.kind === "blocks") {
|
|
42
|
-
|
|
43
|
-
`deferred until its first consumer lands ((genre-fence-format), specs/architecture/15-kinds.md).`);
|
|
142
|
+
return prose.values.map((value) => renderMemberFence(value, options)).join("\n\n") + "\n";
|
|
44
143
|
}
|
|
45
144
|
const mentionable = options.mentionable ?? new Set();
|
|
46
145
|
for (const mention of prose.mentions) {
|
|
47
146
|
if (!mentionable.has(mention.target.address)) {
|
|
48
147
|
throw new Error(`member \`${member.name}\`: mention of \`${mention.target.address}\` resolves to no ` +
|
|
49
|
-
`declared value — a mention cannot dangle (specs/
|
|
148
|
+
`declared value — a mention cannot dangle (specs/model/contract.md).`);
|
|
50
149
|
}
|
|
51
150
|
}
|
|
52
151
|
return renderText(prose);
|
|
53
152
|
}
|
|
54
|
-
/** Every requirement name a `satisfies` claim may fill — assembly `require` ∪ member `requires`. */
|
|
55
|
-
function declaredRequirements(harness) {
|
|
56
|
-
const set = new Set();
|
|
57
|
-
for (const name of Object.keys(harness.require))
|
|
58
|
-
set.add(name);
|
|
59
|
-
for (const member of harness.members) {
|
|
60
|
-
for (const name of Object.keys(member.requires))
|
|
61
|
-
set.add(name);
|
|
62
|
-
}
|
|
63
|
-
return set;
|
|
64
|
-
}
|
|
65
|
-
/** Every address a mention may name — declared requirement names ∪ each member's `kind:name`. */
|
|
66
|
-
function declaredAddresses(harness) {
|
|
67
|
-
const set = declaredRequirements(harness);
|
|
68
|
-
for (const member of harness.members)
|
|
69
|
-
set.add(`${member.kind}:${member.name}`);
|
|
70
|
-
return set;
|
|
71
|
-
}
|
|
72
153
|
/**
|
|
73
|
-
* The two declare-side refusals emit runs before it produces a byte
|
|
74
|
-
*
|
|
154
|
+
* The two declare-side refusals emit runs before it produces a byte:
|
|
155
|
+
* a `satisfies` claim naming
|
|
75
156
|
* no declared requirement (a dangling join), and a `required` requirement no
|
|
76
157
|
* member fills (an unfilled required requirement).
|
|
77
158
|
*
|
|
@@ -86,7 +167,7 @@ function refuseBrokenSource(harness) {
|
|
|
86
167
|
if (!requirements.has(name)) {
|
|
87
168
|
throw new Error(`member \`${member.name}\`: \`satisfies\` claims requirement \`${name}\`, which no ` +
|
|
88
169
|
`harness-level or member-published requirement declares — a dangling join ` +
|
|
89
|
-
`(specs/
|
|
170
|
+
`(specs/model/pipeline.md, "Emit", the "Refusing" bullet).`);
|
|
90
171
|
}
|
|
91
172
|
filled.add(name);
|
|
92
173
|
}
|
|
@@ -106,54 +187,94 @@ function refuseBrokenSource(harness) {
|
|
|
106
187
|
if (!filled.has(name)) {
|
|
107
188
|
throw new Error(`required requirement \`${name}\` (declared by ${source}) is filled by no member's ` +
|
|
108
189
|
`\`satisfies\` — an unfilled required requirement ` +
|
|
109
|
-
`(specs/
|
|
190
|
+
`(specs/model/pipeline.md, "Emit", the "Refusing" bullet).`);
|
|
110
191
|
}
|
|
111
192
|
}
|
|
112
193
|
}
|
|
113
|
-
/**
|
|
194
|
+
/**
|
|
195
|
+
* A fields-only registration member (a hook, an MCP server) surfaces embedded in a
|
|
196
|
+
* host manifest, so it owns no standalone artifact — its facts erase into a
|
|
197
|
+
* {@link RegistrationFact} for the manifest write face, never a projected member.
|
|
198
|
+
*/
|
|
199
|
+
function isRegistration(member) {
|
|
200
|
+
return member.facts.shape === "fields";
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* A member is projected iff its kind lives at a path locus and is not a fields-only
|
|
204
|
+
* registration member — an embedded member and a registration member each carry no
|
|
205
|
+
* standalone projection.
|
|
206
|
+
*/
|
|
114
207
|
function isProjected(member) {
|
|
115
|
-
return member.facts.locus.kind === "at";
|
|
208
|
+
return member.facts.locus.kind === "at" && !isRegistration(member);
|
|
116
209
|
}
|
|
117
210
|
/**
|
|
118
211
|
* The resolved absolute path of a `file()` prose asset, or `undefined` for
|
|
119
212
|
* `text`/`blocks` prose (or no prose) — the lift's own-path detection
|
|
120
|
-
* (
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
213
|
+
* (drift: the lock is what names a path a
|
|
214
|
+
* projection, so the engine needs each `file()` member's true source path to
|
|
215
|
+
* tell a lifted member's own file apart from a generated one). Resolves
|
|
216
|
+
* against the declaring module's own `import.meta.url` (`prose.moduleUrl`),
|
|
217
|
+
* never the process cwd — the path is the stating module's, not the
|
|
218
|
+
* workspace's.
|
|
124
219
|
*/
|
|
125
|
-
function fileSourcePath(member
|
|
220
|
+
function fileSourcePath(member) {
|
|
126
221
|
const prose = member.prose;
|
|
127
222
|
if (prose?.kind !== "file")
|
|
128
223
|
return undefined;
|
|
129
|
-
return
|
|
224
|
+
return fileURLToPath(new URL(prose.path, prose.moduleUrl));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The harness's fields-only registration members as the public {@link RegistrationFact}
|
|
228
|
+
* view — the seam's own `registration` rows ({@link registrationRows}) mapped to the
|
|
229
|
+
* nested `collectionAddress` shape the `EmitResult` sibling exposes, so the two cannot
|
|
230
|
+
* disagree on what a manifest carries. Kind-then-key ordered so double emit is byte-stable.
|
|
231
|
+
*
|
|
232
|
+
* # Throws
|
|
233
|
+
* If a fields-only member declares no collection address — it surfaces in no manifest.
|
|
234
|
+
*/
|
|
235
|
+
function registrationFacts(harness) {
|
|
236
|
+
return registrationRows(harness).map((row) => ({
|
|
237
|
+
kind: row.kind,
|
|
238
|
+
key: row.key,
|
|
239
|
+
collectionAddress: { manifest: row.manifest, keyPath: row.key_path },
|
|
240
|
+
fields: row.fields,
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* The harness's residual settings keys as the public {@link SettingsResidue} view — the
|
|
245
|
+
* seam's own `settings` rows ({@link settingsRows}) surfaced under the `EmitResult` sibling,
|
|
246
|
+
* so the two cannot disagree on what a manifest's residue carries. Key-sorted, the same
|
|
247
|
+
* byte-stable order the seam family takes.
|
|
248
|
+
*/
|
|
249
|
+
function settingsResidue(harness) {
|
|
250
|
+
return settingsRows(harness).map((row) => ({ manifest: row.manifest, key: row.key, value: row.value }));
|
|
130
251
|
}
|
|
131
252
|
/** The harness's projected members as payload members, deterministically kind-then-name ordered. */
|
|
132
253
|
function orderedMembers(harness, options) {
|
|
133
254
|
return [...harness.members]
|
|
134
255
|
.filter(isProjected)
|
|
135
|
-
.sort((a, b) => (a.kind
|
|
136
|
-
(a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
|
256
|
+
.sort((a, b) => compareStrings(a.kind, b.kind) || compareStrings(a.name, b.name))
|
|
137
257
|
.map((member) => ({
|
|
138
258
|
kind: member.kind,
|
|
139
259
|
name: member.name,
|
|
140
|
-
|
|
260
|
+
// The generated row carries a mutable field list; the member's is read-only,
|
|
261
|
+
// so copy each pair into a fresh tuple — the same values, a shape the row accepts.
|
|
262
|
+
fields: member.fields.map(([name, value]) => [name, value]),
|
|
141
263
|
body: resolveBody(member, options),
|
|
142
|
-
source_path: fileSourcePath(member
|
|
264
|
+
source_path: fileSourcePath(member),
|
|
143
265
|
}));
|
|
144
266
|
}
|
|
145
267
|
/**
|
|
146
268
|
* Compile the whole face in one deterministic pass: the declaration rows (its
|
|
147
|
-
* rollup and its
|
|
269
|
+
* rollup and its seven families) and every projected member's erased payload.
|
|
148
270
|
* Prose resolves once (`file()` assets read in, mentions resolution-checked
|
|
149
271
|
* against the harness's declared values). Double-emit verified — nondeterministic
|
|
150
|
-
* authoring is a loud failure, never a silent churn
|
|
272
|
+
* authoring is a loud failure, never a silent churn.
|
|
151
273
|
*/
|
|
152
|
-
export function emit(harness
|
|
274
|
+
export function emit(harness) {
|
|
153
275
|
refuseBrokenSource(harness);
|
|
154
276
|
const resolve = {
|
|
155
277
|
mentionable: declaredAddresses(harness),
|
|
156
|
-
baseDir: options.baseDir,
|
|
157
278
|
};
|
|
158
279
|
const compile = () => {
|
|
159
280
|
const members = orderedMembers(harness, resolve);
|
|
@@ -161,8 +282,10 @@ export function emit(harness, options = {}) {
|
|
|
161
282
|
return {
|
|
162
283
|
declarations,
|
|
163
284
|
members,
|
|
164
|
-
seam:
|
|
285
|
+
seam: encodeSeam({ declarations, members }),
|
|
165
286
|
permissions: permissionUnion(harness.members.flatMap((member) => [...member.needs])),
|
|
287
|
+
registrations: registrationFacts(harness),
|
|
288
|
+
settings: settingsResidue(harness),
|
|
166
289
|
};
|
|
167
290
|
};
|
|
168
291
|
const first = compile();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One assembly-scope fact — the root member's own declarations plus the
|
|
3
|
+
* graph edges the harness binds: a `fact` discriminator (`mode`, `edge`)
|
|
4
|
+
* plus the columns that fact carries. Absent columns are omitted from the
|
|
5
|
+
* lock, so each row round-trips to exactly what its producer wrote.
|
|
6
|
+
*/
|
|
7
|
+
export type AssemblyFactRow = {
|
|
8
|
+
/**
|
|
9
|
+
* The fact discriminator: `mode` or `edge`.
|
|
10
|
+
*/
|
|
11
|
+
fact: string;
|
|
12
|
+
/**
|
|
13
|
+
* The scalar value a `mode` fact carries (the root member's declared
|
|
14
|
+
* enforcement mode).
|
|
15
|
+
*/
|
|
16
|
+
value?: string;
|
|
17
|
+
/**
|
|
18
|
+
* An `edge` fact's source kind.
|
|
19
|
+
*/
|
|
20
|
+
from?: string;
|
|
21
|
+
/**
|
|
22
|
+
* An `edge` fact's reference field.
|
|
23
|
+
*/
|
|
24
|
+
field?: string;
|
|
25
|
+
/**
|
|
26
|
+
* An `edge` fact's target kind.
|
|
27
|
+
*/
|
|
28
|
+
to?: string;
|
|
29
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A node-scope clause row's scalar bound — `min_len`'s `min`, `max_len`/`max_lines`'s
|
|
3
|
+
* `max`, each endpoint optional so the row carries only what the predicate declared.
|
|
4
|
+
*/
|
|
5
|
+
export type BoundRow = {
|
|
6
|
+
/**
|
|
7
|
+
* The inclusive lower bound, when the predicate declares one (`min_len`).
|
|
8
|
+
*/
|
|
9
|
+
min?: number;
|
|
10
|
+
/**
|
|
11
|
+
* The inclusive upper bound, when the predicate declares one (`max_len`/`max_lines`).
|
|
12
|
+
*/
|
|
13
|
+
max?: number;
|
|
14
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An `allowed_chars` clause row's declared character class — the wire form of
|
|
3
|
+
* [`crate::contract::Charset`]: inclusive `"<lo>-<hi>"` range specs plus a literal
|
|
4
|
+
* string of individually permitted characters.
|
|
5
|
+
*/
|
|
6
|
+
export type CharsetRow = {
|
|
7
|
+
/**
|
|
8
|
+
* The inclusive character ranges, each a two-character `"<lo>-<hi>"` spec.
|
|
9
|
+
*/
|
|
10
|
+
ranges?: Array<string>;
|
|
11
|
+
/**
|
|
12
|
+
* The individually permitted characters, when any are declared.
|
|
13
|
+
*/
|
|
14
|
+
chars?: string;
|
|
15
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { BoundRow } from "./BoundRow.js";
|
|
2
|
+
import type { CharsetRow } from "./CharsetRow.js";
|
|
3
|
+
import type { CountBoundRow } from "./CountBoundRow.js";
|
|
4
|
+
import type { DegreeBoundRow } from "./DegreeBoundRow.js";
|
|
5
|
+
import type { RangeBoundRow } from "./RangeBoundRow.js";
|
|
6
|
+
import type { SectionContainsRow } from "./SectionContainsRow.js";
|
|
7
|
+
/**
|
|
8
|
+
* One clause of a kind's effective contract, reduced to the columns the lock records:
|
|
9
|
+
* which kind it governs, the predicate's key, the field it targets (when it names one),
|
|
10
|
+
* its declared severity, its guidance and cite — the clause's four channels
|
|
11
|
+
* —
|
|
12
|
+
* and, per predicate, its own argument: the node-set/edge-scope predicates
|
|
13
|
+
* carry
|
|
14
|
+
* their bounds/target, and the node-scope predicates that need more than
|
|
15
|
+
* `field`/`severity` (`min_len`/`max_len`/`max_lines`'s bound, `allowed_chars`'s
|
|
16
|
+
* charset, `forbidden_keys`'s keys, `deny`'s values) carry theirs too — so a kind's
|
|
17
|
+
* own floor clause round-trips losslessly, not identity+severity alone.
|
|
18
|
+
* `unique`'s field rides the shared `field`
|
|
19
|
+
* column (the same slot `required`/`min_len`/… target); the rest carry their own
|
|
20
|
+
* optional columns since a plain field/severity pair cannot express them.
|
|
21
|
+
*
|
|
22
|
+
* Not `Eq`: the `range` column carries `f64` bounds ([`RangeBoundRow`]), so the
|
|
23
|
+
* whole clause-row family it rides is `PartialEq`-only — the same trade
|
|
24
|
+
* [`crate::contract::Predicate`] already makes for its own `Range` bounds.
|
|
25
|
+
*/
|
|
26
|
+
export type ClauseRow = {
|
|
27
|
+
/**
|
|
28
|
+
* The kind whose contract carries the clause. `None` when this row is nested
|
|
29
|
+
* inside a [`RequirementRow`]'s own [`clauses`](RequirementRow::clauses) — a
|
|
30
|
+
* requirement's set-scope demand names no kind of its own; it ranges over
|
|
31
|
+
* whatever kind the requirement's own row already carries.
|
|
32
|
+
*/
|
|
33
|
+
kind?: string;
|
|
34
|
+
/**
|
|
35
|
+
* The predicate's clause key (`required`, `max_len`, …).
|
|
36
|
+
*/
|
|
37
|
+
predicate: string;
|
|
38
|
+
/**
|
|
39
|
+
* The field (or marker) the predicate constrains, when it names one.
|
|
40
|
+
*/
|
|
41
|
+
field?: string;
|
|
42
|
+
/**
|
|
43
|
+
* The clause's declared severity (`required` / `advisory`).
|
|
44
|
+
*/
|
|
45
|
+
severity: string;
|
|
46
|
+
/**
|
|
47
|
+
* The just-in-time teaching channel — the best-practice prose the predicate
|
|
48
|
+
* cannot encode, quoted at the point of a failing finding.
|
|
49
|
+
*/
|
|
50
|
+
guidance?: string;
|
|
51
|
+
/**
|
|
52
|
+
* The external-fact source backing the clause — a doc URL plus retrieved date,
|
|
53
|
+
* carried as data.
|
|
54
|
+
*/
|
|
55
|
+
cite?: string;
|
|
56
|
+
/**
|
|
57
|
+
* The `count` clause's satisfier-set-size bound, when the predicate is `count`.
|
|
58
|
+
*/
|
|
59
|
+
count?: CountBoundRow;
|
|
60
|
+
/**
|
|
61
|
+
* The `membership` clause's target requirement name, when the predicate is
|
|
62
|
+
* `membership`.
|
|
63
|
+
*/
|
|
64
|
+
target?: string;
|
|
65
|
+
/**
|
|
66
|
+
* The `degree` clause's in/out edge-count bound, when the predicate is `degree`.
|
|
67
|
+
*/
|
|
68
|
+
degree?: DegreeBoundRow;
|
|
69
|
+
/**
|
|
70
|
+
* The `min_len`/`max_len`/`max_lines` clause's scalar bound, when the predicate
|
|
71
|
+
* is one of those three.
|
|
72
|
+
*/
|
|
73
|
+
bound?: BoundRow;
|
|
74
|
+
/**
|
|
75
|
+
* The `allowed_chars` clause's declared character class, when the predicate is
|
|
76
|
+
* `allowed_chars`.
|
|
77
|
+
*/
|
|
78
|
+
charset?: CharsetRow;
|
|
79
|
+
/**
|
|
80
|
+
* The `forbidden_keys` clause's forbidden key list, when the predicate is
|
|
81
|
+
* `forbidden_keys`.
|
|
82
|
+
*/
|
|
83
|
+
keys?: Array<string>;
|
|
84
|
+
/**
|
|
85
|
+
* The `deny` clause's forbidden value list, when the predicate is `deny`.
|
|
86
|
+
*/
|
|
87
|
+
values?: Array<string>;
|
|
88
|
+
/**
|
|
89
|
+
* The `range` clause's inclusive numeric bound, when the predicate is `range`.
|
|
90
|
+
*/
|
|
91
|
+
range?: RangeBoundRow;
|
|
92
|
+
/**
|
|
93
|
+
* The `section_contains` clause's heading prefix and required marker, when the
|
|
94
|
+
* predicate is `section_contains`.
|
|
95
|
+
*/
|
|
96
|
+
section?: SectionContainsRow;
|
|
97
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A kind's declared **collection address** row — the manifest a registration member
|
|
3
|
+
* surfaces in and the key path it keys at, the presence-coupled pair
|
|
4
|
+
* [`KindFactRow::collection_address`] carries. Absent from a [`KindFactRow`] means the
|
|
5
|
+
* kind owns its own file locus.
|
|
6
|
+
*/
|
|
7
|
+
export type CollectionAddressRow = {
|
|
8
|
+
/**
|
|
9
|
+
* The host manifest the registration surfaces in (`settings.json`, `.mcp.json`).
|
|
10
|
+
*/
|
|
11
|
+
manifest: string;
|
|
12
|
+
/**
|
|
13
|
+
* The manifest key path the registration keys at (`hooks.<Event>`, `mcpServers.*`), a
|
|
14
|
+
* closed vocabulary the engine's kind lift rejects an unknown value from.
|
|
15
|
+
*/
|
|
16
|
+
key_path: string;
|
|
17
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One entry belonging to one of a [`NestedMemberRow`]'s sibling collections: the
|
|
3
|
+
* collection name, the entry's own key, and its leaf fields — the row's
|
|
4
|
+
* flattened, order-preserving shape (`to_table`/`from_table` serialize the whole
|
|
5
|
+
* column as one array, the discipline every other array-shaped declaration
|
|
6
|
+
* family gets from `toml_edit`).
|
|
7
|
+
*/
|
|
8
|
+
export type CollectionEntryRow = {
|
|
9
|
+
/**
|
|
10
|
+
* The collection this entry belongs to.
|
|
11
|
+
*/
|
|
12
|
+
collection: string;
|
|
13
|
+
/**
|
|
14
|
+
* The entry's key among its collection's siblings.
|
|
15
|
+
*/
|
|
16
|
+
key: string;
|
|
17
|
+
/**
|
|
18
|
+
* The entry's own leaf fields, field name → authored string.
|
|
19
|
+
*/
|
|
20
|
+
leaves: {
|
|
21
|
+
[key in string]: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One collection entry's wire shape as the SDK payload carries it, nested under
|
|
3
|
+
* its owning collection name — [`deserialize_collections`] copies the collection
|
|
4
|
+
* name onto each entry it flattens into a [`CollectionEntryRow`].
|
|
5
|
+
*/
|
|
6
|
+
export type CollectionEntryWire = {
|
|
7
|
+
/**
|
|
8
|
+
* The entry's key among its collection's siblings.
|
|
9
|
+
*/
|
|
10
|
+
key: string;
|
|
11
|
+
/**
|
|
12
|
+
* The entry's own leaf fields, field name → resolved string.
|
|
13
|
+
*/
|
|
14
|
+
leaves: {
|
|
15
|
+
[key in string]: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A requirement row's `count` bound — the satisfier-set size's inclusive `[min, max]`.
|
|
3
|
+
*/
|
|
4
|
+
export type CountBoundRow = {
|
|
5
|
+
/**
|
|
6
|
+
* The inclusive lower bound on the satisfier-set size.
|
|
7
|
+
*/
|
|
8
|
+
min: number;
|
|
9
|
+
/**
|
|
10
|
+
* The inclusive upper bound on the satisfier-set size.
|
|
11
|
+
*/
|
|
12
|
+
max: number;
|
|
13
|
+
};
|