@githolon/dsl 0.102.5 → 0.103.1

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 (132) 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 +14 -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 +5539 -1228
  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 +239 -102
  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/acquire_session_test.dart +9 -0
  15. package/dart/test/adv_realm_interference_test.dart +7 -6
  16. package/dart/test/cloud_bound_topology_test.dart +3 -3
  17. package/dart/test/cluster_births_test.dart +4 -3
  18. package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
  19. package/dart/test/cluster_local_autosync_test.dart +3 -2
  20. package/dart/test/cluster_scale_test.dart +3 -2
  21. package/dart/test/dlq_session_surface_test.dart +7 -6
  22. package/dart/test/first_app_gauntlet_test.dart +14 -21
  23. package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
  24. package/dart/test/four_layer_nested_topology_test.dart +4 -4
  25. package/dart/test/harness_phase_timeout_test.dart +2 -2
  26. package/dart/test/local_sync_inert_test.dart +1 -1
  27. package/dart/test/parent_signer_recovery_test.dart +2 -2
  28. package/dart/test/sealing_client_test.dart +4 -3
  29. package/dart/test/session_bound_test.dart +8 -7
  30. package/dart/test/test_harness_test.dart +13 -12
  31. package/dart/test/vo_read_typing_test.dart +4 -3
  32. package/dart/test/watch_after_offer_stall_test.dart +4 -3
  33. package/dart/test/webview_bridge_test.dart +102 -5
  34. package/dart_types/CHANGELOG.md +5 -0
  35. package/dart_types/lib/nomos_types.dart +9 -0
  36. package/dart_types/lib/src/nomos_ref.dart +20 -0
  37. package/dart_types/lib/src/optional_ref.dart +85 -0
  38. package/dart_types/lib/src/sha256_digest.dart +41 -0
  39. package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
  40. package/dart_types/pubspec.yaml +1 -1
  41. package/dart_types/test/optional_ref_test.dart +44 -0
  42. package/dart_types/test/sha256_digest_test.dart +18 -0
  43. package/package.json +9 -5
  44. package/runtime/generated-application-host.js +5365 -1105
  45. package/src/aggregate.ts +18 -0
  46. package/src/application_composition.ts +6 -32
  47. package/src/attested_core.ts +6 -3
  48. package/src/birth_meter.ts +1 -0
  49. package/src/birth_meter_ensure.ts +28 -2
  50. package/src/births.ts +1 -1
  51. package/src/build_package.ts +162 -538
  52. package/src/business_birth.ts +19 -3
  53. package/src/canonical_json.ts +21 -0
  54. package/src/check_package_main.ts +73 -33
  55. package/src/clear_directive.ts +19 -8
  56. package/src/codegen_dart.ts +305 -22
  57. package/src/codegen_dart_application.ts +862 -292
  58. package/src/codegen_dart_client.ts +1084 -407
  59. package/src/codegen_dart_scenario.ts +12 -12
  60. package/src/codegen_proof.ts +169 -39
  61. package/src/codegen_provider_dart.ts +17 -1
  62. package/src/codegen_ts.ts +298 -106
  63. package/src/compat_manifest.ts +2 -2
  64. package/src/compat_package_main.ts +8 -12
  65. package/src/compile_package_main.ts +1860 -592
  66. package/src/compose.ts +1 -1
  67. package/src/directive.ts +208 -15
  68. package/src/engine_entry.ts +165 -111
  69. package/src/evolve_preview.ts +29 -69
  70. package/src/extremum.ts +1 -1
  71. package/src/fields.ts +183 -4
  72. package/src/flow_gate.ts +75 -2
  73. package/src/framework/application_home.ts +2 -0
  74. package/src/framework/auth_provider_law.ts +2 -2
  75. package/src/framework/birth_meter.ts +7 -19
  76. package/src/framework/bootstrap.ts +0 -2
  77. package/src/framework/business_corrections.ts +9 -5
  78. package/src/framework/domain_lifecycle.ts +15 -29
  79. package/src/framework/encryption_scope.ts +2 -1
  80. package/src/framework/framework_aggregates.ts +2 -0
  81. package/src/framework/genesis.ts +16 -14
  82. package/src/framework/home.ts +20 -14
  83. package/src/framework/owned_custody.ts +9 -3
  84. package/src/framework/platform_admin.ts +3 -3
  85. package/src/framework/relations.ts +21 -13
  86. package/src/framework/signing_identity.ts +45 -40
  87. package/src/framework/workspace_invariant.ts +245 -0
  88. package/src/framework/workspaces.ts +23 -29
  89. package/src/index.ts +36 -16
  90. package/src/intent.ts +178 -6
  91. package/src/law_check.ts +32 -11
  92. package/src/law_graph.ts +2902 -0
  93. package/src/law_graph_usda.ts +1799 -0
  94. package/src/law_identity.ts +435 -0
  95. package/src/law_value_type.ts +230 -0
  96. package/src/lsp_server.ts +58 -29
  97. package/src/ops.ts +7 -0
  98. package/src/package_config.ts +141 -0
  99. package/src/package_dependencies.ts +108 -0
  100. package/src/package_dependency_usage.ts +150 -0
  101. package/src/package_source_imports.ts +71 -0
  102. package/src/plan_source_slice.ts +335 -0
  103. package/src/point_guard.ts +192 -0
  104. package/src/precondition.ts +44 -9
  105. package/src/read_projection_namespace.ts +1 -1
  106. package/src/runtime_zod.ts +44 -0
  107. package/src/secondary_ensure.ts +67 -14
  108. package/src/shape_compat.ts +1 -1
  109. package/src/stable_id_identity_bridge.ts +149 -0
  110. package/src/stable_id_lockfile.ts +140 -6
  111. package/src/stable_ids.ts +1 -1
  112. package/src/stable_ids_for_module.ts +348 -0
  113. package/src/stable_ids_types.ts +1 -1
  114. package/src/sum.ts +1 -1
  115. package/src/usd_state.ts +1 -1
  116. package/src/usda_relationship_integrity.ts +66 -0
  117. package/src/usda_text.ts +18 -0
  118. package/src/usdz.ts +78 -0
  119. package/src/value_object.ts +95 -0
  120. package/src/wire.ts +19 -0
  121. package/src/wire_encode.ts +22 -0
  122. package/src/workspace_sharding.ts +52 -36
  123. package/src/workspace_sharing.ts +28 -4
  124. package/src/zod_introspection.ts +1 -1
  125. package/dart/test/cluster_attested_test.dart +0 -201
  126. package/src/compile_engine.ts +0 -595
  127. package/src/manifest.ts +0 -1476
  128. package/src/read_projection_composition.ts +0 -117
  129. package/src/read_projection_manifest.ts +0 -115
  130. package/src/relation_composition.ts +0 -286
  131. package/src/usd.ts +0 -1967
  132. 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,21 +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
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";
30
36
 
31
37
  interface DomainConfig {
32
38
  key: string;
33
39
  name?: string;
34
40
  modules: string[];
35
41
  }
36
- interface PackageConfig {
37
- name: string;
38
- domains: DomainConfig[];
42
+ interface PackageConfig extends NomosPackageConfig {
43
+ readonly domains: readonly DomainConfig[];
39
44
  }
40
45
 
41
46
  function fail(msg: string): never {
@@ -44,7 +49,7 @@ function fail(msg: string): never {
44
49
  }
45
50
 
46
51
  /**
47
- * 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
48
53
  * `compile_package_main` does: import the config, import each domain's TS modules, and hand them to
49
54
  * `composeDomainModule` (the shared composition primitive that discovers directives/aggregates and
50
55
  * applies the secondary-ensure auto-declare). Returns the composed modules ready for the twins.
@@ -60,62 +65,84 @@ export async function loadComposedModules(configArg: string): Promise<DomainModu
60
65
  */
61
66
  export async function loadComposedModulesWithSources(
62
67
  configArg: string,
63
- ): Promise<{ modules: DomainModule[]; domainFiles: Map<string, string[]> }> {
68
+ ): Promise<{
69
+ modules: DomainModule[];
70
+ domainFiles: Map<string, string[]>;
71
+ dependencyFindings: readonly Finding[];
72
+ }> {
64
73
  const configPath = path.resolve(process.cwd(), configArg);
65
74
  if (!existsSync(configPath)) fail(`config not found: ${configPath}`);
66
75
  const cfgDir = path.dirname(configPath);
67
76
  // Cache-bust the config import so a re-load (the LSP re-check) sees edits, not the first-import cache.
68
77
  const cfgUrl = `${pathToFileURL(configPath).href}?t=${Date.now()}`;
69
78
  const cfgModule = (await import(cfgUrl)) as { default?: PackageConfig };
70
- const cfg = cfgModule.default;
71
- if (!cfg || typeof cfg.name !== "string" || !Array.isArray(cfg.domains) || cfg.domains.length === 0) {
72
- 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}`);
73
87
  }
74
88
  const composed: DomainModule[] = [];
75
89
  const domainFiles = new Map<string, string[]>();
76
90
  for (const d of cfg.domains) {
77
- if (typeof d.key !== "string" || !Array.isArray(d.modules) || d.modules.length === 0) {
78
- fail(`each domain needs { key, modules: [path, ...] }; got ${JSON.stringify(d)}`);
79
- }
80
91
  const mods: Mod[] = [];
81
92
  const files: string[] = [];
82
93
  for (const rel of d.modules) {
83
94
  const abs = path.resolve(cfgDir, rel);
84
95
  if (!existsSync(abs)) fail(`domain '${d.key}': module not found: ${abs}`);
85
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) {
86
104
  // Cache-bust so a re-import (the LSP re-check after an edit) re-evaluates the edited module.
87
105
  mods.push((await import(`${pathToFileURL(abs).href}?t=${Date.now()}`)) as Mod);
88
106
  }
89
107
  domainFiles.set(d.key, files);
90
108
  composed.push(composeDomainModule({ name: d.name ?? d.key, domain: d.key, modules: mods }));
91
109
  }
92
- 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
+ };
93
120
  }
94
121
 
95
122
  /**
96
123
  * Load the PRIOR law surface for `--against <path>` (the evolve preview's baseline). The supported
97
- * 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
98
125
  * the committed `nomos.stable-ids.json` LOCKFILE (aggregate identity only — no directive payloads).
99
126
  * A live `https://…` prior is NOT supported (the installed package's stable-ids aren't retrievable
100
- * 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.
101
128
  */
102
129
  function loadPriorEvolveLaw(arg: string): { law: EvolveLaw; source: string; note?: string } {
103
130
  if (/^https?:\/\//.test(arg)) {
104
131
  fail(
105
132
  `--against does not support a live URL (${arg}) — the installed package's #58 stable-ids are ` +
106
- `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.`,
107
134
  );
108
135
  }
109
136
  const abs = path.resolve(process.cwd(), arg);
110
137
  if (!existsSync(abs)) fail(`--against: prior not found: ${abs}`);
111
- // 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.
112
139
  let file = abs;
113
140
  if (statSync(abs).isDirectory()) {
114
- const deploy = findDeployJson(abs);
141
+ const iface = findInterfaceJson(abs);
115
142
  const lock = path.join(abs, "nomos.stable-ids.json");
116
- if (deploy !== undefined) file = deploy;
143
+ if (iface !== undefined) file = iface;
117
144
  else if (existsSync(lock)) file = lock;
118
- 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}`);
119
146
  }
120
147
  let parsed: unknown;
121
148
  try {
@@ -123,22 +150,35 @@ function loadPriorEvolveLaw(arg: string): { law: EvolveLaw; source: string; note
123
150
  } catch (e) {
124
151
  fail(`--against: could not parse ${file}: ${(e as Error).message}`);
125
152
  }
126
- const p = parsed as Record<string, unknown>;
127
- if (p.domains !== undefined && p.interface === undefined && p.packageUsda === undefined) {
153
+ if (path.basename(file) === "nomos.stable-ids.json") {
128
154
  return {
129
155
  law: evolveLawFromLockfile(parseStableIdLockfile(parsed)),
130
156
  source: file,
131
- 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.`,
132
158
  };
133
159
  }
134
- 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
+ };
135
175
  }
136
176
 
137
- /** Find a `*.deploy.json` under a directory — directly, or one level down in `build/`. */
138
- 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 {
139
179
  const scan = (d: string): string | undefined => {
140
180
  if (!existsSync(d)) return undefined;
141
- const hit = readdirSync(d).find((f) => f.endsWith(".deploy.json"));
181
+ const hit = readdirSync(d).find((f) => f.endsWith(".interface.json"));
142
182
  return hit !== undefined ? path.join(d, hit) : undefined;
143
183
  };
144
184
  return scan(dir) ?? scan(path.join(dir, "build"));
@@ -162,7 +202,7 @@ async function main(): Promise<void> {
162
202
  // positional — `prior.json` belongs to the flag, not the config slot).
163
203
  const againstIdx = argv.findIndex((a) => a === "--against");
164
204
  const positionals = argv.filter((a, i) => !a.startsWith("--") && i !== againstIdx + 1);
165
- const configArg = positionals[0] ?? "nomos.package.mjs";
205
+ const configArg = positionals[0] ?? "nomos.project.ts";
166
206
  const againstArg =
167
207
  againstIdx >= 0
168
208
  ? argv[againstIdx + 1]
@@ -170,14 +210,14 @@ async function main(): Promise<void> {
170
210
 
171
211
  const asJson = argv.includes("--json");
172
212
 
173
- const { modules, domainFiles } = await loadComposedModulesWithSources(configArg);
174
- const findings: Finding[] = [...checkDomainModules(modules)];
213
+ const { modules, domainFiles, dependencyFindings } = await loadComposedModulesWithSources(configArg);
214
+ const findings: Finding[] = [...dependencyFindings, ...checkDomainModules(modules)];
175
215
 
176
216
  // ── EVOLVE PREVIEW (--against): diff the current build against a prior law + pre-fire the gate. ──
177
217
  let evolveDispositions: Record<string, unknown> | undefined;
178
218
  if (againstIdx >= 0 || againstArg !== undefined) {
179
219
  if (againstArg === undefined || againstArg.startsWith("--")) {
180
- 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>`);
181
221
  }
182
222
  const lock = loadCwdLockfile(configArg);
183
223
  if (lock === undefined && !asJson) {
@@ -224,7 +264,7 @@ async function main(): Promise<void> {
224
264
  // The consolidated, pasteable dispositions the destructive evolves demand (per domain).
225
265
  if (evolveDispositions !== undefined) {
226
266
  console.log(
227
- "\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:",
228
268
  );
229
269
  console.log(JSON.stringify(evolveDispositions, null, 2));
230
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
  }