@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.
- package/compile_package.mjs +43 -2
- package/compile_proof.d.mts +47 -0
- package/compile_proof.mjs +343 -0
- package/dart/CHANGELOG.md +7 -0
- package/dart/lib/src/dev_cloud.dart +8 -8
- package/dart/lib/src/nms_generated_runtime.dart +2 -0
- package/dart/lib/src/runner/node-entry.mjs +5504 -1236
- package/dart/lib/src/runner/offline-install.mjs +4578 -528
- package/dart/lib/src/test_harness.dart +206 -152
- package/dart/lib/src/webview_bridge.dart +196 -89
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_credentials_test.dart +4 -3
- package/dart/test/adv_realm_interference_test.dart +7 -6
- package/dart/test/cloud_bound_topology_test.dart +3 -3
- package/dart/test/cluster_births_test.dart +4 -3
- package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
- package/dart/test/cluster_local_autosync_test.dart +3 -2
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +7 -6
- package/dart/test/first_app_gauntlet_test.dart +14 -21
- package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
- package/dart/test/four_layer_nested_topology_test.dart +4 -4
- package/dart/test/harness_phase_timeout_test.dart +2 -2
- package/dart/test/local_sync_inert_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +2 -2
- package/dart/test/sealing_client_test.dart +4 -3
- package/dart/test/session_bound_test.dart +8 -7
- package/dart/test/test_harness_test.dart +13 -12
- package/dart/test/vo_read_typing_test.dart +4 -3
- package/dart/test/watch_after_offer_stall_test.dart +4 -3
- package/dart/test/webview_bridge_test.dart +102 -5
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/nomos_types.dart +9 -0
- package/dart_types/lib/src/nomos_ref.dart +20 -0
- package/dart_types/lib/src/optional_ref.dart +85 -0
- package/dart_types/lib/src/sha256_digest.dart +41 -0
- package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/optional_ref_test.dart +44 -0
- package/dart_types/test/sha256_digest_test.dart +18 -0
- package/package.json +9 -5
- package/runtime/generated-application-host.js +5365 -1105
- package/src/aggregate.ts +18 -0
- package/src/application_composition.ts +6 -32
- package/src/attested_core.ts +6 -3
- package/src/birth_meter.ts +1 -0
- package/src/birth_meter_ensure.ts +28 -2
- package/src/births.ts +1 -1
- package/src/build_package.ts +162 -538
- package/src/business_birth.ts +19 -3
- package/src/canonical_json.ts +21 -0
- package/src/check_package_main.ts +76 -35
- package/src/clear_directive.ts +19 -8
- package/src/codegen_dart.ts +305 -22
- package/src/codegen_dart_application.ts +844 -292
- package/src/codegen_dart_client.ts +1084 -407
- package/src/codegen_dart_scenario.ts +12 -12
- package/src/codegen_proof.ts +169 -39
- package/src/codegen_provider_dart.ts +17 -1
- package/src/codegen_ts.ts +298 -106
- package/src/compat_manifest.ts +2 -2
- package/src/compat_package_main.ts +8 -12
- package/src/compile_package_main.ts +1866 -597
- package/src/compose.ts +1 -1
- package/src/directive.ts +208 -15
- package/src/engine_entry.ts +165 -111
- package/src/evolve_preview.ts +43 -81
- package/src/extremum.ts +1 -1
- package/src/fields.ts +183 -4
- package/src/flow_gate.ts +75 -2
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +2 -2
- package/src/framework/birth_meter.ts +7 -19
- package/src/framework/bootstrap.ts +0 -2
- package/src/framework/business_corrections.ts +9 -5
- package/src/framework/domain_lifecycle.ts +15 -29
- package/src/framework/encryption_scope.ts +2 -1
- package/src/framework/framework_aggregates.ts +2 -0
- package/src/framework/genesis.ts +16 -14
- package/src/framework/home.ts +20 -14
- package/src/framework/owned_custody.ts +9 -3
- package/src/framework/platform_admin.ts +3 -3
- package/src/framework/relations.ts +21 -13
- package/src/framework/signing_identity.ts +45 -40
- package/src/framework/workspace_invariant.ts +245 -0
- package/src/framework/workspaces.ts +23 -29
- package/src/index.ts +36 -16
- package/src/intent.ts +178 -6
- package/src/law_check.ts +32 -11
- package/src/law_graph.ts +2902 -0
- package/src/law_graph_usda.ts +1799 -0
- package/src/law_identity.ts +435 -0
- package/src/law_value_type.ts +230 -0
- package/src/lsp_server.ts +61 -31
- package/src/ops.ts +7 -0
- package/src/package_config.ts +141 -0
- package/src/package_dependencies.ts +108 -0
- package/src/package_dependency_usage.ts +150 -0
- package/src/package_source_imports.ts +71 -0
- package/src/plan_source_slice.ts +335 -0
- package/src/point_guard.ts +192 -0
- package/src/precondition.ts +44 -9
- package/src/read_projection_namespace.ts +1 -1
- package/src/runtime_zod.ts +44 -0
- package/src/secondary_ensure.ts +67 -14
- package/src/shape_compat.ts +1 -1
- package/src/stable_id_identity_bridge.ts +149 -0
- package/src/stable_id_lockfile.ts +278 -0
- package/src/stable_ids.ts +7 -2
- package/src/stable_ids_for_module.ts +348 -0
- package/src/stable_ids_types.ts +1 -1
- package/src/sum.ts +1 -1
- package/src/usd_state.ts +1 -1
- package/src/usda_relationship_integrity.ts +66 -0
- package/src/usda_text.ts +18 -0
- package/src/usdz.ts +78 -0
- package/src/value_object.ts +95 -0
- package/src/wire.ts +19 -0
- package/src/wire_encode.ts +22 -0
- package/src/workspace_sharding.ts +52 -36
- package/src/workspace_sharing.ts +28 -4
- package/src/zod_introspection.ts +1 -1
- package/dart/test/cluster_attested_test.dart +0 -201
- package/src/compile_engine.ts +0 -595
- package/src/manifest.ts +0 -1476
- package/src/read_projection_composition.ts +0 -117
- package/src/read_projection_manifest.ts +0 -115
- package/src/relation_composition.ts +0 -286
- package/src/usd.ts +0 -1967
- package/src/usd_layers.ts +0 -1776
package/src/build_package.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* THE GENERIC PACKAGE BUILD SURFACE — `@githolon/dsl/build-package` (#M4).
|
|
10
10
|
*
|
|
11
|
-
* BUILD-TIME ONLY (imports `node:crypto`
|
|
11
|
+
* BUILD-TIME ONLY (imports `node:crypto` for derived artifact digests): reached via the
|
|
12
12
|
* `@githolon/dsl/build-package` subpath, NEVER the runtime barrel — the engine bundle
|
|
13
13
|
* must stay node-builtin-free (esbuild `--platform=neutral`).
|
|
14
14
|
*
|
|
@@ -19,37 +19,26 @@
|
|
|
19
19
|
* 15-positional-parameter `module(...)` helper (dedupe by wire id, the
|
|
20
20
|
* impure-capability directive check, default read-models, and the
|
|
21
21
|
* HASH-LOAD-BEARING omit-when-empty key discipline).
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* read routing lives in the package OpenAPI now. Kept as a function for callers that want the value.
|
|
26
|
-
* * `buildIdentity(modules)` — the per-domain IDENTITY emit + admits_domain_hash registry (#193/#136),
|
|
27
|
-
* hoisted from `emit_identity.ts` (palette-gap domains recorded-and-excluded). The admission identity
|
|
28
|
-
* is carried in the package USD IR + the committed `nomos.stable-ids.json` lockfile; the old
|
|
29
|
-
* `domain_identity_*.json` sidecars (and `writeIdentity`) are REMOVED. The per-domain hash is surfaced
|
|
30
|
-
* in the compile summary.
|
|
31
|
-
* * `emitUsdJsonForModules(modules)` — the one-line USD-IR emit every
|
|
32
|
-
* `emit_*_usd_json.ts` wrapped (`emitUsd` over `/Nomos/<domain>` layers).
|
|
22
|
+
* Projection routes and admission identity are deliberately absent from this surface: both now
|
|
23
|
+
* come from the typed USDA law graph. The retired JSON read/identity manifest builders were
|
|
24
|
+
* removed so tenant tooling cannot accidentally create a second law representation.
|
|
33
25
|
*/
|
|
34
26
|
import { createHash } from "node:crypto";
|
|
35
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
36
|
-
import { join } from "node:path";
|
|
37
|
-
|
|
38
|
-
import { z } from "zod";
|
|
39
27
|
|
|
40
28
|
import type { AggregateHandle } from "./aggregate.js";
|
|
41
29
|
import type { Directive } from "./directive.js";
|
|
42
30
|
import type { DomainModule, PermissionVocabulary, DartImport } from "./codegen_dart.js";
|
|
43
|
-
import {
|
|
31
|
+
import type { AnyCount } from "./count.js";
|
|
44
32
|
import type { QueryDecl } from "./query.js";
|
|
45
33
|
import type { SpatialDecl } from "./spatial.js";
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
34
|
+
import type { AnySum } from "./sum.js";
|
|
35
|
+
import type { AnyExtremum } from "./extremum.js";
|
|
48
36
|
import type { DerivedDecl } from "./derived.js";
|
|
49
37
|
import type { CombinedDecl } from "./combined.js";
|
|
50
38
|
import type { ImpureCapabilityDecl } from "./codegen_provider_dart.js";
|
|
51
39
|
import type { WorkspaceTypeDecl } from "./workspace_type.js";
|
|
52
40
|
import type { WorkspaceInvariantDecl } from "./framework/workspace_invariant.js";
|
|
41
|
+
import { withWorkspaceSharingInvitationExports } from "./workspace_sharing.js";
|
|
53
42
|
import {
|
|
54
43
|
applicationLanguageOf,
|
|
55
44
|
assertApplicationLanguageCoversAuthoredDirectives,
|
|
@@ -67,6 +56,7 @@ import { withClearDirectives } from "./clear_directive.js";
|
|
|
67
56
|
import { expandRoleExports, rolesOf } from "./role.js";
|
|
68
57
|
import {
|
|
69
58
|
withAttestedReadExports,
|
|
59
|
+
NomosSourceFrontier,
|
|
70
60
|
isTrustSourceDecl,
|
|
71
61
|
isTrustPolicyDecl,
|
|
72
62
|
type TrustSourceDecl,
|
|
@@ -77,15 +67,14 @@ import {
|
|
|
77
67
|
mergeRelationTypeDeclarations,
|
|
78
68
|
type RelationTypeDecl,
|
|
79
69
|
} from "./relation_schema.js";
|
|
80
|
-
import { domainHash, emitManifestBytes } from "./manifest.js";
|
|
81
70
|
import { collectValueObjectDescriptors } from "./value_object.js";
|
|
82
|
-
import { emitUsd } from "./usd.js";
|
|
83
71
|
import { expandCapabilityExports } from "./capability_exports.js";
|
|
84
72
|
import { RelationTuple, relationTuplesByObject } from "./framework/relations.js";
|
|
85
73
|
import { signerDelegationByUser } from "./framework/signing_identity.js";
|
|
86
74
|
import { AuthProvider, setAuthProvider, authProvider } from "./framework/workspaces.js";
|
|
87
75
|
import { parentAttestedBirthMarker } from "./births.js";
|
|
88
76
|
import { discoverUnsanctionedEnsures, sanctioningEnsureDirective } from "./secondary_ensure.js";
|
|
77
|
+
import { probeEnsuredTypes, probePlanOps } from "./codegen_proof.js";
|
|
89
78
|
import { assertIdOfLint } from "./idof_lint.js";
|
|
90
79
|
import { assertSurfacedResolvers } from "./surfaced_lint.js";
|
|
91
80
|
import {
|
|
@@ -191,6 +180,12 @@ export interface DomainModuleSpec {
|
|
|
191
180
|
readonly dartImports?: readonly DartImport[];
|
|
192
181
|
readonly dartRefImports?: DomainModule["dartRefImports"];
|
|
193
182
|
readonly permissionVocabulary?: PermissionVocabulary;
|
|
183
|
+
/**
|
|
184
|
+
* Layer-emission only: the caller is composing one authored file from a domain that has already
|
|
185
|
+
* passed the Surfaced resolver lint as a complete multi-file domain. Cross-file aggregate and
|
|
186
|
+
* resolver declarations are intentionally incomplete in an individual OpenUSD layer.
|
|
187
|
+
*/
|
|
188
|
+
readonly deferCrossModuleSurfacedLint?: boolean;
|
|
194
189
|
}
|
|
195
190
|
|
|
196
191
|
/**
|
|
@@ -212,6 +207,7 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
|
212
207
|
directivesOf(merged),
|
|
213
208
|
domain,
|
|
214
209
|
);
|
|
210
|
+
merged = withWorkspaceSharingInvitationExports(merged, applicationLanguage.workspaceSharing);
|
|
215
211
|
}
|
|
216
212
|
merged = expandBusinessBirthExports(merged, spec.domain ?? spec.name);
|
|
217
213
|
merged = withOwnedCustodyExports(merged);
|
|
@@ -245,6 +241,31 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
|
245
241
|
byId.set(policy.invitationLaw.claim.id, policy.invitationLaw.claim);
|
|
246
242
|
}
|
|
247
243
|
const discoveredDirectives = directivesOf(merged);
|
|
244
|
+
// A directive builder already possessed the typed aggregate handle. Retain that compiler link so
|
|
245
|
+
// re-exporting a lawful directive is self-contained: its local target schema joins the domain without
|
|
246
|
+
// a second magic re-export. A cross-workspace birth remains a dependency edge, not a copied child schema.
|
|
247
|
+
for (const directive of discoveredDirectives) {
|
|
248
|
+
if (directive.birthsChild !== true && directive.aggregate !== undefined) {
|
|
249
|
+
const prior = byId.get(directive.aggregate.id);
|
|
250
|
+
byId.set(
|
|
251
|
+
directive.aggregate.id,
|
|
252
|
+
(prior as { businessBirthInternal?: unknown } | undefined)?.businessBirthInternal === true
|
|
253
|
+
? { ...directive.aggregate, businessBirthInternal: true } as AggregateHandle
|
|
254
|
+
: directive.aggregate,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
for (const output of directive.secondaryOutputs ?? []) {
|
|
258
|
+
if (output.aggregate !== undefined) {
|
|
259
|
+
const prior = byId.get(output.aggregate.id);
|
|
260
|
+
byId.set(
|
|
261
|
+
output.aggregate.id,
|
|
262
|
+
(prior as { businessBirthInternal?: unknown } | undefined)?.businessBirthInternal === true
|
|
263
|
+
? { ...output.aggregate, businessBirthInternal: true } as AggregateHandle
|
|
264
|
+
: output.aggregate,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
248
269
|
const directives = applyConflictPolicyResolvers(
|
|
249
270
|
applicationLanguage === undefined
|
|
250
271
|
? discoveredDirectives
|
|
@@ -286,6 +307,114 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
|
286
307
|
const declaresOwnedRead = [...byId.values()].some(
|
|
287
308
|
(a) => (a as { audience?: { kind?: unknown } }).audience?.kind === "owned",
|
|
288
309
|
);
|
|
310
|
+
// BUSINESS-BIRTH INITIALISER EFFECT SIGNATURE: `birth(A, { initialise })`
|
|
311
|
+
// lowers to a private `nomosSeedA` directive. The tenant writes the
|
|
312
|
+
// initializer, but never receives that generated directive and therefore
|
|
313
|
+
// cannot honestly append `.alsoEnsures(B)` to it. Capture the initializer's
|
|
314
|
+
// observed typed effects into the compiler-owned seed before applying the
|
|
315
|
+
// same closed-effect check used for ordinary, tenant-authored directives.
|
|
316
|
+
for (let index = 0; index < directives.length; index += 1) {
|
|
317
|
+
const candidate = directives[index]!;
|
|
318
|
+
if (candidate.businessBirthSeed !== true) continue;
|
|
319
|
+
const where = `${spec.domain ?? spec.name}/${candidate.id}`;
|
|
320
|
+
const probed = probePlanOps(candidate, where);
|
|
321
|
+
if (probed === undefined) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
`[business birth effects] ${where}: Nomos could not analyse the generated birth initializer. ` +
|
|
324
|
+
"Supply a lawful .example(...) for any cross-field payload constraints; the child seed " +
|
|
325
|
+
"will not be emitted with an incomplete effect signature.",
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
const declared = new Set<string>([
|
|
329
|
+
`${candidate.marker}:${candidate.aggregateId}`,
|
|
330
|
+
...(candidate.secondaryOutputs ?? []).map(
|
|
331
|
+
(output) => `${output.marker}:${output.aggregateId}`,
|
|
332
|
+
),
|
|
333
|
+
]);
|
|
334
|
+
const additions: NonNullable<Directive<unknown>["secondaryOutputs"]>[number][] = [];
|
|
335
|
+
for (const op of probed.ops) {
|
|
336
|
+
const marker = op.marker;
|
|
337
|
+
const aggregateId = op.aggregateType;
|
|
338
|
+
if (marker === undefined || aggregateId === undefined) continue;
|
|
339
|
+
const key = `${marker}:${aggregateId}`;
|
|
340
|
+
if (declared.has(key)) continue;
|
|
341
|
+
const aggregate = byId.get(aggregateId);
|
|
342
|
+
if (aggregate === undefined) {
|
|
343
|
+
throw new Error(
|
|
344
|
+
`[business birth effects] ${where}: the initializer writes ${marker} -> ${aggregateId}, ` +
|
|
345
|
+
"but that aggregate is not part of the domain. Export its aggregate declaration so the " +
|
|
346
|
+
"generated child law can carry its schema.",
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
declared.add(key);
|
|
350
|
+
additions.push({ marker, aggregateId, aggregate });
|
|
351
|
+
}
|
|
352
|
+
if (additions.length > 0) {
|
|
353
|
+
directives[index] = {
|
|
354
|
+
...candidate,
|
|
355
|
+
secondaryOutputs: [...(candidate.secondaryOutputs ?? []), ...additions],
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// OWNED CREATE/ENSURE EFFECT SIGNATURE: `create(Owned)` / `ensure(Owned, id)` deliberately seats the
|
|
360
|
+
// verified creator on the framework RelationTuple in the SAME plan. That consequence is Nomos-generated,
|
|
361
|
+
// so the tenant must not know to spell `.alsoEnsures(RelationTuple)` themselves; make the transformed law
|
|
362
|
+
// state the hidden effect honestly before the generic effect checker probes it.
|
|
363
|
+
for (let index = 0; index < directives.length; index += 1) {
|
|
364
|
+
const candidate = directives[index]!;
|
|
365
|
+
const target = byId.get(candidate.aggregateId);
|
|
366
|
+
const ownsTarget = target?.audience?.kind === "owned";
|
|
367
|
+
const birthsOwnedRecord = candidate.marker === "creates" || candidate.marker === "ensures";
|
|
368
|
+
const alreadyDeclared = (candidate.secondaryOutputs ?? []).some(
|
|
369
|
+
(output) => output.marker === "ensures" && output.aggregateId === RelationTuple.id,
|
|
370
|
+
);
|
|
371
|
+
if (ownsTarget && birthsOwnedRecord && !alreadyDeclared) {
|
|
372
|
+
directives[index] = {
|
|
373
|
+
...candidate,
|
|
374
|
+
secondaryOutputs: [
|
|
375
|
+
...(candidate.secondaryOutputs ?? []),
|
|
376
|
+
{ marker: "ensures", aggregateId: RelationTuple.id, aggregate: RelationTuple },
|
|
377
|
+
],
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// `writeTuple(...)` and attested foreign reads have deterministic framework bookkeeping
|
|
382
|
+
// effects visible in the plan but intentionally hidden from tenant business types. Infer
|
|
383
|
+
// only those framework effects into the signature before the closed-effect checker runs.
|
|
384
|
+
// Arbitrary tenant aggregates remain explicit `.alsoEnsures(...)` declarations.
|
|
385
|
+
const inferredFrameworkEnsures = new Map<string, AggregateHandle>([
|
|
386
|
+
[RelationTuple.id, RelationTuple as AggregateHandle],
|
|
387
|
+
[NomosSourceFrontier.id, NomosSourceFrontier as AggregateHandle],
|
|
388
|
+
]);
|
|
389
|
+
for (let index = 0; index < directives.length; index += 1) {
|
|
390
|
+
const candidate = directives[index]!;
|
|
391
|
+
if (candidate.birthsChild === true || candidate.manifestOnly === true) continue;
|
|
392
|
+
const probedEnsures = probeEnsuredTypes(
|
|
393
|
+
candidate,
|
|
394
|
+
`${spec.domain ?? spec.name}/${candidate.id}`,
|
|
395
|
+
);
|
|
396
|
+
const additions: AggregateHandle[] = [];
|
|
397
|
+
for (const aggregateId of new Set(probedEnsures)) {
|
|
398
|
+
const aggregate = inferredFrameworkEnsures.get(aggregateId);
|
|
399
|
+
if (aggregate === undefined) continue;
|
|
400
|
+
if ((candidate.secondaryOutputs ?? []).some(
|
|
401
|
+
(output) => output.marker === "ensures" && output.aggregateId === aggregate.id,
|
|
402
|
+
)) continue;
|
|
403
|
+
additions.push(aggregate);
|
|
404
|
+
}
|
|
405
|
+
if (additions.length === 0) continue;
|
|
406
|
+
directives[index] = {
|
|
407
|
+
...candidate,
|
|
408
|
+
secondaryOutputs: [
|
|
409
|
+
...(candidate.secondaryOutputs ?? []),
|
|
410
|
+
...additions.map((aggregate) => ({
|
|
411
|
+
marker: "ensures" as const,
|
|
412
|
+
aggregateId: aggregate.id,
|
|
413
|
+
aggregate,
|
|
414
|
+
})),
|
|
415
|
+
],
|
|
416
|
+
};
|
|
417
|
+
}
|
|
289
418
|
if (declaresPrivateRead) {
|
|
290
419
|
if (!byId.has(AuthProvider.id)) {
|
|
291
420
|
byId.set(AuthProvider.id, declaresOwnedRead
|
|
@@ -490,17 +619,17 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
|
490
619
|
? { ...relationTuplesByObject, businessBirthInternal: true } as QueryDecl
|
|
491
620
|
: relationTuplesByObject);
|
|
492
621
|
}
|
|
493
|
-
// FIRST-CLASS VALUE OBJECTS (value_object.ts): the deduped, sorted descriptors of every
|
|
494
|
-
// named VO the domain's aggregates carry (transitive nested VOs included) — the input to
|
|
495
|
-
// the additive `nomosValueObjects` USD key. Omit-when-empty (era rule): a VO-free domain's
|
|
496
|
-
// module — and thus its USD/package bytes — is byte-identical to before VOs existed.
|
|
497
|
-
const valueObjects = collectValueObjectDescriptors(aggregates);
|
|
498
622
|
// DOMAIN CONTRACTS (domain_contracts.md): the public DTO shapes this domain declares, discovered by
|
|
499
623
|
// shape across the merged module list. Deduped by id (later-wins), sorted for byte-determinism. Omit-
|
|
500
624
|
// when-empty (era rule): a contract-free domain's module/USD/package bytes are byte-identical.
|
|
501
625
|
const contractById = new Map<string, import("./contract.js").ContractHandle>();
|
|
502
626
|
for (const c of contractsOf(merged)) contractById.set(c.id, c);
|
|
503
627
|
const contracts = [...contractById.values()].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
628
|
+
// FIRST-CLASS VALUE OBJECTS (value_object.ts): close over both stored aggregates and public
|
|
629
|
+
// contracts. A contract may be the only local owner of a named value object (especially a
|
|
630
|
+
// cross-domain publication DTO); collecting aggregates alone left a dangling type reference in
|
|
631
|
+
// the canonical law graph. Transitive nested VOs remain included by the descriptor collector.
|
|
632
|
+
const valueObjects = collectValueObjectDescriptors([...aggregates, ...contracts]);
|
|
504
633
|
// SPATIAL × VO PAIRING (fail at build, not at fold): a spatial index over a VO-typed field
|
|
505
634
|
// must index a GEO value object (an RFC 7946 geometry — the R*Tree's bbox source). A
|
|
506
635
|
// structural VO (e.g. a PostalAddress) has no geometry to index. Non-VO fields (a
|
|
@@ -619,11 +748,13 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
|
619
748
|
// chained conflict-reference codegen consumes AND the canonical manifest records as the
|
|
620
749
|
// hash-bearing conflictResolution contract. A Surfaced-free domain runs zero checks and is
|
|
621
750
|
// byte-identical; a resolver policy deliberately becomes part of business law.
|
|
622
|
-
const surfacedResolvers =
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
751
|
+
const surfacedResolvers = spec.deferCrossModuleSurfacedLint
|
|
752
|
+
? []
|
|
753
|
+
: assertSurfacedResolvers(
|
|
754
|
+
aggregates,
|
|
755
|
+
directives,
|
|
756
|
+
spec.domain ?? spec.name,
|
|
757
|
+
);
|
|
627
758
|
const publicApplicationIntents = applicationLanguage === undefined
|
|
628
759
|
? undefined
|
|
629
760
|
: [
|
|
@@ -696,513 +827,6 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
|
696
827
|
};
|
|
697
828
|
}
|
|
698
829
|
|
|
699
|
-
// ─────────────────────────────────────────────────────────────────────────────────
|
|
700
|
-
// buildReadManifest — domain_manifests.json (hoisted from emit_manifests.ts)
|
|
701
|
-
// ─────────────────────────────────────────────────────────────────────────────────
|
|
702
|
-
|
|
703
|
-
/** One field's read-projection descriptor: the leaf kind (+ a map's value kind). */
|
|
704
|
-
export interface FieldKindDescriptor {
|
|
705
|
-
kind: string;
|
|
706
|
-
mapValueKind?: string;
|
|
707
|
-
}
|
|
708
|
-
export type AggregateSchema = Record<string, FieldKindDescriptor>;
|
|
709
|
-
export type AggregateFieldKinds = Record<string, AggregateSchema>;
|
|
710
|
-
|
|
711
|
-
export interface QueryKeyField {
|
|
712
|
-
field: string;
|
|
713
|
-
kind: string;
|
|
714
|
-
}
|
|
715
|
-
export interface QueryDescriptor {
|
|
716
|
-
id: string;
|
|
717
|
-
key: QueryKeyField[];
|
|
718
|
-
returns: string;
|
|
719
|
-
}
|
|
720
|
-
export interface CountDescriptor {
|
|
721
|
-
id: string;
|
|
722
|
-
of: string;
|
|
723
|
-
by: string | null;
|
|
724
|
-
/** The canonical predicate (`WirePred` in the Rust parser — `{kind:"eq"|"ne"|"and"|"or",…}`).
|
|
725
|
-
* OMITTED entirely when the count declares none: predicate-free read manifests stay
|
|
726
|
-
* byte-identical (the Rust side reads it `#[serde(rename = "where", default)]`). */
|
|
727
|
-
where?: unknown;
|
|
728
|
-
}
|
|
729
|
-
export interface SpatialDescriptor {
|
|
730
|
-
id: string;
|
|
731
|
-
of: string;
|
|
732
|
-
on: string;
|
|
733
|
-
}
|
|
734
|
-
export interface ProjectionReturnDescriptor {
|
|
735
|
-
type: string;
|
|
736
|
-
nullable: boolean;
|
|
737
|
-
jsonSchema: unknown;
|
|
738
|
-
}
|
|
739
|
-
export interface DerivedDescriptor {
|
|
740
|
-
id: string;
|
|
741
|
-
of: string;
|
|
742
|
-
returns: ProjectionReturnDescriptor;
|
|
743
|
-
}
|
|
744
|
-
export interface CombinedDescriptor {
|
|
745
|
-
id: string;
|
|
746
|
-
of: string;
|
|
747
|
-
refField: string;
|
|
748
|
-
reads: string;
|
|
749
|
-
returns: ProjectionReturnDescriptor;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
/** One maintained-sum descriptor (the projection's `WireSum` shape — note `field`). */
|
|
753
|
-
export interface SumDescriptor {
|
|
754
|
-
id: string;
|
|
755
|
-
of: string;
|
|
756
|
-
field: string;
|
|
757
|
-
by: string | null;
|
|
758
|
-
/** The canonical predicate — same omit-when-absent discipline as {@link CountDescriptor}. */
|
|
759
|
-
where?: unknown;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
/** One min/max read-manifest descriptor — the Rust `WireExtremum` shape (#47): identical
|
|
763
|
-
* to {@link SumDescriptor}; the KIND is implicit in which array it rides (`mins` vs `maxes`). */
|
|
764
|
-
export interface ExtremumDescriptor {
|
|
765
|
-
id: string;
|
|
766
|
-
of: string;
|
|
767
|
-
field: string;
|
|
768
|
-
by: string | null;
|
|
769
|
-
where?: unknown;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
/** The combined read-manifest artifact (the EXACT wire shape the Rust read engine parses). */
|
|
773
|
-
export interface ReadManifest {
|
|
774
|
-
aggregateFieldKinds: AggregateFieldKinds;
|
|
775
|
-
queries: QueryDescriptor[];
|
|
776
|
-
counts: CountDescriptor[];
|
|
777
|
-
spatials: SpatialDescriptor[];
|
|
778
|
-
deriveds: DerivedDescriptor[];
|
|
779
|
-
combineds: CombinedDescriptor[];
|
|
780
|
-
/** OMITTED when no domain declares a sum (read manifests predating sums stay byte-identical). */
|
|
781
|
-
sums?: SumDescriptor[];
|
|
782
|
-
/** OMITTED when no domain declares a min/max (extremum-free read manifests stay byte-identical). */
|
|
783
|
-
mins?: ExtremumDescriptor[];
|
|
784
|
-
maxes?: ExtremumDescriptor[];
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
type ZodLike = {
|
|
788
|
-
_def?: { type?: string; innerType?: ZodLike };
|
|
789
|
-
def?: { type?: string; innerType?: ZodLike };
|
|
790
|
-
};
|
|
791
|
-
|
|
792
|
-
function zodDef(zt: ZodLike): NonNullable<ZodLike["_def"]> {
|
|
793
|
-
return zt._def ?? zt.def ?? {};
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
function zodTypeName(zt: ZodLike): string {
|
|
797
|
-
return zodDef(zt).type ?? "unknown";
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
function describeProjectionReturn(schema: z.ZodTypeAny): ProjectionReturnDescriptor {
|
|
801
|
-
let cur = schema as unknown as ZodLike;
|
|
802
|
-
let nullable = false;
|
|
803
|
-
while (
|
|
804
|
-
zodTypeName(cur) === "optional" ||
|
|
805
|
-
zodTypeName(cur) === "default" ||
|
|
806
|
-
zodTypeName(cur) === "nullable"
|
|
807
|
-
) {
|
|
808
|
-
nullable = true;
|
|
809
|
-
const inner = zodDef(cur).innerType;
|
|
810
|
-
if (inner === undefined) break;
|
|
811
|
-
cur = inner;
|
|
812
|
-
}
|
|
813
|
-
return {
|
|
814
|
-
type: zodTypeName(cur),
|
|
815
|
-
nullable,
|
|
816
|
-
jsonSchema: z.toJSONSchema(schema),
|
|
817
|
-
};
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
/** Capture one aggregate's field KINDS straight off the DSL `Field` objects. */
|
|
821
|
-
function schemaOf(agg: AggregateHandle): AggregateSchema {
|
|
822
|
-
const fields: AggregateSchema = {};
|
|
823
|
-
// STORED fields only — virtual `t.hasMany` inverses are partitioned into `agg.hasMany`.
|
|
824
|
-
for (const [name, field] of Object.entries(agg.fields)) {
|
|
825
|
-
const descriptor: FieldKindDescriptor = { kind: field.kind };
|
|
826
|
-
if (field.mapValueKind !== undefined) descriptor.mapValueKind = field.mapValueKind;
|
|
827
|
-
fields[name] = descriptor;
|
|
828
|
-
}
|
|
829
|
-
return fields;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
/** Stable JSON for an aggregate schema, to detect a genuine cross-domain divergence. */
|
|
833
|
-
function stable(schema: AggregateSchema): string {
|
|
834
|
-
const sorted: AggregateSchema = {};
|
|
835
|
-
for (const k of Object.keys(schema).sort()) sorted[k] = schema[k]!;
|
|
836
|
-
return JSON.stringify(sorted);
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
/**
|
|
840
|
-
* Resolve the routing KIND of one query KEY FIELD against the returns-type's field
|
|
841
|
-
* schema. NO FALLBACK: an undeclared key field is a declaration bug — THROW.
|
|
842
|
-
*/
|
|
843
|
-
function keyFieldKind(
|
|
844
|
-
byType: AggregateFieldKinds,
|
|
845
|
-
queryId: string,
|
|
846
|
-
returns: string,
|
|
847
|
-
keyField: string,
|
|
848
|
-
): string {
|
|
849
|
-
const schema = byType[returns];
|
|
850
|
-
if (schema === undefined) {
|
|
851
|
-
throw new Error(
|
|
852
|
-
`build-package: query '${queryId}' returns '${returns}', which has no field ` +
|
|
853
|
-
`schema — the returns handle must be an aggregate emitted into the manifest.`,
|
|
854
|
-
);
|
|
855
|
-
}
|
|
856
|
-
// A dotted key indexes a sub-value of a top-level JSON leaf; kind off the leading field.
|
|
857
|
-
const lookup = keyField.includes(".") ? keyField.split(".")[0]! : keyField;
|
|
858
|
-
const descriptor = schema[lookup];
|
|
859
|
-
if (descriptor === undefined) {
|
|
860
|
-
throw new Error(
|
|
861
|
-
`build-package: query '${queryId}' is keyed on '${keyField}' (field '${lookup}'), ` +
|
|
862
|
-
`which '${returns}' does not declare — a query cannot index an undefined field.`,
|
|
863
|
-
);
|
|
864
|
-
}
|
|
865
|
-
return descriptor.kind;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
function describeQuery(byType: AggregateFieldKinds, q: QueryDecl): QueryDescriptor {
|
|
869
|
-
return {
|
|
870
|
-
id: q.id,
|
|
871
|
-
key: q.key.map((field) => ({
|
|
872
|
-
field,
|
|
873
|
-
kind: keyFieldKind(byType, q.id, q.returns, field),
|
|
874
|
-
})),
|
|
875
|
-
returns: q.returns,
|
|
876
|
-
};
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
function describeCount(byType: AggregateFieldKinds, c: CountDecl): CountDescriptor {
|
|
880
|
-
if (byType[c.of] === undefined) {
|
|
881
|
-
throw new Error(
|
|
882
|
-
`build-package: count '${c.id}' tallies '${c.of}', which has no field schema — ` +
|
|
883
|
-
`the of-type must be an aggregate emitted into the manifest.`,
|
|
884
|
-
);
|
|
885
|
-
}
|
|
886
|
-
// `where` rides to the projection (WireCount parses it) — OMITTED when absent, so
|
|
887
|
-
// predicate-free read manifests keep their exact pre-slice-4 bytes.
|
|
888
|
-
return { id: c.id, of: c.of, by: c.by ?? null, ...(c.where !== undefined ? { where: c.where } : {}) };
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
function describeSpatial(byType: AggregateFieldKinds, s: SpatialDecl): SpatialDescriptor {
|
|
892
|
-
const schema = byType[s.of];
|
|
893
|
-
if (schema === undefined) {
|
|
894
|
-
throw new Error(
|
|
895
|
-
`build-package: spatial '${s.id}' covers '${s.of}', which has no field schema — ` +
|
|
896
|
-
`the of-type must be an aggregate emitted into the manifest.`,
|
|
897
|
-
);
|
|
898
|
-
}
|
|
899
|
-
if (schema[s.on] === undefined) {
|
|
900
|
-
throw new Error(
|
|
901
|
-
`build-package: spatial '${s.id}' indexes geometry field '${s.on}' on '${s.of}', ` +
|
|
902
|
-
`which '${s.of}' does not declare — the geometry field must exist (no fallback).`,
|
|
903
|
-
);
|
|
904
|
-
}
|
|
905
|
-
return { id: s.id, of: s.of, on: s.on };
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
function describeDerived(byType: AggregateFieldKinds, d: DerivedDecl): DerivedDescriptor {
|
|
909
|
-
if (byType[d.of] === undefined) {
|
|
910
|
-
throw new Error(
|
|
911
|
-
`build-package: derived '${d.id}' derives from '${d.of}', which has no field ` +
|
|
912
|
-
`schema — the of-type must be an aggregate emitted into the manifest.`,
|
|
913
|
-
);
|
|
914
|
-
}
|
|
915
|
-
return { id: d.id, of: d.of, returns: describeProjectionReturn(d.returns) };
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
function describeCombined(byType: AggregateFieldKinds, c: CombinedDecl): CombinedDescriptor {
|
|
919
|
-
if (byType[c.of] === undefined) {
|
|
920
|
-
throw new Error(
|
|
921
|
-
`build-package: combined '${c.id}' lives on '${c.of}', which has no field ` +
|
|
922
|
-
`schema — the of-type must be an aggregate emitted into the manifest.`,
|
|
923
|
-
);
|
|
924
|
-
}
|
|
925
|
-
if (byType[c.reads] === undefined) {
|
|
926
|
-
throw new Error(
|
|
927
|
-
`build-package: combined '${c.id}' reads '${c.reads}', which has no field ` +
|
|
928
|
-
`schema — the queried type must be an aggregate emitted into the manifest.`,
|
|
929
|
-
);
|
|
930
|
-
}
|
|
931
|
-
return {
|
|
932
|
-
id: c.id,
|
|
933
|
-
of: c.of,
|
|
934
|
-
refField: c.refField,
|
|
935
|
-
reads: c.reads,
|
|
936
|
-
returns: describeProjectionReturn(c.returns),
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
/**
|
|
941
|
-
* Build the single combined READ manifest for the modules (`domain_manifests.json`).
|
|
942
|
-
* Hoisted VERBATIM-in-behaviour from `emit_manifests.ts` `buildManifests` — sorted
|
|
943
|
-
* keys/arrays for byte-stability, divergence detection across domains, NO-FALLBACK
|
|
944
|
-
* throws on undeclared of-types (the Rust parse would reject them anyway, loudly,
|
|
945
|
-
* and break ALL reads for the workspace — fail at build instead).
|
|
946
|
-
*/
|
|
947
|
-
export function buildReadManifest(modules: readonly DomainModule[]): ReadManifest {
|
|
948
|
-
const byType: AggregateFieldKinds = {};
|
|
949
|
-
for (const mod of modules) {
|
|
950
|
-
for (const agg of mod.aggregates as AggregateHandle[]) {
|
|
951
|
-
const schema = schemaOf(agg);
|
|
952
|
-
const existing = byType[agg.id];
|
|
953
|
-
if (existing !== undefined && stable(existing) !== stable(schema)) {
|
|
954
|
-
throw new Error(
|
|
955
|
-
`build-package: aggregate '${agg.id}' has DIVERGENT field schemas across ` +
|
|
956
|
-
`domains — the read projection cannot have two shapes for one wire type. ` +
|
|
957
|
-
`Reconcile the aggregate definition.`,
|
|
958
|
-
);
|
|
959
|
-
}
|
|
960
|
-
byType[agg.id] = schema;
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
const sorted: AggregateFieldKinds = {};
|
|
964
|
-
for (const k of Object.keys(byType).sort()) sorted[k] = byType[k]!;
|
|
965
|
-
|
|
966
|
-
const byId = new Map<string, QueryDescriptor>();
|
|
967
|
-
for (const mod of modules) {
|
|
968
|
-
for (const q of mod.queries ?? []) {
|
|
969
|
-
const descriptor = describeQuery(sorted, q);
|
|
970
|
-
const existing = byId.get(q.id);
|
|
971
|
-
if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(descriptor)) {
|
|
972
|
-
throw new Error(
|
|
973
|
-
`build-package: query id '${q.id}' is declared with DIVERGENT routing across ` +
|
|
974
|
-
`domains — one id cannot map to two routes. Reconcile the query declaration.`,
|
|
975
|
-
);
|
|
976
|
-
}
|
|
977
|
-
byId.set(q.id, descriptor);
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
const queries = [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
981
|
-
|
|
982
|
-
const countsById = new Map<string, CountDescriptor>();
|
|
983
|
-
for (const mod of modules) {
|
|
984
|
-
for (const raw of mod.counts ?? []) {
|
|
985
|
-
const c = finishCount(raw);
|
|
986
|
-
const descriptor = describeCount(sorted, c);
|
|
987
|
-
const existing = countsById.get(c.id);
|
|
988
|
-
if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(descriptor)) {
|
|
989
|
-
throw new Error(
|
|
990
|
-
`build-package: count id '${c.id}' is declared with DIVERGENT maintenance across ` +
|
|
991
|
-
`domains — one id cannot map to two counters. Reconcile the count declaration.`,
|
|
992
|
-
);
|
|
993
|
-
}
|
|
994
|
-
countsById.set(c.id, descriptor);
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
const counts = [...countsById.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
998
|
-
|
|
999
|
-
// Maintained SUMS (sharding slice 3 routes the derived `nomosEstateSummary` through
|
|
1000
|
-
// here; tenant sums ride the same lane). The projection's `WireSum` names the summed
|
|
1001
|
-
// field `field`. Omitted entirely when none — sum-free read manifests stay byte-identical.
|
|
1002
|
-
const sumsById = new Map<string, SumDescriptor>();
|
|
1003
|
-
for (const mod of modules) {
|
|
1004
|
-
for (const raw of mod.sums ?? []) {
|
|
1005
|
-
const s = finishSum(raw);
|
|
1006
|
-
if (sorted[s.of] === undefined) {
|
|
1007
|
-
throw new Error(
|
|
1008
|
-
`build-package: sum '${s.id}' tallies '${s.of}', which has no field schema — ` +
|
|
1009
|
-
`the of-type must be an aggregate emitted into the manifest.`,
|
|
1010
|
-
);
|
|
1011
|
-
}
|
|
1012
|
-
const descriptor: SumDescriptor = { id: s.id, of: s.of, field: s.sumField, by: s.by ?? null, ...(s.where !== undefined ? { where: s.where } : {}) };
|
|
1013
|
-
const existing = sumsById.get(s.id);
|
|
1014
|
-
if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(descriptor)) {
|
|
1015
|
-
throw new Error(
|
|
1016
|
-
`build-package: sum id '${s.id}' is declared with DIVERGENT maintenance across ` +
|
|
1017
|
-
`domains — one id cannot map to two totals. Reconcile the sum declaration.`,
|
|
1018
|
-
);
|
|
1019
|
-
}
|
|
1020
|
-
sumsById.set(s.id, descriptor);
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
const sums = [...sumsById.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
1024
|
-
|
|
1025
|
-
// Maintained MIN/MAX reads (#47 — the extremum RPC's manifest half): the SAME lane
|
|
1026
|
-
// as sums, two arrays (the kind is implicit in which array a descriptor rides —
|
|
1027
|
-
// the Rust `WireExtremum` has parsed them since the read-engine slice). Omitted
|
|
1028
|
-
// entirely when none — extremum-free read manifests stay byte-identical.
|
|
1029
|
-
const lowerExtrema = (key: "mins" | "maxes"): ExtremumDescriptor[] => {
|
|
1030
|
-
const byId = new Map<string, ExtremumDescriptor>();
|
|
1031
|
-
for (const mod of modules) {
|
|
1032
|
-
for (const raw of mod[key] ?? []) {
|
|
1033
|
-
const e = finishExtremum(raw);
|
|
1034
|
-
if (sorted[e.of] === undefined) {
|
|
1035
|
-
throw new Error(
|
|
1036
|
-
`build-package: ${key === "mins" ? "min" : "max"} '${e.id}' reads '${e.of}', which has no field schema — ` +
|
|
1037
|
-
`the of-type must be an aggregate emitted into the manifest.`,
|
|
1038
|
-
);
|
|
1039
|
-
}
|
|
1040
|
-
const descriptor: ExtremumDescriptor = { id: e.id, of: e.of, field: e.valueField, by: e.by ?? null, ...(e.where !== undefined ? { where: e.where } : {}) };
|
|
1041
|
-
const existing = byId.get(e.id);
|
|
1042
|
-
if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(descriptor)) {
|
|
1043
|
-
throw new Error(
|
|
1044
|
-
`build-package: ${key === "mins" ? "min" : "max"} id '${e.id}' is declared with DIVERGENT ` +
|
|
1045
|
-
`maintenance across domains — one id cannot map to two extrema. Reconcile the declaration.`,
|
|
1046
|
-
);
|
|
1047
|
-
}
|
|
1048
|
-
byId.set(e.id, descriptor);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
1052
|
-
};
|
|
1053
|
-
const mins = lowerExtrema("mins");
|
|
1054
|
-
const maxes = lowerExtrema("maxes");
|
|
1055
|
-
|
|
1056
|
-
const spatialsById = new Map<string, SpatialDescriptor>();
|
|
1057
|
-
for (const mod of modules) {
|
|
1058
|
-
for (const s of mod.spatials ?? []) {
|
|
1059
|
-
const descriptor = describeSpatial(sorted, s);
|
|
1060
|
-
const existing = spatialsById.get(s.id);
|
|
1061
|
-
if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(descriptor)) {
|
|
1062
|
-
throw new Error(
|
|
1063
|
-
`build-package: spatial id '${s.id}' is declared with DIVERGENT maintenance across ` +
|
|
1064
|
-
`domains — one id cannot map to two indexes. Reconcile the spatial declaration.`,
|
|
1065
|
-
);
|
|
1066
|
-
}
|
|
1067
|
-
spatialsById.set(s.id, descriptor);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
const spatials = [...spatialsById.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
1071
|
-
|
|
1072
|
-
const derivedsByNamespaceAndId = new Map<string, DerivedDescriptor>();
|
|
1073
|
-
for (const mod of modules) {
|
|
1074
|
-
for (const d of mod.deriveds ?? []) {
|
|
1075
|
-
const descriptor = describeDerived(sorted, d);
|
|
1076
|
-
const key = `${descriptor.of}\u0000${descriptor.id}`;
|
|
1077
|
-
const existing = derivedsByNamespaceAndId.get(key);
|
|
1078
|
-
if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(descriptor)) {
|
|
1079
|
-
throw new Error(
|
|
1080
|
-
`build-package: derived field '${descriptor.of}.${descriptor.id}' is declared ` +
|
|
1081
|
-
`with DIVERGENT projection across domains. Reconcile the declaration.`,
|
|
1082
|
-
);
|
|
1083
|
-
}
|
|
1084
|
-
derivedsByNamespaceAndId.set(key, descriptor);
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
const deriveds = [...derivedsByNamespaceAndId.values()].sort(
|
|
1088
|
-
(a, b) => a.of.localeCompare(b.of) || a.id.localeCompare(b.id),
|
|
1089
|
-
);
|
|
1090
|
-
|
|
1091
|
-
const combinedsByNamespaceAndId = new Map<string, CombinedDescriptor>();
|
|
1092
|
-
for (const mod of modules) {
|
|
1093
|
-
for (const c of mod.combineds ?? []) {
|
|
1094
|
-
const descriptor = describeCombined(sorted, c);
|
|
1095
|
-
const key = `${descriptor.of}\u0000${descriptor.id}`;
|
|
1096
|
-
const existing = combinedsByNamespaceAndId.get(key);
|
|
1097
|
-
if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(descriptor)) {
|
|
1098
|
-
throw new Error(
|
|
1099
|
-
`build-package: combined field '${descriptor.of}.${descriptor.id}' is declared ` +
|
|
1100
|
-
`with DIVERGENT projection across domains. Reconcile the declaration.`,
|
|
1101
|
-
);
|
|
1102
|
-
}
|
|
1103
|
-
combinedsByNamespaceAndId.set(key, descriptor);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
const combineds = [...combinedsByNamespaceAndId.values()].sort(
|
|
1107
|
-
(a, b) => a.of.localeCompare(b.of) || a.id.localeCompare(b.id),
|
|
1108
|
-
);
|
|
1109
|
-
|
|
1110
|
-
return {
|
|
1111
|
-
aggregateFieldKinds: sorted,
|
|
1112
|
-
queries,
|
|
1113
|
-
counts,
|
|
1114
|
-
spatials,
|
|
1115
|
-
deriveds,
|
|
1116
|
-
combineds,
|
|
1117
|
-
...(sums.length > 0 ? { sums } : {}),
|
|
1118
|
-
...(mins.length > 0 ? { mins } : {}),
|
|
1119
|
-
...(maxes.length > 0 ? { maxes } : {}),
|
|
1120
|
-
};
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
// ─────────────────────────────────────────────────────────────────────────────────
|
|
1124
|
-
// buildIdentity / writeIdentity — the IDENTITY artifacts (hoisted from emit_identity.ts)
|
|
1125
|
-
// ─────────────────────────────────────────────────────────────────────────────────
|
|
1126
|
-
|
|
1127
|
-
/** One domain that could not produce a canonical identity, with the reason recorded. */
|
|
1128
|
-
export interface ExcludedDomain {
|
|
1129
|
-
readonly domain: string;
|
|
1130
|
-
readonly reason: string;
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
/** The result of emitting the identity manifest over a set of domain modules. */
|
|
1134
|
-
export interface IdentityEmit {
|
|
1135
|
-
/** `{domain: canonicalManifestString}` — the gate's NOMOS_DOMAIN_MANIFEST shape. */
|
|
1136
|
-
readonly manifests: Record<string, string>;
|
|
1137
|
-
/** `{domain: sha256(canonicalManifest)}` — the certified hash registry. */
|
|
1138
|
-
readonly hashes: Record<string, string>;
|
|
1139
|
-
/** Domains OMITTED because they have no canonical identity (palette gap), recorded. */
|
|
1140
|
-
readonly excluded: ExcludedDomain[];
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
/**
|
|
1144
|
-
* Build the identity manifest + hashes. A domain whose canonical manifest THROWS (a
|
|
1145
|
-
* palette-only driver the kernel cannot lower) is RECORDED in `excluded` and omitted —
|
|
1146
|
-
* never fabricated. The cross-language anchor (`sha256(bytes) === domainHash`) is
|
|
1147
|
-
* asserted for every EMITTED domain; an anchor failure is a hard error.
|
|
1148
|
-
*/
|
|
1149
|
-
export function buildIdentity(modules: readonly DomainModule[]): IdentityEmit {
|
|
1150
|
-
const manifests: Record<string, string> = {};
|
|
1151
|
-
const hashes: Record<string, string> = {};
|
|
1152
|
-
const excluded: ExcludedDomain[] = [];
|
|
1153
|
-
|
|
1154
|
-
for (const mod of [...modules].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))) {
|
|
1155
|
-
let bytes: Buffer;
|
|
1156
|
-
let hash: string;
|
|
1157
|
-
try {
|
|
1158
|
-
bytes = emitManifestBytes(mod);
|
|
1159
|
-
hash = domainHash(mod);
|
|
1160
|
-
} catch (e) {
|
|
1161
|
-
// No canonical identity under the current kernel palette — record, do not fabricate.
|
|
1162
|
-
excluded.push({ domain: mod.name, reason: (e as Error).message });
|
|
1163
|
-
continue;
|
|
1164
|
-
}
|
|
1165
|
-
const fileHash = createHash("sha256").update(bytes).digest("hex");
|
|
1166
|
-
if (fileHash !== hash) {
|
|
1167
|
-
throw new Error(
|
|
1168
|
-
`build-package: ${mod.name} manifest-byte sha256 ${fileHash} != domainHash ${hash} — ` +
|
|
1169
|
-
`the identity anchor is broken (the gate would reject these bytes).`,
|
|
1170
|
-
);
|
|
1171
|
-
}
|
|
1172
|
-
manifests[mod.name] = bytes.toString("utf8");
|
|
1173
|
-
hashes[mod.name] = hash;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
if (Object.keys(manifests).length === 0) {
|
|
1177
|
-
throw new Error(
|
|
1178
|
-
"build-package: produced NO domain identities — refusing to emit an empty registry.",
|
|
1179
|
-
);
|
|
1180
|
-
}
|
|
1181
|
-
return { manifests, hashes, excluded };
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
// writeIdentity (the domain_identity_*.json sidecars) — REMOVED. The admission identity is carried in the
|
|
1185
|
-
// package USD IR + the committed nomos.stable-ids.json lockfile; the deploy/runtime contract never read the
|
|
1186
|
-
// sidecars (they were local diagnostics that only confused /build consumers). `buildIdentity` stays — its
|
|
1187
|
-
// per-domain hash is surfaced in the compile summary.
|
|
1188
|
-
|
|
1189
|
-
// ─────────────────────────────────────────────────────────────────────────────────
|
|
1190
|
-
// The canonical USD-IR emit
|
|
1191
|
-
// ─────────────────────────────────────────────────────────────────────────────────
|
|
1192
|
-
|
|
1193
|
-
/**
|
|
1194
|
-
* The USD-IR JSON for a package's modules: `emitUsd` over `/Nomos/<domain>` layers,
|
|
1195
|
-
* `JSON.stringify`'d EXACTLY like every `emit_*_usd_json.ts` (single line, insertion
|
|
1196
|
-
* order — `emitUsd`'s sorted layers/prims are what make it reproducible; do NOT
|
|
1197
|
-
* canonicalize or pretty-print, the package hash rides on these bytes).
|
|
1198
|
-
*/
|
|
1199
|
-
export function emitUsdJsonForModules(modules: readonly DomainModule[]): string {
|
|
1200
|
-
const doc = emitUsd(
|
|
1201
|
-
modules.map((mod) => ({ path: `/Nomos/${mod.domain ?? mod.name}`, module: mod })),
|
|
1202
|
-
);
|
|
1203
|
-
return JSON.stringify(doc);
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
830
|
/** sha256 hex over a UTF-8 string — the deploy identity (`policy:{hash}`). */
|
|
1207
831
|
export function sha256HexUtf8(text: string): string {
|
|
1208
832
|
return createHash("sha256").update(Buffer.from(text, "utf8")).digest("hex");
|