@githolon/dsl 0.2.2 → 0.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -22,6 +22,9 @@
22
22
  "./usd-layers": "./src/usd_layers.ts",
23
23
  "./usd-state": "./src/usd_state.ts",
24
24
  "./engine-entry": "./src/engine_entry.ts",
25
+ "./workspace-type": "./src/workspace_type.ts",
26
+ "./workspace-routing": "./src/workspace_routing.ts",
27
+ "./workspace-sharding": "./src/workspace_sharding.ts",
25
28
  "./build-package": "./src/build_package.ts",
26
29
  "./compile-engine": "./src/compile_engine.ts",
27
30
  "./codegen-dart": "./src/codegen_dart.ts",
@@ -45,10 +45,13 @@ import type { DomainModule, PermissionVocabulary, DartImport } from "./codegen_d
45
45
  import { finishCount, type AnyCount, type CountDecl } from "./count.js";
46
46
  import type { QueryDecl } from "./query.js";
47
47
  import type { SpatialDecl } from "./spatial.js";
48
- import type { AnySum } from "./sum.js";
48
+ import { finishSum, type AnySum } from "./sum.js";
49
+ import { finishExtremum, type AnyExtremum } from "./extremum.js";
49
50
  import type { DerivedDecl } from "./derived.js";
50
51
  import type { CombinedDecl } from "./combined.js";
51
52
  import type { ImpureCapabilityDecl } from "./codegen_provider_dart.js";
53
+ import type { WorkspaceTypeDecl } from "./workspace_type.js";
54
+ import type { WorkspaceInvariantDecl } from "./framework/workspace_invariant.js";
52
55
  import { domainHash, emitManifestBytes } from "./manifest.js";
53
56
  import { emitUsd } from "./usd.js";
54
57
 
@@ -108,7 +111,14 @@ export interface DomainModuleSpec {
108
111
  readonly combineds?: readonly CombinedDecl[];
109
112
  readonly impureCapabilities?: readonly ImpureCapabilityDecl[];
110
113
  readonly sums?: readonly AnySum[];
114
+ /** Maintained MIN/MAX reads (#47 — the extremum RPC's manifest half). Config-named like sums. */
115
+ readonly mins?: readonly AnyExtremum[];
116
+ readonly maxes?: readonly AnyExtremum[];
111
117
  readonly spatials?: readonly SpatialDecl[];
118
+ /** First-class workspace types (sharding §10 slice 1) — the law-declared taxonomy. */
119
+ readonly workspaceTypes?: readonly WorkspaceTypeDecl[];
120
+ /** Declared workspace invariants (#266) — presence is hashed; bodies ship in the lump. */
121
+ readonly workspaceInvariants?: readonly WorkspaceInvariantDecl[];
112
122
  readonly dartImports?: readonly DartImport[];
113
123
  readonly dartRefImports?: DomainModule["dartRefImports"];
114
124
  readonly permissionVocabulary?: PermissionVocabulary;
@@ -149,6 +159,10 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
149
159
  const combineds = [...(spec.combineds ?? [])];
150
160
  const impureCapabilities = [...(spec.impureCapabilities ?? [])];
151
161
  const sums = [...(spec.sums ?? [])];
162
+ const mins = [...(spec.mins ?? [])];
163
+ const maxes = [...(spec.maxes ?? [])];
164
+ const workspaceTypes = [...(spec.workspaceTypes ?? [])];
165
+ const workspaceInvariants = [...(spec.workspaceInvariants ?? [])];
152
166
  const dartImports = [...(spec.dartImports ?? [])];
153
167
 
154
168
  return {
@@ -164,6 +178,15 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
164
178
  ...(readModels.length > 0 ? { readModelAggregates: [...readModels] } : {}),
165
179
  ...(impureCapabilities.length > 0 ? { impureCapabilities } : {}),
166
180
  ...(sums.length > 0 ? { sums } : {}),
181
+ // Omit-when-empty — extremum-free domains' canonical manifests stay byte-identical.
182
+ ...(mins.length > 0 ? { mins } : {}),
183
+ ...(maxes.length > 0 ? { maxes } : {}),
184
+ // Omit-when-empty — HASH-LOAD-BEARING like every optional key here: a taxonomy-
185
+ // free domain's canonical manifest must stay byte-identical (sharding §10 slice 1).
186
+ ...(workspaceTypes.length > 0 ? { workspaceTypes } : {}),
187
+ // Omit-when-empty — presence-only hashed law (#266): an invariant-free domain's
188
+ // canonical manifest must stay byte-identical (adding/removing an invariant moves it).
189
+ ...(workspaceInvariants.length > 0 ? { workspaceInvariants } : {}),
167
190
  ...(dartImports.length > 0 ? { dartImports } : {}),
168
191
  ...(spec.dartRefImports !== undefined && spec.dartRefImports.length > 0
169
192
  ? { dartRefImports: [...spec.dartRefImports] }
@@ -199,6 +222,10 @@ export interface CountDescriptor {
199
222
  id: string;
200
223
  of: string;
201
224
  by: string | null;
225
+ /** The canonical predicate (`WirePred` in the Rust parser — `{kind:"eq"|"ne"|"and"|"or",…}`).
226
+ * OMITTED entirely when the count declares none: predicate-free read manifests stay
227
+ * byte-identical (the Rust side reads it `#[serde(rename = "where", default)]`). */
228
+ where?: unknown;
202
229
  }
203
230
  export interface SpatialDescriptor {
204
231
  id: string;
@@ -223,6 +250,26 @@ export interface CombinedDescriptor {
223
250
  returns: ProjectionReturnDescriptor;
224
251
  }
225
252
 
253
+ /** One maintained-sum descriptor (the projection's `WireSum` shape — note `field`). */
254
+ export interface SumDescriptor {
255
+ id: string;
256
+ of: string;
257
+ field: string;
258
+ by: string | null;
259
+ /** The canonical predicate — same omit-when-absent discipline as {@link CountDescriptor}. */
260
+ where?: unknown;
261
+ }
262
+
263
+ /** One min/max read-manifest descriptor — the Rust `WireExtremum` shape (#47): identical
264
+ * to {@link SumDescriptor}; the KIND is implicit in which array it rides (`mins` vs `maxes`). */
265
+ export interface ExtremumDescriptor {
266
+ id: string;
267
+ of: string;
268
+ field: string;
269
+ by: string | null;
270
+ where?: unknown;
271
+ }
272
+
226
273
  /** The combined read-manifest artifact (the EXACT wire shape the Rust read engine parses). */
227
274
  export interface ReadManifest {
228
275
  aggregateFieldKinds: AggregateFieldKinds;
@@ -231,6 +278,11 @@ export interface ReadManifest {
231
278
  spatials: SpatialDescriptor[];
232
279
  deriveds: DerivedDescriptor[];
233
280
  combineds: CombinedDescriptor[];
281
+ /** OMITTED when no domain declares a sum (read manifests predating sums stay byte-identical). */
282
+ sums?: SumDescriptor[];
283
+ /** OMITTED when no domain declares a min/max (extremum-free read manifests stay byte-identical). */
284
+ mins?: ExtremumDescriptor[];
285
+ maxes?: ExtremumDescriptor[];
234
286
  }
235
287
 
236
288
  type ZodLike = {
@@ -332,7 +384,9 @@ function describeCount(byType: AggregateFieldKinds, c: CountDecl): CountDescript
332
384
  `the of-type must be an aggregate emitted into the manifest.`,
333
385
  );
334
386
  }
335
- return { id: c.id, of: c.of, by: c.by ?? null };
387
+ // `where` rides to the projection (WireCount parses it) OMITTED when absent, so
388
+ // predicate-free read manifests keep their exact pre-slice-4 bytes.
389
+ return { id: c.id, of: c.of, by: c.by ?? null, ...(c.where !== undefined ? { where: c.where } : {}) };
336
390
  }
337
391
 
338
392
  function describeSpatial(byType: AggregateFieldKinds, s: SpatialDecl): SpatialDescriptor {
@@ -443,6 +497,63 @@ export function buildReadManifest(modules: readonly DomainModule[]): ReadManifes
443
497
  }
444
498
  const counts = [...countsById.values()].sort((a, b) => a.id.localeCompare(b.id));
445
499
 
500
+ // Maintained SUMS (sharding slice 3 routes the derived `nomosEstateSummary` through
501
+ // here; tenant sums ride the same lane). The projection's `WireSum` names the summed
502
+ // field `field`. Omitted entirely when none — sum-free read manifests stay byte-identical.
503
+ const sumsById = new Map<string, SumDescriptor>();
504
+ for (const mod of modules) {
505
+ for (const raw of mod.sums ?? []) {
506
+ const s = finishSum(raw);
507
+ if (sorted[s.of] === undefined) {
508
+ throw new Error(
509
+ `build-package: sum '${s.id}' tallies '${s.of}', which has no field schema — ` +
510
+ `the of-type must be an aggregate emitted into the manifest.`,
511
+ );
512
+ }
513
+ const descriptor: SumDescriptor = { id: s.id, of: s.of, field: s.sumField, by: s.by ?? null, ...(s.where !== undefined ? { where: s.where } : {}) };
514
+ const existing = sumsById.get(s.id);
515
+ if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(descriptor)) {
516
+ throw new Error(
517
+ `build-package: sum id '${s.id}' is declared with DIVERGENT maintenance across ` +
518
+ `domains — one id cannot map to two totals. Reconcile the sum declaration.`,
519
+ );
520
+ }
521
+ sumsById.set(s.id, descriptor);
522
+ }
523
+ }
524
+ const sums = [...sumsById.values()].sort((a, b) => a.id.localeCompare(b.id));
525
+
526
+ // Maintained MIN/MAX reads (#47 — the extremum RPC's manifest half): the SAME lane
527
+ // as sums, two arrays (the kind is implicit in which array a descriptor rides —
528
+ // the Rust `WireExtremum` has parsed them since the read-engine slice). Omitted
529
+ // entirely when none — extremum-free read manifests stay byte-identical.
530
+ const lowerExtrema = (key: "mins" | "maxes"): ExtremumDescriptor[] => {
531
+ const byId = new Map<string, ExtremumDescriptor>();
532
+ for (const mod of modules) {
533
+ for (const raw of mod[key] ?? []) {
534
+ const e = finishExtremum(raw);
535
+ if (sorted[e.of] === undefined) {
536
+ throw new Error(
537
+ `build-package: ${key === "mins" ? "min" : "max"} '${e.id}' reads '${e.of}', which has no field schema — ` +
538
+ `the of-type must be an aggregate emitted into the manifest.`,
539
+ );
540
+ }
541
+ const descriptor: ExtremumDescriptor = { id: e.id, of: e.of, field: e.valueField, by: e.by ?? null, ...(e.where !== undefined ? { where: e.where } : {}) };
542
+ const existing = byId.get(e.id);
543
+ if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(descriptor)) {
544
+ throw new Error(
545
+ `build-package: ${key === "mins" ? "min" : "max"} id '${e.id}' is declared with DIVERGENT ` +
546
+ `maintenance across domains — one id cannot map to two extrema. Reconcile the declaration.`,
547
+ );
548
+ }
549
+ byId.set(e.id, descriptor);
550
+ }
551
+ }
552
+ return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
553
+ };
554
+ const mins = lowerExtrema("mins");
555
+ const maxes = lowerExtrema("maxes");
556
+
446
557
  const spatialsById = new Map<string, SpatialDescriptor>();
447
558
  for (const mod of modules) {
448
559
  for (const s of mod.spatials ?? []) {
@@ -497,7 +608,17 @@ export function buildReadManifest(modules: readonly DomainModule[]): ReadManifes
497
608
  (a, b) => a.of.localeCompare(b.of) || a.id.localeCompare(b.id),
498
609
  );
499
610
 
500
- return { aggregateFieldKinds: sorted, queries, counts, spatials, deriveds, combineds };
611
+ return {
612
+ aggregateFieldKinds: sorted,
613
+ queries,
614
+ counts,
615
+ spatials,
616
+ deriveds,
617
+ combineds,
618
+ ...(sums.length > 0 ? { sums } : {}),
619
+ ...(mins.length > 0 ? { mins } : {}),
620
+ ...(maxes.length > 0 ? { maxes } : {}),
621
+ };
501
622
  }
502
623
 
503
624
  // ─────────────────────────────────────────────────────────────────────────────────
@@ -45,6 +45,7 @@ import type { OrderedReadDecl } from "./ordered_read.js";
45
45
  import type { DerivedDecl } from "./derived.js";
46
46
  import type { CombinedDecl } from "./combined.js";
47
47
  import { emitProviderSdk, type ImpureCapabilityDecl } from "./codegen_provider_dart.js";
48
+ import type { WorkspaceTypeDecl } from "./workspace_type.js";
48
49
 
49
50
  /** A directive of any payload type. `Directive<P>` is invariant in `P`, so the
50
51
  * generator (which only introspects shapes) takes the `any`-payload form. */
@@ -2407,6 +2408,20 @@ export interface DomainModule {
2407
2408
  * aggregate `hasInvariant` / a directive `plan`). See `docs/workspace_invariant.md`.
2408
2409
  */
2409
2410
  workspaceInvariants?: WorkspaceInvariantDecl[];
2411
+ /**
2412
+ * The domain's FIRST-CLASS WORKSPACE TYPES (`workspace_type.ts`; sharding §10
2413
+ * RATIFIED, slice 1) — the law-declared workspace taxonomy
2414
+ * (`workspaceType("estate").root(Estate).hasMany(() => SiteHome)`). ADDITIVE +
2415
+ * OPTIONAL: a domain that declares none omits this entirely and is byte-identical
2416
+ * in the canonical manifest to before this key existed (taxonomy-free domains are
2417
+ * hash-stable). When declared, `manifest.ts` lowers the taxonomy AND the derived
2418
+ * homing table (nearest packed axis via the aggregates' `t.ref` chains) into the
2419
+ * canonical manifest — hash-bearing law — after the FAIL-CLOSED homing walk
2420
+ * (no-path / ambiguous / cross-home = compile error with a named remedy). Birth
2421
+ * lanes are DERIVED from `hasMany` over the EXISTING platform machinery
2422
+ * (`codegen_ts.ts` emits the typed helpers); domain devs never see a compound key.
2423
+ */
2424
+ workspaceTypes?: WorkspaceTypeDecl[];
2410
2425
  /**
2411
2426
  * The domain's ENGINE-PROJECTED DERIVED read fields (read-engine: derived read fields).
2412
2427
  * Each is a PURE fn of ONE aggregate's folded fields, computed BY THE ENGINE during the
package/src/codegen_ts.ts CHANGED
@@ -45,6 +45,8 @@ import { finishCount } from "./count.js";
45
45
  import { finishSum } from "./sum.js";
46
46
  import type { DerivedDecl } from "./derived.js";
47
47
  import type { CombinedDecl } from "./combined.js";
48
+ import { resolveWorkspaceTypes, type ResolvedWorkspaceType, type WorkspaceTypeDecl } from "./workspace_type.js";
49
+ import { deriveClientMintPlan, type ClientMintPlanEntry } from "./workspace_routing.js";
48
50
 
49
51
  const cap = (s: string) => (s.length ? s[0]!.toUpperCase() + s.slice(1) : s);
50
52
  const camel = (s: string) => s.replace(/[_\s-]+(\w)/g, (_m, c: string) => c.toUpperCase());
@@ -187,18 +189,33 @@ export function autoStampFields(schema: z.ZodTypeAny): string[] {
187
189
  return out;
188
190
  }
189
191
 
190
- /** The payload type text, with auto-stamped fields surfaced as optional (`?`). */
191
- function tsTypeOfPayload(schema: z.ZodTypeAny, autoStamped: readonly string[]): string {
192
- if (zodKind(schema) !== "object" || autoStamped.length === 0) return tsTypeOfZod(schema, "");
192
+ /** The payload type text, with auto-stamped / front-door-minted / auto-placed fields optional (`?`). */
193
+ function tsTypeOfPayload(
194
+ schema: z.ZodTypeAny,
195
+ autoStamped: readonly string[],
196
+ minted: readonly string[] = [],
197
+ autoPlaced: readonly string[] = [],
198
+ ): string {
199
+ if (zodKind(schema) !== "object" || (autoStamped.length === 0 && minted.length === 0 && autoPlaced.length === 0)) {
200
+ return tsTypeOfZod(schema, "");
201
+ }
193
202
  const stamped = new Set(autoStamped);
203
+ const mintedSet = new Set(minted);
204
+ const placedSet = new Set(autoPlaced);
194
205
  const fields = Object.entries(zodObjectShape(schema)).map(([name, raw]) => {
195
206
  let f = raw as z.ZodTypeAny;
196
- let optional = stamped.has(name);
207
+ let optional = stamped.has(name) || mintedSet.has(name) || placedSet.has(name);
197
208
  while (zodKind(f) === "optional" || zodKind(f) === "default") {
198
209
  optional = true;
199
210
  f = zodDef(f).innerType!;
200
211
  }
201
- const note = stamped.has(name) ? " // auto-stamped with ISO now() at dispatch when omitted" : "";
212
+ const note = stamped.has(name)
213
+ ? " // auto-stamped with ISO now() at dispatch when omitted"
214
+ : mintedSet.has(name)
215
+ ? " // kernel-minted at dispatch when omitted (the front-door mint; returned on the result)"
216
+ : placedSet.has(name)
217
+ ? " // picked LEAST-LOADED over the coordinator's law-state at dispatch when omitted (sharding §3)"
218
+ : "";
202
219
  return ` ${name}${optional ? "?" : ""}: ${tsTypeOfZod(f, " ")};${note}`;
203
220
  });
204
221
  return fields.length ? `{\n${fields.join("\n")}\n}` : "Record<string, never>";
@@ -266,6 +283,21 @@ export interface TsClientOptions {
266
283
  readonly domainHash: string;
267
284
  }
268
285
 
286
+ /** TRUE when any module declares a PACKED workspace taxonomy (the routed-holon shape).
287
+ * Gates every slice-4 paged-surface emission: taxonomy-free clients stay BYTE-IDENTICAL. */
288
+ function anyPackedTaxonomy(modules: readonly DomainModule[]): boolean {
289
+ for (const mod of modules) {
290
+ const types = (mod as { workspaceTypes?: readonly WorkspaceTypeDecl[] }).workspaceTypes;
291
+ if (!types || types.length === 0) continue;
292
+ try {
293
+ for (const t of resolveWorkspaceTypes(types, mod.name).values()) {
294
+ if (t.mode === "packed") return true;
295
+ }
296
+ } catch { /* an unresolvable taxonomy is the compile gate's verdict, never codegen's */ }
297
+ }
298
+ return false;
299
+ }
300
+
269
301
  export function generateTsClient(modules: readonly DomainModule[], opts: TsClientOptions): string {
270
302
  const hashConst = tsHashConstName(opts.packageName);
271
303
  const out: string[] = [];
@@ -294,6 +326,12 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
294
326
  ` /** Declared-count / declared-sum O(1) reads (maintained by the read engine). */`,
295
327
  ` count(countId: string, groupKey?: string): Promise<number>;`,
296
328
  ` sum(sumId: string, groupKey?: string): Promise<number>;`,
329
+ ...(anyPackedTaxonomy(modules)
330
+ ? [
331
+ ` /** THE LOGICAL PAGED ROW SURFACE (sharding §5.3, ruling 2) — present on a ROUTED holon. */`,
332
+ ` pages?(queryId: string, params?: Record<string, unknown>, page?: { pageSize?: number; cursor?: string | null }): Promise<{ rows: unknown[]; cursor: string | null; done: boolean }>;`,
333
+ ]
334
+ : []),
297
335
  ` /** The DEAD-LETTER QUEUE — refused-but-legitimate intents the app must handle`,
298
336
  ` * explicitly (inspect → retry after a law fix → or discard). Work is never lost. */`,
299
337
  ` deadLetters(): Promise<{ id?: string; oid: string; source: string; error: string; domain?: string | null; directiveId?: string | null; at: string }[]>;`,
@@ -351,6 +389,149 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
351
389
  out.push(`}`, ``);
352
390
  }
353
391
 
392
+ // ── DERIVED BIRTH LANES (sharding §10 RATIFIED, slice 1) ──
393
+ // A `workspaceType(...).hasMany(...)` taxonomy DERIVES typed birth helpers over
394
+ // the EXISTING platform machinery (`POST /v1/platforms/...` — derived, never
395
+ // forked). Fully typesafe per Jack's ruling: the caller passes LOGICAL names
396
+ // (parent param named after the parent TYPE); the `p--ws` composition is internal
397
+ // plumbing — the composed workspace name comes back on the result for `connect()`.
398
+ // PACKED child types derive NO holon-birth helper: a packed birth is a shard-map
399
+ // PLACEMENT (slice 2's lane).
400
+ const birthModules = modules.filter(
401
+ (m) => m.workspaceTypes !== undefined && m.workspaceTypes.length > 0,
402
+ );
403
+ // THE FRONT-DOOR MINT PLAN (sharding slice 2; taxonomy packages ONLY — a
404
+ // taxonomy-free package's generated client is byte-identical to before this
405
+ // existed): routed `.creates` directives whose target id rides the payload mint
406
+ // it through the holon's kernel mint with the home's ROUTE TAG folded in
407
+ // (self-routing ids, §4); the derived placement directives mint the axis-root id.
408
+ const mintPlans = new Map<DomainModule, Map<string, ClientMintPlanEntry>>();
409
+ for (const mod of birthModules) {
410
+ const plan = deriveClientMintPlan(
411
+ mod as {
412
+ name: string;
413
+ aggregates: AggregateHandle[];
414
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
415
+ directives: any[];
416
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
417
+ workspaceTypes?: any[];
418
+ },
419
+ );
420
+ if (plan.length > 0) mintPlans.set(mod, new Map(plan.map((e) => [e.directive, e])));
421
+ }
422
+ if (mintPlans.size > 0) {
423
+ out.push(
424
+ `// ── the front-door kernel mint (routed creates — ids are internal plumbing) ──`,
425
+ ``,
426
+ `/** The ROUTED holon surface (@githolon/client with a taxonomy law): the kernel mint. */`,
427
+ `export interface NomosRoutedHolon extends NomosHolon {`,
428
+ ` /** Reserve a kernel-minted typed id; \`homeKey\`/\`tagHex\` fold the home's route tag in. */`,
429
+ ` mint(typeTag: string, opts?: { homeKey?: string; tagHex?: string }): Promise<string>;`,
430
+ `}`,
431
+ ``,
432
+ `/** The 48-bit route-tag slot of a kernel-minted id (the UUIDv7 leading 12 hex chars). */`,
433
+ `const routeTagOfId = (id: string): string | undefined => {`,
434
+ ` const i = id.indexOf("_");`,
435
+ ` if (i <= 0) return undefined;`,
436
+ ` const body = id.slice(i + 1).replace(/-/g, "").toLowerCase();`,
437
+ ` return /^[0-9a-f]{32}$/.test(body) ? body.slice(0, 12) : undefined;`,
438
+ `};`,
439
+ ``,
440
+ );
441
+ }
442
+ if (birthModules.length > 0) {
443
+ out.push(
444
+ `// ── derived birth lanes (workspace taxonomy → the EXISTING platform lanes) ──`,
445
+ ``,
446
+ `/** Auth a birth presents — every birth needs a principal (the cloud's law). */`,
447
+ `export interface NomosBirthAuth {`,
448
+ ` /** Auth-provider access token, sent as \`x-nomos-auth\` (verified by the cloud). */`,
449
+ ` authToken?: string;`,
450
+ ` /** Transitional bare principal uid, sent as \`x-nomos-principal\` (no token). */`,
451
+ ` principal?: string;`,
452
+ ` /** Bearer credential when the lane is gated (platform admin key / invite key). */`,
453
+ ` bearer?: string;`,
454
+ `}`,
455
+ ``,
456
+ `/** A birth lane's response — \`workspace\` is the born holon's addressable name. */`,
457
+ `export interface NomosBirthResult {`,
458
+ ` ok: boolean;`,
459
+ ` /** The addressable workspace name (pass it to \`connect({ workspace })\`). */`,
460
+ ` workspace?: string;`,
461
+ ` /** Returned ONCE at birth — the workspace's deploy credential. SAVE IT. */`,
462
+ ` workspaceSecret?: string;`,
463
+ ` /** Platform births only — the platform's own governance credential, ONCE. */`,
464
+ ` platformAdminKey?: string;`,
465
+ ` error?: string;`,
466
+ ` [k: string]: unknown;`,
467
+ `}`,
468
+ ``,
469
+ `const nomosBirth = async (cloud: string, path: string, auth: NomosBirthAuth): Promise<NomosBirthResult> => {`,
470
+ ` const headers: Record<string, string> = {};`,
471
+ ` if (auth.authToken !== undefined) headers["x-nomos-auth"] = auth.authToken;`,
472
+ ` if (auth.principal !== undefined) headers["x-nomos-principal"] = auth.principal;`,
473
+ ` if (auth.bearer !== undefined) headers["authorization"] = \`Bearer \${auth.bearer}\`;`,
474
+ ` const res = await fetch(\`\${cloud.replace(/\\/$/, "")}\${path}\`, { method: "POST", headers });`,
475
+ ` return (await res.json()) as NomosBirthResult;`,
476
+ `};`,
477
+ ``,
478
+ );
479
+ const emittedBirths = new Set<string>();
480
+ for (const mod of birthModules) {
481
+ let types: Map<string, ResolvedWorkspaceType>;
482
+ try {
483
+ types = resolveWorkspaceTypes(mod.workspaceTypes!, mod.name);
484
+ } catch (e) {
485
+ // The compile gate (compile_package_main) already failed a bad taxonomy
486
+ // loudly; reaching here means codegen was driven directly — same verdict.
487
+ throw new Error(`codegen-ts: ${(e as Error).message}`);
488
+ }
489
+ const childIds = new Set([...types.values()].flatMap((t) => [...t.childIds]));
490
+ const fnName = (typeId: string) => `birth${cap(camel(typeId))}`;
491
+ const emit = (name: string, doc: string, params: string[], pathExpr: string) => {
492
+ if (emittedBirths.has(name)) return;
493
+ emittedBirths.add(name);
494
+ out.push(
495
+ `/** ${doc} */`,
496
+ `export const ${name} = (opts: NomosBirthAuth & { cloud: string; ${params.join("; ")} }): Promise<NomosBirthResult> =>`,
497
+ ` nomosBirth(opts.cloud, ${pathExpr}, opts);`,
498
+ ``,
499
+ );
500
+ };
501
+ for (const t of types.values()) {
502
+ if (t.mode === "packed") continue; // placement, not a holon birth (slice 2)
503
+ const isPlatform = t.childIds.length > 0;
504
+ if (!childIds.has(t.id)) {
505
+ // A TOP-LEVEL type: births directly under the cloud.
506
+ emit(
507
+ fnName(t.id),
508
+ `Birth a '${t.id}' workspace${isPlatform ? " (a platform-kind coordinator: hosts its own subtree)" : ""} — derived from the taxonomy over the existing ${isPlatform ? "platform" : "workspace"} lane.`,
509
+ isPlatform ? [`name: string`] : [`name: string`, `tier?: "edge" | "heavy"`],
510
+ isPlatform
511
+ ? `\`/v1/platforms/\${encodeURIComponent(opts.name)}\``
512
+ : `\`/v1/workspaces/\${encodeURIComponent(opts.name)}\${opts.tier === "heavy" ? "?tier=heavy" : ""}\``,
513
+ );
514
+ }
515
+ for (const childId of t.childIds) {
516
+ const child = types.get(childId)!;
517
+ if (child.mode === "packed") continue;
518
+ const parentParam = lcFirst(camel(t.id));
519
+ const childIsPlatform = child.childIds.length > 0;
520
+ emit(
521
+ fnName(child.id),
522
+ `Birth a '${child.id}' under a '${t.id}' — the taxonomy's hasMany edge over the existing platform lane (lineage + pool enforced by the parent's law).`,
523
+ childIsPlatform
524
+ ? [`${parentParam}: string`, `name: string`]
525
+ : [`${parentParam}: string`, `name: string`, `tier?: "edge" | "heavy"`],
526
+ childIsPlatform
527
+ ? `\`/v1/platforms/\${encodeURIComponent(opts.${parentParam})}/platforms/\${encodeURIComponent(opts.name)}\``
528
+ : `\`/v1/platforms/\${encodeURIComponent(opts.${parentParam})}/workspaces/\${encodeURIComponent(opts.name)}\${opts.tier === "heavy" ? "?tier=heavy" : ""}\``,
529
+ );
530
+ }
531
+ }
532
+ }
533
+ }
534
+
354
535
  // ── payload types + per-domain client factories ──
355
536
  const usedPayloadNames = new Set<string>();
356
537
  for (const mod of modules) {
@@ -360,6 +541,18 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
360
541
  );
361
542
  out.push(`// ── domain "${domain}" ──`, ``);
362
543
 
544
+ const mintPlan = mintPlans.get(mod);
545
+ // PLACEMENT directives (packed taxonomy births): their `shard` is AUTO-PICKED
546
+ // least-loaded by the routed holon at dispatch — optional on the typed surface
547
+ // (the §7 transparency contract: a dev never names a shard).
548
+ const placementDirs = new Set<string>();
549
+ if (mod.workspaceTypes !== undefined && mod.workspaceTypes.length > 0) {
550
+ try {
551
+ for (const t of resolveWorkspaceTypes(mod.workspaceTypes, mod.name).values()) {
552
+ if (t.mode === "packed") placementDirs.add(`birth${cap(camel(t.id))}`);
553
+ }
554
+ } catch { /* the compile gate already failed a bad taxonomy loudly */ }
555
+ }
363
556
  const methods: string[] = [];
364
557
  for (const d of dirs) {
365
558
  let payloadName = `${cap(d.id)}Payload`;
@@ -367,11 +560,34 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
367
560
  usedPayloadNames.add(payloadName);
368
561
  const schema = (d as unknown as { payloadSchema: z.ZodTypeAny }).payloadSchema;
369
562
  const stamps = autoStampFields(schema);
563
+ const mint = mintPlan?.get(d.id);
564
+ const placed = placementDirs.has(d.id) ? ["shard"] : [];
370
565
  out.push(
371
- `/** \`${domain}/${d.id}\` payload (mirrors the engine's zod schema).${stamps.length ? ` Omitted ${stamps.map((s) => `\`${s}\``).join("/")} auto-stamp(s) ISO now() at dispatch.` : ""} */`,
372
- `export type ${payloadName} = ${tsTypeOfPayload(schema, stamps)};`,
566
+ `/** \`${domain}/${d.id}\` payload (mirrors the engine's zod schema).${stamps.length ? ` Omitted ${stamps.map((s) => `\`${s}\``).join("/")} auto-stamp(s) ISO now() at dispatch.` : ""}${mint ? ` Omitted \`${mint.field}\` is KERNEL-MINTED at dispatch (returned on the result).` : ""}${placed.length ? " Omitted `shard` is picked LEAST-LOADED at dispatch (the routed holon)." : ""} */`,
567
+ `export type ${payloadName} = ${tsTypeOfPayload(schema, stamps, mint ? [mint.field] : [], placed)};`,
373
568
  ``,
374
569
  );
570
+ if (mint !== undefined) {
571
+ const mintOpts =
572
+ mint.homeKeyField !== undefined
573
+ ? `, { homeKey: payload.${mint.homeKeyField} }`
574
+ : mint.tagFromIdField !== undefined
575
+ ? `, { tagHex: routeTagOfId(payload.${mint.tagFromIdField}) }`
576
+ : "";
577
+ const body = stamps.length
578
+ ? `stampNow({ ...payload, ${mint.field} }, ${JSON.stringify(stamps)})`
579
+ : `{ ...payload, ${mint.field} }`;
580
+ methods.push(
581
+ ` /** Author \`${domain}/${d.id}\`: the front-door MINTS \`${mint.field}\` (route-tagged) when omitted`,
582
+ ` * — ids stay internal plumbing — and returns it with the new local head. */`,
583
+ ` ${d.id}: async (payload: ${payloadName}): Promise<{ head: string; ${mint.field}: string }> => {`,
584
+ ` const ${mint.field} = payload.${mint.field} ?? (await (holon as NomosRoutedHolon).mint(${JSON.stringify(mint.mintType)}${mintOpts}));`,
585
+ ` const head = await holon.dispatch(${JSON.stringify(domain)}, ${JSON.stringify(d.id)}, ${body}, { domainHash });`,
586
+ ` return { head, ${mint.field} };`,
587
+ ` },`,
588
+ );
589
+ continue;
590
+ }
375
591
  methods.push(
376
592
  ` /** Author \`${domain}/${d.id}\` locally under the installed law; returns the new local head. */`,
377
593
  stamps.length
@@ -394,6 +610,18 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
394
610
  ` /** Declared query \`${q.id}\` (indexed probe — routed by the read manifest). */`,
395
611
  ` ${lcFirst(camel(q.id))}: (params: { ${params} }) => holon.query(${JSON.stringify(q.id)}, params) as Promise<Row<${q.returns}Data>[]>,`,
396
612
  );
613
+ // THE LOGICAL PAGED ROW SURFACE (sharding §5.3, ruling 2 — slice 4): emitted
614
+ // ONLY for packed-taxonomy domains (taxonomy-free clients stay byte-identical).
615
+ // The dev sees ordinary pagination; the shard walk is the implementation.
616
+ if (anyPackedTaxonomy(modules)) {
617
+ methods.push(
618
+ ` /** \`${q.id}\`, paged at estate width (ruling 2): ordinary pagination, stable cursor. */`,
619
+ ` ${lcFirst(camel(q.id))}Pages: (params: { ${params} }, page?: { pageSize?: number; cursor?: string | null }) => {`,
620
+ ` if (!holon.pages) throw new Error("pages() needs the ROUTED holon - connect() to the logical (coordinator) workspace, not a shard");`,
621
+ ` return holon.pages(${JSON.stringify(q.id)}, params, page) as Promise<{ rows: Row<${q.returns}Data>[]; cursor: string | null; done: boolean }>;`,
622
+ ` },`,
623
+ );
624
+ }
397
625
  }
398
626
 
399
627
  for (const c of (mod.counts ?? []).map((raw) => finishCount(raw))) {