@githolon/dsl 0.114.0 → 0.115.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.
Files changed (67) hide show
  1. package/assets/operation-contract.wasm +0 -0
  2. package/dart/CHANGELOG.md +40 -0
  3. package/dart/lib/src/application_framework_calls.dart +7 -1
  4. package/dart/lib/src/console_enrolment.dart +7 -0
  5. package/dart/lib/src/console_http_io.dart +22 -15
  6. package/dart/lib/src/dev_cloud.dart +62 -0
  7. package/dart/lib/src/operation_receipts.dart +74 -7
  8. package/dart/lib/src/reception/dart_reception.dart +26 -6
  9. package/dart/lib/src/runner/node-entry.mjs +7129 -4424
  10. package/dart/lib/src/runtime_v2_transport.dart +3 -0
  11. package/dart/pubspec.lock +1 -1
  12. package/dart/pubspec.yaml +2 -2
  13. package/dart/test/console_enrolment_test.dart +47 -0
  14. package/dart/test/model_adoption_test.dart +49 -0
  15. package/dart/test/runtime_projections_test.dart +41 -0
  16. package/dart_types/CHANGELOG.md +24 -0
  17. package/dart_types/lib/src/approved_model.dart +78 -1
  18. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +528 -2
  19. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +24 -0
  20. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +1897 -112
  21. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +98 -0
  22. package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +16 -0
  23. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +43 -2
  24. package/dart_types/pubspec.yaml +1 -1
  25. package/package.json +4 -2
  26. package/src/aggregate.ts +93 -0
  27. package/src/anonymous_value_interning.ts +205 -0
  28. package/src/baseline.ts +27 -0
  29. package/src/baseline_export.ts +432 -0
  30. package/src/baseline_import.ts +312 -0
  31. package/src/baseline_usda.ts +186 -0
  32. package/src/birth_key_rule.ts +472 -0
  33. package/src/births.ts +207 -24
  34. package/src/build_provenance.ts +10 -0
  35. package/src/business_birth.ts +50 -4
  36. package/src/check_package_main.ts +50 -6
  37. package/src/codegen_call_proto.ts +5 -18
  38. package/src/codegen_dart.ts +95 -3
  39. package/src/codegen_dart_client.ts +52 -6
  40. package/src/codegen_dart_facade.ts +57 -35
  41. package/src/codegen_dart_framework_calls.ts +20 -1
  42. package/src/codegen_proof.ts +5 -2
  43. package/src/codegen_result_proto.ts +8 -6
  44. package/src/codegen_result_usda.ts +4 -0
  45. package/src/codegen_ts.ts +109 -10
  46. package/src/compatibility_graph.ts +41 -4
  47. package/src/compatibility_usda.ts +11 -1
  48. package/src/compile_package_main.ts +214 -51
  49. package/src/composed_wire_identity.ts +24 -0
  50. package/src/directive.ts +6 -0
  51. package/src/domain_requirements.ts +75 -0
  52. package/src/engine_javascript.ts +12 -6
  53. package/src/fields.ts +109 -0
  54. package/src/identity_usda.ts +77 -51
  55. package/src/index.ts +25 -1
  56. package/src/intent.ts +5 -0
  57. package/src/law_graph.ts +142 -1
  58. package/src/law_graph_usda.ts +25 -141
  59. package/src/law_layer_prims.ts +199 -0
  60. package/src/law_unit_selection.ts +45 -0
  61. package/src/law_version.ts +107 -0
  62. package/src/package_config.ts +44 -0
  63. package/src/package_dependencies.ts +8 -0
  64. package/src/value_object.ts +6 -3
  65. package/src/wire.ts +2 -0
  66. package/src/wire_paths.ts +17 -0
  67. package/src/workspace_routing.ts +1 -1
@@ -0,0 +1,205 @@
1
+ // NOMOS — Structural interning of anonymous value types: one semantic shape, one canonical node.
2
+ //
3
+ // Anonymous value types are structural values, not independently lived declarations. The graph
4
+ // deliberately retains their source owner for diagnostics, but USDA must not turn that provenance
5
+ // into thousands of duplicate runtime prims. This computes a semantic key which excludes owner
6
+ // identity, then points every equivalent type graph — and each of its member fields and union
7
+ // variants — at one canonical node. Declared types remain untouched. Recursive graphs keep their
8
+ // owner-scoped identity for now: aliasing a cycle needs an explicit recursion-isomorphism proof,
9
+ // whereas every acyclic case below is exact by construction.
10
+ //
11
+ // ONE interning, TWO consumers. The USDA emitter (law_graph_usda.ts) redirects duplicate prims to
12
+ // the canonical prim; the graph discovery (law_graph.ts) ledgers a nested payload field's wire key
13
+ // against the canonical member, because that is the key installed law serves and generated clients
14
+ // encode (codegen_call_proto.ts `composedWireIdentity`). Computing it twice from the same nodes
15
+ // is what keeps the ledger, the law and the clients on one allocation (CAP-660).
16
+ import type {
17
+ LawAnonymousValueFieldNode,
18
+ LawAnonymousValueTypeNode,
19
+ LawAnonymousValueVariantNode,
20
+ } from "./law_graph.js";
21
+
22
+ export interface AnonymousValueInterningInput {
23
+ readonly types: readonly LawAnonymousValueTypeNode[];
24
+ readonly fields: readonly LawAnonymousValueFieldNode[];
25
+ readonly variants: readonly LawAnonymousValueVariantNode[];
26
+ /** Whether this type is emitted as its own `Types/t_<sid>` prim (wrappers and framework scalars are not). */
27
+ readonly ownsPrim: (type: LawAnonymousValueTypeNode) => boolean;
28
+ }
29
+
30
+ export interface AnonymousValueInterning {
31
+ /** Every prim-owning type with a semantic key → its canonical type id (itself when it is the canonical one). */
32
+ readonly canonicalTypeIdById: ReadonlyMap<string, string>;
33
+ /** A duplicate type's member field → the canonical type's equivalent member (duplicates only). */
34
+ readonly canonicalFieldIdById: ReadonlyMap<string, string>;
35
+ /** A duplicate sum's variant → the canonical sum's equivalent variant (duplicates only). */
36
+ readonly canonicalVariantIdById: ReadonlyMap<string, string>;
37
+ /** The owner-free structural key of a type, or undefined for a recursive (or unknown) graph. */
38
+ readonly semanticTypeKey: (id: string) => string | undefined;
39
+ }
40
+
41
+ /** A type whose shape is spelled by the framework's own scalar prim, or a wrapper, owns no prim. */
42
+ export function anonymousTypeOwnsPrim(type: LawAnonymousValueTypeNode): boolean {
43
+ const expression = type.expression;
44
+ if (expression.kind === "string" && expression.constraints.length === 0) return false;
45
+ if (expression.kind === "integer" && expression.constraints.length === 0) return false;
46
+ if (expression.kind === "boolean") return false;
47
+ if (expression.kind === "nullable" || expression.kind === "default") return false;
48
+ return true;
49
+ }
50
+
51
+ export function internAnonymousValues(input: AnonymousValueInterningInput): AnonymousValueInterning {
52
+ const typeById = new Map<string, LawAnonymousValueTypeNode>(input.types.map((node) => [node.entity.id as string, node] as const));
53
+ const fieldsByOwner = new Map<string, LawAnonymousValueFieldNode[]>();
54
+ for (const field of input.fields) {
55
+ const owned = fieldsByOwner.get(field.owner.id) ?? [];
56
+ owned.push(field);
57
+ fieldsByOwner.set(field.owner.id, owned);
58
+ }
59
+ const variantsByOwner = new Map<string, LawAnonymousValueVariantNode[]>();
60
+ for (const variant of input.variants) {
61
+ const owned = variantsByOwner.get(variant.owner.id) ?? [];
62
+ owned.push(variant);
63
+ variantsByOwner.set(variant.owner.id, owned);
64
+ }
65
+
66
+ const memo = new Map<string, string | null>();
67
+ const semanticTypeKey = (id: string, active = new Set<string>()): string | undefined => {
68
+ const memoized = memo.get(id);
69
+ if (memoized !== undefined) return memoized ?? undefined;
70
+ if (active.has(id)) {
71
+ memo.set(id, null);
72
+ return undefined;
73
+ }
74
+ const node = typeById.get(id);
75
+ if (node === undefined) return undefined;
76
+ active.add(id);
77
+ const childKey = (childId: string): string | undefined => semanticTypeKey(childId, active);
78
+ let key: string | undefined;
79
+ switch (node.expression.kind) {
80
+ case "string":
81
+ case "number":
82
+ case "integer":
83
+ case "boolean":
84
+ case "null":
85
+ case "unknown":
86
+ case "enum":
87
+ case "literal":
88
+ key = JSON.stringify(node.expression);
89
+ break;
90
+ case "list": {
91
+ const child = childKey(node.expression.element.id);
92
+ if (child !== undefined) key = JSON.stringify(["list", child]);
93
+ break;
94
+ }
95
+ case "map": {
96
+ const child = childKey(node.expression.value.id);
97
+ if (child !== undefined) key = JSON.stringify(["map", child]);
98
+ break;
99
+ }
100
+ case "nullable":
101
+ case "default": {
102
+ // Wrappers do not own prims, but their meaning must survive structural
103
+ // interning. A nullable field is not the same contract as a non-null field.
104
+ const child = childKey(node.expression.value.id);
105
+ if (child !== undefined) key = JSON.stringify(node.expression.kind === "nullable"
106
+ ? ["nullable", child]
107
+ : ["default", child, node.expression.defaultValue]);
108
+ break;
109
+ }
110
+ case "record": {
111
+ const members: Array<readonly [string, boolean, string]> = [];
112
+ for (const field of fieldsByOwner.get(id) ?? []) {
113
+ const child = childKey(field.value.id);
114
+ if (child === undefined) break;
115
+ members.push([field.name, field.required, child]);
116
+ }
117
+ if (members.length === (fieldsByOwner.get(id)?.length ?? 0)) {
118
+ members.sort(([left], [right]) => left.localeCompare(right));
119
+ key = JSON.stringify(["record", members,
120
+ node.expression.semantic?.kind === "value-object" ? node.expression.semantic.geometry : undefined]);
121
+ }
122
+ break;
123
+ }
124
+ case "sum": {
125
+ const members: Array<readonly [string, string]> = [];
126
+ for (const variant of variantsByOwner.get(id) ?? []) {
127
+ const child = childKey(variant.value.id);
128
+ if (child === undefined) break;
129
+ // A union arm's discriminator is part of its wire contract. Two sums
130
+ // with the same carried value shapes but different arm labels cannot
131
+ // share one USDA prim: authoring reconstructs the nominal oneof from
132
+ // these labels before it decodes the generated Protobuf call.
133
+ members.push([variant.label ?? variant.entity.label, child]);
134
+ }
135
+ if (members.length === (variantsByOwner.get(id)?.length ?? 0)) {
136
+ members.sort(([leftLabel, leftChild], [rightLabel, rightChild]) =>
137
+ leftLabel.localeCompare(rightLabel) || leftChild.localeCompare(rightChild));
138
+ key = JSON.stringify(["sum", members]);
139
+ }
140
+ break;
141
+ }
142
+ case "recursive":
143
+ key = undefined;
144
+ break;
145
+ }
146
+ active.delete(id);
147
+ memo.set(id, key ?? null);
148
+ return key;
149
+ };
150
+
151
+ const canonicalTypeBySemanticKey = new Map<string, LawAnonymousValueTypeNode>();
152
+ const canonicalTypeIdById = new Map<string, string>();
153
+ for (const type of [...input.types].sort((left, right) => left.entity.sid.localeCompare(right.entity.sid))) {
154
+ if (!input.ownsPrim(type)) continue;
155
+ const key = semanticTypeKey(type.entity.id);
156
+ if (key === undefined) continue;
157
+ const canonical = canonicalTypeBySemanticKey.get(key);
158
+ if (canonical === undefined) {
159
+ canonicalTypeBySemanticKey.set(key, type);
160
+ canonicalTypeIdById.set(type.entity.id, type.entity.id);
161
+ continue;
162
+ }
163
+ canonicalTypeIdById.set(type.entity.id, canonical.entity.id);
164
+ }
165
+
166
+ // When two record/sum types share one canonical prim, their nested structural references must
167
+ // share the canonical child paths too. This preserves effects which target a nested payload field;
168
+ // no dangling path is allowed merely because its source owner was the non-canonical occurrence.
169
+ const canonicalFieldIdById = new Map<string, string>();
170
+ const canonicalVariantIdById = new Map<string, string>();
171
+ for (const [duplicateId, canonicalId] of canonicalTypeIdById) {
172
+ if (duplicateId === canonicalId) continue;
173
+ const canonicalFields = new Map<string, LawAnonymousValueFieldNode>();
174
+ for (const field of fieldsByOwner.get(canonicalId) ?? []) {
175
+ const child = semanticTypeKey(field.value.id);
176
+ if (child !== undefined) canonicalFields.set(JSON.stringify([field.name, field.required, child]), field);
177
+ }
178
+ for (const field of fieldsByOwner.get(duplicateId) ?? []) {
179
+ const child = semanticTypeKey(field.value.id);
180
+ const canonical = child === undefined
181
+ ? undefined
182
+ : canonicalFields.get(JSON.stringify([field.name, field.required, child]));
183
+ if (canonical !== undefined) canonicalFieldIdById.set(field.entity.id, canonical.entity.id);
184
+ }
185
+
186
+ const canonicalVariants = new Map<string, LawAnonymousValueVariantNode[]>();
187
+ for (const variant of variantsByOwner.get(canonicalId) ?? []) {
188
+ const child = semanticTypeKey(variant.value.id);
189
+ if (child === undefined) continue;
190
+ const key = JSON.stringify([variant.label ?? variant.entity.label, child]);
191
+ const matches = canonicalVariants.get(key) ?? [];
192
+ matches.push(variant);
193
+ canonicalVariants.set(key, matches);
194
+ }
195
+ for (const variant of variantsByOwner.get(duplicateId) ?? []) {
196
+ const child = semanticTypeKey(variant.value.id);
197
+ if (child === undefined) continue;
198
+ const key = JSON.stringify([variant.label ?? variant.entity.label, child]);
199
+ const canonical = canonicalVariants.get(key)?.shift();
200
+ if (canonical !== undefined) canonicalVariantIdById.set(variant.entity.id, canonical.entity.id);
201
+ }
202
+ }
203
+
204
+ return { canonicalTypeIdById, canonicalFieldIdById, canonicalVariantIdById, semanticTypeKey: (id) => semanticTypeKey(id) };
205
+ }
@@ -0,0 +1,27 @@
1
+ // NOMOS — The released-baseline surface `githolon baseline` and CI load (`@githolon/dsl/baseline`).
2
+ //
3
+ // One import for the three halves of CAP-660: the artifact (baseline_usda.ts), its export from
4
+ // verified units (baseline_export.ts) and its verification/import against a ledger (baseline_import.ts).
5
+ export {
6
+ BASELINE_NO_RELEASE,
7
+ NOMOS_BASELINE_FILENAME,
8
+ NOMOS_BASELINE_FORMAT,
9
+ baselineSha256,
10
+ isBaselineUsda,
11
+ parseBaselineUsda,
12
+ serializeBaselineUsda,
13
+ type BaselineSource,
14
+ type BaselineWireAllocation,
15
+ type ReleasedBaseline,
16
+ } from "./baseline_usda.js";
17
+ export { exportBaselineFromUnits, type BaselineExport, type BaselineExportInput } from "./baseline_export.js";
18
+ export {
19
+ BaselineRefusal,
20
+ assertBaselineRetained,
21
+ importBaseline,
22
+ verifyBaselinePin,
23
+ type BaselineImport,
24
+ type BaselinePin,
25
+ type BaselineRefusalCode,
26
+ } from "./baseline_import.js";
27
+ export { lawUnitDirectoryName } from "./law_units.js";
@@ -0,0 +1,432 @@
1
+ // NOMOS — EXPORT A RELEASED BASELINE FROM CUSTODY (architecture/domain_release_lifecycle.md, step 1).
2
+ //
3
+ // Input: a verified law unit set — the compiler's `build/<name>.units`, a release's units downloaded
4
+ // from the console registry, or the units a workspace's installed law names. Every unit hash is
5
+ // re-derived from the bytes before any opinion is read (release_inspection.ts `readInspectionUnits`),
6
+ // so the baseline can only ever describe law that actually exists.
7
+ //
8
+ // Output: the continuity released law carries, in the baseline artifact (baseline_usda.ts):
9
+ //
10
+ // * every ledger-minted declaration — its sid (the prim name), current label, prior labels and
11
+ // parent — with the generation recovered by re-minting (law_identity.ts mints a sid from
12
+ // `kind:parent:label:generation`, so the generation is provable from the sid, never guessed);
13
+ // * every retirement the released law declares, as a tombstone;
14
+ // * every operation field's transport allocation (canonical wire key + aliases), top-level and
15
+ // nested, under the ledger coordinate law_graph.ts allocates it by (wire_paths.ts).
16
+ //
17
+ // What released law does NOT carry — value contracts, merge drivers, tombstones older than the
18
+ // release's own declarations — stays in `nomos.identity.usda`. The baseline is the anchor, not
19
+ // the ledger. Prims whose sid is owned or structural (payload contracts, anonymous value types) are
20
+ // not identities the compiler reconciles, so they are read for their wire keys and nothing else.
21
+ import { createHash } from "node:crypto";
22
+ import { readInspectionUnits } from "./release_inspection.js";
23
+ import { lawUnitSetIdentityWith } from "./law_unit_set_identity.js";
24
+ import {
25
+ LAW_FRAMEWORK_PARENT,
26
+ lawEntityKey,
27
+ validateLawIdentityRegistry,
28
+ type LawEntityKind,
29
+ type LawIdentityEntity,
30
+ type LawIdentityRegistry,
31
+ } from "./law_identity.js";
32
+ import {
33
+ mintAggregateSid,
34
+ mintDomainSid,
35
+ mintFieldSid,
36
+ mintGlobalAggregateSid,
37
+ mintStableId,
38
+ } from "./stable_ids.js";
39
+ import type { StableAggregateId, StableFieldId, StableIds } from "./stable_ids_types.js";
40
+ import type { CompilerIdentityModel } from "./identity_model.js";
41
+ import {
42
+ indexUsdaPrims,
43
+ readUsdaPrims,
44
+ usdaChild,
45
+ usdaString,
46
+ usdaStringList,
47
+ type UsdaPrim,
48
+ } from "./law_layer_prims.js";
49
+ import {
50
+ BASELINE_NO_RELEASE,
51
+ serializeBaselineUsda,
52
+ baselineSha256,
53
+ type BaselineSource,
54
+ type BaselineWireAllocation,
55
+ type ReleasedBaseline,
56
+ } from "./baseline_usda.js";
57
+ import { listElementPath, mapValuePath, nestedMemberPath, unionVariantPath } from "./wire_paths.js";
58
+
59
+ /** The deepest generation re-minting looks for. A label recreated more often than this is unheard of. */
60
+ const MAX_RECOVERED_GENERATION = 64;
61
+
62
+ const sha256Hex = (bytes: Uint8Array): string => createHash("sha256").update(bytes).digest("hex");
63
+
64
+ /** Container prim name → the entity kind its children declare. */
65
+ const CONTAINER_KINDS: Readonly<Record<string, LawEntityKind>> = {
66
+ Aggregates: "aggregate",
67
+ Types: "type",
68
+ ObjectTypes: "object-type",
69
+ Contracts: "contract",
70
+ Relations: "relation",
71
+ Intents: "intent",
72
+ Reads: "read",
73
+ Policies: "policy",
74
+ Invariants: "invariant",
75
+ Capabilities: "capability",
76
+ WorkspaceTypes: "workspace-type",
77
+ Flows: "flow",
78
+ };
79
+
80
+ const SID = /^[0-9a-f]{16}$/u;
81
+
82
+ export interface BaselineExportInput {
83
+ /** A directory of law units (`build/<name>.units` or a materialised release). */
84
+ readonly unitsDir: string;
85
+ readonly source: BaselineSource;
86
+ readonly release?: string;
87
+ readonly series?: string;
88
+ readonly predecessor?: string;
89
+ readonly workspace?: string;
90
+ readonly workspaceHead?: string;
91
+ }
92
+
93
+ export interface BaselineExport {
94
+ readonly baseline: ReleasedBaseline;
95
+ /** The artifact bytes, deterministic for the same units and provenance. */
96
+ readonly text: string;
97
+ /** Its content address — what `nomos.project.ts` pins. */
98
+ readonly sha256: string;
99
+ /** Declarations whose sid is owned or structural; read for wire keys only. */
100
+ readonly nonLedgerPrims: number;
101
+ }
102
+
103
+ interface ScannedEntity {
104
+ readonly key: string;
105
+ readonly entity: LawIdentityEntity;
106
+ }
107
+
108
+ function generationOf(
109
+ kind: LawEntityKind,
110
+ parent: string | undefined,
111
+ labels: readonly string[],
112
+ sid: string,
113
+ ): number | undefined {
114
+ for (const label of labels) {
115
+ for (let generation = 0; generation <= MAX_RECOVERED_GENERATION; generation += 1) {
116
+ if (mintStableId(`law-entity:${kind}:${parent ?? "@root"}:${label}:generation:${generation}`) === sid) {
117
+ return generation;
118
+ }
119
+ }
120
+ }
121
+ return undefined;
122
+ }
123
+
124
+ /**
125
+ * Prove that `sid` is a ledger identity for (kind, parent, label lineage): either minted by
126
+ * law_identity.ts (generation recovered) or a legacy stable_ids.ts mint (generation 0). Returns the
127
+ * parent the ledger reconciles it under — a framework aggregate's is `@framework`, not its domain.
128
+ */
129
+ function classifyLedgerEntity(
130
+ kind: LawEntityKind,
131
+ parent: string | undefined,
132
+ domainSid: string | undefined,
133
+ labels: readonly string[],
134
+ sid: string,
135
+ ): { parent: string | undefined; generation: number } | undefined {
136
+ const generation = generationOf(kind, parent, labels, sid);
137
+ if (generation !== undefined) return { parent, generation };
138
+ if (kind === "aggregate") {
139
+ const framework = generationOf(kind, LAW_FRAMEWORK_PARENT, labels, sid);
140
+ if (framework !== undefined) return { parent: LAW_FRAMEWORK_PARENT, generation: framework };
141
+ if (labels.some((label) => mintGlobalAggregateSid(label) === sid)) return { parent: LAW_FRAMEWORK_PARENT, generation: 0 };
142
+ if (domainSid !== undefined && labels.some((label) => mintAggregateSid(domainSid, label) === sid)) {
143
+ return { parent, generation: 0 };
144
+ }
145
+ }
146
+ if (kind === "domain" && labels.some((label) => mintDomainSid(label) === sid)) return { parent: undefined, generation: 0 };
147
+ if (kind === "field" && parent !== undefined) {
148
+ const parentSid = parent.slice(parent.indexOf("_") + 1);
149
+ if (labels.some((label) => mintFieldSid(parentSid, label) === sid)) return { parent, generation: 0 };
150
+ }
151
+ return undefined;
152
+ }
153
+
154
+ function sidOf(prim: UsdaPrim): string | undefined {
155
+ const split = prim.name.indexOf("_");
156
+ const sid = split === -1 ? "" : prim.name.slice(split + 1);
157
+ return SID.test(sid) ? sid : undefined;
158
+ }
159
+
160
+ /** Walk one domain layer for its ledger entities, declared retirements and stable-id surface. */
161
+ function scanDomainLayer(
162
+ domainPrim: UsdaPrim,
163
+ out: { entities: ScannedEntity[]; nonLedger: { value: number } },
164
+ ): { name: string; stableIds: StableIds } | undefined {
165
+ const domainSid = sidOf(domainPrim);
166
+ const domainLabel = usdaString(domainPrim, "label");
167
+ if (domainSid === undefined || domainLabel === undefined) return undefined;
168
+ const domainLabels = [domainLabel, ...(usdaStringList(domainPrim, "was") ?? [])];
169
+ const domainClass = classifyLedgerEntity("domain", undefined, undefined, domainLabels, domainSid);
170
+ const domainKey = lawEntityKey("domain", domainSid);
171
+ if (domainClass !== undefined) {
172
+ out.entities.push({
173
+ key: domainKey,
174
+ entity: {
175
+ kind: "domain",
176
+ sid: domainSid,
177
+ label: domainLabel,
178
+ ...(domainLabels.length > 1 ? { priorLabels: domainLabels.slice(1) } : {}),
179
+ generation: domainClass.generation,
180
+ status: "active",
181
+ },
182
+ });
183
+ } else {
184
+ out.nonLedger.value += 1;
185
+ }
186
+
187
+ const aggregates: Record<string, StableAggregateId> = {};
188
+ const scanFields = (owner: UsdaPrim, ownerKey: string): Record<string, StableFieldId> => {
189
+ const fields: Record<string, StableFieldId> = {};
190
+ for (const fieldPrim of usdaChild(owner, "Fields")?.children ?? []) {
191
+ const sid = sidOf(fieldPrim);
192
+ const label = usdaString(fieldPrim, "label");
193
+ if (sid === undefined || label === undefined) continue;
194
+ const was = usdaStringList(fieldPrim, "was") ?? [];
195
+ const classified = classifyLedgerEntity("field", ownerKey, undefined, [label, ...was], sid);
196
+ if (classified === undefined) {
197
+ out.nonLedger.value += 1;
198
+ continue;
199
+ }
200
+ out.entities.push({
201
+ key: lawEntityKey("field", sid),
202
+ entity: {
203
+ kind: "field",
204
+ sid,
205
+ parent: ownerKey,
206
+ label,
207
+ ...(was.length > 0 ? { priorLabels: was } : {}),
208
+ generation: classified.generation,
209
+ status: "active",
210
+ },
211
+ });
212
+ fields[label] = { sid, ...(was.length > 0 ? { was: [...was] } : {}) };
213
+ }
214
+ return fields;
215
+ };
216
+
217
+ for (const container of domainPrim.children) {
218
+ const kind = CONTAINER_KINDS[container.name];
219
+ if (kind === undefined) continue;
220
+ for (const prim of container.children) {
221
+ const sid = sidOf(prim);
222
+ const label = usdaString(prim, "label");
223
+ if (sid === undefined || label === undefined) continue;
224
+ const was = usdaStringList(prim, "was") ?? [];
225
+ const classified = classifyLedgerEntity(kind, domainKey, domainSid, [label, ...was], sid);
226
+ if (classified === undefined) {
227
+ out.nonLedger.value += 1;
228
+ continue;
229
+ }
230
+ const key = lawEntityKey(kind, sid);
231
+ out.entities.push({
232
+ key,
233
+ entity: {
234
+ kind,
235
+ sid,
236
+ ...(classified.parent === undefined ? {} : { parent: classified.parent }),
237
+ label,
238
+ ...(was.length > 0 ? { priorLabels: was } : {}),
239
+ generation: classified.generation,
240
+ status: "active",
241
+ },
242
+ });
243
+ if (kind === "aggregate") {
244
+ const fields = scanFields(prim, key);
245
+ aggregates[label] = { sid, ...(was.length > 0 ? { was: [...was] } : {}), fields };
246
+ } else if (kind === "contract") {
247
+ scanFields(prim, key);
248
+ }
249
+ }
250
+ }
251
+
252
+ for (const retirement of usdaChild(domainPrim, "Retirements")?.children ?? []) {
253
+ const kind = usdaString(retirement, "entityKind");
254
+ const sid = usdaString(retirement, "sid");
255
+ const label = usdaString(retirement, "label");
256
+ if ((kind !== "aggregate" && kind !== "field") || sid === undefined || label === undefined || !SID.test(sid)) continue;
257
+ const parentSid = usdaString(retirement, "parentSid");
258
+ const parent = kind === "field"
259
+ ? (parentSid === undefined ? undefined : lawEntityKey("aggregate", parentSid))
260
+ : domainKey;
261
+ const classified = classifyLedgerEntity(kind, parent, domainSid, [label], sid);
262
+ if (classified === undefined) {
263
+ out.nonLedger.value += 1;
264
+ continue;
265
+ }
266
+ out.entities.push({
267
+ key: lawEntityKey(kind, sid),
268
+ entity: {
269
+ kind,
270
+ sid,
271
+ ...(classified.parent === undefined ? {} : { parent: classified.parent }),
272
+ label,
273
+ generation: classified.generation,
274
+ status: "retired",
275
+ },
276
+ });
277
+ }
278
+
279
+ return { name: domainLabel, stableIds: { v: 1, domain: domainSid, aggregates } };
280
+ }
281
+
282
+ /** Walk an intent's payload contracts for every field's transport allocation, nested included. */
283
+ function scanWireAllocations(
284
+ intentPrim: UsdaPrim,
285
+ prims: ReadonlyMap<string, UsdaPrim>,
286
+ out: BaselineWireAllocation[],
287
+ ): void {
288
+ const intentSid = sidOf(intentPrim);
289
+ if (intentSid === undefined) return;
290
+ const intent = lawEntityKey("intent", intentSid);
291
+ const seen = new Set<string>();
292
+ const allocate = (field: UsdaPrim, path: string): void => {
293
+ const canonical = usdaString(field, "wireKey");
294
+ if (canonical === undefined) return;
295
+ const aliases = usdaStringList(field, "wireAliases") ?? [];
296
+ const coordinate = `${intent}\u0000${path}`;
297
+ if (seen.has(coordinate)) return;
298
+ seen.add(coordinate);
299
+ out.push({ intent, field: path, protobuf: { canonical, ...(aliases.length > 0 ? { aliases } : {}) } });
300
+ };
301
+ const walkType = (typePath: string | undefined, path: string, visiting: ReadonlySet<string>): void => {
302
+ if (typePath === undefined || visiting.has(typePath)) return;
303
+ const type = prims.get(typePath);
304
+ if (type === undefined) return;
305
+ const next = new Set(visiting).add(typePath);
306
+ switch (usdaString(type, "typeKind")) {
307
+ case "record":
308
+ for (const field of usdaChild(type, "Fields")?.children ?? []) walkField(field, path, next);
309
+ break;
310
+ case "list":
311
+ walkType(type.rels.get("elementType")?.[0], listElementPath(path), next);
312
+ break;
313
+ case "map":
314
+ walkType(type.rels.get("valueType")?.[0], mapValuePath(path), next);
315
+ break;
316
+ case "union": {
317
+ const variants = usdaChild(type, "Variants")?.children ?? [];
318
+ variants.forEach((variant, index) => {
319
+ const label = usdaString(variant, "label") ?? String(index);
320
+ walkType(variant.rels.get("type")?.[0], unionVariantPath(path, label), next);
321
+ });
322
+ break;
323
+ }
324
+ case "recursive":
325
+ walkType(type.rels.get("body")?.[0], path, next);
326
+ break;
327
+ default:
328
+ break;
329
+ }
330
+ };
331
+ const walkField = (field: UsdaPrim, parentPath: string, visiting: ReadonlySet<string>): void => {
332
+ const label = usdaString(field, "label");
333
+ if (label === undefined) return;
334
+ const path = parentPath === "" ? label : nestedMemberPath(parentPath, label);
335
+ allocate(field, path);
336
+ walkType(field.rels.get("type")?.[0], path, visiting);
337
+ };
338
+ for (const relation of ["accepts", "publicAccepts"]) {
339
+ const contract = prims.get(intentPrim.rels.get(relation)?.[0] ?? "");
340
+ if (contract === undefined) continue;
341
+ for (const field of usdaChild(contract, "Fields")?.children ?? []) walkField(field, "", new Set());
342
+ }
343
+ }
344
+
345
+ /** Order entities so every parent precedes its children (validation and import both need it). */
346
+ function orderByParent(entities: readonly ScannedEntity[]): ScannedEntity[] {
347
+ const byKey = new Map(entities.map((scanned) => [scanned.key, scanned]));
348
+ const ordered: ScannedEntity[] = [];
349
+ const placed = new Set<string>();
350
+ const place = (scanned: ScannedEntity, trail: ReadonlySet<string>): void => {
351
+ if (placed.has(scanned.key)) return;
352
+ const parent = scanned.entity.parent;
353
+ if (parent !== undefined && parent !== LAW_FRAMEWORK_PARENT && !trail.has(parent)) {
354
+ const parentEntity = byKey.get(parent);
355
+ if (parentEntity !== undefined) place(parentEntity, new Set(trail).add(scanned.key));
356
+ }
357
+ placed.add(scanned.key);
358
+ ordered.push(scanned);
359
+ };
360
+ for (const scanned of [...entities].sort((l, r) => (l.key < r.key ? -1 : l.key > r.key ? 1 : 0))) place(scanned, new Set());
361
+ return ordered;
362
+ }
363
+
364
+ /** Export the baseline a verified unit set carries. Throws on unverifiable units or an unreadable layer. */
365
+ export function exportBaselineFromUnits(input: BaselineExportInput): BaselineExport {
366
+ const units = readInspectionUnits(input.unitsDir);
367
+ const unitSet = lawUnitSetIdentityWith(sha256Hex, units.map((unit) => ({ key: unit.key, unitHash: unit.hash })));
368
+
369
+ const roots: UsdaPrim[] = [];
370
+ for (const unit of units) {
371
+ for (const asset of unit.assets) {
372
+ if (!/^domains\/[^/]+\.usda$/u.test(asset.logicalPath) && asset.logicalPath !== "framework.usda") continue;
373
+ roots.push(...readUsdaPrims(new TextDecoder().decode(asset.bytes)));
374
+ }
375
+ }
376
+ const prims = indexUsdaPrims(roots);
377
+
378
+ const scanned = { entities: [] as ScannedEntity[], nonLedger: { value: 0 } };
379
+ const domains: CompilerIdentityModel["domains"] = {};
380
+ const wireAllocations: BaselineWireAllocation[] = [];
381
+ for (const [primPath, prim] of prims) {
382
+ if (!/^\/Nomos\/Domains\/d_[0-9a-f]{16}$/u.test(primPath)) continue;
383
+ const domain = scanDomainLayer(prim, scanned);
384
+ if (domain !== undefined) domains[domain.name] = { stableIds: domain.stableIds };
385
+ for (const intent of usdaChild(prim, "Intents")?.children ?? []) scanWireAllocations(intent, prims, wireAllocations);
386
+ }
387
+
388
+ const entities: Record<string, LawIdentityEntity> = {};
389
+ for (const { key, entity } of orderByParent(scanned.entities)) {
390
+ const prior = entities[key];
391
+ if (prior !== undefined) {
392
+ // One framework aggregate composed into several domain layers is one identity; the layers agree.
393
+ if (prior.kind !== entity.kind || prior.label !== entity.label) {
394
+ throw new Error(`baseline export: ${key} is declared as '${prior.label}' and '${entity.label}' in the same unit set`);
395
+ }
396
+ continue;
397
+ }
398
+ // A field's aggregate or a retirement's parent must be a ledger entity of this set; a child of a
399
+ // non-ledger prim (an owned contract's field) is read for wire keys elsewhere, not as identity.
400
+ const parent = entity.parent;
401
+ if (parent !== undefined && parent !== LAW_FRAMEWORK_PARENT && entities[parent] === undefined) {
402
+ scanned.nonLedger.value += 1;
403
+ continue;
404
+ }
405
+ entities[key] = entity;
406
+ }
407
+ const identity: LawIdentityRegistry = {
408
+ v: 1,
409
+ entities: Object.fromEntries(Object.entries(entities).sort(([l], [r]) => (l < r ? -1 : l > r ? 1 : 0))),
410
+ };
411
+ validateLawIdentityRegistry(identity);
412
+
413
+ const baselineBody: Omit<ReleasedBaseline, "format"> = {
414
+ source: input.source,
415
+ release: input.release ?? BASELINE_NO_RELEASE,
416
+ series: input.series ?? "",
417
+ predecessor: input.predecessor ?? "",
418
+ unitSet,
419
+ units: units.map((unit) => ({ key: unit.key, unitHash: unit.hash })),
420
+ workspace: input.workspace ?? "",
421
+ workspaceHead: input.workspaceHead ?? "",
422
+ continuity: { v: 1, domains, identity },
423
+ wireAllocations: [...wireAllocations].sort((l, r) => (l.intent < r.intent ? -1 : l.intent > r.intent ? 1 : l.field < r.field ? -1 : l.field > r.field ? 1 : 0)),
424
+ };
425
+ const text = serializeBaselineUsda(baselineBody);
426
+ return {
427
+ baseline: { format: "nomos.baseline.v1", ...baselineBody },
428
+ text,
429
+ sha256: baselineSha256(text),
430
+ nonLedgerPrims: scanned.nonLedger.value,
431
+ };
432
+ }