@githolon/dsl 0.103.1 → 0.103.2

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.
@@ -103,7 +103,11 @@ import {
103
103
  import { synthesizePreconditionInvariants } from "./precondition.js";
104
104
  import { BIRTH_FRAMEWORK_QUERIES, synthesizeBirthQuotaInvariants } from "./birth_meter.js";
105
105
  import { withBirthMeterExports } from "./birth_meter_ensure.js";
106
- import { expandBusinessBirthExports } from "./business_birth.js";
106
+ import {
107
+ BUSINESS_BIRTH_POLICY_DOMAINS_EXPORT,
108
+ bindBusinessBirthPolicyDomains,
109
+ expandBusinessBirthExports,
110
+ } from "./business_birth.js";
107
111
  import { withOwnedCustodyExports } from "./framework/owned_custody.js";
108
112
  import { expandRoleExports, rolesOf } from "./role.js";
109
113
  import { collectEngineRouting } from "./engine_entry.js";
@@ -158,6 +162,8 @@ import { checkDomainModules, formatFindings, hasRefusal } from "./law_check.js";
158
162
  import type { NomosCompat } from "./compat_manifest.js";
159
163
  import { requiredKernelFeatures, type FeatureProbeModule } from "./kernel_capabilities.js";
160
164
  import { APPLICATION_HOME_DOMAIN } from "./framework/application_home_domain.js";
165
+ import { applicationHomePolicyLaw } from "./framework/application_home.js";
166
+ import { isApplicationHomeGenesis } from "./application_home_genesis.js";
161
167
  import { resolveApplicationTopology } from "./application_topology.js";
162
168
  import { isBusinessIntent } from "./intent.js";
163
169
  import {
@@ -1805,6 +1811,10 @@ function generatedPackageApplication(
1805
1811
  const invitationPolicies = members.flatMap(({ domain, invitationPolicies }) =>
1806
1812
  invitationPolicies.map((policy) => ({ domain, ...policy })));
1807
1813
  const hasInvitations = invitationPolicies.length > 0;
1814
+ const rootMember = members.find(({ domain }) => domain === homeDomain);
1815
+ if (rootMember === undefined) {
1816
+ throw new Error(`generated Dart package application cannot expose the model for home domain '${homeDomain}'`);
1817
+ }
1808
1818
  const duplicate = members.find((candidate, index) =>
1809
1819
  members.findIndex((other) => other.member === candidate.member) !== index);
1810
1820
  if (duplicate !== undefined) {
@@ -1817,6 +1827,7 @@ function generatedPackageApplication(
1817
1827
  ["currentUser", "the generated signed-in application participant"],
1818
1828
  ["currentStatus", "the generated application status"],
1819
1829
  ["watchStatus", "the generated application status watch"],
1830
+ ["model", "the generated model lifecycle"],
1820
1831
  ["recipient", "the generated typed recipient boundary"],
1821
1832
  ["createInvitation", "the generated invitation capability"],
1822
1833
  ["redeemInvitation", "the generated invitation redemption"],
@@ -1836,7 +1847,7 @@ function generatedPackageApplication(
1836
1847
  `import 'package:flutter/widgets.dart';`,
1837
1848
  `import 'package:nomos_flutter/src/application_status.dart' show ApplicationSignIn, ApplicationStatus;`,
1838
1849
  `import 'package:nomos_client/src/nms_generated_runtime.dart' show NmsInternalUsers, NomosSession, User${hasInvitations ? ", Invitation, Recipient" : ""};`,
1839
- `import 'package:nomos_flutter/src/generated_application_host.dart' show NmsInternalApplicationHost, NmsInternalApplicationUserBinding${hasInvitations ? ", NmsInternalInvitationPolicy" : ""};`,
1850
+ `import 'package:nomos_flutter/src/generated_application_host.dart' show NomosApplicationStartupState, NmsInternalApplicationHost, NmsInternalApplicationUserBinding${hasInvitations ? ", NmsInternalInvitationPolicy" : ""};`,
1840
1851
  ...members.map(({ file, prefix }) =>
1841
1852
  `import '${file}.application.dart' as ${prefix};`),
1842
1853
  ``,
@@ -1866,6 +1877,9 @@ function generatedPackageApplication(
1866
1877
  ]),
1867
1878
  ` const ${application}._({required NomosSession nmsSession, ${hasInvitations ? "required NmsInternalApplicationUserBinding nmsUser, " : ""}required this.currentUser, ${members.map(({ member }) => `required this.${member}`).join(", ")}}) : _nmsSession = nmsSession${hasInvitations ? ", _nmsUser = nmsUser" : ""};`,
1868
1879
  ``,
1880
+ ` /// Prove, adopt or restore the package law against this workspace's exact history.`,
1881
+ ` ${rootMember.prefix}.NomosModel get model => ${rootMember.member}.model;`,
1882
+ ``,
1869
1883
  ` /// Current durability and convergence across this business application.`,
1870
1884
  ` ApplicationStatus get currentStatus => NmsInternalApplicationHost.currentStatus(_nmsSession);`,
1871
1885
  ``,
@@ -1916,10 +1930,12 @@ function generatedPackageApplication(
1916
1930
  ` required Widget Function(BuildContext context, ${application} application) builder,`,
1917
1931
  ` Widget Function(BuildContext context)? loadingBuilder,`,
1918
1932
  ` Widget Function(BuildContext context, Object error, VoidCallback retry)? errorBuilder,`,
1933
+ ` ValueChanged<NomosApplicationStartupState>? onStartupStateChanged,`,
1919
1934
  ` }) => NmsInternalApplicationHost.home<${application}>(`,
1920
1935
  ` key: key, cloud: cloud, application: application, signIn: signIn,`,
1921
1936
  ` bind: nomosInternalBind${application}, builder: builder,`,
1922
1937
  ` loadingBuilder: loadingBuilder, errorBuilder: errorBuilder,`,
1938
+ ` onStartupStateChanged: onStartupStateChanged,`,
1923
1939
  ` );`,
1924
1940
  `}`,
1925
1941
  ``,
@@ -2085,6 +2101,7 @@ function emitDartPackages(
2085
2101
  compat?: NomosCompat,
2086
2102
  describePackageName = packages.client,
2087
2103
  lawUsdzPath?: string,
2104
+ applicationMaxChildWorkspacesPerUser?: number,
2088
2105
  ): { written: string[]; cursed: string[] } {
2089
2106
  for (const [role, name] of Object.entries(packages)) {
2090
2107
  if (typeof name !== "string" || !isPubPackageName(name)) {
@@ -2547,6 +2564,9 @@ function emitDartPackages(
2547
2564
  surfaceName,
2548
2565
  applicationFile,
2549
2566
  applicationModules: perDomain.map(({ dd: candidate }) => candidate.modForDart),
2567
+ ...(applicationMaxChildWorkspacesPerUser === undefined
2568
+ ? {}
2569
+ : { applicationMaxChildWorkspacesPerUser }),
2550
2570
  },
2551
2571
  ),
2552
2572
  "utf8",
@@ -2765,6 +2785,52 @@ async function main(): Promise<void> {
2765
2785
  `rename the tenant domain (Nomos will not silently overwrite or reroute it)`,
2766
2786
  );
2767
2787
  }
2788
+ if (cfg.rootPart !== undefined && cfg.application === undefined) {
2789
+ fail(
2790
+ "published application packages must declare application: { maxChildWorkspacesPerUser }; " +
2791
+ "Nomos will not compile a birth-capable home with an implicit quota",
2792
+ );
2793
+ }
2794
+ // A born application home may carry several co-resident law namespaces. BirthPolicy is namespaced
2795
+ // law-state, so genesis must seed every domain that can itself birth; seeding only the private home
2796
+ // domain would leave (for example) `home/birthEstate` correctly refusing its first offer. Discover the
2797
+ // set from the authored modules and bake it into the private genesis plan. The host never supplies it.
2798
+ const applicationBirthPolicyDomains: string[] = [];
2799
+ let applicationHomeGenesisSource:
2800
+ | { readonly modulePath: string; readonly exportName: string }
2801
+ | undefined;
2802
+ if (cfg.rootPart !== undefined) {
2803
+ applicationBirthPolicyDomains.push(APPLICATION_HOME_DOMAIN);
2804
+ }
2805
+ for (const domain of cfg.domains) {
2806
+ let authored: Mod = {};
2807
+ for (const rel of domain.modules) {
2808
+ const abs = path.resolve(cfgDir, rel);
2809
+ if (!existsSync(abs)) fail(`domain '${domain.key}': module not found: ${abs}`);
2810
+ const imported = await import(pathToFileURL(abs).href) as Mod;
2811
+ authored = { ...authored, ...imported };
2812
+ for (const [exportName, value] of Object.entries(imported)) {
2813
+ if (value !== cfg.application?.homeGenesis || !isApplicationHomeGenesis(value)) continue;
2814
+ if (applicationHomeGenesisSource !== undefined) {
2815
+ fail(
2816
+ "application.homeGenesis is exported from more than one configured law module; " +
2817
+ "keep one canonical declaration",
2818
+ );
2819
+ }
2820
+ applicationHomeGenesisSource = { modulePath: abs, exportName };
2821
+ }
2822
+ }
2823
+ const expanded = expandBusinessBirthExports(authored, domain.key);
2824
+ if (directivesOf(expanded).some((dir) => dir.birthsChild === true)) {
2825
+ applicationBirthPolicyDomains.push(domain.key);
2826
+ }
2827
+ }
2828
+ if (cfg.application?.homeGenesis !== undefined && applicationHomeGenesisSource === undefined) {
2829
+ fail(
2830
+ "application.homeGenesis must be exported by one configured law module; " +
2831
+ "genesis is custody law, not an inline compiler-only callback",
2832
+ );
2833
+ }
2768
2834
  const domainsToCompile: DomainConfig[] = [
2769
2835
  ...cfg.domains,
2770
2836
  ...(cfg.rootPart === undefined
@@ -2944,9 +3010,39 @@ async function main(): Promise<void> {
2944
3010
  }
2945
3011
  }
2946
3012
  }
3013
+ if (mods.some((mod) => Object.prototype.hasOwnProperty.call(mod, BUSINESS_BIRTH_POLICY_DOMAINS_EXPORT))) {
3014
+ fail(
3015
+ `domain '${d.key}' exports reserved compiler binding '${BUSINESS_BIRTH_POLICY_DOMAINS_EXPORT}'; ` +
3016
+ "rename the tenant export",
3017
+ );
3018
+ }
3019
+ if (d.key === APPLICATION_HOME_DOMAIN) {
3020
+ const max = cfg.application?.maxChildWorkspacesPerUser;
3021
+ if (max === undefined) {
3022
+ fail("the private application home has no declared maxChildWorkspacesPerUser policy");
3023
+ }
3024
+ const homeGenesis = cfg.application?.homeGenesis;
3025
+ const policyMod = applicationHomePolicyLaw(
3026
+ max,
3027
+ applicationBirthPolicyDomains,
3028
+ homeGenesis,
3029
+ ) as Mod;
3030
+ mods.push(policyMod);
3031
+ sourceExprs.push(
3032
+ `() => require(${JSON.stringify(path.join(DSL_DIR, "src", "framework", "application_home.ts"))})` +
3033
+ `.applicationHomePolicyLaw(` +
3034
+ `${JSON.stringify(max)}, ${JSON.stringify(applicationBirthPolicyDomains)}, ` +
3035
+ (applicationHomeGenesisSource === undefined
3036
+ ? `undefined)`
3037
+ : `require(${JSON.stringify(applicationHomeGenesisSource.modulePath)})` +
3038
+ `[${JSON.stringify(applicationHomeGenesisSource.exportName)}])`),
3039
+ );
3040
+ }
2947
3041
  let merged: Mod = {};
2948
3042
  for (const m of mods) merged = { ...merged, ...m };
2949
- merged = expandBusinessBirthExports(merged, d.key);
3043
+ merged[BUSINESS_BIRTH_POLICY_DOMAINS_EXPORT] = applicationBirthPolicyDomains;
3044
+ bindBusinessBirthPolicyDomains(merged, applicationBirthPolicyDomains);
3045
+ merged = expandBusinessBirthExports(merged, d.key, applicationBirthPolicyDomains);
2950
3046
  merged = withOwnedCustodyExports(merged);
2951
3047
  // `capability()` bundles expand into discoverable entries (the CLI plane's
2952
3048
  // merge point) — queries/sums/sid-minting/manifests all see the pieces.
@@ -3055,6 +3151,10 @@ async function main(): Promise<void> {
3055
3151
  }
3056
3152
  }
3057
3153
 
3154
+ sourceExprs.push(
3155
+ `() => ({ ${JSON.stringify(BUSINESS_BIRTH_POLICY_DOMAINS_EXPORT)}: ` +
3156
+ `${JSON.stringify(applicationBirthPolicyDomains)} })`,
3157
+ );
3058
3158
  const domainSource = ` ${JSON.stringify(d.key)}: [${sourceExprs.join(", ")}],`;
3059
3159
  entryDomainSourceByKey.set(d.key, domainSource);
3060
3160
  entryGeneratedSourceExprsByKey.set(d.key, sourceExprs.slice(d.modules.length));
@@ -3562,6 +3662,15 @@ async function main(): Promise<void> {
3562
3662
  // clients/interfaces; using the pre-closure executable module here published a USDA-only `birth` route.
3563
3663
  const graphModule = closedDomainModulesByName.get(mod.name) ?? mod;
3564
3664
  const discoveredGraph = discoverLawGraph(graphModule, continuity, nextIdentity);
3665
+ for (const node of discoveredGraph.graph.nodes) {
3666
+ if (node.kind === "intent" && node.births && node.birthPolicySeed === undefined) {
3667
+ fail(
3668
+ `domain '${identityName}' birth intent '${node.entity.label}' has no declared birth-policy ` +
3669
+ "genesis verb; Nomos will not compile a birth-capable law whose children can enter " +
3670
+ "custody without explicit policy",
3671
+ );
3672
+ }
3673
+ }
3565
3674
  nextIdentity = discoveredGraph.registry;
3566
3675
  nextLock.identity = nextIdentity;
3567
3676
  typedLawGraphs.set(identityName, discoveredGraph.graph);
@@ -4864,7 +4973,14 @@ async function main(): Promise<void> {
4864
4973
  let birthWarnings: string[] = [];
4865
4974
  let proofNotes: string[] = [];
4866
4975
  try {
4867
- const proofSrc = generateTsProof(presentationDomainModules, { packageName: cfg.name, domainHash, interface: packageInterface });
4976
+ const proofSrc = generateTsProof(presentationDomainModules, {
4977
+ packageName: cfg.name,
4978
+ domainHash,
4979
+ interface: packageInterface,
4980
+ ...(cfg.application?.maxChildWorkspacesPerUser === undefined
4981
+ ? {}
4982
+ : { applicationMaxChildWorkspacesPerUser: cfg.application.maxChildWorkspacesPerUser }),
4983
+ });
4868
4984
  proofPath = path.join(outDir, `${cfg.name}.proof.mts`);
4869
4985
  writeFileSync(proofPath, proofSrc, "utf8");
4870
4986
  // THE PER-DOMAIN LEGS INDEX: every domain with a creating write, so
@@ -5269,7 +5385,7 @@ async function main(): Promise<void> {
5269
5385
  if (dartPackages !== undefined) {
5270
5386
  const r = emitDartPackages(
5271
5387
  presentationDomainModules, dartOut, domainHash, packageInterface, dartPackages,
5272
- dartNames, nomosCompat, cfg.name, lawUsdzPath,
5388
+ dartNames, nomosCompat, cfg.name, lawUsdzPath, cfg.application?.maxChildWorkspacesPerUser,
5273
5389
  );
5274
5390
  dartFiles = r.written;
5275
5391
  dartCursedHints = r.cursed;
package/src/directive.ts CHANGED
@@ -447,6 +447,14 @@ export interface Directive<P = unknown> {
447
447
  * `attestationAuthorizedGate`/`.attestationAuthorized()` naming convention; the declaration is the noun.)
448
448
  */
449
449
  readonly birthsChild?: boolean;
450
+ /** Additional ordinary offers which the born workspace's typed law requires in genesis.
451
+ * This is compiler/runtime metadata attached only to a birthing directive. It contains no
452
+ * payload or fallback value: the kernel checks that the re-derived recipe invokes each named
453
+ * law verb exactly once after installation. */
454
+ readonly birthGenesisRequirements?: readonly {
455
+ readonly domain: string;
456
+ readonly directiveId: string;
457
+ }[];
450
458
  /**
451
459
  * PROOF-TOOLING PRODUCER MARKER (relation-seed leg). Declares that this directive ESTABLISHES the named
452
460
  * relation(s) — i.e. its `plan` folds a `writeTuple` that grants the relation. Purely a static hint for
@@ -9,6 +9,7 @@
9
9
  // routable through the runtime OpenAPI, while TS/Dart/OpenAPI business surfaces omit it.
10
10
 
11
11
  import type { AggregateHandle } from "../aggregate.js";
12
+ import type { ApplicationHomeGenesis } from "../application_home_genesis.js";
12
13
  import type { Directive } from "../directive.js";
13
14
  import type { QueryDecl } from "../query.js";
14
15
  import { APPLICATION_HOME_DOMAIN } from "./application_home_domain.js";
@@ -70,8 +71,24 @@ const aggregate = (value: AggregateHandle): Internal<AggregateHandle> => interna
70
71
  const directive = <P>(value: Directive<P>): Internal<Directive<P>> => internal(value);
71
72
  const query = (value: QueryDecl): Internal<QueryDecl> => internal(value);
72
73
 
73
- // Parent primitive + the law its born home uses.
74
- export const __nomos_birthReceivingGroup = directive(birthReceivingGroupForDomain(APPLICATION_HOME_DOMAIN));
74
+ // Parent primitive + the law its born home uses. The compiler supplies the application's explicit
75
+ // allowance through this factory; there is deliberately no static/default route to discover.
76
+ export function applicationHomePolicyLaw(
77
+ maxChildWorkspacesPerUser: number,
78
+ birthPolicyDomains: readonly string[],
79
+ homeGenesis?: ApplicationHomeGenesis,
80
+ ) {
81
+ return {
82
+ __nomos_birthReceivingGroup: directive(
83
+ birthReceivingGroupForDomain(
84
+ APPLICATION_HOME_DOMAIN,
85
+ maxChildWorkspacesPerUser,
86
+ birthPolicyDomains,
87
+ homeGenesis,
88
+ ),
89
+ ),
90
+ };
91
+ }
75
92
  export const __nomos_ReceivingGroup = aggregate(ReceivingGroup);
76
93
  export const __nomos_seedReceivingGroup = directive(seedReceivingGroup);
77
94
  export const __nomos_ReceivingGroupPolicy = aggregate(ReceivingGroupPolicy);
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * The meter's STRUCTURE (record + queries + quota invariant + auto-capture) is auto-ensured on every
10
10
  * `.births()` law — see `../birth_meter.ts`. THIS module is the part a tenant COMPOSES IN when they
11
- * want to GOVERN the allowance instead of living on the conservative default:
11
+ * uses to govern its required, explicitly seeded allowance:
12
12
  *
13
13
  * - {@link setBirthPolicy} — set/amend the workspace's default allowance (admin-gated).
14
14
  * - {@link grantBirthAllowance} — a per-subject override (admin-gated; latest grant wins).
@@ -56,7 +56,6 @@ export {
56
56
  childLifecyclesByStatus,
57
57
  birthPolicy,
58
58
  birthGrantByPrincipal,
59
- DEFAULT_MAX_BIRTHS_PER_PRINCIPAL,
60
59
  birthQuotaVerdict,
61
60
  birthQuotaInvariantId,
62
61
  } from "../birth_meter.js";
@@ -238,7 +237,7 @@ export const setBirthPolicy = directive("setBirthPolicy")
238
237
  .ensures(BirthPolicy)
239
238
  .payload(
240
239
  z.object({
241
- maxBirthsPerPrincipal: z.number().int().positive(),
240
+ maxBirthsPerPrincipal: z.number().int().nonnegative(),
242
241
  setBy: z.string().min(1),
243
242
  /** Caller-stamped (the timestamp doctrine). */
244
243
  setAt: z.string().min(1),
@@ -257,7 +256,7 @@ export const seedBirthPolicy = directive("seedBirthPolicy")
257
256
  .ensures(BirthPolicy)
258
257
  .payload(
259
258
  z.object({
260
- maxBirthsPerPrincipal: z.number().int().positive(),
259
+ maxBirthsPerPrincipal: z.number().int().nonnegative(),
261
260
  setBy: z.string().min(1),
262
261
  setAt: z.string().min(1),
263
262
  }),
@@ -274,7 +273,7 @@ export const grantBirthAllowance = directive("grantBirthAllowance")
274
273
  .payload(
275
274
  z.object({
276
275
  principal: z.string().min(1),
277
- maxBirths: z.number().int().positive(),
276
+ maxBirths: z.number().int().nonnegative(),
278
277
  grantedBy: z.string().min(1),
279
278
  /** epoch-ms — the recency tiebreak (latest grant wins), caller-stamped. */
280
279
  grantedAt: z.number().int().nonnegative(),
@@ -25,6 +25,7 @@
25
25
  * the result, and the owner-authored lane to maintain it.)
26
26
  */
27
27
  import { z } from "zod";
28
+ import type { ApplicationHomeGenesis } from "../application_home_genesis.js";
28
29
  import { aggregate, instance, OWNED_READ_RELATION } from "../aggregate.js";
29
30
  // The device-key-hash wire shape (same validator signing_identity/workspaces use module-locally).
30
31
  const sha256Hex = z.string().regex(/^[0-9a-f]{64}$/, "expected lowercase sha256 hex (the owner's device pubkey hash)");
@@ -268,8 +269,8 @@ export function receivingGroupNameOf(principal: string): string {
268
269
  * so HomeOwner is seeded and `homeNameBindsOwner` passes (a birth without it would be an invalid, un-owned
269
270
  * home).
270
271
  */
271
- // The kernel's era-0 birth-seed resolver retains `home` as its fallback dispatch domain for standalone
272
- // framework laws. Compiler-composed business applications use the obscure APPLICATION_HOME_DOMAIN instead.
272
+ // Standalone framework law explicitly declares `home`; compiler-composed business applications explicitly
273
+ // declare the obscure APPLICATION_HOME_DOMAIN. The kernel refuses law with no attestation-seed registrar.
273
274
  export const RECEIVING_GROUP_LAW_DOMAIN = "home";
274
275
  /**
275
276
  * Build the private home-birth primitive for the domain key that carries the home law.
@@ -279,11 +280,33 @@ export const RECEIVING_GROUP_LAW_DOMAIN = "home";
279
280
  * tenant remains free to model a business concept called "home". The key is closed over
280
281
  * here and written into every child-genesis step; it is never a tenant payload field.
281
282
  */
282
- export function birthReceivingGroupForDomain(groupLawDomain: string) {
283
+ export function birthReceivingGroupForDomain(
284
+ groupLawDomain: string,
285
+ maxChildWorkspacesPerUser: number,
286
+ birthPolicyDomains: readonly string[],
287
+ homeGenesis?: ApplicationHomeGenesis,
288
+ ) {
283
289
  if (groupLawDomain.length === 0) {
284
290
  throw new Error("birthReceivingGroupForDomain requires a non-empty receiving-group law domain");
285
291
  }
286
- return directive("nomosInternalBirthReceivingGroup7c1f5e")
292
+ if (!Number.isInteger(maxChildWorkspacesPerUser) || maxChildWorkspacesPerUser < 0) {
293
+ throw new Error(
294
+ "birthReceivingGroupForDomain requires an explicit non-negative maxChildWorkspacesPerUser; " +
295
+ "Nomos has no compiled birth-policy fallback",
296
+ );
297
+ }
298
+ const policyDomains = [...new Set(birthPolicyDomains)];
299
+ if (
300
+ policyDomains.length === 0 ||
301
+ policyDomains.some((domain) => domain.trim().length === 0) ||
302
+ !policyDomains.includes(groupLawDomain)
303
+ ) {
304
+ throw new Error(
305
+ "birthReceivingGroupForDomain requires every co-resident birth-capable law domain, " +
306
+ "including its own domain; Nomos will not birth partially configured law",
307
+ );
308
+ }
309
+ const birthDirective = directive("nomosInternalBirthReceivingGroup7c1f5e")
287
310
  .creates(ReceivingGroup)
288
311
  .payload(
289
312
  z.object({
@@ -372,6 +395,32 @@ export function birthReceivingGroupForDomain(groupLawDomain: string) {
372
395
  actor: founder,
373
396
  payload: { principal: p.owner, workspaceName: groupName, bornAt: p.bornAt },
374
397
  },
398
+ // The receiving group is born with its child-workspace allowance as explicit law-state. The
399
+ // value is closed over by the package's application policy, so neither the host nor the end
400
+ // user can substitute it in the offer payload.
401
+ ...policyDomains.map((domain) => ({
402
+ domain,
403
+ directiveId: "seedBirthPolicy",
404
+ actor: founder,
405
+ payload: {
406
+ maxBirthsPerPrincipal: maxChildWorkspacesPerUser,
407
+ setBy: p.owner,
408
+ setAt: p.bornAt,
409
+ },
410
+ })),
411
+ // Tenant founding law is an ordered list of ordinary offers. Values derive solely from the
412
+ // canonical home facts; every payload is parsed by the target directive before it reaches
413
+ // the child's author gate. There is no host append point and no framework-selected fallback.
414
+ ...(homeGenesis?.steps ?? []).map((step) => ({
415
+ domain: step.domain,
416
+ directiveId: step.directive.id,
417
+ actor: founder,
418
+ payload: step.directive.payloadSchema.parse(step.payload({
419
+ owner: p.owner,
420
+ workspaceName: groupName,
421
+ bornAt: p.bornAt,
422
+ })),
423
+ })),
375
424
  // ENROL THE OWNER'S DEVICE on the warrant-free prefix (when supplied) — enrollSigner self-binds to
376
425
  // ctx.principal() (the owner), so this binds `ownerKeyHash` as the owner's signer BEFORE the warrant
377
426
  // closes bootstrap. This is what makes the born home SIGNER-READY (able to sign + self-serve-birth its own
@@ -418,9 +467,26 @@ export function birthReceivingGroupForDomain(groupLawDomain: string) {
418
467
  })
419
468
  .reads(authProviderQuery) // the parent-side AuthProvider capture the inheritance copies (the #58 lane)
420
469
  .births(); // BIRTH IS A DECLARED KERNEL PRIMITIVE — emits the `nomosBirthGate` era key; the kernel folds the recipe
470
+ return {
471
+ ...birthDirective,
472
+ ...(homeGenesis === undefined || homeGenesis.steps.length === 0
473
+ ? {}
474
+ : {
475
+ birthGenesisRequirements: homeGenesis.steps.map((step) => ({
476
+ domain: step.domain,
477
+ directiveId: step.directive.id,
478
+ })),
479
+ }),
480
+ };
421
481
  }
422
482
 
423
- export const birthReceivingGroup = birthReceivingGroupForDomain(RECEIVING_GROUP_LAW_DOMAIN);
483
+ /** Build the standalone framework receiving-group law with an explicit allowance. */
484
+ export const birthReceivingGroup = (maxChildWorkspacesPerUser: number) =>
485
+ birthReceivingGroupForDomain(
486
+ RECEIVING_GROUP_LAW_DOMAIN,
487
+ maxChildWorkspacesPerUser,
488
+ [RECEIVING_GROUP_LAW_DOMAIN],
489
+ );
424
490
 
425
491
  /**
426
492
  * THE SHARED-WITH-ME INDEX (§5) — one record per cross-custody attestation delivered to this home holon: