@dtmd/temper 0.0.2 → 0.0.4
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 +1 -1
- package/dist/src/assembly.d.ts +4 -7
- package/dist/src/assembly.js +2 -3
- package/dist/src/builtins.d.ts +105 -0
- package/dist/src/builtins.js +183 -0
- package/dist/src/claude-code.d.ts +6 -4
- package/dist/src/claude-code.js +5 -4
- package/dist/src/contract.d.ts +66 -22
- package/dist/src/contract.js +51 -7
- package/dist/src/declarations.d.ts +60 -9
- package/dist/src/declarations.js +80 -18
- package/dist/src/emit.d.ts +34 -35
- package/dist/src/emit.js +37 -49
- package/dist/src/genres.d.ts +8 -35
- package/dist/src/genres.js +7 -40
- package/dist/src/index.d.ts +12 -14
- package/dist/src/index.js +8 -8
- package/package.json +1 -1
- package/dist/src/lock.d.ts +0 -43
- package/dist/src/lock.js +0 -127
- package/dist/src/project.d.ts +0 -79
- package/dist/src/project.js +0 -162
- package/dist/src/toml.d.ts +0 -26
- package/dist/src/toml.js +0 -194
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* ({@link declarationsToJson}) — not a designed IR, versioned in lockstep.
|
|
10
10
|
*/
|
|
11
11
|
import type { Harness } from "./assembly.js";
|
|
12
|
+
import type { Charset } from "./contract.js";
|
|
12
13
|
/** One kind's declaration row — its identity and declared runtime facts. */
|
|
13
14
|
export interface KindFactRow {
|
|
14
15
|
readonly name: string;
|
|
@@ -17,24 +18,68 @@ export interface KindFactRow {
|
|
|
17
18
|
readonly governs_glob: string;
|
|
18
19
|
readonly format?: string;
|
|
19
20
|
readonly unit_shape?: string;
|
|
20
|
-
readonly
|
|
21
|
+
readonly registration?: string;
|
|
21
22
|
}
|
|
22
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* One clause of a kind's effective contract, or one of a requirement's own
|
|
25
|
+
* set-/edge-scope demands — the same row shape either way (`src/drift.rs`
|
|
26
|
+
* `ClauseRow`). `kind` is absent when this row is nested inside a
|
|
27
|
+
* `RequirementRow`'s own `clauses`: a requirement's demand names no kind of its
|
|
28
|
+
* own (`10-contracts.md`, "Decision: set-scope demands are clauses").
|
|
29
|
+
*/
|
|
23
30
|
export interface ClauseRow {
|
|
24
|
-
readonly kind
|
|
31
|
+
readonly kind?: string;
|
|
25
32
|
readonly predicate: string;
|
|
26
33
|
readonly field?: string;
|
|
27
34
|
readonly severity: string;
|
|
35
|
+
/** The just-in-time teaching channel the predicate cannot encode (`10-contracts.md`, "guidance"). */
|
|
36
|
+
readonly guidance?: string;
|
|
37
|
+
/** The external-fact source backing the clause — a doc URL plus retrieved date (`10-contracts.md`, "cite"). */
|
|
38
|
+
readonly cite?: string;
|
|
39
|
+
/** The `count` predicate's satisfier-set-size bound. */
|
|
40
|
+
readonly count?: {
|
|
41
|
+
readonly min: number;
|
|
42
|
+
readonly max: number;
|
|
43
|
+
};
|
|
44
|
+
/** The `membership` predicate's target requirement name. */
|
|
45
|
+
readonly target?: string;
|
|
46
|
+
/** The `degree` predicate's in/out edge-count bound. */
|
|
47
|
+
readonly degree?: {
|
|
48
|
+
readonly incoming?: {
|
|
49
|
+
readonly min?: number;
|
|
50
|
+
readonly max?: number;
|
|
51
|
+
};
|
|
52
|
+
readonly outgoing?: {
|
|
53
|
+
readonly min?: number;
|
|
54
|
+
readonly max?: number;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
/** The `min_len`/`max_len`/`max_lines` predicate's scalar bound. */
|
|
58
|
+
readonly bound?: {
|
|
59
|
+
readonly min?: number;
|
|
60
|
+
readonly max?: number;
|
|
61
|
+
};
|
|
62
|
+
/** The `allowed_chars` predicate's declared character class. */
|
|
63
|
+
readonly charset?: Charset;
|
|
64
|
+
/** The `forbidden_keys` predicate's forbidden key list. */
|
|
65
|
+
readonly keys?: readonly string[];
|
|
66
|
+
/** The `deny` predicate's forbidden value list. */
|
|
67
|
+
readonly values?: readonly string[];
|
|
28
68
|
}
|
|
29
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* One named requirement's declaration row — the scalar facets plus its own
|
|
71
|
+
* `count`/`unique`/`membership`/`degree` clause rows (`10-contracts.md`,
|
|
72
|
+
* "Decision: set-scope demands are clauses"): the requirement's `clauses` array
|
|
73
|
+
* is the whole of its set-/edge-scope demand, no facet columns beside it.
|
|
74
|
+
*/
|
|
30
75
|
export interface RequirementRow {
|
|
31
76
|
readonly name: string;
|
|
32
77
|
readonly kind?: string;
|
|
33
|
-
readonly package?: string;
|
|
34
78
|
readonly required: boolean;
|
|
79
|
+
readonly clauses: readonly ClauseRow[];
|
|
35
80
|
readonly verified_by?: string;
|
|
36
81
|
}
|
|
37
|
-
/** One assembly-scope fact — authority
|
|
82
|
+
/** One assembly-scope fact — authority or an edge. */
|
|
38
83
|
export interface AssemblyFactRow {
|
|
39
84
|
readonly fact: string;
|
|
40
85
|
readonly value?: string;
|
|
@@ -42,17 +87,23 @@ export interface AssemblyFactRow {
|
|
|
42
87
|
readonly field?: string;
|
|
43
88
|
readonly to?: string;
|
|
44
89
|
}
|
|
45
|
-
/**
|
|
90
|
+
/** One member→requirement fill edge — a resolved `satisfies` key. */
|
|
91
|
+
export interface SatisfiesRow {
|
|
92
|
+
readonly member: string;
|
|
93
|
+
readonly requirement: string;
|
|
94
|
+
}
|
|
95
|
+
/** The five declaration families — the whole erased program the lock and pipe carry. */
|
|
46
96
|
export interface Declarations {
|
|
47
97
|
readonly kinds: readonly KindFactRow[];
|
|
48
98
|
readonly clauses: readonly ClauseRow[];
|
|
49
99
|
readonly requirements: readonly RequirementRow[];
|
|
50
100
|
readonly assembly: readonly AssemblyFactRow[];
|
|
101
|
+
readonly satisfies: readonly SatisfiesRow[];
|
|
51
102
|
}
|
|
52
|
-
/** Compile a harness into its
|
|
103
|
+
/** Compile a harness into its five declaration families — the erased program. */
|
|
53
104
|
export declare function compileDeclarations(harness: Harness): Declarations;
|
|
54
105
|
/** The SDK's pinned engine/interchange version — the JSON pipe rides it in lockstep. */
|
|
55
|
-
export declare const SEAM_VERSION =
|
|
106
|
+
export declare const SEAM_VERSION = 2;
|
|
56
107
|
/**
|
|
57
108
|
* Serialize the declaration rows to the internal versioned JSON pipe
|
|
58
109
|
* (`20-surface.md`, "The seam"). Not a designed IR — a stable public interchange
|
package/dist/src/declarations.js
CHANGED
|
@@ -8,8 +8,65 @@
|
|
|
8
8
|
* (`SDK-RECUT-CORPUS-FACE`). The same rows ride the internal versioned JSON pipe
|
|
9
9
|
* ({@link declarationsToJson}) — not a designed IR, versioned in lockstep.
|
|
10
10
|
*/
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Compile one `Clause` into its lock row: the shared `key`/`field`/`severity`/
|
|
13
|
+
* `guidance`/`cite` columns — the clause's four channels surviving erasure
|
|
14
|
+
* (`10-contracts.md`, "The clause — the atom of a contract") — plus, when the
|
|
15
|
+
* predicate carries them, the `count`/`target`/`degree` argument columns a
|
|
16
|
+
* requirement's own set-/edge-scope demand needs (`10-contracts.md`, "Judged
|
|
17
|
+
* at the node-set scope" / "Judged at the edge scope"), and the
|
|
18
|
+
* `bound`/`charset`/`keys`/`values` argument columns a kind's own node-scope
|
|
19
|
+
* floor clause needs (`min_len`/`max_len`/`max_lines`'s bound,
|
|
20
|
+
* `allowed_chars`'s charset, `forbidden_keys`'s keys, `deny`'s values) — so
|
|
21
|
+
* the lock encodes the floor losslessly, not identity+severity alone. `kind`
|
|
22
|
+
* is supplied only for a kind's own `expect` clause; a requirement's nested
|
|
23
|
+
* clause carries none.
|
|
24
|
+
*/
|
|
25
|
+
function clauseRow(clause, kind) {
|
|
26
|
+
const { predicate } = clause;
|
|
27
|
+
return {
|
|
28
|
+
kind,
|
|
29
|
+
predicate: predicate.key,
|
|
30
|
+
field: predicate.field,
|
|
31
|
+
severity: clause.severity,
|
|
32
|
+
guidance: clause.guidance,
|
|
33
|
+
cite: clause.cite,
|
|
34
|
+
count: predicate.key === "count"
|
|
35
|
+
? { min: predicate.args?.min ?? 0, max: predicate.args?.max ?? Number.MAX_SAFE_INTEGER }
|
|
36
|
+
: undefined,
|
|
37
|
+
target: predicate.key === "membership" ? predicate.target : undefined,
|
|
38
|
+
degree: predicate.key === "degree"
|
|
39
|
+
? {
|
|
40
|
+
incoming: edgeBoundArgs(predicate.args, "incoming"),
|
|
41
|
+
outgoing: edgeBoundArgs(predicate.args, "outgoing"),
|
|
42
|
+
}
|
|
43
|
+
: undefined,
|
|
44
|
+
bound: nodeScopeBoundArgs(predicate),
|
|
45
|
+
charset: predicate.key === "allowed_chars" ? predicate.charset : undefined,
|
|
46
|
+
keys: predicate.key === "forbidden_keys" ? predicate.keys : undefined,
|
|
47
|
+
values: predicate.key === "deny" ? predicate.values : undefined,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** `min_len`/`max_len`/`max_lines`'s scalar bound off their shared `min`/`max`
|
|
51
|
+
* args keys — `undefined` for every other predicate, and for these three when
|
|
52
|
+
* neither endpoint is present. */
|
|
53
|
+
function nodeScopeBoundArgs(predicate) {
|
|
54
|
+
if (predicate.key !== "min_len" && predicate.key !== "max_len" && predicate.key !== "max_lines") {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const min = predicate.args?.min;
|
|
58
|
+
const max = predicate.args?.max;
|
|
59
|
+
return min === undefined && max === undefined ? undefined : { min, max };
|
|
60
|
+
}
|
|
61
|
+
/** One `degree` direction's `{min, max}` off its flat `<dir>_min`/`<dir>_max` args
|
|
62
|
+
* keys — `undefined` when neither is present (that direction is unconstrained). */
|
|
63
|
+
function edgeBoundArgs(args, direction) {
|
|
64
|
+
const min = args?.[`${direction}_min`];
|
|
65
|
+
const max = args?.[`${direction}_max`];
|
|
66
|
+
return min === undefined && max === undefined ? undefined : { min, max };
|
|
67
|
+
}
|
|
68
|
+
/** The lock label for a kind's declared registration. */
|
|
69
|
+
function registrationLabel(registration) {
|
|
13
70
|
switch (registration.via) {
|
|
14
71
|
case "always":
|
|
15
72
|
return "always";
|
|
@@ -38,7 +95,7 @@ function kindFactRow(facts) {
|
|
|
38
95
|
governs_glob: facts.locus.glob,
|
|
39
96
|
format: facts.format,
|
|
40
97
|
unit_shape: facts.unitShape,
|
|
41
|
-
|
|
98
|
+
registration: registrationLabel(facts.registration),
|
|
42
99
|
};
|
|
43
100
|
}
|
|
44
101
|
/** The distinct locus-bearing kinds in play — member kinds ∪ expect kinds, name-sorted. */
|
|
@@ -78,20 +135,18 @@ function requirementRows(harness) {
|
|
|
78
135
|
name,
|
|
79
136
|
kind: requirement.kind?.key,
|
|
80
137
|
required: requirement.required ?? false,
|
|
138
|
+
clauses: (requirement.clauses ?? []).map((clause) => clauseRow(clause)),
|
|
81
139
|
verified_by: requirement.verifiedBy,
|
|
82
140
|
}));
|
|
83
141
|
}
|
|
84
142
|
/**
|
|
85
143
|
* The assembly-scope facts, in a stable order: authority (always declared — the
|
|
86
144
|
* `shared` default anchors every harness until a surface-authority posture is
|
|
87
|
-
* authored),
|
|
88
|
-
*
|
|
145
|
+
* authored), then one edge row per kind edge field (`40-composition.md`;
|
|
146
|
+
* `45-governance.md`).
|
|
89
147
|
*/
|
|
90
|
-
function assemblyFactRows(
|
|
148
|
+
function assemblyFactRows(kinds) {
|
|
91
149
|
const facts = [{ fact: "authority", value: "shared" }];
|
|
92
|
-
if (harness.reachability !== undefined) {
|
|
93
|
-
facts.push({ fact: "reachability", value: harness.reachability });
|
|
94
|
-
}
|
|
95
150
|
for (const kind of kinds) {
|
|
96
151
|
for (const edge of kind.edgeFields ?? []) {
|
|
97
152
|
facts.push({ fact: "edge", from: kind.name, field: edge.field, to: edge.to });
|
|
@@ -99,29 +154,36 @@ function assemblyFactRows(harness, kinds) {
|
|
|
99
154
|
}
|
|
100
155
|
return facts;
|
|
101
156
|
}
|
|
102
|
-
/**
|
|
157
|
+
/** The `satisfies` rows — every member's fill claims, member-then-requirement sorted. */
|
|
158
|
+
function satisfiesRows(harness) {
|
|
159
|
+
const rows = [];
|
|
160
|
+
for (const member of harness.members) {
|
|
161
|
+
for (const requirement of member.satisfies) {
|
|
162
|
+
rows.push({ member: member.name, requirement });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return rows.sort((a, b) => (a.member < b.member ? -1 : a.member > b.member ? 1 : 0) ||
|
|
166
|
+
(a.requirement < b.requirement ? -1 : a.requirement > b.requirement ? 1 : 0));
|
|
167
|
+
}
|
|
168
|
+
/** Compile a harness into its five declaration families — the erased program. */
|
|
103
169
|
export function compileDeclarations(harness) {
|
|
104
170
|
const kinds = kindsInPlay(harness);
|
|
105
171
|
const clauses = [];
|
|
106
172
|
for (const binding of [...harness.expect].sort((a, b) => (a.kind.key < b.kind.key ? -1 : a.kind.key > b.kind.key ? 1 : 0))) {
|
|
107
173
|
for (const clause of binding.clauses) {
|
|
108
|
-
clauses.push(
|
|
109
|
-
kind: binding.kind.key,
|
|
110
|
-
predicate: clause.predicate.key,
|
|
111
|
-
field: clause.predicate.field,
|
|
112
|
-
severity: clause.severity,
|
|
113
|
-
});
|
|
174
|
+
clauses.push(clauseRow(clause, binding.kind.key));
|
|
114
175
|
}
|
|
115
176
|
}
|
|
116
177
|
return {
|
|
117
178
|
kinds: kinds.map(kindFactRow),
|
|
118
179
|
clauses,
|
|
119
180
|
requirements: requirementRows(harness),
|
|
120
|
-
assembly: assemblyFactRows(
|
|
181
|
+
assembly: assemblyFactRows(kinds),
|
|
182
|
+
satisfies: satisfiesRows(harness),
|
|
121
183
|
};
|
|
122
184
|
}
|
|
123
185
|
/** The SDK's pinned engine/interchange version — the JSON pipe rides it in lockstep. */
|
|
124
|
-
export const SEAM_VERSION =
|
|
186
|
+
export const SEAM_VERSION = 2;
|
|
125
187
|
/**
|
|
126
188
|
* Serialize the declaration rows to the internal versioned JSON pipe
|
|
127
189
|
* (`20-surface.md`, "The seam"). Not a designed IR — a stable public interchange
|
package/dist/src/emit.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Emit — the compile from the six-noun face to the
|
|
2
|
+
* Emit — the compile from the six-noun face to the seam's JSON pipe
|
|
3
3
|
* (`specs/architecture/20-surface.md`, "Emit — total, byte-reproducible, refusing";
|
|
4
4
|
* "The seam — one implementation"). The SDK implements **no semantics**: emit
|
|
5
|
-
* produces plain data — the declaration rows the engine reads
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* source), refuses before it
|
|
9
|
-
* byte-reproducible — double-emit verified at every
|
|
5
|
+
* produces plain data — the declaration rows the engine reads and, per projected
|
|
6
|
+
* member, its ordered typed fields and resolved prose body. The engine is the
|
|
7
|
+
* sole compiler of every projection and the whole lock; the SDK writes neither.
|
|
8
|
+
* Emit is total (members are the only source), refuses before it produces a byte
|
|
9
|
+
* on a broken source, and is byte-reproducible — double-emit verified at every
|
|
10
|
+
* run (law 5).
|
|
10
11
|
*/
|
|
11
12
|
import type { Harness } from "./assembly.js";
|
|
12
|
-
import type { Projection } from "./project.js";
|
|
13
13
|
import type { Declarations } from "./declarations.js";
|
|
14
14
|
/** How a `file()` asset's module-relative path resolves at emit. */
|
|
15
15
|
export interface ResolveOptions {
|
|
@@ -18,31 +18,38 @@ export interface ResolveOptions {
|
|
|
18
18
|
/** The addresses a mention may name — resolution-checked; a mention cannot dangle. */
|
|
19
19
|
readonly mentionable?: ReadonlySet<string>;
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
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. */
|
|
22
35
|
export interface EmitOptions {
|
|
23
36
|
/** Base dir a `file()` asset's module-relative path resolves against (default: cwd). */
|
|
24
37
|
readonly baseDir?: string;
|
|
25
|
-
/**
|
|
26
|
-
* The harness root the committed projection is read from so a re-emit carries
|
|
27
|
-
* install's placement lines through the whole-file re-emit (`20-surface.md`, the
|
|
28
|
-
* two-projectors seam). Absent reads no committed projection; [`writeEmit`]
|
|
29
|
-
* passes its `targetDir` here so a re-emit preserves them.
|
|
30
|
-
*/
|
|
31
|
-
readonly projectionDir?: string;
|
|
32
38
|
}
|
|
33
39
|
/**
|
|
34
|
-
* A full emit's compiled outputs — the seam the engine reads
|
|
35
|
-
*
|
|
36
|
-
*
|
|
40
|
+
* A full emit's compiled outputs — the whole seam the engine reads
|
|
41
|
+
* (`20-surface.md`, "The seam"). A pure function of the harness, so [`emit`]
|
|
42
|
+
* double-verifies it.
|
|
37
43
|
*/
|
|
38
44
|
export interface EmitResult {
|
|
39
|
-
/** The
|
|
40
|
-
readonly projections: readonly Projection[];
|
|
41
|
-
/** The `lock.toml` bytes — rollup rows plus the `[declaration]` families. */
|
|
42
|
-
readonly lock: string;
|
|
43
|
-
/** The declaration rows — the erased program the lock and JSON pipe both carry. */
|
|
45
|
+
/** The declaration rows — the erased program the lock's five families carry. */
|
|
44
46
|
readonly declarations: Declarations;
|
|
45
|
-
/** The
|
|
47
|
+
/** The projected members — the engine's sole input for every projection. */
|
|
48
|
+
readonly members: readonly PayloadMember[];
|
|
49
|
+
/**
|
|
50
|
+
* The internal versioned JSON pipe to the engine — not a designed IR. The
|
|
51
|
+
* SDK's whole output surface: printed to stdout, never written to a file.
|
|
52
|
+
*/
|
|
46
53
|
readonly seam: string;
|
|
47
54
|
/**
|
|
48
55
|
* The derived permission list — the union of every member's `needs`, deduped and
|
|
@@ -53,18 +60,10 @@ export interface EmitResult {
|
|
|
53
60
|
readonly permissions: readonly string[];
|
|
54
61
|
}
|
|
55
62
|
/**
|
|
56
|
-
* Compile the whole face in one deterministic pass: the
|
|
57
|
-
* rollup and its
|
|
58
|
-
*
|
|
63
|
+
* Compile the whole face in one deterministic pass: the declaration rows (its
|
|
64
|
+
* rollup and its five families) and every projected member's erased payload.
|
|
65
|
+
* Prose resolves once (`file()` assets read in, mentions resolution-checked
|
|
59
66
|
* against the harness's declared values). Double-emit verified — nondeterministic
|
|
60
67
|
* authoring is a loud failure, never a silent churn (law 5).
|
|
61
68
|
*/
|
|
62
69
|
export declare function emit(harness: Harness, options?: EmitOptions): EmitResult;
|
|
63
|
-
/**
|
|
64
|
-
* Run a full [`emit`] and write its committed artifacts under `targetDir`: the
|
|
65
|
-
* lock to `lock.toml` and each projection to its `.claude/**` path (parent
|
|
66
|
-
* directories created). Whole-file writes — a projection is regenerated, never
|
|
67
|
-
* patched. The JSON pipe is in-flight, not a committed artifact, so it is not
|
|
68
|
-
* written (`20-surface.md`, "the committed seam" is artifacts plus lock).
|
|
69
|
-
*/
|
|
70
|
-
export declare function writeEmit(harness: Harness, targetDir: string, options?: EmitOptions): EmitResult;
|
package/dist/src/emit.js
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Emit — the compile from the six-noun face to the
|
|
2
|
+
* Emit — the compile from the six-noun face to the seam's JSON pipe
|
|
3
3
|
* (`specs/architecture/20-surface.md`, "Emit — total, byte-reproducible, refusing";
|
|
4
4
|
* "The seam — one implementation"). The SDK implements **no semantics**: emit
|
|
5
|
-
* produces plain data — the declaration rows the engine reads
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* source), refuses before it
|
|
9
|
-
* byte-reproducible — double-emit verified at every
|
|
5
|
+
* produces plain data — the declaration rows the engine reads and, per projected
|
|
6
|
+
* member, its ordered typed fields and resolved prose body. The engine is the
|
|
7
|
+
* sole compiler of every projection and the whole lock; the SDK writes neither.
|
|
8
|
+
* Emit is total (members are the only source), refuses before it produces a byte
|
|
9
|
+
* on a broken source, and is byte-reproducible — double-emit verified at every
|
|
10
|
+
* run (law 5).
|
|
10
11
|
*/
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
12
|
+
import { resolve as resolvePath } from "node:path";
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
13
14
|
import { renderText } from "./prose.js";
|
|
14
15
|
import { permissionUnion } from "./needs.js";
|
|
15
|
-
import {
|
|
16
|
-
import { lockRow, stampLock } from "./lock.js";
|
|
17
|
-
import { compileDeclarations, declarationsToJson } from "./declarations.js";
|
|
16
|
+
import { SEAM_VERSION, compileDeclarations } from "./declarations.js";
|
|
18
17
|
/**
|
|
19
18
|
* Resolve a member's prose to its final body bytes: a `file()` asset is read in
|
|
20
19
|
* byte-for-byte; a `text` body's mentions are resolution-checked (loud on a
|
|
@@ -30,7 +29,7 @@ function resolveBody(member, options) {
|
|
|
30
29
|
if (prose === undefined)
|
|
31
30
|
return "";
|
|
32
31
|
if (prose.kind === "file") {
|
|
33
|
-
const assetPath =
|
|
32
|
+
const assetPath = fileSourcePath(member, options);
|
|
34
33
|
try {
|
|
35
34
|
return readFileSync(assetPath, "utf8");
|
|
36
35
|
}
|
|
@@ -71,7 +70,7 @@ function declaredAddresses(harness) {
|
|
|
71
70
|
return set;
|
|
72
71
|
}
|
|
73
72
|
/**
|
|
74
|
-
* The two declare-side refusals emit runs before it
|
|
73
|
+
* The two declare-side refusals emit runs before it produces a byte
|
|
75
74
|
* (`20-surface.md`, "Emit refuses before it writes"): a `satisfies` claim naming
|
|
76
75
|
* no declared requirement (a dangling join), and a `required` requirement no
|
|
77
76
|
* member fills (an unfilled required requirement).
|
|
@@ -115,23 +114,38 @@ function refuseBrokenSource(harness) {
|
|
|
115
114
|
function isProjected(member) {
|
|
116
115
|
return member.facts.locus.kind === "at";
|
|
117
116
|
}
|
|
118
|
-
/**
|
|
119
|
-
|
|
117
|
+
/**
|
|
118
|
+
* The resolved absolute path of a `file()` prose asset, or `undefined` for
|
|
119
|
+
* `text`/`blocks` prose (or no prose) — the lift's own-path detection
|
|
120
|
+
* (`specs/architecture/20-surface.md`, "surface authority is a declared
|
|
121
|
+
* posture": the lock is what names a path a projection, so the engine needs
|
|
122
|
+
* each `file()` member's true source path to tell a lifted member's own file
|
|
123
|
+
* apart from a generated one).
|
|
124
|
+
*/
|
|
125
|
+
function fileSourcePath(member, options) {
|
|
126
|
+
const prose = member.prose;
|
|
127
|
+
if (prose?.kind !== "file")
|
|
128
|
+
return undefined;
|
|
129
|
+
return resolvePath(options.baseDir ?? process.cwd(), prose.path);
|
|
130
|
+
}
|
|
131
|
+
/** The harness's projected members as payload members, deterministically kind-then-name ordered. */
|
|
132
|
+
function orderedMembers(harness, options) {
|
|
120
133
|
return [...harness.members]
|
|
121
134
|
.filter(isProjected)
|
|
122
135
|
.sort((a, b) => (a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0) ||
|
|
123
136
|
(a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
|
124
137
|
.map((member) => ({
|
|
125
|
-
|
|
138
|
+
kind: member.kind,
|
|
126
139
|
name: member.name,
|
|
127
140
|
fields: member.fields,
|
|
128
141
|
body: resolveBody(member, options),
|
|
142
|
+
source_path: fileSourcePath(member, options),
|
|
129
143
|
}));
|
|
130
144
|
}
|
|
131
145
|
/**
|
|
132
|
-
* Compile the whole face in one deterministic pass: the
|
|
133
|
-
* rollup and its
|
|
134
|
-
*
|
|
146
|
+
* Compile the whole face in one deterministic pass: the declaration rows (its
|
|
147
|
+
* rollup and its five families) and every projected member's erased payload.
|
|
148
|
+
* Prose resolves once (`file()` assets read in, mentions resolution-checked
|
|
135
149
|
* against the harness's declared values). Double-emit verified — nondeterministic
|
|
136
150
|
* authoring is a loud failure, never a silent churn (law 5).
|
|
137
151
|
*/
|
|
@@ -142,46 +156,20 @@ export function emit(harness, options = {}) {
|
|
|
142
156
|
baseDir: options.baseDir,
|
|
143
157
|
};
|
|
144
158
|
const compile = () => {
|
|
145
|
-
const
|
|
146
|
-
const projections = inputs.map((input) => projectMember(input, { projectionDir: options.projectionDir }));
|
|
147
|
-
const rows = inputs.map((input, i) => lockRow(input.facts.name, projections[i]));
|
|
159
|
+
const members = orderedMembers(harness, resolve);
|
|
148
160
|
const declarations = compileDeclarations(harness);
|
|
149
161
|
return {
|
|
150
|
-
projections,
|
|
151
|
-
lock: stampLock(rows, declarations),
|
|
152
162
|
declarations,
|
|
153
|
-
|
|
163
|
+
members,
|
|
164
|
+
seam: JSON.stringify({ version: SEAM_VERSION, declarations, members }, null, 2) + "\n",
|
|
154
165
|
permissions: permissionUnion(harness.members.flatMap((member) => [...member.needs])),
|
|
155
166
|
};
|
|
156
167
|
};
|
|
157
168
|
const first = compile();
|
|
158
169
|
const second = compile();
|
|
159
|
-
if (first.
|
|
160
|
-
first.seam !== second.seam ||
|
|
161
|
-
!sameProjections(first.projections, second.projections)) {
|
|
170
|
+
if (first.seam !== second.seam) {
|
|
162
171
|
throw new Error("double-emit divergence: two passes over the same harness produced different bytes — " +
|
|
163
172
|
"authoring code is nondeterministic (a timestamp? an unordered map?).");
|
|
164
173
|
}
|
|
165
174
|
return first;
|
|
166
175
|
}
|
|
167
|
-
/** Whether two projection lists are byte-identical, path and bytes both. */
|
|
168
|
-
function sameProjections(a, b) {
|
|
169
|
-
return a.length === b.length && a.every((p, i) => p.path === b[i].path && p.bytes === b[i].bytes);
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Run a full [`emit`] and write its committed artifacts under `targetDir`: the
|
|
173
|
-
* lock to `lock.toml` and each projection to its `.claude/**` path (parent
|
|
174
|
-
* directories created). Whole-file writes — a projection is regenerated, never
|
|
175
|
-
* patched. The JSON pipe is in-flight, not a committed artifact, so it is not
|
|
176
|
-
* written (`20-surface.md`, "the committed seam" is artifacts plus lock).
|
|
177
|
-
*/
|
|
178
|
-
export function writeEmit(harness, targetDir, options = {}) {
|
|
179
|
-
const result = emit(harness, { ...options, projectionDir: options.projectionDir ?? targetDir });
|
|
180
|
-
writeFileSync(join(targetDir, "lock.toml"), result.lock);
|
|
181
|
-
for (const projection of result.projections) {
|
|
182
|
-
const path = join(targetDir, projection.path);
|
|
183
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
184
|
-
writeFileSync(path, projection.bytes);
|
|
185
|
-
}
|
|
186
|
-
return result;
|
|
187
|
-
}
|
package/dist/src/genres.d.ts
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
* "A genre is a kind at the block locus"; ratified `specs/intent/00-intent.md`, the
|
|
4
4
|
* genre Decision). A genre value's meaning-carrying fields are prose leaves —
|
|
5
5
|
* authored strings, law-5 protected one by one — plus keyed sibling collections.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* This constructor carries the **shape only**: any predicate over a genre value
|
|
7
|
+
* is a clause some module ships, never here (`15-kinds.md`, the genre Decision).
|
|
8
|
+
* There is no prescribed genre ontology — a corpus that argues differently
|
|
9
|
+
* declares its own genres with the same machinery (`15-kinds.md`, "a genre is
|
|
10
|
+
* a full kind, and genre checks are data, never engine").
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
* (`20-surface.md`). The byte-identical posture-2 fence render awaits
|
|
12
|
+
* `genreValue()` is the posture-3 spelling — a fully composed value passed to
|
|
13
|
+
* `blocks()` (`20-surface.md`). The byte-identical posture-2 fence render awaits
|
|
12
14
|
* `(genre-fence-format)`, deferred until its first consumer lands.
|
|
13
15
|
*/
|
|
14
16
|
/**
|
|
@@ -18,7 +20,7 @@
|
|
|
18
20
|
* the leaf-address Decision).
|
|
19
21
|
*/
|
|
20
22
|
export interface GenreValue {
|
|
21
|
-
/** The genre name —
|
|
23
|
+
/** The genre name — a project's own, never a built-in prescribed ontology. */
|
|
22
24
|
readonly genre: string;
|
|
23
25
|
/** The value's key — the identity a leaf address carries (`surface-authority`). */
|
|
24
26
|
readonly key: string;
|
|
@@ -27,35 +29,6 @@ export interface GenreValue {
|
|
|
27
29
|
/** Keyed sibling collections: collection → entry key → field → authored string. */
|
|
28
30
|
readonly collections: Readonly<Record<string, Readonly<Record<string, Readonly<Record<string, string>>>>>>;
|
|
29
31
|
}
|
|
30
|
-
/** A rejected alternative: keyed by option slug, its rationale a prose leaf. */
|
|
31
|
-
export interface Alternative {
|
|
32
|
-
readonly because: string;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* The `decision` genre — the Chosen/Rejected convention, typed. Sibling
|
|
36
|
-
* collections are keyed by option slug, never positional: positional addresses
|
|
37
|
-
* die on insertion and reorder, which is exactly when impact must survive.
|
|
38
|
-
*/
|
|
39
|
-
export declare function decision(init: {
|
|
40
|
-
key: string;
|
|
41
|
-
chosen: string;
|
|
42
|
-
rejected?: Readonly<Record<string, Alternative>>;
|
|
43
|
-
}): GenreValue;
|
|
44
|
-
/** The `law` genre — a numbered law's statement with its named bounds. */
|
|
45
|
-
export declare function law(init: {
|
|
46
|
-
key: string;
|
|
47
|
-
statement: string;
|
|
48
|
-
bounds?: Readonly<Record<string, {
|
|
49
|
-
claim: string;
|
|
50
|
-
}>>;
|
|
51
|
-
}): GenreValue;
|
|
52
|
-
/** The `bound` genre — the honest bound: claim, deferral, unlock condition. */
|
|
53
|
-
export declare function bound(init: {
|
|
54
|
-
key: string;
|
|
55
|
-
claim: string;
|
|
56
|
-
deferred: string;
|
|
57
|
-
unlock: string;
|
|
58
|
-
}): GenreValue;
|
|
59
32
|
/** A project's own genre — the same machinery, an author-declared shape. */
|
|
60
33
|
export declare function genreValue(init: {
|
|
61
34
|
genre: string;
|
package/dist/src/genres.js
CHANGED
|
@@ -3,49 +3,16 @@
|
|
|
3
3
|
* "A genre is a kind at the block locus"; ratified `specs/intent/00-intent.md`, the
|
|
4
4
|
* genre Decision). A genre value's meaning-carrying fields are prose leaves —
|
|
5
5
|
* authored strings, law-5 protected one by one — plus keyed sibling collections.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* This constructor carries the **shape only**: any predicate over a genre value
|
|
7
|
+
* is a clause some module ships, never here (`15-kinds.md`, the genre Decision).
|
|
8
|
+
* There is no prescribed genre ontology — a corpus that argues differently
|
|
9
|
+
* declares its own genres with the same machinery (`15-kinds.md`, "a genre is
|
|
10
|
+
* a full kind, and genre checks are data, never engine").
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
* (`20-surface.md`). The byte-identical posture-2 fence render awaits
|
|
12
|
+
* `genreValue()` is the posture-3 spelling — a fully composed value passed to
|
|
13
|
+
* `blocks()` (`20-surface.md`). The byte-identical posture-2 fence render awaits
|
|
12
14
|
* `(genre-fence-format)`, deferred until its first consumer lands.
|
|
13
15
|
*/
|
|
14
|
-
/**
|
|
15
|
-
* The `decision` genre — the Chosen/Rejected convention, typed. Sibling
|
|
16
|
-
* collections are keyed by option slug, never positional: positional addresses
|
|
17
|
-
* die on insertion and reorder, which is exactly when impact must survive.
|
|
18
|
-
*/
|
|
19
|
-
export function decision(init) {
|
|
20
|
-
return {
|
|
21
|
-
genre: "decision",
|
|
22
|
-
key: init.key,
|
|
23
|
-
leaves: { chosen: init.chosen },
|
|
24
|
-
collections: {
|
|
25
|
-
rejected: Object.fromEntries(Object.entries(init.rejected ?? {}).map(([slug, alt]) => [slug, { because: alt.because }])),
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
/** The `law` genre — a numbered law's statement with its named bounds. */
|
|
30
|
-
export function law(init) {
|
|
31
|
-
return {
|
|
32
|
-
genre: "law",
|
|
33
|
-
key: init.key,
|
|
34
|
-
leaves: { statement: init.statement },
|
|
35
|
-
collections: {
|
|
36
|
-
bounds: Object.fromEntries(Object.entries(init.bounds ?? {}).map(([slug, bound]) => [slug, { claim: bound.claim }])),
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
/** The `bound` genre — the honest bound: claim, deferral, unlock condition. */
|
|
41
|
-
export function bound(init) {
|
|
42
|
-
return {
|
|
43
|
-
genre: "bound",
|
|
44
|
-
key: init.key,
|
|
45
|
-
leaves: { claim: init.claim, deferred: init.deferred, unlock: init.unlock },
|
|
46
|
-
collections: {},
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
16
|
/** A project's own genre — the same machinery, an author-declared shape. */
|
|
50
17
|
export function genreValue(init) {
|
|
51
18
|
return {
|