@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
@@ -54,10 +54,16 @@ export const CURRENT_LAW_PREFIX = "nomos:current-law:";
54
54
 
55
55
  export interface BusinessBirthMetadata {
56
56
  readonly aggregateId: string;
57
+ /** Owning law domain for closure-safe cross-workspace intent linking. */
58
+ readonly domain: string;
57
59
  readonly publicPayloadSchema: z.ZodObject<z.ZodRawShape>;
58
60
  readonly rootId: string;
59
61
  readonly namespace: string;
60
62
  readonly keyField: string;
63
+ /** Exact child-genesis offer route prepared before the parent birth dispatch. */
64
+ readonly seedDirectiveId: string;
65
+ /** Compiler-owned child-genesis routes which may be appended after the business seed. */
66
+ readonly genesisDirectiveIds: readonly string[];
61
67
  /** Private proof/lowering policy; omitted from every generated tenant payload. */
62
68
  readonly founderRoles: readonly string[];
63
69
  readonly founderScope: string;
@@ -69,6 +75,7 @@ export interface BusinessBirthDirective extends Directive<Record<string, unknown
69
75
 
70
76
  type BusinessBirthInternalDirective<P = Record<string, unknown>> = Directive<P> & {
71
77
  readonly businessBirthInternal: true;
78
+ readonly businessBirthSeed?: true;
72
79
  };
73
80
 
74
81
  function internalDirective<P>(d: Directive<P>): BusinessBirthInternalDirective<P> {
@@ -189,10 +196,12 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
189
196
  }),
190
197
  ),
191
198
  ];
192
- });
199
+ })
200
+ .alsoEnsures(RelationTuple);
193
201
  const seed = {
194
202
  ...seedBase,
195
203
  businessBirthInternal: true,
204
+ businessBirthSeed: true,
196
205
  } as BusinessBirthInternalDirective;
197
206
 
198
207
  const birthBase = directive(birthId)
@@ -228,7 +237,6 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
228
237
  actor: founder,
229
238
  payload: { domainHash: `${CURRENT_LAW_PREFIX}nomos` },
230
239
  domainHash: `${CURRENT_LAW_PREFIX}nomos`,
231
- domainPackageB64: "",
232
240
  },
233
241
  {
234
242
  domain: "nomos",
@@ -236,7 +244,6 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
236
244
  actor: founder,
237
245
  payload: { domainHash: `${CURRENT_LAW_PREFIX}${domain}` },
238
246
  domainHash: `${CURRENT_LAW_PREFIX}${domain}`,
239
- domainPackageB64: "",
240
247
  },
241
248
  {
242
249
  domain,
@@ -298,10 +305,19 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
298
305
 
299
306
  const metadata: BusinessBirthMetadata = {
300
307
  aggregateId: decl.aggregate.id,
308
+ domain,
301
309
  publicPayloadSchema: publicSchema,
302
310
  rootId,
303
311
  namespace,
304
312
  keyField: decl.keyField,
313
+ seedDirectiveId: seedId,
314
+ genesisDirectiveIds: [
315
+ "enrollSigner",
316
+ "seedAuthProvider",
317
+ "seedBirthAttestation",
318
+ "seedGenesis",
319
+ "nomosBirthRecordEnsure",
320
+ ],
305
321
  founderRoles: decl.founderRoles,
306
322
  founderScope: decl.founderScope,
307
323
  };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Deterministic JSON for compiler-internal fingerprints whose model is already typed.
3
+ *
4
+ * This is a byte-stable serializer, not a law representation. Canonical Nomos law is
5
+ * typed USDA; callers use this only when a small derived compiler value (for example a
6
+ * generated client interface) needs a reproducible digest.
7
+ */
8
+ function canonicalize(value: unknown): unknown {
9
+ if (Array.isArray(value)) return value.map(canonicalize);
10
+ if (value !== null && typeof value === "object") {
11
+ const object = value as Record<string, unknown>;
12
+ const sorted: Record<string, unknown> = {};
13
+ for (const key of Object.keys(object).sort()) sorted[key] = canonicalize(object[key]);
14
+ return sorted;
15
+ }
16
+ return value;
17
+ }
18
+
19
+ export function canonicalJson(value: unknown): string {
20
+ return JSON.stringify(canonicalize(value));
21
+ }
@@ -5,7 +5,7 @@
5
5
 
6
6
  /**
7
7
  * `githolon check` — the author-time gate as a standalone command
8
- * (architecture/author_time_gate.md, slice 1). Loads `nomos.package.mjs`, composes each
8
+ * (architecture/author_time_gate.md, slice 1). Loads `nomos.project.ts`, composes each
9
9
  * domain the SAME way `compile` does (`composeDomainModule` — the one owner of directive
10
10
  * discovery), runs {@link checkDomainModules}, prints the unified report, and exits
11
11
  * non-zero on any REFUSE-level finding. `compile` runs the same twins implicitly.
@@ -21,20 +21,26 @@ import { LawSourceMap } from "./law_source_map.js";
21
21
  import {
22
22
  currentEvolveLaw,
23
23
  evolveDiff,
24
- evolveLawFromDeployJson,
24
+ evolveLawFromInterface,
25
25
  evolveLawFromLockfile,
26
26
  type EvolveLaw,
27
27
  type StableIdLockfile,
28
28
  } from "./evolve_preview.js";
29
+ import { parseStableIdLockfile } from "./stable_id_lockfile.js";
30
+ import {
31
+ validateNomosPackageConfig,
32
+ type NomosPackageConfig,
33
+ } from "./package_config.js";
34
+ import { assertPublicPackageImports } from "./package_source_imports.js";
35
+ import { packageDependencyUsageFindings } from "./package_dependency_usage.js";
29
36
 
30
37
  interface DomainConfig {
31
38
  key: string;
32
39
  name?: string;
33
40
  modules: string[];
34
41
  }
35
- interface PackageConfig {
36
- name: string;
37
- domains: DomainConfig[];
42
+ interface PackageConfig extends NomosPackageConfig {
43
+ readonly domains: readonly DomainConfig[];
38
44
  }
39
45
 
40
46
  function fail(msg: string): never {
@@ -43,7 +49,7 @@ function fail(msg: string): never {
43
49
  }
44
50
 
45
51
  /**
46
- * Compose the package's domain modules from `nomos.package.mjs` — the check-time subset of what
52
+ * Compose the package's domain modules from `nomos.project.ts` — the check-time subset of what
47
53
  * `compile_package_main` does: import the config, import each domain's TS modules, and hand them to
48
54
  * `composeDomainModule` (the shared composition primitive that discovers directives/aggregates and
49
55
  * applies the secondary-ensure auto-declare). Returns the composed modules ready for the twins.
@@ -59,62 +65,84 @@ export async function loadComposedModules(configArg: string): Promise<DomainModu
59
65
  */
60
66
  export async function loadComposedModulesWithSources(
61
67
  configArg: string,
62
- ): Promise<{ modules: DomainModule[]; domainFiles: Map<string, string[]> }> {
68
+ ): Promise<{
69
+ modules: DomainModule[];
70
+ domainFiles: Map<string, string[]>;
71
+ dependencyFindings: readonly Finding[];
72
+ }> {
63
73
  const configPath = path.resolve(process.cwd(), configArg);
64
74
  if (!existsSync(configPath)) fail(`config not found: ${configPath}`);
65
75
  const cfgDir = path.dirname(configPath);
66
76
  // Cache-bust the config import so a re-load (the LSP re-check) sees edits, not the first-import cache.
67
77
  const cfgUrl = `${pathToFileURL(configPath).href}?t=${Date.now()}`;
68
78
  const cfgModule = (await import(cfgUrl)) as { default?: PackageConfig };
69
- const cfg = cfgModule.default;
70
- if (!cfg || typeof cfg.name !== "string" || !Array.isArray(cfg.domains) || cfg.domains.length === 0) {
71
- fail(`config must default-export { name, domains: [{ key, modules }, ...] }`);
79
+ let cfg: PackageConfig;
80
+ let runtimeDependencies: ReturnType<typeof validateNomosPackageConfig>["runtimeDependencies"];
81
+ try {
82
+ const validated = validateNomosPackageConfig(cfgModule.default);
83
+ cfg = validated.config as PackageConfig;
84
+ runtimeDependencies = validated.runtimeDependencies;
85
+ } catch (error) {
86
+ fail(`package contract refused: ${(error as Error).message}`);
72
87
  }
73
88
  const composed: DomainModule[] = [];
74
89
  const domainFiles = new Map<string, string[]>();
75
90
  for (const d of cfg.domains) {
76
- if (typeof d.key !== "string" || !Array.isArray(d.modules) || d.modules.length === 0) {
77
- fail(`each domain needs { key, modules: [path, ...] }; got ${JSON.stringify(d)}`);
78
- }
79
91
  const mods: Mod[] = [];
80
92
  const files: string[] = [];
81
93
  for (const rel of d.modules) {
82
94
  const abs = path.resolve(cfgDir, rel);
83
95
  if (!existsSync(abs)) fail(`domain '${d.key}': module not found: ${abs}`);
84
96
  files.push(abs);
97
+ }
98
+ try {
99
+ assertPublicPackageImports(files);
100
+ } catch (error) {
101
+ fail(`package import boundary refused: ${(error as Error).message}`);
102
+ }
103
+ for (const abs of files) {
85
104
  // Cache-bust so a re-import (the LSP re-check after an edit) re-evaluates the edited module.
86
105
  mods.push((await import(`${pathToFileURL(abs).href}?t=${Date.now()}`)) as Mod);
87
106
  }
88
107
  domainFiles.set(d.key, files);
89
108
  composed.push(composeDomainModule({ name: d.name ?? d.key, domain: d.key, modules: mods }));
90
109
  }
91
- return { modules: composed, domainFiles };
110
+ return {
111
+ modules: composed,
112
+ domainFiles,
113
+ dependencyFindings: packageDependencyUsageFindings({
114
+ packageName: cfg.name,
115
+ exportedDomains: cfg.domains.map((domain) => domain.key),
116
+ runtimeDependencies,
117
+ modules: composed,
118
+ }),
119
+ };
92
120
  }
93
121
 
94
122
  /**
95
123
  * Load the PRIOR law surface for `--against <path>` (the evolve preview's baseline). The supported
96
- * prior is a DEPLOY.JSON (`build/<name>.deploy.json`), a BUILD DIR / package dir containing one, or
124
+ * prior is a diagnostic `build/<name>.interface.json`, a BUILD DIR / package dir containing one, or
97
125
  * the committed `nomos.stable-ids.json` LOCKFILE (aggregate identity only — no directive payloads).
98
126
  * A live `https://…` prior is NOT supported (the installed package's stable-ids aren't retrievable
99
- * from GET /domains); point `--against` at the deployed law's committed deploy.json instead.
127
+ * from GET /domains); point `--against` at the deployed source's interface and lock sidecars instead.
100
128
  */
101
129
  function loadPriorEvolveLaw(arg: string): { law: EvolveLaw; source: string; note?: string } {
102
130
  if (/^https?:\/\//.test(arg)) {
103
131
  fail(
104
132
  `--against does not support a live URL (${arg}) — the installed package's #58 stable-ids are ` +
105
- `not retrievable from GET /domains. Point --against at the deployed law's build/<name>.deploy.json.`,
133
+ `not retrievable from GET /domains. Point --against at the deployed source's build/<name>.interface.json.`,
106
134
  );
107
135
  }
108
136
  const abs = path.resolve(process.cwd(), arg);
109
137
  if (!existsSync(abs)) fail(`--against: prior not found: ${abs}`);
110
- // Resolve a directory to a deploy.json (build dir or package dir) or its lockfile.
138
+ // Resolve a directory to its diagnostic interface or stable-id lockfile.
111
139
  let file = abs;
112
140
  if (statSync(abs).isDirectory()) {
113
- const deploy = findDeployJson(abs);
141
+ const iface = findInterfaceJson(abs);
114
142
  const lock = path.join(abs, "nomos.stable-ids.json");
115
- if (deploy !== undefined) file = deploy;
143
+ if (iface !== undefined) file = iface;
116
144
  else if (existsSync(lock)) file = lock;
117
- else fail(`--against: no *.deploy.json or nomos.stable-ids.json under ${abs}`);
145
+ else fail(`--against: no *.interface.json or nomos.stable-ids.json under ${abs}`);
118
146
  }
119
147
  let parsed: unknown;
120
148
  try {
@@ -122,22 +150,35 @@ function loadPriorEvolveLaw(arg: string): { law: EvolveLaw; source: string; note
122
150
  } catch (e) {
123
151
  fail(`--against: could not parse ${file}: ${(e as Error).message}`);
124
152
  }
125
- const p = parsed as Record<string, unknown>;
126
- if (p.domains !== undefined && p.interface === undefined && p.packageUsda === undefined) {
153
+ if (path.basename(file) === "nomos.stable-ids.json") {
127
154
  return {
128
- law: evolveLawFromLockfile(parsed as StableIdLockfile),
155
+ law: evolveLawFromLockfile(parseStableIdLockfile(parsed)),
129
156
  source: file,
130
- note: `prior is a lockfile — aggregate/field identity is diffed, but directive payload changes cannot be (deploy.json carries the interface).`,
157
+ note: `prior is a lockfile — aggregate/field identity is diffed, but directive payload changes require the sibling interface sidecar.`,
131
158
  };
132
159
  }
133
- return { law: evolveLawFromDeployJson(parsed as never), source: file };
160
+ const lockCandidates = [
161
+ path.join(path.dirname(file), "nomos.stable-ids.json"),
162
+ path.join(path.dirname(path.dirname(file)), "nomos.stable-ids.json"),
163
+ ];
164
+ const lockFile = lockCandidates.find((candidate) => existsSync(candidate));
165
+ const lock = lockFile === undefined
166
+ ? undefined
167
+ : parseStableIdLockfile(JSON.parse(readFileSync(lockFile, "utf8")));
168
+ return {
169
+ law: evolveLawFromInterface(parsed as Parameters<typeof evolveLawFromInterface>[0], lock),
170
+ source: lockFile === undefined ? file : `${file} + ${lockFile}`,
171
+ ...(lockFile === undefined
172
+ ? { note: `prior has no stable-id lock beside it — directive shapes are checked, but driver and contract evolution evidence is incomplete.` }
173
+ : {}),
174
+ };
134
175
  }
135
176
 
136
- /** Find a `*.deploy.json` under a directory — directly, or one level down in `build/`. */
137
- function findDeployJson(dir: string): string | undefined {
177
+ /** Find a compiler diagnostic `*.interface.json` directly or one level down in `build/`. */
178
+ function findInterfaceJson(dir: string): string | undefined {
138
179
  const scan = (d: string): string | undefined => {
139
180
  if (!existsSync(d)) return undefined;
140
- const hit = readdirSync(d).find((f) => f.endsWith(".deploy.json"));
181
+ const hit = readdirSync(d).find((f) => f.endsWith(".interface.json"));
141
182
  return hit !== undefined ? path.join(d, hit) : undefined;
142
183
  };
143
184
  return scan(dir) ?? scan(path.join(dir, "build"));
@@ -149,7 +190,7 @@ function loadCwdLockfile(configArg: string): StableIdLockfile | undefined {
149
190
  const lockPath = path.join(cfgDir, "nomos.stable-ids.json");
150
191
  if (!existsSync(lockPath)) return undefined;
151
192
  try {
152
- return JSON.parse(readFileSync(lockPath, "utf8")) as StableIdLockfile;
193
+ return parseStableIdLockfile(JSON.parse(readFileSync(lockPath, "utf8")));
153
194
  } catch {
154
195
  return undefined;
155
196
  }
@@ -161,7 +202,7 @@ async function main(): Promise<void> {
161
202
  // positional — `prior.json` belongs to the flag, not the config slot).
162
203
  const againstIdx = argv.findIndex((a) => a === "--against");
163
204
  const positionals = argv.filter((a, i) => !a.startsWith("--") && i !== againstIdx + 1);
164
- const configArg = positionals[0] ?? "nomos.package.mjs";
205
+ const configArg = positionals[0] ?? "nomos.project.ts";
165
206
  const againstArg =
166
207
  againstIdx >= 0
167
208
  ? argv[againstIdx + 1]
@@ -169,14 +210,14 @@ async function main(): Promise<void> {
169
210
 
170
211
  const asJson = argv.includes("--json");
171
212
 
172
- const { modules, domainFiles } = await loadComposedModulesWithSources(configArg);
173
- const findings: Finding[] = [...checkDomainModules(modules)];
213
+ const { modules, domainFiles, dependencyFindings } = await loadComposedModulesWithSources(configArg);
214
+ const findings: Finding[] = [...dependencyFindings, ...checkDomainModules(modules)];
174
215
 
175
216
  // ── EVOLVE PREVIEW (--against): diff the current build against a prior law + pre-fire the gate. ──
176
217
  let evolveDispositions: Record<string, unknown> | undefined;
177
218
  if (againstIdx >= 0 || againstArg !== undefined) {
178
219
  if (againstArg === undefined || againstArg.startsWith("--")) {
179
- fail(`--against needs a path: --against <build/<name>.deploy.json | build dir | nomos.stable-ids.json>`);
220
+ fail(`--against needs a path: --against <build/<name>.interface.json | build dir | nomos.stable-ids.json>`);
180
221
  }
181
222
  const lock = loadCwdLockfile(configArg);
182
223
  if (lock === undefined && !asJson) {
@@ -223,7 +264,7 @@ async function main(): Promise<void> {
223
264
  // The consolidated, pasteable dispositions the destructive evolves demand (per domain).
224
265
  if (evolveDispositions !== undefined) {
225
266
  console.log(
226
- "\ngitholon check: to admit the destructive evolve, add `dispositions` to the domain's deploy JSON:",
267
+ "\ngitholon check: to admit the destructive evolve, include `dispositions` in the installation offer:",
227
268
  );
228
269
  console.log(JSON.stringify(evolveDispositions, null, 2));
229
270
  }
@@ -51,7 +51,12 @@ export function clearDirectiveId(aggregateId: string, field: string): string {
51
51
  * needn't read it), plan = the one `clear` op. `.requires(relation)` iff the field declared
52
52
  * `clearRequires`.
53
53
  */
54
- export function makeClearDirective(agg: AggregateHandle, field: string, requires?: string): Directive<unknown> {
54
+ export function makeClearDirective(
55
+ agg: AggregateHandle,
56
+ field: string,
57
+ requires?: string,
58
+ object?: string,
59
+ ): Directive<unknown> {
55
60
  const payloadId = idOf(agg);
56
61
  const built = directive(clearDirectiveId(agg.id, field))
57
62
  .mutates(agg)
@@ -60,14 +65,20 @@ export function makeClearDirective(agg: AggregateHandle, field: string, requires
60
65
  .doc(
61
66
  `Explicitly clears the '${field}' field of a ${agg.id} (writes an explicit null — the declared clearable lane).`,
62
67
  ) as unknown as RequirableDirective<unknown>;
63
- return requires !== undefined ? built.requires(requires) : built;
68
+ return requires !== undefined
69
+ ? built.requires(requires, object === undefined ? undefined : { object })
70
+ : built;
64
71
  }
65
72
 
66
- /** The clearable `(field, requires?)` entries of one aggregate handle. */
67
- function clearableFieldsOf(agg: AggregateHandle): { field: string; requires?: string }[] {
68
- const out: { field: string; requires?: string }[] = [];
73
+ /** The clearable `(field, requires?, object?)` entries of one aggregate handle. */
74
+ function clearableFieldsOf(agg: AggregateHandle): { field: string; requires?: string; object?: string }[] {
75
+ const out: { field: string; requires?: string; object?: string }[] = [];
69
76
  for (const [name, f0] of Object.entries(agg.fields as Record<string, Field>)) {
70
- if (f0.isClearable === true) out.push({ field: name, ...(f0.clearRequires !== undefined ? { requires: f0.clearRequires } : {}) });
77
+ if (f0.isClearable === true) out.push({
78
+ field: name,
79
+ ...(f0.clearRequires !== undefined ? { requires: f0.clearRequires } : {}),
80
+ ...(f0.clearRequiresObject !== undefined ? { object: f0.clearRequiresObject } : {}),
81
+ });
71
82
  }
72
83
  return out;
73
84
  }
@@ -94,10 +105,10 @@ export function withClearDirectives<M extends Record<string, unknown>>(mod: M):
94
105
 
95
106
  const additions: Record<string, unknown> = {};
96
107
  for (const agg of aggs) {
97
- for (const { field, requires } of clearableFieldsOf(agg)) {
108
+ for (const { field, requires, object } of clearableFieldsOf(agg)) {
98
109
  const id = clearDirectiveId(agg.id, field);
99
110
  if (existingIds.has(id) || id in mod) continue;
100
- additions[id] = makeClearDirective(agg, field, requires);
111
+ additions[id] = makeClearDirective(agg, field, requires, object);
101
112
  existingIds.add(id);
102
113
  }
103
114
  }