@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
@@ -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
  }