@githolon/dsl 0.119.12 → 0.119.28
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/assets/operation-contract.wasm +0 -0
- package/compile_package.mjs +80 -74
- package/compile_proof.d.mts +1 -0
- package/compile_proof.mjs +29 -3
- package/dart/CHANGELOG.md +89 -0
- package/dart/lib/src/runner/node-entry.mjs +531 -197
- package/dart/lib/src/testing/test_peer.dart +36 -0
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart_types/CHANGELOG.md +4 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +2673 -1194
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +7 -7
- package/src/anonymous_value_interning.ts +14 -7
- package/src/baseline_export.ts +64 -6
- package/src/baseline_import.ts +17 -0
- package/src/baseline_usda.ts +2 -2
- package/src/codegen_result_usda.ts +3 -1
- package/src/compile_package_main.ts +10 -7
- package/src/compiler_toolchain.ts +35 -0
- package/src/dart_analysis_roots.ts +71 -0
- package/src/intent.ts +3 -1
- package/src/law_graph.ts +11 -74
- package/src/law_graph_usda.ts +4 -4
- package/src/law_identity.ts +5 -3
- package/src/openusd_compiled_layer.ts +7 -2
- package/src/value_wire_continuity.ts +150 -0
package/dart_types/pubspec.yaml
CHANGED
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
Pure kernel wire/data types for Nomos Dart frontends — no transport, no bridge,
|
|
10
10
|
no runtime. Typed protobuf and USDA boundary values plus the offer codec,
|
|
11
11
|
reactive read engine, schema validation, and data-only error/trace types.
|
|
12
|
-
version: 0.
|
|
12
|
+
version: 0.16.0
|
|
13
13
|
|
|
14
14
|
environment:
|
|
15
15
|
sdk: ^3.5.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/dsl",
|
|
3
|
-
"version": "0.119.
|
|
3
|
+
"version": "0.119.28",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
|
|
@@ -82,16 +82,16 @@
|
|
|
82
82
|
"test": "vitest run"
|
|
83
83
|
},
|
|
84
84
|
"dependencies": {
|
|
85
|
-
"@githolon/runtime": "
|
|
86
|
-
"@bufbuild/protobuf": "
|
|
87
|
-
"@bufbuild/protoc-gen-es": "
|
|
85
|
+
"@githolon/runtime": "0.119.28",
|
|
86
|
+
"@bufbuild/protobuf": "2.13.0",
|
|
87
|
+
"@bufbuild/protoc-gen-es": "2.13.0",
|
|
88
88
|
"esbuild": "0.24.2",
|
|
89
89
|
"fflate": "0.8.2",
|
|
90
|
-
"tsx": "
|
|
91
|
-
"typescript": "
|
|
90
|
+
"tsx": "4.22.4",
|
|
91
|
+
"typescript": "5.9.3",
|
|
92
92
|
"vscode-languageserver": "^9.0.1",
|
|
93
93
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
94
|
-
"zod": "
|
|
94
|
+
"zod": "4.4.3"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
97
|
"@types/node": "^25.9.2",
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
// NOMOS — Structural interning
|
|
1
|
+
// NOMOS — Structural interning: one semantic shape AND wire contract, one canonical node.
|
|
2
2
|
//
|
|
3
3
|
// Anonymous value types are structural values, not independently lived declarations. The graph
|
|
4
4
|
// deliberately retains their source owner for diagnostics, but USDA must not turn that provenance
|
|
5
5
|
// into thousands of duplicate runtime prims. This computes a semantic key which excludes owner
|
|
6
|
-
// identity, then points
|
|
7
|
-
// variants
|
|
6
|
+
// identity, retaining member wire allocations, then points equivalent type graphs and their member
|
|
7
|
+
// fields and union variants at one canonical node. Declared types remain untouched. Recursive graphs keep their
|
|
8
8
|
// owner-scoped identity for now: aliasing a cycle needs an explicit recursion-isomorphism proof,
|
|
9
9
|
// whereas every acyclic case below is exact by construction.
|
|
10
10
|
//
|
|
11
|
+
// Discovery may ignore wire allocations to find candidates for a first allocation (CAP-983).
|
|
12
|
+
// Final sharing always includes them: equal shapes with distinct released keys remain separate.
|
|
11
13
|
// ONE interning, TWO consumers. The USDA emitter (law_graph_usda.ts) redirects duplicate prims to
|
|
12
14
|
// the canonical prim; the graph discovery (law_graph.ts) ledgers a nested payload field's wire key
|
|
13
15
|
// against the canonical member, because that is the key installed law serves and generated clients
|
|
@@ -23,6 +25,8 @@ export interface AnonymousValueInterningInput {
|
|
|
23
25
|
readonly types: readonly LawAnonymousValueTypeNode[];
|
|
24
26
|
readonly fields: readonly LawAnonymousValueFieldNode[];
|
|
25
27
|
readonly variants: readonly LawAnonymousValueVariantNode[];
|
|
28
|
+
/** Discovery only: find shape equivalence before retaining transport allocations. */
|
|
29
|
+
readonly ignoreWireAllocations?: boolean;
|
|
26
30
|
/** Whether this type is emitted as its own `Types/t_<sid>` prim (wrappers and framework scalars are not). */
|
|
27
31
|
readonly ownsPrim: (type: LawAnonymousValueTypeNode) => boolean;
|
|
28
32
|
}
|
|
@@ -108,11 +112,13 @@ export function internAnonymousValues(input: AnonymousValueInterningInput): Anon
|
|
|
108
112
|
break;
|
|
109
113
|
}
|
|
110
114
|
case "record": {
|
|
111
|
-
const members: Array<readonly [string, boolean, string]> = [];
|
|
115
|
+
const members: Array<readonly [string, boolean, string, (string | undefined)?, (readonly string[])?]> = [];
|
|
112
116
|
for (const field of fieldsByOwner.get(id) ?? []) {
|
|
113
117
|
const child = childKey(field.value.id);
|
|
114
118
|
if (child === undefined) break;
|
|
115
|
-
members.push(
|
|
119
|
+
members.push(input.ignoreWireAllocations
|
|
120
|
+
? [field.name, field.required, child]
|
|
121
|
+
: [field.name, field.required, child, field.entity.wireKey, [...(field.entity.wireAliases ?? [])].sort()]);
|
|
116
122
|
}
|
|
117
123
|
if (members.length === (fieldsByOwner.get(id)?.length ?? 0)) {
|
|
118
124
|
members.sort(([left], [right]) => left.localeCompare(right));
|
|
@@ -122,7 +128,7 @@ export function internAnonymousValues(input: AnonymousValueInterningInput): Anon
|
|
|
122
128
|
break;
|
|
123
129
|
}
|
|
124
130
|
case "sum": {
|
|
125
|
-
const members: Array<readonly [string, string]> = [];
|
|
131
|
+
const members: Array<readonly [string, string, (string | undefined)?]> = [];
|
|
126
132
|
for (const variant of variantsByOwner.get(id) ?? []) {
|
|
127
133
|
const child = childKey(variant.value.id);
|
|
128
134
|
if (child === undefined) break;
|
|
@@ -130,7 +136,8 @@ export function internAnonymousValues(input: AnonymousValueInterningInput): Anon
|
|
|
130
136
|
// with the same carried value shapes but different arm labels cannot
|
|
131
137
|
// share one USDA prim: authoring reconstructs the nominal oneof from
|
|
132
138
|
// these labels before it decodes the generated Protobuf call.
|
|
133
|
-
members.push([variant.label ?? variant.entity.label, child]
|
|
139
|
+
members.push(input.ignoreWireAllocations ? [variant.label ?? variant.entity.label, child]
|
|
140
|
+
: [variant.label ?? variant.entity.label, child, variant.entity.wireKey]);
|
|
134
141
|
}
|
|
135
142
|
if (members.length === (variantsByOwner.get(id)?.length ?? 0)) {
|
|
136
143
|
members.sort(([leftLabel, leftChild], [rightLabel, rightChild]) =>
|
package/src/baseline_export.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { readInspectionUnits } from "./release_inspection.js";
|
|
|
23
23
|
import { lawUnitSetIdentityWith } from "./law_unit_set_identity.js";
|
|
24
24
|
import {
|
|
25
25
|
LAW_FRAMEWORK_PARENT,
|
|
26
|
+
createLawIdentityReconciler,
|
|
26
27
|
lawEntityKey,
|
|
27
28
|
validateLawIdentityRegistry,
|
|
28
29
|
type LawEntityKind,
|
|
@@ -48,6 +49,7 @@ import {
|
|
|
48
49
|
} from "./law_layer_prims.js";
|
|
49
50
|
import {
|
|
50
51
|
BASELINE_NO_RELEASE,
|
|
52
|
+
NOMOS_BASELINE_FORMAT,
|
|
51
53
|
serializeBaselineUsda,
|
|
52
54
|
baselineSha256,
|
|
53
55
|
type BaselineSource,
|
|
@@ -284,13 +286,14 @@ function scanWireAllocations(
|
|
|
284
286
|
intentPrim: UsdaPrim,
|
|
285
287
|
prims: ReadonlyMap<string, UsdaPrim>,
|
|
286
288
|
out: BaselineWireAllocation[],
|
|
289
|
+
valueRoot?: { owner: string; declaredTypes: ReadonlySet<string> },
|
|
287
290
|
): void {
|
|
288
291
|
const intentSid = sidOf(intentPrim);
|
|
289
292
|
if (intentSid === undefined) return;
|
|
290
|
-
const intent = lawEntityKey("intent", intentSid);
|
|
293
|
+
const intent = valueRoot?.owner ?? lawEntityKey("intent", intentSid);
|
|
291
294
|
const seen = new Set<string>();
|
|
292
295
|
const allocate = (field: UsdaPrim, path: string): void => {
|
|
293
|
-
const canonical = usdaString(field, "wireKey");
|
|
296
|
+
const canonical = usdaString(field, "wireKey") ?? (field.name.startsWith("v_") ? sidOf(field) : undefined);
|
|
294
297
|
if (canonical === undefined) return;
|
|
295
298
|
const aliases = usdaStringList(field, "wireAliases") ?? [];
|
|
296
299
|
const coordinate = `${intent}\u0000${path}`;
|
|
@@ -299,7 +302,7 @@ function scanWireAllocations(
|
|
|
299
302
|
out.push({ intent, field: path, protobuf: { canonical, ...(aliases.length > 0 ? { aliases } : {}) } });
|
|
300
303
|
};
|
|
301
304
|
const walkType = (typePath: string | undefined, path: string, visiting: ReadonlySet<string>): void => {
|
|
302
|
-
if (typePath === undefined || visiting.has(typePath)) return;
|
|
305
|
+
if (typePath === undefined || visiting.has(typePath) || valueRoot?.declaredTypes.has(typePath)) return;
|
|
303
306
|
const type = prims.get(typePath);
|
|
304
307
|
if (type === undefined) return;
|
|
305
308
|
const next = new Set(visiting).add(typePath);
|
|
@@ -317,7 +320,9 @@ function scanWireAllocations(
|
|
|
317
320
|
const variants = usdaChild(type, "Variants")?.children ?? [];
|
|
318
321
|
variants.forEach((variant, index) => {
|
|
319
322
|
const label = usdaString(variant, "label") ?? String(index);
|
|
320
|
-
|
|
323
|
+
const arm = unionVariantPath(path, label);
|
|
324
|
+
allocate(variant, arm);
|
|
325
|
+
walkType(variant.rels.get("type")?.[0], arm, next);
|
|
321
326
|
});
|
|
322
327
|
break;
|
|
323
328
|
}
|
|
@@ -335,6 +340,25 @@ function scanWireAllocations(
|
|
|
335
340
|
allocate(field, path);
|
|
336
341
|
walkType(field.rels.get("type")?.[0], path, visiting);
|
|
337
342
|
};
|
|
343
|
+
if (valueRoot !== undefined) {
|
|
344
|
+
if (valueRoot.owner.startsWith("f_")) walkType(intentPrim.rels.get("type")?.[0], "$", new Set());
|
|
345
|
+
else if (valueRoot.owner.startsWith("q_")) {
|
|
346
|
+
walkType(intentPrim.rels.get("returns")?.[0], "$returns", new Set());
|
|
347
|
+
const contract = prims.get(intentPrim.rels.get("contract")?.[0] ?? "");
|
|
348
|
+
for (const parameter of (contract === undefined ? [] : usdaChild(contract, "Parameters")?.children ?? [])) {
|
|
349
|
+
const name = usdaString(parameter, "name");
|
|
350
|
+
if (name !== undefined) walkType(parameter.rels.get("type")?.[0], nestedMemberPath("$parameters", name), new Set());
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else for (const variant of usdaChild(intentPrim, "Variants")?.children ?? []) {
|
|
354
|
+
const label = usdaString(variant, "label");
|
|
355
|
+
if (label === undefined) throw new Error("baseline export: a value variant lacks its discriminator");
|
|
356
|
+
const arm = unionVariantPath("$", label);
|
|
357
|
+
allocate(variant, arm);
|
|
358
|
+
walkType(variant.rels.get("type")?.[0], arm, new Set());
|
|
359
|
+
}
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
338
362
|
for (const relation of ["accepts", "publicAccepts"]) {
|
|
339
363
|
const contract = prims.get(intentPrim.rels.get(relation)?.[0] ?? "");
|
|
340
364
|
if (contract === undefined) continue;
|
|
@@ -404,11 +428,45 @@ export function exportBaselineFromUnits(input: BaselineExportInput): BaselineExp
|
|
|
404
428
|
}
|
|
405
429
|
entities[key] = entity;
|
|
406
430
|
}
|
|
407
|
-
|
|
431
|
+
let identity: LawIdentityRegistry = {
|
|
408
432
|
v: 1,
|
|
409
433
|
entities: Object.fromEntries(Object.entries(entities).sort(([l], [r]) => (l < r ? -1 : l > r ? 1 : 0))),
|
|
410
434
|
};
|
|
411
435
|
validateLawIdentityRegistry(identity);
|
|
436
|
+
// Recover read-value continuity from the bytes actually served, including pre-CAP-983 releases.
|
|
437
|
+
// Named declarations own their nested allocations. Anonymous representative IDs are not owners.
|
|
438
|
+
const declaredTypes = new Set([...prims].filter(([, prim]) => {
|
|
439
|
+
const sid = sidOf(prim);
|
|
440
|
+
return sid !== undefined && identity.entities[`t_${sid}`]?.kind === "type";
|
|
441
|
+
}).map(([path]) => path));
|
|
442
|
+
const valueAllocations: BaselineWireAllocation[] = [];
|
|
443
|
+
for (const [, prim] of prims) {
|
|
444
|
+
const sid = sidOf(prim);
|
|
445
|
+
if (sid === undefined) continue;
|
|
446
|
+
const owner = prim.name.startsWith("f_") ? `f_${sid}` : prim.name.startsWith("t_") ? `t_${sid}` : prim.name.startsWith("q_") ? `q_${sid}` : undefined;
|
|
447
|
+
if (owner === undefined || identity.entities[owner] === undefined) continue;
|
|
448
|
+
scanWireAllocations(prim, prims, valueAllocations, { owner, declaredTypes });
|
|
449
|
+
}
|
|
450
|
+
// Repeated framework declarations may expose the same coordinate in several layers.
|
|
451
|
+
// A baseline must report disagreement, not let preferred-first reconciliation hide it.
|
|
452
|
+
const releasedByCoordinate = new Map<string, BaselineWireAllocation>();
|
|
453
|
+
for (const allocation of valueAllocations) {
|
|
454
|
+
const coordinate = `${allocation.intent}\0${allocation.field}`;
|
|
455
|
+
const prior = releasedByCoordinate.get(coordinate);
|
|
456
|
+
if (prior !== undefined && prior.protobuf.canonical !== allocation.protobuf.canonical) {
|
|
457
|
+
throw new Error(`baseline export: conflicting released wire allocations for ${allocation.intent}.${allocation.field}: ${prior.protobuf.canonical}, ${allocation.protobuf.canonical}`);
|
|
458
|
+
}
|
|
459
|
+
const aliases = [...new Set([...(prior?.protobuf.aliases ?? []), ...(allocation.protobuf.aliases ?? [])])].sort();
|
|
460
|
+
releasedByCoordinate.set(coordinate, { ...allocation, protobuf: { canonical: allocation.protobuf.canonical,
|
|
461
|
+
...(aliases.length === 0 ? {} : { aliases }),
|
|
462
|
+
} });
|
|
463
|
+
}
|
|
464
|
+
const reconciler = createLawIdentityReconciler(identity);
|
|
465
|
+
for (const allocation of releasedByCoordinate.values()) reconciler.reconcile({
|
|
466
|
+
kind: "value-field", parent: allocation.intent, labels: [allocation.field],
|
|
467
|
+
preferredOperationFieldWire: { [allocation.field]: { protobuf: allocation.protobuf } }, retireAbsent: false,
|
|
468
|
+
});
|
|
469
|
+
identity = reconciler.finish();
|
|
412
470
|
|
|
413
471
|
const baselineBody: Omit<ReleasedBaseline, "format"> = {
|
|
414
472
|
source: input.source,
|
|
@@ -424,7 +482,7 @@ export function exportBaselineFromUnits(input: BaselineExportInput): BaselineExp
|
|
|
424
482
|
};
|
|
425
483
|
const text = serializeBaselineUsda(baselineBody);
|
|
426
484
|
return {
|
|
427
|
-
baseline: { format:
|
|
485
|
+
baseline: { format: NOMOS_BASELINE_FORMAT, ...baselineBody },
|
|
428
486
|
text,
|
|
429
487
|
sha256: baselineSha256(text),
|
|
430
488
|
nonLedgerPrims: scanned.nonLedger.value,
|
package/src/baseline_import.ts
CHANGED
|
@@ -160,6 +160,15 @@ export function importBaseline(
|
|
|
160
160
|
retirementDivergence.push(`${key} (${coordinateOf(releasedEntity)}): retired by the release, active in the ledger`);
|
|
161
161
|
continue;
|
|
162
162
|
}
|
|
163
|
+
if (releasedEntity.kind === "value-field") {
|
|
164
|
+
const expected = releasedEntity.operationFieldWire?.protobuf;
|
|
165
|
+
const actual = local.operationFieldWire?.protobuf;
|
|
166
|
+
if (expected === undefined || actual?.canonical !== expected.canonical ||
|
|
167
|
+
(expected.aliases ?? []).some(alias => !actual.aliases?.includes(alias))) {
|
|
168
|
+
throw new BaselineRefusal("baseline-wire-divergence", `nested value ${coordinateOf(releasedEntity)} disagrees with released wire custody`,
|
|
169
|
+
[`released ${expected?.canonical ?? "missing"}, ledger ${actual?.canonical ?? "missing"}`]);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
163
172
|
if (local.label === releasedEntity.label || local.priorLabels?.includes(releasedEntity.label) === true) {
|
|
164
173
|
verified += 1;
|
|
165
174
|
continue;
|
|
@@ -288,6 +297,14 @@ export function assertBaselineRetained(final: LawIdentityRegistry, baseline: Rel
|
|
|
288
297
|
continue;
|
|
289
298
|
}
|
|
290
299
|
if (local.status !== "active") continue; // retired by a declared candidate-law retirement; the evolution gate judged it
|
|
300
|
+
if (releasedEntity.kind === "value-field") {
|
|
301
|
+
const expected = releasedEntity.operationFieldWire?.protobuf;
|
|
302
|
+
const actual = local.operationFieldWire?.protobuf;
|
|
303
|
+
if (expected === undefined || actual?.canonical !== expected.canonical ||
|
|
304
|
+
(expected.aliases ?? []).some(alias => !actual.aliases?.includes(alias))) {
|
|
305
|
+
details.push(`${coordinateOf(releasedEntity)}: released wire allocation changed`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
291
308
|
const lineage = [releasedEntity.label, ...(releasedEntity.priorLabels ?? [])];
|
|
292
309
|
const occupant = activeAt(final.entities, releasedEntity.kind, local.parent, lineage);
|
|
293
310
|
if (occupant !== undefined && occupant[0] !== key) {
|
package/src/baseline_usda.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from "./identity_usda.js";
|
|
27
27
|
import type { LawOperationFieldWire } from "./law_identity.js";
|
|
28
28
|
|
|
29
|
-
export const NOMOS_BASELINE_FORMAT = "nomos.baseline.
|
|
29
|
+
export const NOMOS_BASELINE_FORMAT = "nomos.baseline.v2";
|
|
30
30
|
/** The pinned baseline's conventional filename, beside `nomos.project.ts`. */
|
|
31
31
|
export const NOMOS_BASELINE_FILENAME = "nomos.baseline.usda";
|
|
32
32
|
/** The baseline's own "no release" marker, matching release_publication.ts `NO_PREDECESSOR`. */
|
|
@@ -142,7 +142,7 @@ function attr(prim: IdentityPrim, name: string): string {
|
|
|
142
142
|
/** Read a baseline back. Refuses anything that is not this exact format. */
|
|
143
143
|
export function parseBaselineUsda(text: string): ReleasedBaseline {
|
|
144
144
|
if (!isBaselineUsda(text)) {
|
|
145
|
-
throw new Error(`${NOMOS_BASELINE_FILENAME} is not ${NOMOS_BASELINE_FORMAT} —
|
|
145
|
+
throw new Error(`${NOMOS_BASELINE_FILENAME} is not ${NOMOS_BASELINE_FORMAT} — re-export from the same released units to recover nested read wire allocations (CAP-983)`);
|
|
146
146
|
}
|
|
147
147
|
const roots = parseIdentityPrims(text, NOMOS_BASELINE_FILENAME);
|
|
148
148
|
const root = roots.find((prim) => prim.name === "NomosBaseline");
|
|
@@ -97,7 +97,9 @@ export const TS_RESULT_USDA_PRELUDE = [
|
|
|
97
97
|
`const _nmsUsdaJsonValue = (prim: _NmsUsdaPrim): unknown => {`,
|
|
98
98
|
` if (prim.typeName === "NomosNullValue") return null; if (prim.typeName === "NomosBoolValue") return _nmsUsdaBoolValue(prim);`,
|
|
99
99
|
` if (prim.typeName === "NomosIntValue" || prim.typeName === "NomosNumberValue") return _nmsUsdaNumberValue(prim); if (prim.typeName === "NomosStringValue" || prim.typeName === "NomosTokenValue") return _nmsUsdaStringValue(prim);`,
|
|
100
|
-
|
|
100
|
+
// Nominal records (including nomosRef) wrap their JSON in Value, just like
|
|
101
|
+
// NomosJsonValue. Keep parity with the kernel and Dart's result decoder.
|
|
102
|
+
` if (prim.typeName === "NomosJsonValue" || prim.typeName === "NomosRecordValue") { const value = prim.children.find((child) => child.name === "Value"); if (!value) throw new Error(prim.typeName + " has no Value"); return _nmsUsdaJsonValue(value); }`,
|
|
101
103
|
` if (prim.typeName === "NomosJsonArray") return prim.children.map(_nmsUsdaJsonValue);`,
|
|
102
104
|
` if (prim.typeName === "NomosJsonObject") { const out: Record<string, unknown> = {}; for (const member of prim.children) { const key = _nmsUsdaProperty(member, "nomos:key"); const value = member.children.find((child) => child.name === "Value"); if (typeof key !== "string" || !value) throw new Error("invalid NomosJsonMember"); out[key] = _nmsUsdaJsonValue(value); } return out; }`,
|
|
103
105
|
` throw new Error("unsupported nominal JSON prim " + prim.typeName);`,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { assertCanonicalCompilerToolchain } from "./compiler_toolchain.js";
|
|
2
|
+
import { dartAnalysisRoots } from "./dart_analysis_roots.js";
|
|
1
3
|
import { collectPackageSources, findBirthKeyWrites, formatBirthKeyWriteWarnings } from "./birth_key_rule.js";
|
|
2
4
|
import type { Directive } from "./directive.js";
|
|
3
5
|
import { usdaString } from "./usda_text.js";
|
|
@@ -2587,12 +2589,12 @@ const dartTypesImportFor = (generatedSource: string): string => {
|
|
|
2587
2589
|
* just the floor generated code needs — a lagging pin forces tenants into dependency_overrides. */
|
|
2588
2590
|
// 0.3.0: SurfacedValue<T>/SurfacedObservation/PendingConflictRow — generated Surfaced-field typing +
|
|
2589
2591
|
// the pendingConflicts read rows.
|
|
2590
|
-
const NOMOS_TYPES_DEP = "^0.
|
|
2592
|
+
const NOMOS_TYPES_DEP = "^0.16.0";
|
|
2591
2593
|
// 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
|
|
2592
2594
|
// DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
|
|
2593
2595
|
// REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
|
|
2594
|
-
const NOMOS_CLIENT_DEP = "^0.70.
|
|
2595
|
-
const NOMOS_FLUTTER_DEP = "^0.69.
|
|
2596
|
+
const NOMOS_CLIENT_DEP = "^0.70.27";
|
|
2597
|
+
const NOMOS_FLUTTER_DEP = "^0.69.18";
|
|
2596
2598
|
// DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in the Dart and Flutter
|
|
2597
2599
|
// package pubspecs — a release without this bump fails the suite, so tenants never
|
|
2598
2600
|
// land back in dependency_overrides (the exact co2 report this closes, third occurrence).
|
|
@@ -4247,6 +4249,7 @@ async function assertOneDslInstance(lawDir: string): Promise<void> {
|
|
|
4247
4249
|
}
|
|
4248
4250
|
|
|
4249
4251
|
async function main(): Promise<void> {
|
|
4252
|
+
assertCanonicalCompilerToolchain(DSL_DIR);
|
|
4250
4253
|
const argv = process.argv.slice(2);
|
|
4251
4254
|
const supportedFlags = new Set([
|
|
4252
4255
|
"--require-usdcat",
|
|
@@ -9156,9 +9159,9 @@ async function main(): Promise<void> {
|
|
|
9156
9159
|
// the analyzer reports unresolved `package:` imports as errors and the gate can't tell a codegen bug
|
|
9157
9160
|
// from an un-got package (the co2 "analyze skipped → false success" trap).
|
|
9158
9161
|
const dOut: string = dartOut;
|
|
9159
|
-
// Analyze exactly THE FILES THIS COMPILE EMITTED (`dartFiles`)
|
|
9160
|
-
// sweep in
|
|
9161
|
-
//
|
|
9162
|
+
// Analyze exactly THE FILES THIS COMPILE EMITTED (`dartFiles`). Compact directory roots only when
|
|
9163
|
+
// their Dart coverage is identical; never sweep in transactional `.nomos-bak` backups, tenant source
|
|
9164
|
+
// or stale legacy-layout leftovers. First `dart pub get` each emitted PACKAGE (a dir up-chain with a
|
|
9162
9165
|
// pubspec) so fixed `package:` imports resolve.
|
|
9163
9166
|
const emittedDart = dartFiles.filter((f) => f.endsWith(".dart"));
|
|
9164
9167
|
const pkgDirOf = (file: string): string | undefined => {
|
|
@@ -9462,7 +9465,7 @@ async function main(): Promise<void> {
|
|
|
9462
9465
|
if (!dartAnalysisCached) {
|
|
9463
9466
|
execFileSync(
|
|
9464
9467
|
"dart",
|
|
9465
|
-
["analyze", "--no-fatal-warnings", ...inPackage],
|
|
9468
|
+
["analyze", "--no-fatal-warnings", ...dartAnalysisRoots(inPackage, pkgDirs)],
|
|
9466
9469
|
{ stdio: "pipe", encoding: "utf8" }
|
|
9467
9470
|
);
|
|
9468
9471
|
mkdirSync(proofDir, { recursive: true });
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { compilerPackageManifest } from "../compile_proof.mjs";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
/** These compiler dependencies emit or contribute bytes to immutable law units.
|
|
7
|
+
* Package-manager overrides must fail rather than silently author a different law.
|
|
8
|
+
* Tenant dependencies remain authored inputs; inspect the compiler's resolution. */
|
|
9
|
+
export function assertCanonicalCompilerToolchain(dslDir: string): void {
|
|
10
|
+
const manifest = join(dslDir, "package.json");
|
|
11
|
+
const dependencies = JSON.parse(readFileSync(manifest, "utf8")).dependencies as Record<string, string>;
|
|
12
|
+
const compiler = createRequire(manifest);
|
|
13
|
+
const check = (name: string, expected: string | undefined, from: NodeJS.Require): string => {
|
|
14
|
+
const prefix = `NMS-TOOLCHAIN-001: non-canonical compiler toolchain: ${name}`;
|
|
15
|
+
if (expected === undefined || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(expected)) {
|
|
16
|
+
throw new Error(`${prefix} must have an exact compiler dependency pin (got ${expected}).`);
|
|
17
|
+
}
|
|
18
|
+
let file: string;
|
|
19
|
+
try { file = compilerPackageManifest(name, from); }
|
|
20
|
+
catch { throw new Error(`${prefix} is missing; reinstall @githolon/dsl with its declared dependencies.`); }
|
|
21
|
+
const actual = JSON.parse(readFileSync(file, "utf8")).version;
|
|
22
|
+
if (actual !== expected) {
|
|
23
|
+
throw new Error(`${prefix} resolved ${actual}, expected ${expected}. Remove compiler dependency overrides and reinstall @githolon/dsl; a toolchain upgrade requires a new compiler release.`);
|
|
24
|
+
}
|
|
25
|
+
return file;
|
|
26
|
+
};
|
|
27
|
+
for (const name of ["@githolon/runtime", "@bufbuild/protobuf", "esbuild", "fflate", "tsx", "typescript", "zod"]) {
|
|
28
|
+
check(name, dependencies[name], compiler);
|
|
29
|
+
}
|
|
30
|
+
const version = dependencies["@bufbuild/protoc-gen-es"];
|
|
31
|
+
const generator = createRequire(check("@bufbuild/protoc-gen-es", version, compiler));
|
|
32
|
+
const plugin = createRequire(check("@bufbuild/protoplugin", version, generator));
|
|
33
|
+
check("@bufbuild/protobuf", dependencies["@bufbuild/protobuf"], generator);
|
|
34
|
+
check("@bufbuild/protobuf", dependencies["@bufbuild/protobuf"], plugin);
|
|
35
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Compact analyzer roots without widening the set of generated Dart files judged.
|
|
5
|
+
* Each file root creates filesystem watches in the Dart analysis server. A layered
|
|
6
|
+
* package can have dozens of roots for the same directory; on macOS their FSEvents
|
|
7
|
+
* teardown can dominate analysis. A directory is equivalent only when every Dart
|
|
8
|
+
* file beneath it was emitted by this compile. Backups, tenant code and symlinks
|
|
9
|
+
* prevent compaction across that boundary.
|
|
10
|
+
*/
|
|
11
|
+
export function dartAnalysisRoots(
|
|
12
|
+
files: readonly string[],
|
|
13
|
+
packageRoots: readonly string[],
|
|
14
|
+
): string[] {
|
|
15
|
+
const owned = new Set(files.map((file) => path.resolve(file)));
|
|
16
|
+
const packages = packageRoots.map((root) => path.resolve(root)).sort((a, b) => b.length - a.length);
|
|
17
|
+
const directories = new Set<string>();
|
|
18
|
+
const explicit = new Set<string>();
|
|
19
|
+
const incompressible = new Set<string>();
|
|
20
|
+
const options = new Map<string, boolean>();
|
|
21
|
+
const hasAnalysisOptions = (directory: string): boolean => {
|
|
22
|
+
const held = options.get(directory);
|
|
23
|
+
if (held !== undefined) return held;
|
|
24
|
+
const parent = path.dirname(directory);
|
|
25
|
+
const found = existsSync(path.join(directory, "analysis_options.yaml")) ||
|
|
26
|
+
(parent !== directory && hasAnalysisOptions(parent));
|
|
27
|
+
options.set(directory, found);
|
|
28
|
+
return found;
|
|
29
|
+
};
|
|
30
|
+
for (const file of owned) {
|
|
31
|
+
const pkg = packages.find((root) => file.startsWith(`${root}${path.sep}`));
|
|
32
|
+
const relative = pkg === undefined ? undefined : path.relative(pkg, file).split(path.sep);
|
|
33
|
+
// Directory roots can apply analyzer exclusions differently from explicit files.
|
|
34
|
+
// Keep the original invocation for inherited/custom options and hidden/build paths.
|
|
35
|
+
if (hasAnalysisOptions(path.dirname(file)) || relative?.some((part) => part.startsWith(".") || part === "build")) {
|
|
36
|
+
incompressible.add(file);
|
|
37
|
+
}
|
|
38
|
+
// Never analyze the package root itself: only the emitted source subtrees.
|
|
39
|
+
if (pkg === undefined || relative!.length === 1) explicit.add(file);
|
|
40
|
+
else directories.add(path.join(pkg, relative![0]!));
|
|
41
|
+
}
|
|
42
|
+
const seen = new Set<string>();
|
|
43
|
+
const visit = (directory: string): { roots: string[]; foreign: boolean } => {
|
|
44
|
+
const roots: string[] = [];
|
|
45
|
+
let foreign = false;
|
|
46
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
47
|
+
const file = path.join(directory, entry.name);
|
|
48
|
+
if (entry.isSymbolicLink()) {
|
|
49
|
+
// Do not infer ownership of anything the analyzer may reach through a link.
|
|
50
|
+
foreign = true;
|
|
51
|
+
} else if (entry.isDirectory()) {
|
|
52
|
+
const child = visit(file);
|
|
53
|
+
roots.push(...child.roots);
|
|
54
|
+
foreign ||= child.foreign;
|
|
55
|
+
} else if (entry.isFile() && entry.name.endsWith(".dart")) {
|
|
56
|
+
if (owned.has(file)) {
|
|
57
|
+
seen.add(file);
|
|
58
|
+
roots.push(file);
|
|
59
|
+
foreign ||= incompressible.has(file);
|
|
60
|
+
} else foreign = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { roots: roots.length > 0 && !foreign ? [directory] : roots, foreign };
|
|
64
|
+
};
|
|
65
|
+
for (const directory of directories) {
|
|
66
|
+
for (const root of visit(directory).roots) explicit.add(root);
|
|
67
|
+
}
|
|
68
|
+
// A missing file must remain an analyzer input, never silently lose coverage.
|
|
69
|
+
for (const file of owned) if (!seen.has(file)) explicit.add(file);
|
|
70
|
+
return [...explicit].sort();
|
|
71
|
+
}
|
package/src/intent.ts
CHANGED
|
@@ -490,7 +490,9 @@ const dedupeBy = <T>(values: readonly T[], key: (value: T) => string): T[] => {
|
|
|
490
490
|
const seen = new Set<string>();
|
|
491
491
|
for (const value of values) {
|
|
492
492
|
const k = key(value);
|
|
493
|
-
if (seen.
|
|
493
|
+
if (seen.has(k)) continue;
|
|
494
|
+
seen.add(k);
|
|
495
|
+
out.push(value);
|
|
494
496
|
}
|
|
495
497
|
return out;
|
|
496
498
|
};
|
package/src/law_graph.ts
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
import type { StableFieldEvolution, StableIds } from "./stable_ids_types.js";
|
|
34
34
|
import { isBusinessIntent, publicOfferRoute } from "./intent.js";
|
|
35
35
|
import { listElementPath, mapValuePath, nestedMemberPath, unionVariantPath } from "./wire_paths.js";
|
|
36
|
-
import {
|
|
36
|
+
import { retainValueWireAllocations } from "./value_wire_continuity.js";
|
|
37
37
|
import {
|
|
38
38
|
lowerLawValueType,
|
|
39
39
|
type LawConstant,
|
|
@@ -1302,7 +1302,7 @@ export function discoverLawGraph(
|
|
|
1302
1302
|
// (wire_paths.ts) in the intent's operation-field scope: the first allocation is still the
|
|
1303
1303
|
// structural sid (unchanged law bytes for every existing package), and from then on the
|
|
1304
1304
|
// ledger keeps it through named and structural generations.
|
|
1305
|
-
const nestedByPath = new Map<string, LawAnonymousValueFieldNode>();
|
|
1305
|
+
const nestedByPath = new Map<string, LawAnonymousValueFieldNode | LawAnonymousValueVariantNode>();
|
|
1306
1306
|
const pathsByNode = new Map<string, string[]>();
|
|
1307
1307
|
const walkNested = (valueRef: LawValueTypeRef, path: string, visiting: ReadonlySet<string>): void => {
|
|
1308
1308
|
if (visiting.has(valueRef.id)) return;
|
|
@@ -1331,7 +1331,9 @@ export function discoverLawGraph(
|
|
|
1331
1331
|
valueNode.expression.variants.forEach((variantRef, index) => {
|
|
1332
1332
|
const variant = provisionalById.get(variantRef.id);
|
|
1333
1333
|
if (variant?.kind !== "structural" || variant.structuralKind !== "value-variant") return;
|
|
1334
|
-
|
|
1334
|
+
const arm = unionVariantPath(path, variant.label ?? String(index));
|
|
1335
|
+
nestedByPath.set(arm, variant);
|
|
1336
|
+
walkNested(variant.value, arm, next);
|
|
1335
1337
|
});
|
|
1336
1338
|
break;
|
|
1337
1339
|
case "nullable":
|
|
@@ -2624,77 +2626,12 @@ export function discoverLawGraph(
|
|
|
2624
2626
|
},
|
|
2625
2627
|
}),
|
|
2626
2628
|
};
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
// explicit evolution decision, never a compile's silent pick.
|
|
2634
|
-
{
|
|
2635
|
-
const valueNodes = [...anonymousValueNodes.values()];
|
|
2636
|
-
const interning = internAnonymousValues({
|
|
2637
|
-
types: valueNodes.filter((node): node is LawAnonymousValueTypeNode => node.structuralKind === "value-type"),
|
|
2638
|
-
fields: valueNodes.filter((node): node is LawAnonymousValueFieldNode => node.structuralKind === "value-field"),
|
|
2639
|
-
variants: valueNodes.filter((node): node is LawAnonymousValueVariantNode => node.structuralKind === "value-variant"),
|
|
2640
|
-
ownsPrim: anonymousTypeOwnsPrim,
|
|
2641
|
-
});
|
|
2642
|
-
const canonicalFieldOf = (fieldId: string): string => interning.canonicalFieldIdById.get(fieldId) ?? fieldId;
|
|
2643
|
-
const allocationByCanonical = new Map<string, { canonical: string; aliases: Set<string>; owners: string[] }>();
|
|
2644
|
-
for (const [intentKey, paths] of nestedPathsByIntent) {
|
|
2645
|
-
if (paths.size === 0) continue;
|
|
2646
|
-
const intentLabel = registry.entities[intentKey]?.label ?? intentKey;
|
|
2647
|
-
const preferredOperationFieldWire: Record<string, LawOperationFieldWire> = {};
|
|
2648
|
-
for (const [path, fieldId] of paths) {
|
|
2649
|
-
const canonical = anonymousValueNodes.get(canonicalFieldOf(fieldId));
|
|
2650
|
-
if (canonical === undefined) fail(`directive '${intentLabel}': nested field '${path}' has no canonical value node`);
|
|
2651
|
-
preferredOperationFieldWire[path] = { protobuf: { canonical: canonical.entity.sid } };
|
|
2652
|
-
}
|
|
2653
|
-
const ledger = identities.reconcile({
|
|
2654
|
-
kind: "operation-field",
|
|
2655
|
-
parent: intentKey,
|
|
2656
|
-
labels: [...paths.keys()],
|
|
2657
|
-
preferredOperationFieldWire,
|
|
2658
|
-
retireAbsent: false,
|
|
2659
|
-
});
|
|
2660
|
-
registry = ledger.registry;
|
|
2661
|
-
for (const [path, fieldId] of paths) {
|
|
2662
|
-
const entity = registry.entities[ledger.resolved[path] ?? ""];
|
|
2663
|
-
if (entity?.kind !== "operation-field" || entity.status !== "active" || entity.operationFieldWire === undefined) {
|
|
2664
|
-
fail(`directive '${intentLabel}' has no active wire allocation for nested field '${path}'`);
|
|
2665
|
-
}
|
|
2666
|
-
const wire = entity.operationFieldWire.protobuf;
|
|
2667
|
-
const canonicalId = canonicalFieldOf(fieldId);
|
|
2668
|
-
const owner = `${intentLabel}.${path}`;
|
|
2669
|
-
const prior = allocationByCanonical.get(canonicalId);
|
|
2670
|
-
if (prior === undefined) {
|
|
2671
|
-
allocationByCanonical.set(canonicalId, { canonical: wire.canonical, aliases: new Set(wire.aliases ?? []), owners: [owner] });
|
|
2672
|
-
continue;
|
|
2673
|
-
}
|
|
2674
|
-
if (prior.canonical !== wire.canonical) {
|
|
2675
|
-
fail(
|
|
2676
|
-
`NMS-WIRE-001 nested fields ${[...prior.owners, owner].map((name) => `'${name}'`).join(" and ")} share one structural value ` +
|
|
2677
|
-
`but hold different wire allocations (${prior.canonical}, ${wire.canonical}); ` +
|
|
2678
|
-
"declare an evolution plan for the reallocation instead of letting a compile choose",
|
|
2679
|
-
);
|
|
2680
|
-
}
|
|
2681
|
-
for (const alias of wire.aliases ?? []) prior.aliases.add(alias);
|
|
2682
|
-
prior.owners.push(owner);
|
|
2683
|
-
}
|
|
2684
|
-
}
|
|
2685
|
-
// Every node of an allocated equivalence class — the canonical member the law emits and each
|
|
2686
|
-
// duplicate a generated client reads — carries the one allocation.
|
|
2687
|
-
for (const node of valueNodes) {
|
|
2688
|
-
if (node.structuralKind !== "value-field") continue;
|
|
2689
|
-
const allocation = allocationByCanonical.get(canonicalFieldOf(node.entity.id));
|
|
2690
|
-
if (allocation === undefined) continue;
|
|
2691
|
-
const aliases = [...allocation.aliases].sort(compareText);
|
|
2692
|
-
anonymousValueNodes.set(node.entity.id, {
|
|
2693
|
-
...node,
|
|
2694
|
-
entity: { ...node.entity, wireKey: allocation.canonical, ...(aliases.length === 0 ? {} : { wireAliases: aliases }) },
|
|
2695
|
-
});
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
2629
|
+
const retainedWire = retainValueWireAllocations({
|
|
2630
|
+
nodes: [...anonymousValueNodes.values()], fields: fieldNodes, types: typeNodes, reads: readContractNodes,
|
|
2631
|
+
operations: nestedPathsByIntent, identities, registry,
|
|
2632
|
+
});
|
|
2633
|
+
registry = retainedWire.registry;
|
|
2634
|
+
for (const node of retainedWire.nodes) anonymousValueNodes.set(node.entity.id, node);
|
|
2698
2635
|
const nodes: DeclaredLawNode[] = [
|
|
2699
2636
|
domainNode,
|
|
2700
2637
|
...aggregateNodes,
|
package/src/law_graph_usda.ts
CHANGED
|
@@ -423,7 +423,7 @@ export function compileSparseLawGraphUsda(
|
|
|
423
423
|
for (const type of anonymousTypes) resolveAnonymousTypePath(type.entity.id);
|
|
424
424
|
|
|
425
425
|
// Anonymous value types are structural values, not independently lived declarations: one semantic
|
|
426
|
-
// shape, one canonical prim (anonymous_value_interning.ts — the same interning the graph discovery
|
|
426
|
+
// shape and wire contract, one canonical prim (anonymous_value_interning.ts — the same interning the graph discovery
|
|
427
427
|
// ledgers nested wire keys against). Declared types remain untouched.
|
|
428
428
|
const interning = internAnonymousValues({
|
|
429
429
|
types: anonymousTypes,
|
|
@@ -463,7 +463,7 @@ export function compileSparseLawGraphUsda(
|
|
|
463
463
|
}
|
|
464
464
|
for (const variant of anonymousVariants) {
|
|
465
465
|
const owner = pathById.get(variant.owner.id);
|
|
466
|
-
if (owner !== undefined) pathById.set(variant.entity.id, `${owner}/Variants/v_${variant.entity.sid}`);
|
|
466
|
+
if (owner !== undefined) pathById.set(variant.entity.id, `${owner}/Variants/v_${variant.entity.wireKey ?? variant.entity.sid}`);
|
|
467
467
|
}
|
|
468
468
|
|
|
469
469
|
// When two record/sum types share one canonical prim, their nested structural references must
|
|
@@ -1489,7 +1489,7 @@ function emitType(
|
|
|
1489
1489
|
for (const [index, variant] of owned.entries()) {
|
|
1490
1490
|
if (index > 0) body.push("");
|
|
1491
1491
|
body.push(
|
|
1492
|
-
`${pad} def Scope "v_${variant.entity.sid}"`,
|
|
1492
|
+
`${pad} def Scope "v_${variant.entity.wireKey ?? variant.entity.sid}"`,
|
|
1493
1493
|
`${pad} {`,
|
|
1494
1494
|
`${pad} custom uniform string nomos:label = ${quote(variant.label ?? variant.entity.label)}`,
|
|
1495
1495
|
`${pad} custom rel nomos:type = <${requiredPath(variant.value.id, paths)}>`,
|
|
@@ -1637,7 +1637,7 @@ function emitAnonymousType(
|
|
|
1637
1637
|
for (const [index, variant] of owned.entries()) {
|
|
1638
1638
|
if (index > 0) body.push("");
|
|
1639
1639
|
body.push(
|
|
1640
|
-
`${pad} def Scope "v_${variant.entity.sid}"`,
|
|
1640
|
+
`${pad} def Scope "v_${variant.entity.wireKey ?? variant.entity.sid}"`,
|
|
1641
1641
|
`${pad} {`,
|
|
1642
1642
|
`${pad} custom uniform string nomos:label = ${quote(variant.label ?? variant.entity.label)}`,
|
|
1643
1643
|
`${pad} custom rel nomos:type = <${requiredPath(variant.value.id, paths)}>`,
|