@githolon/dsl 0.4.0 → 0.5.0
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 +72 -11
- package/src/compile_package_main.ts +241 -13
- 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 +15 -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
package/src/manifest.ts
CHANGED
|
@@ -48,6 +48,14 @@ import {
|
|
|
48
48
|
canonicalTaxonomyFragment,
|
|
49
49
|
type CanonicalDirectiveRoute,
|
|
50
50
|
} from "./workspace_routing.js";
|
|
51
|
+
import {
|
|
52
|
+
mintAggregateSid,
|
|
53
|
+
mintDomainSid,
|
|
54
|
+
mintFieldSid,
|
|
55
|
+
type StableAggregateId,
|
|
56
|
+
type StableFieldId,
|
|
57
|
+
type StableIds,
|
|
58
|
+
} from "./stable_ids.js";
|
|
51
59
|
|
|
52
60
|
/** One captured `[fieldName, zodKind]` payload field, with optionality. */
|
|
53
61
|
export interface CanonicalPayloadField {
|
|
@@ -126,6 +134,15 @@ export interface CanonicalDirective {
|
|
|
126
134
|
* before this key existed (pinned golden hashes stay UNCHANGED).
|
|
127
135
|
*/
|
|
128
136
|
readonly certifiedReads?: CanonicalCertifiedRead[];
|
|
137
|
+
/**
|
|
138
|
+
* The directive's DECLARED CAPTURED-READ queries (#58 — the captured-read lane):
|
|
139
|
+
* the O(1) DSL queries its `plan` may `read()` on the write path, each carried as
|
|
140
|
+
* `{queryId, key, returns}` (the read RECIPE is the contract — a different key or
|
|
141
|
+
* returns-type is a different read, so it moves the hash). SORTED by queryId.
|
|
142
|
+
* OMITTED ENTIRELY when the directive declares none — a captured-read-free
|
|
143
|
+
* directive is byte-identical in the manifest to before this key existed.
|
|
144
|
+
*/
|
|
145
|
+
readonly capturedReads?: CanonicalCapturedRead[];
|
|
129
146
|
/**
|
|
130
147
|
* The directive's DECLARED required HOST CAPABILITY ports (`TARGET_deps.dot` cluster_ports;
|
|
131
148
|
* the HOST/PORT axis, invariant 3): the capability ports the host must PROVIDE for the
|
|
@@ -178,6 +195,15 @@ export interface CanonicalRelationEvidence {
|
|
|
178
195
|
readonly kind: string;
|
|
179
196
|
}
|
|
180
197
|
|
|
198
|
+
/** One DECLARED captured-read query (#58) — the write-path `read()` recipe in the
|
|
199
|
+
* manifest: the query id, its index key (DECLARED order — index column order), and the
|
|
200
|
+
* aggregate TYPE it returns. */
|
|
201
|
+
export interface CanonicalCapturedRead {
|
|
202
|
+
readonly queryId: string;
|
|
203
|
+
readonly key: string[];
|
|
204
|
+
readonly returns: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
181
207
|
/** One captured DECLARED CertifiedRead — the profiled write-path read identity in the manifest.
|
|
182
208
|
* `queryId` is the gate's required-read key; `sql`/`multiRow`/`uniqueTieBreakers` are the recipe
|
|
183
209
|
* + shape the admission re-run + the profile pin (a recipe change moves the domain hash). */
|
|
@@ -378,6 +404,18 @@ export interface CanonicalManifest {
|
|
|
378
404
|
readonly domain: string;
|
|
379
405
|
readonly aggregates: CanonicalAggregate[];
|
|
380
406
|
readonly directives: CanonicalDirective[];
|
|
407
|
+
/**
|
|
408
|
+
* STABLE IDENTIFIERS (#58 — the foundation: names are LABELS, identity is MINTED by
|
|
409
|
+
* the compiler at first appearance, never dev-assigned). Carries `{stableId, name}`
|
|
410
|
+
* for the domain, every aggregate, and every field — keyed by CURRENT name, each
|
|
411
|
+
* entry holding its `sid` plus the label lineage `was` (prior names, oldest first;
|
|
412
|
+
* compiler-derived from continuity, never dev-written). ALWAYS EMITTED by the post-
|
|
413
|
+
* #58 compiler — this key MOVES every enforcement-era identity hash (named and
|
|
414
|
+
* expected; inert-era artifacts already built stay byte-identical untouched). The
|
|
415
|
+
* EVOLVE GATE diffs old vs new law BY SID; the projection folds via the sid lineage
|
|
416
|
+
* so a renamed entity's old-era data coheres under the new label.
|
|
417
|
+
*/
|
|
418
|
+
readonly stableIds?: StableIds;
|
|
381
419
|
/**
|
|
382
420
|
* The domain's WORKSPACE INVARIANTS (#266), SORTED by id, PRESENCE ONLY (id + `on`).
|
|
383
421
|
* OMITTED ENTIRELY when the domain declares none — so an invariant-free domain is
|
|
@@ -636,6 +674,23 @@ function canonicalEmits(
|
|
|
636
674
|
* NAME (the `reads:{}` key) is incidental to identity (it is `decide`'s ergonomics), so it is
|
|
637
675
|
* NOT captured — only the `query_id` + recipe.
|
|
638
676
|
*/
|
|
677
|
+
/**
|
|
678
|
+
* Canonicalize a directive's DECLARED captured-read queries (#58) into a manifest
|
|
679
|
+
* fragment. OMIT-WHEN-EMPTY: returns `{}` (no `capturedReads` key) when the directive
|
|
680
|
+
* declares none — the omission keeps read-free hashes unchanged. When non-empty,
|
|
681
|
+
* returns a `capturedReads` array SORTED by queryId, each carrying the read recipe
|
|
682
|
+
* `{queryId, key, returns}` (key in DECLARED order — index column order).
|
|
683
|
+
*/
|
|
684
|
+
function canonicalCapturedReads(
|
|
685
|
+
declared: QueryDecl[],
|
|
686
|
+
): { capturedReads?: CanonicalCapturedRead[] } {
|
|
687
|
+
if (declared.length === 0) return {};
|
|
688
|
+
const capturedReads: CanonicalCapturedRead[] = declared
|
|
689
|
+
.map((q) => ({ queryId: q.id, key: [...q.key], returns: q.returns }))
|
|
690
|
+
.sort((a, b) => (a.queryId < b.queryId ? -1 : a.queryId > b.queryId ? 1 : 0));
|
|
691
|
+
return { capturedReads };
|
|
692
|
+
}
|
|
693
|
+
|
|
639
694
|
function canonicalCertifiedReads(
|
|
640
695
|
declared: Record<string, CertifiedReadDecl>,
|
|
641
696
|
): { certifiedReads?: CanonicalCertifiedRead[] } {
|
|
@@ -888,6 +943,50 @@ function canonicalOrderedReads(
|
|
|
888
943
|
return { orderedReads: items };
|
|
889
944
|
}
|
|
890
945
|
|
|
946
|
+
/**
|
|
947
|
+
* The domain's STABLE-ID surface (#58): continuity-carried when the compiler attached
|
|
948
|
+
* `mod.stableIdContinuity` (renames resolved, sids + lineage carried), deterministic
|
|
949
|
+
* first-appearance minting otherwise — so the manifest stays a pure function of the
|
|
950
|
+
* module (+ its attached continuity). Any aggregate/field the continuity does not
|
|
951
|
+
* cover (a genuinely new appearance) mints fresh.
|
|
952
|
+
*/
|
|
953
|
+
function stableIdsForModule(
|
|
954
|
+
mod: DomainModule,
|
|
955
|
+
aggregates: CanonicalAggregate[],
|
|
956
|
+
): StableIds {
|
|
957
|
+
const continuity = mod.stableIdContinuity;
|
|
958
|
+
const domainSid = continuity?.domain ?? mintDomainSid(mod.name);
|
|
959
|
+
// The CURRENT field KINDS off the live handles (the retype arm's identity input —
|
|
960
|
+
// a `t.string()` → `t.int()` retype moves `kind` while the driver stays Lww).
|
|
961
|
+
const handlesById = new Map(mod.aggregates.map((a) => [a.id, a]));
|
|
962
|
+
const out: Record<string, StableAggregateId> = {};
|
|
963
|
+
for (const agg of aggregates) {
|
|
964
|
+
const prior = continuity?.aggregates[agg.id];
|
|
965
|
+
const aggSid = prior?.sid ?? mintAggregateSid(domainSid, agg.id);
|
|
966
|
+
const handleFields = (handlesById.get(agg.id)?.fields ?? {}) as Record<
|
|
967
|
+
string,
|
|
968
|
+
{ kind?: string } | undefined
|
|
969
|
+
>;
|
|
970
|
+
const fields: Record<string, StableFieldId> = {};
|
|
971
|
+
for (const fieldName of Object.keys(agg.schema).sort()) {
|
|
972
|
+
const pf = prior?.fields[fieldName];
|
|
973
|
+
const kind = handleFields[fieldName]?.kind;
|
|
974
|
+
fields[fieldName] = {
|
|
975
|
+
sid: pf?.sid ?? mintFieldSid(aggSid, fieldName),
|
|
976
|
+
...(pf?.was !== undefined && pf.was.length > 0 ? { was: pf.was } : {}),
|
|
977
|
+
// The CURRENT kind, always from the live module (continuity never supplies it).
|
|
978
|
+
...(kind !== undefined ? { kind } : {}),
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
out[agg.id] = {
|
|
982
|
+
sid: aggSid,
|
|
983
|
+
...(prior?.was !== undefined && prior.was.length > 0 ? { was: prior.was } : {}),
|
|
984
|
+
fields,
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
return { v: 1, domain: domainSid, aggregates: out };
|
|
988
|
+
}
|
|
989
|
+
|
|
891
990
|
/**
|
|
892
991
|
* Build the canonical semantic manifest for a domain module — purely from the
|
|
893
992
|
* in-memory DSL objects. No file reads, no bundle, no esbuild: the manifest is a
|
|
@@ -919,6 +1018,7 @@ export function domainManifest(mod: DomainModule): CanonicalManifest {
|
|
|
919
1018
|
// Omit-when-empty: a directive declaring NO boundary contributes neither key,
|
|
920
1019
|
// so its canonical manifest is byte-identical to before the boundary existed.
|
|
921
1020
|
...canonicalReads(d.declaredReads),
|
|
1021
|
+
...canonicalCapturedReads(d.declaredQueryReads ?? []),
|
|
922
1022
|
...canonicalEmits(d.declaredEmits),
|
|
923
1023
|
...canonicalCertifiedReads(d.declaredCertifiedReads),
|
|
924
1024
|
...canonicalRelations(d.declaredRelations),
|
|
@@ -939,6 +1039,9 @@ export function domainManifest(mod: DomainModule): CanonicalManifest {
|
|
|
939
1039
|
domain: mod.name,
|
|
940
1040
|
aggregates,
|
|
941
1041
|
directives,
|
|
1042
|
+
// STABLE IDS (#58): always emitted by this compiler — the one deliberate
|
|
1043
|
+
// hash-mover (enforcement-era identity hashes move; named + expected).
|
|
1044
|
+
stableIds: stableIdsForModule(mod, aggregates),
|
|
942
1045
|
...canonicalWorkspaceInvariants(mod.workspaceInvariants),
|
|
943
1046
|
...canonicalQueries(mod.queries),
|
|
944
1047
|
...canonicalCounts(allCounts.length > 0 ? allCounts : undefined),
|
package/src/read.ts
CHANGED
|
@@ -46,9 +46,38 @@ function host(): NomosRead {
|
|
|
46
46
|
* result}` into the intent's read footprint — so the read is replayable and the write's premise is
|
|
47
47
|
* committed. `query` is a declared, indexed {@link QueryDecl} (e.g. a `t.hasMany` inverse from
|
|
48
48
|
* `hasManyIndexes`); `args` are its index-key values.
|
|
49
|
+
*
|
|
50
|
+
* DEPLOYABLE SINCE #58 — WHEN DECLARED: the sealed wasm engine provides `nomos.read` for law that
|
|
51
|
+
* DECLARES the captured-read lane — the directive whose plan reads must declare each query with
|
|
52
|
+
* `.reads(theQuery)` (which lands the `nomosReadGate` key in the law's USD-IR). The engine then
|
|
53
|
+
* serves the read LIVE from the PRE-APPLY committed state at author, captures `{queryId, args,
|
|
54
|
+
* result}` into the intent's `captured_ports.reads`, replays the capture at verify, and the one
|
|
55
|
+
* gate RE-DERIVES each captured read at the pre-apply position and byte-compares — a typed
|
|
56
|
+
* read-conflict refusal on drift (CAS as law). Undeclared `read()` in law with NO declared
|
|
57
|
+
* captured-read query still REFUSES TO PACKAGE (`nomos-compile` greps the bundled lump for
|
|
58
|
+
* {@link CAPTURED_READ_LUMP_MARKER}, which lives ONLY in this function's body and is tree-shaken
|
|
59
|
+
* away with it) — and an inert host (no `nomos.read`) still halts with the named error below.
|
|
49
60
|
*/
|
|
50
61
|
export function read<T = unknown>(query: QueryDecl, args: Record<string, string>): T {
|
|
62
|
+
const h = host() as { read?: unknown } | undefined;
|
|
63
|
+
if (typeof h?.read !== "function") {
|
|
64
|
+
// The string below IS the compile gate's marker — keep it byte-identical to
|
|
65
|
+
// CAPTURED_READ_LUMP_MARKER (compile_package_main.ts greps the bundled lump for it).
|
|
66
|
+
throw new Error(
|
|
67
|
+
`read('${query.id}'): nomos.read is not provided by this host — the engine serves ` +
|
|
68
|
+
`captured reads only for law that DECLARES them: add .reads(${query.id}'s QueryDecl) ` +
|
|
69
|
+
`to the directive and recompile (#58).`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
51
72
|
const result = host().read(query.id, args) as T;
|
|
52
73
|
footprint.push({ queryId: query.id, args, result });
|
|
53
74
|
return result;
|
|
54
75
|
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The compile gate's lump sentinel — the EXACT substring of the refusal `read()` throws above.
|
|
79
|
+
* It exists in a bundled engine lump iff `read` itself survived tree-shaking, i.e. iff some
|
|
80
|
+
* domain module actually references the captured-read lane. `nomos-compile` greps the lump for
|
|
81
|
+
* it and refuses to package (fail-closed: better a named compile refusal than a runtime halt).
|
|
82
|
+
*/
|
|
83
|
+
export const CAPTURED_READ_LUMP_MARKER = "nomos.read is not provided by this host";
|
|
@@ -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,21 @@ 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, the whole lifecycle on the same engine plane the cloud edge runs — use
|
|
143
|
+
* the CLI: `githolon proof` (the generated proof's offline legs on a local
|
|
144
|
+
* holon) and `githolon dev` (the watch→recompile→law-live→proof inner loop).
|
|
145
|
+
* Those mint ids, run the gate, and fold — exactly what this function does NOT.
|
|
131
146
|
*/
|
|
132
147
|
export function executeDirectiveToIntent<P>(
|
|
133
148
|
directive: Directive<P>,
|