@githolon/dsl 0.103.0 → 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.
- package/dart/CHANGELOG.md +14 -0
- package/dart/lib/src/dev_cloud.dart +5 -0
- package/dart/lib/src/runner/node-entry.mjs +47 -4
- package/dart/lib/src/runner/offline-install.mjs +1 -1
- package/dart/lib/src/test_harness.dart +33 -1
- package/dart/lib/src/webview_bridge.dart +51 -21
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_test.dart +9 -0
- package/dart/test/first_app_gauntlet_test.dart +16 -1
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/src/application_home_genesis.ts +73 -0
- package/src/birth_meter.ts +7 -15
- package/src/birth_meter_ensure.ts +9 -0
- package/src/births.ts +18 -0
- package/src/business_birth.ts +44 -1
- package/src/codegen_dart_application.ts +27 -1
- package/src/codegen_dart_scenario.ts +7 -0
- package/src/codegen_proof.ts +11 -1
- package/src/compile_package_main.ts +123 -7
- package/src/directive.ts +8 -0
- package/src/framework/application_home.ts +19 -2
- package/src/framework/birth_meter.ts +4 -5
- package/src/framework/home.ts +71 -5
- package/src/framework/workspaces.ts +44 -57
- package/src/index.ts +7 -1
- package/src/law_graph.ts +23 -0
- package/src/law_graph_usda.ts +11 -1
- package/src/package_config.ts +30 -0
package/src/births.ts
CHANGED
|
@@ -84,6 +84,11 @@ export interface BusinessBirthDecl<
|
|
|
84
84
|
readonly founderRoles: readonly string[];
|
|
85
85
|
/** The business noun those founder roles govern. */
|
|
86
86
|
readonly founderScope: string;
|
|
87
|
+
/** Explicit policy inherited by the BORN workspace. Zero declares a leaf workspace. */
|
|
88
|
+
readonly maxChildWorkspacesPerUser: number;
|
|
89
|
+
/** @internal Compiler-derived namespaces installed into the born workspace. The declaration owns
|
|
90
|
+
* the value; this list only repeats that value wherever the composed law requires its own state. */
|
|
91
|
+
__nomosBirthPolicyDomains?: readonly string[];
|
|
87
92
|
/** Optional business noun used only to make the durable address readable. Defaults to the aggregate id. */
|
|
88
93
|
readonly namespace?: string;
|
|
89
94
|
/** Owning business domain, needed only when another domain composes this birth into a wider intent. */
|
|
@@ -114,6 +119,11 @@ export interface BusinessBirthSpec<F extends Record<string, Field>> {
|
|
|
114
119
|
/** Business noun governed by those roles. Defaults to the born aggregate noun. */
|
|
115
120
|
readonly scope?: string;
|
|
116
121
|
};
|
|
122
|
+
/** Law for the workspace being born. This is mandatory because silence must not become a
|
|
123
|
+
* compiler-chosen quota; zero explicitly declares that the born workspace is a leaf. */
|
|
124
|
+
readonly children: {
|
|
125
|
+
readonly maxWorkspacesPerUser: number;
|
|
126
|
+
};
|
|
117
127
|
/** Additional atomic business initialization which belongs to the same birth. */
|
|
118
128
|
readonly initialise?: (
|
|
119
129
|
root: AggregateRef<F>,
|
|
@@ -174,6 +184,13 @@ export function birth<F extends Record<string, Field>>(
|
|
|
174
184
|
`birth(${aggregate.id}): founder scope '${founderScope}' is not a business noun`,
|
|
175
185
|
);
|
|
176
186
|
}
|
|
187
|
+
const maxChildWorkspacesPerUser = spec.children?.maxWorkspacesPerUser;
|
|
188
|
+
if (!Number.isInteger(maxChildWorkspacesPerUser) || maxChildWorkspacesPerUser < 0) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`birth(${aggregate.id}): children.maxWorkspacesPerUser must be an explicit non-negative integer; ` +
|
|
191
|
+
"zero declares a leaf workspace and Nomos has no compiled fallback",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
177
194
|
type Payload = BusinessBirthPayload<F>;
|
|
178
195
|
const declaration = {
|
|
179
196
|
__isBusinessBirth: true,
|
|
@@ -188,6 +205,7 @@ export function birth<F extends Record<string, Field>>(
|
|
|
188
205
|
},
|
|
189
206
|
founderRoles,
|
|
190
207
|
founderScope,
|
|
208
|
+
maxChildWorkspacesPerUser,
|
|
191
209
|
...(spec.namespace !== undefined ? { namespace: spec.namespace } : {}),
|
|
192
210
|
};
|
|
193
211
|
const inDomain = (domain: string): BusinessBirthDecl<Payload, F> => {
|
package/src/business_birth.ts
CHANGED
|
@@ -51,6 +51,8 @@ import {
|
|
|
51
51
|
|
|
52
52
|
/** Private recipe selectors. The kernel resolves these against the parent's current installed law. */
|
|
53
53
|
export const CURRENT_LAW_PREFIX = "nomos:current-law:";
|
|
54
|
+
/** @internal Compiler-only domain enumeration. It carries no allowance or authority. */
|
|
55
|
+
export const BUSINESS_BIRTH_POLICY_DOMAINS_EXPORT = "__nomos_birth_policy_domains_c9f14a";
|
|
54
56
|
|
|
55
57
|
export interface BusinessBirthMetadata {
|
|
56
58
|
readonly aggregateId: string;
|
|
@@ -124,6 +126,7 @@ const applicationIntentOnlyBirths = new WeakSet<object>();
|
|
|
124
126
|
function lowerOne<P extends Record<string, unknown>, F extends Record<string, Field>>(
|
|
125
127
|
decl: BusinessBirthDecl<P, F>,
|
|
126
128
|
domain: string,
|
|
129
|
+
initialBirthPolicyDomains: readonly string[] = [domain],
|
|
127
130
|
): Record<string, unknown> {
|
|
128
131
|
const fields = (decl.payloadSchema as z.ZodObject<z.ZodRawShape>).shape;
|
|
129
132
|
for (const field of INTERNAL_FIELDS) {
|
|
@@ -245,6 +248,16 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
|
|
|
245
248
|
payload: { domainHash: `${CURRENT_LAW_PREFIX}${domain}` },
|
|
246
249
|
domainHash: `${CURRENT_LAW_PREFIX}${domain}`,
|
|
247
250
|
},
|
|
251
|
+
...(decl.__nomosBirthPolicyDomains ?? initialBirthPolicyDomains).map((policyDomain) => ({
|
|
252
|
+
domain: policyDomain,
|
|
253
|
+
directiveId: "seedBirthPolicy",
|
|
254
|
+
actor: founder,
|
|
255
|
+
payload: {
|
|
256
|
+
maxBirthsPerPrincipal: decl.maxChildWorkspacesPerUser,
|
|
257
|
+
setBy: founder,
|
|
258
|
+
setAt: p.bornAt,
|
|
259
|
+
},
|
|
260
|
+
})),
|
|
248
261
|
{
|
|
249
262
|
domain,
|
|
250
263
|
directiveId: seedId,
|
|
@@ -396,10 +409,29 @@ export function businessBirthConsequence<
|
|
|
396
409
|
return lowered;
|
|
397
410
|
}
|
|
398
411
|
|
|
412
|
+
/** @internal Bind the compiler-derived composed-law namespaces to declarations already captured by
|
|
413
|
+
* wider business intents. This chooses no policy value; it only repeats the tenant-declared value
|
|
414
|
+
* across the namespaces whose USDA says they can birth. */
|
|
415
|
+
export function bindBusinessBirthPolicyDomains(
|
|
416
|
+
mod: Record<string, unknown>,
|
|
417
|
+
domains: readonly string[],
|
|
418
|
+
): void {
|
|
419
|
+
const declarations = businessBirthsOf(mod);
|
|
420
|
+
if (declarations.length === 0) return;
|
|
421
|
+
const unique = [...new Set(domains)];
|
|
422
|
+
if (unique.length === 0 || unique.some((value) => value.trim().length === 0)) {
|
|
423
|
+
throw new Error("birth policy namespaces must be a non-empty list of domain names");
|
|
424
|
+
}
|
|
425
|
+
for (const declaration of declarations) {
|
|
426
|
+
declaration.__nomosBirthPolicyDomains = unique;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
399
430
|
/** Lower the one tenant-facing birth declaration into ordinary, gate-visible framework directives. */
|
|
400
431
|
export function expandBusinessBirthExports<M extends Record<string, unknown>>(
|
|
401
432
|
mod: M,
|
|
402
433
|
domain: string,
|
|
434
|
+
birthPolicyDomains?: readonly string[],
|
|
403
435
|
): Record<string, unknown> {
|
|
404
436
|
const declarations = businessBirthsOf(mod);
|
|
405
437
|
if (declarations.length === 0) return mod;
|
|
@@ -409,7 +441,18 @@ export function expandBusinessBirthExports<M extends Record<string, unknown>>(
|
|
|
409
441
|
"ambiguous — model one born aggregate root per domain",
|
|
410
442
|
);
|
|
411
443
|
}
|
|
412
|
-
const
|
|
444
|
+
const compiledDomains = mod[BUSINESS_BIRTH_POLICY_DOMAINS_EXPORT];
|
|
445
|
+
const selectedDomains = birthPolicyDomains ?? (
|
|
446
|
+
Array.isArray(compiledDomains) && compiledDomains.every((value) => typeof value === "string")
|
|
447
|
+
? compiledDomains as string[]
|
|
448
|
+
: [domain]
|
|
449
|
+
);
|
|
450
|
+
const policyDomains = [...new Set(selectedDomains)];
|
|
451
|
+
if (policyDomains.length === 0 || policyDomains.some((value) => value.trim().length === 0)) {
|
|
452
|
+
throw new Error(`birth(${declarations[0]!.aggregate.id}): compiled law declares no policy namespaces`);
|
|
453
|
+
}
|
|
454
|
+
bindBusinessBirthPolicyDomains(mod, policyDomains);
|
|
455
|
+
const lowered = lowerOne(declarations[0]!, domain, policyDomains);
|
|
413
456
|
for (const [key, generated] of Object.entries(lowered)) {
|
|
414
457
|
if (!Object.prototype.hasOwnProperty.call(mod, key)) continue;
|
|
415
458
|
const existing = mod[key];
|
|
@@ -405,6 +405,7 @@ export function generateDartApplication(
|
|
|
405
405
|
applicationMembers.set(name, owner);
|
|
406
406
|
};
|
|
407
407
|
claimApplicationMember("activity", "the generated business history");
|
|
408
|
+
claimApplicationMember("model", "the generated model lifecycle");
|
|
408
409
|
const collectionLines = new Map<string, string[]>();
|
|
409
410
|
const collectionMembers = new Map<string, Set<string>>();
|
|
410
411
|
const siblingImports = new Map<string, Set<string>>();
|
|
@@ -1168,6 +1169,18 @@ export function generateDartApplication(
|
|
|
1168
1169
|
` for (final record in records) {`,
|
|
1169
1170
|
` seen.add(NmsInternalRecordOrigins.require(record).workspace);`,
|
|
1170
1171
|
` }`,
|
|
1172
|
+
` for (final record in await shared()) {`,
|
|
1173
|
+
` final workspace = NmsInternalRecordOrigins.require(record).workspace;`,
|
|
1174
|
+
` if (seen.add(workspace)) records.add(record);`,
|
|
1175
|
+
` }`,
|
|
1176
|
+
` return records;`,
|
|
1177
|
+
` }`,
|
|
1178
|
+
``,
|
|
1179
|
+
` /// The ${birth.aggregateId} lives shared with the signed-in person.`,
|
|
1180
|
+
` /// Unlike [available], this does not open lives founded by that person.`,
|
|
1181
|
+
` Future<List<${rootModel}>> shared() async {`,
|
|
1182
|
+
` final records = <${rootModel}>[];`,
|
|
1183
|
+
` final seen = <String>{};`,
|
|
1171
1184
|
` final shares = await _client.bridge.query('sharedByObject', {'object': 'workspace:self'}, null, _client.actor);`,
|
|
1172
1185
|
` for (final row in shares) {`,
|
|
1173
1186
|
` final raw = row['data'];`,
|
|
@@ -1191,6 +1204,12 @@ export function generateDartApplication(
|
|
|
1191
1204
|
` return records;`,
|
|
1192
1205
|
` }`,
|
|
1193
1206
|
``,
|
|
1207
|
+
` /// React when this person's shared ${birth.aggregateId} lives change.`,
|
|
1208
|
+
` Stream<List<${rootModel}>> watchShared() =>`,
|
|
1209
|
+
` _client.bridge`,
|
|
1210
|
+
` .watch('sharedByObject', {'object': 'workspace:self'}, null, _client.actor, 60000)`,
|
|
1211
|
+
` .asyncMap((_) => shared());`,
|
|
1212
|
+
``,
|
|
1194
1213
|
` /// React when this person's available ${birth.aggregateId} lives change.`,
|
|
1195
1214
|
` Stream<List<${rootModel}>> watchAvailable() =>`,
|
|
1196
1215
|
` Stream<List<${rootModel}>>.multi((events) {`,
|
|
@@ -1904,7 +1923,9 @@ export function generateDartApplication(
|
|
|
1904
1923
|
`import 'package:nomos_flutter/src/application_status.dart' show ApplicationSignIn, ApplicationStatus;`,
|
|
1905
1924
|
]
|
|
1906
1925
|
: []),
|
|
1907
|
-
`import 'package:nomos_flutter/src/generated_application_host.dart' show
|
|
1926
|
+
`import 'package:nomos_flutter/src/generated_application_host.dart' show ${
|
|
1927
|
+
tenantRoot ? "NomosApplicationStartupState, " : ""
|
|
1928
|
+
}NmsInternalApplicationUserBinding${
|
|
1908
1929
|
tenantRoot || sharingPolicies.length > 0
|
|
1909
1930
|
? ", NmsInternalApplicationHost"
|
|
1910
1931
|
: ""
|
|
@@ -1954,6 +1975,9 @@ export function generateDartApplication(
|
|
|
1954
1975
|
` ${application}._(${client} client${
|
|
1955
1976
|
tenantRoot ? ", User currentUser" : ""
|
|
1956
1977
|
}) : ${applicationInitializers.join(", ")};`,
|
|
1978
|
+
``,
|
|
1979
|
+
` /// Prove, adopt or restore the law this application was generated against.`,
|
|
1980
|
+
` NomosModel get model => _client.model;`,
|
|
1957
1981
|
...(opts.flutterHost === true && tenantRoot
|
|
1958
1982
|
? [
|
|
1959
1983
|
``,
|
|
@@ -1977,10 +2001,12 @@ export function generateDartApplication(
|
|
|
1977
2001
|
` required Widget Function(BuildContext context, ${application} application) builder,`,
|
|
1978
2002
|
` Widget Function(BuildContext context)? loadingBuilder,`,
|
|
1979
2003
|
` Widget Function(BuildContext context, Object error, VoidCallback retry)? errorBuilder,`,
|
|
2004
|
+
` ValueChanged<NomosApplicationStartupState>? onStartupStateChanged,`,
|
|
1980
2005
|
` }) => NmsInternalApplicationHost.home<${application}>(`,
|
|
1981
2006
|
` key: key, cloud: cloud, application: application, signIn: signIn,`,
|
|
1982
2007
|
` bind: nomosInternalBind${application}, builder: builder,`,
|
|
1983
2008
|
` loadingBuilder: loadingBuilder, errorBuilder: errorBuilder,`,
|
|
2009
|
+
` onStartupStateChanged: onStartupStateChanged,`,
|
|
1984
2010
|
` );`,
|
|
1985
2011
|
]
|
|
1986
2012
|
: []),
|
|
@@ -90,6 +90,7 @@ export function generateDartScenario(
|
|
|
90
90
|
readonly surfaceName?: string;
|
|
91
91
|
readonly applicationFile?: string;
|
|
92
92
|
readonly applicationModules?: readonly DomainModule[];
|
|
93
|
+
readonly applicationMaxChildWorkspacesPerUser?: number;
|
|
93
94
|
} = {},
|
|
94
95
|
): string {
|
|
95
96
|
const domain = options.surfaceName ?? mod.domain ?? mod.name;
|
|
@@ -195,6 +196,9 @@ export function generateDartScenario(
|
|
|
195
196
|
` final packageFiles = await _applicationPackages();`,
|
|
196
197
|
` final environment = await NomosTestHarness.ephemeral(`,
|
|
197
198
|
` lawUsdzPaths: packageFiles.map((file) => file.path).toList(growable: false),`,
|
|
199
|
+
...(options.applicationMaxChildWorkspacesPerUser === undefined
|
|
200
|
+
? []
|
|
201
|
+
: [` applicationMaxChildWorkspacesPerUser: ${options.applicationMaxChildWorkspacesPerUser},`]),
|
|
198
202
|
` ownerSubject: person._principal,`,
|
|
199
203
|
` workspace: nmsInternalReceivingGroupNameOf(person._principal),`,
|
|
200
204
|
` nodeEntry: Platform.environment['NOMOS_NODE_ENTRY'],`,
|
|
@@ -224,6 +228,9 @@ export function generateDartScenario(
|
|
|
224
228
|
` final cloud = await NomosTestCloud.start(`,
|
|
225
229
|
` serverEntry: serverEntry, platform: applicationName,`,
|
|
226
230
|
` lawUsdzPaths: packageFiles.map((file) => file.path).toList(growable: false),`,
|
|
231
|
+
...(options.applicationMaxChildWorkspacesPerUser === undefined
|
|
232
|
+
? []
|
|
233
|
+
: [` applicationMaxChildWorkspacesPerUser: ${options.applicationMaxChildWorkspacesPerUser},`]),
|
|
227
234
|
` onDiagnostic: Platform.environment['NOMOS_TEST_DIAGNOSTICS'] == '1' ? stderr.writeln : null,`,
|
|
228
235
|
` );`,
|
|
229
236
|
` late final NomosTestHarness environment;`,
|
package/src/codegen_proof.ts
CHANGED
|
@@ -2441,6 +2441,9 @@ export interface TsProofOptions {
|
|
|
2441
2441
|
readonly domainHash: string;
|
|
2442
2442
|
/** Stable-id structural interface used to teach the offline harness the hidden owned-birth seal slots. */
|
|
2443
2443
|
readonly interface?: Readonly<Record<string, DomainInterface>>;
|
|
2444
|
+
/** Compiled application-home child allowance. The local proof cloud seeds this exact law fact before
|
|
2445
|
+
* warranting its throwaway platform; absent means this is a domain package, not an application root. */
|
|
2446
|
+
readonly applicationMaxChildWorkspacesPerUser?: number;
|
|
2444
2447
|
}
|
|
2445
2448
|
|
|
2446
2449
|
/** THE PROOF TAXONOMY (co2 feedback): which legs a domain carries.
|
|
@@ -2455,6 +2458,8 @@ export type ProofKind = "compile-valid" | "standalone-provable" | "child-birth-p
|
|
|
2455
2458
|
* marker shape; the `proof-legs.json` index is a map of these by domain key). */
|
|
2456
2459
|
export interface ProofLegs {
|
|
2457
2460
|
readonly domain: string;
|
|
2461
|
+
/** Application policy carried to the local proof ceremony; never a runner default. */
|
|
2462
|
+
readonly applicationMaxChildWorkspacesPerUser?: number;
|
|
2458
2463
|
/** THE TAXONOMY (co2 feedback) — the legs this domain has, in proof order. EVERY domain has at least
|
|
2459
2464
|
* `compile-valid`. OPTIONAL + ADDITIVE: an older-compiler legs file without it is treated as
|
|
2460
2465
|
* `["standalone-provable"]` (it only emitted standalone-provable domains). */
|
|
@@ -3223,7 +3228,12 @@ function generateSealedApplicationProof(
|
|
|
3223
3228
|
const domain = mod.domain ?? mod.name;
|
|
3224
3229
|
const applicationName = tsApplicationName(opts.packageName);
|
|
3225
3230
|
const opener = `open${applicationName}`;
|
|
3226
|
-
const marker = JSON.stringify(
|
|
3231
|
+
const marker = JSON.stringify({
|
|
3232
|
+
...primaryLegs,
|
|
3233
|
+
...(opts.applicationMaxChildWorkspacesPerUser === undefined
|
|
3234
|
+
? {}
|
|
3235
|
+
: { applicationMaxChildWorkspacesPerUser: opts.applicationMaxChildWorkspacesPerUser }),
|
|
3236
|
+
});
|
|
3227
3237
|
const standalone = orderedStandaloneCreates(mod)[0];
|
|
3228
3238
|
const businessBirth = primaryLegs.childBirth?.businessAggregateId !== undefined
|
|
3229
3239
|
? primaryLegs.childBirth
|
|
@@ -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 {
|
|
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 {
|
|
@@ -1603,8 +1609,8 @@ const NOMOS_TYPES_DEP = "^0.8.0";
|
|
|
1603
1609
|
// 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
|
|
1604
1610
|
// DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
|
|
1605
1611
|
// REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
|
|
1606
|
-
const NOMOS_CLIENT_DEP = "^0.64.
|
|
1607
|
-
const NOMOS_FLUTTER_DEP = "^0.63.
|
|
1612
|
+
const NOMOS_CLIENT_DEP = "^0.64.3";
|
|
1613
|
+
const NOMOS_FLUTTER_DEP = "^0.63.4";
|
|
1608
1614
|
// DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
|
|
1609
1615
|
// and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
|
|
1610
1616
|
// land back in dependency_overrides (the exact co2 report this closes, third occurrence).
|
|
@@ -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 =
|
|
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, {
|
|
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
|
-
|
|
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
|
-
*
|
|
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().
|
|
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().
|
|
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().
|
|
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(),
|