@githolon/dsl 0.103.7 → 0.103.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.
@@ -284,7 +284,7 @@ function dartActionOutputs(
284
284
  const fields = new Set<string>();
285
285
  return outputs.map((output) => {
286
286
  const dartType = concreteClassName(output.aggregateId);
287
- const field = member(dartType);
287
+ const field = member(output.many ? plural(dartType) : dartType);
288
288
  if (fields.has(field)) {
289
289
  throw new Error(
290
290
  `Dart application result for '${directive.id}' has two aggregate consequences named '${field}'; ` +
@@ -1140,7 +1140,11 @@ export function generateDartApplication(
1140
1140
  ` Future<${targetApplication}> _nmsOpenReference(NomosRef<${rootType}> reference) {`,
1141
1141
  ` final workspace = NmsInternalNomosRefs.workspace(reference);`,
1142
1142
  ` final existing = _nmsApplications[workspace];`,
1143
- ` if (existing != null) return existing;`,
1143
+ ` if (existing != null) {`,
1144
+ ` return NmsInternalApplicationHost.nmsInternalAwaitReferencedApplication(`,
1145
+ ` workspace: workspace, opening: existing,`,
1146
+ ` );`,
1147
+ ` }`,
1144
1148
  ` late final Future<${targetApplication}> opened;`,
1145
1149
  ` opened = (() async {`,
1146
1150
  ` try {`,
@@ -1153,7 +1157,9 @@ export function generateDartApplication(
1153
1157
  ` }`,
1154
1158
  ` })();`,
1155
1159
  ` _nmsApplications[workspace] = opened;`,
1156
- ` return opened;`,
1160
+ ` return NmsInternalApplicationHost.nmsInternalAwaitReferencedApplication(`,
1161
+ ` workspace: workspace, opening: opened,`,
1162
+ ` );`,
1157
1163
  ` }`,
1158
1164
  ``,
1159
1165
  ` /// Open the workspace named by a typed ${rootType} custody reference.`,
@@ -2080,7 +2086,7 @@ export function generateDartApplication(
2080
2086
  ` Widget Function(BuildContext context, Object error, VoidCallback retry)? errorBuilder,`,
2081
2087
  ` ApplicationModelChange? onModelChange,`,
2082
2088
  ` ValueChanged<NomosApplicationStartupState>? onStartupStateChanged,`,
2083
- ` Duration startupTimeout = const Duration(seconds: 45),`,
2089
+ ` Duration startupTimeout = const Duration(minutes: 3),`,
2084
2090
  ` }) => NmsInternalApplicationHost.home<${application}>(`,
2085
2091
  ` key: key, cloud: cloud, application: application, signIn: signIn,`,
2086
2092
  ` bind: nomosInternalBind${application}, builder: builder,`,
@@ -3338,7 +3338,14 @@ function generateSealedApplicationProof(
3338
3338
  ` if (!response.ok || !body.token) fail("the generated application could not attest its private device: " + (body.error || response.status));`,
3339
3339
  ` return body.token;`,
3340
3340
  `};`,
3341
- `const application = await ${opener}({ cloud, application: applicationName, signIn });`,
3341
+ `const performanceEnabled = process.env.NOMOS_PROOF_PERFORMANCE === "1";`,
3342
+ `const reportPerformance = performanceEnabled`,
3343
+ ` ? (sample: { readonly operation: string; readonly name: string; readonly durationMs: number; readonly domain?: string; readonly directive?: string }) =>`,
3344
+ ` console.error("[nomos-application-performance] " + JSON.stringify(sample))`,
3345
+ ` : undefined;`,
3346
+ `const coldOpenStarted = performance.now();`,
3347
+ `const application = await ${opener}({ cloud, application: applicationName, signIn, onPerformance: reportPerformance });`,
3348
+ `if (reportPerformance) reportPerformance({ operation: "open", name: "generated application ready (cold)", durationMs: performance.now() - coldOpenStarted });`,
3342
3349
  `ok("opened the generated ${applicationName}; identity, custody, recovery and sync stay inside Nomos");`,
3343
3350
  `const waitUntilSaved = async (): Promise<void> => {`,
3344
3351
  ` const current = await application.currentStatus();`,
@@ -3467,6 +3474,15 @@ function generateSealedApplicationProof(
3467
3474
  `const _proofDisposable = application as unknown as { [Symbol.asyncDispose]?: () => Promise<void> };`,
3468
3475
  `const _proofDispose = _proofDisposable[Symbol.asyncDispose];`,
3469
3476
  `if (_proofDispose) await Promise.race([_proofDispose(), new Promise<void>((resolve) => setTimeout(resolve, 1_000))]);`,
3477
+ `if (performanceEnabled) {`,
3478
+ ` const warmOpenStarted = performance.now();`,
3479
+ ` const warmApplication = await ${opener}({ cloud, application: applicationName, signIn, onPerformance: reportPerformance });`,
3480
+ ` reportPerformance?.({ operation: "open", name: "generated application ready (warm)", durationMs: performance.now() - warmOpenStarted });`,
3481
+ ` const warmStatus = await warmApplication.currentStatus();`,
3482
+ ` if (warmStatus.phase === "attention") fail(warmStatus.message ?? "the warm application needs attention");`,
3483
+ ` const warmDisposable = warmApplication as unknown as { [Symbol.asyncDispose]?: () => Promise<void> };`,
3484
+ ` if (warmDisposable[Symbol.asyncDispose]) await Promise.race([warmDisposable[Symbol.asyncDispose]!(), new Promise<void>((resolve) => setTimeout(resolve, 1_000))]);`,
3485
+ `}`,
3470
3486
  `process.exit(0);`,
3471
3487
  ``,
3472
3488
  );
package/src/codegen_ts.ts CHANGED
@@ -3920,6 +3920,10 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
3920
3920
  ),
3921
3921
  `type _${applicationName}Business = ${applicationType};`,
3922
3922
  `export type ${applicationName}Status = _NomosApplicationStatus;`,
3923
+ `export interface ${applicationName}PerformanceSample {`,
3924
+ ` readonly operation: "open" | "offer" | "birth" | "read" | "sync" | "maintenance";`,
3925
+ ` readonly name: string; readonly durationMs: number; readonly domain?: string; readonly directive?: string;`,
3926
+ `}`,
3923
3927
  `export type ${applicationName} = _${applicationName}Business & _NomosApplicationParticipant & _NomosApplicationLifecycle & _NomosApplicationModelLifecycle${hasInvitations ? " & _NomosApplicationInvitations" : ""};`,
3924
3928
  `export interface ${applicationName}OpenOptions {`,
3925
3929
  ` /** Nomos Cloud serving this deployed application. Configure it once at the app boundary. */`,
@@ -3928,6 +3932,8 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
3928
3932
  ` readonly application: string;`,
3929
3933
  ` /** Return the signed-in authority credential; when supplied, bind its opaque device challenge. */`,
3930
3934
  ` readonly signIn: (request?: { readonly deviceChallenge?: string }) => Promise<string>;`,
3935
+ ` /** Typed timings for developer tooling; Nomos routing, credentials and Git custody remain private. */`,
3936
+ ` readonly onPerformance?: (sample: ${applicationName}PerformanceSample) => void;`,
3931
3937
  `}`,
3932
3938
  `function _nomosPublicDomain<T extends Record<string, unknown>>(domain: T): _NomosPublicDomain<T> {`,
3933
3939
  ` const application: Record<string, unknown> = {};`,
@@ -4055,7 +4061,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
4055
4061
  ` * or parallel cache around it. Nomos owns offline custody and synchronization. */`,
4056
4062
  `export function ${applicationOpener}(options: ${applicationName}OpenOptions): Promise<${applicationName}> {`,
4057
4063
  ` return nomosInternalOpenGeneratedApplication(`,
4058
- ` { cloud: options.cloud, application: options.application, signIn: options.signIn, lawHash: ${hashConst} },`,
4064
+ ` { cloud: options.cloud, application: options.application, signIn: options.signIn, onPerformance: options.onPerformance, lawHash: ${hashConst} },`,
4059
4065
  ` (holon, user) => _nomosBind${applicationName}(holon as NomosHolon, user),`,
4060
4066
  ` );`,
4061
4067
  `}`,
@@ -197,6 +197,10 @@ const DSL_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
197
197
  * here so a generated offer can never silently fall back to initializing the whole tenant domain.
198
198
  */
199
199
  const BUSINESS_BIRTH_FRAMEWORK_PLAN_SOURCES: Readonly<Record<string, PlanSourceSliceTarget>> = {
200
+ seedBirthPolicy: {
201
+ modulePath: path.join(DSL_DIR, "src", "framework", "birth_meter.ts"),
202
+ exportName: "seedBirthPolicy",
203
+ },
200
204
  enrollSigner: {
201
205
  modulePath: path.join(DSL_DIR, "src", "framework", "signing_identity.ts"),
202
206
  exportName: "enrollSigner",
@@ -1642,12 +1646,12 @@ const dartTypesImportFor = (generatedSource: string): string => {
1642
1646
  * just the floor generated code needs — a lagging pin forces tenants into dependency_overrides. */
1643
1647
  // 0.3.0: SurfacedValue<T>/SurfacedObservation/PendingConflictRow — generated Surfaced-field typing +
1644
1648
  // the pendingConflicts read rows.
1645
- const NOMOS_TYPES_DEP = "^0.8.3";
1649
+ const NOMOS_TYPES_DEP = "^0.8.4";
1646
1650
  // 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
1647
1651
  // DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
1648
1652
  // REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
1649
- const NOMOS_CLIENT_DEP = "^0.64.6";
1650
- const NOMOS_FLUTTER_DEP = "^0.63.9";
1653
+ const NOMOS_CLIENT_DEP = "^0.64.8";
1654
+ const NOMOS_FLUTTER_DEP = "^0.63.11";
1651
1655
  // DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
1652
1656
  // and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
1653
1657
  // land back in dependency_overrides (the exact co2 report this closes, third occurrence).
@@ -1917,14 +1921,25 @@ function generatedPackageApplication(
1917
1921
  ` /// Prove, adopt or restore the package law against this workspace's exact history.`,
1918
1922
  ` ${rootMember.prefix}.NomosModel get model => ${rootMember.member}.model;`,
1919
1923
  ``,
1920
- ` /// Current durability and convergence across this business application.`,
1921
- ` ApplicationStatus get currentStatus => NmsInternalApplicationHost.currentApplicationStatus(_nmsSession);`,
1924
+ ` /// Current durability and convergence for the receiving-group workspace.`,
1925
+ ` /// A slow or offline sibling never makes this workspace's readiness ambiguous.`,
1926
+ ` ApplicationStatus get currentStatus => NmsInternalApplicationHost.currentStatus(_nmsSession);`,
1922
1927
  ``,
1923
- ` /// React whenever this application saves, receives work, goes offline or needs attention.`,
1924
- ` Stream<ApplicationStatus> watchStatus() => NmsInternalApplicationHost.watchApplicationStatus(_nmsSession);`,
1928
+ ` /// React whenever this receiving group saves, receives work, goes offline or needs attention.`,
1929
+ ` Stream<ApplicationStatus> watchStatus() => NmsInternalApplicationHost.watchStatus(_nmsSession);`,
1925
1930
  ``,
1926
- ` /// Wait until accepted local work is in shared custody and this application has caught up.`,
1931
+ ` /// Wait until this receiving group's accepted work is in custody and caught up.`,
1927
1932
  ` Future<void> whenCurrent({Duration timeout = const Duration(minutes: 2)}) =>`,
1933
+ ` NmsInternalApplicationHost.whenCurrent(_nmsSession, timeout: timeout);`,
1934
+ ``,
1935
+ ` /// Aggregate posture across every child currently owned by this generated application.`,
1936
+ ` ApplicationStatus get applicationStatus => NmsInternalApplicationHost.currentApplicationStatus(_nmsSession);`,
1937
+ ``,
1938
+ ` /// React to aggregate posture across every generated child currently in this application plane.`,
1939
+ ` Stream<ApplicationStatus> watchApplicationStatus() => NmsInternalApplicationHost.watchApplicationStatus(_nmsSession);`,
1940
+ ``,
1941
+ ` /// Explicitly wait for every generated child currently owned by this application.`,
1942
+ ` Future<void> whenApplicationCurrent({Duration timeout = const Duration(minutes: 2)}) =>`,
1928
1943
  ` NmsInternalApplicationHost.whenApplicationCurrent(_nmsSession, timeout: timeout);`,
1929
1944
  ...(hasInvitations
1930
1945
  ? [
@@ -1973,7 +1988,7 @@ function generatedPackageApplication(
1973
1988
  ` Widget Function(BuildContext context, Object error, VoidCallback retry)? errorBuilder,`,
1974
1989
  ` ApplicationModelChange? onModelChange,`,
1975
1990
  ` ValueChanged<NomosApplicationStartupState>? onStartupStateChanged,`,
1976
- ` Duration startupTimeout = const Duration(seconds: 45),`,
1991
+ ` Duration startupTimeout = const Duration(minutes: 3),`,
1977
1992
  ` }) => NmsInternalApplicationHost.home<${application}>(`,
1978
1993
  ` key: key, cloud: cloud, application: application, signIn: signIn,`,
1979
1994
  ` bind: nomosInternalBind${application}, builder: builder,`,
@@ -2544,7 +2559,8 @@ function emitDartPackages(
2544
2559
  "library;",
2545
2560
  "",
2546
2561
  `export 'package:${packages.types}/${genRel}/${businessSharedFile}';`,
2547
- `export 'package:nomos_flutter/src/generated_application_host.dart' show ApplicationModelApproval, ApplicationModelDecision, ApplicationModelPolicy, ApplicationModelReview;`,
2562
+ `export 'package:nomos_flutter/src/generated_application_host.dart' show ApplicationModelApproval, ApplicationModelDecision, ApplicationModelPolicy, ApplicationModelReview, NomosApplicationStartupPhase, NomosApplicationStartupState, NomosApplicationStartupTimeout, NomosWorkspaceOpenTimeout;`,
2563
+ `export 'package:nomos_types/nomos_types.dart' show CustodyError, CustodyPhase, CustodySource, NomosRecommendedAction;`,
2548
2564
  ...tenantTypesExportKeys
2549
2565
  .map((file) => {
2550
2566
  const names = tenantDartExportNames(typesSources.get(file) ?? "");
@@ -5161,7 +5177,7 @@ async function main(): Promise<void> {
5161
5177
  const temporaryDeclarationPath = temporaryClientPath.replace(/\.ts$/, ".d.ts");
5162
5178
  const runtimeTypesSource = [
5163
5179
  `export declare function nomosInternalOpenGeneratedApplication<T extends object>(`,
5164
- ` options: { cloud: string; application: string; signIn: (request?: { readonly deviceChallenge?: string }) => Promise<string>; lawHash: string },`,
5180
+ ` 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 },`,
5165
5181
  ` bind: (holon: unknown, user: { readonly identityId: string; readonly identityAddress: string }) => T,`,
5166
5182
  `): Promise<T & {`,
5167
5183
  ` currentStatus(): Promise<any>;`,
@@ -294,10 +294,12 @@ export const stageDomain = directive(DOMAIN_LIFECYCLE_DIRECTIVES.Stage)
294
294
  })
295
295
  .requires(MODEL_LIFECYCLE_AUTHORITY);
296
296
 
297
- /** Make a staged package current. The package bytes are already committed; the kernel
298
- * re-loads them from prefix state and runs the evolve gate on this phase transition.
299
- * The prior version stays Active by default so gradual-rollout clients can still have
300
- * their declared era checked; the newer activation stamp alone makes this law current. */
297
+ /** Make a staged or retained Active package current. The package bytes are already
298
+ * committed; the kernel re-loads them from prefix state and runs the evolve gate on
299
+ * this governed transition. Prior versions stay Active by default so gradual-rollout
300
+ * clients can still have their declared era checked; the newer activation stamp alone
301
+ * makes this law current. Re-promoting a shadowed Active package is therefore the lawful,
302
+ * history-proved inverse of a later package taking its domain keys. */
301
303
  export const promoteDomain = directive(DOMAIN_LIFECYCLE_DIRECTIVES.Promote)
302
304
  .mutates(DomainInstallation)
303
305
  .payload(z.object({
package/src/intent.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  import type { z } from "zod";
10
10
 
11
11
  import type {
12
+ BusinessSourceTrust,
12
13
  Directive,
13
14
  PreconditionDescriptor,
14
15
  ReferentialMarker,
@@ -233,11 +234,38 @@ function unsupportedKernelGate(directive: Directive<any>): string | undefined {
233
234
  if (directive.delegatedRoleGate !== undefined) return "delegated authority";
234
235
  if (directive.firstDeviceEnrollGate !== undefined) return "first-device enrollment";
235
236
  if (directive.certifiesEraGate !== undefined) return "era certification";
236
- if (directive.businessSourceTrust !== undefined) return "source-trust preflight";
237
237
  if ((directive.businessRecordInputs?.length ?? 0) > 0) return "record-input binding";
238
238
  return undefined;
239
239
  }
240
240
 
241
+ function remapBusinessSourceTrust<I>(
242
+ step: IntentStep<I, any>,
243
+ trust: BusinessSourceTrust,
244
+ ): BusinessSourceTrust {
245
+ const field = (privatePath: string): string => {
246
+ const mapped = remapStepPayloadPath(step, privatePath.split("."));
247
+ if (mapped === undefined || mapped.length === 0) {
248
+ throw new Error(
249
+ `compound intent cannot remap source-trust field '${privatePath}' from '${step.directive.id}'`,
250
+ );
251
+ }
252
+ return mapped.join(".");
253
+ };
254
+ return {
255
+ viaDirectiveId: trust.viaDirectiveId,
256
+ fromPayload: field(trust.fromPayload),
257
+ queryId: trust.queryId,
258
+ queryArgs: Object.fromEntries(
259
+ Object.entries(trust.queryArgs).map(([target, source]) => [target, field(source)]),
260
+ ),
261
+ copy: Object.fromEntries(
262
+ Object.entries(trust.copy).map(([target, source]) => [target, field(source)]),
263
+ ),
264
+ genesisField: trust.genesisField,
265
+ keyHashesField: trust.keyHashesField,
266
+ };
267
+ }
268
+
241
269
  const dedupeBy = <T>(values: readonly T[], key: (value: T) => string): T[] => {
242
270
  const out: T[] = [];
243
271
  const seen = new Set<string>();
@@ -333,24 +361,44 @@ class IntentBuilder<I> {
333
361
 
334
362
  const primary = this.steps.find((step) => step.directive.birthsChild !== true)?.directive
335
363
  ?? this.steps[0]!.directive;
336
- const outputs: SecondaryOutput[] = [];
337
- for (const [index, step] of this.steps.entries()) {
364
+ const allOutputs: SecondaryOutput[] = [];
365
+ for (const step of this.steps) {
338
366
  const candidate = step.directive;
339
- if (
340
- candidate.birthsChild !== true &&
341
- candidate !== primary &&
342
- !(candidate.aggregateId === primary.aggregateId && candidate.marker === primary.marker)
343
- ) {
344
- outputs.push({ aggregateId: candidate.aggregateId, marker: candidate.marker });
367
+ if (candidate.birthsChild !== true) {
368
+ allOutputs.push({
369
+ aggregateId: candidate.aggregateId,
370
+ marker: candidate.marker,
371
+ ...(candidate.outputCardinality === "many" ? { many: true } : {}),
372
+ ...(candidate.outputCardinality === "optional" ? { optional: true } : {}),
373
+ });
374
+ allOutputs.push(...(candidate.secondaryOutputs ?? []));
345
375
  }
346
- if (candidate.birthsChild !== true) outputs.push(...(candidate.secondaryOutputs ?? []));
347
376
  }
348
- const secondaryOutputs = dedupeBy(
349
- outputs.filter(
350
- (output) => !(output.aggregateId === primary.aggregateId && output.marker === primary.marker),
351
- ),
352
- (output) => `${output.marker}\0${output.aggregateId}`,
377
+ const outputGroups = new Map<string, SecondaryOutput[]>();
378
+ for (const output of allOutputs) {
379
+ const key = `${output.marker}\0${output.aggregateId}`;
380
+ outputGroups.set(key, [...(outputGroups.get(key) ?? []), output]);
381
+ }
382
+ const composedOutput = (group: readonly SecondaryOutput[]): SecondaryOutput => ({
383
+ aggregateId: group[0]!.aggregateId,
384
+ marker: group[0]!.marker,
385
+ ...(group.length > 1 || group.some((output) => output.many === true)
386
+ ? { many: true }
387
+ : group[0]!.optional === true
388
+ ? { optional: true }
389
+ : {}),
390
+ });
391
+ const primaryOutput = composedOutput(
392
+ outputGroups.get(`${primary.marker}\0${primary.aggregateId}`) ?? [{
393
+ aggregateId: primary.aggregateId,
394
+ marker: primary.marker,
395
+ }],
353
396
  );
397
+ const secondaryOutputs = [...outputGroups.values()]
398
+ .map(composedOutput)
399
+ .filter(
400
+ (output) => !(output.aggregateId === primary.aggregateId && output.marker === primary.marker),
401
+ );
354
402
  const inferredReturns = [
355
403
  primary.aggregateId,
356
404
  ...secondaryOutputs.map((output) => output.aggregateId),
@@ -421,6 +469,16 @@ class IntentBuilder<I> {
421
469
  }
422
470
  }
423
471
  const relationGate = oneStaticRelationGate(this.steps);
472
+ const sourceTrusts = this.steps.flatMap((step) =>
473
+ step.directive.businessSourceTrust === undefined
474
+ ? []
475
+ : [remapBusinessSourceTrust(step, step.directive.businessSourceTrust)],
476
+ );
477
+ if (sourceTrusts.length > 1) {
478
+ throw new Error(
479
+ `intent('${this.id}') composes more than one source-trust preflight — split the business action by source`,
480
+ );
481
+ }
424
482
  const steps = [...this.steps];
425
483
  const plan = (facts: I, ctx: Ports): PlannedOp[] => {
426
484
  const returned: PlannedOp[] = [];
@@ -455,8 +513,10 @@ class IntentBuilder<I> {
455
513
  // engine cannot rebuild its aggregate registry entry and silently drops
456
514
  // compound intents that also birth a child.
457
515
  ...(primary.aggregate === undefined ? {} : { aggregate: primary.aggregate }),
458
- ...(primary.outputCardinality !== undefined
459
- ? { outputCardinality: primary.outputCardinality }
516
+ ...(primaryOutput.many === true
517
+ ? { outputCardinality: "many" as const }
518
+ : primaryOutput.optional === true
519
+ ? { outputCardinality: "optional" as const }
460
520
  : {}),
461
521
  ...(secondaryOutputs.length > 0 ? { secondaryOutputs } : {}),
462
522
  payloadSchema: (birthSteps[0]?.directive.payloadSchema ?? this.contractDeclaration.accepts) as unknown as z.ZodType<I>,
@@ -475,6 +535,9 @@ class IntentBuilder<I> {
475
535
  ].sort(),
476
536
  ...(preconditions.length > 0 ? { preconditions } : {}),
477
537
  ...(relationGate !== undefined ? { requiresRelationGate: relationGate } : {}),
538
+ ...(sourceTrusts[0] === undefined
539
+ ? {}
540
+ : { businessSourceTrust: sourceTrusts[0] }),
478
541
  ...(businessBirth === undefined
479
542
  ? {}
480
543
  : {