@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
package/dist/src/emit.d.ts
CHANGED
|
@@ -1,48 +1,29 @@
|
|
|
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
11
|
import type { Harness } from "./assembly.js";
|
|
13
12
|
import type { Declarations } from "./declarations.js";
|
|
14
|
-
|
|
13
|
+
import type { PayloadMember } from "./generated/index.js";
|
|
14
|
+
export type { PayloadMember } from "./generated/index.js";
|
|
15
|
+
/** What a mention may resolve against at emit. */
|
|
15
16
|
export interface ResolveOptions {
|
|
16
|
-
/** Base dir a `file()` module-relative path resolves against (default: cwd). */
|
|
17
|
-
readonly baseDir?: string;
|
|
18
17
|
/** The addresses a mention may name — resolution-checked; a mention cannot dangle. */
|
|
19
18
|
readonly mentionable?: ReadonlySet<string>;
|
|
20
19
|
}
|
|
21
|
-
/** One projected member's erased payload — the engine derives its locus from the kind's own declaration row. */
|
|
22
|
-
export interface PayloadMember {
|
|
23
|
-
/** The kind's bare name — joins the payload's `declarations.kinds` family. */
|
|
24
|
-
readonly kind: string;
|
|
25
|
-
/** Identity within the kind. */
|
|
26
|
-
readonly name: string;
|
|
27
|
-
/** The kind's typed fields, flat and ordered — the projected frontmatter. */
|
|
28
|
-
readonly fields: ReadonlyArray<readonly [string, unknown]>;
|
|
29
|
-
/** The resolved prose body, byte-faithful. */
|
|
30
|
-
readonly body: string;
|
|
31
|
-
/** The resolved `file()` asset's absolute path; absent for `text`/`blocks` prose. */
|
|
32
|
-
readonly source_path?: string;
|
|
33
|
-
}
|
|
34
|
-
/** Emit-time inputs beyond the harness — where a `file()` asset's module-relative path resolves against. */
|
|
35
|
-
export interface EmitOptions {
|
|
36
|
-
/** Base dir a `file()` asset's module-relative path resolves against (default: cwd). */
|
|
37
|
-
readonly baseDir?: string;
|
|
38
|
-
}
|
|
39
20
|
/**
|
|
40
|
-
* A full emit's compiled outputs — the whole seam the engine reads
|
|
41
|
-
*
|
|
21
|
+
* A full emit's compiled outputs — the whole seam the engine reads.
|
|
22
|
+
* A pure function of the harness, so [`emit`]
|
|
42
23
|
* double-verifies it.
|
|
43
24
|
*/
|
|
44
25
|
export interface EmitResult {
|
|
45
|
-
/** The declaration rows — the erased program the lock's
|
|
26
|
+
/** The declaration rows — the erased program the lock's seven families carry. */
|
|
46
27
|
readonly declarations: Declarations;
|
|
47
28
|
/** The projected members — the engine's sole input for every projection. */
|
|
48
29
|
readonly members: readonly PayloadMember[];
|
|
@@ -53,7 +34,7 @@ export interface EmitResult {
|
|
|
53
34
|
readonly seam: string;
|
|
54
35
|
/**
|
|
55
36
|
* The derived permission list — the union of every member's `needs`, deduped and
|
|
56
|
-
|
|
37
|
+
* sorted.
|
|
57
38
|
* Folds into the settings artifact once hook/MCP members land; carried here as
|
|
58
39
|
* data until then.
|
|
59
40
|
*/
|
|
@@ -61,9 +42,9 @@ export interface EmitResult {
|
|
|
61
42
|
}
|
|
62
43
|
/**
|
|
63
44
|
* Compile the whole face in one deterministic pass: the declaration rows (its
|
|
64
|
-
* rollup and its
|
|
45
|
+
* rollup and its seven families) and every projected member's erased payload.
|
|
65
46
|
* Prose resolves once (`file()` assets read in, mentions resolution-checked
|
|
66
47
|
* against the harness's declared values). Double-emit verified — nondeterministic
|
|
67
|
-
* authoring is a loud failure, never a silent churn
|
|
48
|
+
* authoring is a loud failure, never a silent churn.
|
|
68
49
|
*/
|
|
69
|
-
export declare function emit(harness: Harness
|
|
50
|
+
export declare function emit(harness: Harness): EmitResult;
|
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, } 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,56 @@ 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
|
-
/** A member is projected iff its kind lives at a path locus (
|
|
194
|
+
/** A member is projected iff its kind lives at a path locus (an embedded member is not). */
|
|
114
195
|
function isProjected(member) {
|
|
115
196
|
return member.facts.locus.kind === "at";
|
|
116
197
|
}
|
|
117
198
|
/**
|
|
118
199
|
* The resolved absolute path of a `file()` prose asset, or `undefined` for
|
|
119
200
|
* `text`/`blocks` prose (or no prose) — the lift's own-path detection
|
|
120
|
-
* (
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
201
|
+
* (drift: the lock is what names a path a
|
|
202
|
+
* projection, so the engine needs each `file()` member's true source path to
|
|
203
|
+
* tell a lifted member's own file apart from a generated one). Resolves
|
|
204
|
+
* against the declaring module's own `import.meta.url` (`prose.moduleUrl`),
|
|
205
|
+
* never the process cwd — the path is the stating module's, not the
|
|
206
|
+
* workspace's.
|
|
124
207
|
*/
|
|
125
|
-
function fileSourcePath(member
|
|
208
|
+
function fileSourcePath(member) {
|
|
126
209
|
const prose = member.prose;
|
|
127
210
|
if (prose?.kind !== "file")
|
|
128
211
|
return undefined;
|
|
129
|
-
return
|
|
212
|
+
return fileURLToPath(new URL(prose.path, prose.moduleUrl));
|
|
130
213
|
}
|
|
131
214
|
/** The harness's projected members as payload members, deterministically kind-then-name ordered. */
|
|
132
215
|
function orderedMembers(harness, options) {
|
|
133
216
|
return [...harness.members]
|
|
134
217
|
.filter(isProjected)
|
|
135
|
-
.sort((a, b) => (a.kind
|
|
136
|
-
(a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
|
218
|
+
.sort((a, b) => compareStrings(a.kind, b.kind) || compareStrings(a.name, b.name))
|
|
137
219
|
.map((member) => ({
|
|
138
220
|
kind: member.kind,
|
|
139
221
|
name: member.name,
|
|
140
|
-
|
|
222
|
+
// The generated row carries a mutable field list; the member's is read-only,
|
|
223
|
+
// so copy each pair into a fresh tuple — the same values, a shape the row accepts.
|
|
224
|
+
fields: member.fields.map(([name, value]) => [name, value]),
|
|
141
225
|
body: resolveBody(member, options),
|
|
142
|
-
source_path: fileSourcePath(member
|
|
226
|
+
source_path: fileSourcePath(member),
|
|
143
227
|
}));
|
|
144
228
|
}
|
|
145
229
|
/**
|
|
146
230
|
* Compile the whole face in one deterministic pass: the declaration rows (its
|
|
147
|
-
* rollup and its
|
|
231
|
+
* rollup and its seven families) and every projected member's erased payload.
|
|
148
232
|
* Prose resolves once (`file()` assets read in, mentions resolution-checked
|
|
149
233
|
* against the harness's declared values). Double-emit verified — nondeterministic
|
|
150
|
-
* authoring is a loud failure, never a silent churn
|
|
234
|
+
* authoring is a loud failure, never a silent churn.
|
|
151
235
|
*/
|
|
152
|
-
export function emit(harness
|
|
236
|
+
export function emit(harness) {
|
|
153
237
|
refuseBrokenSource(harness);
|
|
154
238
|
const resolve = {
|
|
155
239
|
mentionable: declaredAddresses(harness),
|
|
156
|
-
baseDir: options.baseDir,
|
|
157
240
|
};
|
|
158
241
|
const compile = () => {
|
|
159
242
|
const members = orderedMembers(harness, resolve);
|
|
@@ -161,7 +244,7 @@ export function emit(harness, options = {}) {
|
|
|
161
244
|
return {
|
|
162
245
|
declarations,
|
|
163
246
|
members,
|
|
164
|
-
seam:
|
|
247
|
+
seam: encodeSeam({ declarations, members }),
|
|
165
248
|
permissions: permissionUnion(harness.members.flatMap((member) => [...member.needs])),
|
|
166
249
|
};
|
|
167
250
|
};
|
|
@@ -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,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
|
+
};
|