@githolon/dsl 0.102.5 → 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 +73 -33
  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 +1858 -590
  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 +29 -69
  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 +58 -29
  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 +140 -6
  110. package/src/stable_ids.ts +1 -1
  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
@@ -63,13 +63,7 @@ import { synthesizeBirthQuotaInvariants } from "./birth_meter.js";
63
63
  import { withBirthMeterExports } from "./birth_meter_ensure.js";
64
64
  import { expandRoleExports } from "./role.js";
65
65
  import { withAttestedReadExports } from "./attested.js";
66
- import { generateOpenApiFragment, mergeOpenApiFragments, type OpenApiDoc } from "./codegen_openapi.js";
67
- import type { QueryDecl } from "./query.js";
68
- import type { AnyCount } from "./count.js";
69
- import type { AnySum } from "./sum.js";
70
- import type { AnyExtremum } from "./extremum.js";
71
- import type { SpatialDecl } from "./spatial.js";
72
- import type { DomainModule } from "./codegen_dart.js";
66
+ import type { OpenApiDoc } from "./codegen_openapi.js";
73
67
  import type { Directive } from "./directive.js";
74
68
  import type { AggregateHandle, AggregateInvariantFn, AggregateInvariantVerdict } from "./aggregate.js";
75
69
  import type { Ports } from "./ctx.js";
@@ -84,6 +78,7 @@ import { expandBusinessBirthExports, isBusinessBirthDirective } from "./business
84
78
  import { withOwnedCustodyExports } from "./framework/owned_custody.js";
85
79
  import { withBusinessCorrectionExports } from "./framework/business_corrections.js";
86
80
  import { withClearDirectives } from "./clear_directive.js";
81
+ import { withWorkspaceSharingInvitationExports } from "./workspace_sharing.js";
87
82
  import {
88
83
  applicationLanguageOf,
89
84
  assertApplicationLanguageCoversAuthoredDirectives,
@@ -123,6 +118,19 @@ export interface EngineRouting {
123
118
  readonly relationOf?: Record<string, readonly string[]>;
124
119
  /** workspace-invariant id → declaring domain keys (#266 — the gate's reads/assert dispatches). */
125
120
  readonly workspaceInvariantOf?: Record<string, readonly string[]>;
121
+ /**
122
+ * Dispatch-domain label → canonical USDA domain identity (`d_<sid>`). Tuple object types use this
123
+ * durable namespace so a domain rename cannot rewrite Zanzibar identity.
124
+ */
125
+ readonly relationNamespaceOf?: Record<string, string>;
126
+ /**
127
+ * Domain key → every non-global aggregate type in the complete compiled domain. A selectively
128
+ * sliced plan may retain a secondary aggregate only inside a closure, so reconstructing this set
129
+ * from the slice's exported declarations can silently lose the type's namespace.
130
+ */
131
+ readonly namespacedAggregatesOf?: Record<string, readonly string[]>;
132
+ /** Domain key → every local relation shape in the complete compiled domain, for the same reason. */
133
+ readonly qualifiableRelationsOf?: Record<string, readonly string[]>;
126
134
  }
127
135
 
128
136
  /** The one declarative input: dispatch key → ORDERED module list (later wins). */
@@ -133,6 +141,19 @@ export interface EngineEntryConfig {
133
141
  * Values may be thunks (lazy boot — see the module header).
134
142
  */
135
143
  readonly domains: Record<string, readonly DomainModuleSource[]>;
144
+ /**
145
+ * Compiler-authenticated exact routes may share one domain-agnostic framework plan. When the
146
+ * requested dispatch domain is absent from `domains`, prepare and execute this sole registry while
147
+ * retaining the requested domain for relation namespacing. Never authored by tenant code.
148
+ */
149
+ readonly routedDomainFallback?: string;
150
+ /**
151
+ * Domain-local dependency initializers emitted by the compiler. They run one per
152
+ * `prepareDomain()` call before the domain's composable sources and their exports are discarded.
153
+ * This turns a tenant module's cross-domain ES import closure into bounded maintenance quanta
154
+ * without registering the imported domain's declarations in the caller's slice.
155
+ */
156
+ readonly preloads?: Record<string, readonly (() => unknown)[]>;
136
157
  /** Optional report registry: `reportId` → a factory `(actor) => Report`. */
137
158
  readonly reports?: Record<string, (actor: string) => EngineReport>;
138
159
  /** REQUIRED when any domain module is a thunk; ignored otherwise. */
@@ -147,16 +168,11 @@ export interface EngineEntryConfig {
147
168
  */
148
169
  readonly seeds?: readonly unknown[];
149
170
  /**
150
- * THE SELF-DESCRIBE INPUT (G1 — openapi residue removal): the per-domain read/maintenance
151
- * decls the OpenAPI fragment needs that are NOT discoverable by shape from the live module
152
- * exports alone (queries/counts/sums/mins/maxes/spatials/workspaceInvariants — the compiler
153
- * already auto-discovered + named-resolved them when it composed each DomainModule). The
154
- * directives + aggregates ARE discovered from the modules; these ride alongside so the
155
- * lump's `globalThis.describe` reproduces the EXACT `<name>.openapi.json` the compiler wrote
156
- * (byte-identity, no Rust re-implementation, no drift). Keyed by domain dispatch key, in the
157
- * same declaration order as `domains`. Omitted ⇒ describe still emits offers from directives.
171
+ * Compiler-derived self-description. It is a presentation projection held inside the separately
172
+ * routed `describe` plan, never executable law IR. Ordinary offer/read plans omit it completely,
173
+ * so OpenAPI/JSON-Schema generation cannot sit on their parse or readiness flame path.
158
174
  */
159
- readonly openapiDecls?: Record<string, OpenApiDomainDecls>;
175
+ readonly openapiDocument?: OpenApiDoc;
160
176
  /**
161
177
  * The package's OpenAPI document title (the compiler's `"<name> (nomos package)"`). Baked into
162
178
  * the lump so `describe` reproduces the compiler's `<name>.openapi.json` byte-for-byte without
@@ -165,17 +181,6 @@ export interface EngineEntryConfig {
165
181
  readonly packageTitle?: string;
166
182
  }
167
183
 
168
- /** The openapi-relevant decls one domain contributes beyond its directives/aggregates. */
169
- export interface OpenApiDomainDecls {
170
- readonly queries?: readonly QueryDecl[];
171
- readonly counts?: readonly AnyCount[];
172
- readonly sums?: readonly AnySum[];
173
- readonly mins?: readonly AnyExtremum[];
174
- readonly maxes?: readonly AnyExtremum[];
175
- readonly spatials?: readonly SpatialDecl[];
176
- readonly workspaceInvariants?: readonly WorkspaceInvariantDecl[];
177
- }
178
-
179
184
  interface RegistryEntry {
180
185
  directive: Directive<unknown>;
181
186
  agg: AggregateHandle;
@@ -264,6 +269,7 @@ function mergeModules(mods: readonly DomainModuleExports[], domain: string): Dom
264
269
  [...directivesOf(merged).values()],
265
270
  domain,
266
271
  );
272
+ merged = withWorkspaceSharingInvitationExports(merged, applicationLanguage.workspaceSharing);
267
273
  }
268
274
  merged = expandBusinessBirthExports(merged, domain);
269
275
  merged = withOwnedCustodyExports(merged);
@@ -327,6 +333,20 @@ function buildDomainSlice(mod: DomainModuleExports, domain: string): DomainSlice
327
333
  ? discoveredDirectives
328
334
  : executableDirectivesForLanguage(applicationLanguage, discoveredDirectives, domain);
329
335
  const dirs = new Map(executableDirectives.map((directive) => [directive.id, directive]));
336
+ // A directive already owns the typed aggregate handle it targets. Retain that
337
+ // link unconditionally in the EXECUTABLE slice instead of requiring a second
338
+ // aggregate re-export. For a cross-workspace birth this does not copy child
339
+ // schema into the parent's canonical law: it only gives this selected runtime
340
+ // plan enough structure to register and validate its declared offer. Canonical
341
+ // package composition keeps its separate child-law dependency rule.
342
+ for (const directive of discoveredDirectives) {
343
+ if (directive.aggregate !== undefined) {
344
+ aggs.set(directive.aggregate.id, directive.aggregate);
345
+ }
346
+ for (const output of directive.secondaryOutputs ?? []) {
347
+ if (output.aggregate !== undefined) aggs.set(output.aggregate.id, output.aggregate);
348
+ }
349
+ }
330
350
  // RELATIONS AUTO-ENSURE (mirror of composeDomainModule): a relate-family directive `.ensures(RelationTuple)`
331
351
  // needs the framework aggregate registered for DISPATCH too — without it the directive is silently dropped
332
352
  // below. Inject iff a directive targets it and the dev didn't already export the handle.
@@ -496,12 +516,16 @@ export function collectEngineRouting(
496
516
  const aggregateInvariantOf: Record<string, string[]> = {};
497
517
  const relationOf: Record<string, string[]> = {};
498
518
  const workspaceInvariantOf: Record<string, string[]> = {};
519
+ const namespacedAggregatesOf: Record<string, string[]> = {};
520
+ const qualifiableRelationsOf: Record<string, string[]> = {};
499
521
  const add = (map: Record<string, string[]>, key: string, domainName: string) => {
500
522
  const list = map[key] ?? (map[key] = []);
501
523
  if (!list.includes(domainName)) list.push(domainName);
502
524
  };
503
525
  for (const [domainName, mods] of Object.entries(domains)) {
504
526
  const slice = buildDomainSlice(mergeModules(mods, domainName), domainName);
527
+ namespacedAggregatesOf[domainName] = [...slice.qualifiableAggregates].sort();
528
+ qualifiableRelationsOf[domainName] = [...slice.qualifiableRelations].sort();
505
529
  // Type-keyed dispatches arrive with the ROW's type — QUALIFIED for a namespaced aggregate (the
506
530
  // runtime `__type` stamp), bare for a global one / a pre-namespacing row. Route BOTH keys so
507
531
  // either era resolves to the declaring domain (the slice lookup strips the qualifier back off).
@@ -513,7 +537,15 @@ export function collectEngineRouting(
513
537
  for (const relationId of slice.invariants.keys()) add(relationOf, relationId, domainName);
514
538
  for (const wsInvariantId of slice.wsInvariants.keys()) add(workspaceInvariantOf, wsInvariantId, domainName);
515
539
  }
516
- return { derivedOf, combinedOf, aggregateInvariantOf, relationOf, workspaceInvariantOf };
540
+ return {
541
+ derivedOf,
542
+ combinedOf,
543
+ aggregateInvariantOf,
544
+ relationOf,
545
+ workspaceInvariantOf,
546
+ namespacedAggregatesOf,
547
+ qualifiableRelationsOf,
548
+ };
517
549
  }
518
550
 
519
551
  /**
@@ -551,6 +583,10 @@ function portsFromHost(principalRaw = ""): Ports {
551
583
  export interface RegisteredEngine {
552
584
  plan(job: unknown): WireEvent[] | { events: WireEvent[]; strikes?: string[]; endorse?: { sink: string; facet: string; reason: string | null }[] };
553
585
  planReport(job: unknown): string;
586
+ /** Initialize at most one lazy dependency/module, or compose an initialized domain slice. */
587
+ prepareDomain(domain: string): { complete: boolean; initialized: number; total: number };
588
+ /** Compiler/tooling probe: force one domain and prove an exact routed offer is registered. */
589
+ hasDirective(domain: string, directiveId: string): boolean;
554
590
  /** The G1 self-describe verb: the lump's own merged OpenAPI, derived from its modules. */
555
591
  describe(opts?: { lawHash?: string; title?: string }): OpenApiDoc;
556
592
  }
@@ -565,6 +601,8 @@ export interface RegisteredEngine {
565
601
  */
566
602
  export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
567
603
  const domainNames = Object.keys(config.domains);
604
+ const sourceDomainFor = (requested: string): string =>
605
+ config.domains[requested] !== undefined ? requested : (config.routedDomainFallback ?? requested);
568
606
  const lazy = Object.values(config.domains).some((mods) =>
569
607
  mods.some((m) => typeof m === "function"),
570
608
  );
@@ -581,15 +619,61 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
581
619
 
582
620
  // ── domain key → its slice (forced once per sandbox; eager boot forces ALL now) ──
583
621
  const sliceCache = new Map<string, DomainSlice>();
622
+ const initializedPreloads = new Map<string, number>();
623
+ const initializedSources = new Map<string, DomainModuleExports[]>();
624
+ function prepareDomain(domainName: string): { complete: boolean; initialized: number; total: number } {
625
+ domainName = sourceDomainFor(domainName);
626
+ const sources = config.domains[domainName];
627
+ if (sources === undefined) return { complete: true, initialized: 0, total: 0 };
628
+ const preloads = config.preloads?.[domainName] ?? [];
629
+ const total = preloads.length + sources.length;
630
+ if (sliceCache.has(domainName)) {
631
+ return { complete: true, initialized: total, total };
632
+ }
633
+ const preloaded = initializedPreloads.get(domainName) ?? 0;
634
+ if (preloaded < preloads.length) {
635
+ preloads[preloaded]!();
636
+ initializedPreloads.set(domainName, preloaded + 1);
637
+ return { complete: false, initialized: preloaded + 1, total };
638
+ }
639
+ let initialized = initializedSources.get(domainName);
640
+ if (initialized === undefined) {
641
+ initialized = [];
642
+ initializedSources.set(domainName, initialized);
643
+ }
644
+ if (initialized.length < sources.length) {
645
+ const source = sources[initialized.length]!;
646
+ initialized.push(typeof source === "function" ? source() : source);
647
+ return { complete: false, initialized: preloads.length + initialized.length, total };
648
+ }
649
+ const slice = buildDomainSlice(mergeModules(initialized, domainName), domainName);
650
+ // The compiler computes these classifications from the complete domain before plan slicing.
651
+ // Prefer that authenticated inventory over the reduced export bag: a closure-retained secondary
652
+ // aggregate/relation is executable law even when tree-shaking removes its top-level export.
653
+ const compiledAggregates = routing.namespacedAggregatesOf?.[domainName];
654
+ if (compiledAggregates !== undefined) {
655
+ slice.qualifiableAggregates = new Set(compiledAggregates);
656
+ }
657
+ const compiledRelations = routing.qualifiableRelationsOf?.[domainName];
658
+ if (compiledRelations !== undefined) {
659
+ slice.qualifiableRelations = new Set(compiledRelations);
660
+ }
661
+ sliceCache.set(domainName, slice);
662
+ initializedSources.delete(domainName);
663
+ initializedPreloads.delete(domainName);
664
+ return { complete: true, initialized: total, total };
665
+ }
584
666
  function sliceOf(domainName: string): DomainSlice | undefined {
667
+ domainName = sourceDomainFor(domainName);
585
668
  const cached = sliceCache.get(domainName);
586
669
  if (cached !== undefined) return cached;
587
670
  const sources = config.domains[domainName];
588
671
  if (sources === undefined) return undefined;
589
- const forced = sources.map((m) => (typeof m === "function" ? m() : m));
590
- const slice = buildDomainSlice(mergeModules(forced, domainName), domainName);
591
- sliceCache.set(domainName, slice);
592
- return slice;
672
+ while (!prepareDomain(domainName).complete) { /* synchronous compatibility lane */ }
673
+ return sliceCache.get(domainName);
674
+ }
675
+ function hasDirective(domainName: string, directiveId: string): boolean {
676
+ return sliceOf(domainName)?.registry.has(directiveId) === true;
593
677
  }
594
678
  if (!lazy) for (const domainName of domainNames) sliceOf(domainName);
595
679
 
@@ -698,23 +782,9 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
698
782
  return found;
699
783
  }
700
784
 
701
- // ── the three WORKSPACE-INVARIANT dispatches (#266 — wire shapes are the gate oracle's
702
- // contract, EXACT: see admission-peer EngineWorkspaceInvariant + plan_workspace_invariant*) ──
703
-
704
- /** `{workspaceInvariantList:{}}` → the DECLARED set `[{id,on},…]` sorted by id (discovery). */
705
- function planWorkspaceInvariantList(): string {
706
- const declared = new Map<string, { id: string; on: string }>();
707
- const domains =
708
- routing.workspaceInvariantOf !== undefined
709
- ? [...new Set(Object.values(routing.workspaceInvariantOf).flat())]
710
- : domainNames;
711
- for (const domainName of domains) {
712
- const slice = sliceOf(domainName);
713
- if (slice === undefined) continue;
714
- for (const w of slice.wsInvariants.values()) declared.set(w.id, { id: w.id, on: w.on });
715
- }
716
- return JSON.stringify([...declared.values()].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)));
717
- }
785
+ // Workspace-invariant read selection uses typed USDA interpreted by Rust whenever the
786
+ // compiler can describe it. This dispatch remains the deterministic JavaScript fallback
787
+ // for tenant selectors that deliberately exceed that bounded read algebra.
718
788
 
719
789
  /** `{workspaceInvariantReads:{id}, payload}` → the bounded ref-set `[{name,aggregate,id},…]`. */
720
790
  function planWorkspaceInvariantReads(job: {
@@ -732,11 +802,7 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
732
802
  throw new Error(`engine workspaceInvariantReads: no workspace invariant registered for "${id}"`);
733
803
  }
734
804
  const intent = (job.intent?.payload ?? {}) as Record<string, unknown>;
735
- // LAW-GATED AUTHZ: the captured author identity (a top-level sibling of `intent`, set by the wasm
736
- // author leg + replayed verbatim) — the governance invariant reads it to gate by the AUTHOR.
737
805
  const principal = (job.capturedPorts?.principal ?? "") as string;
738
- // VA-core (Phase 3): the kernel-DERIVED author-verified flag (`captured_ports.principal_verified`),
739
- // threaded the same way as `principal`, so an invariant may gate on a VERIFIED author (e.g. enrollSignerSelf).
740
806
  const principalVerified = job.capturedPorts?.principal_verified === true;
741
807
  return JSON.stringify(decl.reads({ intent, principal, principalVerified }));
742
808
  }
@@ -848,20 +914,16 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
848
914
  queryRows?: unknown[];
849
915
  priorState?: Record<string, unknown>;
850
916
  }): string {
851
- // Branch order mirrors emit_engine.ts: workspace-invariant trio → aggregateInvariant →
917
+ // Branch order mirrors emit_engine.ts: workspace-invariant bodies → aggregateInvariant →
852
918
  // invariant → derive → combine → report (the gate's most recent dispatch key wins).
853
919
  const wsJob = job as {
854
920
  intent?: {
855
- workspaceInvariantList?: unknown;
856
921
  workspaceInvariantReads?: { id?: string };
857
922
  workspaceInvariant?: { id?: string };
858
923
  payload?: unknown;
859
924
  };
860
925
  priorState?: Record<string, unknown>;
861
926
  };
862
- if (wsJob.intent?.workspaceInvariantList !== undefined) {
863
- return planWorkspaceInvariantList();
864
- }
865
927
  if (wsJob.intent?.workspaceInvariantReads !== undefined) {
866
928
  return planWorkspaceInvariantReads(wsJob);
867
929
  }
@@ -934,7 +996,12 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
934
996
  // token qualifies explicitly. (`module.domain` === `intent.domain`.) Synchronous plan execution ⇒
935
997
  // cleared in the `finally`.
936
998
  const slice = sliceOf(domain)!;
937
- __setDispatchContext({ domain, qualifiable: slice.qualifiableRelations, qualifiableAggregates: slice.qualifiableAggregates });
999
+ __setDispatchContext({
1000
+ domain,
1001
+ relationNamespace: routing.relationNamespaceOf?.[domain] ?? domain,
1002
+ qualifiable: slice.qualifiableRelations,
1003
+ qualifiableAggregates: slice.qualifiableAggregates,
1004
+ });
938
1005
  let wire: ReturnType<typeof executeDirectiveToIntent>;
939
1006
  try {
940
1007
  wire = executeDirectiveToIntent(entry.directive, entry.agg, intent.payload as never, ctx);
@@ -952,6 +1019,28 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
952
1019
  // `captured_ports.births` and folds it after admit; decode_plan_output ignores it for the byte-compare),
953
1020
  // so a birth-free intent still emits the bare event array, byte-identical (the era rule).
954
1021
  const births = wire.births;
1022
+ // CURRENT KERNELS expose a Rust-owned plan sink inside the sealed engine. Hand the completed
1023
+ // typed wire values to it while they are still native QuickJS objects, so the kernel can build
1024
+ // and meter `Event`s directly instead of walking a second returned object graph. This is an
1025
+ // execution ABI, not authority: the same values still pass the ordinary kernel gates, and old
1026
+ // kernels/packages retain the returned-value lane below for era replay.
1027
+ const submitNativePlan = (globalThis as unknown as {
1028
+ nomos?: {
1029
+ __submitPlan?: (
1030
+ events: WireEvent[],
1031
+ strikes: string[],
1032
+ endorse: { sink: string; facet: string; reason: string | null }[],
1033
+ births: { workspace: string; genesisChain: unknown }[],
1034
+ ) => void;
1035
+ };
1036
+ }).nomos?.__submitPlan;
1037
+ if (typeof submitNativePlan === "function") {
1038
+ submitNativePlan(wire.events, wire.strikes, endorse ?? [], births ?? []);
1039
+ // The Rust sink now owns the result. Return the smallest lawful sentinel; the kernel ignores
1040
+ // it when a native submission is present and reconstructs compatibility bytes from its typed
1041
+ // events. This avoids retaining and re-traversing the large event graph at the ABI boundary.
1042
+ return [];
1043
+ }
955
1044
  if (!wire.strikes.length && !(endorse && endorse.length) && !(births && births.length)) return wire.events;
956
1045
  return {
957
1046
  events: wire.events,
@@ -972,58 +1061,21 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
972
1061
  * package title), matching the compiler's `domainHash`/`"<name> (nomos package)"`.
973
1062
  */
974
1063
  function describe(opts?: { lawHash?: string; title?: string }): OpenApiDoc {
975
- const lawHash = opts?.lawHash;
976
- const fragments: OpenApiDoc[] = [];
977
- for (const domainName of domainNames) {
978
- const sources = config.domains[domainName];
979
- if (sources === undefined) continue;
980
- const merged = mergeModules(sources.map((m) => (typeof m === "function" ? m() : m)), domainName);
981
- // Aggregates dedup by wire id + RelationTuple auto-ensure (the compose contract) — openapi
982
- // reads aggregates only for the hasMany inverse indexes, so this matches composeDomainModule.
983
- const byId = aggregatesOf(merged);
984
- const discoveredDirectives = [...directivesOf(merged).values()];
985
- const applicationLanguage = applicationLanguageOf([merged], domainName);
986
- const directives = applicationLanguage === undefined
987
- ? discoveredDirectives
988
- : executableDirectivesForLanguage(applicationLanguage, discoveredDirectives, domainName);
989
- const applicationIntents = applicationLanguage === undefined
990
- ? undefined
991
- : [
992
- ...applicationLanguage.intents,
993
- ...directives.filter((directive) =>
994
- isBusinessBirthDirective(directive) &&
995
- !applicationLanguage.intents.some((intent) => intent.id === directive.id)),
996
- ];
997
- if (!byId.has(RelationTuple.id) && directives.some((d) => d.aggregateId === RelationTuple.id)) {
998
- byId.set(RelationTuple.id, RelationTuple);
999
- }
1000
- const decls = config.openapiDecls?.[domainName] ?? {};
1001
- const module = {
1002
- name: domainName,
1003
- domain: domainName,
1004
- aggregates: [...byId.values()],
1005
- directives,
1006
- ...(applicationIntents === undefined ? {} : { applicationIntents }),
1007
- ...(decls.queries && decls.queries.length > 0 ? { queries: [...decls.queries] } : {}),
1008
- ...(decls.counts && decls.counts.length > 0 ? { counts: [...decls.counts] } : {}),
1009
- ...(decls.sums && decls.sums.length > 0 ? { sums: [...decls.sums] } : {}),
1010
- ...(decls.mins && decls.mins.length > 0 ? { mins: [...decls.mins] } : {}),
1011
- ...(decls.maxes && decls.maxes.length > 0 ? { maxes: [...decls.maxes] } : {}),
1012
- ...(decls.spatials && decls.spatials.length > 0 ? { spatials: [...decls.spatials] } : {}),
1013
- ...(decls.workspaceInvariants && decls.workspaceInvariants.length > 0
1014
- ? { workspaceInvariants: [...decls.workspaceInvariants] }
1015
- : {}),
1016
- } as unknown as DomainModule;
1017
- fragments.push(generateOpenApiFragment(module, {
1018
- ...(lawHash !== undefined ? { lawHash } : {}),
1019
- includeFrameworkRoutes: true,
1020
- }));
1064
+ const document = config.openapiDocument;
1065
+ if (document === undefined) {
1066
+ throw new Error("engine describe: selected plan has no compiled OpenAPI projection");
1021
1067
  }
1022
- const title = opts?.title ?? config.packageTitle;
1023
- return mergeOpenApiFragments(fragments, {
1024
- ...(title !== undefined ? { title } : {}),
1025
- ...(lawHash !== undefined ? { lawHash } : {}),
1026
- });
1068
+ const lawHash = opts?.lawHash;
1069
+ const title = opts?.title ?? config.packageTitle ?? document.info["title"];
1070
+ const priorInfo = document.info;
1071
+ const info: Record<string, unknown> = {
1072
+ ...priorInfo,
1073
+ ...(title === undefined ? {} : { title }),
1074
+ version: lawHash === undefined ? (priorInfo["version"] ?? "0") : lawHash.slice(0, 12),
1075
+ };
1076
+ if (lawHash === undefined) delete info["x-nomos-law-hash"];
1077
+ else info["x-nomos-law-hash"] = lawHash;
1078
+ return { ...document, info };
1027
1079
  }
1028
1080
 
1029
1081
  // The engine evals the bundle as a classic script and FREEZES globalThis before
@@ -1031,7 +1083,9 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
1031
1083
  // describe verb (the kernel `describe` run mode evals `globalThis.describe(opts)`).
1032
1084
  (globalThis as { plan?: unknown }).plan = plan;
1033
1085
  (globalThis as { planReport?: unknown }).planReport = planReport;
1086
+ (globalThis as { __nomosPrepareDomain?: unknown }).__nomosPrepareDomain = prepareDomain;
1087
+ (globalThis as { __nomosHasDirective?: unknown }).__nomosHasDirective = hasDirective;
1034
1088
  (globalThis as { describe?: unknown }).describe = describe;
1035
1089
 
1036
- return { plan, planReport, describe } as RegisteredEngine;
1090
+ return { plan, planReport, prepareDomain, hasDirective, describe } as RegisteredEngine;
1037
1091
  }
@@ -7,7 +7,7 @@
7
7
  * THE EVOLVE PREVIEW — `githolon check --against <prior>` (architecture/author_time_gate.md,
8
8
  * the `--against` row of check slice 3). The evolve gate is the kernel's #58 diff-by-stable-id
9
9
  * (executor/src/law58.rs::evolve_violation): a RENAME (same sid, new label) admits silently; a
10
- * true REMOVAL / merge-driver change refuses TYPED unless the deploy JSON carries a
10
+ * true REMOVAL / merge-driver change refuses TYPED unless the installation offer carries a
11
11
  * `dispositions` acknowledgement. A changed BUSINESS VALUE CONTRACT is stricter: it needs
12
12
  * deterministic `.evolvesFrom(...)` coverage and cannot be waived by an acknowledgement.
13
13
  *
@@ -38,7 +38,6 @@ import type { StableIdLockfile } from "./stable_id_lockfile.js";
38
38
  export type { StableIdLockfile } from "./stable_id_lockfile.js";
39
39
  import type { DomainModule } from "./codegen_dart.js";
40
40
  import type { WireSchema } from "./wire.js";
41
- import { parseUsdaDocument } from "./usd_layers.js";
42
41
 
43
42
  // ── the normalized law shape both sides of the diff speak ──────────────────────────
44
43
 
@@ -72,7 +71,7 @@ export interface EvolveDomain {
72
71
  /** A whole package's evolve surface, keyed by DOMAIN KEY. */
73
72
  export type EvolveLaw = Record<string, EvolveDomain>;
74
73
 
75
- /** The dispositions fragment the deploy JSON carries to admit destructive evolves (the exact
74
+ /** The dispositions fragment the installDomain offer carries to admit destructive evolves (the exact
76
75
  * shape executor/src/law58.rs::dispositions_from_payload parses). */
77
76
  export interface EvolveDispositionsJson {
78
77
  retired?: string[];
@@ -112,7 +111,7 @@ function aggregatesFromStableIds(
112
111
  return out;
113
112
  }
114
113
 
115
- /** Build an EvolveDomain from a DomainInterface (deploy.json's `interface`) — its aggregates are
114
+ /** Build an EvolveDomain from a diagnostic DomainInterface — its aggregates are
116
115
  * already sid-keyed with kinds+labels, its directives carry payload shapes. Drivers are absent
117
116
  * from an interface (it exposes only read kinds), so a driver-only retype is not seen from an
118
117
  * interface alone — the lockfile lane (aggregatesFromStableIds + schemas) carries them. */
@@ -132,72 +131,29 @@ function evolveDomainFromInterface(iface: DomainInterface): EvolveDomain {
132
131
  };
133
132
  }
134
133
 
135
- /** A deploy.json body (the subset this preview reads): the per-domain interface + the package
136
- * bytes (for the embedded stable-ids/driver schemas when no lockfile rides alongside). */
137
- export interface DeployJson {
138
- interface?: Record<string, DomainInterface>;
139
- packageUsda?: string;
140
- }
141
-
142
- /** Reconstruct the effective law directly from a native `.package.usda` stage. */
143
- export function usdJsonFromPackage(bundle: string): unknown | undefined {
144
- try {
145
- return parseUsdaDocument(bundle);
146
- } catch {
147
- return undefined;
148
- }
149
- }
150
-
151
- /** Per-domain-key { stableIds, schemas } parsed out of a package's embedded USD-IR (mirrors
152
- * executor/src/law58.rs::stable_ids_from_usd + the aggregate-prim schema harvest). */
153
- export function stableIdsFromUsd(
154
- usd: unknown,
155
- ): Record<string, { stableIds: StableIds; schemas: Record<string, WireSchema> }> {
156
- const out: Record<string, { stableIds: StableIds; schemas: Record<string, WireSchema> }> = {};
157
- const layers = (usd as { layers?: unknown[] })?.layers;
158
- if (!Array.isArray(layers)) return out;
159
- for (const layer of layers) {
160
- const l = layer as {
161
- path?: string;
162
- nomosStableIds?: StableIds;
163
- prims?: Array<{ kind?: string; type?: string; schema?: Record<string, unknown> }>;
164
- };
165
- const path = typeof l.path === "string" ? l.path : "";
166
- const key = path.startsWith("/Nomos/") ? path.slice("/Nomos/".length) : "";
167
- if (key === "" || l.nomosStableIds === undefined) continue;
168
- const schemas: Record<string, WireSchema> = {};
169
- for (const prim of l.prims ?? []) {
170
- if (prim.kind === "aggregate" && typeof prim.type === "string" && prim.schema) {
171
- schemas[prim.type] = prim.schema as WireSchema;
172
- }
173
- }
174
- out[key] = { stableIds: l.nomosStableIds, schemas };
175
- }
176
- return out;
177
- }
178
-
179
- /** Build an EvolveLaw from a deploy.json body: interface for directive payloads + sid-keyed read
180
- * shapes, and the embedded package USD-IR for the driver schemas (so a driver-only retype is
181
- * seen from a deploy.json alone). */
182
- export function evolveLawFromDeployJson(deploy: DeployJson): EvolveLaw {
134
+ /** Build an EvolveLaw from compiler diagnostic sidecars. The interface supplies directive and
135
+ * read shapes; the stable-id lock supplies identity, drivers and value-contract evolution. Neither
136
+ * is deployable law: canonical law remains the composed, logically named USDA closure interpreted
137
+ * by the kernel; `.law.usdz` is only its lossless one-file transport envelope. */
138
+ export function evolveLawFromInterface(
139
+ ifaces: Record<string, DomainInterface>,
140
+ lock?: StableIdLockfile,
141
+ ): EvolveLaw {
183
142
  const law: EvolveLaw = {};
184
- const ifaces = deploy.interface ?? {};
185
- const usd = deploy.packageUsda !== undefined ? usdJsonFromPackage(deploy.packageUsda) : undefined;
186
- const fromUsd = usd !== undefined ? stableIdsFromUsd(usd) : {};
187
143
  for (const [key, iface] of Object.entries(ifaces)) {
188
144
  const dom = evolveDomainFromInterface(iface);
189
- // Augment fields with drivers off the package USD-IR schemas (interface carries none).
190
- const usdDom = fromUsd[key];
191
- if (usdDom !== undefined) {
145
+ const locked = lock?.domains[key] ?? lock?.domains[iface.domain];
146
+ const stableIds = locked?.stableIds;
147
+ if (stableIds !== undefined) {
192
148
  for (const agg of Object.values(dom.aggregates)) {
193
- const stableAggregate = Object.values(usdDom.stableIds.aggregates)
149
+ const stableAggregate = Object.values(stableIds.aggregates)
194
150
  .find((candidate) => candidate.sid === agg.sid);
195
151
  const stableFields = new Map(
196
152
  Object.values(stableAggregate?.fields ?? {})
197
153
  .map((field) => [field.sid, field] as const),
198
154
  );
199
- const schema = usdDom.schemas[agg.label]
200
- ?? Object.entries(usdDom.schemas).find(([type]) => type.endsWith(`.${agg.label}`))?.[1];
155
+ const schema = locked?.schemas?.[agg.label]
156
+ ?? Object.entries(locked?.schemas ?? {}).find(([type]) => type.endsWith(`.${agg.label}`))?.[1];
201
157
  for (const f of Object.values(agg.fields)) {
202
158
  const driver = schema?.[f.label];
203
159
  if (driver !== undefined) (f as { driver?: string }).driver = JSON.stringify(driver);
@@ -212,10 +168,14 @@ export function evolveLawFromDeployJson(deploy: DeployJson): EvolveLaw {
212
168
  }
213
169
  law[key] = dom;
214
170
  }
215
- // A deploy.json with NO interface (older builds) still yields aggregate identity from the USD.
216
- for (const [key, sd] of Object.entries(fromUsd)) {
171
+ // A lockfile without a sibling interface still supports aggregate/field identity checks.
172
+ for (const [key, locked] of Object.entries(lock?.domains ?? {})) {
217
173
  if (law[key] !== undefined) continue;
218
- law[key] = { domainSid: sd.stableIds.domain, aggregates: aggregatesFromStableIds(sd.stableIds, sd.schemas), directives: {} };
174
+ law[key] = {
175
+ domainSid: locked.stableIds?.domain,
176
+ aggregates: aggregatesFromStableIds(locked.stableIds, locked.schemas),
177
+ directives: {},
178
+ };
219
179
  }
220
180
  return law;
221
181
  }
@@ -275,7 +235,7 @@ export function currentEvolveLaw(
275
235
  // ── the diff ────────────────────────────────────────────────────────────────────────
276
236
 
277
237
  /** The full preview result: the findings AND the merged dispositions the destructive findings
278
- * demand, keyed by DOMAIN KEY (exactly the object a deploy JSON carries per installDomain). */
238
+ * demand, keyed by DOMAIN KEY (the object carried by an installDomain offer). */
279
239
  export interface EvolveDiffResult {
280
240
  readonly findings: Finding[];
281
241
  /** domain key → the dispositions fragment that makes THIS domain's destructive evolves admit. */
@@ -337,7 +297,7 @@ function diffDomain(
337
297
  domain,
338
298
  level: "refuse",
339
299
  what: `aggregate '${oldAgg.label}' (stable id ${sid}) is REMOVED — the evolve gate will refuse without a disposition`,
340
- fix: `paste into your deploy JSON: "dispositions": { "retired": ["${sid}"] }`,
300
+ fix: `include in the installation offer: "dispositions": { "retired": ["${sid}"] }`,
341
301
  });
342
302
  continue;
343
303
  }
@@ -359,7 +319,7 @@ function diffDomain(
359
319
  domain,
360
320
  level: "refuse",
361
321
  what: `field '${oldAgg.label}.${oldF.label}' (stable id ${fSid}) is REMOVED — the evolve gate will refuse without a disposition`,
362
- fix: `paste into your deploy JSON: "dispositions": { "retired": ["${fSid}"] }`,
322
+ fix: `include in the installation offer: "dispositions": { "retired": ["${fSid}"] }`,
363
323
  });
364
324
  continue;
365
325
  }
@@ -396,7 +356,7 @@ function diffDomain(
396
356
  domain,
397
357
  level: "refuse",
398
358
  what: `field '${oldAgg.label}.${oldF.label}' (stable id ${fSid}) is RETYPED in place (${how}) — the evolve gate will refuse without a disposition`,
399
- fix: `paste into your deploy JSON: "dispositions": { "retyped": { "${fSid}": "<why goes here>" } } — or add a NEW field instead`,
359
+ fix: `include in the installation offer: "dispositions": { "retyped": { "${fSid}": "<why goes here>" } } — or add a NEW field instead`,
400
360
  });
401
361
  }
402
362
  }
@@ -492,7 +452,7 @@ export function evolveDiff(prior: EvolveLaw, current: EvolveLaw): EvolveDiffResu
492
452
  domain,
493
453
  level: "refuse",
494
454
  what: `domain '${domain}' aggregate '${agg.label}' (stable id ${sid}) disappears — the current build no longer carries domain '${domain}'`,
495
- fix: `restore the domain, or (a true removal) redeploy with "dispositions": { "retired": ["${sid}"] }`,
455
+ fix: `restore the domain, or include "dispositions": { "retired": ["${sid}"] } in the replacement installation offer`,
496
456
  });
497
457
  }
498
458
  continue;
package/src/extremum.ts CHANGED
@@ -28,7 +28,7 @@
28
28
  * VALIDATION RULE (manifest-load, not type-level): the `valueField` parameter MUST be
29
29
  * an `int`-kind field of the `of`-aggregate. The DSL type system cannot enforce this
30
30
  * at the `min/max(id, valueField)` call site (the aggregate is not yet known), but
31
- * `AggregateSchemas::parse` (readmodel/src/manifest.rs) performs a no-fallback reject
31
+ * typed USDA projection-law admission performs a no-fallback reject
32
32
  * if the field is absent or non-int on the declared aggregate.
33
33
  *
34
34
  * ORDER-SENSITIVE GUARDRAIL: min/max expose NO `.first`/`.take`/`.orderBy`. min/max