@githolon/dsl 0.102.4 → 0.103.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 (131) hide show
  1. package/compile_package.mjs +43 -2
  2. package/compile_proof.d.mts +47 -0
  3. package/compile_proof.mjs +343 -0
  4. package/dart/CHANGELOG.md +7 -0
  5. package/dart/lib/src/dev_cloud.dart +8 -8
  6. package/dart/lib/src/nms_generated_runtime.dart +2 -0
  7. package/dart/lib/src/runner/node-entry.mjs +5504 -1236
  8. package/dart/lib/src/runner/offline-install.mjs +4578 -528
  9. package/dart/lib/src/test_harness.dart +206 -152
  10. package/dart/lib/src/webview_bridge.dart +196 -89
  11. package/dart/pubspec.lock +1 -1
  12. package/dart/pubspec.yaml +2 -2
  13. package/dart/test/acquire_session_credentials_test.dart +4 -3
  14. package/dart/test/adv_realm_interference_test.dart +7 -6
  15. package/dart/test/cloud_bound_topology_test.dart +3 -3
  16. package/dart/test/cluster_births_test.dart +4 -3
  17. package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
  18. package/dart/test/cluster_local_autosync_test.dart +3 -2
  19. package/dart/test/cluster_scale_test.dart +3 -2
  20. package/dart/test/dlq_session_surface_test.dart +7 -6
  21. package/dart/test/first_app_gauntlet_test.dart +14 -21
  22. package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
  23. package/dart/test/four_layer_nested_topology_test.dart +4 -4
  24. package/dart/test/harness_phase_timeout_test.dart +2 -2
  25. package/dart/test/local_sync_inert_test.dart +1 -1
  26. package/dart/test/parent_signer_recovery_test.dart +2 -2
  27. package/dart/test/sealing_client_test.dart +4 -3
  28. package/dart/test/session_bound_test.dart +8 -7
  29. package/dart/test/test_harness_test.dart +13 -12
  30. package/dart/test/vo_read_typing_test.dart +4 -3
  31. package/dart/test/watch_after_offer_stall_test.dart +4 -3
  32. package/dart/test/webview_bridge_test.dart +102 -5
  33. package/dart_types/CHANGELOG.md +5 -0
  34. package/dart_types/lib/nomos_types.dart +9 -0
  35. package/dart_types/lib/src/nomos_ref.dart +20 -0
  36. package/dart_types/lib/src/optional_ref.dart +85 -0
  37. package/dart_types/lib/src/sha256_digest.dart +41 -0
  38. package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
  39. package/dart_types/pubspec.yaml +1 -1
  40. package/dart_types/test/optional_ref_test.dart +44 -0
  41. package/dart_types/test/sha256_digest_test.dart +18 -0
  42. package/package.json +9 -5
  43. package/runtime/generated-application-host.js +5365 -1105
  44. package/src/aggregate.ts +18 -0
  45. package/src/application_composition.ts +6 -32
  46. package/src/attested_core.ts +6 -3
  47. package/src/birth_meter.ts +1 -0
  48. package/src/birth_meter_ensure.ts +28 -2
  49. package/src/births.ts +1 -1
  50. package/src/build_package.ts +162 -538
  51. package/src/business_birth.ts +19 -3
  52. package/src/canonical_json.ts +21 -0
  53. package/src/check_package_main.ts +76 -35
  54. package/src/clear_directive.ts +19 -8
  55. package/src/codegen_dart.ts +305 -22
  56. package/src/codegen_dart_application.ts +844 -292
  57. package/src/codegen_dart_client.ts +1084 -407
  58. package/src/codegen_dart_scenario.ts +12 -12
  59. package/src/codegen_proof.ts +169 -39
  60. package/src/codegen_provider_dart.ts +17 -1
  61. package/src/codegen_ts.ts +298 -106
  62. package/src/compat_manifest.ts +2 -2
  63. package/src/compat_package_main.ts +8 -12
  64. package/src/compile_package_main.ts +1866 -597
  65. package/src/compose.ts +1 -1
  66. package/src/directive.ts +208 -15
  67. package/src/engine_entry.ts +165 -111
  68. package/src/evolve_preview.ts +43 -81
  69. package/src/extremum.ts +1 -1
  70. package/src/fields.ts +183 -4
  71. package/src/flow_gate.ts +75 -2
  72. package/src/framework/application_home.ts +2 -0
  73. package/src/framework/auth_provider_law.ts +2 -2
  74. package/src/framework/birth_meter.ts +7 -19
  75. package/src/framework/bootstrap.ts +0 -2
  76. package/src/framework/business_corrections.ts +9 -5
  77. package/src/framework/domain_lifecycle.ts +15 -29
  78. package/src/framework/encryption_scope.ts +2 -1
  79. package/src/framework/framework_aggregates.ts +2 -0
  80. package/src/framework/genesis.ts +16 -14
  81. package/src/framework/home.ts +20 -14
  82. package/src/framework/owned_custody.ts +9 -3
  83. package/src/framework/platform_admin.ts +3 -3
  84. package/src/framework/relations.ts +21 -13
  85. package/src/framework/signing_identity.ts +45 -40
  86. package/src/framework/workspace_invariant.ts +245 -0
  87. package/src/framework/workspaces.ts +23 -29
  88. package/src/index.ts +36 -16
  89. package/src/intent.ts +178 -6
  90. package/src/law_check.ts +32 -11
  91. package/src/law_graph.ts +2902 -0
  92. package/src/law_graph_usda.ts +1799 -0
  93. package/src/law_identity.ts +435 -0
  94. package/src/law_value_type.ts +230 -0
  95. package/src/lsp_server.ts +61 -31
  96. package/src/ops.ts +7 -0
  97. package/src/package_config.ts +141 -0
  98. package/src/package_dependencies.ts +108 -0
  99. package/src/package_dependency_usage.ts +150 -0
  100. package/src/package_source_imports.ts +71 -0
  101. package/src/plan_source_slice.ts +335 -0
  102. package/src/point_guard.ts +192 -0
  103. package/src/precondition.ts +44 -9
  104. package/src/read_projection_namespace.ts +1 -1
  105. package/src/runtime_zod.ts +44 -0
  106. package/src/secondary_ensure.ts +67 -14
  107. package/src/shape_compat.ts +1 -1
  108. package/src/stable_id_identity_bridge.ts +149 -0
  109. package/src/stable_id_lockfile.ts +278 -0
  110. package/src/stable_ids.ts +7 -2
  111. package/src/stable_ids_for_module.ts +348 -0
  112. package/src/stable_ids_types.ts +1 -1
  113. package/src/sum.ts +1 -1
  114. package/src/usd_state.ts +1 -1
  115. package/src/usda_relationship_integrity.ts +66 -0
  116. package/src/usda_text.ts +18 -0
  117. package/src/usdz.ts +78 -0
  118. package/src/value_object.ts +95 -0
  119. package/src/wire.ts +19 -0
  120. package/src/wire_encode.ts +22 -0
  121. package/src/workspace_sharding.ts +52 -36
  122. package/src/workspace_sharing.ts +28 -4
  123. package/src/zod_introspection.ts +1 -1
  124. package/dart/test/cluster_attested_test.dart +0 -201
  125. package/src/compile_engine.ts +0 -595
  126. package/src/manifest.ts +0 -1476
  127. package/src/read_projection_composition.ts +0 -117
  128. package/src/read_projection_manifest.ts +0 -115
  129. package/src/relation_composition.ts +0 -286
  130. package/src/usd.ts +0 -1967
  131. package/src/usd_layers.ts +0 -1776
@@ -0,0 +1,348 @@
1
+ import { createHash } from "node:crypto";
2
+ import { z } from "zod";
3
+ import type { DomainModule } from "./codegen_dart.js";
4
+ import { canonicalJson } from "./canonical_json.js";
5
+ import { encodeDriver } from "./drivers.js";
6
+ import type { Field } from "./fields.js";
7
+ import {
8
+ mintAggregateSid,
9
+ mintDomainSid,
10
+ mintFieldSid,
11
+ type StableAggregateId,
12
+ type StableFieldEvolution,
13
+ type StableFieldId,
14
+ type StableIds,
15
+ } from "./stable_ids.js";
16
+ import { encodeKernelSchema } from "./wire_encode.js";
17
+
18
+
19
+ /**
20
+ * The domain's STABLE-ID surface (#58): continuity-carried when the compiler attached
21
+ * `mod.stableIdContinuity` (renames resolved, sids + lineage carried), deterministic
22
+ * first-appearance minting otherwise — so the manifest stays a pure function of the
23
+ * module (+ its attached continuity). Any aggregate/field the continuity does not
24
+ * cover (a genuinely new appearance) mints fresh.
25
+ */
26
+ export function stableIdsForModule(mod: DomainModule): StableIds {
27
+ const continuity = mod.stableIdContinuity;
28
+ const domainSid = continuity?.domain ?? mintDomainSid(mod.name);
29
+ // The CURRENT field KINDS off the live handles (the retype arm's identity input —
30
+ // a `t.string()` → `t.int()` retype moves `kind` while the driver stays Lww).
31
+ const handlesById = new Map(mod.aggregates.map((a) => [a.id, a]));
32
+ const out: Record<string, StableAggregateId> = {};
33
+ for (const agg of [...mod.aggregates].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))) {
34
+ const prior = continuity?.aggregates[agg.id];
35
+ const aggSid = prior?.sid ?? mintAggregateSid(domainSid, agg.id);
36
+ const handleFields = (handlesById.get(agg.id)?.fields ?? {}) as Record<string, Field | undefined>;
37
+ const fields: Record<string, StableFieldId> = {};
38
+ // `encodeKernelSchema` is the typed storage palette and omits virtual relations; stable field
39
+ // identity therefore follows the exact stored field set without constructing a JSON law manifest.
40
+ for (const fieldName of Object.keys(encodeKernelSchema(agg)).sort()) {
41
+ const pf = prior?.fields[fieldName];
42
+ const kind = handleFields[fieldName]?.kind;
43
+ const handle = handleFields[fieldName];
44
+ const details = handle === undefined ? undefined : fieldValueContractDetails(handle);
45
+ const driver = handle === undefined ? undefined : encodeDriver(handle.driver);
46
+ const schema =
47
+ handle === undefined || kind === undefined || details === undefined || driver === undefined
48
+ ? undefined
49
+ : fieldSchemaIdentity(aggSid, pf?.sid ?? mintFieldSid(aggSid, fieldName), kind, details.contract, driver);
50
+ const evolutions =
51
+ handle === undefined || schema === undefined || driver === undefined || details === undefined
52
+ ? []
53
+ : fieldEvolutionRules(handle, pf, aggSid, pf?.sid ?? mintFieldSid(aggSid, fieldName), details, driver, schema);
54
+ fields[fieldName] = {
55
+ sid: pf?.sid ?? mintFieldSid(aggSid, fieldName),
56
+ ...(pf?.was !== undefined && pf.was.length > 0 ? { was: pf.was } : {}),
57
+ // The CURRENT kind, always from the live module (continuity never supplies it).
58
+ ...(kind !== undefined ? { kind } : {}),
59
+ ...(details !== undefined ? { contract: details.contract, shape: details.shape } : {}),
60
+ ...(schema !== undefined ? { schema } : {}),
61
+ ...(driver !== undefined ? { driver } : {}),
62
+ ...(evolutions.length > 0 ? { evolutions } : {}),
63
+ // Birth-presence law rides identity: the evolve guard reads it (fromBirth may not be
64
+ // NEWLY declared on a sid that existed without it).
65
+ ...(handleFields[fieldName]?.isFromBirth === true ? { fromBirth: true as const } : {}),
66
+ };
67
+ }
68
+ out[agg.id] = {
69
+ sid: aggSid,
70
+ ...(prior?.was !== undefined && prior.was.length > 0 ? { was: prior.was } : {}),
71
+ fields,
72
+ };
73
+ }
74
+ return { v: 1, domain: domainSid, aggregates: out };
75
+ }
76
+ /**
77
+ * Identity of the value a generated TS/Dart conflict resolver is allowed to call `T`.
78
+ * This is deliberately narrower than the domain hash (unrelated law may roll out without
79
+ * invalidating a conflict) and richer than `Field.kind` (enum arms, value-object members,
80
+ * nullability, references and encrypted/plain decoding all matter to the public type).
81
+ */
82
+ function fieldValueContractDetails(field: Field): { contract: string; shape: unknown } {
83
+ const valueObjectContract = (meta: NonNullable<Field["valueObject"]>) => ({
84
+ descriptor: meta.descriptor,
85
+ dependencies: meta.dependencies,
86
+ ...(meta.geometry !== undefined ? { geometry: meta.geometry } : {}),
87
+ });
88
+ const contractShape = normalizeFieldContract({
89
+ v: "nomos-field-value-contract-v1",
90
+ kind: field.kind,
91
+ jsonSchema: z.toJSONSchema(field.zod as z.ZodTypeAny),
92
+ ...(field.mapValueKind !== undefined ? { mapValueKind: field.mapValueKind } : {}),
93
+ ...(field.jsonShape !== undefined ? { jsonShape: field.jsonShape } : {}),
94
+ ...(field.valueObject !== undefined
95
+ ? { valueObject: valueObjectContract(field.valueObject) }
96
+ : {}),
97
+ ...(field.mapValueVo !== undefined
98
+ ? { mapValueVo: valueObjectContract(field.mapValueVo) }
99
+ : {}),
100
+ ...(field.refAggregateId !== undefined ? { refAggregateId: field.refAggregateId } : {}),
101
+ ...(field.foreignRef !== undefined ? { foreignRef: field.foreignRef } : {}),
102
+ ...(field.nomosRef !== undefined ? { nomosRef: field.nomosRef } : {}),
103
+ ...(field.versionedForeignRef !== undefined
104
+ ? { versionedForeignRef: field.versionedForeignRef }
105
+ : {}),
106
+ ...(field.nullSemantic !== undefined ? { nullSemantic: field.nullSemantic } : {}),
107
+ ...(field.isClearable === true ? { clearable: true } : {}),
108
+ ...(field.isEncrypted === true ? { encrypted: true } : {}),
109
+ });
110
+ const contract = createHash("sha256")
111
+ .update(
112
+ `nomos-field-value-contract:v1:${canonicalJson(contractShape)}`,
113
+ "utf8",
114
+ )
115
+ .digest("hex");
116
+ return { contract, shape: contractShape };
117
+ }
118
+
119
+ function fieldSchemaIdentity(
120
+ aggregateSid: string,
121
+ fieldSid: string,
122
+ kind: string,
123
+ valueContract: string,
124
+ driver: unknown,
125
+ ): string {
126
+ return createHash("sha256")
127
+ .update(
128
+ canonicalJson({
129
+ v: "nomos-field-schema-v2",
130
+ aggregate: aggregateSid,
131
+ field: fieldSid,
132
+ kind,
133
+ valueContract,
134
+ driver,
135
+ }),
136
+ "utf8",
137
+ )
138
+ .digest("hex");
139
+ }
140
+
141
+ function fieldEvolutionRules(
142
+ field: Field,
143
+ prior: StableFieldId | undefined,
144
+ aggregateSid: string,
145
+ fieldSid: string,
146
+ target: { contract: string; shape: unknown },
147
+ driver: unknown,
148
+ targetSchema: string,
149
+ ): StableFieldEvolution[] {
150
+ const bySource = new Map<string, StableFieldEvolution>();
151
+ const add = (rule: StableFieldEvolution, origin: string) => {
152
+ if (rule.from === rule.to) return;
153
+ const existing = bySource.get(rule.from);
154
+ if (existing !== undefined && canonicalJson(existing) !== canonicalJson(rule)) {
155
+ throw new Error(
156
+ `field evolution collision for source contract ${rule.from}: ${origin} disagrees with an existing route. ` +
157
+ `One source contract must have exactly one deterministic destination.`,
158
+ );
159
+ }
160
+ bySource.set(rule.from, rule);
161
+ };
162
+
163
+ for (const rule of prior?.evolutions ?? []) add(rule, "the prior release");
164
+
165
+ for (const declaration of field.evolutions ?? []) {
166
+ const source = fieldValueContractDetails(declaration.source);
167
+ const sourceSchema = fieldSchemaIdentity(
168
+ aggregateSid,
169
+ fieldSid,
170
+ declaration.source.kind,
171
+ source.contract,
172
+ driver,
173
+ );
174
+ if (declaration.mode === "identity" && !safeIdentityProjection(source.shape, target.shape)) {
175
+ throw new Error(
176
+ `.evolvesFrom(source) is not a safe identity widening: some source values are not valid ` +
177
+ `under the target field. Supply a finite business transform as the second argument.`,
178
+ );
179
+ }
180
+ add(
181
+ {
182
+ from: sourceSchema,
183
+ to: targetSchema,
184
+ source: {
185
+ kind: declaration.source.kind,
186
+ ...(declaration.source.mapValueKind !== undefined
187
+ ? { mapValueKind: declaration.source.mapValueKind }
188
+ : {}),
189
+ },
190
+ mode: declaration.mode,
191
+ ...(declaration.cases !== undefined ? { cases: declaration.cases } : {}),
192
+ },
193
+ ".evolvesFrom()",
194
+ );
195
+ }
196
+
197
+ // The compiler silently owns genuinely compatible widening between consecutive releases.
198
+ // The prior lock carries the full value descriptor, so this is a proof, not a cast.
199
+ if (
200
+ prior?.schema !== undefined &&
201
+ prior.schema !== targetSchema &&
202
+ prior.shape !== undefined &&
203
+ canonicalJson(prior.driver) === canonicalJson(driver) &&
204
+ safeAutomaticWidening(prior.shape, target.shape) &&
205
+ !bySource.has(prior.schema)
206
+ ) {
207
+ add(
208
+ {
209
+ from: prior.schema,
210
+ to: targetSchema,
211
+ source: {
212
+ kind: prior.kind ?? field.kind,
213
+ ...((prior.shape as { mapValueKind?: string } | undefined)?.mapValueKind !== undefined
214
+ ? { mapValueKind: (prior.shape as { mapValueKind: string }).mapValueKind }
215
+ : {}),
216
+ },
217
+ mode: "identity",
218
+ },
219
+ "the compiler's compatible-widening proof",
220
+ );
221
+ }
222
+
223
+ // Every retained source route must reach the one current target. This is the compile-time
224
+ // completeness gate for all still-supported eras, not merely the immediately prior law.
225
+ for (const source of bySource.keys()) {
226
+ const seen = new Set<string>();
227
+ let cursor = source;
228
+ while (cursor !== targetSchema) {
229
+ if (seen.has(cursor)) {
230
+ throw new Error(`field evolution graph cycles at ${cursor}; every source must terminate at ${targetSchema}`);
231
+ }
232
+ seen.add(cursor);
233
+ const next = bySource.get(cursor);
234
+ if (next === undefined) {
235
+ throw new Error(
236
+ `field evolution route from ${source} stops at ${cursor}, not the current contract ${targetSchema}. ` +
237
+ `Carry the intermediate source with .evolvesFrom(...) before this rollout.`,
238
+ );
239
+ }
240
+ cursor = next.to;
241
+ }
242
+ }
243
+
244
+ return [...bySource.values()].sort((a, b) => (a.from < b.from ? -1 : a.from > b.from ? 1 : 0));
245
+ }
246
+
247
+ /** Explicit identity projection: the tenant declares the business meaning is continuous;
248
+ * the compiler still proves every old value is accepted and its physical decoding is unchanged. */
249
+ function safeIdentityProjection(sourceShape: unknown, targetShape: unknown): boolean {
250
+ if (sourceShape === null || targetShape === null || typeof sourceShape !== "object" || typeof targetShape !== "object") {
251
+ return canonicalJson(sourceShape) === canonicalJson(targetShape);
252
+ }
253
+ const source = sourceShape as Record<string, unknown>;
254
+ const target = targetShape as Record<string, unknown>;
255
+ const physical = (shape: Record<string, unknown>) => {
256
+ const { kind, mapValueKind, jsonShape, encrypted } = shape;
257
+ return { kind, mapValueKind, jsonShape, encrypted };
258
+ };
259
+ return canonicalJson(physical(source)) === canonicalJson(physical(target))
260
+ && jsonSchemaSubset(source.jsonSchema, target.jsonSchema);
261
+ }
262
+
263
+ /** Automatic widening is stricter: every non-schema business descriptor must remain
264
+ * identical. Brands, units, references and null meaning require an explicit declaration. */
265
+ function safeAutomaticWidening(sourceShape: unknown, targetShape: unknown): boolean {
266
+ if (sourceShape === null || targetShape === null || typeof sourceShape !== "object" || typeof targetShape !== "object") {
267
+ return canonicalJson(sourceShape) === canonicalJson(targetShape);
268
+ }
269
+ const source = sourceShape as Record<string, unknown>;
270
+ const target = targetShape as Record<string, unknown>;
271
+ const metadata = (shape: Record<string, unknown>) => {
272
+ const { jsonSchema: _jsonSchema, ...rest } = shape;
273
+ return rest;
274
+ };
275
+ return canonicalJson(metadata(source)) === canonicalJson(metadata(target))
276
+ && jsonSchemaSubset(source.jsonSchema, target.jsonSchema);
277
+ }
278
+
279
+ function jsonSchemaSubset(source: unknown, target: unknown): boolean {
280
+ if (canonicalJson(source) === canonicalJson(target)) return true;
281
+ if (source === null || target === null || typeof source !== "object" || typeof target !== "object") return false;
282
+ const s = source as Record<string, unknown>;
283
+ const t = target as Record<string, unknown>;
284
+ if (Array.isArray(s.enum)) return s.enum.every((value) => jsonSchemaAcceptsLiteral(t, value));
285
+ if (Object.prototype.hasOwnProperty.call(s, "const")) return jsonSchemaAcceptsLiteral(t, s.const);
286
+ const sUnion = (Array.isArray(s.anyOf) ? s.anyOf : Array.isArray(s.oneOf) ? s.oneOf : undefined);
287
+ if (sUnion !== undefined) return sUnion.every((arm) => jsonSchemaSubset(arm, target));
288
+ const tUnion = (Array.isArray(t.anyOf) ? t.anyOf : Array.isArray(t.oneOf) ? t.oneOf : undefined);
289
+ if (tUnion !== undefined) return tUnion.some((arm) => jsonSchemaSubset(source, arm));
290
+ const sourceTypes = new Set(Array.isArray(s.type) ? s.type : s.type === undefined ? [] : [s.type]);
291
+ const targetTypes = new Set(Array.isArray(t.type) ? t.type : t.type === undefined ? [] : [t.type]);
292
+ if ([...sourceTypes].some((type) => !targetTypes.has(type))) return false;
293
+ if (sourceTypes.has("object")) {
294
+ const sp = (s.properties ?? {}) as Record<string, unknown>;
295
+ const tp = (t.properties ?? {}) as Record<string, unknown>;
296
+ const sr = new Set(Array.isArray(s.required) ? s.required as string[] : []);
297
+ const tr = new Set(Array.isArray(t.required) ? t.required as string[] : []);
298
+ if ([...tr].some((name) => !sr.has(name))) return false;
299
+ for (const [name, schema] of Object.entries(sp)) {
300
+ if (tp[name] === undefined) {
301
+ if (t.additionalProperties === false) return false;
302
+ } else if (!jsonSchemaSubset(schema, tp[name])) {
303
+ return false;
304
+ }
305
+ }
306
+ if (s.additionalProperties !== false && t.additionalProperties === false) return false;
307
+ }
308
+ if (sourceTypes.has("array") && s.items !== undefined && t.items !== undefined) {
309
+ if (!jsonSchemaSubset(s.items, t.items)) return false;
310
+ }
311
+ return sourceTypes.size > 0 && [...sourceTypes].every((type) => targetTypes.has(type));
312
+ }
313
+
314
+ function jsonSchemaAcceptsLiteral(schema: Record<string, unknown>, value: unknown): boolean {
315
+ if (Array.isArray(schema.enum)) return schema.enum.some((candidate) => canonicalJson(candidate) === canonicalJson(value));
316
+ if (Object.prototype.hasOwnProperty.call(schema, "const")) return canonicalJson(schema.const) === canonicalJson(value);
317
+ const union = Array.isArray(schema.anyOf) ? schema.anyOf : Array.isArray(schema.oneOf) ? schema.oneOf : undefined;
318
+ if (union !== undefined) return union.some((arm) => arm !== null && typeof arm === "object" && jsonSchemaAcceptsLiteral(arm as Record<string, unknown>, value));
319
+ const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value === "number" ? "number" : typeof value;
320
+ const types = new Set(Array.isArray(schema.type) ? schema.type : schema.type === undefined ? [] : [schema.type]);
321
+ return types.has(actual) || (actual === "number" && types.has("integer") && Number.isInteger(value));
322
+ }
323
+
324
+ /** JSON Schema contains a handful of set-valued arrays whose authored order is not part
325
+ * of the accepted value language. Normalize those (and VO enum `values`) so a cosmetic
326
+ * enum/union reorder does not manufacture an N/N+1 incompatibility. */
327
+ function normalizeFieldContract(value: unknown, parentKey = ""): unknown {
328
+ if (Array.isArray(value)) {
329
+ const items = value.map((item) => normalizeFieldContract(item));
330
+ if (["enum", "required", "type", "values", "oneOf", "anyOf", "allOf"].includes(parentKey)) {
331
+ return items.sort((a, b) => {
332
+ const left = canonicalJson(a);
333
+ const right = canonicalJson(b);
334
+ return left < right ? -1 : left > right ? 1 : 0;
335
+ });
336
+ }
337
+ return items;
338
+ }
339
+ if (value !== null && typeof value === "object") {
340
+ return Object.fromEntries(
341
+ Object.entries(value as Record<string, unknown>).map(([key, child]) => [
342
+ key,
343
+ normalizeFieldContract(child, key),
344
+ ]),
345
+ );
346
+ }
347
+ return value;
348
+ }
@@ -8,7 +8,7 @@
8
8
  * RUNTIME type graph (DomainModule's optional `stableIdContinuity`) never drags
9
9
  * `node:crypto` into a tenant scaffold's typecheck (scaffolds compile without
10
10
  * `@types/node`). The minting/derivation machinery lives in `stable_ids.ts`
11
- * (build-time only — `nomos-compile` + `@githolon/dsl/manifest`).
11
+ * (build-time only — `nomos-compile` + `stable_ids_for_module.ts`).
12
12
  */
13
13
 
14
14
  /** The read decoder needed to interpret one evolution rule's source value. */
package/src/sum.ts CHANGED
@@ -26,7 +26,7 @@
26
26
  * VALIDATION RULE (manifest-load, not type-level): the `sumField` parameter MUST be
27
27
  * an `int`-kind field of the `of`-aggregate. The DSL type system cannot enforce this
28
28
  * at the `sum(id, sumField)` call site (the aggregate is not yet known), but
29
- * `AggregateSchemas::parse` (readmodel/src/manifest.rs) performs a no-fallback reject
29
+ * typed USDA projection-law admission performs a no-fallback reject
30
30
  * if the field is absent or non-int on the declared aggregate.
31
31
  *
32
32
  * ORDER-SENSITIVE GUARDRAIL: sum exposes NO `.first`/`.take`/`.orderBy`. Same
package/src/usd_state.ts CHANGED
@@ -46,7 +46,7 @@
46
46
  * never the runtime barrel. The deployable package bytes and the domainHash do not
47
47
  * move — state emission reads the PROJECTION, it never enters the law.
48
48
  */
49
- import { primName, usdaString, usdaStringArray } from "./usd_layers.js";
49
+ import { primName, usdaString, usdaStringArray } from "./usda_text.js";
50
50
 
51
51
  /** The state-layer format marker (customLayerData `nomos:format`). */
52
52
  export const NOMOS_USD_STATE_LAYER_FORMAT = "nomos.usd-state-layer.v1";
@@ -0,0 +1,66 @@
1
+ // NOMOS USDA law is stricter than a general OpenUSD scene: every internal typed relationship must
2
+ // resolve inside the authenticated closure. OpenUSD intentionally permits dangling relationships,
3
+ // so stock usdcat cannot provide this Nomos-specific compile-validity guarantee.
4
+
5
+ export interface UsdaRelationshipAsset {
6
+ readonly logicalPath: string;
7
+ readonly utf8: string;
8
+ }
9
+
10
+ interface PrimFrame {
11
+ readonly indent: number;
12
+ readonly path: string;
13
+ }
14
+
15
+ interface RelationshipTarget {
16
+ readonly asset: string;
17
+ readonly line: number;
18
+ readonly path: string;
19
+ }
20
+
21
+ const PRIM_DECLARATION = /^(\s*)(?:def|over)(?:\s+[A-Za-z_][A-Za-z0-9_:]*)?\s+"([^"]+)"\s*$/;
22
+ const RELATIONSHIP_TARGET = /<([^>]+)>/g;
23
+
24
+ /**
25
+ * Refuse internal relationship targets which do not name a prim authored anywhere in the closure.
26
+ *
27
+ * This intentionally checks only absolute `/Nomos` targets. Asset paths, external scene references
28
+ * and ordinary USD relationships retain normal OpenUSD semantics; Nomos law identities do not.
29
+ */
30
+ export function assertUsdaRelationshipIntegrity(assets: readonly UsdaRelationshipAsset[]): void {
31
+ const prims = new Set<string>();
32
+ const targets: RelationshipTarget[] = [];
33
+
34
+ for (const asset of assets) {
35
+ const stack: PrimFrame[] = [];
36
+ const lines = asset.utf8.split(/\r?\n/);
37
+ for (const [offset, line] of lines.entries()) {
38
+ const declaration = PRIM_DECLARATION.exec(line);
39
+ if (declaration !== null) {
40
+ const indent = declaration[1]!.length;
41
+ while (stack.length > 0 && stack[stack.length - 1]!.indent >= indent) stack.pop();
42
+ const parent = stack[stack.length - 1]?.path ?? "";
43
+ const path = `${parent}/${declaration[2]!}`;
44
+ stack.push({ indent, path });
45
+ prims.add(path);
46
+ }
47
+
48
+ for (const match of line.matchAll(RELATIONSHIP_TARGET)) {
49
+ const path = match[1]!;
50
+ if (path === "/Nomos" || path.startsWith("/Nomos/")) {
51
+ targets.push({ asset: asset.logicalPath, line: offset + 1, path });
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ const missing = targets.filter((target) => !prims.has(target.path));
58
+ if (missing.length === 0) return;
59
+ const examples = missing.slice(0, 8).map((target) =>
60
+ `${target.asset}:${target.line} targets missing prim <${target.path}>`).join("\n ");
61
+ const remainder = missing.length > 8 ? `\n ... and ${missing.length - 8} more` : "";
62
+ throw new Error(
63
+ `typed USDA relationship integrity failed (${missing.length} dangling internal target${missing.length === 1 ? "" : "s"}):\n ` +
64
+ `${examples}${remainder}`,
65
+ );
66
+ }
@@ -0,0 +1,18 @@
1
+ // NOMOS — minimal deterministic USDA text literals for build-time emitters.
2
+ // This module deliberately has no parser, generic value tree, flattening model or law vocabulary.
3
+
4
+ /** Escape a string for a double-quoted USDA string literal. */
5
+ export function usdaString(value: string): string {
6
+ return JSON.stringify(value);
7
+ }
8
+
9
+ /** Emit a USDA `string[]` literal. */
10
+ export function usdaStringArray(items: readonly string[]): string {
11
+ return `[${items.map(usdaString).join(", ")}]`;
12
+ }
13
+
14
+ /** A valid USD prim-name identifier for an arbitrary authored label. */
15
+ export function primName(id: string): string {
16
+ const sanitized = id.replace(/[^A-Za-z0-9_]/g, "_");
17
+ return /^[A-Za-z_]/.test(sanitized) ? sanitized : `_${sanitized}`;
18
+ }
package/src/usdz.ts ADDED
@@ -0,0 +1,78 @@
1
+ // Deterministic USDZ transport for canonical Nomos law. This is deliberately the
2
+ // uncompressed OpenUSD package profile: USDA first, 64-byte-aligned file payloads,
3
+ // fixed timestamps, and no host-specific metadata.
4
+
5
+ const CRC_TABLE = (() => {
6
+ const table = new Uint32Array(256);
7
+ for (let n = 0; n < 256; n += 1) {
8
+ let value = n;
9
+ for (let bit = 0; bit < 8; bit += 1) {
10
+ value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
11
+ }
12
+ table[n] = value >>> 0;
13
+ }
14
+ return table;
15
+ })();
16
+
17
+ export function crc32(bytes: Uint8Array): number {
18
+ let value = 0xffffffff;
19
+ for (const byte of bytes) value = CRC_TABLE[(value ^ byte) & 0xff]! ^ (value >>> 8);
20
+ return (value ^ 0xffffffff) >>> 0;
21
+ }
22
+
23
+ export function usdzPack(entries: ReadonlyArray<readonly [string, Uint8Array]>): Uint8Array {
24
+ if (entries.length === 0 || !entries[0]![0].endsWith(".usda")) {
25
+ throw new Error("USDZ law package must place its composed USDA root layer first");
26
+ }
27
+ const jsonEntry = entries.find(([name]) => name.toLowerCase().endsWith(".json"));
28
+ if (jsonEntry !== undefined) {
29
+ throw new Error(
30
+ `Nomos law USDZ cannot contain JSON ('${jsonEntry[0]}'); ` +
31
+ "express law and package metadata as typed USDA",
32
+ );
33
+ }
34
+ const chunks: Uint8Array[] = [];
35
+ const central: Uint8Array[] = [];
36
+ let offset = 0;
37
+ const w16 = (v: number): number[] => [v & 0xff, (v >>> 8) & 0xff];
38
+ const w32 = (v: number): number[] => [v & 0xff, (v >>> 8) & 0xff, (v >>> 16) & 0xff, (v >>> 24) & 0xff];
39
+ for (const [name, data] of entries) {
40
+ const nameBytes = new TextEncoder().encode(name);
41
+ const crc = crc32(data);
42
+ const headerLen = 30 + nameBytes.length;
43
+ const misalignment = (offset + headerLen) % 64;
44
+ let extraLen = 0;
45
+ if (misalignment !== 0) {
46
+ extraLen = 64 - misalignment;
47
+ if (extraLen < 4) extraLen += 64;
48
+ }
49
+ const padding = extraLen === 0 ? 0 : extraLen - 4;
50
+ const local = new Uint8Array([
51
+ ...w32(0x04034b50), ...w16(20), ...w16(0), ...w16(0), ...w16(0), ...w16(0x21),
52
+ ...w32(crc), ...w32(data.length), ...w32(data.length), ...w16(nameBytes.length),
53
+ ...w16(extraLen), ...nameBytes,
54
+ ...(extraLen > 0 ? [...w16(0x1986), ...w16(padding), ...new Array<number>(padding).fill(0)] : []),
55
+ ]);
56
+ chunks.push(local, data);
57
+ central.push(new Uint8Array([
58
+ ...w32(0x02014b50), ...w16(20), ...w16(20), ...w16(0), ...w16(0), ...w16(0),
59
+ ...w16(0x21), ...w32(crc), ...w32(data.length), ...w32(data.length),
60
+ ...w16(nameBytes.length), ...w16(0), ...w16(0), ...w16(0), ...w16(0),
61
+ ...w32(0), ...w32(offset), ...nameBytes,
62
+ ]));
63
+ offset += local.length + data.length;
64
+ }
65
+ const centralStart = offset;
66
+ const centralLen = central.reduce((sum, entry) => sum + entry.length, 0);
67
+ const eocd = new Uint8Array([
68
+ ...w32(0x06054b50), ...w16(0), ...w16(0), ...w16(entries.length), ...w16(entries.length),
69
+ ...w32(centralLen), ...w32(centralStart), ...w16(0),
70
+ ]);
71
+ const output = new Uint8Array(offset + centralLen + eocd.length);
72
+ let at = 0;
73
+ for (const chunk of [...chunks, ...central, eocd]) {
74
+ output.set(chunk, at);
75
+ at += chunk.length;
76
+ }
77
+ return output;
78
+ }
@@ -58,6 +58,7 @@ import { z } from "zod";
58
58
  import { isRuntimeLaw } from "./authoring_context.js";
59
59
  import type { Driver } from "./drivers.js";
60
60
  import {
61
+ isSha256DigestSchema,
61
62
  valueObjectField,
62
63
  type Field,
63
64
  type GeometryKind,
@@ -281,6 +282,99 @@ export function idOf(aggregate: { id: string; __isContract?: true } | string): z
281
282
  return schema;
282
283
  }
283
284
 
285
+ /**
286
+ * A directive-payload publication pin. Like `idOf(...)`, this is a compile-time
287
+ * annotation over an ordinary JSON wire value; generated clients expose one
288
+ * `VersionedNomosRef<TVersion, TSeries>` so callers cannot mismatch workspace,
289
+ * type, publication id, series id, or current-pointer law.
290
+ */
291
+ export const NOMOS_VERSIONED_REF_BRAND: unique symbol = Symbol.for("nomos.versionedRefOf");
292
+
293
+ export interface VersionedRefOfMeta {
294
+ readonly targetAggregateType: string;
295
+ readonly targetKind: "aggregate" | "contract";
296
+ readonly seriesAggregateType: string;
297
+ readonly seriesKind: "aggregate" | "contract";
298
+ readonly currentField: string;
299
+ }
300
+
301
+ type RefTarget = {
302
+ id: string;
303
+ __isContract?: true;
304
+ fields: Record<string, { nomosRef?: { targetAggregateId: string } }>;
305
+ };
306
+
307
+ const refTargetMeta = (target: RefTarget, helper: string): { type: string; kind: "aggregate" | "contract" } => {
308
+ const type = target.id;
309
+ if (typeof type !== "string" || type === "") {
310
+ throw new Error(`${helper}: pass an aggregate or contract handle with a stable id.`);
311
+ }
312
+ return {
313
+ type,
314
+ kind: target.__isContract === true ? "contract" : "aggregate",
315
+ };
316
+ };
317
+
318
+ const nomosAddressSchema = z.object({
319
+ workspace: z.string().min(1),
320
+ type: z.string().min(1),
321
+ id: z.string().min(1),
322
+ });
323
+
324
+ /**
325
+ * A payload field holding an immutable publication and its stable current-version
326
+ * series. The kernel receives `{pinned, series}`; `current` is compiled law and
327
+ * never caller-controlled.
328
+ */
329
+ export function versionedRefOf(opts: {
330
+ target: RefTarget;
331
+ series: RefTarget;
332
+ current: string;
333
+ }): z.ZodType<{ pinned: z.infer<typeof nomosAddressSchema>; series: z.infer<typeof nomosAddressSchema> }> {
334
+ const target = refTargetMeta(opts.target, "versionedRefOf");
335
+ const series = refTargetMeta(opts.series, "versionedRefOf");
336
+ if (typeof opts.current !== "string" || opts.current === "") {
337
+ throw new Error("versionedRefOf: current must name the stable series current-pointer field.");
338
+ }
339
+ const current = opts.series.fields[opts.current];
340
+ if (current === undefined) {
341
+ throw new Error(
342
+ `versionedRefOf: series '${series.type}' has no current field '${opts.current}'.`,
343
+ );
344
+ }
345
+ if (current.nomosRef?.targetAggregateId !== target.type) {
346
+ throw new Error(
347
+ `versionedRefOf: '${series.type}.${opts.current}' must be t.nomosRef(${target.type}); ` +
348
+ "it is the O(1) current pointer used for freshness.",
349
+ );
350
+ }
351
+ const schema = z.object({ pinned: nomosAddressSchema, series: nomosAddressSchema });
352
+ Object.defineProperty(schema, NOMOS_VERSIONED_REF_BRAND, {
353
+ value: {
354
+ targetAggregateType: target.type,
355
+ targetKind: target.kind,
356
+ seriesAggregateType: series.type,
357
+ seriesKind: series.kind,
358
+ currentField: opts.current,
359
+ } satisfies VersionedRefOfMeta,
360
+ enumerable: false,
361
+ });
362
+ return schema;
363
+ }
364
+
365
+ /** Resolve `versionedRefOf(...)` metadata through omission/null wrappers. */
366
+ export function versionedRefOfMetaOf(zt: z.ZodTypeAny): VersionedRefOfMeta | undefined {
367
+ let cur: z.ZodTypeAny | undefined = zt;
368
+ for (let hops = 0; cur !== undefined && hops < 16; hops++) {
369
+ const branded = (cur as unknown as Record<symbol, unknown>)[NOMOS_VERSIONED_REF_BRAND];
370
+ if (typeof branded === "object" && branded !== null) return branded as VersionedRefOfMeta;
371
+ const kind = zodKind(cur);
372
+ if (kind !== "optional" && kind !== "default" && kind !== "nullable") return undefined;
373
+ cur = zodDef(cur).innerType;
374
+ }
375
+ return undefined;
376
+ }
377
+
284
378
  /**
285
379
  * THE BRANDED-ID BRAND (dart_codegen_parity.md P2 — deliberately NOT an aggregate reference) —
286
380
  * `brandedId("SiteId")` is a NOMINAL string: wire-identical to `z.string()` but the generated TS is a branded alias
@@ -408,6 +502,7 @@ function describeZod(
408
502
  ...(idOfTargetKindOf(schema) === "contract" ? { idOfContract: true as const } : {}),
409
503
  };
410
504
  }
505
+ if (isSha256DigestSchema(schema)) return { type: "string", digest: "sha256" };
411
506
  return typeof brandedBrand === "string" ? { type: "string", brand: brandedBrand } : { type: "string" };
412
507
  }
413
508
  case "record": {