@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 CHANGED
@@ -1,3 +1,17 @@
1
+ ## 0.64.4
2
+
3
+ - Require generated applications to seed explicit child-birth and workspace policies before first use;
4
+ missing law remains a typed kernel refusal instead of a compiled allowance or creation fallback.
5
+ - Keep application startup single-flight and bounded through authentication, custody restore, law install
6
+ and client binding so concurrent UI consumers share one lifecycle and one actionable result.
7
+
8
+ ## 0.64.3
9
+
10
+ - Single-flight generated child-workspace acquisition and continuous sync so concurrent references share one
11
+ open, one lifecycle result and one error rather than cloning or registering the same workspace twice.
12
+ - Generate separate shared-workspace reads so applications can render denormalized owned-workspace cards
13
+ without eagerly opening every founded child merely to discover genuinely shared lives.
14
+
1
15
  ## 0.64.2
2
16
 
3
17
  - Execute generated applications through the typed USDA-native kernel boundary, including native read
@@ -126,6 +126,7 @@ class NomosTestCloud {
126
126
  /// goes last and is retained as the explicit law used to birth a receiving group. A payload package is
127
127
  /// not made current on the parent merely to make it available to a child.
128
128
  List<String> lawUsdzPaths = const [],
129
+ int? applicationMaxChildWorkspacesPerUser,
129
130
  Duration readyTimeout = const Duration(seconds: 30),
130
131
  void Function(String line)? onDiagnostic,
131
132
  }) async {
@@ -137,6 +138,10 @@ class NomosTestCloud {
137
138
  if (ephemeral) '--ephemeral',
138
139
  if (dataDir != null) ...['--data', dataDir],
139
140
  if (platform != null) ...['--platform', platform],
141
+ if (applicationMaxChildWorkspacesPerUser != null) ...[
142
+ '--application-max-child-workspaces-per-user',
143
+ '$applicationMaxChildWorkspacesPerUser',
144
+ ],
140
145
  for (final deploy in [
141
146
  ...lawUsdzPaths,
142
147
  if (lawUsdzPath != null && !lawUsdzPaths.contains(lawUsdzPath))
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // <define:__NOMOS_KERNEL_RELEASE__>
4
- var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "efe619cbabba8f5d9d1a1309e7ab60feb489ddb4794ce5a6a95f35c7e7ae5928", version: "kernel-efe619cb", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience"], protocol: 1 }, testMint: { hash: "0a801b8f40a17d3a1534d1452f0dc990f160299f6db55fe9c0c39d7aa726730b", version: "kernel-0a801b8f", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience"], protocol: 1 } };
4
+ var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "4bf8f2709135d729475c4dfbabb2014f8d1e10ff506506da1ee0299a808c415e", version: "kernel-4bf8f270", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience"], protocol: 1 }, testMint: { hash: "c83316d63542760b76df0588e42c1ec34367536790bb73775694c6d167a8a87e", version: "kernel-c83316d6", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience"], protocol: 1 } };
5
5
 
6
6
  // runner/node-entry.ts
7
7
  import { createInterface } from "node:readline";
@@ -16,6 +16,43 @@ var RUNNER_BUNDLE_VERSION = typeof __NOMOS_BUNDLE_VERSION__ !== "undefined" ? __
16
16
  var plainObject = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : null;
17
17
  var errorMessage = (e, fallback = "nomos runtime error") => String(e?.message ?? e ?? fallback);
18
18
  var compact = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== null && v !== ""));
19
+ var PERF_COMPONENT_LIMIT = 12;
20
+ var PERF_PHASE_LIMIT = 16;
21
+ var PERF_NOTE_LIMIT = 12;
22
+ var numericEntries = (value, limit) => {
23
+ const entries = Object.entries(plainObject(value) || {}).filter(([, v]) => typeof v === "number" && Number.isFinite(v) && v !== 0).map(([name, n]) => [name, n]).sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]));
24
+ return {
25
+ values: Object.fromEntries(entries.slice(0, limit)),
26
+ omitted: Math.max(0, entries.length - limit)
27
+ };
28
+ };
29
+ function boundedKernelSpans(value) {
30
+ const root = plainObject(value);
31
+ if (!root) return value;
32
+ const ranked = Object.entries(root).map(([name, raw]) => {
33
+ const row = plainObject(raw) || {};
34
+ const phases = numericEntries(row.phases, PERF_PHASE_LIMIT);
35
+ const notes = numericEntries(row.notes, PERF_NOTE_LIMIT);
36
+ const flat = numericEntries(row, PERF_PHASE_LIMIT);
37
+ const totalUs = typeof row.totalUs === "number" && Number.isFinite(row.totalUs) ? row.totalUs : Object.values(flat.values).reduce((sum, n) => sum + Number(n), 0);
38
+ const shaped = plainObject(row.phases) || plainObject(row.notes) || row.totalUs !== void 0 ? compact({
39
+ totalUs,
40
+ phases: Object.keys(phases.values).length ? phases.values : void 0,
41
+ notes: Object.keys(notes.values).length ? notes.values : void 0,
42
+ omittedPhases: phases.omitted || void 0,
43
+ omittedNotes: notes.omitted || void 0
44
+ }) : compact({
45
+ ...flat.values,
46
+ omittedMetrics: flat.omitted || void 0
47
+ });
48
+ return { name, totalUs, shaped };
49
+ }).sort((a, b) => b.totalUs - a.totalUs);
50
+ const kept = ranked.slice(0, PERF_COMPONENT_LIMIT);
51
+ return {
52
+ ...Object.fromEntries(kept.map(({ name, shaped }) => [name, shaped])),
53
+ ...ranked.length > PERF_COMPONENT_LIMIT ? { _summary: { omittedComponents: ranked.length - PERF_COMPONENT_LIMIT } } : {}
54
+ };
55
+ }
19
56
  var errorInfo = (e, req = {}, component = "nomos.webview.runner") => {
20
57
  const err = e;
21
58
  const existing = plainObject(err?.errorInfo) || plainObject(err?.info);
@@ -99,6 +136,7 @@ function createWebviewRunner({ post: post2, openRealm: openRealm2 }) {
99
136
  const spanStarts = /* @__PURE__ */ new Map();
100
137
  const forwardSpan = (session, raw) => {
101
138
  const s = plainObject(raw) ?? {};
139
+ const presented = s.kernelSpans === void 0 ? s : { ...s, kernelSpans: boundedKernelSpans(s.kernelSpans) };
102
140
  const id = String(s.id ?? "");
103
141
  if (s.phase === "begin" && id) {
104
142
  spanStarts.set(id, s);
@@ -115,13 +153,13 @@ function createWebviewRunner({ post: post2, openRealm: openRealm2 }) {
115
153
  elapsedMs,
116
154
  domain: s.domain,
117
155
  directiveId: s.directiveId,
118
- kernelSpans: s.kernelSpans,
156
+ kernelSpans: presented.kernelSpans,
119
157
  born: s.born,
120
158
  birthOutcomes: s.birthOutcomes
121
159
  }))}`);
122
160
  }
123
161
  }
124
- post2({ v: WEBVIEW_BRIDGE_VERSION, event: "span", session, span: raw });
162
+ post2({ v: WEBVIEW_BRIDGE_VERSION, event: "span", session, span: presented });
125
163
  };
126
164
  const pendingCustodyAsks = /* @__PURE__ */ new Map();
127
165
  let connectGate = Promise.resolve();
@@ -566,7 +604,12 @@ function createWebviewRunner({ post: post2, openRealm: openRealm2 }) {
566
604
  return await (await need(a)).__nomosPrepareOwnedRecordAction({ action: a.action, scope: a.scope, record: a.record, peer: a.peer });
567
605
  },
568
606
  async cryptoOwnedBirthScopeKey(a) {
569
- return await (await need(a)).__nomosOwnedBirthScopeKey({ scope: a.scope, namespace: a.namespace, key: a.key, record: a.record });
607
+ return await (await need(a)).__nomosOwnedBirthScopeKey({
608
+ scope: a.scope,
609
+ namespace: a.namespace,
610
+ key: a.key,
611
+ ...a.record !== void 0 ? { record: a.record } : {}
612
+ });
570
613
  },
571
614
  async cryptoOwnedDeviceRequest(a) {
572
615
  return { request: await (await need(a)).__nomosOwnedDeviceRequest({ scope: a.scope }) };
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
  };
7
7
 
8
8
  // <define:__NOMOS_KERNEL_RELEASE__>
9
- var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "efe619cbabba8f5d9d1a1309e7ab60feb489ddb4794ce5a6a95f35c7e7ae5928", version: "kernel-efe619cb", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience"], protocol: 1 }, testMint: { hash: "0a801b8f40a17d3a1534d1452f0dc990f160299f6db55fe9c0c39d7aa726730b", version: "kernel-0a801b8f", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience"], protocol: 1 } };
9
+ var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "4bf8f2709135d729475c4dfbabb2014f8d1e10ff506506da1ee0299a808c415e", version: "kernel-4bf8f270", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience"], protocol: 1 }, testMint: { hash: "c83316d63542760b76df0588e42c1ec34367536790bb73775694c6d167a8a87e", version: "kernel-c83316d6", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience"], protocol: 1 } };
10
10
 
11
11
  // runner/offline-install.ts
12
12
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync } from "node:fs";
@@ -519,6 +519,7 @@ class NomosTestHarness {
519
519
  /// [autoTearDown] — when true (default) and [registerTearDown] is given, registers [dispose] as a teardown.
520
520
  static Future<NomosTestHarness> ephemeral({
521
521
  required List<String> lawUsdzPaths,
522
+ int? applicationMaxChildWorkspacesPerUser,
522
523
  String? ownerSubject,
523
524
  String workspace = 'test-ws',
524
525
  String clientId = 'test-runner',
@@ -670,6 +671,14 @@ class NomosTestHarness {
670
671
  ),
671
672
  );
672
673
  if (birthsApplicationHome) {
674
+ final maxChildWorkspaces = applicationMaxChildWorkspacesPerUser;
675
+ if (maxChildWorkspaces == null || maxChildWorkspaces < 0) {
676
+ throw NomosTestHarnessException(
677
+ 'a generated application scenario must carry its law-declared '
678
+ 'application.maxChildWorkspacesPerUser into the test ceremony; '
679
+ 'Nomos has no test-only birth-policy fallback',
680
+ );
681
+ }
673
682
  final devicePublic = info['devicePublic']?.toString();
674
683
  final deviceSecret = info['deviceSecret']?.toString();
675
684
  final framework = info['frameworkHash']?.toString();
@@ -689,6 +698,29 @@ class NomosTestHarness {
689
698
  );
690
699
  }
691
700
  final keyHash = sha256.convert(base64Decode(devicePublic)).toString();
701
+ final bornAt = DateTime.now().toUtc().toIso8601String();
702
+ await _phased(
703
+ phase: 'application: establish the parent birth policy',
704
+ workspace: installWorkspace,
705
+ awaiting: 'law-declared seedBirthPolicy offer',
706
+ timeout: phaseTimeout ?? _kOtherDeadline,
707
+ onDiagnostic: onDiagnostic,
708
+ // This throwaway parent is still on its warrant-free founding prefix. The generated
709
+ // scenario carries the value from the same package declaration that compiled the
710
+ // receiving-group plan; the harness does not choose or default it. The ordinary
711
+ // seed offer crosses the kernel author gate before the first birth is attempted.
712
+ run: () => plane.session(installWorkspace).offerOutcome(
713
+ domain: '__nomos_application_home_v1',
714
+ directiveId: 'seedBirthPolicy',
715
+ actor: owner,
716
+ authorSecret: deviceSecret,
717
+ payload: <String, Object?>{
718
+ 'maxBirthsPerPrincipal': maxChildWorkspaces,
719
+ 'setBy': owner,
720
+ 'setAt': bornAt,
721
+ },
722
+ ),
723
+ );
692
724
  final outcome = await _phased(
693
725
  phase: 'application: birth the signed-in application',
694
726
  workspace: workspace,
@@ -707,7 +739,7 @@ class NomosTestHarness {
707
739
  // Ordered application deployment is reference packages first, deferred child payload last.
708
740
  // The parent serves the references; the receiving group is born with only its payload law.
709
741
  'lawHash': laws.last,
710
- 'bornAt': DateTime.now().toUtc().toIso8601String(),
742
+ 'bornAt': bornAt,
711
743
  },
712
744
  ),
713
745
  );
@@ -1039,6 +1039,11 @@ class NomosBridge {
1039
1039
  // [acquireSession]'s per-workspace single-flight: workspace → the in-flight open every concurrent
1040
1040
  // acquirer joins (removed when it completes; a live session then hits the fast path instead).
1041
1041
  final Map<String, Future<NomosSession>> _acquiring = {};
1042
+ // Every workspace opened as part of one stateful application participates in continuous custody exchange.
1043
+ // Starting that lifecycle is itself single-flight: generated navigation may discover the same child through
1044
+ // several references, but the runner gets one autoSync registration for that session.
1045
+ final Set<String> _autoSyncSessions = {};
1046
+ final Map<String, Future<void>> _autoSyncStarting = {};
1042
1047
  int _seq = 0;
1043
1048
  final String _traceId = _newTraceId();
1044
1049
  late final StreamSubscription<Map<String, Object?>> _sub;
@@ -1875,14 +1880,34 @@ class NomosBridge {
1875
1880
  plane._sessionPrincipals[workspace] = sourcePrincipal;
1876
1881
  }
1877
1882
 
1883
+ Future<NomosSession> ready(NomosSession opened) async {
1884
+ inheritPrincipal();
1885
+ if (!plane._autoSyncSessions.contains(workspace)) {
1886
+ final start = plane._autoSyncStarting.putIfAbsent(workspace, () async {
1887
+ await opened.autoSync();
1888
+ plane._autoSyncSessions.add(workspace);
1889
+ });
1890
+ try {
1891
+ await start;
1892
+ } finally {
1893
+ if (identical(plane._autoSyncStarting[workspace], start)) {
1894
+ plane._autoSyncStarting.remove(workspace);
1895
+ }
1896
+ }
1897
+ }
1898
+ return opened;
1899
+ }
1900
+
1878
1901
  if (plane._sessionNames.contains(workspace)) {
1879
1902
  inheritPrincipal();
1880
- return Future.value(session(workspace));
1903
+ return ready(session(workspace));
1881
1904
  }
1882
1905
  // SINGLE-FLIGHT: one in-flight open per workspace per plane; concurrent callers join it.
1883
1906
  return plane._acquiring.putIfAbsent(workspace, () async {
1884
1907
  try {
1885
- if (plane._sessionNames.contains(workspace)) return session(workspace);
1908
+ if (plane._sessionNames.contains(workspace)) {
1909
+ return ready(session(workspace));
1910
+ }
1886
1911
  // Consult the plane's TRUTHFUL registry (mounted ∪ parked ∪ resident custody — the runner's
1887
1912
  // realm.list()): anything the device already holds opens locally, no cloud, no credentials.
1888
1913
  var resident = false;
@@ -1898,8 +1923,7 @@ class NomosBridge {
1898
1923
  session: workspace,
1899
1924
  allowUnborn: true,
1900
1925
  authorSecret: authorSecret);
1901
- inheritPrincipal();
1902
- return session(workspace);
1926
+ return ready(session(workspace));
1903
1927
  }
1904
1928
  if (ifAbsentCredentials == null) {
1905
1929
  throw StateError(
@@ -1916,8 +1940,7 @@ class NomosBridge {
1916
1940
  authorSecret: c.authorSecret ?? authorSecret,
1917
1941
  allowUnborn: c.allowUnborn,
1918
1942
  );
1919
- inheritPrincipal();
1920
- return session(workspace);
1943
+ return ready(session(workspace));
1921
1944
  } finally {
1922
1945
  // completed (either way) — the next acquire re-evaluates fresh (a live session hits the fast path).
1923
1946
  plane._acquiring.remove(workspace);
@@ -1935,11 +1958,12 @@ class NomosBridge {
1935
1958
  /// @internal The immutable genesis of a sibling custody chain. This is observational metadata used to
1936
1959
  /// compose an ordinary source-enrolment offer; the kernel still reopens and proves every foreign read.
1937
1960
  Future<String> nomosWorkspaceGenesis(String workspace,
1938
- {String? session}) async =>
1961
+ {String? session}) async =>
1939
1962
  (_asMap(await _call('nomosWorkspaceGenesis', {
1940
- 'workspace': workspace,
1941
- if (session != null) 'session': session,
1942
- }))['genesis'] ?? '')
1963
+ 'workspace': workspace,
1964
+ if (session != null) 'session': session,
1965
+ }))['genesis'] ??
1966
+ '')
1943
1967
  .toString();
1944
1968
 
1945
1969
  /// UNDO the most-recent action via the kernel STRIKE primitive (the fold drops the struck intent by parity).
@@ -2452,7 +2476,8 @@ class NomosBridge {
2452
2476
  /// Compare an immutable publication pin with the stable series' one compiled
2453
2477
  /// current pointer. This is an O(current-state) check: it neither resolves the
2454
2478
  /// pinned publication nor reconstructs aggregate history.
2455
- Future<ReferenceFreshness<TVersion>> checkReferenceFreshness<TVersion, TSeries>(
2479
+ Future<ReferenceFreshness<TVersion>>
2480
+ checkReferenceFreshness<TVersion, TSeries>(
2456
2481
  VersionedNomosRef<TVersion, TSeries> reference,
2457
2482
  ) async {
2458
2483
  final result = _asMap(await _call('checkRefFreshness', {
@@ -2460,8 +2485,7 @@ class NomosBridge {
2460
2485
  {
2461
2486
  'pinned': NmsInternalNomosRefs.toJson(reference.pinned),
2462
2487
  'series': NmsInternalNomosRefs.toJson(reference.series),
2463
- 'currentField':
2464
- NmsInternalVersionedNomosRefs.currentField(reference),
2488
+ 'currentField': NmsInternalVersionedNomosRefs.currentField(reference),
2465
2489
  },
2466
2490
  ],
2467
2491
  }));
@@ -2666,8 +2690,8 @@ class NomosBridge {
2666
2690
  }));
2667
2691
 
2668
2692
  /// FRAMEWORK-INTERNAL business birth: derive the unborn child's owned key from its deterministic address.
2669
- Future<NomosScopeKey> nomosOwnedBirthScopeKey(
2670
- String scope, String namespace, Object? key, String recordType, String recordId) async {
2693
+ Future<NomosScopeKey> nomosOwnedBirthScopeKey(String scope, String namespace,
2694
+ Object? key, String recordType, String recordId) async {
2671
2695
  final out = _asMap(await _call('cryptoOwnedBirthScopeKey', {
2672
2696
  'scope': scope,
2673
2697
  'namespace': namespace,
@@ -2799,14 +2823,20 @@ class NomosBridge {
2799
2823
  /// Each round arrives on [syncEvents] as `{ event:'sync', reason, result }`; socket state as `{event:'link'}`.
2800
2824
  /// Call once after [connect] — then the app never polls.
2801
2825
  Future<void> autoSync(
2802
- {int? debounceMs, int? fallbackMs, String? signalId}) async =>
2803
- await _call('autoSync', {
2804
- if (debounceMs != null) 'debounceMs': debounceMs,
2805
- if (fallbackMs != null) 'fallbackMs': fallbackMs,
2806
- if (signalId != null) 'signalId': signalId
2807
- });
2826
+ {int? debounceMs, int? fallbackMs, String? signalId}) async {
2827
+ await _call('autoSync', {
2828
+ if (debounceMs != null) 'debounceMs': debounceMs,
2829
+ if (fallbackMs != null) 'fallbackMs': fallbackMs,
2830
+ if (signalId != null) 'signalId': signalId
2831
+ });
2832
+ final workspace = _sessionName;
2833
+ if (workspace != null) _eventPlane._autoSyncSessions.add(workspace);
2834
+ }
2835
+
2808
2836
  Future<void> stopAutoSync() async {
2809
2837
  await _call('stopAutoSync');
2838
+ final workspace = _sessionName;
2839
+ if (workspace != null) _eventPlane._autoSyncSessions.remove(workspace);
2810
2840
  }
2811
2841
 
2812
2842
  /// Background-sync telemetry: each autoSync round (`event:'sync'`) and doorbell socket state (`event:'link'`).
package/dart/pubspec.lock CHANGED
@@ -191,7 +191,7 @@ packages:
191
191
  path: "../dart_types"
192
192
  relative: true
193
193
  source: path
194
- version: "0.8.1"
194
+ version: "0.8.2"
195
195
  package_config:
196
196
  dependency: "direct main"
197
197
  description:
package/dart/pubspec.yaml CHANGED
@@ -8,7 +8,7 @@ description: >-
8
8
  Nomos 2 Dart frontend types — the hand-written kernel wire types plus the
9
9
  code-generated per-domain aggregate + directive-payload types. (De)serializes
10
10
  the EXACT serde JSON the Rust kernel + projection produce/consume.
11
- version: 0.64.2
11
+ version: 0.64.4
12
12
 
13
13
  environment:
14
14
  sdk: ^3.5.0
@@ -17,7 +17,7 @@ dependencies:
17
17
  # The pure-data core (wire types, offer codec, NomosReads, schema validation, error
18
18
  # info). nomos_client depends on and re-exports it, so existing imports are unchanged.
19
19
  # For local monorepo dev, pubspec_overrides.yaml points this at ../dart_types.
20
- nomos_types: ^0.8.1
20
+ nomos_types: ^0.8.2
21
21
  json_schema: ^5.2.2
22
22
  crypto: ^3.0.0
23
23
  package_config: ^2.1.0
@@ -25,6 +25,11 @@ class FakeRunner implements BridgeTransport {
25
25
  }
26
26
 
27
27
  int get connectCount => sent.where((m) => m['op'] == 'connect').length;
28
+ int autoSyncCount(String workspace) => sent
29
+ .where((m) =>
30
+ m['op'] == 'autoSync' &&
31
+ ((m['args'] as Map)['session']).toString() == workspace)
32
+ .length;
28
33
  Map<String, Object?> connectArgs(int i) =>
29
34
  (sent.where((m) => m['op'] == 'connect').elementAt(i)['args'] as Map)
30
35
  .cast<String, Object?>();
@@ -89,6 +94,8 @@ void main() {
89
94
  fail('credentials must not be requested for a mounted session'));
90
95
  expect(identical(s, bridge.session('home')), isTrue);
91
96
  expect(runner.connectCount, 1, reason: 'no re-connect for a live session');
97
+ expect(runner.autoSyncCount('home'), 1,
98
+ reason: 'an acquired application workspace continuously syncs');
92
99
  await bridge.dispose();
93
100
  });
94
101
 
@@ -140,6 +147,8 @@ void main() {
140
147
  reason: 'ONE connect for three concurrent acquires');
141
148
  expect(identical(results[0], results[1]), isTrue);
142
149
  expect(identical(results[1], results[2]), isTrue);
150
+ expect(runner.autoSyncCount('cloudy'), 1,
151
+ reason: 'the workspace lifecycle is single-flight too');
143
152
  final args = runner.connectArgs(0);
144
153
  expect(args['cloud'], 'https://cloud.example');
145
154
  expect(args['authToken'], 'tok');
@@ -148,6 +148,21 @@ void main() {
148
148
  final home = harness.session('home');
149
149
  final bornEstate = await _leg(
150
150
  'leg 2: internal CO2 law setup (Estate → Site → Asset)', () async {
151
+ // This is an internal framework-conformance setup, so declare its parent allowance explicitly
152
+ // through the same offer a compiled application ceremony uses. There is deliberately no harness
153
+ // or kernel fallback for a birth-capable workspace with missing policy.
154
+ final bornAt = DateTime.now().toUtc().toIso8601String();
155
+ await home.offerOutcome(
156
+ domain: 'co2',
157
+ directiveId: 'seedBirthPolicy',
158
+ payload: {
159
+ 'maxBirthsPerPrincipal': 1,
160
+ 'setBy': _owner,
161
+ 'setAt': bornAt,
162
+ },
163
+ actor: _owner,
164
+ authorSecret: harness.deviceSecret(home.workspace),
165
+ );
151
166
  final birth = await home.offerOutcome(
152
167
  domain: 'co2',
153
168
  directiveId: 'nomosBirthEstate',
@@ -157,7 +172,7 @@ void main() {
157
172
  'country': 'GB',
158
173
  'baselineTonnesCo2e': 4200,
159
174
  'principal': _owner,
160
- 'bornAt': DateTime.now().toUtc().toIso8601String(),
175
+ 'bornAt': bornAt,
161
176
  },
162
177
  actor: _owner,
163
178
  );
@@ -1,3 +1,8 @@
1
+ ## 0.8.2
2
+
3
+ - Carry the typed bounded-startup, missing-law and governed child-policy errors used by the USDA-native
4
+ application lifecycle, preserving kernel refusals as actionable frontend states.
5
+
1
6
  ## 0.8.1
2
7
 
3
8
  - Carry the typed USDA-native kernel protocol, plan diagnostics, reference freshness and application
@@ -9,7 +9,7 @@ description: >-
9
9
  Pure kernel wire/data types for Nomos Dart frontends — no transport, no bridge,
10
10
  no runtime. The hand-written serde-JSON-compatible types plus the offer codec,
11
11
  reactive read engine, schema validation, and data-only error/trace types.
12
- version: 0.8.1
12
+ version: 0.8.2
13
13
 
14
14
  environment:
15
15
  sdk: ^3.5.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.103.0",
3
+ "version": "0.103.2",
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.",
@@ -0,0 +1,73 @@
1
+ // NOMOS — explicit application-home founding ceremony.
2
+
3
+ import type { Directive } from "./directive.js";
4
+
5
+ const APPLICATION_HOME_GENESIS_BRAND = "__nomos_application_home_genesis_v1" as const;
6
+
7
+ /** Facts fixed by the canonical receiving-group birth. Tenant law may derive founding offers
8
+ * from these facts; the host supplies none of them and cannot append an initializer. */
9
+ export interface ApplicationHomeGenesisFacts {
10
+ readonly owner: string;
11
+ readonly workspaceName: string;
12
+ readonly bornAt: string;
13
+ }
14
+
15
+ export interface ApplicationHomeGenesisStep<
16
+ P extends Record<string, unknown> = Record<string, unknown>,
17
+ > {
18
+ readonly domain: string;
19
+ readonly directive: Directive<P>;
20
+ readonly payload: (facts: ApplicationHomeGenesisFacts) => P;
21
+ }
22
+
23
+ export interface ApplicationHomeGenesis {
24
+ readonly [APPLICATION_HOME_GENESIS_BRAND]: true;
25
+ readonly steps: readonly ApplicationHomeGenesisStep<any>[];
26
+ }
27
+
28
+ /** Declare one ordinary offer which must run during application-home genesis. The directive's
29
+ * own schema and invariants remain the authority over its payload; this helper only binds the
30
+ * canonical founder facts to that offer and names the law namespace which owns it. */
31
+ export function offerAtHomeGenesis<P extends Record<string, unknown>>(
32
+ domain: string,
33
+ directive: Directive<P>,
34
+ payload: (facts: ApplicationHomeGenesisFacts) => P,
35
+ ): ApplicationHomeGenesisStep<P> {
36
+ const namespace = domain.trim();
37
+ if (!/^[a-z][a-z0-9_-]*$/.test(namespace)) {
38
+ throw new Error(`offerAtHomeGenesis: '${domain}' is not a law domain name`);
39
+ }
40
+ if (directive === null || typeof directive !== "object" || typeof directive.id !== "string") {
41
+ throw new Error("offerAtHomeGenesis expects a finished intent or directive");
42
+ }
43
+ return Object.freeze({ domain: namespace, directive, payload });
44
+ }
45
+
46
+ /** The complete tenant-declared extension to the canonical application-home ceremony.
47
+ * An empty declaration is deliberate; omission means there are no tenant founding offers. */
48
+ export function applicationHomeGenesis<
49
+ const Steps extends readonly ApplicationHomeGenesisStep<any>[],
50
+ >(
51
+ ...steps: Steps
52
+ ): ApplicationHomeGenesis {
53
+ const seen = new Set<string>();
54
+ for (const step of steps) {
55
+ const key = `${step.domain}\u001f${step.directive.id}`;
56
+ if (seen.has(key)) {
57
+ throw new Error(
58
+ `applicationHomeGenesis repeats '${step.domain}/${step.directive.id}'; ` +
59
+ "one founding offer must have one accountable position",
60
+ );
61
+ }
62
+ seen.add(key);
63
+ }
64
+ return Object.freeze({
65
+ [APPLICATION_HOME_GENESIS_BRAND]: true,
66
+ steps: Object.freeze([...steps]),
67
+ });
68
+ }
69
+
70
+ export function isApplicationHomeGenesis(value: unknown): value is ApplicationHomeGenesis {
71
+ return value !== null && typeof value === "object" &&
72
+ (value as Record<string, unknown>)[APPLICATION_HOME_GENESIS_BRAND] === true;
73
+ }
@@ -28,7 +28,8 @@
28
28
  * CAS-re-derived on every lane), and a synthesized framework `workspaceInvariant` (the exact
29
29
  * WorkspaceQuota shape) refuses `birth-quota-exceeded` once the subject already holds its
30
30
  * allowance. Allowance resolution: the latest {@link BirthGrant} for the subject if any, else
31
- * the workspace's {@link BirthPolicy} singleton, else {@link DEFAULT_MAX_BIRTHS_PER_PRINCIPAL}.
31
+ * the workspace's REQUIRED {@link BirthPolicy} singleton. There is no compiled allowance:
32
+ * missing policy is invalid state and fails closed as `birth-policy-not-configured`.
32
33
  *
33
34
  * A3 — THE POOL CHAINS TO ROOT. A parent births a child WITH the child's own descendant budget
34
35
  * (`buildGenesisChain`'s `descendantPool` threads a `seedBirthPolicy` genesis step), so every
@@ -164,15 +165,6 @@ export const BIRTH_FRAMEWORK_QUERIES: readonly QueryDecl[] = [
164
165
  childLifecyclesByStatus,
165
166
  ];
166
167
 
167
- /**
168
- * THE DEFAULT BIRTH ALLOWANCE when a workspace carries neither a BirthPolicy singleton nor a grant
169
- * for the subject: a CONSERVATIVE floor for non-root workspaces (a home births a handful of estate
170
- * children; a platform that needs more seeds a BirthPolicy at its own genesis — the A3 induction —
171
- * or grants per-subject allowances). Root/platform governance already carries the 50-workspace
172
- * `WorkspaceQuota`; THIS default only ever binds law that never set its own policy.
173
- */
174
- export const DEFAULT_MAX_BIRTHS_PER_PRINCIPAL = 5;
175
-
176
168
  // ── the meter subject + the auto-record/auto-capture halves the `.births()` lane calls ─────────────
177
169
 
178
170
  /** The payload-carried birth SUBJECT the meter counts under: `principal`, else `owner`, else `""`
@@ -257,9 +249,9 @@ type MeterRow = { id?: string; data?: Record<string, unknown> };
257
249
  * THE BIRTH-QUOTA VERDICT — the PURE predicate over the COMMITTED captured reads (the same
258
250
  * `{queryId,args,result}` the plan recorded, CAS-re-derived on every lane). Counts the subject's
259
251
  * birth records and compares against its allowance (the latest BirthGrant.maxBirths if any, else
260
- * BirthPolicy.maxBirthsPerPrincipal, else {@link DEFAULT_MAX_BIRTHS_PER_PRINCIPAL}). The NEW birth's
261
- * record is NOT yet in the pre-apply state, so `count < allowance` (strict) is the correct test. A
262
- * subject-less birth fails CLOSED. Identical on author, edge admission, and verify_chain.
252
+ * BirthPolicy.maxBirthsPerPrincipal). The NEW birth's record is NOT yet in the pre-apply state, so
253
+ * `count < allowance` (strict) is the correct test. A subject-less birth and a workspace with no
254
+ * configured policy both fail CLOSED. Identical on author, edge admission, and verify_chain.
263
255
  */
264
256
  export function birthQuotaVerdict(
265
257
  reads: readonly { queryId: string; args: Record<string, string>; result: unknown }[],
@@ -277,7 +269,7 @@ export function birthQuotaVerdict(
277
269
  .filter((r) => r.data?.status === "retired" || r.data?.status === "reclaimed")
278
270
  .length;
279
271
  const count = Math.max(0, born - terminal);
280
- // ALLOWANCE: the LATEST BirthGrant (max grantedAt) wins; else the BirthPolicy singleton; else the floor.
272
+ // ALLOWANCE: the LATEST BirthGrant (max grantedAt) wins; else the REQUIRED BirthPolicy singleton.
281
273
  let allowance: number | undefined;
282
274
  let latestGrantedAt = -Infinity;
283
275
  for (const g of rowsOf(birthGrantByPrincipal.id, "principal", subject)) {
@@ -293,7 +285,7 @@ export function birthQuotaVerdict(
293
285
  const max = Number(policyRows[0]?.data?.maxBirthsPerPrincipal ?? NaN);
294
286
  if (Number.isFinite(max)) allowance = max;
295
287
  }
296
- if (allowance === undefined) allowance = DEFAULT_MAX_BIRTHS_PER_PRINCIPAL;
288
+ if (allowance === undefined) return { reject: "birth-policy-not-configured" };
297
289
  return count < allowance ? { accept: true } : { reject: "birth-quota-exceeded" };
298
290
  }
299
291
 
@@ -31,6 +31,7 @@ import {
31
31
  BirthGrant,
32
32
  } from "./birth_meter.js";
33
33
  import {
34
+ grantBirthAllowance,
34
35
  reclaimChild,
35
36
  reclaimChildAsAdmin,
36
37
  reclaimChildAsAdminRequiresRetired,
@@ -41,6 +42,8 @@ import {
41
42
  retireChildAsAdminRequiresBirth,
42
43
  retireChildRequiresBirth,
43
44
  retireChildNotReclaimed,
45
+ seedBirthPolicy,
46
+ setBirthPolicy,
44
47
  } from "./framework/birth_meter.js";
45
48
  import { RelationTuple, relationTupleInstanceId, relationTuplesByObject } from "./framework/relations.js";
46
49
  import { GENESIS_WARRANT_ID, GenesisWarrant, seedGenesis } from "./framework/genesis.js";
@@ -236,6 +239,12 @@ export function withBirthMeterExports<M extends Record<string, unknown>>(mod: M)
236
239
  internalize("__nomos_NomosChildLifecycle", NomosChildLifecycle);
237
240
  internalize("__nomos_BirthPolicy", BirthPolicy);
238
241
  internalize("__nomos_BirthGrant", BirthGrant);
242
+ // A birth-capable workspace is never allowed to depend on a compiled allowance. Its law must
243
+ // carry both the genesis seed and the governed amendment verbs; presentation codegen keeps these
244
+ // framework controls private while the kernel can still route and authorize them normally.
245
+ internalize("__nomos_seedBirthPolicy", seedBirthPolicy);
246
+ internalize("__nomos_setBirthPolicy", setBirthPolicy);
247
+ internalize("__nomos_grantBirthAllowance", grantBirthAllowance);
239
248
  internalize("__nomos_retireChild", retireChild);
240
249
  internalize("__nomos_reclaimChild", reclaimChild);
241
250
  internalize("__nomos_retireChildAsAdmin", retireChildAsAdmin);