@githolon/dsl 0.100.6 → 0.101.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 (82) hide show
  1. package/dart/CHANGELOG.md +6 -0
  2. package/dart/lib/src/dev_cloud.dart +46 -15
  3. package/dart/lib/src/test_harness.dart +78 -16
  4. package/dart/lib/src/webview_bridge.dart +640 -126
  5. package/dart/pubspec.lock +1 -1
  6. package/dart/pubspec.yaml +2 -2
  7. package/dart/test/bundle_version_pairing_test.dart +11 -22
  8. package/dart/test/co2_product_gauntlet_test.dart +228 -0
  9. package/dart/test/errors_test.dart +63 -12
  10. package/dart/test/first_app_gauntlet_test.dart +29 -3
  11. package/dart/test/webview_bridge_test.dart +274 -0
  12. package/dart_types/CHANGELOG.md +6 -0
  13. package/dart_types/lib/src/errors.dart +136 -30
  14. package/dart_types/lib/src/surfaced.dart +48 -2
  15. package/dart_types/lib/src/wire.dart +3 -1
  16. package/dart_types/pubspec.yaml +1 -1
  17. package/dart_types/test/surfaced_test.dart +19 -0
  18. package/package.json +1 -1
  19. package/src/activity.ts +43 -4
  20. package/src/aggregate.ts +132 -22
  21. package/src/attested_core.ts +1 -1
  22. package/src/authoring.ts +119 -6
  23. package/src/birth_meter.ts +4 -4
  24. package/src/birth_meter_ensure.ts +9 -1
  25. package/src/births.ts +53 -22
  26. package/src/build_package.ts +55 -14
  27. package/src/business_birth.ts +48 -7
  28. package/src/codegen_dart.ts +267 -37
  29. package/src/codegen_dart_client.ts +921 -82
  30. package/src/codegen_openapi.ts +28 -5
  31. package/src/codegen_proof.ts +107 -11
  32. package/src/codegen_ts.ts +961 -135
  33. package/src/compile_package_main.ts +155 -45
  34. package/src/conflicts.ts +252 -0
  35. package/src/describe_model.ts +448 -0
  36. package/src/directive.ts +5 -38
  37. package/src/drivers.ts +12 -4
  38. package/src/engine_entry.ts +4 -0
  39. package/src/evolve_preview.ts +72 -11
  40. package/src/fields.ts +201 -55
  41. package/src/framework/application_home.ts +103 -0
  42. package/src/framework/application_home_domain.ts +2 -0
  43. package/src/framework/attested_receipts.ts +2 -2
  44. package/src/framework/auth_provider_law.ts +51 -24
  45. package/src/framework/business_corrections.ts +170 -0
  46. package/src/framework/disclosure.ts +1 -1
  47. package/src/framework/domain_lifecycle.ts +250 -16
  48. package/src/framework/encryption_identity.ts +1 -1
  49. package/src/framework/encryption_scope.ts +21 -13
  50. package/src/framework/era_certification.ts +1 -1
  51. package/src/framework/framework_aggregates.ts +2 -0
  52. package/src/framework/genesis.ts +1 -1
  53. package/src/framework/home.ts +22 -9
  54. package/src/framework/identity.ts +5 -5
  55. package/src/framework/impure_capability.ts +2 -2
  56. package/src/framework/owned_custody.ts +561 -0
  57. package/src/framework/platform_admin.ts +1 -1
  58. package/src/framework/relations.ts +9 -1
  59. package/src/framework/repair.ts +1 -1
  60. package/src/framework/signing_identity.ts +2 -2
  61. package/src/framework/source_roster.ts +1 -1
  62. package/src/framework/workspaces.ts +17 -10
  63. package/src/generated_client_names.ts +39 -0
  64. package/src/index.ts +20 -15
  65. package/src/kernel_capabilities.ts +25 -1
  66. package/src/law_check.ts +4 -1
  67. package/src/manifest.ts +356 -63
  68. package/src/read_projection_manifest.ts +8 -0
  69. package/src/read_projection_namespace.ts +4 -0
  70. package/src/relation.ts +1 -1
  71. package/src/shape_compat.ts +47 -2
  72. package/src/stable_ids.ts +38 -6
  73. package/src/stable_ids_types.ts +30 -0
  74. package/src/surfaced_lint.ts +0 -0
  75. package/src/usd.ts +62 -11
  76. package/src/usd_layers.ts +54 -3
  77. package/src/value_object.ts +23 -19
  78. package/src/wire.ts +1 -0
  79. package/src/wire_encode.ts +48 -4
  80. package/src/workspace_routing.ts +57 -0
  81. package/src/workspace_sharding.ts +9 -9
  82. package/src/certified_read.ts +0 -313
@@ -67,12 +67,14 @@ import {
67
67
  } from "./codegen_dart.js";
68
68
  import type { ContractHandle } from "./contract.js";
69
69
  import { generateDartBridgeClient } from "./codegen_dart_client.js";
70
+ import { buildNomosDescribeModel } from "./describe_model.js";
70
71
  import {
71
72
  isFrameworkAggregate,
72
73
  isFrameworkDirective,
73
74
  isFrameworkQuery,
74
75
  } from "./framework/framework_aggregates.js";
75
76
  import { RelationTuple, relationTuplesByObject } from "./framework/relations.js";
77
+ import { isNamespacedAggregate } from "./read_projection_namespace.js";
76
78
  import { finishCount, type AnyCount } from "./count.js";
77
79
  import type { QueryDecl } from "./query.js";
78
80
  import type { SpatialDecl } from "./spatial.js";
@@ -94,6 +96,7 @@ import { synthesizePreconditionInvariants } from "./precondition.js";
94
96
  import { BIRTH_FRAMEWORK_QUERIES, synthesizeBirthQuotaInvariants } from "./birth_meter.js";
95
97
  import { withBirthMeterExports } from "./birth_meter_ensure.js";
96
98
  import { expandBusinessBirthExports } from "./business_birth.js";
99
+ import { withOwnedCustodyExports } from "./framework/owned_custody.js";
97
100
  import { expandRoleExports, rolesOf } from "./role.js";
98
101
  import { collectEngineRouting } from "./engine_entry.js";
99
102
  import {
@@ -134,6 +137,7 @@ import { generateTsProof, proofLegsIndex } from "./codegen_proof.js";
134
137
  import { checkDomainModules, formatFindings, hasRefusal } from "./law_check.js";
135
138
  import type { NomosCompat } from "./compat_manifest.js";
136
139
  import { requiredKernelFeatures, type FeatureProbeModule } from "./kernel_capabilities.js";
140
+ import { APPLICATION_HOME_DOMAIN } from "./framework/application_home_domain.js";
137
141
 
138
142
  const DSL_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
139
143
 
@@ -494,8 +498,8 @@ const FRAMEWORK_FILE = "nomos_framework";
494
498
  /** The shared basename of the domain-contracts DTO file emitted once per package (domain_contracts.md). */
495
499
  const CONTRACTS_FILE = "contracts";
496
500
 
497
- /** The shared basename of the ACTIVITY REGISTRY file emitted once per package (activity_descriptors.md).
498
- * Absent when no directive declares `.activity(...)` — an activity-free package emits no `activity_registry.dart`. */
501
+ /** The shared basename of the ACCOUNTABLE ACTIVITY REGISTRY emitted once per package when it carries at
502
+ * least one public business directive. `.activity(...)` enriches the generated baseline; it does not gate it. */
499
503
  const ACTIVITY_FILE = "activity_registry";
500
504
 
501
505
  /** Collect every domain contract across the package's modules, deduped by id (later-wins). Sorted by id.
@@ -783,6 +787,7 @@ function emitDart(
783
787
  barrelName = "nomos_domains",
784
788
  compat?: NomosCompat,
785
789
  names?: Readonly<Record<string, string>>,
790
+ describePackageName = barrelName,
786
791
  ): string[] {
787
792
  const domainSources: string[] = []; // for flat-mode alias validation (dart.names)
788
793
  const sourceByFile = new Map<string, string>(); // fileBase → src, for precise alias-import selection
@@ -889,14 +894,14 @@ function emitDart(
889
894
  aggregateIdClassNames: packageAggregateIdClassNames(modules),
890
895
  }));
891
896
  }
892
- // 2a-ter. the shared ACTIVITY REGISTRY (`activity_registry.dart`) — the pure `renderActivity`, ONCE. Emitted
893
- // iff a directive opts in via `.activity(...)` (activity-free packages stay byte-identical). Pure
894
- // (nomos_types), so the client folds typed activity rows from the intent log with no runtime import.
897
+ // 2a-ter. the shared ACCOUNTABLE ACTIVITY + DESCRIPTION REGISTRY (`activity_registry.dart`) — emitted
898
+ // ONCE for every package. A read-only package has an empty activity registry but still exposes
899
+ // describe(model/current/rules/history/decisions/evidence).
900
+ // Pure (nomos_types), so the client folds typed rows from the intent log with no runtime import.
895
901
  const packageActivity = collectActivityDescriptors(modules);
896
- const activityImportUri = packageActivity.length > 0 ? `${ACTIVITY_FILE}.dart` : undefined;
897
- if (packageActivity.length > 0) {
898
- writeGenerated(ACTIVITY_FILE, generateDartActivityRegistry(packageActivity));
899
- }
902
+ const describeModel = buildNomosDescribeModel(modules, describePackageName);
903
+ const activityImportUri = `${ACTIVITY_FILE}.dart`;
904
+ writeGenerated(ACTIVITY_FILE, generateDartActivityRegistry(packageActivity));
900
905
  // CROSS-DOMAIN `t.foreignRef` (auto import). A foreignRef field typed `ForeignRef<CatalogueListing>` +
901
906
  // `CatalogueListing.fromJson` needs the TARGET aggregate's Dart class — which lives in the target DOMAIN's
902
907
  // generated file. Within ONE package the compiler knows which domain owns each aggregate, so we auto-emit
@@ -972,7 +977,9 @@ function emitDart(
972
977
  ...(compat !== undefined ? { compat } : {}),
973
978
  supportImport,
974
979
  ...(domainFrameworkImport !== undefined ? { frameworkImportUri: domainFrameworkImport } : {}),
975
- ...(packageActivity.length > 0 ? { emitActivity: true, ...(activityImportUri !== undefined ? { activityImportUri } : {}) } : {}),
980
+ emitActivity: true,
981
+ activityImportUri,
982
+ describeModel,
976
983
  packageAggregateDomains,
977
984
  });
978
985
  const cp = path.join(genDir, `${mod.name}.client.dart`);
@@ -1051,11 +1058,11 @@ const DART_TYPES_IMPORT = `import 'package:nomos_types/nomos_types.dart';`;
1051
1058
  * just the floor generated code needs — a lagging pin forces tenants into dependency_overrides. */
1052
1059
  // 0.3.0: SurfacedValue<T>/SurfacedObservation/PendingConflictRow — generated Surfaced-field typing +
1053
1060
  // the pendingConflicts read rows.
1054
- const NOMOS_TYPES_DEP = "^0.6.0";
1061
+ const NOMOS_TYPES_DEP = "^0.7.0";
1055
1062
  // 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
1056
1063
  // DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
1057
1064
  // REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
1058
- const NOMOS_CLIENT_DEP = "^0.62.0";
1065
+ const NOMOS_CLIENT_DEP = "^0.63.0";
1059
1066
  // DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
1060
1067
  // and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
1061
1068
  // land back in dependency_overrides (the exact co2 report this closes, third occurrence).
@@ -1145,6 +1152,7 @@ function emitDartPackages(
1145
1152
  packages: { readonly types: string; readonly client: string; readonly test?: string; readonly compat?: string; readonly barrelExports?: "all" | "explicit" },
1146
1153
  names: Readonly<Record<string, string>> | undefined,
1147
1154
  compat?: NomosCompat,
1155
+ describePackageName = packages.client,
1148
1156
  ): { written: string[]; cursed: string[] } {
1149
1157
  for (const [role, name] of Object.entries(packages)) {
1150
1158
  if (typeof name !== "string" || !isPubPackageName(name)) {
@@ -1160,6 +1168,7 @@ function emitDartPackages(
1160
1168
  if (new Set(roleNames).size !== roleNames.length) fail(`dart.packages: types/client/test/compat names must be distinct`);
1161
1169
 
1162
1170
  const packageContracts = collectPackageContracts(modules);
1171
+ const describeModel = buildNomosDescribeModel(modules, describePackageName);
1163
1172
  const shared = collectDartSharedSymbols(modules, packageContracts);
1164
1173
  const written: string[] = [];
1165
1174
  const genRel = DART_GENERATED_SUBDIR.split(path.sep).join("/");
@@ -1222,10 +1231,9 @@ function emitDartPackages(
1222
1231
  aggregateIdClassNames: packageAggregateIdClassNames(modules),
1223
1232
  }));
1224
1233
  }
1225
- // ACTIVITY REGISTRY (layered): the pure `renderActivity` in the TYPES package (nomos_types only). The
1226
- // client resolves it through the types-package barrel (which re-exports every types file). Opt-in only.
1234
+ // ACTIVITY + DESCRIPTION REGISTRY (layered): universal, including packages with empty history.
1227
1235
  const packageActivity = collectActivityDescriptors(modules);
1228
- if (packageActivity.length > 0) writeTypes(ACTIVITY_FILE, generateDartActivityRegistry(packageActivity));
1236
+ writeTypes(ACTIVITY_FILE, generateDartActivityRegistry(packageActivity));
1229
1237
  // CROSS-DOMAIN foreignRef (layered): map aggregate id → its domain's type file, so a foreignRef field's
1230
1238
  // target class resolves via a sibling import (the purity gate now permits sibling type files).
1231
1239
  const aggIdToTypeFile = new Map<string, string>();
@@ -1357,7 +1365,8 @@ function emitDartPackages(
1357
1365
  ...(compat !== undefined ? { compat } : {}),
1358
1366
  supportImport: "package",
1359
1367
  typesImportUri,
1360
- ...(packageActivity.length > 0 ? { emitActivity: true } : {}),
1368
+ emitActivity: true,
1369
+ describeModel,
1361
1370
  packageAggregateDomains: buildPackageAggregateDomains(modules),
1362
1371
  // Under 'explicit' the types barrel withholds per-domain classes — import every domain's TYPES
1363
1372
  // file directly so the client's read-model/payload/ref/typed-id references resolve. (The no-dup-
@@ -1552,6 +1561,20 @@ async function main(): Promise<void> {
1552
1561
  fail(`package name '${cfg.name}' is not a safe basename (letters, digits, spaces, '.', '-', '_')`);
1553
1562
  }
1554
1563
 
1564
+ // ONE BUSINESS BIRTH IS A COMPLETE PRODUCT ENTRYPOINT. The compiler privately appends the
1565
+ // application/home law which turns a signed-in identity into its home workspace; that home can then
1566
+ // call the generated business birth. Tenant config never names this domain. The deliberately obscure
1567
+ // key reduces collision likelihood, but collision handling is still fail-closed: never reinterpret a
1568
+ // tenant's same-named concept as framework machinery.
1569
+ if (cfg.domains.some((d) => d.key === APPLICATION_HOME_DOMAIN)) {
1570
+ fail(
1571
+ `domain key '${APPLICATION_HOME_DOMAIN}' is reserved by Nomos for the private application/home law; ` +
1572
+ `rename the tenant domain (Nomos will not silently overwrite or reroute it)`,
1573
+ );
1574
+ }
1575
+ const domainsToCompile: DomainConfig[] = [...cfg.domains];
1576
+ let applicationHomeInjected = false;
1577
+
1555
1578
  const outDir = path.resolve(cfgDir, cfg.outDir ?? "build");
1556
1579
  mkdirSync(outDir, { recursive: true });
1557
1580
 
@@ -1599,7 +1622,7 @@ async function main(): Promise<void> {
1599
1622
  tasksQueryId: string | null;
1600
1623
  }[] = [];
1601
1624
 
1602
- for (const d of cfg.domains) {
1625
+ for (const d of domainsToCompile) {
1603
1626
  if (typeof d.key !== "string" || !Array.isArray(d.modules) || d.modules.length === 0) {
1604
1627
  fail(`each domain needs { key, modules: [path, ...] }; got ${JSON.stringify(d)}`);
1605
1628
  }
@@ -1620,7 +1643,21 @@ async function main(): Promise<void> {
1620
1643
  }
1621
1644
  let merged: Mod = {};
1622
1645
  for (const m of mods) merged = { ...merged, ...m };
1646
+ const declaresBusinessBirth = Object.values(merged).some(
1647
+ (value) =>
1648
+ value !== null &&
1649
+ typeof value === "object" &&
1650
+ (value as { __isBusinessBirth?: unknown }).__isBusinessBirth === true,
1651
+ );
1652
+ if (declaresBusinessBirth && !applicationHomeInjected) {
1653
+ applicationHomeInjected = true;
1654
+ domainsToCompile.push({
1655
+ key: APPLICATION_HOME_DOMAIN,
1656
+ modules: [path.join(DSL_DIR, "src", "framework", "application_home.ts")],
1657
+ });
1658
+ }
1623
1659
  merged = expandBusinessBirthExports(merged, d.key);
1660
+ merged = withOwnedCustodyExports(merged);
1624
1661
  // `capability()` bundles expand into discoverable entries (the CLI plane's
1625
1662
  // merge point) — queries/sums/sid-minting/manifests all see the pieces.
1626
1663
  merged = expandCapabilityExports(merged);
@@ -1756,7 +1793,7 @@ async function main(): Promise<void> {
1756
1793
  // taxonomy-free packages emit NOTHING here (the slice-1 byte-stability law).
1757
1794
  if (shardingModForDomain !== undefined && shardingLazyExpr !== undefined && !eagerLump) {
1758
1795
  const auxKey = `${d.key}/nomos-sharding`;
1759
- if (cfg.domains.some((x) => x.key === auxKey) || routingInput[auxKey] !== undefined) {
1796
+ if (domainsToCompile.some((x) => x.key === auxKey) || routingInput[auxKey] !== undefined) {
1760
1797
  fail(
1761
1798
  `domain key '${auxKey}' is reserved for the derived sharding law's invariant routing — rename the domain`,
1762
1799
  );
@@ -1911,7 +1948,7 @@ async function main(): Promise<void> {
1911
1948
  ? { dartImports: d.dartImports }
1912
1949
  : {}),
1913
1950
  ...(d.dartRefImports !== undefined && d.dartRefImports.length > 0
1914
- ? { dartRefImports: d.dartRefImports }
1951
+ ? { dartRefImports: [...d.dartRefImports] }
1915
1952
  : {}),
1916
1953
  ...(permissionVocabulary !== undefined ? { permissionVocabulary } : {}),
1917
1954
  }),
@@ -2042,6 +2079,13 @@ async function main(): Promise<void> {
2042
2079
  }
2043
2080
  }
2044
2081
 
2082
+ // Presentation is a separate concern from executable law. The private application/home domain remains
2083
+ // in `domainModules` for hashing, routing, self-description, replay and static checking, but generated
2084
+ // tenant clients/proofs/interfaces contain business vocabulary only.
2085
+ const presentationDomainModules = domainModules.filter(
2086
+ (mod) => (mod.domain ?? mod.name) !== APPLICATION_HOME_DOMAIN,
2087
+ );
2088
+
2045
2089
  // ── STABLE-ID CONTINUITY (#58 — names are labels, identity is minted) ──
2046
2090
  // Derive each composed domain's stable ids from the PRIOR compile: the committed
2047
2091
  // `nomos.stable-ids.json` lockfile beside the config (the durable source — survives
@@ -2065,12 +2109,23 @@ async function main(): Promise<void> {
2065
2109
  const identityName = mod.name;
2066
2110
  // The CURRENT canonical aggregate schemas (a pre-continuity manifest pass —
2067
2111
  // aggregates/schemas do not depend on continuity).
2068
- let currentAggs: { id: string; schema: WireSchema; kinds?: Record<string, string> }[];
2112
+ let currentAggs: {
2113
+ id: string;
2114
+ schema: WireSchema;
2115
+ kinds?: Record<string, string>;
2116
+ global?: boolean;
2117
+ }[];
2069
2118
  try {
2070
2119
  const preManifest = domainManifest(mod);
2071
2120
  currentAggs = preManifest.aggregates.map((a) => ({
2072
2121
  id: a.id,
2073
2122
  schema: a.schema,
2123
+ // One framework aggregate may be composed into many independently compiled
2124
+ // domain packages. Give that shared row one global stable-id scope so the
2125
+ // installed read manifest sees the same field contract from every package.
2126
+ global: mod.aggregates.some(
2127
+ (candidate) => candidate.id === a.id && !isNamespacedAggregate(candidate),
2128
+ ),
2074
2129
  // The CURRENT field kinds (off the pre-continuity stableIds emission) — the
2075
2130
  // pair rule's kind axis ("rename + add a field" in one release still infers).
2076
2131
  kinds: Object.fromEntries(
@@ -2154,7 +2209,7 @@ async function main(): Promise<void> {
2154
2209
  // `connect()` negotiates compatibility by SHAPE (stable-id subtyping), not by law hash.
2155
2210
  // architecture/shape_compatibility.md. Multiple modules sharing a domain key merge.
2156
2211
  const packageInterface: Record<string, DomainInterface> = {};
2157
- for (const mod of domainModules) {
2212
+ for (const mod of presentationDomainModules) {
2158
2213
  const key = mod.domain ?? mod.name;
2159
2214
  const di = domainInterface(mod, nextLock.domains[mod.name]?.stableIds);
2160
2215
  const prior = packageInterface[key];
@@ -2176,7 +2231,7 @@ async function main(): Promise<void> {
2176
2231
  // supported: each codegen layer mints a named recursive type for it (TS `type <Name>`, Dart
2177
2232
  // recursive class). Surfaced here so the tenant sees which fields became which named types.
2178
2233
  const recursiveNotes: string[] = [];
2179
- for (const mod of domainModules) {
2234
+ for (const mod of presentationDomainModules) {
2180
2235
  for (const d of (mod.directives ?? []) as Array<{ id?: unknown; payloadSchema?: unknown }>) {
2181
2236
  if (!d || typeof d.id !== "string" || !d.payloadSchema || typeof d.payloadSchema !== "object") continue;
2182
2237
  for (const { path, name } of recursiveLazyTypes(d.payloadSchema as Parameters<typeof recursiveLazyTypes>[0], d.id)) {
@@ -2311,6 +2366,24 @@ async function main(): Promise<void> {
2311
2366
  // JS shim with the platform-native binary, which only an exec can run.
2312
2367
  const esbuildBin = path.join(path.dirname(esbuildPkgJson), esbuildBinRel ?? "bin/esbuild");
2313
2368
 
2369
+ const canonicalizeEngineJavascript = (source: string): string => {
2370
+ // esbuild includes module labels derived from the package's install topology in
2371
+ // non-minified bundles. The same law therefore used to hash differently when the
2372
+ // DSL was reached through this monorepo (../../dsl/src/...) and through a packed
2373
+ // dependency (node_modules/@githolon/dsl/src/...). Canonicalise only esbuild's
2374
+ // generated comment/function labels: tenant strings and executable imports are
2375
+ // deliberately outside these narrow replacements.
2376
+ const body = source
2377
+ .replace(/^\s*"use strict";\s*$/gm, "")
2378
+ .replace(/^(\s*\/\/ )(?:(?:\.\.\/)+dsl\/src\/|node_modules\/@githolon\/dsl\/src\/)/gm, "$1@githolon/dsl/src/")
2379
+ .replace(/"(?:(?:\.\.\/)+dsl\/src\/|node_modules\/@githolon\/dsl\/src\/)([^"]+)"\(\) \{/g, '"@githolon/dsl/src/$1"() {')
2380
+ .replace(/^(\s*\/\/ )(?:(?:\.\.\/)+node_modules\/zod\/|node_modules\/zod\/)/gm, "$1zod/")
2381
+ .replace(/"(?:(?:\.\.\/)+node_modules\/zod\/|node_modules\/zod\/)([^"]+)"\(\) \{/g, '"zod/$1"() {')
2382
+ .replace(/\n[ \t]*\n/g, "\n")
2383
+ .trimEnd();
2384
+ return `"use strict";\n${body}\n`;
2385
+ };
2386
+
2314
2387
  const compact = compactFlag || cfg.compact === true;
2315
2388
  let javascript: string;
2316
2389
  if (compact) {
@@ -2349,6 +2422,7 @@ async function main(): Promise<void> {
2349
2422
  { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], cwd: cfgDir, maxBuffer: 256 * 1024 * 1024 },
2350
2423
  ).trimEnd() + "\n";
2351
2424
  }
2425
+ javascript = canonicalizeEngineJavascript(javascript);
2352
2426
  if (!/globalThis\.plan\s*=/.test(javascript) && !/globalThis\["plan"\]\s*=/.test(javascript)) {
2353
2427
  fail(`bundled engine entry does not assign globalThis.plan — refusing to package`);
2354
2428
  }
@@ -2442,7 +2516,7 @@ async function main(): Promise<void> {
2442
2516
  "utf8",
2443
2517
  );
2444
2518
  try {
2445
- execFileSync(process.execPath, [probePath, lumpPath, JSON.stringify(cfg.domains.map((d) => d.key))], {
2519
+ execFileSync(process.execPath, [probePath, lumpPath, JSON.stringify(domainsToCompile.map((d) => d.key))], {
2446
2520
  encoding: "utf8",
2447
2521
  stdio: ["ignore", "ignore", "pipe"],
2448
2522
  });
@@ -2555,7 +2629,7 @@ async function main(): Promise<void> {
2555
2629
  }
2556
2630
  })();
2557
2631
  const compatDomains: NomosCompat["domains"] = {};
2558
- for (const m of domainModules) {
2632
+ for (const m of presentationDomainModules) {
2559
2633
  const key = m.domain ?? m.name;
2560
2634
  const identityHash = identity.hashes[key];
2561
2635
  compatDomains[key] = identityHash === undefined ? {} : { identityHash };
@@ -2564,7 +2638,11 @@ async function main(): Promise<void> {
2564
2638
  // era-key features). Baked into the client so connect() can check it against the target
2565
2639
  // kernel's advertised capabilities BEFORE the law folds (kernel_capabilities.ts).
2566
2640
  const requiresKernel = ((): string[] => {
2567
- const probes: FeatureProbeModule[] = domainModules.map((m) => (m.queries ? { queries: m.queries } : {}));
2641
+ const probes: FeatureProbeModule[] = domainModules.map((m) => ({
2642
+ ...(m.queries ? { queries: m.queries } : {}),
2643
+ ...(m.surfacedResolvers ? { surfacedResolvers: m.surfacedResolvers } : {}),
2644
+ ...(m.aggregates ? { aggregates: m.aggregates } : {}),
2645
+ }));
2568
2646
  try {
2569
2647
  const parsed = JSON.parse(usdJson) as { layers?: Record<string, unknown>[] };
2570
2648
  for (const layer of parsed.layers ?? []) probes.push({ layer });
@@ -2607,7 +2685,7 @@ async function main(): Promise<void> {
2607
2685
  const summary = {
2608
2686
  package: cfg.name,
2609
2687
  domainHash,
2610
- domains: domainModules.map((m) => ({
2688
+ domains: presentationDomainModules.map((m) => ({
2611
2689
  domain: m.domain ?? m.name,
2612
2690
  aggregates: m.aggregates.map((a) => a.id),
2613
2691
  directives: m.directives.map((d) => d.id),
@@ -2647,14 +2725,14 @@ async function main(): Promise<void> {
2647
2725
  let birthWarnings: string[] = [];
2648
2726
  let proofNotes: string[] = [];
2649
2727
  try {
2650
- const proofSrc = generateTsProof(domainModules, { packageName: cfg.name, domainHash });
2728
+ const proofSrc = generateTsProof(presentationDomainModules, { packageName: cfg.name, domainHash, interface: packageInterface });
2651
2729
  proofPath = path.join(outDir, `${cfg.name}.proof.mts`);
2652
2730
  writeFileSync(proofPath, proofSrc, "utf8");
2653
2731
  // THE PER-DOMAIN LEGS INDEX: every domain with a creating write, so
2654
2732
  // `githolon proof --domain <key>` proves ANY of them offline (the .mts
2655
2733
  // proves the primary one live). A multi-domain package no longer locks the
2656
2734
  // proof to whichever domain happens to be first.
2657
- const legsIndex = proofLegsIndex(domainModules);
2735
+ const legsIndex = proofLegsIndex(presentationDomainModules, packageInterface);
2658
2736
  if (legsIndex !== null) {
2659
2737
  writeFileSync(path.join(outDir, `${cfg.name}.proof-legs.json`), JSON.stringify(legsIndex, null, 2) + "\n", "utf8");
2660
2738
  proofDomains = Object.keys(legsIndex.domains);
@@ -2671,7 +2749,7 @@ async function main(): Promise<void> {
2671
2749
  // itself failed, still derive the per-domain diagnostics (best-effort).
2672
2750
  if (proofSkip !== undefined && birthWarnings.length === 0) {
2673
2751
  try {
2674
- const idx = proofLegsIndex(domainModules);
2752
+ const idx = proofLegsIndex(presentationDomainModules, packageInterface);
2675
2753
  if (idx !== null) {
2676
2754
  birthWarnings = Object.values(idx.domains).flatMap((l) => l.birthDiagnostics ?? []);
2677
2755
  proofNotes = Object.values(idx.domains).flatMap((l) => l.proofDiagnostics ?? []);
@@ -2689,7 +2767,7 @@ async function main(): Promise<void> {
2689
2767
  ` queries: ${d.queries.join(", ") || "—"} counts: ${d.counts.join(", ") || "—"} sums: ${d.sums.join(", ") || "—"}`,
2690
2768
  ]),
2691
2769
  `deploy: POST ${cfg.name}.deploy.json to /v2/workspaces/<ws>/domains (or: githolon deploy <ws>)`,
2692
- `typed client: ${cfg.name}.client.ts — ${tsClientFactoryName(domainModules[0]?.domain ?? domainModules[0]?.name ?? cfg.name)}(holon), law hash baked in`,
2770
+ `typed client: ${cfg.name}.client.ts — ${tsClientFactoryName(presentationDomainModules[0]?.domain ?? presentationDomainModules[0]?.name ?? cfg.name)}(holon), law hash baked in`,
2693
2771
  proofPath !== undefined
2694
2772
  ? `generated proof: ${cfg.name}.proof.mts — run: githolon proof (live; ends ALL GREEN or names the jam)`
2695
2773
  : `generated proof: SKIPPED — ${proofSkip}`,
@@ -2700,7 +2778,7 @@ async function main(): Promise<void> {
2700
2778
  // ── 5. the typed TS client (#10 — the web sibling of codegen_dart), with the
2701
2779
  // deployed law's content hash baked in (emitted AFTER the package is hashed) ──
2702
2780
  const clientPath = path.join(outDir, `${cfg.name}.client.ts`);
2703
- writeFileSync(clientPath, generateTsClient(domainModules, { packageName: cfg.name, domainHash, interface: packageInterface, compat: nomosCompat }), "utf8");
2781
+ writeFileSync(clientPath, generateTsClient(presentationDomainModules, { packageName: cfg.name, domainHash, interface: packageInterface, compat: nomosCompat }), "utf8");
2704
2782
 
2705
2783
  // ── 6. the typed Dart frontend (opt-in; the Flutter sibling of step 5, emitted
2706
2784
  // after the package hash like the TS client) ──
@@ -2746,7 +2824,7 @@ async function main(): Promise<void> {
2746
2824
  // COMPILE ATOMICITY (co2): the Dart emit is transactional (beginAtomicGenDir) — a mid-emit fail()/throw
2747
2825
  // restores the prior good packages instead of leaving them wiped/partial. Finalized at process exit.
2748
2826
  if (dartPackages !== undefined) {
2749
- const r = emitDartPackages(domainModules, dartOut, domainHash, packageInterface, dartPackages, dartNames, nomosCompat);
2827
+ const r = emitDartPackages(presentationDomainModules, dartOut, domainHash, packageInterface, dartPackages, dartNames, nomosCompat, cfg.name);
2750
2828
  dartFiles = r.written;
2751
2829
  dartCursedHints = r.cursed;
2752
2830
  } else {
@@ -2759,7 +2837,7 @@ async function main(): Promise<void> {
2759
2837
  .replace(/^(?=[0-9])/, "n");
2760
2838
  const dartBarrel =
2761
2839
  typeof dartCfg === "object" && dartCfg.barrel !== undefined ? dartCfg.barrel : defaultBarrel;
2762
- dartFiles = emitDart(domainModules, dartOut, domainHash, packageInterface, dartSupportImport, dartBarrel, nomosCompat, dartNames);
2840
+ dartFiles = emitDart(presentationDomainModules, dartOut, domainHash, packageInterface, dartSupportImport, dartBarrel, nomosCompat, dartNames, cfg.name);
2763
2841
  }
2764
2842
  // COMPILE-VALID MUST MEAN COMPILABLE (co2): `githolon compile` reported success while the generated Dart
2765
2843
  // had hard compiler errors (a duplicate id symbol, missing contract-id defs). Run `dart analyze` over the
@@ -2810,16 +2888,29 @@ async function main(): Promise<void> {
2810
2888
  }
2811
2889
  return undefined;
2812
2890
  })();
2813
- let dartMissing = false;
2814
- let unresolved: string | undefined;
2891
+ // Package generation must not depend on which SDKs happen to be installed on the compiler's
2892
+ // machine. Write every source-dogfood override first; the analyzer loop may legitimately stop
2893
+ // at the first missing `dart`, but that must not leave later solve roots pinned to hosted code.
2815
2894
  for (const pkg of pkgDirs) {
2816
2895
  if (localDart) {
2817
2896
  const spec = readFileSync(path.join(pkg, "pubspec.yaml"), "utf8");
2818
2897
  const lines = ["dependency_overrides:"];
2819
- if (/(^|\n)\s+nomos_client\s*:/.test(spec)) lines.push(` nomos_client:`, ` path: ${localDart.client}`);
2820
- if (/(^|\n)\s+nomos_types\s*:/.test(spec)) lines.push(` nomos_types:`, ` path: ${localDart.types}`);
2898
+ const usesClient = /(^|\n)\s+nomos_client\s*:/.test(spec);
2899
+ const usesTypes = /(^|\n)\s+nomos_types\s*:/.test(spec);
2900
+ if (usesClient) lines.push(` nomos_client:`, ` path: ${localDart.client}`);
2901
+ // A generated CLIENT or TEST package may only mention nomos_client directly, but that local
2902
+ // package depends on the simultaneously-developed nomos_types. Pub overrides do not propagate
2903
+ // from dependencies, so override the transitive core at THIS solve root as well. Otherwise a
2904
+ // perfectly ordinary recompile of an older generated package can fail version solving before
2905
+ // analyzer ever sees the code — exactly the package-skew ceremony this local dogfood gate exists
2906
+ // to absorb for tenant developers.
2907
+ if (usesTypes || usesClient) lines.push(` nomos_types:`, ` path: ${localDart.types}`);
2821
2908
  if (lines.length > 1) writeFileSync(path.join(pkg, "pubspec_overrides.yaml"), lines.join("\n") + "\n", "utf8");
2822
2909
  }
2910
+ }
2911
+ let dartMissing = false;
2912
+ let unresolved: string | undefined;
2913
+ for (const pkg of pkgDirs) {
2823
2914
  try {
2824
2915
  execFileSync("dart", ["pub", "get"], { cwd: pkg, stdio: "pipe", encoding: "utf8" });
2825
2916
  } catch (e) {
@@ -2870,20 +2961,23 @@ async function main(): Promise<void> {
2870
2961
  }
2871
2962
  {
2872
2963
  const sum = (pick: (m: (typeof domainModules)[number]) => readonly unknown[] | undefined) =>
2873
- domainModules.reduce((n, m) => n + (pick(m)?.length ?? 0), 0);
2964
+ presentationDomainModules.reduce((n, m) => n + (pick(m)?.length ?? 0), 0);
2874
2965
  console.log(` reads ${sum((m) => m.queries)} quer(y/ies), ${sum((m) => m.counts)} count(s), ${sum((m) => m.sums)} sum(s) — declared (routed via the openapi)`);
2875
2966
  }
2876
2967
  // SECONDARY-CREATE AUTO-DECLARE (secondary_ensure.ts): surface every type the compiler sanctioned
2877
2968
  // from a plan's deterministic secondary `ensure(...)` — the id-mint-gate DX made visible (the author
2878
2969
  // wrote `ensure(B, …)`; the compiler declared `Ensure → B` so the deterministic id admits).
2879
- for (const m of domainModules) {
2970
+ for (const m of presentationDomainModules) {
2880
2971
  for (const ty of m.autoDeclaredEnsures ?? []) {
2881
2972
  console.log(` ensure auto-declared Ensure→${ty} in '${m.domain ?? m.name}' from a plan's secondary ensure(...) (id-mint gate opened)`);
2882
2973
  }
2883
2974
  }
2884
- console.log(` identity ${Object.keys(identity.manifests).length} domain(s) (carried in the package IR + nomos.stable-ids.json; no sidecar files)${identity.excluded.length ? ` — ${identity.excluded.length} EXCLUDED (palette gap)` : ""}`);
2885
- for (const [dom, h] of Object.entries(identity.hashes)) console.log(` ${dom.padEnd(20)} ${h}`);
2886
- for (const ex of identity.excluded) console.log(` EXCLUDED ${ex.domain}: ${ex.reason}`);
2975
+ const presentedIdentityNames = new Set(presentationDomainModules.map((m) => m.domain ?? m.name));
2976
+ const presentedHashes = Object.entries(identity.hashes).filter(([domain]) => presentedIdentityNames.has(domain));
2977
+ const presentedExcluded = identity.excluded.filter((entry) => presentedIdentityNames.has(entry.domain));
2978
+ console.log(` identity ${presentedHashes.length} business domain(s) (private Nomos lifecycle law included but not exposed)${presentedExcluded.length ? ` — ${presentedExcluded.length} EXCLUDED (palette gap)` : ""}`);
2979
+ for (const [dom, h] of presentedHashes) console.log(` ${dom.padEnd(20)} ${h}`);
2980
+ for (const ex of presentedExcluded) console.log(` EXCLUDED ${ex.domain}: ${ex.reason}`);
2887
2981
  // STABLE IDS (#58): the minted/carried identity surface + every INFERRED rename —
2888
2982
  // the silent lane made visible (if an inference is wrong — you meant remove+add —
2889
2983
  // split the edit into two compiles, or answer the evolve gate with a disposition).
@@ -2895,7 +2989,7 @@ async function main(): Promise<void> {
2895
2989
  );
2896
2990
  }
2897
2991
  }
2898
- for (const m of domainModules) {
2992
+ for (const m of presentationDomainModules) {
2899
2993
  if (m.workspaceTypes !== undefined && m.workspaceTypes.length > 0) {
2900
2994
  console.log(
2901
2995
  ` taxonomy ${m.domain ?? m.name}: ${m.workspaceTypes.length} workspace type(s) — homing derived from t.ref chains; birth lanes typed in the client`,
@@ -2920,9 +3014,15 @@ async function main(): Promise<void> {
2920
3014
  // gate's refusal vocabulary, unified into ONE report. `githolon check` runs the same pass;
2921
3015
  // compile runs it implicitly and REFUSES on any refuse-level finding (a statically-provable
2922
3016
  // runtime refusal shipped as law). Warns/notes are printed but never block the compile.
2923
- const checkFindings = checkDomainModules(domainModules);
3017
+ const allCheckFindings = checkDomainModules(domainModules);
3018
+ const isFrameworkFinding = (finding: (typeof allCheckFindings)[number]) =>
3019
+ finding.domain === APPLICATION_HOME_DOMAIN || finding.directiveId?.startsWith("nomos_internal_") === true;
3020
+ const checkFindings = allCheckFindings.filter((finding) => !isFrameworkFinding(finding));
3021
+ const privateRefusals = allCheckFindings.filter(
3022
+ (finding) => isFrameworkFinding(finding) && finding.level === "refuse",
3023
+ );
2924
3024
  if (checkFindings.length > 0) {
2925
- console.log(` check ${checkFindings.length} finding(s) (githolon check):`);
3025
+ console.log(` check ${checkFindings.length} business-law finding(s) (githolon check):`);
2926
3026
  for (const line of formatFindings(checkFindings)) {
2927
3027
  const mark = line.includes("REFUSE") ? "✗" : line.includes("WARN") ? "⚠" : "•";
2928
3028
  console.log(` ${mark} ${line}`);
@@ -2934,6 +3034,16 @@ async function main(): Promise<void> {
2934
3034
  process.exitCode = 1;
2935
3035
  }
2936
3036
  }
3037
+ if (privateRefusals.length > 0) {
3038
+ console.error(
3039
+ ` ✗ Nomos lifecycle self-check failed inside this compiler release. Tenant law is not at fault; ` +
3040
+ `upgrade @githolon/dsl or report the compiler version.`,
3041
+ );
3042
+ if (process.env.NOMOS_DEBUG_FRAMEWORK === "1") {
3043
+ for (const line of formatFindings(privateRefusals)) console.error(` ${line}`);
3044
+ }
3045
+ process.exitCode = 1;
3046
+ }
2937
3047
  if (dartOut !== undefined) {
2938
3048
  if (dartPackages !== undefined) {
2939
3049
  const pkgList = [