@githolon/dsl 0.113.4 → 0.114.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 (58) hide show
  1. package/assets/operation-contract.wasm +0 -0
  2. package/compile_package.mjs +23 -3
  3. package/compile_proof.d.mts +7 -0
  4. package/compile_proof.mjs +38 -3
  5. package/dart/CHANGELOG.md +20 -0
  6. package/dart/lib/src/application_framework_calls.dart +9 -5
  7. package/dart/lib/src/generated_call.dart +14 -3
  8. package/dart/lib/src/nms_generated_runtime.dart +7 -0
  9. package/dart/lib/src/node_runner_transport.dart +77 -8
  10. package/dart/lib/src/nomos_trace.dart +107 -0
  11. package/dart/lib/src/reception/dart_reception.dart +148 -11
  12. package/dart/lib/src/rows_watch.dart +390 -0
  13. package/dart/lib/src/runner/node-entry.mjs +2803 -690
  14. package/dart/lib/src/sync_status_projection.dart +94 -1
  15. package/dart/lib/src/sync_status_reducer.dart +5 -2
  16. package/dart/lib/src/test_harness.dart +107 -41
  17. package/dart/lib/src/testing/test_peer.dart +107 -99
  18. package/dart/lib/src/testing/test_peer_host.dart +305 -0
  19. package/dart/lib/src/typed_watch_registry.dart +26 -3
  20. package/dart/pubspec.lock +1 -1
  21. package/dart/pubspec.yaml +2 -2
  22. package/dart/test/background_model_adoption_test.dart +1 -0
  23. package/dart/test/custodian_warming_test.dart +105 -0
  24. package/dart/test/fixtures/offer_watch_order_peer.dart +74 -0
  25. package/dart/test/kernel_author_after_read_test.dart +6 -3
  26. package/dart/test/kernel_fence_test.dart +4 -1
  27. package/dart/test/model_adoption_test.dart +17 -0
  28. package/dart/test/nomos_trace_test.dart +91 -0
  29. package/dart/test/own_offer_watch_order_test.dart +90 -0
  30. package/dart/test/receiving_group_open_test.dart +4 -8
  31. package/dart/test/replica_readiness_test.dart +14 -4
  32. package/dart/test/rows_watch_test.dart +331 -0
  33. package/dart/test/test_peer_host_selection_test.dart +49 -0
  34. package/dart_types/CHANGELOG.md +15 -0
  35. package/dart_types/lib/src/approved_model.dart +22 -5
  36. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +535 -1
  37. package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +65 -0
  38. package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +14 -0
  39. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +359 -2
  40. package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +65 -0
  41. package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +20 -2
  42. package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +278 -9
  43. package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +7 -85
  44. package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +35 -0
  45. package/dart_types/pubspec.yaml +1 -1
  46. package/package.json +4 -2
  47. package/src/build_provenance.ts +7 -2
  48. package/src/codegen_dart_client.ts +21 -9
  49. package/src/codegen_dart_facade.ts +1 -0
  50. package/src/codegen_dart_framework_calls.ts +12 -9
  51. package/src/codegen_dart_scenario.ts +7 -0
  52. package/src/codegen_ts.ts +26 -24
  53. package/src/compile_package_main.ts +65 -61
  54. package/src/engine_entry.ts +4 -48
  55. package/src/law_unit_set_identity.ts +44 -0
  56. package/src/query/compile.ts +0 -0
  57. package/src/release_inspection.ts +24 -0
  58. package/src/release_publication.ts +298 -0
@@ -251,10 +251,6 @@ import {
251
251
  tsClientFactoryName,
252
252
  recursiveLazyTypes,
253
253
  } from "./codegen_ts.js";
254
- import {
255
- generateOpenApiFragment,
256
- mergeOpenApiFragments,
257
- } from "./codegen_openapi.js";
258
254
  import {
259
255
  domainInterfaceFromLawGraph,
260
256
  publicClientInterface,
@@ -269,6 +265,7 @@ import {
269
265
  packageRoutesLayerUsda,
270
266
  splitLawUnits,
271
267
  } from "./law_units.js";
268
+ import { lawUnitSetIdentityWith } from "./law_unit_set_identity.js";
272
269
  import { checkDomainModules, formatFindings, hasRefusal } from "./law_check.js";
273
270
  import {
274
271
  currentEvolveLaw,
@@ -2145,10 +2142,16 @@ function emitDartOperationProto(
2145
2142
  return [protoPath, generatedBinding];
2146
2143
  }
2147
2144
 
2145
+ /** The compiled law's identities (CAP-773): the unit set is the law; the package is the `.law.usdz` bytes. */
2146
+ interface CompiledLawIdentities {
2147
+ readonly unitSet: string;
2148
+ readonly packageSha256: string;
2149
+ }
2150
+
2148
2151
  function emitDart(
2149
2152
  modules: readonly DomainModule[],
2150
2153
  dartOut: string,
2151
- domainHash: string,
2154
+ law: CompiledLawIdentities,
2152
2155
  lawGraphs: ReadonlyMap<string, LawGraph>,
2153
2156
  lawPaths: ReadonlyMap<string, ReadonlyMap<string, string>>,
2154
2157
  packageInterface: Record<string, DomainInterface> = {},
@@ -2224,7 +2227,7 @@ function emitDart(
2224
2227
  ? []
2225
2228
  : [{ module, graph, paths }];
2226
2229
  }),
2227
- { lawSha256: domainHash }
2230
+ { lawUnitSet: law.unitSet, lawPackageSha256: law.packageSha256 }
2228
2231
  );
2229
2232
  if (applicationFrameworkCallsSource !== undefined) {
2230
2233
  const applicationFrameworkCallsPath = path.join(
@@ -2364,7 +2367,7 @@ function emitDart(
2364
2367
  writeGenerated(mod.name, src);
2365
2368
 
2366
2369
  const clientSrc = generateDartDomainClient(modForDart, {
2367
- domainHash,
2370
+ domainHash: law.unitSet,
2368
2371
  callUsda: {
2369
2372
  graph: lawGraphs.get(mod.name)!,
2370
2373
  paths: lawPaths.get(mod.name)!,
@@ -2539,12 +2542,12 @@ const dartTypesImportFor = (generatedSource: string): string => {
2539
2542
  * just the floor generated code needs — a lagging pin forces tenants into dependency_overrides. */
2540
2543
  // 0.3.0: SurfacedValue<T>/SurfacedObservation/PendingConflictRow — generated Surfaced-field typing +
2541
2544
  // the pendingConflicts read rows.
2542
- const NOMOS_TYPES_DEP = "^0.12.0";
2545
+ const NOMOS_TYPES_DEP = "^0.13.0";
2543
2546
  // 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
2544
2547
  // DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
2545
2548
  // REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
2546
- const NOMOS_CLIENT_DEP = "^0.67.2";
2547
- const NOMOS_FLUTTER_DEP = "^0.66.4";
2549
+ const NOMOS_CLIENT_DEP = "^0.68.0";
2550
+ const NOMOS_FLUTTER_DEP = "^0.67.0";
2548
2551
  // DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in the Dart and Flutter
2549
2552
  // package pubspecs — a release without this bump fails the suite, so tenants never
2550
2553
  // land back in dependency_overrides (the exact co2 report this closes, third occurrence).
@@ -3282,7 +3285,7 @@ function removeGeneratedClientLawAssets(pkgDir: string): boolean {
3282
3285
  function emitDartPackages(
3283
3286
  modules: readonly DomainModule[],
3284
3287
  dartOut: string,
3285
- domainHash: string,
3288
+ law: CompiledLawIdentities,
3286
3289
  lawGraphs: ReadonlyMap<string, LawGraph>,
3287
3290
  lawPaths: ReadonlyMap<string, ReadonlyMap<string, string>>,
3288
3291
  packageInterface: Record<string, DomainInterface>,
@@ -3677,7 +3680,7 @@ function emitDartPackages(
3677
3680
  ? []
3678
3681
  : [{ module, graph, paths }];
3679
3682
  }),
3680
- { lawSha256: domainHash }
3683
+ { lawUnitSet: law.unitSet, lawPackageSha256: law.packageSha256 }
3681
3684
  );
3682
3685
  const workspaceAccessReadImports = new Map<string, string>();
3683
3686
  for (const module of operationProtoModules) {
@@ -3727,7 +3730,7 @@ function emitDartPackages(
3727
3730
  const workspaceAccessReadImportUri = workspaceAccessReadImports.get(mod.name);
3728
3731
  const makeClient = (imports: readonly string[]) =>
3729
3732
  generateDartDomainClient(dd.modForDart, {
3730
- domainHash,
3733
+ domainHash: law.unitSet,
3731
3734
  callUsda: {
3732
3735
  graph: lawGraphs.get(mod.name)!,
3733
3736
  paths: lawPaths.get(mod.name)!,
@@ -3818,7 +3821,7 @@ function emitDartPackages(
3818
3821
  invitationPolicies: (
3819
3822
  dd.modForDart.applicationWorkspaceSharing ?? []
3820
3823
  ).map((policy) => ({
3821
- domainHash,
3824
+ domainHash: law.unitSet,
3822
3825
  query: policy.invitationLaw.pendingByRecipient.id,
3823
3826
  redeem: policy.invitationLaw.redeem.id,
3824
3827
  personField: policy.person,
@@ -3858,6 +3861,7 @@ function emitDartPackages(
3858
3861
  `export 'package:${packages.types}/${genRel}/${businessSharedFile}';`,
3859
3862
  `export 'package:nomos_client/src/workspace_open_progress.dart' show ApplicationWorkspaceOpenProgress;`,
3860
3863
  `export 'package:nomos_client/src/write_progress.dart' show NomosWrite, NomosWriteProgress, NomosWritePhase;`,
3864
+ `export 'package:nomos_client/src/rows_watch.dart' show NomosRows, NomosRowProjection, NomosRowsProjectionStream;`,
3861
3865
  `export 'package:nomos_types/nomos_types.dart' show CustodyError, CustodyPhase, CustodySource, NomosRecommendedAction;`,
3862
3866
  ...tenantTypesExportKeys
3863
3867
  .map((file) => {
@@ -5539,15 +5543,6 @@ async function main(): Promise<void> {
5539
5543
  finding.directiveId?.startsWith("nomos_internal_") === true ||
5540
5544
  (finding.directiveId !== undefined &&
5541
5545
  frameworkComposedDirectives.has(`${finding.domain}\0${finding.directiveId}`));
5542
- // Self-description is a compiler-owned presentation projection, not executable law. Build it
5543
- // once from the same composed modules and place it only in the separately routed describe plan;
5544
- // exact offer/read plans must not carry OpenAPI generation and Zod JSON-Schema machinery.
5545
- const runtimeOpenApiDocument = mergeOpenApiFragments(
5546
- domainModules.map((module) =>
5547
- generateOpenApiFragment(module, { includeFrameworkRoutes: true })
5548
- ),
5549
- { title: `${cfg.name} (nomos package)` }
5550
- );
5551
5546
 
5552
5547
  if (preflightFlag) {
5553
5548
  const businessFindings = authorTimeFindings.filter(
@@ -6607,7 +6602,6 @@ async function main(): Promise<void> {
6607
6602
  const planEntrySource = (
6608
6603
  domainKeys: readonly string[],
6609
6604
  includeReports: boolean,
6610
- includeDescribe: boolean,
6611
6605
  sourceOverrides: ReadonlyMap<string, string> = new Map(),
6612
6606
  includePreloads = true,
6613
6607
  routingOverride?: Record<string, unknown>,
@@ -6659,12 +6653,6 @@ async function main(): Promise<void> {
6659
6653
  ...(!sharedRuntime && zodSeedNames.length > 0
6660
6654
  ? [` seeds: [${zodSeedNames.join(", ")}],`]
6661
6655
  : []),
6662
- ...(includeDescribe
6663
- ? [` openapiDocument: ${JSON.stringify(runtimeOpenApiDocument)},`]
6664
- : []),
6665
- ...(includeDescribe
6666
- ? [` packageTitle: ${JSON.stringify(`${cfg.name} (nomos package)`)},`]
6667
- : []),
6668
6656
  ...workspaceEffects.config,
6669
6657
  `});`,
6670
6658
  ...(includeMintInstalls
@@ -6841,7 +6829,6 @@ async function main(): Promise<void> {
6841
6829
  const compilePlan = async (
6842
6830
  domainKeys: readonly string[],
6843
6831
  includeReports: boolean,
6844
- includeDescribe: boolean,
6845
6832
  options: {
6846
6833
  readonly sourceOverrides?: ReadonlyMap<string, string>;
6847
6834
  readonly sliceTargets?: readonly PlanSourceSliceTarget[];
@@ -6880,7 +6867,6 @@ async function main(): Promise<void> {
6880
6867
  const signature = JSON.stringify({
6881
6868
  domains: ordered,
6882
6869
  reports: includeReports,
6883
- describe: includeDescribe,
6884
6870
  slice:
6885
6871
  effectiveSliceTargets?.map((target) => [
6886
6872
  target.modulePath,
@@ -6895,7 +6881,6 @@ async function main(): Promise<void> {
6895
6881
  planEntrySource(
6896
6882
  ordered,
6897
6883
  includeReports,
6898
- includeDescribe,
6899
6884
  options.sourceOverrides,
6900
6885
  options.includePreloads ?? options.sliceTargets === undefined,
6901
6886
  options.routingOverride,
@@ -7035,14 +7020,9 @@ async function main(): Promise<void> {
7035
7020
  const routePlan = async (
7036
7021
  selector: string,
7037
7022
  domainKeys: readonly string[],
7038
- includeReports = false,
7039
- includeDescribe = false
7023
+ includeReports = false
7040
7024
  ): Promise<void> => {
7041
- const asset = await compilePlan(
7042
- domainKeys,
7043
- includeReports,
7044
- includeDescribe
7045
- );
7025
+ const asset = await compilePlan(domainKeys, includeReports);
7046
7026
  planRoutes.push({ selector, planSha256: asset.sha256 });
7047
7027
  };
7048
7028
  const routeAsset = (selector: string, asset: UsdPlanAsset): void => {
@@ -7086,7 +7066,7 @@ async function main(): Promise<void> {
7086
7066
  // content hash, and emitted sorted by selector/hash), so compile them concurrently and bind
7087
7067
  // routes afterwards in declaration order.
7088
7068
  const domainPlans = await Promise.all(
7089
- orderedDomainKeys.map((key) => compilePlan([key], false, false))
7069
+ orderedDomainKeys.map((key) => compilePlan([key], false))
7090
7070
  );
7091
7071
  orderedDomainKeys.forEach((key, index) => {
7092
7072
  const asset = domainPlans[index]!;
@@ -7158,7 +7138,7 @@ async function main(): Promise<void> {
7158
7138
  const source = ` ${JSON.stringify(
7159
7139
  BUSINESS_BIRTH_FRAMEWORK_PLAN_DOMAIN
7160
7140
  )}: [${selectedSources.join(", ")}],`;
7161
- frameworkPlan = compilePlan([domain], false, false, {
7141
+ frameworkPlan = compilePlan([domain], false, {
7162
7142
  sourceOverrides: new Map([[domain, source]]),
7163
7143
  includePreloads: false,
7164
7144
  cacheKey: "business-birth-framework",
@@ -7228,7 +7208,7 @@ async function main(): Promise<void> {
7228
7208
  selectedModule,
7229
7209
  ...generated,
7230
7210
  ].join(", ")}],`;
7231
- const asset = await compilePlan([domain], false, false, {
7211
+ const asset = await compilePlan([domain], false, {
7232
7212
  sourceOverrides: new Map([[domain, source]]),
7233
7213
  sliceTargets: selected,
7234
7214
  });
@@ -7386,7 +7366,7 @@ async function main(): Promise<void> {
7386
7366
  )}: [${moduleExpression}],`;
7387
7367
  workspaceInvariantPlans.push({
7388
7368
  invariantIds: members.map(([id]) => id),
7389
- plan: compilePlan([domain], false, false, {
7369
+ plan: compilePlan([domain], false, {
7390
7370
  sourceOverrides: new Map([[domain, domainSource]]),
7391
7371
  sliceTargets: [...selectedByKey.values()],
7392
7372
  cacheKey: `workspace-invariants:${directiveId}:${members
@@ -7403,9 +7383,11 @@ async function main(): Promise<void> {
7403
7383
  }
7404
7384
  }
7405
7385
  stage("workspace invariant plans");
7406
- const reportRoutes: Promise<void>[] = [
7407
- routePlan("describe", orderedDomainKeys, false, true),
7408
- ];
7386
+ // No `describe` route: the kernel derives a workspace's description from its installed law
7387
+ // (rpc_describe renders it from the command contract and read projection), so a compiled
7388
+ // self-description plan over every domain was never executed, and it was the one composition
7389
+ // file a single-domain edit moved (CAP-658).
7390
+ const reportRoutes: Promise<void>[] = [];
7409
7391
  for (const reportId of Object.keys(cfg.reports ?? {}))
7410
7392
  reportRoutes.push(routePlan(`report:report:${reportId}`, [], true));
7411
7393
  const routeTable = (
@@ -7447,7 +7429,7 @@ async function main(): Promise<void> {
7447
7429
  }
7448
7430
  }
7449
7431
  await Promise.all(reportRoutes);
7450
- stage("describe + report plans");
7432
+ stage("report plans");
7451
7433
  // Adaptive offer splitting deliberately compiles candidate parents before retaining their children.
7452
7434
  // Only route-reachable content is custody; exploratory compiler work never becomes law bytes.
7453
7435
  const routedPlanHashes = new Set(planRoutes.map((route) => route.planSha256));
@@ -7934,8 +7916,10 @@ async function main(): Promise<void> {
7934
7916
  if (probeFailure !== undefined) fail(probeFailure);
7935
7917
  }
7936
7918
  stage("boot probe (verdict)");
7937
- // The composed USDA closure is the law. Generated clients, lifecycle rows and
7938
- // compatibility metadata all bind this identity, never the retired flattened envelope.
7919
+ // The PACKAGE identity: this compiler's own closure of the package (root.usda, routes and its derived
7920
+ // finished goods included). It names the `.law.usdz` bytes and the package lane; it is NOT what a
7921
+ // unit-lane target serves (that target's kernel composes the units itself, CAP-773). The law
7922
+ // identity is the unit set identity below.
7939
7923
  const domainHash = lawClosure.identity;
7940
7924
  const rootAsset = lawClosure.assets.find(
7941
7925
  (asset) => asset.logicalPath === lawClosure.rootLogicalPath
@@ -7949,11 +7933,14 @@ async function main(): Promise<void> {
7949
7933
  .map((asset) => [`layers/${asset.logicalPath}`, asset.bytes] as const),
7950
7934
  ]);
7951
7935
  writeFileSync(lawUsdzPath, lawUsdz);
7952
- // The compiled law's identity on its own line, for release gates that need the hash and nothing else.
7936
+ // The package closure's identity on its own line, for gates that check the `.law.usdz` bytes.
7953
7937
  writeFileSync(path.join(outDir, `${cfg.name}.law.sha256`), `${domainHash}\n`, "utf8");
7954
7938
  // D21: the same law, authored as per-domain units the kernel composes. Split from the closure just
7955
7939
  // emitted (its bytes and identity are untouched), previewed for exact coverage, and written as plain
7956
7940
  // trees: in custody each unit is a git tree, so git owns compression and deduplication.
7941
+ // THE LAW IDENTITY (CAP-773): the unit set identity of the units below, the kernel's own definition
7942
+ // (law_unit_set_identity.ts). Releases, generated clients and qualification name the law by it.
7943
+ let unitSetIdentity: string;
7957
7944
  {
7958
7945
  const lawUnits = (() => {
7959
7946
  try {
@@ -7975,6 +7962,12 @@ async function main(): Promise<void> {
7975
7962
  }
7976
7963
  writeFileSync(path.join(unitPath, LAW_UNIT_MANIFEST), unit.manifest);
7977
7964
  }
7965
+ unitSetIdentity = lawUnitSetIdentityWith(
7966
+ (bytes) => createHash("sha256").update(bytes).digest("hex"),
7967
+ lawUnits.map((unit) => ({ key: unit.key, unitHash: unit.hash }))
7968
+ );
7969
+ // The law identity on its own line, for publishers and gates that need it and nothing else.
7970
+ writeFileSync(path.join(outDir, `${cfg.name}.units.sha256`), `${unitSetIdentity}\n`, "utf8");
7978
7971
  }
7979
7972
  const layeredRootPath = path.join(
7980
7973
  lawClosureAssetsPath,
@@ -8133,6 +8126,7 @@ async function main(): Promise<void> {
8133
8126
  package: cfg.name,
8134
8127
  // Binds the summary to the published package asset consumed by an application stage.
8135
8128
  sourceIdentity: composedUsdIdentity!,
8129
+ unitSetIdentity,
8136
8130
  domainHash,
8137
8131
  lawClosureIdentity: lawClosure.identity,
8138
8132
  domains: presentationDomainModules.map((m) => ({
@@ -8168,7 +8162,9 @@ async function main(): Promise<void> {
8168
8162
 
8169
8163
  const summaryTxt = [
8170
8164
  `${cfg.name} — compiled Nomos domain package`,
8171
- `law (domainHash): ${domainHash} # composed typed USDA closure identity`,
8165
+ `law (unit set): ${unitSetIdentity} # THE law identity: ${cfg.name}.units/, as releases, clients and qualification name it (${cfg.name}.units.sha256)`,
8166
+ `package closure: ${domainHash} # package — not what targets serve: this compiler's closure of ${cfg.name}.law.usdz (${cfg.name}.law.sha256)`,
8167
+ `served law: githolon domains status <ws> # each target's kernel composes the units into its own closure (it depends on that kernel's composer)`,
8172
8168
  `law transport: ${path.basename(
8173
8169
  lawUsdzPath
8174
8170
  )} # lossless one-file USDZ envelope; not canonical custody`,
@@ -8222,6 +8218,7 @@ async function main(): Promise<void> {
8222
8218
  const provenance = buildProvenanceStatement({
8223
8219
  packageName: cfg.name,
8224
8220
  domainHash,
8221
+ unitSetIdentity,
8225
8222
  artifacts: [
8226
8223
  { name: path.basename(lawUsdzPath) },
8227
8224
  {
@@ -8272,7 +8269,8 @@ async function main(): Promise<void> {
8272
8269
  );
8273
8270
  const clientOptions = {
8274
8271
  packageName: cfg.name,
8275
- domainHash,
8272
+ // THE LAW the generated clients name: the unit set (CAP-773), never the package closure.
8273
+ domainHash: unitSetIdentity,
8276
8274
  lawAssetName: `${cfg.name}.law.usdz`,
8277
8275
  interface: packageInterface,
8278
8276
  kernelContracts,
@@ -8380,10 +8378,9 @@ async function main(): Promise<void> {
8380
8378
  writeKeyProofUsda(file: string, format: string, key: string): void;
8381
8379
  };
8382
8380
  const declarationProofFormat = "nomos.typescript-declaration-proof.v1";
8383
- const normalizedClientSource = clientSource.replaceAll(
8384
- domainHash,
8385
- "<nomos-composed-law-identity>"
8386
- );
8381
+ const normalizedClientSource = clientSource
8382
+ .replaceAll(unitSetIdentity, "<nomos-law-unit-set-identity>")
8383
+ .replaceAll(domainHash, "<nomos-package-identity>");
8387
8384
  const declarationInputIdentity = createHash("sha256")
8388
8385
  .update(`${declarationProofFormat}\0`)
8389
8386
  .update(normalizedClientSource)
@@ -8863,7 +8860,7 @@ async function main(): Promise<void> {
8863
8860
  const r = emitDartPackages(
8864
8861
  presentationDomainModules,
8865
8862
  dartOut,
8866
- domainHash,
8863
+ { unitSet: unitSetIdentity, packageSha256: domainHash },
8867
8864
  typedLawGraphs,
8868
8865
  typedLawPathsByName,
8869
8866
  packageInterface,
@@ -8891,7 +8888,7 @@ async function main(): Promise<void> {
8891
8888
  dartFiles = emitDart(
8892
8889
  presentationDomainModules,
8893
8890
  dartOut,
8894
- domainHash,
8891
+ { unitSet: unitSetIdentity, packageSha256: domainHash },
8895
8892
  typedLawGraphs,
8896
8893
  typedLawPathsByName,
8897
8894
  packageInterface,
@@ -9274,10 +9271,11 @@ async function main(): Promise<void> {
9274
9271
  // ── summary ──
9275
9272
  const rel = (p: string) => path.relative(process.cwd(), p);
9276
9273
  console.log(`nomos-compile: ${cfg.name}`);
9274
+ console.log(` law unit set ${unitSetIdentity} (${rel(path.join(outDir, `${cfg.name}.units`))})`);
9277
9275
  console.log(
9278
- ` law ${rel(lawUsdzPath)} (${lawUsdz.byteLength} transport bytes)`
9276
+ ` package ${domainHash} — package — not what targets serve (${rel(lawUsdzPath)}, ${lawUsdz.byteLength} transport bytes)`
9279
9277
  );
9280
- console.log(` domainHash ${domainHash}`);
9278
+ console.log(` served githolon domains status <ws> — each target's kernel composes the units into its own closure`);
9281
9279
  console.log(
9282
9280
  custodyPlacementDeclarations.length > 0
9283
9281
  ? ` placement ${rel(custodyPlacementDeclarationsPath)} (${
@@ -9506,6 +9504,7 @@ async function main(): Promise<void> {
9506
9504
  dslDir: string;
9507
9505
  configPath: string;
9508
9506
  argumentIdentity: string;
9507
+ importedSources?: readonly string[];
9509
9508
  }): unknown;
9510
9509
  generatedOutputFiles(
9511
9510
  outDir: string,
@@ -9525,6 +9524,11 @@ async function main(): Promise<void> {
9525
9524
  dslDir: DSL_DIR,
9526
9525
  configPath,
9527
9526
  argumentIdentity: compileArgumentIdentity,
9527
+ importedSources: [
9528
+ ...((globalThis as Record<symbol, unknown>)[
9529
+ Symbol.for("nomos.compile.importedSources")
9530
+ ] as ReadonlySet<string> | undefined ?? []),
9531
+ ],
9528
9532
  });
9529
9533
  const outputs = proof.generatedOutputFiles(outDir, [
9530
9534
  lockPath,
@@ -73,7 +73,6 @@ import {
73
73
  nomosSourceFrontierEnsure,
74
74
  withAttestedReadExports,
75
75
  } from "./attested.js";
76
- import type { OpenApiDoc } from "./codegen_openapi.js";
77
76
  import type { Directive } from "./directive.js";
78
77
  import type { AggregateHandle, AggregateInvariantFn, AggregateInvariantVerdict } from "./aggregate.js";
79
78
  import type { Ports } from "./ctx.js";
@@ -178,18 +177,6 @@ export interface EngineEntryConfig {
178
177
  * `sideEffects: false` (a bare side-effect import would be tree-shaken away).
179
178
  */
180
179
  readonly seeds?: readonly unknown[];
181
- /**
182
- * Compiler-derived self-description. It is a presentation projection held inside the separately
183
- * routed `describe` plan, never executable law IR. Ordinary offer/read plans omit it completely,
184
- * so OpenAPI/JSON-Schema generation cannot sit on their parse or readiness flame path.
185
- */
186
- readonly openapiDocument?: OpenApiDoc;
187
- /**
188
- * The package's OpenAPI document title (the compiler's `"<name> (nomos package)"`). Baked into
189
- * the lump so `describe` reproduces the compiler's public description byte-for-byte without
190
- * the kernel knowing the package name. The kernel MAY override it (the workspace-level title).
191
- */
192
- readonly packageTitle?: string;
193
180
  /** Compiler-generated nominal encoders keyed by the exact target Directive singleton. Source law
194
181
  * can name a target by type, while the runtime emits only canonical USDA. */
195
182
  readonly workspaceEffectTargets?: readonly {
@@ -297,7 +284,7 @@ function mergeModules(mods: readonly DomainModuleExports[], domain: string): Dom
297
284
  // without per-piece re-exports (capability_exports.ts).
298
285
  // METERED BIRTH: a `.births()`-declaring domain gains the birth-meter structural exports
299
286
  // (the sanctioning `nomosBirthRecordEnsure` + the meter aggregates) at this same seam, so
300
- // the registry + `describe()` see the exact surface the composed manifest carries.
287
+ // the registry sees the exact surface the composed manifest carries.
301
288
  // ROLE SUGAR: lift every `role(...).emit()` into its grant/revoke/seed directives at the SAME
302
289
  // engine merge point so the registry dispatches them (the compiler saw them via the same expander).
303
290
  return expandRoleExports(
@@ -650,8 +637,6 @@ export interface RegisteredEngine {
650
637
  prepareDomain(domain: string): { complete: boolean; initialized: number; total: number };
651
638
  /** Compiler/tooling probe: force one domain and prove an exact routed offer is registered. */
652
639
  hasDirective(domain: string, directiveId: string): boolean;
653
- /** The G1 self-describe verb: the lump's own merged OpenAPI, derived from its modules. */
654
- describe(opts?: { lawHash?: string; title?: string }): OpenApiDoc;
655
640
  }
656
641
 
657
642
  /**
@@ -1155,42 +1140,13 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
1155
1140
  };
1156
1141
  }
1157
1142
 
1158
- /**
1159
- * THE SELF-DESCRIBE VERB (G1) — the lump's OWN OpenAPI, derived from the SAME modules it
1160
- * dispatches. The kernel selects the content-addressed describe plan from the native USD
1161
- * package in custody, so the workspace's API surface is a pure,
1162
- * deterministic function of installed law — NOT a host-staged blob. Its public `paths` reproduce
1163
- * the public description the compiler derived; describe also carries a private, law-derived
1164
- * framework routing manifest which the host consumes internally and never publishes.
1165
- * `opts.lawHash`/`opts.title` are injected by the kernel at describe-time (the version stamp +
1166
- * package title), matching the compiler's `domainHash`/`"<name> (nomos package)"`.
1167
- */
1168
- function describe(opts?: { lawHash?: string; title?: string }): OpenApiDoc {
1169
- const document = config.openapiDocument;
1170
- if (document === undefined) {
1171
- throw new Error("engine describe: selected plan has no compiled OpenAPI projection");
1172
- }
1173
- const lawHash = opts?.lawHash;
1174
- const title = opts?.title ?? config.packageTitle ?? document.info["title"];
1175
- const priorInfo = document.info;
1176
- const info: Record<string, unknown> = {
1177
- ...priorInfo,
1178
- ...(title === undefined ? {} : { title }),
1179
- version: lawHash === undefined ? (priorInfo["version"] ?? "0") : lawHash.slice(0, 12),
1180
- };
1181
- if (lawHash === undefined) delete info["x-nomos-law-hash"];
1182
- else info["x-nomos-law-hash"] = lawHash;
1183
- return { ...document, info };
1184
- }
1185
-
1186
1143
  // The engine evals the bundle as a classic script and FREEZES globalThis before
1187
- // dispatch — assign all entries NOW, at top-level eval. `describe` is the G1 self-
1188
- // describe verb (the kernel `describe` run mode evals `globalThis.describe(opts)`).
1144
+ // dispatch — assign all entries NOW, at top-level eval. There is no `describe` entry: the
1145
+ // kernel derives a workspace's description from its installed law (CAP-658).
1189
1146
  (globalThis as { plan?: unknown }).plan = plan;
1190
1147
  (globalThis as { planReport?: unknown }).planReport = planReport;
1191
1148
  (globalThis as { __nomosPrepareDomain?: unknown }).__nomosPrepareDomain = prepareDomain;
1192
1149
  (globalThis as { __nomosHasDirective?: unknown }).__nomosHasDirective = hasDirective;
1193
- (globalThis as { describe?: unknown }).describe = describe;
1194
1150
 
1195
- return { plan, planReport, prepareDomain, hasDirective, describe } as RegisteredEngine;
1151
+ return { plan, planReport, prepareDomain, hasDirective } as RegisteredEngine;
1196
1152
  }
@@ -0,0 +1,44 @@
1
+ // THE LAW IDENTITY (CAP-773, architecture/law_units_format.md "Identities").
2
+ //
3
+ // A law is a set of content-addressed units (framework, composition, one per domain). Its identity is the unit set
4
+ // identity: sha256("nomos-law-unit-set-identity-v1\0" { key "\0" unitHash "\0" }*), units sorted by key in byte
5
+ // order. It names exactly which units the set holds and nothing about how a kernel composes them, so publishers,
6
+ // generated clients, the console and qualification all name law by it, and every kernel agrees on it whatever its
7
+ // composer version. The kernel's definition is `law_unit_set_identity` (kernel/src/law_units.rs); the two are bound
8
+ // by one vector (law_unit_set_identity.test.ts, law_units.rs tests) and by a compiled fixture the real kernel composes
9
+ // (kernel/tests/compiled_law_units.rs).
10
+ //
11
+ // Not the law identity: the realised closure a target's kernel composes from the set (it also depends on the
12
+ // kernel's OpenUSD pin and composer version), the compiler's package closure (`build/<name>.law.sha256`), and the
13
+ // kernel's composition identity (the set record's `setIdentity`, which binds the set to one composer).
14
+ //
15
+ // Dependency-free and synchronous over the bytes: each runtime hashes the preimage with its own sha256 (node:crypto
16
+ // in the compiler, WebCrypto in the console and CLI, the Peer Office's own digest on a device).
17
+
18
+ export const LAW_UNIT_SET_IDENTITY_TAG = "nomos-law-unit-set-identity-v1";
19
+
20
+ export interface LawUnitSetMember {
21
+ readonly key: string;
22
+ readonly unitHash: string;
23
+ }
24
+
25
+ const HEX64 = /^[0-9a-f]{64}$/;
26
+ const byteOrder = (left: string, right: string): number => (left < right ? -1 : left > right ? 1 : 0);
27
+
28
+ /** The exact bytes the unit set identity hashes. Refuses a duplicate key or a unit hash that is not a sha256. */
29
+ export function lawUnitSetIdentityPreimage(units: readonly LawUnitSetMember[]): Uint8Array {
30
+ const sorted = [...units].sort((left, right) => byteOrder(left.key, right.key));
31
+ const parts = [`${LAW_UNIT_SET_IDENTITY_TAG}\0`];
32
+ sorted.forEach((unit, index) => {
33
+ if (unit.key.length === 0 || unit.key.includes("\0")) throw new Error(`law unit key ${JSON.stringify(unit.key)} is not a unit key`);
34
+ if (index > 0 && sorted[index - 1]!.key === unit.key) throw new Error(`law unit ${unit.key} appears twice in one set`);
35
+ if (!HEX64.test(unit.unitHash)) throw new Error(`law unit ${unit.key} names ${JSON.stringify(unit.unitHash)}, not a unit hash`);
36
+ parts.push(unit.key, "\0", unit.unitHash, "\0");
37
+ });
38
+ return new TextEncoder().encode(parts.join(""));
39
+ }
40
+
41
+ /** The unit set identity, hashed with the caller's synchronous sha256 (lowercase hex out). */
42
+ export function lawUnitSetIdentityWith(sha256Hex: (bytes: Uint8Array) => string, units: readonly LawUnitSetMember[]): string {
43
+ return sha256Hex(lawUnitSetIdentityPreimage(units));
44
+ }
Binary file
@@ -145,3 +145,27 @@ export function inspectReleaseBaseline(input: { identity: string; units: string;
145
145
  'Unobserved declarations may be out of package scope or structurally interned; absence is not a compatibility refusal.',
146
146
  'Consumer compatibility, state evolution and publication provenance are not certified by this report.'] };
147
147
  }
148
+
149
+ /**
150
+ * A compiled `build/<name>.units` directory as a release candidate and the bytes to upload: every unit
151
+ * verified by `readInspectionUnits` (digests, canonical manifest, unit hash), every file named by digest.
152
+ * The console verifies the same bytes again on publication (release_publication.ts `verifyRelease`).
153
+ */
154
+ export function releaseCandidateFromUnits(directory: string, series: string, predecessor: string): {
155
+ candidate: { series: string; predecessor: string; units: { key: string; unitHash: string; files: { path: string; sha256: string; size: number }[] }[] };
156
+ blobs: Map<string, Uint8Array>;
157
+ } {
158
+ const blobs = new Map<string, Uint8Array>();
159
+ const units = readInspectionUnits(directory).map((unit) => {
160
+ const files = [...unit.assets.map((asset) => ({ path: asset.logicalPath, bytes: asset.bytes })),
161
+ { path: 'unit.usda', bytes: unit.manifest }]
162
+ .sort((l, r) => (l.path < r.path ? -1 : l.path > r.path ? 1 : 0))
163
+ .map(({ path: filePath, bytes }) => {
164
+ const sha256 = sha(bytes);
165
+ blobs.set(sha256, new Uint8Array(bytes));
166
+ return { path: filePath, sha256, size: bytes.byteLength };
167
+ });
168
+ return { key: unit.key, unitHash: unit.hash, files };
169
+ }).sort((l, r) => (l.key < r.key ? -1 : l.key > r.key ? 1 : 0));
170
+ return { candidate: { series, predecessor, units }, blobs };
171
+ }