@githolon/dsl 0.4.0 → 0.5.1
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/package.json +2 -1
- package/src/build_package.ts +4 -0
- package/src/capability_exports.ts +55 -0
- package/src/codegen_dart.ts +9 -0
- package/src/codegen_proof.ts +140 -12
- package/src/compile_package_main.ts +255 -14
- package/src/directive.ts +35 -10
- package/src/engine_entry.ts +5 -1
- package/src/framework/capability.ts +215 -0
- package/src/framework/impure_capability.ts +25 -3
- package/src/framework/workspaces.ts +129 -0
- package/src/index.ts +9 -0
- package/src/manifest.ts +103 -0
- package/src/read.ts +29 -0
- package/src/stable_ids.ts +226 -0
- package/src/stable_ids_types.ts +40 -0
- package/src/usd.ts +54 -0
- package/src/usd_layers.ts +65 -1
- package/src/wire_encode.ts +18 -0
- package/dart/.dart_tool/package_config.json +0 -328
- package/dart/.dart_tool/package_graph.json +0 -485
- package/dart/.dart_tool/pub/bin/test/test.dart-3.11.5.snapshot +0 -0
- package/dart/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjU= +0 -0
- package/dart/.dart_tool/version +0 -1
- package/dart/build/native_assets/macos/native_assets.json +0 -1
- package/dart/build/test_cache/build/89a6598c8854ed031dfc25d83c80860e.cache.dill.track.dill +0 -0
- package/dart/build/unit_test_assets/AssetManifest.bin +0 -0
- package/dart/build/unit_test_assets/FontManifest.json +0 -1
- package/dart/build/unit_test_assets/NOTICES.Z +0 -0
- package/dart/build/unit_test_assets/NativeAssetsManifest.json +0 -1
- package/dart/build/unit_test_assets/shaders/ink_sparkle.frag +0 -0
- package/dart/build/unit_test_assets/shaders/stretch_effect.frag +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
|
|
2
|
+
// remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
|
|
3
|
+
// wasm32-wasip1 artifact {kernel · projection · embedded
|
|
4
|
+
// QuickJS engine} on V8 + WASI-shim, byte-identical everywhere. V8 = portability; the one
|
|
5
|
+
// wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
|
|
6
|
+
// If a file isn't this / hosting this / authoring for this / proving this — it's gone.
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* STABLE IDENTIFIERS (#58) — names are LABELS; identity is MINTED by the compiler at
|
|
10
|
+
* FIRST APPEARANCE and never dev-assigned.
|
|
11
|
+
*
|
|
12
|
+
* Every aggregate, every field, and the domain itself carry a compiler-minted stable
|
|
13
|
+
* id (`sid`). The sid is the entity's IDENTITY across law upgrades: the evolve gate
|
|
14
|
+
* diffs old vs new law BY SID (a rename — same sid, new label — is metadata and
|
|
15
|
+
* admits silently; a true removal/retype is a typed refusal unless the upgrade intent
|
|
16
|
+
* carries a disposition), and the projection folds via the sid lineage so a renamed
|
|
17
|
+
* aggregate's old-era rows cohere under the new label automatically.
|
|
18
|
+
*
|
|
19
|
+
* MINTING (first appearance, deterministic): a sid is the first 16 hex chars of
|
|
20
|
+
* sha256 over a versioned scope string — so a clean checkout re-mints the SAME id
|
|
21
|
+
* for the same first appearance (no lockfile needed for the common case):
|
|
22
|
+
*
|
|
23
|
+
* domain sid("domain:<domainName>")
|
|
24
|
+
* aggregate sid("aggregate:<domainSid>:<aggName>")
|
|
25
|
+
* field sid("field:<aggSid>:<fieldName>")
|
|
26
|
+
*
|
|
27
|
+
* Note the NESTING: field sids mint under the AGGREGATE SID (not its name), so a
|
|
28
|
+
* renamed aggregate's fields keep their identities for free.
|
|
29
|
+
*
|
|
30
|
+
* CONTINUITY (across compiles): `nomos-compile` reads the PRIOR build's identity
|
|
31
|
+
* manifests / the committed `nomos.stable-ids.json` lockfile and derives a continuity
|
|
32
|
+
* table per domain — match by name first; then the UNAMBIGUOUS-PAIR rule: exactly ONE
|
|
33
|
+
* disappeared name + exactly ONE same-shaped appeared name is an inferred RENAME (the
|
|
34
|
+
* sid is carried, the old label recorded in `was`, and the compile summary prints the
|
|
35
|
+
* inference). Anything still unmatched is left for THE EVOLVE GATE's typed question
|
|
36
|
+
* at deploy — the dev answers in the upgrade intent's disposition
|
|
37
|
+
* ({retired | retyped | rebinds}).
|
|
38
|
+
*
|
|
39
|
+
* `was` is the LABEL LINEAGE: every label this sid previously wore, oldest first.
|
|
40
|
+
* It is compiler-derived (never dev-written) and HASH-BEARING (it is law): the
|
|
41
|
+
* projection reads it to fold old-era field/type labels into the current ones.
|
|
42
|
+
*
|
|
43
|
+
* BUILD-TIME ONLY (imports `node:crypto`): reached via `@githolon/dsl/stable-ids` /
|
|
44
|
+
* `@githolon/dsl/manifest`, never the runtime barrel. The pure TYPE surface lives in
|
|
45
|
+
* `stable_ids_types.ts` so the runtime type graph stays node-free.
|
|
46
|
+
*/
|
|
47
|
+
import { createHash } from "node:crypto";
|
|
48
|
+
import type { WireSchema } from "./wire.js";
|
|
49
|
+
import type { StableAggregateId, StableFieldId, StableIds } from "./stable_ids_types.js";
|
|
50
|
+
|
|
51
|
+
export type { StableAggregateId, StableFieldId, StableIds } from "./stable_ids_types.js";
|
|
52
|
+
|
|
53
|
+
/** Mint one stable id: first 16 hex chars of sha256 over the versioned scope. */
|
|
54
|
+
export function mintStableId(scope: string): string {
|
|
55
|
+
return createHash("sha256").update(`nomos-sid:v1:${scope}`, "utf8").digest("hex").slice(0, 16);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function mintDomainSid(domainName: string): string {
|
|
59
|
+
return mintStableId(`domain:${domainName}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function mintAggregateSid(domainSid: string, aggName: string): string {
|
|
63
|
+
return mintStableId(`aggregate:${domainSid}:${aggName}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function mintFieldSid(aggSid: string, fieldName: string): string {
|
|
67
|
+
return mintStableId(`field:${aggSid}:${fieldName}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** One inferred rename, printed by the compile summary (visibility for the silent lane). */
|
|
71
|
+
export interface InferredRename {
|
|
72
|
+
/** `"<Agg>"` for an aggregate rename, `"<Agg>.<field>"` for a field rename. */
|
|
73
|
+
readonly what: string;
|
|
74
|
+
readonly from: string;
|
|
75
|
+
readonly to: string;
|
|
76
|
+
readonly sid: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Derive the stable-id CONTINUITY for one domain from its PRIOR manifest's stableIds
|
|
81
|
+
* (the chain/build holds v's sids) against the CURRENT compile's names.
|
|
82
|
+
*
|
|
83
|
+
* 1. match by NAME — same name carries its sid + lineage forward;
|
|
84
|
+
* 2. the UNAMBIGUOUS-PAIR rule — exactly one disappeared + one appeared name of the
|
|
85
|
+
* same kind AND shape (aggregate: same field-name set; field: same driver AND
|
|
86
|
+
* same read kind, with exactly ONE matching appearance) is an inferred RENAME:
|
|
87
|
+
* the sid is carried, `was` extended;
|
|
88
|
+
* 3. anything else mints fresh at first appearance (a leftover disappearance is the
|
|
89
|
+
* EVOLVE GATE's question at deploy — never silently resolved here).
|
|
90
|
+
*
|
|
91
|
+
* Returns the continuity keyed by CURRENT names (exactly the shape `domainManifest`
|
|
92
|
+
* consumes) plus the inferred renames for the compile summary.
|
|
93
|
+
*/
|
|
94
|
+
export function deriveStableIdContinuity(
|
|
95
|
+
domainName: string,
|
|
96
|
+
currentAggregates: { id: string; schema: WireSchema; kinds?: Record<string, string> }[],
|
|
97
|
+
prior: StableIds | undefined,
|
|
98
|
+
/** The prior canonical manifest's aggregate schemas (keyed by PRIOR name) — when
|
|
99
|
+
* supplied, tightens the field-pair rule to same-driver only. */
|
|
100
|
+
priorSchemas?: Record<string, WireSchema>,
|
|
101
|
+
): { continuity: StableIds; inferred: InferredRename[] } {
|
|
102
|
+
const inferred: InferredRename[] = [];
|
|
103
|
+
const domainSid = prior?.domain ?? mintDomainSid(domainName);
|
|
104
|
+
|
|
105
|
+
const priorAggs = prior?.aggregates ?? {};
|
|
106
|
+
const currentNames = new Set(currentAggregates.map((a) => a.id));
|
|
107
|
+
|
|
108
|
+
// Pass 1 — name matches.
|
|
109
|
+
const resolved: Record<
|
|
110
|
+
string,
|
|
111
|
+
{ sid: string; was?: string[]; priorFields?: Record<string, StableFieldId>; priorSchema?: WireSchema }
|
|
112
|
+
> = {};
|
|
113
|
+
for (const agg of currentAggregates) {
|
|
114
|
+
const p = priorAggs[agg.id];
|
|
115
|
+
if (p !== undefined) {
|
|
116
|
+
resolved[agg.id] = {
|
|
117
|
+
sid: p.sid,
|
|
118
|
+
...(p.was !== undefined ? { was: p.was } : {}),
|
|
119
|
+
priorFields: p.fields,
|
|
120
|
+
...(priorSchemas?.[agg.id] !== undefined ? { priorSchema: priorSchemas[agg.id] } : {}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Pass 2 — the unambiguous aggregate pair (one disappeared, one appeared, same shape).
|
|
126
|
+
const disappeared = Object.keys(priorAggs).filter((n) => !currentNames.has(n));
|
|
127
|
+
const appeared = currentAggregates.filter((a) => resolved[a.id] === undefined);
|
|
128
|
+
if (disappeared.length === 1 && appeared.length === 1) {
|
|
129
|
+
const oldName = disappeared[0]!;
|
|
130
|
+
const oldAgg = priorAggs[oldName]!;
|
|
131
|
+
const newAgg = appeared[0]!;
|
|
132
|
+
// Shape gate: the renamed aggregate must keep its field-name set (a rename is a
|
|
133
|
+
// LABEL move, not a remodel) — a remodel goes to the evolve gate's typed question.
|
|
134
|
+
const oldFields = Object.keys(oldAgg.fields).sort().join(" ");
|
|
135
|
+
const newFields = Object.keys(newAgg.schema).sort().join(" ");
|
|
136
|
+
if (oldFields === newFields) {
|
|
137
|
+
resolved[newAgg.id] = {
|
|
138
|
+
sid: oldAgg.sid,
|
|
139
|
+
was: [...(oldAgg.was ?? []), oldName],
|
|
140
|
+
priorFields: oldAgg.fields,
|
|
141
|
+
...(priorSchemas?.[oldName] !== undefined ? { priorSchema: priorSchemas[oldName] } : {}),
|
|
142
|
+
};
|
|
143
|
+
inferred.push({ what: newAgg.id, from: oldName, to: newAgg.id, sid: oldAgg.sid });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Per-aggregate field continuity (name match + the unambiguous field pair).
|
|
148
|
+
const aggregates: Record<string, StableAggregateId> = {};
|
|
149
|
+
for (const agg of currentAggregates) {
|
|
150
|
+
const r = resolved[agg.id];
|
|
151
|
+
const aggSid = r?.sid ?? mintAggregateSid(domainSid, agg.id);
|
|
152
|
+
const priorFields = r?.priorFields ?? {};
|
|
153
|
+
const fieldNames = Object.keys(agg.schema);
|
|
154
|
+
const fields: Record<string, StableFieldId> = {};
|
|
155
|
+
for (const f of fieldNames) {
|
|
156
|
+
const pf = priorFields[f];
|
|
157
|
+
if (pf !== undefined) {
|
|
158
|
+
fields[f] = { sid: pf.sid, ...(pf.was !== undefined ? { was: pf.was } : {}) };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const fDisappeared = Object.keys(priorFields).filter((n) => !fieldNames.includes(n));
|
|
162
|
+
const fAppeared = fieldNames.filter((n) => fields[n] === undefined);
|
|
163
|
+
if (fDisappeared.length === 1 && fAppeared.length >= 1) {
|
|
164
|
+
const oldF = fDisappeared[0]!;
|
|
165
|
+
// Shape gate for a field rename: the DRIVER (merge law) AND the KIND (read
|
|
166
|
+
// type) must be unchanged — a rename is a LABEL move. Among the appeared
|
|
167
|
+
// names, the pair binds IFF EXACTLY ONE matches the disappeared field's
|
|
168
|
+
// shape (so "rename + add a field" in one release still infers; two
|
|
169
|
+
// same-shaped appearances stay ambiguous — the evolve gate asks).
|
|
170
|
+
const priorDriver = priorFieldDriver(r, oldF);
|
|
171
|
+
const priorKind = priorFields[oldF]?.kind;
|
|
172
|
+
const candidates = fAppeared.filter((newF) => {
|
|
173
|
+
const driverOk =
|
|
174
|
+
priorDriver === undefined || priorDriver === JSON.stringify(agg.schema[newF]);
|
|
175
|
+
const newKind = agg.kinds?.[newF];
|
|
176
|
+
const kindOk = priorKind === undefined || newKind === undefined || priorKind === newKind;
|
|
177
|
+
return driverOk && kindOk;
|
|
178
|
+
});
|
|
179
|
+
if (candidates.length === 1) {
|
|
180
|
+
const newF = candidates[0]!;
|
|
181
|
+
const pf = priorFields[oldF]!;
|
|
182
|
+
fields[newF] = { sid: pf.sid, was: [...(pf.was ?? []), oldF] };
|
|
183
|
+
inferred.push({ what: `${agg.id}.${newF}`, from: oldF, to: newF, sid: pf.sid });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (const f of fieldNames) {
|
|
187
|
+
if (fields[f] === undefined) fields[f] = { sid: mintFieldSid(aggSid, f) };
|
|
188
|
+
}
|
|
189
|
+
aggregates[agg.id] = {
|
|
190
|
+
sid: aggSid,
|
|
191
|
+
...(r?.was !== undefined ? { was: r.was } : {}),
|
|
192
|
+
fields,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { continuity: { v: 1, domain: domainSid, aggregates }, inferred };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** The prior driver snapshot for a field, when the caller threaded the prior canonical
|
|
200
|
+
* manifest's schemas through `priorSchemas` (the stableIds block itself carries no
|
|
201
|
+
* drivers). Returns `undefined` (pair allowed on name-cardinality alone) when no
|
|
202
|
+
* snapshot is available. */
|
|
203
|
+
function priorFieldDriver(
|
|
204
|
+
resolved: { priorSchema?: WireSchema } | undefined,
|
|
205
|
+
oldField: string,
|
|
206
|
+
): string | undefined {
|
|
207
|
+
const schema = resolved?.priorSchema;
|
|
208
|
+
if (schema === undefined) return undefined;
|
|
209
|
+
const d = schema[oldField];
|
|
210
|
+
return d === undefined ? undefined : JSON.stringify(d);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Parse the `stableIds` block out of one PRIOR canonical-manifest JSON string —
|
|
214
|
+
* tolerant (absent/era-old manifests return `undefined`, never a throw). */
|
|
215
|
+
export function stableIdsFromManifestJson(manifestJson: string): StableIds | undefined {
|
|
216
|
+
try {
|
|
217
|
+
const parsed = JSON.parse(manifestJson) as { stableIds?: StableIds };
|
|
218
|
+
const s = parsed.stableIds;
|
|
219
|
+
if (s && s.v === 1 && typeof s.domain === "string" && s.aggregates && typeof s.aggregates === "object") {
|
|
220
|
+
return s;
|
|
221
|
+
}
|
|
222
|
+
return undefined;
|
|
223
|
+
} catch {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
|
|
2
|
+
// remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
|
|
3
|
+
// wasm32-wasip1 artifact {kernel · projection · embedded
|
|
4
|
+
// QuickJS engine}. If a file isn't this / hosting this / authoring for this / proving this — it's gone.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* STABLE-ID TYPES (#58) — the pure type surface of `stable_ids.ts`, split out so the
|
|
8
|
+
* RUNTIME type graph (DomainModule's optional `stableIdContinuity`) never drags
|
|
9
|
+
* `node:crypto` into a tenant scaffold's typecheck (scaffolds compile without
|
|
10
|
+
* `@types/node`). The minting/derivation machinery lives in `stable_ids.ts`
|
|
11
|
+
* (build-time only — `nomos-compile` + `@githolon/dsl/manifest`).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** One field's stable identity: the minted sid + the labels it previously wore. */
|
|
15
|
+
export interface StableFieldId {
|
|
16
|
+
readonly sid: string;
|
|
17
|
+
/** Prior labels of this sid, oldest first. OMITTED when the label never changed. */
|
|
18
|
+
readonly was?: string[];
|
|
19
|
+
/** The field's READ KIND (`string`/`int`/`json`/…): the retype arm of the evolve
|
|
20
|
+
* gate diffs it per sid (a driver change alone misses `t.string()` → `t.int()`).
|
|
21
|
+
* Always set at emission; continuity input may omit it (the current module is the
|
|
22
|
+
* source of the CURRENT kind). */
|
|
23
|
+
readonly kind?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** One aggregate's stable identity: sid + label lineage + per-field sids (keyed by CURRENT field name). */
|
|
27
|
+
export interface StableAggregateId {
|
|
28
|
+
readonly sid: string;
|
|
29
|
+
readonly was?: string[];
|
|
30
|
+
readonly fields: Record<string, StableFieldId>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A domain's full stable-id surface — the `stableIds` canonical-manifest key / `nomosStableIds` USD key. */
|
|
34
|
+
export interface StableIds {
|
|
35
|
+
readonly v: 1;
|
|
36
|
+
/** The domain's own minted sid. */
|
|
37
|
+
readonly domain: string;
|
|
38
|
+
/** Aggregates keyed by CURRENT name (the label). */
|
|
39
|
+
readonly aggregates: Record<string, StableAggregateId>;
|
|
40
|
+
}
|
package/src/usd.ts
CHANGED
|
@@ -188,6 +188,31 @@ export interface UsdLayer {
|
|
|
188
188
|
/** Declared workspace invariants `{id, on}` the gate executes, sorted by id. */
|
|
189
189
|
readonly workspaceInvariants?: { id: string; on: string }[];
|
|
190
190
|
};
|
|
191
|
+
/**
|
|
192
|
+
* STABLE IDENTIFIERS (#58 — names are labels, identity is minted). The layer's
|
|
193
|
+
* domain/aggregate/field sids + label lineages, carried verbatim from the canonical
|
|
194
|
+
* manifest's `stableIds`. ALWAYS present on post-#58-compiled law (the one
|
|
195
|
+
* deliberate hash-mover); ABSENT on every bundle compiled before — the era key the
|
|
196
|
+
* EVOLVE GATE and the projection's sid-lineage fold switch on. A law-upgrade deploy
|
|
197
|
+
* whose old AND new packages both carry this key is DIFFED BY SID at the one gate.
|
|
198
|
+
*/
|
|
199
|
+
readonly nomosStableIds?: import("./stable_ids_types.js").StableIds;
|
|
200
|
+
/**
|
|
201
|
+
* THE CAPTURED-READ GATE DECLARATION (#58). Present IFF ≥1 directive of this layer
|
|
202
|
+
* declares a captured-read query (`.reads(someQuery)`): the law's EXPLICIT opt-in
|
|
203
|
+
* to `nomos.read` at the one gate — the engine serves declared reads LIVE from the
|
|
204
|
+
* pre-apply state at author, replays the captured footprint at verify, and the gate
|
|
205
|
+
* RE-DERIVES each captured read at the pre-apply position (typed read-conflict on
|
|
206
|
+
* drift — CAS as law). OMITTED for read-free law (byte-identical hash — the exact
|
|
207
|
+
* `nomosInvariantGate` era discipline; bundles without the key behave as before).
|
|
208
|
+
*/
|
|
209
|
+
readonly nomosReadGate?: {
|
|
210
|
+
readonly v: 1;
|
|
211
|
+
/** Every declared read query's recipe, keyed by query id. */
|
|
212
|
+
readonly queries: Record<string, { key: string[]; returns: string }>;
|
|
213
|
+
/** directive id → the SORTED query ids its plan may read. */
|
|
214
|
+
readonly directives: Record<string, string[]>;
|
|
215
|
+
};
|
|
191
216
|
}
|
|
192
217
|
|
|
193
218
|
/** The reserved variant name declaring a set's DEFAULT (USD has a default variant). */
|
|
@@ -267,12 +292,41 @@ export function emitUsd(
|
|
|
267
292
|
const wsInvariants = [...(l.module.workspaceInvariants ?? [])]
|
|
268
293
|
.map((w) => ({ id: w.id, on: w.on }))
|
|
269
294
|
.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
295
|
+
// THE CAPTURED-READ GATE DECLARATION (#58): collected from the canonical
|
|
296
|
+
// manifest's per-directive `capturedReads` (omit-when-empty — read-free law's
|
|
297
|
+
// USD is byte-identical apart from the always-on stableIds key).
|
|
298
|
+
const rgQueries = new Map<string, { key: string[]; returns: string }>();
|
|
299
|
+
const rgDirectives = new Map<string, string[]>();
|
|
300
|
+
for (const d of manifest.directives) {
|
|
301
|
+
if (d.capturedReads === undefined || d.capturedReads.length === 0) continue;
|
|
302
|
+
rgDirectives.set(d.id, d.capturedReads.map((r) => r.queryId));
|
|
303
|
+
for (const r of d.capturedReads) {
|
|
304
|
+
rgQueries.set(r.queryId, { key: [...r.key], returns: r.returns });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// SORTED-KEY records (byte-determinism — the layered flatten must reproduce
|
|
308
|
+
// these bytes exactly; see usd_layers.ts).
|
|
309
|
+
const readGateQueries: Record<string, { key: string[]; returns: string }> = {};
|
|
310
|
+
for (const k of [...rgQueries.keys()].sort()) readGateQueries[k] = rgQueries.get(k)!;
|
|
311
|
+
const readGateDirectives: Record<string, string[]> = {};
|
|
312
|
+
for (const k of [...rgDirectives.keys()].sort()) readGateDirectives[k] = rgDirectives.get(k)!;
|
|
270
313
|
return {
|
|
271
314
|
path: l.path,
|
|
272
315
|
prims: encodeModuleToPrims(l.path, l.module),
|
|
273
316
|
...(manifest.queries !== undefined ? { queries: manifest.queries } : {}),
|
|
274
317
|
...(manifest.deriveds !== undefined ? { deriveds: manifest.deriveds } : {}),
|
|
275
318
|
...(manifest.combineds !== undefined ? { combineds: manifest.combineds } : {}),
|
|
319
|
+
// STABLE IDS (#58): every post-#58 compile carries them (the era key).
|
|
320
|
+
...(manifest.stableIds !== undefined ? { nomosStableIds: manifest.stableIds } : {}),
|
|
321
|
+
...(Object.keys(readGateDirectives).length > 0
|
|
322
|
+
? {
|
|
323
|
+
nomosReadGate: {
|
|
324
|
+
v: 1 as const,
|
|
325
|
+
queries: readGateQueries,
|
|
326
|
+
directives: readGateDirectives,
|
|
327
|
+
},
|
|
328
|
+
}
|
|
329
|
+
: {}),
|
|
276
330
|
...(invariantAggregates.length > 0 || wsInvariants.length > 0
|
|
277
331
|
? {
|
|
278
332
|
nomosInvariantGate: {
|
package/src/usd_layers.ts
CHANGED
|
@@ -340,6 +340,14 @@ export function usdaLayerText(
|
|
|
340
340
|
` {`,
|
|
341
341
|
` custom string nomos:kind = "domain"`,
|
|
342
342
|
` custom string nomos:domain = ${usdaString(domain)}`,
|
|
343
|
+
// STABLE IDS + the captured-read gate (#58): layer-level law keys, hex-carried
|
|
344
|
+
// so the .usda round-trips byte-faithfully to the IR layer.
|
|
345
|
+
...(layer.nomosStableIds !== undefined
|
|
346
|
+
? [` custom string nomos:stableIdsHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosStableIds)))}`]
|
|
347
|
+
: []),
|
|
348
|
+
...(layer.nomosReadGate !== undefined
|
|
349
|
+
? [` custom string nomos:readGateHex = ${usdaString(hexUtf8(JSON.stringify(layer.nomosReadGate)))}`]
|
|
350
|
+
: []),
|
|
343
351
|
``,
|
|
344
352
|
...body,
|
|
345
353
|
` }`,
|
|
@@ -456,12 +464,52 @@ export function flattenLayeredUsd(layers: readonly UsdLayer[]): UsdDocument {
|
|
|
456
464
|
const queries = foldById<UsdQuery>(stack.map((l) => l.queries));
|
|
457
465
|
const deriveds = foldById<UsdDerived>(stack.map((l) => l.deriveds));
|
|
458
466
|
const combineds = foldById<UsdCombined>(stack.map((l) => l.combineds));
|
|
467
|
+
// STABLE IDS (#58): fold per-module stableIds — domain sid from the latest
|
|
468
|
+
// declaring layer (they coincide by construction: one domain, one mint scope),
|
|
469
|
+
// aggregates merged later-wins per CURRENT name, SORTED keys (byte-determinism
|
|
470
|
+
// against the composed emission's sorted-aggregate insertion order).
|
|
471
|
+
let stableIds: UsdLayer["nomosStableIds"];
|
|
472
|
+
{
|
|
473
|
+
const aggs = new Map<string, NonNullable<UsdLayer["nomosStableIds"]>["aggregates"][string]>();
|
|
474
|
+
let domainSid: string | undefined;
|
|
475
|
+
for (const l of stack) {
|
|
476
|
+
if (l.nomosStableIds === undefined) continue;
|
|
477
|
+
domainSid = l.nomosStableIds.domain;
|
|
478
|
+
for (const [name, a] of Object.entries(l.nomosStableIds.aggregates)) aggs.set(name, a);
|
|
479
|
+
}
|
|
480
|
+
if (domainSid !== undefined) {
|
|
481
|
+
const aggregates: NonNullable<UsdLayer["nomosStableIds"]>["aggregates"] = {};
|
|
482
|
+
for (const k of [...aggs.keys()].sort()) aggregates[k] = aggs.get(k)!;
|
|
483
|
+
stableIds = { v: 1, domain: domainSid, aggregates };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
// The captured-read gate (#58): queries + per-directive read sets merged
|
|
487
|
+
// later-wins, SORTED keys (matching the composed emission).
|
|
488
|
+
let readGate: UsdLayer["nomosReadGate"];
|
|
489
|
+
{
|
|
490
|
+
const q = new Map<string, { key: string[]; returns: string }>();
|
|
491
|
+
const d = new Map<string, string[]>();
|
|
492
|
+
for (const l of stack) {
|
|
493
|
+
if (l.nomosReadGate === undefined) continue;
|
|
494
|
+
for (const [id, spec] of Object.entries(l.nomosReadGate.queries)) q.set(id, spec);
|
|
495
|
+
for (const [id, reads] of Object.entries(l.nomosReadGate.directives)) d.set(id, reads);
|
|
496
|
+
}
|
|
497
|
+
if (d.size > 0) {
|
|
498
|
+
const queriesRec: Record<string, { key: string[]; returns: string }> = {};
|
|
499
|
+
for (const k of [...q.keys()].sort()) queriesRec[k] = q.get(k)!;
|
|
500
|
+
const directivesRec: Record<string, string[]> = {};
|
|
501
|
+
for (const k of [...d.keys()].sort()) directivesRec[k] = d.get(k)!;
|
|
502
|
+
readGate = { v: 1, queries: queriesRec, directives: directivesRec };
|
|
503
|
+
}
|
|
504
|
+
}
|
|
459
505
|
return {
|
|
460
506
|
path,
|
|
461
507
|
prims: [...prims.keys()].sort().map((p) => prims.get(p)!),
|
|
462
508
|
...(queries !== undefined ? { queries } : {}),
|
|
463
509
|
...(deriveds !== undefined ? { deriveds } : {}),
|
|
464
510
|
...(combineds !== undefined ? { combineds } : {}),
|
|
511
|
+
...(stableIds !== undefined ? { nomosStableIds: stableIds } : {}),
|
|
512
|
+
...(readGate !== undefined ? { nomosReadGate: readGate } : {}),
|
|
465
513
|
};
|
|
466
514
|
});
|
|
467
515
|
|
|
@@ -579,9 +627,21 @@ function attrStringArray(prim: ParsedPrim, name: string): string[] {
|
|
|
579
627
|
export function parseUsdaDocument(text: string): UsdDocument {
|
|
580
628
|
const prims = parseUsdaPrims(text);
|
|
581
629
|
const domainByScope = new Map<string, string>();
|
|
630
|
+
const stableIdsByDomain = new Map<string, UsdLayer["nomosStableIds"]>();
|
|
631
|
+
const readGateByDomain = new Map<string, UsdLayer["nomosReadGate"]>();
|
|
582
632
|
for (const p of prims) {
|
|
583
633
|
if (p.attrs.get("nomos:kind") === "domain") {
|
|
584
|
-
|
|
634
|
+
const domain = attrString(p, "nomos:domain");
|
|
635
|
+
domainByScope.set(p.path, domain);
|
|
636
|
+
// STABLE IDS + the captured-read gate (#58): the hex-carried layer-level keys.
|
|
637
|
+
const sidHex = p.attrs.get("nomos:stableIdsHex");
|
|
638
|
+
if (typeof sidHex === "string") {
|
|
639
|
+
stableIdsByDomain.set(domain, JSON.parse(unhexUtf8(sidHex)) as UsdLayer["nomosStableIds"]);
|
|
640
|
+
}
|
|
641
|
+
const rgHex = p.attrs.get("nomos:readGateHex");
|
|
642
|
+
if (typeof rgHex === "string") {
|
|
643
|
+
readGateByDomain.set(domain, JSON.parse(unhexUtf8(rgHex)) as UsdLayer["nomosReadGate"]);
|
|
644
|
+
}
|
|
585
645
|
}
|
|
586
646
|
}
|
|
587
647
|
|
|
@@ -669,12 +729,16 @@ export function parseUsdaDocument(text: string): UsdDocument {
|
|
|
669
729
|
const layers: UsdLayer[] = [...buckets.keys()]
|
|
670
730
|
.map((domain) => {
|
|
671
731
|
const b = buckets.get(domain)!;
|
|
732
|
+
const sids = stableIdsByDomain.get(domain);
|
|
733
|
+
const rg = readGateByDomain.get(domain);
|
|
672
734
|
return {
|
|
673
735
|
path: `/Nomos/${domain}`,
|
|
674
736
|
prims: [...b.prims].sort(byPath),
|
|
675
737
|
...(b.queries.length > 0 ? { queries: [...b.queries].sort(byId) } : {}),
|
|
676
738
|
...(b.deriveds.length > 0 ? { deriveds: [...b.deriveds].sort(byId) } : {}),
|
|
677
739
|
...(b.combineds.length > 0 ? { combineds: [...b.combineds].sort(byId) } : {}),
|
|
740
|
+
...(sids !== undefined ? { nomosStableIds: sids } : {}),
|
|
741
|
+
...(rg !== undefined ? { nomosReadGate: rg } : {}),
|
|
678
742
|
};
|
|
679
743
|
})
|
|
680
744
|
.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
package/src/wire_encode.ts
CHANGED
|
@@ -128,6 +128,24 @@ function encodeKernelFieldOp(op: FieldOp, field: Field | undefined): WireFieldOp
|
|
|
128
128
|
*
|
|
129
129
|
* `agg` is the directive's declared target handle, used to resolve field kinds
|
|
130
130
|
* when encoding values; ops emitted to sibling aggregates encode by value-kind.
|
|
131
|
+
*
|
|
132
|
+
* ⚠️ THIS IS PLAN LOWERING ONLY — NOT A DOMAIN TEST HARNESS. It runs your
|
|
133
|
+
* directive's `plan()` and encodes the resulting ops into the wire Intent shape.
|
|
134
|
+
* It does NOT touch the kernel: it does not admit through the gate (no
|
|
135
|
+
* invariants, no minted-id check, no terminal-once, no captured reads), it does
|
|
136
|
+
* not fold state, and it requires a hand-provided `ctx` (`deterministicPorts`)
|
|
137
|
+
* with a stub `nomos.mint`. Use it to ASSERT THE BYTES a plan lowers to (the
|
|
138
|
+
* `impure_capability`/`capability` lowering tests are the canonical use), never
|
|
139
|
+
* to test domain LIFECYCLE.
|
|
140
|
+
*
|
|
141
|
+
* For a KERNEL-BACKED local harness — admission through the real gate, folds,
|
|
142
|
+
* reads, minted ids, the whole lifecycle on the same engine plane the cloud
|
|
143
|
+
* edge runs — use the CLI, NOT this function:
|
|
144
|
+
* • `githolon dev --once` — ONE compile → law-live-locally → proof cycle, then
|
|
145
|
+
* exit (the CI/smoke lane for lifecycle + id-mint tests — a non-zero exit
|
|
146
|
+
* names the jam). `githolon dev` (no flag) is the same on a watch loop.
|
|
147
|
+
* • `githolon proof` — the generated proof's offline legs on a local holon.
|
|
148
|
+
* Those mint ids, run the gate, and fold — exactly what this function does NOT.
|
|
131
149
|
*/
|
|
132
150
|
export function executeDirectiveToIntent<P>(
|
|
133
151
|
directive: Directive<P>,
|