@githolon/dsl 0.102.5 → 0.103.1

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.
Files changed (132) hide show
  1. package/compile_package.mjs +43 -2
  2. package/compile_proof.d.mts +47 -0
  3. package/compile_proof.mjs +343 -0
  4. package/dart/CHANGELOG.md +14 -0
  5. package/dart/lib/src/dev_cloud.dart +8 -8
  6. package/dart/lib/src/nms_generated_runtime.dart +2 -0
  7. package/dart/lib/src/runner/node-entry.mjs +5539 -1228
  8. package/dart/lib/src/runner/offline-install.mjs +4578 -528
  9. package/dart/lib/src/test_harness.dart +206 -152
  10. package/dart/lib/src/webview_bridge.dart +239 -102
  11. package/dart/pubspec.lock +1 -1
  12. package/dart/pubspec.yaml +2 -2
  13. package/dart/test/acquire_session_credentials_test.dart +4 -3
  14. package/dart/test/acquire_session_test.dart +9 -0
  15. package/dart/test/adv_realm_interference_test.dart +7 -6
  16. package/dart/test/cloud_bound_topology_test.dart +3 -3
  17. package/dart/test/cluster_births_test.dart +4 -3
  18. package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
  19. package/dart/test/cluster_local_autosync_test.dart +3 -2
  20. package/dart/test/cluster_scale_test.dart +3 -2
  21. package/dart/test/dlq_session_surface_test.dart +7 -6
  22. package/dart/test/first_app_gauntlet_test.dart +14 -21
  23. package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
  24. package/dart/test/four_layer_nested_topology_test.dart +4 -4
  25. package/dart/test/harness_phase_timeout_test.dart +2 -2
  26. package/dart/test/local_sync_inert_test.dart +1 -1
  27. package/dart/test/parent_signer_recovery_test.dart +2 -2
  28. package/dart/test/sealing_client_test.dart +4 -3
  29. package/dart/test/session_bound_test.dart +8 -7
  30. package/dart/test/test_harness_test.dart +13 -12
  31. package/dart/test/vo_read_typing_test.dart +4 -3
  32. package/dart/test/watch_after_offer_stall_test.dart +4 -3
  33. package/dart/test/webview_bridge_test.dart +102 -5
  34. package/dart_types/CHANGELOG.md +5 -0
  35. package/dart_types/lib/nomos_types.dart +9 -0
  36. package/dart_types/lib/src/nomos_ref.dart +20 -0
  37. package/dart_types/lib/src/optional_ref.dart +85 -0
  38. package/dart_types/lib/src/sha256_digest.dart +41 -0
  39. package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
  40. package/dart_types/pubspec.yaml +1 -1
  41. package/dart_types/test/optional_ref_test.dart +44 -0
  42. package/dart_types/test/sha256_digest_test.dart +18 -0
  43. package/package.json +9 -5
  44. package/runtime/generated-application-host.js +5365 -1105
  45. package/src/aggregate.ts +18 -0
  46. package/src/application_composition.ts +6 -32
  47. package/src/attested_core.ts +6 -3
  48. package/src/birth_meter.ts +1 -0
  49. package/src/birth_meter_ensure.ts +28 -2
  50. package/src/births.ts +1 -1
  51. package/src/build_package.ts +162 -538
  52. package/src/business_birth.ts +19 -3
  53. package/src/canonical_json.ts +21 -0
  54. package/src/check_package_main.ts +73 -33
  55. package/src/clear_directive.ts +19 -8
  56. package/src/codegen_dart.ts +305 -22
  57. package/src/codegen_dart_application.ts +862 -292
  58. package/src/codegen_dart_client.ts +1084 -407
  59. package/src/codegen_dart_scenario.ts +12 -12
  60. package/src/codegen_proof.ts +169 -39
  61. package/src/codegen_provider_dart.ts +17 -1
  62. package/src/codegen_ts.ts +298 -106
  63. package/src/compat_manifest.ts +2 -2
  64. package/src/compat_package_main.ts +8 -12
  65. package/src/compile_package_main.ts +1860 -592
  66. package/src/compose.ts +1 -1
  67. package/src/directive.ts +208 -15
  68. package/src/engine_entry.ts +165 -111
  69. package/src/evolve_preview.ts +29 -69
  70. package/src/extremum.ts +1 -1
  71. package/src/fields.ts +183 -4
  72. package/src/flow_gate.ts +75 -2
  73. package/src/framework/application_home.ts +2 -0
  74. package/src/framework/auth_provider_law.ts +2 -2
  75. package/src/framework/birth_meter.ts +7 -19
  76. package/src/framework/bootstrap.ts +0 -2
  77. package/src/framework/business_corrections.ts +9 -5
  78. package/src/framework/domain_lifecycle.ts +15 -29
  79. package/src/framework/encryption_scope.ts +2 -1
  80. package/src/framework/framework_aggregates.ts +2 -0
  81. package/src/framework/genesis.ts +16 -14
  82. package/src/framework/home.ts +20 -14
  83. package/src/framework/owned_custody.ts +9 -3
  84. package/src/framework/platform_admin.ts +3 -3
  85. package/src/framework/relations.ts +21 -13
  86. package/src/framework/signing_identity.ts +45 -40
  87. package/src/framework/workspace_invariant.ts +245 -0
  88. package/src/framework/workspaces.ts +23 -29
  89. package/src/index.ts +36 -16
  90. package/src/intent.ts +178 -6
  91. package/src/law_check.ts +32 -11
  92. package/src/law_graph.ts +2902 -0
  93. package/src/law_graph_usda.ts +1799 -0
  94. package/src/law_identity.ts +435 -0
  95. package/src/law_value_type.ts +230 -0
  96. package/src/lsp_server.ts +58 -29
  97. package/src/ops.ts +7 -0
  98. package/src/package_config.ts +141 -0
  99. package/src/package_dependencies.ts +108 -0
  100. package/src/package_dependency_usage.ts +150 -0
  101. package/src/package_source_imports.ts +71 -0
  102. package/src/plan_source_slice.ts +335 -0
  103. package/src/point_guard.ts +192 -0
  104. package/src/precondition.ts +44 -9
  105. package/src/read_projection_namespace.ts +1 -1
  106. package/src/runtime_zod.ts +44 -0
  107. package/src/secondary_ensure.ts +67 -14
  108. package/src/shape_compat.ts +1 -1
  109. package/src/stable_id_identity_bridge.ts +149 -0
  110. package/src/stable_id_lockfile.ts +140 -6
  111. package/src/stable_ids.ts +1 -1
  112. package/src/stable_ids_for_module.ts +348 -0
  113. package/src/stable_ids_types.ts +1 -1
  114. package/src/sum.ts +1 -1
  115. package/src/usd_state.ts +1 -1
  116. package/src/usda_relationship_integrity.ts +66 -0
  117. package/src/usda_text.ts +18 -0
  118. package/src/usdz.ts +78 -0
  119. package/src/value_object.ts +95 -0
  120. package/src/wire.ts +19 -0
  121. package/src/wire_encode.ts +22 -0
  122. package/src/workspace_sharding.ts +52 -36
  123. package/src/workspace_sharing.ts +28 -4
  124. package/src/zod_introspection.ts +1 -1
  125. package/dart/test/cluster_attested_test.dart +0 -201
  126. package/src/compile_engine.ts +0 -595
  127. package/src/manifest.ts +0 -1476
  128. package/src/read_projection_composition.ts +0 -117
  129. package/src/read_projection_manifest.ts +0 -115
  130. package/src/relation_composition.ts +0 -286
  131. package/src/usd.ts +0 -1967
  132. package/src/usd_layers.ts +0 -1776
@@ -27,10 +27,17 @@ import 'package:nomos_types/nomos_types.dart'
27
27
  NomosTraceContext,
28
28
  NomosErrorInfo,
29
29
  NomosRef,
30
+ VersionedNomosRef,
31
+ ReferenceFreshness,
32
+ ReferenceCurrent,
33
+ ReferenceStale,
34
+ ReferenceFreshnessUnavailable,
30
35
  CustodyBreakPolicy,
31
36
  NomosCustodyEvent,
32
37
  NomosCustodyStatus;
33
38
  import 'package:nomos_types/src/nomos_ref.dart' show NmsInternalNomosRefs;
39
+ import 'package:nomos_types/src/versioned_nomos_ref.dart'
40
+ show NmsInternalVersionedNomosRefs;
34
41
  import 'package:nomos_types/src/foreign_ref.dart'
35
42
  show NomosForeignReader, NomosForeignWorkspaceReader;
36
43
 
@@ -43,6 +50,17 @@ import 'session_bound.dart' show DomainNotInstalledForSession;
43
50
  /// bridge root so app code reads as the four-layer split (engine → session → client → values).
44
51
  typedef NomosEngine = NomosBridge;
45
52
 
53
+ /// One stable bounded page from an authenticated declared-query index. [cursor] is opaque framework
54
+ /// state: pass it back unchanged to request the next page; [done] means this page exhausted the query.
55
+ final class NomosPage<T> {
56
+ const NomosPage(
57
+ {required this.rows, required this.cursor, required this.done});
58
+
59
+ final List<T> rows;
60
+ final String? cursor;
61
+ final bool done;
62
+ }
63
+
46
64
  /// The protocol version the runner and the bridge agree on (`webview-runner.mjs` `WEBVIEW_BRIDGE_VERSION`).
47
65
  const int kWebviewBridgeVersion = 1;
48
66
 
@@ -1021,6 +1039,11 @@ class NomosBridge {
1021
1039
  // [acquireSession]'s per-workspace single-flight: workspace → the in-flight open every concurrent
1022
1040
  // acquirer joins (removed when it completes; a live session then hits the fast path instead).
1023
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 = {};
1024
1047
  int _seq = 0;
1025
1048
  final String _traceId = _newTraceId();
1026
1049
  late final StreamSubscription<Map<String, Object?>> _sub;
@@ -1498,7 +1521,7 @@ class NomosBridge {
1498
1521
  {'id': id, if (session != null) 'session': session}));
1499
1522
 
1500
1523
  /// SALVAGE — best-effort extraction of this holon's local-only authored work (un-synced pending
1501
- /// writes + the DLQ), each with its full `intentB64` bytes: `{pendingWrites, deadLetters}`. The
1524
+ /// writes + the DLQ), each with its full `changeB64` bytes: `{pendingWrites, deadLetters}`. The
1502
1525
  /// read half of the recovery lane (`connect(recover: true)` runs salvage → [absorbSalvage]).
1503
1526
  Future<Map<String, Object?>> salvage({String? session}) async =>
1504
1527
  _asMap(await _call('salvage', {if (session != null) 'session': session}));
@@ -1537,11 +1560,9 @@ class NomosBridge {
1537
1560
  /// `encodeIntentOfferBytes`, with `security.actor` included iff [actor] is set). The generated Dart client
1538
1561
  /// calls THIS — it never hand-builds offer bytes, so there is exactly ONE copy of the wire format. Returns
1539
1562
  /// the new local head; durable on [sync]. ([offer] with opaque bytes stays for back-compat carriers.)
1540
- /// [attestations] hand-injects the signed foreign-read envelopes a captured-read directive consumes
1541
- /// (each `{queryId, result, attestation}` — the shape the kernel's attested-read gate re-derives against
1542
- /// its trust policy); [force] carries the deploy/evolve force flag through. Both are OMITTED from the wire
1543
- /// when absent, so a plain offer is byte-identical to before. [session] pins the colocated workspace in a
1544
- /// cluster harness (absent ⇒ the runner's sole-session default).
1563
+ /// Declared foreign reads are resolved and proved inside the shared kernel plane; callers never fetch or
1564
+ /// inject evidence. [force] carries the deploy/evolve force flag through. [session] pins the workspace in
1565
+ /// a cluster harness (absent ⇒ the runner's sole-session default).
1545
1566
  @Deprecated(
1546
1567
  'Engine-level string-y writes are the footgun (session_bound_clients.md ask 1/7): '
1547
1568
  'dispatch through a generated <Domain>Client bound to a NomosSession '
@@ -1553,7 +1574,6 @@ class NomosBridge {
1553
1574
  required Object? payload,
1554
1575
  String? actor,
1555
1576
  String? domainHash,
1556
- List<Map<String, Object?>>? attestations,
1557
1577
  List<Map<String, Object?>>? nomosConflictSnapshots,
1558
1578
  bool? force,
1559
1579
  String? session,
@@ -1567,7 +1587,6 @@ class NomosBridge {
1567
1587
  'payload': payload,
1568
1588
  if (actor != null) 'actor': actor,
1569
1589
  if (domainHash != null) 'domainHash': domainHash,
1570
- if (attestations != null) 'attestations': attestations,
1571
1590
  if (nomosConflictSnapshots != null)
1572
1591
  '__nomosConflictSnapshots': nomosConflictSnapshots,
1573
1592
  if (force != null) 'force': force,
@@ -1591,7 +1610,6 @@ class NomosBridge {
1591
1610
  required Object? payload,
1592
1611
  String? actor,
1593
1612
  String? domainHash,
1594
- List<Map<String, Object?>>? attestations,
1595
1613
  List<Map<String, Object?>>? nomosConflictSnapshots,
1596
1614
  bool? force,
1597
1615
  String? session,
@@ -1603,7 +1621,6 @@ class NomosBridge {
1603
1621
  payload: payload,
1604
1622
  actor: actor,
1605
1623
  domainHash: domainHash,
1606
- attestations: attestations,
1607
1624
  nomosConflictSnapshots: nomosConflictSnapshots,
1608
1625
  force: force,
1609
1626
  session: session,
@@ -1621,7 +1638,6 @@ class NomosBridge {
1621
1638
  required Object? payload,
1622
1639
  String? actor,
1623
1640
  String? domainHash,
1624
- List<Map<String, Object?>>? attestations,
1625
1641
  List<Map<String, Object?>>? nomosConflictSnapshots,
1626
1642
  bool? force,
1627
1643
  String? session,
@@ -1644,7 +1660,6 @@ class NomosBridge {
1644
1660
  'payload': payload,
1645
1661
  if (actor != null) 'actor': actor,
1646
1662
  if (domainHash != null) 'domainHash': domainHash,
1647
- if (attestations != null) 'attestations': attestations,
1648
1663
  if (nomosConflictSnapshots != null)
1649
1664
  '__nomosConflictSnapshots': nomosConflictSnapshots,
1650
1665
  if (force != null) 'force': force,
@@ -1786,6 +1801,22 @@ class NomosBridge {
1786
1801
  return (r['offer'] as Map?)?.cast<String, Object?>();
1787
1802
  }
1788
1803
 
1804
+ /// Prepare the exact authenticated execution slice a generated client is about to use. This is
1805
+ /// a readiness hint only: compilation, composition, cache custody and all trust decisions remain
1806
+ /// inside the kernel. Generated `bind` calls it so first-use cost is paid before the client becomes
1807
+ /// interactive, rather than on the person's first button press.
1808
+ Future<void> prepareDomain({
1809
+ required String domain,
1810
+ String? directiveId,
1811
+ required String packageHash,
1812
+ }) async {
1813
+ await _call('prepareDomain', {
1814
+ 'domain': domain,
1815
+ if (directiveId != null) 'directiveId': directiveId,
1816
+ 'packageHash': packageHash,
1817
+ });
1818
+ }
1819
+
1789
1820
  /// FOLLOW A [NomosRef] — THE cross-workspace primitive (session_bound_clients.md ask 8): mount
1790
1821
  /// the referenced workspace on this device's plane (idempotent — an already-open workspace is
1791
1822
  /// just returned) and hand back its [NomosSession]. Typed client construction is then one line:
@@ -1849,14 +1880,34 @@ class NomosBridge {
1849
1880
  plane._sessionPrincipals[workspace] = sourcePrincipal;
1850
1881
  }
1851
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
+
1852
1901
  if (plane._sessionNames.contains(workspace)) {
1853
1902
  inheritPrincipal();
1854
- return Future.value(session(workspace));
1903
+ return ready(session(workspace));
1855
1904
  }
1856
1905
  // SINGLE-FLIGHT: one in-flight open per workspace per plane; concurrent callers join it.
1857
1906
  return plane._acquiring.putIfAbsent(workspace, () async {
1858
1907
  try {
1859
- if (plane._sessionNames.contains(workspace)) return session(workspace);
1908
+ if (plane._sessionNames.contains(workspace)) {
1909
+ return ready(session(workspace));
1910
+ }
1860
1911
  // Consult the plane's TRUTHFUL registry (mounted ∪ parked ∪ resident custody — the runner's
1861
1912
  // realm.list()): anything the device already holds opens locally, no cloud, no credentials.
1862
1913
  var resident = false;
@@ -1872,8 +1923,7 @@ class NomosBridge {
1872
1923
  session: workspace,
1873
1924
  allowUnborn: true,
1874
1925
  authorSecret: authorSecret);
1875
- inheritPrincipal();
1876
- return session(workspace);
1926
+ return ready(session(workspace));
1877
1927
  }
1878
1928
  if (ifAbsentCredentials == null) {
1879
1929
  throw StateError(
@@ -1890,8 +1940,7 @@ class NomosBridge {
1890
1940
  authorSecret: c.authorSecret ?? authorSecret,
1891
1941
  allowUnborn: c.allowUnborn,
1892
1942
  );
1893
- inheritPrincipal();
1894
- return session(workspace);
1943
+ return ready(session(workspace));
1895
1944
  } finally {
1896
1945
  // completed (either way) — the next acquire re-evaluates fresh (a live session hits the fast path).
1897
1946
  plane._acquiring.remove(workspace);
@@ -1906,6 +1955,17 @@ class NomosBridge {
1906
1955
  (_asMap(await _call('mintId', {'typeTag': typeTag}))['id'] ?? '')
1907
1956
  .toString();
1908
1957
 
1958
+ /// @internal The immutable genesis of a sibling custody chain. This is observational metadata used to
1959
+ /// compose an ordinary source-enrolment offer; the kernel still reopens and proves every foreign read.
1960
+ Future<String> nomosWorkspaceGenesis(String workspace,
1961
+ {String? session}) async =>
1962
+ (_asMap(await _call('nomosWorkspaceGenesis', {
1963
+ 'workspace': workspace,
1964
+ if (session != null) 'session': session,
1965
+ }))['genesis'] ??
1966
+ '')
1967
+ .toString();
1968
+
1909
1969
  /// UNDO the most-recent action via the kernel STRIKE primitive (the fold drops the struck intent by parity).
1910
1970
  /// [scope] `'all'` undoes any author's most-recent action; `'user'` only THIS replica's own. [actions] is the
1911
1971
  /// app's set of undoable directive ids (so undo never strikes a framework/control intent). [domain] +
@@ -1924,7 +1984,6 @@ class NomosBridge {
1924
1984
  required Object? payload,
1925
1985
  String? actor,
1926
1986
  String? domainHash,
1927
- List<Map<String, Object?>>? attestations,
1928
1987
  List<Map<String, Object?>>? nomosConflictSnapshots,
1929
1988
  bool? force,
1930
1989
  String? session,
@@ -1935,21 +1994,10 @@ class NomosBridge {
1935
1994
  payload: payload,
1936
1995
  actor: actor,
1937
1996
  domainHash: domainHash,
1938
- attestations: attestations,
1939
1997
  nomosConflictSnapshots: nomosConflictSnapshots,
1940
1998
  force: force,
1941
1999
  session: session);
1942
2000
 
1943
- /// ATTESTED READ — serve + SIGN a declared query over a COLOCATED source session's certified fold (the
1944
- /// dynamic-source trust lane; architecture/dynamic_source_attested_reads.md). In a [NomosTestHarness.cluster]
1945
- /// (one engine plane, many sessions) this fulfils the read IN-PROCESS from the [from] session and returns
1946
- /// `{ entry: {queryId, result, attestation:{source_genesis, public_key, …}} }` — pass `result['entry']`
1947
- /// (the whole entry map) as one [offerDirective] `attestations` element, or read
1948
- /// `entry['attestation']['source_genesis']` to enrol the source in a reader's allow-list. [args] are the
1949
- /// query params. [session] is the caller/reader session (defaults to this view's bound session).
1950
- /// [attestorSecret] is the source's base64 read-attestor secret (from [authorKeygen]); when omitted the
1951
- /// source session's connect-time `authorSecret` signs. [nowMs] stamps the freshness clock (defaults to the
1952
- /// source's own clock).
1953
2001
  /// RELEASE COHERENCE — assert the connected cloud's kernel supports every capability this law needs (the
1954
2002
  /// generated client passes its baked `requiresKernel`, e.g. `['compoundKeys']` for a compound-key query).
1955
2003
  /// Throws a typed [KernelCoherenceError] on a skew (a stale/mismatched cloud kernel) with the named reason,
@@ -1964,23 +2012,6 @@ class NomosBridge {
1964
2012
  });
1965
2013
  }
1966
2014
 
1967
- Future<Map<String, Object?>> attestedRead({
1968
- required String from,
1969
- required String queryId,
1970
- Map<String, Object?> args = const {},
1971
- String? attestorSecret,
1972
- int? nowMs,
1973
- String? session,
1974
- }) async =>
1975
- _asMap(await _call('attestedRead', {
1976
- 'from': from,
1977
- 'queryId': queryId,
1978
- 'args': args,
1979
- if (attestorSecret != null) 'attestorSecret': attestorSecret,
1980
- if (nowMs != null) 'nowMs': nowMs,
1981
- if (session != null) 'session': session,
1982
- }));
1983
-
1984
2015
  /// MAKE A WORKSPACE — the one-call tenant path. A child workspace under a platform/[parent] is a SIGNED
1985
2016
  /// control-plane offer; the holon seals `createWorkspace` (or [directiveId] `birthChild`, or ANY
1986
2017
  /// tenant-defined `.births()` directive — see [domain]) with the requester's device key ([authorSecret],
@@ -2196,8 +2227,10 @@ class NomosBridge {
2196
2227
 
2197
2228
  /// Watch the RUNTIME activity feed: a stream that re-emits the newest-first activity rows on every local
2198
2229
  /// change (doorbell/sync tick). `base` = a commit-oid cursor. Cancelling drops the watch on the runner.
2199
- Stream<List<dynamic>> watchActivity({String? base, String? session}) =>
2200
- _watchStream(kind: 'activity', base: base, session: session);
2230
+ Stream<List<dynamic>> watchActivity(
2231
+ {String? base, int? limit, String? session}) =>
2232
+ _watchStream(
2233
+ kind: 'activity', base: base, limit: limit, session: session);
2201
2234
 
2202
2235
  /// Mint an ed25519 DEVICE KEY in-kernel → `{secret, public}` (base64). The SECRET stays on this device
2203
2236
  /// (persist it in secure storage); pass it to [connect] as `authorSecret` (or per-offer `signWith`) so the
@@ -2388,16 +2421,46 @@ class NomosBridge {
2388
2421
  /// behavior). `principal` stamps the private-read gate (defaults to the connection's bound principal).
2389
2422
  /// Both are OMITTED from the wire when absent, so a public un-keyed read is byte-identical to before.
2390
2423
  Future<List<Map<String, dynamic>>> query(String queryId,
2391
- [Map<String, Object?> params = const {},
2392
- List<NomosScopeKey>? keys,
2393
- String? principal,
2394
- String? session]) async =>
2395
- _asRows(await _call('query', {
2396
- 'queryId': queryId,
2397
- 'params': params,
2398
- ...?_readAuthArgs(keys, principal),
2399
- if (session != null) 'session': session,
2400
- }));
2424
+ [Map<String, Object?> params = const {},
2425
+ List<NomosScopeKey>? keys,
2426
+ String? principal,
2427
+ String? session]) async {
2428
+ return _asRows(await _call('query', {
2429
+ 'queryId': queryId,
2430
+ 'params': params,
2431
+ ...?_readAuthArgs(keys, principal),
2432
+ if (session != null) 'session': session,
2433
+ }));
2434
+ }
2435
+
2436
+ /// One bounded query page. Unlike [query], this never requires the kernel to materialize or serialize
2437
+ /// the complete result set merely to show the first screen.
2438
+ Future<NomosPage<Map<String, dynamic>>> queryPage(
2439
+ String queryId, {
2440
+ Map<String, Object?> params = const {},
2441
+ int pageSize = 100,
2442
+ String? cursor,
2443
+ List<NomosScopeKey>? keys,
2444
+ String? principal,
2445
+ String? session,
2446
+ }) async {
2447
+ if (pageSize < 1 || pageSize > 1000) {
2448
+ throw RangeError.range(pageSize, 1, 1000, 'pageSize');
2449
+ }
2450
+ final result = _asMap(await _call('queryPage', {
2451
+ 'queryId': queryId,
2452
+ 'params': params,
2453
+ 'pageSize': pageSize,
2454
+ if (cursor != null) 'cursor': cursor,
2455
+ ...?_readAuthArgs(keys, principal),
2456
+ if (session != null) 'session': session,
2457
+ }));
2458
+ return NomosPage<Map<String, dynamic>>(
2459
+ rows: _asRows(result),
2460
+ cursor: result['cursor'] as String?,
2461
+ done: result['done'] == true,
2462
+ );
2463
+ }
2401
2464
 
2402
2465
  /// A single aggregate by id (the by-id read). `keys`/`principal`: see [query].
2403
2466
  Future<List<Map<String, dynamic>>> queryById(String id,
@@ -2410,6 +2473,42 @@ class NomosBridge {
2410
2473
  if (session != null) 'session': session,
2411
2474
  }));
2412
2475
 
2476
+ /// Compare an immutable publication pin with the stable series' one compiled
2477
+ /// current pointer. This is an O(current-state) check: it neither resolves the
2478
+ /// pinned publication nor reconstructs aggregate history.
2479
+ Future<ReferenceFreshness<TVersion>>
2480
+ checkReferenceFreshness<TVersion, TSeries>(
2481
+ VersionedNomosRef<TVersion, TSeries> reference,
2482
+ ) async {
2483
+ final result = _asMap(await _call('checkRefFreshness', {
2484
+ 'refs': [
2485
+ {
2486
+ 'pinned': NmsInternalNomosRefs.toJson(reference.pinned),
2487
+ 'series': NmsInternalNomosRefs.toJson(reference.series),
2488
+ 'currentField': NmsInternalVersionedNomosRefs.currentField(reference),
2489
+ },
2490
+ ],
2491
+ }));
2492
+ final unique = result['unique'] as List?;
2493
+ final first = unique == null || unique.isEmpty
2494
+ ? const <String, Object?>{}
2495
+ : _asMap(unique.first);
2496
+ final outcome = _asMap(first['outcome']);
2497
+ final status = (outcome['status'] ?? 'unknown').toString();
2498
+ if (status == 'current') {
2499
+ return ReferenceCurrent<TVersion>(reference.pinned);
2500
+ }
2501
+ if (status == 'stale') {
2502
+ final latest = outcome['latest'];
2503
+ if (latest != null) {
2504
+ return ReferenceStale<TVersion>(
2505
+ NmsInternalNomosRefs.fromJson<TVersion>(latest),
2506
+ );
2507
+ }
2508
+ }
2509
+ return ReferenceFreshnessUnavailable<TVersion>(status);
2510
+ }
2511
+
2413
2512
  /// Verified points in this workspace's history, oldest to newest. The generated client presents these
2414
2513
  /// as `client.past.moments()`; callers pass the opaque [NomosMoment] back to `past.at(moment)` and never
2415
2514
  /// need to know that the framework uses commit identities underneath.
@@ -2591,12 +2690,13 @@ class NomosBridge {
2591
2690
  }));
2592
2691
 
2593
2692
  /// FRAMEWORK-INTERNAL business birth: derive the unborn child's owned key from its deterministic address.
2594
- Future<NomosScopeKey> nomosOwnedBirthScopeKey(
2595
- String scope, String namespace, Object? key) async {
2693
+ Future<NomosScopeKey> nomosOwnedBirthScopeKey(String scope, String namespace,
2694
+ Object? key, String recordType, String recordId) async {
2596
2695
  final out = _asMap(await _call('cryptoOwnedBirthScopeKey', {
2597
2696
  'scope': scope,
2598
2697
  'namespace': namespace,
2599
2698
  'key': key,
2699
+ 'record': {'type': recordType, 'id': recordId},
2600
2700
  }));
2601
2701
  return NomosScopeKey(
2602
2702
  scope: (out['scope'] ?? scope).toString(),
@@ -2723,14 +2823,20 @@ class NomosBridge {
2723
2823
  /// Each round arrives on [syncEvents] as `{ event:'sync', reason, result }`; socket state as `{event:'link'}`.
2724
2824
  /// Call once after [connect] — then the app never polls.
2725
2825
  Future<void> autoSync(
2726
- {int? debounceMs, int? fallbackMs, String? signalId}) async =>
2727
- await _call('autoSync', {
2728
- if (debounceMs != null) 'debounceMs': debounceMs,
2729
- if (fallbackMs != null) 'fallbackMs': fallbackMs,
2730
- if (signalId != null) 'signalId': signalId
2731
- });
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
+
2732
2836
  Future<void> stopAutoSync() async {
2733
2837
  await _call('stopAutoSync');
2838
+ final workspace = _sessionName;
2839
+ if (workspace != null) _eventPlane._autoSyncSessions.remove(workspace);
2734
2840
  }
2735
2841
 
2736
2842
  /// Background-sync telemetry: each autoSync round (`event:'sync'`) and doorbell socket state (`event:'link'`).
@@ -2876,13 +2982,15 @@ class NomosBridge {
2876
2982
  Stream<List<dynamic>> watch(String queryId,
2877
2983
  [Map<String, Object?> params = const {},
2878
2984
  String? session,
2879
- String? principal]) =>
2985
+ String? principal,
2986
+ int? intervalMs]) =>
2880
2987
  _watchStream(
2881
2988
  kind: 'query',
2882
2989
  queryId: queryId,
2883
2990
  params: params,
2884
2991
  session: session,
2885
- principal: principal);
2992
+ principal: principal,
2993
+ intervalMs: intervalMs);
2886
2994
  Stream<List<dynamic>> watchById(String id,
2887
2995
  [String? session, String? principal]) =>
2888
2996
  _watchStream(
@@ -3003,12 +3111,18 @@ class NomosBridge {
3003
3111
  final subId = 'c${++_eventPlane._seq}';
3004
3112
  late final StreamController<Map<String, Object?>> ctrl;
3005
3113
  ctrl = StreamController<Map<String, Object?>>(
3006
- onListen: () => _call('watch', {
3007
- 'subId': subId,
3008
- 'kind': 'compat',
3009
- 'requirement': requirement,
3010
- if (session != null) 'session': session,
3011
- }),
3114
+ onListen: () {
3115
+ unawaited(_call('watch', {
3116
+ 'subId': subId,
3117
+ 'kind': 'compat',
3118
+ 'requirement': requirement,
3119
+ if (session != null) 'session': session,
3120
+ }).then<void>((_) {}, onError: (Object error, StackTrace stackTrace) {
3121
+ if (!ctrl.isClosed && ctrl.hasListener) {
3122
+ ctrl.addError(error, stackTrace);
3123
+ }
3124
+ }));
3125
+ },
3012
3126
  onCancel: () {
3013
3127
  _eventPlane._compatWatches.remove(subId);
3014
3128
  // Cancellation is a local lifecycle boundary and must complete even if the runner is slow,
@@ -3029,25 +3143,35 @@ class NomosBridge {
3029
3143
  String? spatialId,
3030
3144
  NomosBbox? bbox,
3031
3145
  String? base,
3146
+ int? limit,
3032
3147
  String? session,
3033
- String? principal}) {
3148
+ String? principal,
3149
+ int? intervalMs}) {
3034
3150
  // Controllers live on the EVENT PLANE (where _onMessage dispatches); the registration `_call` stays
3035
3151
  // THIS instance's (a session's override stamps `args.session`) — see [_eventPlane].
3036
3152
  final subId = 'w${++_eventPlane._seq}';
3037
3153
  late final StreamController<List<dynamic>> ctrl;
3038
3154
  ctrl = StreamController<List<dynamic>>(
3039
- onListen: () => _call('watch', {
3040
- 'subId': subId,
3041
- 'kind': kind,
3042
- if (queryId != null) 'queryId': queryId,
3043
- if (params != null) 'params': params,
3044
- if (id != null) 'id': id,
3045
- if (spatialId != null) 'spatialId': spatialId,
3046
- if (bbox != null) 'bbox': bbox.toJson(),
3047
- if (base != null) 'base': base,
3048
- if (principal != null) 'principal': principal,
3049
- if (session != null) 'session': session,
3050
- }),
3155
+ onListen: () {
3156
+ unawaited(_call('watch', {
3157
+ 'subId': subId,
3158
+ 'kind': kind,
3159
+ if (queryId != null) 'queryId': queryId,
3160
+ if (params != null) 'params': params,
3161
+ if (id != null) 'id': id,
3162
+ if (spatialId != null) 'spatialId': spatialId,
3163
+ if (bbox != null) 'bbox': bbox.toJson(),
3164
+ if (base != null) 'base': base,
3165
+ if (limit != null) 'limit': limit,
3166
+ if (principal != null) 'principal': principal,
3167
+ if (intervalMs != null) 'intervalMs': intervalMs,
3168
+ if (session != null) 'session': session,
3169
+ }).then<void>((_) {}, onError: (Object error, StackTrace stackTrace) {
3170
+ if (!ctrl.isClosed && ctrl.hasListener) {
3171
+ ctrl.addError(error, stackTrace);
3172
+ }
3173
+ }));
3174
+ },
3051
3175
  onCancel: () {
3052
3176
  _eventPlane._watches.remove(subId);
3053
3177
  // A caller owns its local subscription; it must never need a healthy transport in order to release
@@ -3062,28 +3186,41 @@ class NomosBridge {
3062
3186
  /// Tear down: stop the transport subscription + fail any in-flight calls.
3063
3187
  Future<void> dispose() async {
3064
3188
  await _sub.cancel();
3065
- for (final entry in _pending.entries) {
3066
- final c = entry.value;
3189
+ final pending = _pending.entries
3190
+ .map((entry) => (
3191
+ completer: entry.value,
3192
+ trace: _pendingTraces[entry.key] ?? const NomosTraceContext(),
3193
+ operation: _pendingOps[entry.key],
3194
+ ))
3195
+ .toList(growable: false);
3196
+ _pending.clear();
3197
+ _pendingOps.clear();
3198
+ _pendingTraces.clear();
3199
+ for (final entry in pending) {
3200
+ final c = entry.completer;
3067
3201
  if (!c.isCompleted) {
3068
- final trace = _pendingTraces[entry.key] ?? const NomosTraceContext();
3069
- final info = NomosErrorInfo.legacy('bridge disposed',
3202
+ final operation = entry.operation ?? 'unknown';
3203
+ final info = NomosErrorInfo.legacy(
3204
+ 'bridge disposed while $operation was pending',
3070
3205
  code: 'nomos.bridge.disposed',
3071
- operation: _pendingOps[entry.key],
3072
- trace: trace);
3206
+ operation: entry.operation,
3207
+ trace: entry.trace);
3073
3208
  c.completeError(nomosErrorFromInfo(info));
3074
3209
  }
3075
3210
  }
3076
- _pending.clear();
3077
- _pendingOps.clear();
3078
- _pendingTraces.clear();
3079
- for (final w in _watches.values) {
3080
- if (!w.isClosed) await w.close();
3081
- }
3211
+ final watches = _watches.values.toList(growable: false);
3082
3212
  _watches.clear();
3083
- for (final w in _compatWatches.values) {
3084
- if (!w.isClosed) await w.close();
3213
+ for (final w in watches) {
3214
+ // A paused consumer can keep StreamController.close() pending forever.
3215
+ // Disposal is the owner boundary: detach the controller immediately and
3216
+ // let its done event complete if/when that consumer resumes.
3217
+ if (!w.isClosed) unawaited(w.close());
3085
3218
  }
3219
+ final compatWatches = _compatWatches.values.toList(growable: false);
3086
3220
  _compatWatches.clear();
3221
+ for (final w in compatWatches) {
3222
+ if (!w.isClosed) unawaited(w.close());
3223
+ }
3087
3224
  _stopStatusPoll();
3088
3225
  if (!_syncEvents.isClosed) await _syncEvents.close();
3089
3226
  if (!_signalEvents.isClosed) await _signalEvents.close();
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.0"
194
+ version: "0.8.1"
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.1
11
+ version: 0.64.3
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.0
20
+ nomos_types: ^0.8.1
21
21
  json_schema: ^5.2.2
22
22
  crypto: ^3.0.0
23
23
  package_config: ^2.1.0
@@ -20,7 +20,7 @@ import 'package:test/test.dart';
20
20
  String get _root => '${Directory.current.path}/../..';
21
21
  String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
22
22
  String get _docsDeploy =>
23
- '$_root/bench/scale/fixtures/surfaced/build/surfaced.deploy.json';
23
+ '$_root/bench/scale/fixtures/surfaced/build/surfaced.law.usdz';
24
24
  String get _runtimeDir =>
25
25
  Platform.environment['NOMOS_DART_RUNTIME'] ??
26
26
  Platform.environment['NOMOS_OFFLINE_RUNTIME'] ??
@@ -34,7 +34,8 @@ String? _missingPrereq() {
34
34
  '(cd bench/scale/fixtures/surfaced && node ../../../../dsl/compile_package.mjs)';
35
35
  }
36
36
  if (!File('$_runtimeDir/holon-testmint.wasm').existsSync() ||
37
- !File('$_runtimeDir/packages.json').existsSync()) {
37
+ !File('$_runtimeDir/bootstrap.law.usdz').existsSync() ||
38
+ !File('$_runtimeDir/nomos.law.usdz').existsSync()) {
38
39
  return 'warm ~/.holon/runtime first (run any `githolon` command once)';
39
40
  }
40
41
  return null;
@@ -65,7 +66,7 @@ void main() {
65
66
  return;
66
67
  }
67
68
  final harness = await NomosTestHarness.ephemeral(
68
- deployJsonPaths: [_docsDeploy],
69
+ lawUsdzPaths: [_docsDeploy],
69
70
  workspace: 'acquire-home',
70
71
  nodeEntry: _repoEntry,
71
72
  runtimeDir: _runtimeDir,
@@ -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');