@githolon/dsl 0.105.7 → 0.105.9

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/dart/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.64.25
2
+
3
+ - Rebuild the vendored Dart runner from `@githolon/client` 0.105.8. Web-client source is an input to
4
+ this immutable pub.dev archive, so the Dart package now advances whenever that source changes.
5
+
1
6
  ## 0.64.24
2
7
 
3
8
  - Restaged with the current kernel candidate: regenerated vendored assets (kernel release identity is baked into them). Published pub.dev archives are immutable, so regenerated bytes ride their own version.
@@ -8499,6 +8499,9 @@ function createSerializedScheduler() {
8499
8499
  return scheduler;
8500
8500
  }
8501
8501
 
8502
+ // src/intent_offer_codec.ts
8503
+ var encoder = new TextEncoder();
8504
+
8502
8505
  // src/index.ts
8503
8506
  var enc4 = new TextEncoder();
8504
8507
  var dec4 = new TextDecoder();
package/dart/pubspec.yaml CHANGED
@@ -7,7 +7,7 @@ repository: https://github.com/Captain-App/nomos2
7
7
  description: >-
8
8
  Nomos 2 Dart client — generated domain values, intent payloads and application
9
9
  lifecycle types over the typed Nomos kernel protocol and USDA custody model.
10
- version: 0.64.24
10
+ version: 0.64.25
11
11
 
12
12
  environment:
13
13
  sdk: ^3.5.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.105.7",
3
+ "version": "0.105.9",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
@@ -7530,6 +7530,9 @@ function createSerializedScheduler() {
7530
7530
  return scheduler;
7531
7531
  }
7532
7532
 
7533
+ // ../cloud/web-client/src/intent_offer_codec.ts
7534
+ var encoder = new TextEncoder();
7535
+
7533
7536
  // ../cloud/web-client/src/index.ts
7534
7537
  var enc4 = new TextEncoder();
7535
7538
  var dec4 = new TextDecoder();
@@ -575,7 +575,7 @@ function introspectField(
575
575
  case "ZodArray": {
576
576
  const elem = zodArrayElement(zt);
577
577
  // A `string[]` is a typed `List<String>`. A list of DECLARED VALUE OBJECTS is typed
578
- // with the generated VO class (vo_authoring_end_to_end.md slice B). ANY other element
578
+ // with the generated VO class (vo_first_class.md). ANY other element
579
579
  // type is arbitrary JSON DATA to the engine, modelled as `List<Object?>`.
580
580
  if (zodTypeName(elem) === "ZodString") {
581
581
  return { name, dartType: "List<String>", toJson: "strList" };
@@ -239,7 +239,7 @@ function sampleOfZod(
239
239
  next.add(zt);
240
240
  return sampleOfZod(zodUnwrapLazy(zt), path, where, voIncludeOptional, next);
241
241
  }
242
- // VALUE OBJECTS FIRST (vo_authoring_end_to_end.md slice C): a declared VO carries its own lawful
242
+ // VALUE OBJECTS FIRST (vo_first_class.md): a declared VO carries its own lawful
243
243
  // sample (the author's validated `example`, the blessed geometry sample, or per-field descriptor
244
244
  // synthesis, recursing through nested VOs / arrays-of-VOs / unions). The raw-zod walk below cannot
245
245
  // see interior constraints (geo's min-2 coordinates, a `.min()` on a subfield), so after building the
package/src/codegen_ts.ts CHANGED
@@ -333,7 +333,7 @@ function tsTypeOfZod(
333
333
  const target = idOfTargetKindOf(zt) === "contract" ? contractTypeName(aggregateType) : aggregateType;
334
334
  return `NomosRef<${target}>`;
335
335
  }
336
- // A DECLARED VALUE OBJECT keeps its NAME (vo_authoring_end_to_end.md slice B): the payload
336
+ // A DECLARED VALUE OBJECT keeps its NAME (vo_first_class.md): the payload
337
337
  // references the same exported interface the read model uses — one name, both directions —
338
338
  // instead of an anonymous inline literal type. Checked on the BARE object only: optional/
339
339
  // default wrappers must keep flowing through the switch so `| undefined` survives.
@@ -761,6 +761,9 @@ export interface TsClientOptions {
761
761
  /** Explicit application home for direct/synthetic generation. The package compiler derives this from
762
762
  * business birth topology and passes it here; ambiguous multi-domain packages fail rather than flatten. */
763
763
  readonly homeDomain?: string;
764
+ /** Emit the structural holon factory for the compiler-owned headless artifact. The ordinary tenant
765
+ * client leaves this false so framework transport types never enter its public declaration graph. */
766
+ readonly exportHolonFactory?: boolean;
764
767
  }
765
768
 
766
769
  /** TS string literal (JSON.stringify covers escaping). */
@@ -1890,9 +1893,9 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1890
1893
  ` /** THE ACTIVITY FEED (declared \`.activity(...)\`): subscribe to per-committed-intent RAW ROWS`,
1891
1894
  ` * (the runtime folds the intent log). \`cb\` fires with the current rows + on every new intent;`,
1892
1895
  ` * returns an unsubscribe fn. The generated client maps these through \`renderActivity\`. */`,
1893
- ` watchActivity(cb: (rows: ActivityRawRow[]) => void, opts?: { base?: string; intervalMs?: number }): () => void;`,
1896
+ ` watchActivity?(cb: (rows: ActivityRawRow[]) => void, opts?: { base?: string; intervalMs?: number }): () => void;`,
1894
1897
  ` /** One-shot: the current committed activity log as RAW ROWS (folded to typed rows by the client). */`,
1895
- ` activityLog(opts?: { base?: string; limit?: number }): Promise<ActivityRawRow[]>;`,
1898
+ ` activityLog?(opts?: { base?: string; limit?: number }): Promise<ActivityRawRow[]>;`,
1896
1899
  ]
1897
1900
  : []),
1898
1901
  ` /** THE COMPAT SUPERPOWER (#72b) — OPTIONAL: a holon from @githolon/client's connect() attaches it.`,
@@ -3658,15 +3661,15 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
3658
3661
  ` * Every public directive has a baseline; \`.activity(...)\` enriches it. Framework/control work`,
3659
3662
  ` * outside this generated package is dropped. Returns stop(). */`,
3660
3663
  ` watchActivity: (cb: (events: ActivityEvent[]) => void, opts?: { base?: string }) =>`,
3661
- ` holon.watchActivity((rows) => cb(rows.map(renderActivity).filter((e): e is ActivityEvent => e !== null)), opts),`,
3664
+ ` _nomosWatchActivity((rows) => cb(rows.map(renderActivity).filter((e): e is ActivityEvent => e !== null)), opts),`,
3662
3665
  ` /** One-shot: the current committed activity log, folded to typed [ActivityEvent]s. */`,
3663
3666
  ` activityLog: async (opts?: { base?: string; limit?: number }): Promise<ActivityEvent[]> =>`,
3664
- ` (await holon.activityLog(opts)).map(renderActivity).filter((e): e is ActivityEvent => e !== null),`,
3667
+ ` (await _nomosActivityLog(opts)).map(renderActivity).filter((e): e is ActivityEvent => e !== null),`,
3665
3668
  ` /** THE SELF-DESCRIPTION: root links to exactly six grounded pages — model, current state, rules,`,
3666
3669
  ` * history, decisions and evidence. The old \`events\` / \`transcript\` fields remain, so this is a`,
3667
3670
  ` * strict enrichment of the activity narrative. \`section\` selects one page. */`,
3668
3671
  ` describe: async (opts: { section?: NomosDescribeSection; since?: string; performedBy?: string; category?: string; limit?: number } = {}): Promise<NomosDescription> => {`,
3669
- ` const raw = await holon.activityLog(opts.limit !== undefined ? { limit: opts.limit } : {});`,
3672
+ ` const raw = await _nomosActivityLog(opts.limit !== undefined ? { limit: opts.limit } : {});`,
3670
3673
  ` let events = raw.map(renderActivity).filter((e): e is ActivityEvent => e !== null);`,
3671
3674
  ` if (opts.performedBy !== undefined) events = events.filter((e) => e.performedBy === opts.performedBy);`,
3672
3675
  ` if (opts.category !== undefined) events = events.filter((e) => e.category === opts.category);`,
@@ -3778,7 +3781,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
3778
3781
  ` * point at it (only directives declaring a ref to the aggregate appear in its story) + a transcript. */`,
3779
3782
  ` describeHistory: async <TAggregate>(record: NomosRef<TAggregate>, opts: { limit?: number } = {}): Promise<ActivityNarrative> => {`,
3780
3783
  ` const aggregateId = _nmsReferenceId(record, "", holon.workspace, "record");`,
3781
- ` const raw = await holon.activityLog(opts.limit !== undefined ? { limit: opts.limit } : {});`,
3784
+ ` const raw = await _nomosActivityLog(opts.limit !== undefined ? { limit: opts.limit } : {});`,
3782
3785
  ` const events = raw.map(renderActivity).filter((e): e is ActivityEvent => e !== null)`,
3783
3786
  ` .filter((e) => Object.values(e.refs).some((r) => _nmsReferenceData(r).id === aggregateId));`,
3784
3787
  ` return { events, transcript: _activityTranscript(events) };`,
@@ -3789,9 +3792,19 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
3789
3792
  out.push(
3790
3793
  `/** Framework binding for the generated application. Law identity, author identity and key custody`,
3791
3794
  ` * are captured from the connected stateful client; callers cannot override them. */`,
3792
- `function ${tsClientFactoryName(domain)}(holon: NomosHolon) {`,
3795
+ `/** THE HOLON-LEVEL FACTORY — the headless/code-mode binding (headless-operation.md). Binds`,
3796
+ ` * structurally to any NomosHolon: a full \`connect()\` holon, or the thin remote holon`,
3797
+ ` * (\`connectRemote\`) for isolates that live beside the edge. The application surface`,
3798
+ ` * (\`open<Name>Application\`) remains the batteries-included entry for devices. */`,
3799
+ `${opts.exportHolonFactory === true ? "export " : ""}function ${tsClientFactoryName(domain)}(holon: NomosHolon) {`,
3793
3800
  ` const domainHash = ${hashConst};`,
3794
3801
  ` const actor = holon.principal;`,
3802
+ ...(activityPresent
3803
+ ? [
3804
+ ` const _nomosActivityLog = (opts?: { base?: string; limit?: number }) => { if (!holon.activityLog) throw new Error("activity reads need a custody-capable @githolon/client binding"); return holon.activityLog(opts); };`,
3805
+ ` const _nomosWatchActivity = (cb: (rows: ActivityRawRow[]) => void, opts?: { base?: string; intervalMs?: number }) => { if (!holon.watchActivity) throw new Error("activity watches need a custody-capable @githolon/client binding"); return holon.watchActivity(cb, opts); };`,
3806
+ ]
3807
+ : []),
3795
3808
  ` holon.prepareDomain?.(${JSON.stringify(domain)}, domainHash);`,
3796
3809
  ...(encPresent ? [` const keyStore: NomosKeyStore | undefined = ${domScopes.length > 0 ? `{ scopeKey: async (scope) => { if (!holon.__nomosScopeKey) throw new Error("encrypted business data needs the current @githolon/client"); return holon.__nomosScopeKey({ scope }); }, readKeys: async (scopes) => { if (!holon.__nomosScopeKeys) throw new Error("encrypted business data needs the current @githolon/client"); return holon.__nomosScopeKeys({ scopes }); } }` : "undefined"};`] : []),
3797
3810
  ...(ownedScope !== undefined && Object.keys(ownedActions).length > 0
@@ -1,4 +1,4 @@
1
- // The CLIENT/LAW COMPATIBILITY MANIFEST (§5, architecture/plug_and_play_dx.md).
1
+ // The CLIENT/LAW COMPATIBILITY MANIFEST (architecture/shape_compatibility.md).
2
2
  //
3
3
  // A DIAGNOSIS surface, NEVER a gate. Clients fit workspaces by STRUCTURAL INTERFACE
4
4
  // (stable-id subtyping, #72), not by law-hash pin — the hash pin was a bug we removed.
@@ -1660,8 +1660,8 @@ const NOMOS_TYPES_DEP = "^0.8.7";
1660
1660
  // 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
1661
1661
  // DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
1662
1662
  // REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
1663
- const NOMOS_CLIENT_DEP = "^0.64.24";
1664
- const NOMOS_FLUTTER_DEP = "^0.63.45";
1663
+ const NOMOS_CLIENT_DEP = "^0.64.25";
1664
+ const NOMOS_FLUTTER_DEP = "^0.63.47";
1665
1665
  // DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
1666
1666
  // and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
1667
1667
  // land back in dependency_overrides (the exact co2 report this closes, third occurrence).
@@ -5080,7 +5080,7 @@ async function main(): Promise<void> {
5080
5080
  identityHashes[module.domain ?? module.name] = hash;
5081
5081
  }
5082
5082
 
5083
- // ── 3a. the CLIENT/LAW COMPATIBILITY MANIFEST (§5, architecture/plug_and_play_dx.md).
5083
+ // ── 3a. the CLIENT/LAW COMPATIBILITY MANIFEST (architecture/shape_compatibility.md).
5084
5084
  // A DIAGNOSIS surface, NEVER a gate: the shape-compat arc (#72) established that clients
5085
5085
  // fit workspaces by STRUCTURAL INTERFACE (stable-id subtyping), not by law-hash pin. This
5086
5086
  // block is baked into both generated clients (TS + Dart) and mirrored into the deploy JSON
@@ -5253,20 +5253,29 @@ async function main(): Promise<void> {
5253
5253
  // deployed law's content hash baked in (emitted AFTER the package is hashed) ──
5254
5254
  const clientPath = path.join(outDir, `${cfg.name}.client.mjs`);
5255
5255
  const clientDeclarationPath = path.join(outDir, `${cfg.name}.client.d.mts`);
5256
- const clientSource = generateTsClient(presentationDomainModules, {
5256
+ const headlessClientPath = path.join(outDir, `${cfg.name}.headless.mjs`);
5257
+ const headlessClientDeclarationPath = path.join(outDir, `${cfg.name}.headless.d.mts`);
5258
+ const clientOptions = {
5257
5259
  packageName: cfg.name,
5258
5260
  domainHash,
5259
5261
  interface: packageInterface,
5260
5262
  compat: nomosCompat,
5261
5263
  homeDomain: resolveApplicationTopology(presentationDomainModules).homeDomain,
5264
+ } as const;
5265
+ const clientSource = generateTsClient(presentationDomainModules, clientOptions);
5266
+ const headlessClientSource = generateTsClient(presentationDomainModules, {
5267
+ ...clientOptions,
5268
+ exportHolonFactory: true,
5262
5269
  });
5263
5270
  const runtimePath = path.join(DSL_DIR, "runtime", "generated-application-host.js");
5264
5271
  if (!existsSync(runtimePath)) {
5265
5272
  fail("the compiler-private generated application runtime is missing — reinstall @githolon/dsl");
5266
5273
  }
5267
5274
  const temporaryClientPath = path.join(outDir, `.${cfg.name}.client.source.ts`);
5275
+ const temporaryHeadlessClientPath = path.join(outDir, `.${cfg.name}.headless.source.ts`);
5268
5276
  const temporaryRuntimeTypesPath = path.join(outDir, `.${cfg.name}.application-runtime.d.ts`);
5269
5277
  const temporaryDeclarationPath = temporaryClientPath.replace(/\.ts$/, ".d.ts");
5278
+ const temporaryHeadlessDeclarationPath = temporaryHeadlessClientPath.replace(/\.ts$/, ".d.ts");
5270
5279
  const runtimeTypesSource = [
5271
5280
  `export declare function nomosInternalOpenGeneratedApplication<T extends object>(`,
5272
5281
  ` options: { cloud: string; application: string; signIn: (request?: { readonly deviceChallenge?: string }) => Promise<string>; onPerformance?: (sample: { readonly operation: "open" | "offer" | "birth" | "read" | "sync" | "maintenance"; readonly name: string; readonly durationMs: number; readonly domain?: string; readonly directive?: string }) => void; lawHash: string; readyCommands?: readonly { readonly domain: string; readonly packageHash: string; readonly directiveId: string }[] },`,
@@ -5306,6 +5315,8 @@ async function main(): Promise<void> {
5306
5315
  .update(`${declarationProofFormat}\0`)
5307
5316
  .update(normalizedClientSource)
5308
5317
  .update("\0")
5318
+ .update(headlessClientSource.replaceAll(domainHash, "<nomos-composed-law-identity>"))
5319
+ .update("\0")
5309
5320
  .update(runtimeTypesSource)
5310
5321
  .update("\0")
5311
5322
  .update(ts.version)
@@ -5313,25 +5324,37 @@ async function main(): Promise<void> {
5313
5324
  .update(readFileSync(fileURLToPath(import.meta.url)))
5314
5325
  .digest("hex");
5315
5326
  const declarationCacheDir = path.join(cfgDir, "node_modules", ".cache", "nomos", "typescript-declarations");
5316
- const declarationCachePath = path.join(declarationCacheDir, `${declarationInputIdentity}.d.mts`);
5317
- const declarationCacheProofPath = path.join(declarationCacheDir, `${declarationInputIdentity}.proof.usda`);
5318
- const declarationProofKey = (bytes: Buffer): string => createHash("sha256")
5319
- .update(`${declarationProofFormat}\0${declarationInputIdentity}\0`)
5327
+ const declarationCachePath = path.join(declarationCacheDir, `${declarationInputIdentity}.client.d.mts`);
5328
+ const headlessDeclarationCachePath = path.join(declarationCacheDir, `${declarationInputIdentity}.headless.d.mts`);
5329
+ const declarationCacheProofPath = path.join(declarationCacheDir, `${declarationInputIdentity}.client.proof.usda`);
5330
+ const headlessDeclarationCacheProofPath = path.join(declarationCacheDir, `${declarationInputIdentity}.headless.proof.usda`);
5331
+ const declarationProofKey = (artifact: "client" | "headless", bytes: Buffer): string => createHash("sha256")
5332
+ .update(`${declarationProofFormat}\0${declarationInputIdentity}\0${artifact}\0`)
5320
5333
  .update(bytes)
5321
5334
  .digest("hex");
5322
5335
  const cachedDeclaration = existsSync(declarationCachePath) ? readFileSync(declarationCachePath) : undefined;
5336
+ const cachedHeadlessDeclaration = existsSync(headlessDeclarationCachePath)
5337
+ ? readFileSync(headlessDeclarationCachePath)
5338
+ : undefined;
5323
5339
  const declarationCacheHit = cachedDeclaration !== undefined &&
5340
+ cachedHeadlessDeclaration !== undefined &&
5324
5341
  declarationProofCodec.readKeyProofUsda(declarationCacheProofPath, declarationProofFormat) ===
5325
- declarationProofKey(cachedDeclaration);
5326
- if (declarationCacheHit) copyFileSync(declarationCachePath, clientDeclarationPath);
5342
+ declarationProofKey("client", cachedDeclaration) &&
5343
+ declarationProofCodec.readKeyProofUsda(headlessDeclarationCacheProofPath, declarationProofFormat) ===
5344
+ declarationProofKey("headless", cachedHeadlessDeclaration);
5345
+ if (declarationCacheHit) {
5346
+ copyFileSync(declarationCachePath, clientDeclarationPath);
5347
+ copyFileSync(headlessDeclarationCachePath, headlessClientDeclarationPath);
5348
+ }
5327
5349
  try {
5328
5350
  if (!declarationCacheHit) {
5329
5351
  writeFileSync(temporaryClientPath, clientSource, "utf8");
5352
+ writeFileSync(temporaryHeadlessClientPath, headlessClientSource, "utf8");
5330
5353
  writeFileSync(temporaryRuntimeTypesPath, runtimeTypesSource, "utf8");
5331
5354
  const typescriptEntry = createRequire(import.meta.url).resolve("typescript/lib/typescript.js");
5332
5355
  const ts = (await import(pathToFileURL(typescriptEntry).href)) as typeof import("typescript");
5333
5356
  const program = ts.createProgram(
5334
- [temporaryClientPath, temporaryRuntimeTypesPath],
5357
+ [temporaryClientPath, temporaryHeadlessClientPath, temporaryRuntimeTypesPath],
5335
5358
  {
5336
5359
  target: ts.ScriptTarget.ES2022,
5337
5360
  module: ts.ModuleKind.NodeNext,
@@ -5351,7 +5374,10 @@ async function main(): Promise<void> {
5351
5374
  );
5352
5375
  const emit = program.emit();
5353
5376
  const diagnostics = [...ts.getPreEmitDiagnostics(program), ...emit.diagnostics];
5354
- if (diagnostics.length > 0 || emit.emitSkipped || !existsSync(temporaryDeclarationPath)) {
5377
+ if (
5378
+ diagnostics.length > 0 || emit.emitSkipped ||
5379
+ !existsSync(temporaryDeclarationPath) || !existsSync(temporaryHeadlessDeclarationPath)
5380
+ ) {
5355
5381
  const detail = diagnostics.map((diagnostic) => {
5356
5382
  const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
5357
5383
  if (diagnostic.file === undefined || diagnostic.start === undefined) return message;
@@ -5497,14 +5523,23 @@ async function main(): Promise<void> {
5497
5523
  ].join("\n");
5498
5524
  writeFileSync(temporaryDeclarationPath, declarationSource, "utf8");
5499
5525
  renameSync(temporaryDeclarationPath, clientDeclarationPath);
5526
+ renameSync(temporaryHeadlessDeclarationPath, headlessClientDeclarationPath);
5500
5527
  mkdirSync(declarationCacheDir, { recursive: true });
5501
5528
  const declarationCacheTemporaryPath = `${declarationCachePath}.tmp-${process.pid}`;
5529
+ const headlessDeclarationCacheTemporaryPath = `${headlessDeclarationCachePath}.tmp-${process.pid}`;
5502
5530
  copyFileSync(clientDeclarationPath, declarationCacheTemporaryPath);
5531
+ copyFileSync(headlessClientDeclarationPath, headlessDeclarationCacheTemporaryPath);
5503
5532
  renameSync(declarationCacheTemporaryPath, declarationCachePath);
5533
+ renameSync(headlessDeclarationCacheTemporaryPath, headlessDeclarationCachePath);
5504
5534
  declarationProofCodec.writeKeyProofUsda(
5505
5535
  declarationCacheProofPath,
5506
5536
  declarationProofFormat,
5507
- declarationProofKey(readFileSync(declarationCachePath)),
5537
+ declarationProofKey("client", readFileSync(declarationCachePath)),
5538
+ );
5539
+ declarationProofCodec.writeKeyProofUsda(
5540
+ headlessDeclarationCacheProofPath,
5541
+ declarationProofFormat,
5542
+ declarationProofKey("headless", readFileSync(headlessDeclarationCachePath)),
5508
5543
  );
5509
5544
  }
5510
5545
  await esbuildMod.build({
@@ -5540,12 +5575,44 @@ async function main(): Promise<void> {
5540
5575
  ) {
5541
5576
  fail("generated TypeScript application did not seal its private runtime binding");
5542
5577
  }
5578
+ await esbuildMod.build({
5579
+ stdin: {
5580
+ contents: headlessClientSource,
5581
+ loader: "ts",
5582
+ resolveDir: outDir,
5583
+ sourcefile: `${cfg.name}.headless.ts`,
5584
+ },
5585
+ outfile: headlessClientPath,
5586
+ bundle: true,
5587
+ packages: "bundle",
5588
+ platform: "node",
5589
+ format: "esm",
5590
+ target: "node20",
5591
+ legalComments: "none",
5592
+ logLevel: "silent",
5593
+ plugins: [{
5594
+ name: "nomos-generated-headless-runtime",
5595
+ setup(build) {
5596
+ build.onResolve(
5597
+ { filter: /^@githolon\/client\/_generated_application_host_7c1f5e$/ },
5598
+ () => ({ path: runtimePath }),
5599
+ );
5600
+ },
5601
+ }],
5602
+ });
5603
+ const headlessBundled = canonicalizeGeneratedClientJavascript(readFileSync(headlessClientPath, "utf8"));
5604
+ writeFileSync(headlessClientPath, headlessBundled, "utf8");
5605
+ if (!new RegExp(`export\\s*\\{[^}]*${tsClientFactoryName(resolveApplicationTopology(presentationDomainModules).homeDomain)}`, "s").test(headlessBundled)) {
5606
+ fail("generated headless application omitted its structural holon factory");
5607
+ }
5543
5608
  } catch (e) {
5544
5609
  fail(`generated TypeScript application could not be sealed — this is a Nomos codegen bug: ${(e as Error).message}`);
5545
5610
  } finally {
5546
5611
  rmSync(temporaryClientPath, { force: true });
5612
+ rmSync(temporaryHeadlessClientPath, { force: true });
5547
5613
  rmSync(temporaryRuntimeTypesPath, { force: true });
5548
5614
  rmSync(temporaryDeclarationPath, { force: true });
5615
+ rmSync(temporaryHeadlessDeclarationPath, { force: true });
5549
5616
  rmSync(path.join(outDir, `${cfg.name}.client.ts`), { force: true });
5550
5617
  }
5551
5618
 
@@ -5903,6 +5970,7 @@ async function main(): Promise<void> {
5903
5970
  }
5904
5971
  }
5905
5972
  console.log(` client ${rel(clientPath)} (typed TS client — payloads, read models, query accessors)`);
5973
+ console.log(` headless ${rel(headlessClientPath)} + ${rel(headlessClientDeclarationPath)} (typed business binding for code-mode/edge holons)`);
5906
5974
  for (const note of recursiveNotes) console.log(` note ${note}`);
5907
5975
  console.log(` openapi served by the holon from installed USDA (GET /v2/workspaces/<ws>/openapi.json) — no build-time copy to drift`);
5908
5976
  if (proofPath !== undefined) {
@@ -96,12 +96,11 @@ export const ReceivingGroup = aggregate("NomosInternalReceivingGroup7c1f5e", {
96
96
  * and secure: a live, verified device may add or revoke another device; keyless IdP enrolment is only the
97
97
  * pre-group bootstrap lane.
98
98
  *
99
- * ALL-DEVICE-LOSS RECOVERY — design done, NOT YET SHIPPED (`architecture/guardian_device_recovery.md`,
100
- * 2026-08-06). DO NOT re-derive this from scratch under a live incident again — read that doc first. The
101
- * primitives already exist (`enrollDelegation` + `birthReceivingGroup` + `revokeSigner`); what's missing is
102
- * a kernel-side closure (birth-repeat currently accepts a SELF-signed delegation onto an already-founded
103
- * group — a live gap, not a feature, until closed) plus client wiring. `guardianQuorum`/`recovery` below are
104
- * the policy fields that design reads.
99
+ * ALL-DEVICE-LOSS RECOVERY — CO2 already recovers through its platform AuthProvider, but the implementation
100
+ * reaches that result by repeating birth after this home's direct enrollment policy refuses. The corrected
101
+ * design (`architecture/guardian_device_recovery.md`, reconciled 2026-08-10) makes recovery authority an
102
+ * explicit tenant-selected policy: platform identity provider or guardian quorum. The missing work is to
103
+ * evaluate that choice at the existing child's frontier and delete the client-side birth-repeat bypass.
105
104
  */
106
105
  export const ReceivingGroupPolicy = aggregate("NomosInternalReceivingGroupPolicy7c1f5e", {
107
106
  additionalDevice: t.string().merge(Lww),
@@ -207,15 +206,15 @@ const receivingGroupPolicyRefs = readSet(
207
206
  );
208
207
 
209
208
  /** An IdP token may bootstrap the parent before the group exists; it may never bypass an existing group's
210
- * device policy. A new group device must be approved by a current device or, later, guardian evidence.
209
+ * device policy. An existing group chooses whether recovery is delegated to its platform AuthProvider or to
210
+ * guardians; the current default below says guardian, while CO2's current product behaviour is platform IdP.
211
211
  *
212
212
  * KNOWN GAP (`architecture/guardian_device_recovery.md`): this invariant is declared `.on("enrollFirstDevice")`
213
213
  * and only fires when that directive targets THIS home directly. It is NOT re-applied when the birth
214
214
  * offer-effect (`run_birth_effect`, repeating `nomosInternalBirthReceivingGroup` against an ALREADY-founded
215
- * home) folds a signer seat via the SAME underlying mechanism — a self-signed keyless delegation currently
216
- * reseats a device there with no guardian, no approval, no delay. Verified live 2026-08-06. Fix belongs in
217
- * the kernel's keyless `verify_to_root` branch (refuse a self-signed cert onto an already-warranted signer
218
- * roster); do not paper over it in the DSL only. */
215
+ * home) folds a signer seat via the SAME underlying mechanism. That is how CO2 recovery works today, but its
216
+ * home frontier still says `guardian-quorum`. The fix is not to ban platform recovery: add the explicit
217
+ * platform-identity-provider policy, make CO2 choose it, and have the child gate verify the chosen evidence. */
219
218
  export const receivingGroupRejectsKeylessAdditionalDevice = workspaceInvariant("ReceivingGroupRejectsKeylessAdditionalDevice")
220
219
  .on("enrollFirstDevice")
221
220
  .reads(receivingGroupPolicyRefs)
@@ -104,7 +104,7 @@ export { enrollFirstDevice, revokeSigner } from "./signing_identity.js";
104
104
  // (this trio) but DELIBERATELY does NOT export the `parentAttestedBirth` era marker — a platform's own 1-link
105
105
  // `[platform_cert]` chain would fail `verify_to_root` (>=2 links), so its own verify doors must stay inert
106
106
  // while it still carries the cert for its children. Fully additive (new aggregate + directive, no era key ⇒
107
- // pre-existing chains replay byte-identical). architecture/co2_attestation_spine.md.
107
+ // pre-existing chains replay byte-identical). See architecture/verified_auth_from_genesis.md.
108
108
  export { BIRTH_ATTESTATION_ID, BirthAttestation, birthAttestation, seedBirthAttestation } from "./home.js";
109
109
  // THE ENROLL-ONCE DELEGATION LANE — a platform records each tenant's device-key delegation (signed by its
110
110
  // onboarding signer S_P) so the kernel reads it at `signer-delegation:<user>` when assembling a home's chain.
package/src/index.ts CHANGED
@@ -464,7 +464,7 @@ export {
464
464
  // a `requires(cap)` aggregate's cap IS a read relation (synthesized into nomosRelations at compile),
465
465
  // and a grant is a `relateSubject({ object:"workspace:self", relation:cap, ... })` tuple. See
466
466
  // framework/relations.ts + relation_schema.ts below.
467
- // NATIVE ZANZIBAR / ReBAC (architecture/zanzibar_native_plan.md, slice 1) — object-keyed
467
+ // NATIVE ZANZIBAR / ReBAC (architecture/authz_relationship_spine.md) — object-keyed
468
468
  // relation-tuple events `object#relation@subject`, the clean-slate-native authz spine.
469
469
  export {
470
470
  RELATION_STATUSES,
package/src/law_check.ts CHANGED
@@ -1351,7 +1351,7 @@ export function checkDomainModules(
1351
1351
  domain,
1352
1352
  level: "warn",
1353
1353
  what: `birth recipe: ${diag}`,
1354
- fix: `see architecture/g3_kernel_fulfils_birth.md / the births DX — the recipe must install both framework + child law and seed genesis`,
1354
+ fix: `see create-githolon/template/docs/08-births.md — the recipe must install both framework + child law and seed genesis`,
1355
1355
  });
1356
1356
  }
1357
1357
  }
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * relation_schema — THE NATIVE ZANZIBAR RELATION SCHEMA (the userset-rewrite law).
8
8
  *
9
- * This is layer 2 of the three-layer authz design (see `architecture/zanzibar_native_plan.md`):
9
+ * This is the schema layer of the authorization design (see `architecture/authz_relationship_spine.md`):
10
10
  * 1. TUPLES = ledger events (`relateSubject`/`RelationTuple`, framework/relations.ts).
11
11
  * 2. SCHEMA = THIS FILE — per-type relation REWRITES authored as PURE DATA, lowered to the
12
12
  * `nomosRelations` USD era key. NO JavaScript on the schema path: a `.relation(...)` produces
@@ -27,7 +27,7 @@
27
27
  * directly(user(), key()) → { rewrite: this, allow:[...] } — the this+allow PAIR so the
28
28
  * type restriction CAN'T be forgotten (the #1 footgun).
29
29
  *
30
- * 2. SECURE-BY-DEFAULT AUDIENCES (the PRIMARY surface; `zanzibar_native_plan.md` F2). Builder
30
+ * 2. SECURE-BY-DEFAULT AUDIENCES (the PRIMARY surface; `authz_relationship_spine.md`). Builder
31
31
  * methods that lower to a relation (or a small set of relations) with the SAFE default baked in:
32
32
  * .public(rel) → rel = directly(anyUser()) (user:* ONLY — never key:*)
33
33
  * .owned(rel?) → owner = directly(user()); rel = directly(self) ∪ computed(owner)
@@ -138,7 +138,7 @@ function payloadFieldKind(spec: PayloadFieldSpec): string {
138
138
  return "string";
139
139
  case "vo":
140
140
  case "voList":
141
- // Interface kinds stay at the base lane for now (slice D of vo_authoring_end_to_end.md
141
+ // Interface kinds stay at the base lane for now (the remaining parity work in dart_codegen_parity.md
142
142
  // upgrades these to a first-class "vo" kind) — a VO IS a json leaf structurally.
143
143
  return "json";
144
144
  case "scalar":
@@ -608,7 +608,7 @@ function describeZod(
608
608
  const VO_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
609
609
 
610
610
  /**
611
- * THE VO BRAND (slice A of vo_authoring_end_to_end.md) — `valueObject()` stamps its zod schema
611
+ * THE VO BRAND (vo_first_class.md) — `valueObject()` stamps its zod schema
612
612
  * with the VO's {@link ValueObjectFieldMeta} under this symbol. The property itself is
613
613
  * non-enumerable, never serialized and never read by the engine's validation path. When attached
614
614
  * to an aggregate field, its canonical descriptor deliberately enters that field's stable value contract.
@@ -1036,7 +1036,7 @@ export const geo = {
1036
1036
  /**
1037
1037
  * Collect the value objects referenced by DIRECTIVE PAYLOADS (top-level fields + array
1038
1038
  * elements), via the {@link NOMOS_VO_BRAND} the schemas carry. CODEGEN-ONLY (slice B of
1039
- * vo_authoring_end_to_end.md): the result feeds the generated Dart/TS surfaces so a
1039
+ * vo_first_class.md): the result feeds the generated Dart/TS surfaces so a
1040
1040
  * payload-only VO still gets its class/interface — it does NOT enter the `nomosValueObjects`
1041
1041
  * IR key (that stays aggregate-derived, keeping package bytes / domainHash untouched).
1042
1042
  */